1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* radio-trust.c - Trust FM Radio card driver for Linux 2.2 |
3 | * by Eric Lammerts <eric@scintilla.utwente.nl> |
4 | * |
5 | * Based on radio-aztech.c. Original notes: |
6 | * |
7 | * Adapted to support the Video for Linux API by |
8 | * Russell Kroll <rkroll@exploits.org>. Based on original tuner code by: |
9 | * |
10 | * Quay Ly |
11 | * Donald Song |
12 | * Jason Lewis (jlewis@twilight.vtc.vsc.edu) |
13 | * Scott McGrath (smcgrath@twilight.vtc.vsc.edu) |
14 | * William McGrath (wmcgrath@twilight.vtc.vsc.edu) |
15 | * |
16 | * Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@kernel.org> |
17 | */ |
18 | |
19 | #include <linux/module.h> |
20 | #include <linux/init.h> |
21 | #include <linux/ioport.h> |
22 | #include <linux/videodev2.h> |
23 | #include <linux/io.h> |
24 | #include <linux/slab.h> |
25 | #include <media/v4l2-device.h> |
26 | #include <media/v4l2-ioctl.h> |
27 | #include "radio-isa.h" |
28 | |
29 | MODULE_AUTHOR("Eric Lammerts, Russell Kroll, Quay Lu, Donald Song, Jason Lewis, Scott McGrath, William McGrath" ); |
30 | MODULE_DESCRIPTION("A driver for the Trust FM Radio card." ); |
31 | MODULE_LICENSE("GPL" ); |
32 | MODULE_VERSION("0.1.99" ); |
33 | |
34 | /* acceptable ports: 0x350 (JP3 shorted), 0x358 (JP3 open) */ |
35 | |
36 | #ifndef CONFIG_RADIO_TRUST_PORT |
37 | #define CONFIG_RADIO_TRUST_PORT -1 |
38 | #endif |
39 | |
40 | #define TRUST_MAX 2 |
41 | |
42 | static int io[TRUST_MAX] = { [0] = CONFIG_RADIO_TRUST_PORT, |
43 | [1 ... (TRUST_MAX - 1)] = -1 }; |
44 | static int radio_nr[TRUST_MAX] = { [0 ... (TRUST_MAX - 1)] = -1 }; |
45 | |
46 | module_param_array(io, int, NULL, 0444); |
47 | MODULE_PARM_DESC(io, "I/O addresses of the Trust FM Radio card (0x350 or 0x358)" ); |
48 | module_param_array(radio_nr, int, NULL, 0444); |
49 | MODULE_PARM_DESC(radio_nr, "Radio device numbers" ); |
50 | |
51 | struct trust { |
52 | struct radio_isa_card isa; |
53 | int ioval; |
54 | }; |
55 | |
56 | static struct radio_isa_card *trust_alloc(void) |
57 | { |
58 | struct trust *tr = kzalloc(size: sizeof(*tr), GFP_KERNEL); |
59 | |
60 | return tr ? &tr->isa : NULL; |
61 | } |
62 | |
63 | /* i2c addresses */ |
64 | #define TDA7318_ADDR 0x88 |
65 | #define TSA6060T_ADDR 0xc4 |
66 | |
67 | #define TR_DELAY do { inb(tr->isa.io); inb(tr->isa.io); inb(tr->isa.io); } while (0) |
68 | #define TR_SET_SCL outb(tr->ioval |= 2, tr->isa.io) |
69 | #define TR_CLR_SCL outb(tr->ioval &= 0xfd, tr->isa.io) |
70 | #define TR_SET_SDA outb(tr->ioval |= 1, tr->isa.io) |
71 | #define TR_CLR_SDA outb(tr->ioval &= 0xfe, tr->isa.io) |
72 | |
73 | static void write_i2c(struct trust *tr, int n, ...) |
74 | { |
75 | unsigned char val, mask; |
76 | va_list args; |
77 | |
78 | va_start(args, n); |
79 | |
80 | /* start condition */ |
81 | TR_SET_SDA; |
82 | TR_SET_SCL; |
83 | TR_DELAY; |
84 | TR_CLR_SDA; |
85 | TR_CLR_SCL; |
86 | TR_DELAY; |
87 | |
88 | for (; n; n--) { |
89 | val = va_arg(args, unsigned); |
90 | for (mask = 0x80; mask; mask >>= 1) { |
91 | if (val & mask) |
92 | TR_SET_SDA; |
93 | else |
94 | TR_CLR_SDA; |
95 | TR_SET_SCL; |
96 | TR_DELAY; |
97 | TR_CLR_SCL; |
98 | TR_DELAY; |
99 | } |
100 | /* acknowledge bit */ |
101 | TR_SET_SDA; |
102 | TR_SET_SCL; |
103 | TR_DELAY; |
104 | TR_CLR_SCL; |
105 | TR_DELAY; |
106 | } |
107 | |
108 | /* stop condition */ |
109 | TR_CLR_SDA; |
110 | TR_DELAY; |
111 | TR_SET_SCL; |
112 | TR_DELAY; |
113 | TR_SET_SDA; |
114 | TR_DELAY; |
115 | |
116 | va_end(args); |
117 | } |
118 | |
119 | static int trust_s_mute_volume(struct radio_isa_card *isa, bool mute, int vol) |
120 | { |
121 | struct trust *tr = container_of(isa, struct trust, isa); |
122 | |
123 | tr->ioval = (tr->ioval & 0xf7) | (mute << 3); |
124 | outb(value: tr->ioval, port: isa->io); |
125 | write_i2c(tr, n: 2, TDA7318_ADDR, vol ^ 0x1f); |
126 | return 0; |
127 | } |
128 | |
129 | static int trust_s_stereo(struct radio_isa_card *isa, bool stereo) |
130 | { |
131 | struct trust *tr = container_of(isa, struct trust, isa); |
132 | |
133 | tr->ioval = (tr->ioval & 0xfb) | (!stereo << 2); |
134 | outb(value: tr->ioval, port: isa->io); |
135 | return 0; |
136 | } |
137 | |
138 | static u32 trust_g_signal(struct radio_isa_card *isa) |
139 | { |
140 | int i, v; |
141 | |
142 | for (i = 0, v = 0; i < 100; i++) |
143 | v |= inb(port: isa->io); |
144 | return (v & 1) ? 0 : 0xffff; |
145 | } |
146 | |
147 | static int trust_s_frequency(struct radio_isa_card *isa, u32 freq) |
148 | { |
149 | struct trust *tr = container_of(isa, struct trust, isa); |
150 | |
151 | freq /= 160; /* Convert to 10 kHz units */ |
152 | freq += 1070; /* Add 10.7 MHz IF */ |
153 | write_i2c(tr, n: 5, TSA6060T_ADDR, (freq << 1) | 1, |
154 | freq >> 7, 0x60 | ((freq >> 15) & 1), 0); |
155 | return 0; |
156 | } |
157 | |
158 | static int basstreble2chip[15] = { |
159 | 0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8 |
160 | }; |
161 | |
162 | static int trust_s_ctrl(struct v4l2_ctrl *ctrl) |
163 | { |
164 | struct radio_isa_card *isa = |
165 | container_of(ctrl->handler, struct radio_isa_card, hdl); |
166 | struct trust *tr = container_of(isa, struct trust, isa); |
167 | |
168 | switch (ctrl->id) { |
169 | case V4L2_CID_AUDIO_BASS: |
170 | write_i2c(tr, n: 2, TDA7318_ADDR, 0x60 | basstreble2chip[ctrl->val]); |
171 | return 0; |
172 | case V4L2_CID_AUDIO_TREBLE: |
173 | write_i2c(tr, n: 2, TDA7318_ADDR, 0x70 | basstreble2chip[ctrl->val]); |
174 | return 0; |
175 | } |
176 | return -EINVAL; |
177 | } |
178 | |
179 | static const struct v4l2_ctrl_ops trust_ctrl_ops = { |
180 | .s_ctrl = trust_s_ctrl, |
181 | }; |
182 | |
183 | static int trust_initialize(struct radio_isa_card *isa) |
184 | { |
185 | struct trust *tr = container_of(isa, struct trust, isa); |
186 | |
187 | tr->ioval = 0xf; |
188 | write_i2c(tr, n: 2, TDA7318_ADDR, 0x80); /* speaker att. LF = 0 dB */ |
189 | write_i2c(tr, n: 2, TDA7318_ADDR, 0xa0); /* speaker att. RF = 0 dB */ |
190 | write_i2c(tr, n: 2, TDA7318_ADDR, 0xc0); /* speaker att. LR = 0 dB */ |
191 | write_i2c(tr, n: 2, TDA7318_ADDR, 0xe0); /* speaker att. RR = 0 dB */ |
192 | write_i2c(tr, n: 2, TDA7318_ADDR, 0x40); /* stereo 1 input, gain = 18.75 dB */ |
193 | |
194 | v4l2_ctrl_new_std(hdl: &isa->hdl, ops: &trust_ctrl_ops, |
195 | V4L2_CID_AUDIO_BASS, min: 0, max: 15, step: 1, def: 8); |
196 | v4l2_ctrl_new_std(hdl: &isa->hdl, ops: &trust_ctrl_ops, |
197 | V4L2_CID_AUDIO_TREBLE, min: 0, max: 15, step: 1, def: 8); |
198 | return isa->hdl.error; |
199 | } |
200 | |
201 | static const struct radio_isa_ops trust_ops = { |
202 | .init = trust_initialize, |
203 | .alloc = trust_alloc, |
204 | .s_mute_volume = trust_s_mute_volume, |
205 | .s_frequency = trust_s_frequency, |
206 | .s_stereo = trust_s_stereo, |
207 | .g_signal = trust_g_signal, |
208 | }; |
209 | |
210 | static const int trust_ioports[] = { 0x350, 0x358 }; |
211 | |
212 | static struct radio_isa_driver trust_driver = { |
213 | .driver = { |
214 | .match = radio_isa_match, |
215 | .probe = radio_isa_probe, |
216 | .remove = radio_isa_remove, |
217 | .driver = { |
218 | .name = "radio-trust" , |
219 | }, |
220 | }, |
221 | .io_params = io, |
222 | .radio_nr_params = radio_nr, |
223 | .io_ports = trust_ioports, |
224 | .num_of_io_ports = ARRAY_SIZE(trust_ioports), |
225 | .region_size = 2, |
226 | .card = "Trust FM Radio" , |
227 | .ops = &trust_ops, |
228 | .has_stereo = true, |
229 | .max_volume = 31, |
230 | }; |
231 | |
232 | static int __init trust_init(void) |
233 | { |
234 | return isa_register_driver(&trust_driver.driver, TRUST_MAX); |
235 | } |
236 | |
237 | static void __exit trust_exit(void) |
238 | { |
239 | isa_unregister_driver(&trust_driver.driver); |
240 | } |
241 | |
242 | module_init(trust_init); |
243 | module_exit(trust_exit); |
244 | |