1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /* SF16-FMR2 and SF16-FMD2 radio driver for Linux
3*4882a593Smuzhiyun * Copyright (c) 2011 Ondrej Zary
4*4882a593Smuzhiyun *
5*4882a593Smuzhiyun * Original driver was (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com
6*4882a593Smuzhiyun * but almost nothing remained here after conversion to generic TEA575x
7*4882a593Smuzhiyun * implementation
8*4882a593Smuzhiyun */
9*4882a593Smuzhiyun
10*4882a593Smuzhiyun #include <linux/delay.h>
11*4882a593Smuzhiyun #include <linux/module.h> /* Modules */
12*4882a593Smuzhiyun #include <linux/init.h> /* Initdata */
13*4882a593Smuzhiyun #include <linux/slab.h>
14*4882a593Smuzhiyun #include <linux/ioport.h> /* request_region */
15*4882a593Smuzhiyun #include <linux/io.h> /* outb, outb_p */
16*4882a593Smuzhiyun #include <linux/isa.h>
17*4882a593Smuzhiyun #include <linux/pnp.h>
18*4882a593Smuzhiyun #include <media/drv-intf/tea575x.h>
19*4882a593Smuzhiyun
20*4882a593Smuzhiyun MODULE_AUTHOR("Ondrej Zary");
21*4882a593Smuzhiyun MODULE_DESCRIPTION("MediaForte SF16-FMR2 and SF16-FMD2 FM radio card driver");
22*4882a593Smuzhiyun MODULE_LICENSE("GPL");
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun /* these cards can only use two different ports (0x384 and 0x284) */
25*4882a593Smuzhiyun #define FMR2_MAX 2
26*4882a593Smuzhiyun
27*4882a593Smuzhiyun static int radio_nr[FMR2_MAX] = { [0 ... (FMR2_MAX - 1)] = -1 };
28*4882a593Smuzhiyun module_param_array(radio_nr, int, NULL, 0444);
29*4882a593Smuzhiyun MODULE_PARM_DESC(radio_nr, "Radio device numbers");
30*4882a593Smuzhiyun
31*4882a593Smuzhiyun struct fmr2 {
32*4882a593Smuzhiyun int io;
33*4882a593Smuzhiyun struct v4l2_device v4l2_dev;
34*4882a593Smuzhiyun struct snd_tea575x tea;
35*4882a593Smuzhiyun struct v4l2_ctrl *volume;
36*4882a593Smuzhiyun struct v4l2_ctrl *balance;
37*4882a593Smuzhiyun bool is_fmd2;
38*4882a593Smuzhiyun };
39*4882a593Smuzhiyun
40*4882a593Smuzhiyun static int num_fmr2_cards;
41*4882a593Smuzhiyun static struct fmr2 *fmr2_cards[FMR2_MAX];
42*4882a593Smuzhiyun static bool isa_registered;
43*4882a593Smuzhiyun static bool pnp_registered;
44*4882a593Smuzhiyun
45*4882a593Smuzhiyun /* the port is hardwired on SF16-FMR2 */
46*4882a593Smuzhiyun #define FMR2_PORT 0x384
47*4882a593Smuzhiyun
48*4882a593Smuzhiyun /* TEA575x tuner pins */
49*4882a593Smuzhiyun #define STR_DATA (1 << 0)
50*4882a593Smuzhiyun #define STR_CLK (1 << 1)
51*4882a593Smuzhiyun #define STR_WREN (1 << 2)
52*4882a593Smuzhiyun #define STR_MOST (1 << 3)
53*4882a593Smuzhiyun /* PT2254A/TC9154A volume control pins */
54*4882a593Smuzhiyun #define PT_ST (1 << 4)
55*4882a593Smuzhiyun #define PT_CK (1 << 5)
56*4882a593Smuzhiyun #define PT_DATA (1 << 6)
57*4882a593Smuzhiyun /* volume control presence pin */
58*4882a593Smuzhiyun #define FMR2_HASVOL (1 << 7)
59*4882a593Smuzhiyun
fmr2_tea575x_set_pins(struct snd_tea575x * tea,u8 pins)60*4882a593Smuzhiyun static void fmr2_tea575x_set_pins(struct snd_tea575x *tea, u8 pins)
61*4882a593Smuzhiyun {
62*4882a593Smuzhiyun struct fmr2 *fmr2 = tea->private_data;
63*4882a593Smuzhiyun u8 bits = 0;
64*4882a593Smuzhiyun
65*4882a593Smuzhiyun bits |= (pins & TEA575X_DATA) ? STR_DATA : 0;
66*4882a593Smuzhiyun bits |= (pins & TEA575X_CLK) ? STR_CLK : 0;
67*4882a593Smuzhiyun /* WRITE_ENABLE is inverted, DATA must be high during read */
68*4882a593Smuzhiyun bits |= (pins & TEA575X_WREN) ? 0 : STR_WREN | STR_DATA;
69*4882a593Smuzhiyun
70*4882a593Smuzhiyun outb(bits, fmr2->io);
71*4882a593Smuzhiyun }
72*4882a593Smuzhiyun
fmr2_tea575x_get_pins(struct snd_tea575x * tea)73*4882a593Smuzhiyun static u8 fmr2_tea575x_get_pins(struct snd_tea575x *tea)
74*4882a593Smuzhiyun {
75*4882a593Smuzhiyun struct fmr2 *fmr2 = tea->private_data;
76*4882a593Smuzhiyun u8 bits = inb(fmr2->io);
77*4882a593Smuzhiyun
78*4882a593Smuzhiyun return ((bits & STR_DATA) ? TEA575X_DATA : 0) |
79*4882a593Smuzhiyun ((bits & STR_MOST) ? TEA575X_MOST : 0);
80*4882a593Smuzhiyun }
81*4882a593Smuzhiyun
fmr2_tea575x_set_direction(struct snd_tea575x * tea,bool output)82*4882a593Smuzhiyun static void fmr2_tea575x_set_direction(struct snd_tea575x *tea, bool output)
83*4882a593Smuzhiyun {
84*4882a593Smuzhiyun }
85*4882a593Smuzhiyun
86*4882a593Smuzhiyun static const struct snd_tea575x_ops fmr2_tea_ops = {
87*4882a593Smuzhiyun .set_pins = fmr2_tea575x_set_pins,
88*4882a593Smuzhiyun .get_pins = fmr2_tea575x_get_pins,
89*4882a593Smuzhiyun .set_direction = fmr2_tea575x_set_direction,
90*4882a593Smuzhiyun };
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun /* TC9154A/PT2254A volume control */
93*4882a593Smuzhiyun
94*4882a593Smuzhiyun /* 18-bit shift register bit definitions */
95*4882a593Smuzhiyun #define TC9154A_ATT_MAJ_0DB (1 << 0)
96*4882a593Smuzhiyun #define TC9154A_ATT_MAJ_10DB (1 << 1)
97*4882a593Smuzhiyun #define TC9154A_ATT_MAJ_20DB (1 << 2)
98*4882a593Smuzhiyun #define TC9154A_ATT_MAJ_30DB (1 << 3)
99*4882a593Smuzhiyun #define TC9154A_ATT_MAJ_40DB (1 << 4)
100*4882a593Smuzhiyun #define TC9154A_ATT_MAJ_50DB (1 << 5)
101*4882a593Smuzhiyun #define TC9154A_ATT_MAJ_60DB (1 << 6)
102*4882a593Smuzhiyun
103*4882a593Smuzhiyun #define TC9154A_ATT_MIN_0DB (1 << 7)
104*4882a593Smuzhiyun #define TC9154A_ATT_MIN_2DB (1 << 8)
105*4882a593Smuzhiyun #define TC9154A_ATT_MIN_4DB (1 << 9)
106*4882a593Smuzhiyun #define TC9154A_ATT_MIN_6DB (1 << 10)
107*4882a593Smuzhiyun #define TC9154A_ATT_MIN_8DB (1 << 11)
108*4882a593Smuzhiyun /* bit 12 is ignored */
109*4882a593Smuzhiyun #define TC9154A_CHANNEL_LEFT (1 << 13)
110*4882a593Smuzhiyun #define TC9154A_CHANNEL_RIGHT (1 << 14)
111*4882a593Smuzhiyun /* bits 15, 16, 17 must be 0 */
112*4882a593Smuzhiyun
113*4882a593Smuzhiyun #define TC9154A_ATT_MAJ(x) (1 << x)
114*4882a593Smuzhiyun #define TC9154A_ATT_MIN(x) (1 << (7 + x))
115*4882a593Smuzhiyun
tc9154a_set_pins(struct fmr2 * fmr2,u8 pins)116*4882a593Smuzhiyun static void tc9154a_set_pins(struct fmr2 *fmr2, u8 pins)
117*4882a593Smuzhiyun {
118*4882a593Smuzhiyun if (!fmr2->tea.mute)
119*4882a593Smuzhiyun pins |= STR_WREN;
120*4882a593Smuzhiyun
121*4882a593Smuzhiyun outb(pins, fmr2->io);
122*4882a593Smuzhiyun }
123*4882a593Smuzhiyun
tc9154a_set_attenuation(struct fmr2 * fmr2,int att,u32 channel)124*4882a593Smuzhiyun static void tc9154a_set_attenuation(struct fmr2 *fmr2, int att, u32 channel)
125*4882a593Smuzhiyun {
126*4882a593Smuzhiyun int i;
127*4882a593Smuzhiyun u32 reg;
128*4882a593Smuzhiyun u8 bit;
129*4882a593Smuzhiyun
130*4882a593Smuzhiyun reg = TC9154A_ATT_MAJ(att / 10) | TC9154A_ATT_MIN((att % 10) / 2);
131*4882a593Smuzhiyun reg |= channel;
132*4882a593Smuzhiyun /* write 18-bit shift register, LSB first */
133*4882a593Smuzhiyun for (i = 0; i < 18; i++) {
134*4882a593Smuzhiyun bit = reg & (1 << i) ? PT_DATA : 0;
135*4882a593Smuzhiyun tc9154a_set_pins(fmr2, bit);
136*4882a593Smuzhiyun udelay(5);
137*4882a593Smuzhiyun tc9154a_set_pins(fmr2, bit | PT_CK);
138*4882a593Smuzhiyun udelay(5);
139*4882a593Smuzhiyun tc9154a_set_pins(fmr2, bit);
140*4882a593Smuzhiyun }
141*4882a593Smuzhiyun
142*4882a593Smuzhiyun /* latch register data */
143*4882a593Smuzhiyun udelay(5);
144*4882a593Smuzhiyun tc9154a_set_pins(fmr2, PT_ST);
145*4882a593Smuzhiyun udelay(5);
146*4882a593Smuzhiyun tc9154a_set_pins(fmr2, 0);
147*4882a593Smuzhiyun }
148*4882a593Smuzhiyun
fmr2_s_ctrl(struct v4l2_ctrl * ctrl)149*4882a593Smuzhiyun static int fmr2_s_ctrl(struct v4l2_ctrl *ctrl)
150*4882a593Smuzhiyun {
151*4882a593Smuzhiyun struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler);
152*4882a593Smuzhiyun struct fmr2 *fmr2 = tea->private_data;
153*4882a593Smuzhiyun int volume, balance, left, right;
154*4882a593Smuzhiyun
155*4882a593Smuzhiyun switch (ctrl->id) {
156*4882a593Smuzhiyun case V4L2_CID_AUDIO_VOLUME:
157*4882a593Smuzhiyun volume = ctrl->val;
158*4882a593Smuzhiyun balance = fmr2->balance->cur.val;
159*4882a593Smuzhiyun break;
160*4882a593Smuzhiyun case V4L2_CID_AUDIO_BALANCE:
161*4882a593Smuzhiyun balance = ctrl->val;
162*4882a593Smuzhiyun volume = fmr2->volume->cur.val;
163*4882a593Smuzhiyun break;
164*4882a593Smuzhiyun default:
165*4882a593Smuzhiyun return -EINVAL;
166*4882a593Smuzhiyun }
167*4882a593Smuzhiyun
168*4882a593Smuzhiyun left = right = volume;
169*4882a593Smuzhiyun if (balance < 0)
170*4882a593Smuzhiyun right = max(0, right + balance);
171*4882a593Smuzhiyun if (balance > 0)
172*4882a593Smuzhiyun left = max(0, left - balance);
173*4882a593Smuzhiyun
174*4882a593Smuzhiyun tc9154a_set_attenuation(fmr2, abs(left - 68), TC9154A_CHANNEL_LEFT);
175*4882a593Smuzhiyun tc9154a_set_attenuation(fmr2, abs(right - 68), TC9154A_CHANNEL_RIGHT);
176*4882a593Smuzhiyun
177*4882a593Smuzhiyun return 0;
178*4882a593Smuzhiyun }
179*4882a593Smuzhiyun
180*4882a593Smuzhiyun static const struct v4l2_ctrl_ops fmr2_ctrl_ops = {
181*4882a593Smuzhiyun .s_ctrl = fmr2_s_ctrl,
182*4882a593Smuzhiyun };
183*4882a593Smuzhiyun
fmr2_tea_ext_init(struct snd_tea575x * tea)184*4882a593Smuzhiyun static int fmr2_tea_ext_init(struct snd_tea575x *tea)
185*4882a593Smuzhiyun {
186*4882a593Smuzhiyun struct fmr2 *fmr2 = tea->private_data;
187*4882a593Smuzhiyun
188*4882a593Smuzhiyun /* FMR2 can have volume control, FMD2 can't (uses SB16 mixer) */
189*4882a593Smuzhiyun if (!fmr2->is_fmd2 && inb(fmr2->io) & FMR2_HASVOL) {
190*4882a593Smuzhiyun fmr2->volume = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_VOLUME, 0, 68, 2, 56);
191*4882a593Smuzhiyun fmr2->balance = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_BALANCE, -68, 68, 2, 0);
192*4882a593Smuzhiyun if (tea->ctrl_handler.error) {
193*4882a593Smuzhiyun printk(KERN_ERR "radio-sf16fmr2: can't initialize controls\n");
194*4882a593Smuzhiyun return tea->ctrl_handler.error;
195*4882a593Smuzhiyun }
196*4882a593Smuzhiyun }
197*4882a593Smuzhiyun
198*4882a593Smuzhiyun return 0;
199*4882a593Smuzhiyun }
200*4882a593Smuzhiyun
201*4882a593Smuzhiyun static const struct pnp_device_id fmr2_pnp_ids[] = {
202*4882a593Smuzhiyun { .id = "MFRad13" }, /* tuner subdevice of SF16-FMD2 */
203*4882a593Smuzhiyun { .id = "" }
204*4882a593Smuzhiyun };
205*4882a593Smuzhiyun MODULE_DEVICE_TABLE(pnp, fmr2_pnp_ids);
206*4882a593Smuzhiyun
fmr2_probe(struct fmr2 * fmr2,struct device * pdev,int io)207*4882a593Smuzhiyun static int fmr2_probe(struct fmr2 *fmr2, struct device *pdev, int io)
208*4882a593Smuzhiyun {
209*4882a593Smuzhiyun int err, i;
210*4882a593Smuzhiyun char *card_name = fmr2->is_fmd2 ? "SF16-FMD2" : "SF16-FMR2";
211*4882a593Smuzhiyun
212*4882a593Smuzhiyun /* avoid errors if a card was already registered at given port */
213*4882a593Smuzhiyun for (i = 0; i < num_fmr2_cards; i++)
214*4882a593Smuzhiyun if (io == fmr2_cards[i]->io)
215*4882a593Smuzhiyun return -EBUSY;
216*4882a593Smuzhiyun
217*4882a593Smuzhiyun strscpy(fmr2->v4l2_dev.name, "radio-sf16fmr2",
218*4882a593Smuzhiyun sizeof(fmr2->v4l2_dev.name)),
219*4882a593Smuzhiyun fmr2->io = io;
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun if (!request_region(fmr2->io, 2, fmr2->v4l2_dev.name)) {
222*4882a593Smuzhiyun printk(KERN_ERR "radio-sf16fmr2: I/O port 0x%x already in use\n", fmr2->io);
223*4882a593Smuzhiyun return -EBUSY;
224*4882a593Smuzhiyun }
225*4882a593Smuzhiyun
226*4882a593Smuzhiyun dev_set_drvdata(pdev, fmr2);
227*4882a593Smuzhiyun err = v4l2_device_register(pdev, &fmr2->v4l2_dev);
228*4882a593Smuzhiyun if (err < 0) {
229*4882a593Smuzhiyun v4l2_err(&fmr2->v4l2_dev, "Could not register v4l2_device\n");
230*4882a593Smuzhiyun release_region(fmr2->io, 2);
231*4882a593Smuzhiyun return err;
232*4882a593Smuzhiyun }
233*4882a593Smuzhiyun fmr2->tea.v4l2_dev = &fmr2->v4l2_dev;
234*4882a593Smuzhiyun fmr2->tea.private_data = fmr2;
235*4882a593Smuzhiyun fmr2->tea.radio_nr = radio_nr[num_fmr2_cards];
236*4882a593Smuzhiyun fmr2->tea.ops = &fmr2_tea_ops;
237*4882a593Smuzhiyun fmr2->tea.ext_init = fmr2_tea_ext_init;
238*4882a593Smuzhiyun strscpy(fmr2->tea.card, card_name, sizeof(fmr2->tea.card));
239*4882a593Smuzhiyun snprintf(fmr2->tea.bus_info, sizeof(fmr2->tea.bus_info), "%s:%s",
240*4882a593Smuzhiyun fmr2->is_fmd2 ? "PnP" : "ISA", dev_name(pdev));
241*4882a593Smuzhiyun
242*4882a593Smuzhiyun if (snd_tea575x_init(&fmr2->tea, THIS_MODULE)) {
243*4882a593Smuzhiyun printk(KERN_ERR "radio-sf16fmr2: Unable to detect TEA575x tuner\n");
244*4882a593Smuzhiyun release_region(fmr2->io, 2);
245*4882a593Smuzhiyun return -ENODEV;
246*4882a593Smuzhiyun }
247*4882a593Smuzhiyun
248*4882a593Smuzhiyun printk(KERN_INFO "radio-sf16fmr2: %s radio card at 0x%x.\n",
249*4882a593Smuzhiyun card_name, fmr2->io);
250*4882a593Smuzhiyun return 0;
251*4882a593Smuzhiyun }
252*4882a593Smuzhiyun
fmr2_isa_match(struct device * pdev,unsigned int ndev)253*4882a593Smuzhiyun static int fmr2_isa_match(struct device *pdev, unsigned int ndev)
254*4882a593Smuzhiyun {
255*4882a593Smuzhiyun struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL);
256*4882a593Smuzhiyun if (!fmr2)
257*4882a593Smuzhiyun return 0;
258*4882a593Smuzhiyun
259*4882a593Smuzhiyun if (fmr2_probe(fmr2, pdev, FMR2_PORT)) {
260*4882a593Smuzhiyun kfree(fmr2);
261*4882a593Smuzhiyun return 0;
262*4882a593Smuzhiyun }
263*4882a593Smuzhiyun dev_set_drvdata(pdev, fmr2);
264*4882a593Smuzhiyun fmr2_cards[num_fmr2_cards++] = fmr2;
265*4882a593Smuzhiyun
266*4882a593Smuzhiyun return 1;
267*4882a593Smuzhiyun }
268*4882a593Smuzhiyun
fmr2_pnp_probe(struct pnp_dev * pdev,const struct pnp_device_id * id)269*4882a593Smuzhiyun static int fmr2_pnp_probe(struct pnp_dev *pdev, const struct pnp_device_id *id)
270*4882a593Smuzhiyun {
271*4882a593Smuzhiyun int ret;
272*4882a593Smuzhiyun struct fmr2 *fmr2 = kzalloc(sizeof(*fmr2), GFP_KERNEL);
273*4882a593Smuzhiyun if (!fmr2)
274*4882a593Smuzhiyun return -ENOMEM;
275*4882a593Smuzhiyun
276*4882a593Smuzhiyun fmr2->is_fmd2 = true;
277*4882a593Smuzhiyun ret = fmr2_probe(fmr2, &pdev->dev, pnp_port_start(pdev, 0));
278*4882a593Smuzhiyun if (ret) {
279*4882a593Smuzhiyun kfree(fmr2);
280*4882a593Smuzhiyun return ret;
281*4882a593Smuzhiyun }
282*4882a593Smuzhiyun pnp_set_drvdata(pdev, fmr2);
283*4882a593Smuzhiyun fmr2_cards[num_fmr2_cards++] = fmr2;
284*4882a593Smuzhiyun
285*4882a593Smuzhiyun return 0;
286*4882a593Smuzhiyun }
287*4882a593Smuzhiyun
fmr2_remove(struct fmr2 * fmr2)288*4882a593Smuzhiyun static void fmr2_remove(struct fmr2 *fmr2)
289*4882a593Smuzhiyun {
290*4882a593Smuzhiyun snd_tea575x_exit(&fmr2->tea);
291*4882a593Smuzhiyun release_region(fmr2->io, 2);
292*4882a593Smuzhiyun v4l2_device_unregister(&fmr2->v4l2_dev);
293*4882a593Smuzhiyun kfree(fmr2);
294*4882a593Smuzhiyun }
295*4882a593Smuzhiyun
fmr2_isa_remove(struct device * pdev,unsigned int ndev)296*4882a593Smuzhiyun static int fmr2_isa_remove(struct device *pdev, unsigned int ndev)
297*4882a593Smuzhiyun {
298*4882a593Smuzhiyun fmr2_remove(dev_get_drvdata(pdev));
299*4882a593Smuzhiyun
300*4882a593Smuzhiyun return 0;
301*4882a593Smuzhiyun }
302*4882a593Smuzhiyun
fmr2_pnp_remove(struct pnp_dev * pdev)303*4882a593Smuzhiyun static void fmr2_pnp_remove(struct pnp_dev *pdev)
304*4882a593Smuzhiyun {
305*4882a593Smuzhiyun fmr2_remove(pnp_get_drvdata(pdev));
306*4882a593Smuzhiyun pnp_set_drvdata(pdev, NULL);
307*4882a593Smuzhiyun }
308*4882a593Smuzhiyun
309*4882a593Smuzhiyun static struct isa_driver fmr2_isa_driver = {
310*4882a593Smuzhiyun .match = fmr2_isa_match,
311*4882a593Smuzhiyun .remove = fmr2_isa_remove,
312*4882a593Smuzhiyun .driver = {
313*4882a593Smuzhiyun .name = "radio-sf16fmr2",
314*4882a593Smuzhiyun },
315*4882a593Smuzhiyun };
316*4882a593Smuzhiyun
317*4882a593Smuzhiyun static struct pnp_driver fmr2_pnp_driver = {
318*4882a593Smuzhiyun .name = "radio-sf16fmr2",
319*4882a593Smuzhiyun .id_table = fmr2_pnp_ids,
320*4882a593Smuzhiyun .probe = fmr2_pnp_probe,
321*4882a593Smuzhiyun .remove = fmr2_pnp_remove,
322*4882a593Smuzhiyun };
323*4882a593Smuzhiyun
fmr2_init(void)324*4882a593Smuzhiyun static int __init fmr2_init(void)
325*4882a593Smuzhiyun {
326*4882a593Smuzhiyun int ret;
327*4882a593Smuzhiyun
328*4882a593Smuzhiyun ret = pnp_register_driver(&fmr2_pnp_driver);
329*4882a593Smuzhiyun if (!ret)
330*4882a593Smuzhiyun pnp_registered = true;
331*4882a593Smuzhiyun ret = isa_register_driver(&fmr2_isa_driver, 1);
332*4882a593Smuzhiyun if (!ret)
333*4882a593Smuzhiyun isa_registered = true;
334*4882a593Smuzhiyun
335*4882a593Smuzhiyun return (pnp_registered || isa_registered) ? 0 : ret;
336*4882a593Smuzhiyun }
337*4882a593Smuzhiyun
fmr2_exit(void)338*4882a593Smuzhiyun static void __exit fmr2_exit(void)
339*4882a593Smuzhiyun {
340*4882a593Smuzhiyun if (pnp_registered)
341*4882a593Smuzhiyun pnp_unregister_driver(&fmr2_pnp_driver);
342*4882a593Smuzhiyun if (isa_registered)
343*4882a593Smuzhiyun isa_unregister_driver(&fmr2_isa_driver);
344*4882a593Smuzhiyun }
345*4882a593Smuzhiyun
346*4882a593Smuzhiyun module_init(fmr2_init);
347*4882a593Smuzhiyun module_exit(fmr2_exit);
348