18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/* SF16-FMR2 and SF16-FMD2 radio driver for Linux
38c2ecf20Sopenharmony_ci * Copyright (c) 2011 Ondrej Zary
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Original driver was (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com
68c2ecf20Sopenharmony_ci * but almost nothing remained here after conversion to generic TEA575x
78c2ecf20Sopenharmony_ci * implementation
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/delay.h>
118c2ecf20Sopenharmony_ci#include <linux/module.h>	/* Modules			*/
128c2ecf20Sopenharmony_ci#include <linux/init.h>		/* Initdata			*/
138c2ecf20Sopenharmony_ci#include <linux/slab.h>
148c2ecf20Sopenharmony_ci#include <linux/ioport.h>	/* request_region		*/
158c2ecf20Sopenharmony_ci#include <linux/io.h>		/* outb, outb_p			*/
168c2ecf20Sopenharmony_ci#include <linux/isa.h>
178c2ecf20Sopenharmony_ci#include <linux/pnp.h>
188c2ecf20Sopenharmony_ci#include <media/drv-intf/tea575x.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ondrej Zary");
218c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("MediaForte SF16-FMR2 and SF16-FMD2 FM radio card driver");
228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/* these cards can only use two different ports (0x384 and 0x284) */
258c2ecf20Sopenharmony_ci#define FMR2_MAX 2
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_cistatic int radio_nr[FMR2_MAX] = { [0 ... (FMR2_MAX - 1)] = -1 };
288c2ecf20Sopenharmony_cimodule_param_array(radio_nr, int, NULL, 0444);
298c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Radio device numbers");
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistruct fmr2 {
328c2ecf20Sopenharmony_ci	int io;
338c2ecf20Sopenharmony_ci	struct v4l2_device v4l2_dev;
348c2ecf20Sopenharmony_ci	struct snd_tea575x tea;
358c2ecf20Sopenharmony_ci	struct v4l2_ctrl *volume;
368c2ecf20Sopenharmony_ci	struct v4l2_ctrl *balance;
378c2ecf20Sopenharmony_ci	bool is_fmd2;
388c2ecf20Sopenharmony_ci};
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_cistatic int num_fmr2_cards;
418c2ecf20Sopenharmony_cistatic struct fmr2 *fmr2_cards[FMR2_MAX];
428c2ecf20Sopenharmony_cistatic bool isa_registered;
438c2ecf20Sopenharmony_cistatic bool pnp_registered;
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* the port is hardwired on SF16-FMR2 */
468c2ecf20Sopenharmony_ci#define FMR2_PORT	0x384
478c2ecf20Sopenharmony_ci
488c2ecf20Sopenharmony_ci/* TEA575x tuner pins */
498c2ecf20Sopenharmony_ci#define STR_DATA	(1 << 0)
508c2ecf20Sopenharmony_ci#define STR_CLK		(1 << 1)
518c2ecf20Sopenharmony_ci#define STR_WREN	(1 << 2)
528c2ecf20Sopenharmony_ci#define STR_MOST	(1 << 3)
538c2ecf20Sopenharmony_ci/* PT2254A/TC9154A volume control pins */
548c2ecf20Sopenharmony_ci#define PT_ST		(1 << 4)
558c2ecf20Sopenharmony_ci#define PT_CK		(1 << 5)
568c2ecf20Sopenharmony_ci#define PT_DATA		(1 << 6)
578c2ecf20Sopenharmony_ci/* volume control presence pin */
588c2ecf20Sopenharmony_ci#define FMR2_HASVOL	(1 << 7)
598c2ecf20Sopenharmony_ci
608c2ecf20Sopenharmony_cistatic void fmr2_tea575x_set_pins(struct snd_tea575x *tea, u8 pins)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	struct fmr2 *fmr2 = tea->private_data;
638c2ecf20Sopenharmony_ci	u8 bits = 0;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ci	bits |= (pins & TEA575X_DATA) ? STR_DATA : 0;
668c2ecf20Sopenharmony_ci	bits |= (pins & TEA575X_CLK)  ? STR_CLK  : 0;
678c2ecf20Sopenharmony_ci	/* WRITE_ENABLE is inverted, DATA must be high during read */
688c2ecf20Sopenharmony_ci	bits |= (pins & TEA575X_WREN) ? 0 : STR_WREN | STR_DATA;
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci	outb(bits, fmr2->io);
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic u8 fmr2_tea575x_get_pins(struct snd_tea575x *tea)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	struct fmr2 *fmr2 = tea->private_data;
768c2ecf20Sopenharmony_ci	u8 bits = inb(fmr2->io);
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci	return  ((bits & STR_DATA) ? TEA575X_DATA : 0) |
798c2ecf20Sopenharmony_ci		((bits & STR_MOST) ? TEA575X_MOST : 0);
808c2ecf20Sopenharmony_ci}
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_cistatic void fmr2_tea575x_set_direction(struct snd_tea575x *tea, bool output)
838c2ecf20Sopenharmony_ci{
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ci
868c2ecf20Sopenharmony_cistatic const struct snd_tea575x_ops fmr2_tea_ops = {
878c2ecf20Sopenharmony_ci	.set_pins = fmr2_tea575x_set_pins,
888c2ecf20Sopenharmony_ci	.get_pins = fmr2_tea575x_get_pins,
898c2ecf20Sopenharmony_ci	.set_direction = fmr2_tea575x_set_direction,
908c2ecf20Sopenharmony_ci};
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci/* TC9154A/PT2254A volume control */
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci/* 18-bit shift register bit definitions */
958c2ecf20Sopenharmony_ci#define TC9154A_ATT_MAJ_0DB	(1 << 0)
968c2ecf20Sopenharmony_ci#define TC9154A_ATT_MAJ_10DB	(1 << 1)
978c2ecf20Sopenharmony_ci#define TC9154A_ATT_MAJ_20DB	(1 << 2)
988c2ecf20Sopenharmony_ci#define TC9154A_ATT_MAJ_30DB	(1 << 3)
998c2ecf20Sopenharmony_ci#define TC9154A_ATT_MAJ_40DB	(1 << 4)
1008c2ecf20Sopenharmony_ci#define TC9154A_ATT_MAJ_50DB	(1 << 5)
1018c2ecf20Sopenharmony_ci#define TC9154A_ATT_MAJ_60DB	(1 << 6)
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci#define TC9154A_ATT_MIN_0DB	(1 << 7)
1048c2ecf20Sopenharmony_ci#define TC9154A_ATT_MIN_2DB	(1 << 8)
1058c2ecf20Sopenharmony_ci#define TC9154A_ATT_MIN_4DB	(1 << 9)
1068c2ecf20Sopenharmony_ci#define TC9154A_ATT_MIN_6DB	(1 << 10)
1078c2ecf20Sopenharmony_ci#define TC9154A_ATT_MIN_8DB	(1 << 11)
1088c2ecf20Sopenharmony_ci/* bit 12 is ignored */
1098c2ecf20Sopenharmony_ci#define TC9154A_CHANNEL_LEFT	(1 << 13)
1108c2ecf20Sopenharmony_ci#define TC9154A_CHANNEL_RIGHT	(1 << 14)
1118c2ecf20Sopenharmony_ci/* bits 15, 16, 17 must be 0 */
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_ci#define	TC9154A_ATT_MAJ(x)	(1 << x)
1148c2ecf20Sopenharmony_ci#define TC9154A_ATT_MIN(x)	(1 << (7 + x))
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic void tc9154a_set_pins(struct fmr2 *fmr2, u8 pins)
1178c2ecf20Sopenharmony_ci{
1188c2ecf20Sopenharmony_ci	if (!fmr2->tea.mute)
1198c2ecf20Sopenharmony_ci		pins |= STR_WREN;
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	outb(pins, fmr2->io);
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic void tc9154a_set_attenuation(struct fmr2 *fmr2, int att, u32 channel)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	int i;
1278c2ecf20Sopenharmony_ci	u32 reg;
1288c2ecf20Sopenharmony_ci	u8 bit;
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	reg = TC9154A_ATT_MAJ(att / 10) | TC9154A_ATT_MIN((att % 10) / 2);
1318c2ecf20Sopenharmony_ci	reg |= channel;
1328c2ecf20Sopenharmony_ci	/* write 18-bit shift register, LSB first */
1338c2ecf20Sopenharmony_ci	for (i = 0; i < 18; i++) {
1348c2ecf20Sopenharmony_ci		bit = reg & (1 << i) ? PT_DATA : 0;
1358c2ecf20Sopenharmony_ci		tc9154a_set_pins(fmr2, bit);
1368c2ecf20Sopenharmony_ci		udelay(5);
1378c2ecf20Sopenharmony_ci		tc9154a_set_pins(fmr2, bit | PT_CK);
1388c2ecf20Sopenharmony_ci		udelay(5);
1398c2ecf20Sopenharmony_ci		tc9154a_set_pins(fmr2, bit);
1408c2ecf20Sopenharmony_ci	}
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	/* latch register data */
1438c2ecf20Sopenharmony_ci	udelay(5);
1448c2ecf20Sopenharmony_ci	tc9154a_set_pins(fmr2, PT_ST);
1458c2ecf20Sopenharmony_ci	udelay(5);
1468c2ecf20Sopenharmony_ci	tc9154a_set_pins(fmr2, 0);
1478c2ecf20Sopenharmony_ci}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cistatic int fmr2_s_ctrl(struct v4l2_ctrl *ctrl)
1508c2ecf20Sopenharmony_ci{
1518c2ecf20Sopenharmony_ci	struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);
1528c2ecf20Sopenharmony_ci	struct fmr2 *fmr2 = tea->private_data;
1538c2ecf20Sopenharmony_ci	int volume, balance, left, right;
1548c2ecf20Sopenharmony_ci
1558c2ecf20Sopenharmony_ci	switch (ctrl->id) {
1568c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_VOLUME:
1578c2ecf20Sopenharmony_ci		volume = ctrl->val;
1588c2ecf20Sopenharmony_ci		balance = fmr2->balance->cur.val;
1598c2ecf20Sopenharmony_ci		break;
1608c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_BALANCE:
1618c2ecf20Sopenharmony_ci		balance = ctrl->val;
1628c2ecf20Sopenharmony_ci		volume = fmr2->volume->cur.val;
1638c2ecf20Sopenharmony_ci		break;
1648c2ecf20Sopenharmony_ci	default:
1658c2ecf20Sopenharmony_ci		return -EINVAL;
1668c2ecf20Sopenharmony_ci	}
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	left = right = volume;
1698c2ecf20Sopenharmony_ci	if (balance < 0)
1708c2ecf20Sopenharmony_ci		right = max(0, right + balance);
1718c2ecf20Sopenharmony_ci	if (balance > 0)
1728c2ecf20Sopenharmony_ci		left = max(0, left - balance);
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	tc9154a_set_attenuation(fmr2, abs(left - 68), TC9154A_CHANNEL_LEFT);
1758c2ecf20Sopenharmony_ci	tc9154a_set_attenuation(fmr2, abs(right - 68), TC9154A_CHANNEL_RIGHT);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	return 0;
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops fmr2_ctrl_ops = {
1818c2ecf20Sopenharmony_ci	.s_ctrl = fmr2_s_ctrl,
1828c2ecf20Sopenharmony_ci};
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_cistatic int fmr2_tea_ext_init(struct snd_tea575x *tea)
1858c2ecf20Sopenharmony_ci{
1868c2ecf20Sopenharmony_ci	struct fmr2 *fmr2 = tea->private_data;
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ci	/* FMR2 can have volume control, FMD2 can't (uses SB16 mixer) */
1898c2ecf20Sopenharmony_ci	if (!fmr2->is_fmd2 && inb(fmr2->io) & FMR2_HASVOL) {
1908c2ecf20Sopenharmony_ci		fmr2->volume = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_VOLUME, 0, 68, 2, 56);
1918c2ecf20Sopenharmony_ci		fmr2->balance = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_BALANCE, -68, 68, 2, 0);
1928c2ecf20Sopenharmony_ci		if (tea->ctrl_handler.error) {
1938c2ecf20Sopenharmony_ci			printk(KERN_ERR "radio-sf16fmr2: can't initialize controls\n");
1948c2ecf20Sopenharmony_ci			return tea->ctrl_handler.error;
1958c2ecf20Sopenharmony_ci		}
1968c2ecf20Sopenharmony_ci	}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	return 0;
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_cistatic const struct pnp_device_id fmr2_pnp_ids[] = {
2028c2ecf20Sopenharmony_ci	{ .id = "MFRad13" }, /* tuner subdevice of SF16-FMD2 */
2038c2ecf20Sopenharmony_ci	{ .id = "" }
2048c2ecf20Sopenharmony_ci};
2058c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(pnp, fmr2_pnp_ids);
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_cistatic int fmr2_probe(struct fmr2 *fmr2, struct device *pdev, int io)
2088c2ecf20Sopenharmony_ci{
2098c2ecf20Sopenharmony_ci	int err, i;
2108c2ecf20Sopenharmony_ci	char *card_name = fmr2->is_fmd2 ? "SF16-FMD2" : "SF16-FMR2";
2118c2ecf20Sopenharmony_ci
2128c2ecf20Sopenharmony_ci	/* avoid errors if a card was already registered at given port */
2138c2ecf20Sopenharmony_ci	for (i = 0; i < num_fmr2_cards; i++)
2148c2ecf20Sopenharmony_ci		if (io == fmr2_cards[i]->io)
2158c2ecf20Sopenharmony_ci			return -EBUSY;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	strscpy(fmr2->v4l2_dev.name, "radio-sf16fmr2",
2188c2ecf20Sopenharmony_ci		sizeof(fmr2->v4l2_dev.name)),
2198c2ecf20Sopenharmony_ci	fmr2->io = io;
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	if (!request_region(fmr2->io, 2, fmr2->v4l2_dev.name)) {
2228c2ecf20Sopenharmony_ci		printk(KERN_ERR "radio-sf16fmr2: I/O port 0x%x already in use\n", fmr2->io);
2238c2ecf20Sopenharmony_ci		return -EBUSY;
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	dev_set_drvdata(pdev, fmr2);
2278c2ecf20Sopenharmony_ci	err = v4l2_device_register(pdev, &fmr2->v4l2_dev);
2288c2ecf20Sopenharmony_ci	if (err < 0) {
2298c2ecf20Sopenharmony_ci		v4l2_err(&fmr2->v4l2_dev, "Could not register v4l2_device\n");
2308c2ecf20Sopenharmony_ci		release_region(fmr2->io, 2);
2318c2ecf20Sopenharmony_ci		return err;
2328c2ecf20Sopenharmony_ci	}
2338c2ecf20Sopenharmony_ci	fmr2->tea.v4l2_dev = &fmr2->v4l2_dev;
2348c2ecf20Sopenharmony_ci	fmr2->tea.private_data = fmr2;
2358c2ecf20Sopenharmony_ci	fmr2->tea.radio_nr = radio_nr[num_fmr2_cards];
2368c2ecf20Sopenharmony_ci	fmr2->tea.ops = &fmr2_tea_ops;
2378c2ecf20Sopenharmony_ci	fmr2->tea.ext_init = fmr2_tea_ext_init;
2388c2ecf20Sopenharmony_ci	strscpy(fmr2->tea.card, card_name, sizeof(fmr2->tea.card));
2398c2ecf20Sopenharmony_ci	snprintf(fmr2->tea.bus_info, sizeof(fmr2->tea.bus_info), "%s:%s",
2408c2ecf20Sopenharmony_ci			fmr2->is_fmd2 ? "PnP" : "ISA", dev_name(pdev));
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	if (snd_tea575x_init(&fmr2->tea, THIS_MODULE)) {
2438c2ecf20Sopenharmony_ci		printk(KERN_ERR "radio-sf16fmr2: Unable to detect TEA575x tuner\n");
2448c2ecf20Sopenharmony_ci		release_region(fmr2->io, 2);
2458c2ecf20Sopenharmony_ci		return -ENODEV;
2468c2ecf20Sopenharmony_ci	}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	printk(KERN_INFO "radio-sf16fmr2: %s radio card at 0x%x.\n",
2498c2ecf20Sopenharmony_ci			card_name, fmr2->io);
2508c2ecf20Sopenharmony_ci	return 0;
2518c2ecf20Sopenharmony_ci}
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_cistatic int fmr2_isa_match(struct device *pdev, unsigned int ndev)
2548c2ecf20Sopenharmony_ci{
2558c2ecf20Sopenharmony_ci	struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL);
2568c2ecf20Sopenharmony_ci	if (!fmr2)
2578c2ecf20Sopenharmony_ci		return 0;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (fmr2_probe(fmr2, pdev, FMR2_PORT)) {
2608c2ecf20Sopenharmony_ci		kfree(fmr2);
2618c2ecf20Sopenharmony_ci		return 0;
2628c2ecf20Sopenharmony_ci	}
2638c2ecf20Sopenharmony_ci	dev_set_drvdata(pdev, fmr2);
2648c2ecf20Sopenharmony_ci	fmr2_cards[num_fmr2_cards++] = fmr2;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci	return 1;
2678c2ecf20Sopenharmony_ci}
2688c2ecf20Sopenharmony_ci
2698c2ecf20Sopenharmony_cistatic int fmr2_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id)
2708c2ecf20Sopenharmony_ci{
2718c2ecf20Sopenharmony_ci	int ret;
2728c2ecf20Sopenharmony_ci	struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL);
2738c2ecf20Sopenharmony_ci	if (!fmr2)
2748c2ecf20Sopenharmony_ci		return -ENOMEM;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	fmr2->is_fmd2 = true;
2778c2ecf20Sopenharmony_ci	ret = fmr2_probe(fmr2, &pdev->dev, pnp_port_start(pdev, 0));
2788c2ecf20Sopenharmony_ci	if (ret) {
2798c2ecf20Sopenharmony_ci		kfree(fmr2);
2808c2ecf20Sopenharmony_ci		return ret;
2818c2ecf20Sopenharmony_ci	}
2828c2ecf20Sopenharmony_ci	pnp_set_drvdata(pdev, fmr2);
2838c2ecf20Sopenharmony_ci	fmr2_cards[num_fmr2_cards++] = fmr2;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	return 0;
2868c2ecf20Sopenharmony_ci}
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_cistatic void fmr2_remove(struct fmr2 *fmr2)
2898c2ecf20Sopenharmony_ci{
2908c2ecf20Sopenharmony_ci	snd_tea575x_exit(&fmr2->tea);
2918c2ecf20Sopenharmony_ci	release_region(fmr2->io, 2);
2928c2ecf20Sopenharmony_ci	v4l2_device_unregister(&fmr2->v4l2_dev);
2938c2ecf20Sopenharmony_ci	kfree(fmr2);
2948c2ecf20Sopenharmony_ci}
2958c2ecf20Sopenharmony_ci
2968c2ecf20Sopenharmony_cistatic int fmr2_isa_remove(struct device *pdev, unsigned int ndev)
2978c2ecf20Sopenharmony_ci{
2988c2ecf20Sopenharmony_ci	fmr2_remove(dev_get_drvdata(pdev));
2998c2ecf20Sopenharmony_ci
3008c2ecf20Sopenharmony_ci	return 0;
3018c2ecf20Sopenharmony_ci}
3028c2ecf20Sopenharmony_ci
3038c2ecf20Sopenharmony_cistatic void fmr2_pnp_remove(struct pnp_dev *pdev)
3048c2ecf20Sopenharmony_ci{
3058c2ecf20Sopenharmony_ci	fmr2_remove(pnp_get_drvdata(pdev));
3068c2ecf20Sopenharmony_ci	pnp_set_drvdata(pdev, NULL);
3078c2ecf20Sopenharmony_ci}
3088c2ecf20Sopenharmony_ci
3098c2ecf20Sopenharmony_cistatic struct isa_driver fmr2_isa_driver = {
3108c2ecf20Sopenharmony_ci	.match		= fmr2_isa_match,
3118c2ecf20Sopenharmony_ci	.remove		= fmr2_isa_remove,
3128c2ecf20Sopenharmony_ci	.driver		= {
3138c2ecf20Sopenharmony_ci		.name	= "radio-sf16fmr2",
3148c2ecf20Sopenharmony_ci	},
3158c2ecf20Sopenharmony_ci};
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_cistatic struct pnp_driver fmr2_pnp_driver = {
3188c2ecf20Sopenharmony_ci	.name		= "radio-sf16fmr2",
3198c2ecf20Sopenharmony_ci	.id_table	= fmr2_pnp_ids,
3208c2ecf20Sopenharmony_ci	.probe		= fmr2_pnp_probe,
3218c2ecf20Sopenharmony_ci	.remove		= fmr2_pnp_remove,
3228c2ecf20Sopenharmony_ci};
3238c2ecf20Sopenharmony_ci
3248c2ecf20Sopenharmony_cistatic int __init fmr2_init(void)
3258c2ecf20Sopenharmony_ci{
3268c2ecf20Sopenharmony_ci	int ret;
3278c2ecf20Sopenharmony_ci
3288c2ecf20Sopenharmony_ci	ret = pnp_register_driver(&fmr2_pnp_driver);
3298c2ecf20Sopenharmony_ci	if (!ret)
3308c2ecf20Sopenharmony_ci		pnp_registered = true;
3318c2ecf20Sopenharmony_ci	ret = isa_register_driver(&fmr2_isa_driver, 1);
3328c2ecf20Sopenharmony_ci	if (!ret)
3338c2ecf20Sopenharmony_ci		isa_registered = true;
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	return (pnp_registered || isa_registered) ? 0 : ret;
3368c2ecf20Sopenharmony_ci}
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_cistatic void __exit fmr2_exit(void)
3398c2ecf20Sopenharmony_ci{
3408c2ecf20Sopenharmony_ci	if (pnp_registered)
3418c2ecf20Sopenharmony_ci		pnp_unregister_driver(&fmr2_pnp_driver);
3428c2ecf20Sopenharmony_ci	if (isa_registered)
3438c2ecf20Sopenharmony_ci		isa_unregister_driver(&fmr2_isa_driver);
3448c2ecf20Sopenharmony_ci}
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_cimodule_init(fmr2_init);
3478c2ecf20Sopenharmony_cimodule_exit(fmr2_exit);
348