Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 1 | /* SF16-FMR2 radio driver for Linux |
| 2 | * Copyright (c) 2011 Ondrej Zary |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 3 | * |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 4 | * Original driver was (c) 2000-2002 Ziglio Frediano, freddy77@angelfire.com |
| 5 | * but almost nothing remained here after conversion to generic TEA575x |
| 6 | * implementation |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 7 | */ |
| 8 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 9 | #include <linux/delay.h> |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 10 | #include <linux/module.h> /* Modules */ |
| 11 | #include <linux/init.h> /* Initdata */ |
Peter Osterlund | fb911ee | 2005-09-13 01:25:15 -0700 | [diff] [blame] | 12 | #include <linux/ioport.h> /* request_region */ |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 13 | #include <linux/io.h> /* outb, outb_p */ |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 14 | #include <sound/tea575x-tuner.h> |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 15 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 16 | MODULE_AUTHOR("Ondrej Zary"); |
| 17 | MODULE_DESCRIPTION("MediaForte SF16-FMR2 FM radio card driver"); |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 18 | MODULE_LICENSE("GPL"); |
| 19 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 20 | struct fmr2 { |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 21 | int io; |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 22 | struct snd_tea575x tea; |
| 23 | struct v4l2_ctrl *volume; |
| 24 | struct v4l2_ctrl *balance; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 25 | }; |
| 26 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 27 | /* the port is hardwired so no need to support multiple cards */ |
| 28 | #define FMR2_PORT 0x384 |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 29 | static struct fmr2 fmr2_card; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 30 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 31 | /* TEA575x tuner pins */ |
| 32 | #define STR_DATA (1 << 0) |
| 33 | #define STR_CLK (1 << 1) |
| 34 | #define STR_WREN (1 << 2) |
| 35 | #define STR_MOST (1 << 3) |
| 36 | /* PT2254A/TC9154A volume control pins */ |
| 37 | #define PT_ST (1 << 4) |
| 38 | #define PT_CK (1 << 5) |
| 39 | #define PT_DATA (1 << 6) |
| 40 | /* volume control presence pin */ |
| 41 | #define FMR2_HASVOL (1 << 7) |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 42 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 43 | static void fmr2_tea575x_set_pins(struct snd_tea575x *tea, u8 pins) |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 44 | { |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 45 | struct fmr2 *fmr2 = tea->private_data; |
| 46 | u8 bits = 0; |
| 47 | |
| 48 | bits |= (pins & TEA575X_DATA) ? STR_DATA : 0; |
| 49 | bits |= (pins & TEA575X_CLK) ? STR_CLK : 0; |
| 50 | /* WRITE_ENABLE is inverted, DATA must be high during read */ |
| 51 | bits |= (pins & TEA575X_WREN) ? 0 : STR_WREN | STR_DATA; |
| 52 | |
| 53 | outb(bits, fmr2->io); |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 54 | } |
| 55 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 56 | static u8 fmr2_tea575x_get_pins(struct snd_tea575x *tea) |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 57 | { |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 58 | struct fmr2 *fmr2 = tea->private_data; |
| 59 | u8 bits = inb(fmr2->io); |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 60 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 61 | return (bits & STR_DATA) ? TEA575X_DATA : 0 | |
| 62 | (bits & STR_MOST) ? TEA575X_MOST : 0; |
| 63 | } |
| 64 | |
| 65 | static void fmr2_tea575x_set_direction(struct snd_tea575x *tea, bool output) |
| 66 | { |
| 67 | } |
| 68 | |
| 69 | static struct snd_tea575x_ops fmr2_tea_ops = { |
| 70 | .set_pins = fmr2_tea575x_set_pins, |
| 71 | .get_pins = fmr2_tea575x_get_pins, |
| 72 | .set_direction = fmr2_tea575x_set_direction, |
| 73 | }; |
| 74 | |
| 75 | /* TC9154A/PT2254A volume control */ |
| 76 | |
| 77 | /* 18-bit shift register bit definitions */ |
| 78 | #define TC9154A_ATT_MAJ_0DB (1 << 0) |
| 79 | #define TC9154A_ATT_MAJ_10DB (1 << 1) |
| 80 | #define TC9154A_ATT_MAJ_20DB (1 << 2) |
| 81 | #define TC9154A_ATT_MAJ_30DB (1 << 3) |
| 82 | #define TC9154A_ATT_MAJ_40DB (1 << 4) |
| 83 | #define TC9154A_ATT_MAJ_50DB (1 << 5) |
| 84 | #define TC9154A_ATT_MAJ_60DB (1 << 6) |
| 85 | |
| 86 | #define TC9154A_ATT_MIN_0DB (1 << 7) |
| 87 | #define TC9154A_ATT_MIN_2DB (1 << 8) |
| 88 | #define TC9154A_ATT_MIN_4DB (1 << 9) |
| 89 | #define TC9154A_ATT_MIN_6DB (1 << 10) |
| 90 | #define TC9154A_ATT_MIN_8DB (1 << 11) |
| 91 | /* bit 12 is ignored */ |
| 92 | #define TC9154A_CHANNEL_LEFT (1 << 13) |
| 93 | #define TC9154A_CHANNEL_RIGHT (1 << 14) |
| 94 | /* bits 15, 16, 17 must be 0 */ |
| 95 | |
| 96 | #define TC9154A_ATT_MAJ(x) (1 << x) |
| 97 | #define TC9154A_ATT_MIN(x) (1 << (7 + x)) |
| 98 | |
| 99 | static void tc9154a_set_pins(struct fmr2 *fmr2, u8 pins) |
| 100 | { |
| 101 | if (!fmr2->tea.mute) |
| 102 | pins |= STR_WREN; |
| 103 | |
| 104 | outb(pins, fmr2->io); |
| 105 | } |
| 106 | |
| 107 | static void tc9154a_set_attenuation(struct fmr2 *fmr2, int att, u32 channel) |
| 108 | { |
| 109 | int i; |
| 110 | u32 reg; |
| 111 | u8 bit; |
| 112 | |
| 113 | reg = TC9154A_ATT_MAJ(att / 10) | TC9154A_ATT_MIN((att % 10) / 2); |
| 114 | reg |= channel; |
| 115 | /* write 18-bit shift register, LSB first */ |
| 116 | for (i = 0; i < 18; i++) { |
| 117 | bit = reg & (1 << i) ? PT_DATA : 0; |
| 118 | tc9154a_set_pins(fmr2, bit); |
| 119 | udelay(5); |
| 120 | tc9154a_set_pins(fmr2, bit | PT_CK); |
| 121 | udelay(5); |
| 122 | tc9154a_set_pins(fmr2, bit); |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 123 | } |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 124 | |
| 125 | /* latch register data */ |
| 126 | udelay(5); |
| 127 | tc9154a_set_pins(fmr2, PT_ST); |
| 128 | udelay(5); |
| 129 | tc9154a_set_pins(fmr2, 0); |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 130 | } |
| 131 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 132 | static int fmr2_s_ctrl(struct v4l2_ctrl *ctrl) |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 133 | { |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 134 | struct snd_tea575x *tea = container_of(ctrl->handler, struct snd_tea575x, ctrl_handler); |
| 135 | struct fmr2 *fmr2 = tea->private_data; |
| 136 | int volume, balance, left, right; |
Douglas Landgraf | 34ab962 | 2007-04-23 17:51:37 -0300 | [diff] [blame] | 137 | |
| 138 | switch (ctrl->id) { |
Douglas Landgraf | 34ab962 | 2007-04-23 17:51:37 -0300 | [diff] [blame] | 139 | case V4L2_CID_AUDIO_VOLUME: |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 140 | volume = ctrl->val; |
| 141 | balance = fmr2->balance->cur.val; |
Douglas Landgraf | 34ab962 | 2007-04-23 17:51:37 -0300 | [diff] [blame] | 142 | break; |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 143 | case V4L2_CID_AUDIO_BALANCE: |
| 144 | balance = ctrl->val; |
| 145 | volume = fmr2->volume->cur.val; |
Douglas Landgraf | 34ab962 | 2007-04-23 17:51:37 -0300 | [diff] [blame] | 146 | break; |
| 147 | default: |
| 148 | return -EINVAL; |
| 149 | } |
| 150 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 151 | left = right = volume; |
| 152 | if (balance < 0) |
| 153 | right = max(0, right + balance); |
| 154 | if (balance > 0) |
| 155 | left = max(0, left - balance); |
Douglas Landgraf | 34ab962 | 2007-04-23 17:51:37 -0300 | [diff] [blame] | 156 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 157 | tc9154a_set_attenuation(fmr2, abs(left - 68), TC9154A_CHANNEL_LEFT); |
| 158 | tc9154a_set_attenuation(fmr2, abs(right - 68), TC9154A_CHANNEL_RIGHT); |
| 159 | |
Douglas Landgraf | 34ab962 | 2007-04-23 17:51:37 -0300 | [diff] [blame] | 160 | return 0; |
| 161 | } |
| 162 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 163 | static const struct v4l2_ctrl_ops fmr2_ctrl_ops = { |
| 164 | .s_ctrl = fmr2_s_ctrl, |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 165 | }; |
| 166 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 167 | static int fmr2_tea_ext_init(struct snd_tea575x *tea) |
| 168 | { |
| 169 | struct fmr2 *fmr2 = tea->private_data; |
| 170 | |
| 171 | if (inb(fmr2->io) & FMR2_HASVOL) { |
| 172 | fmr2->volume = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_VOLUME, 0, 68, 2, 56); |
| 173 | fmr2->balance = v4l2_ctrl_new_std(&tea->ctrl_handler, &fmr2_ctrl_ops, V4L2_CID_AUDIO_BALANCE, -68, 68, 2, 0); |
| 174 | if (tea->ctrl_handler.error) { |
| 175 | printk(KERN_ERR "radio-sf16fmr2: can't initialize contrls\n"); |
| 176 | return tea->ctrl_handler.error; |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | return 0; |
| 181 | } |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 182 | |
| 183 | static int __init fmr2_init(void) |
| 184 | { |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 185 | struct fmr2 *fmr2 = &fmr2_card; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 186 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 187 | fmr2->io = FMR2_PORT; |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 188 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 189 | if (!request_region(fmr2->io, 2, "SF16-FMR2")) { |
| 190 | printk(KERN_ERR "radio-sf16fmr2: I/O port 0x%x already in use\n", fmr2->io); |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 191 | return -EBUSY; |
| 192 | } |
| 193 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 194 | fmr2->tea.private_data = fmr2; |
| 195 | fmr2->tea.ops = &fmr2_tea_ops; |
| 196 | fmr2->tea.ext_init = fmr2_tea_ext_init; |
| 197 | strlcpy(fmr2->tea.card, "SF16-FMR2", sizeof(fmr2->tea.card)); |
| 198 | strcpy(fmr2->tea.bus_info, "ISA"); |
| 199 | |
| 200 | if (snd_tea575x_init(&fmr2->tea)) { |
| 201 | printk(KERN_ERR "radio-sf16fmr2: Unable to detect TEA575x tuner\n"); |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 202 | release_region(fmr2->io, 2); |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 203 | return -ENODEV; |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 204 | } |
| 205 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 206 | printk(KERN_INFO "radio-sf16fmr2: SF16-FMR2 radio card at 0x%x.\n", fmr2->io); |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 207 | return 0; |
| 208 | } |
| 209 | |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 210 | static void __exit fmr2_exit(void) |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 211 | { |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 212 | struct fmr2 *fmr2 = &fmr2_card; |
| 213 | |
Ondrej Zary | 9e8fa0e | 2011-06-01 16:57:11 -0300 | [diff] [blame] | 214 | snd_tea575x_exit(&fmr2->tea); |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 215 | release_region(fmr2->io, 2); |
Linus Torvalds | 1da177e | 2005-04-16 15:20:36 -0700 | [diff] [blame] | 216 | } |
| 217 | |
| 218 | module_init(fmr2_init); |
Hans Verkuil | c32a9d7 | 2009-03-06 13:53:26 -0300 | [diff] [blame] | 219 | module_exit(fmr2_exit); |