Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* Miro PCM20 radio driver for Linux radio support |
2 | * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> | |
3 | * Thanks to Norberto Pellici for the ACI device interface specification | |
4 | * The API part is based on the radiotrack driver by M. Kirkwood | |
5 | * This driver relies on the aci mixer (drivers/sound/aci.c) | |
6 | * Look there for further info... | |
7 | */ | |
8 | ||
9 | /* Revision history: | |
10 | * | |
11 | * 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> | |
12 | * 2000-09-05 Robert Siemer <Robert.Siemer@gmx.de> | |
13 | * removed unfinished volume control (maybe adding it later again) | |
14 | * use OSS-mixer; added stereo control | |
15 | */ | |
16 | ||
17 | /* What ever you think about the ACI, version 0x07 is not very well! | |
18 | * I can't get frequency, 'tuner status', 'tuner flags' or mute/mono | |
4286c6f6 | 19 | * conditions... Robert |
1da177e4 LT |
20 | */ |
21 | ||
22 | #include <linux/module.h> | |
23 | #include <linux/init.h> | |
24 | #include <linux/videodev.h> | |
5e87efa3 | 25 | #include <media/v4l2-common.h> |
2938d780 | 26 | #include "oss/aci.h" |
1da177e4 LT |
27 | #include "miropcm20-rds-core.h" |
28 | ||
29 | static int radio_nr = -1; | |
30 | module_param(radio_nr, int, 0); | |
31 | ||
32 | struct pcm20_device { | |
33 | unsigned long freq; | |
34 | int muted; | |
35 | int stereo; | |
36 | }; | |
37 | ||
38 | ||
39 | static int pcm20_mute(struct pcm20_device *dev, unsigned char mute) | |
40 | { | |
41 | dev->muted = mute; | |
42 | return aci_write_cmd(ACI_SET_TUNERMUTE, mute); | |
43 | } | |
44 | ||
45 | static int pcm20_stereo(struct pcm20_device *dev, unsigned char stereo) | |
46 | { | |
47 | dev->stereo = stereo; | |
48 | return aci_write_cmd(ACI_SET_TUNERMONO, !stereo); | |
49 | } | |
50 | ||
51 | static int pcm20_setfreq(struct pcm20_device *dev, unsigned long freq) | |
52 | { | |
53 | unsigned char freql; | |
54 | unsigned char freqh; | |
55 | ||
56 | dev->freq=freq; | |
57 | ||
58 | freq /= 160; | |
59 | if (!(aci_version==0x07 || aci_version>=0xb0)) | |
60 | freq /= 10; /* I don't know exactly which version | |
61 | * needs this hack */ | |
62 | freql = freq & 0xff; | |
63 | freqh = freq >> 8; | |
64 | ||
65 | aci_rds_cmd(RDS_RESET, NULL, 0); | |
66 | pcm20_stereo(dev, 1); | |
67 | ||
68 | return aci_rw_cmd(ACI_WRITE_TUNE, freql, freqh); | |
69 | } | |
70 | ||
71 | static int pcm20_getflags(struct pcm20_device *dev, __u32 *flags, __u16 *signal) | |
72 | { | |
73 | /* okay, check for signal, stereo and rds here... */ | |
74 | int i; | |
75 | unsigned char buf; | |
76 | ||
77 | if ((i=aci_rw_cmd(ACI_READ_TUNERSTATION, -1, -1))<0) | |
78 | return i; | |
79 | pr_debug("check_sig: 0x%x\n", i); | |
80 | if (i & 0x80) { | |
81 | /* no signal from tuner */ | |
82 | *flags=0; | |
83 | *signal=0; | |
84 | return 0; | |
85 | } else | |
86 | *signal=0xffff; | |
87 | ||
88 | if ((i=aci_rw_cmd(ACI_READ_TUNERSTEREO, -1, -1))<0) | |
89 | return i; | |
90 | if (i & 0x40) { | |
91 | *flags=0; | |
92 | } else { | |
93 | /* stereo */ | |
94 | *flags=VIDEO_TUNER_STEREO_ON; | |
95 | /* I can't see stereo, when forced to mono */ | |
96 | dev->stereo=1; | |
97 | } | |
98 | ||
99 | if ((i=aci_rds_cmd(RDS_STATUS, &buf, 1))<0) | |
100 | return i; | |
101 | if (buf & 1) | |
102 | /* RDS available */ | |
103 | *flags|=VIDEO_TUNER_RDS_ON; | |
104 | else | |
105 | return 0; | |
106 | ||
107 | if ((i=aci_rds_cmd(RDS_RXVALUE, &buf, 1))<0) | |
108 | return i; | |
109 | pr_debug("rds-signal: %d\n", buf); | |
110 | if (buf > 15) { | |
111 | printk("miropcm20-radio: RX strengths unexpected high...\n"); | |
112 | buf=15; | |
113 | } | |
114 | /* refine signal */ | |
115 | if ((*signal=SCALE(15, 0xffff, buf))==0) | |
116 | *signal = 1; | |
117 | ||
118 | return 0; | |
119 | } | |
120 | ||
121 | static int pcm20_do_ioctl(struct inode *inode, struct file *file, | |
122 | unsigned int cmd, void *arg) | |
123 | { | |
124 | struct video_device *dev = video_devdata(file); | |
125 | struct pcm20_device *pcm20 = dev->priv; | |
126 | int i; | |
4286c6f6 | 127 | |
1da177e4 LT |
128 | switch(cmd) |
129 | { | |
130 | case VIDIOCGCAP: | |
131 | { | |
132 | struct video_capability *v = arg; | |
133 | memset(v,0,sizeof(*v)); | |
134 | v->type=VID_TYPE_TUNER; | |
135 | strcpy(v->name, "Miro PCM20"); | |
136 | v->channels=1; | |
137 | v->audios=1; | |
138 | return 0; | |
139 | } | |
140 | case VIDIOCGTUNER: | |
141 | { | |
142 | struct video_tuner *v = arg; | |
4286c6f6 | 143 | if(v->tuner) /* Only 1 tuner */ |
1da177e4 LT |
144 | return -EINVAL; |
145 | v->rangelow=87*16000; | |
146 | v->rangehigh=108*16000; | |
147 | pcm20_getflags(pcm20, &v->flags, &v->signal); | |
148 | v->flags|=VIDEO_TUNER_LOW; | |
149 | v->mode=VIDEO_MODE_AUTO; | |
150 | strcpy(v->name, "FM"); | |
151 | return 0; | |
152 | } | |
153 | case VIDIOCSTUNER: | |
154 | { | |
155 | struct video_tuner *v = arg; | |
156 | if(v->tuner!=0) | |
157 | return -EINVAL; | |
158 | /* Only 1 tuner so no setting needed ! */ | |
159 | return 0; | |
160 | } | |
161 | case VIDIOCGFREQ: | |
162 | { | |
163 | unsigned long *freq = arg; | |
164 | *freq = pcm20->freq; | |
165 | return 0; | |
166 | } | |
167 | case VIDIOCSFREQ: | |
168 | { | |
169 | unsigned long *freq = arg; | |
170 | pcm20->freq = *freq; | |
171 | i=pcm20_setfreq(pcm20, pcm20->freq); | |
172 | pr_debug("First view (setfreq): 0x%x\n", i); | |
173 | return i; | |
174 | } | |
175 | case VIDIOCGAUDIO: | |
4286c6f6 | 176 | { |
1da177e4 LT |
177 | struct video_audio *v = arg; |
178 | memset(v,0, sizeof(*v)); | |
179 | v->flags=VIDEO_AUDIO_MUTABLE; | |
180 | if (pcm20->muted) | |
181 | v->flags|=VIDEO_AUDIO_MUTE; | |
182 | v->mode=VIDEO_SOUND_STEREO; | |
183 | if (pcm20->stereo) | |
184 | v->mode|=VIDEO_SOUND_MONO; | |
185 | /* v->step=2048; */ | |
186 | strcpy(v->name, "Radio"); | |
4286c6f6 | 187 | return 0; |
1da177e4 LT |
188 | } |
189 | case VIDIOCSAUDIO: | |
190 | { | |
191 | struct video_audio *v = arg; | |
4286c6f6 | 192 | if(v->audio) |
1da177e4 LT |
193 | return -EINVAL; |
194 | ||
195 | pcm20_mute(pcm20, !!(v->flags&VIDEO_AUDIO_MUTE)); | |
196 | if(v->flags&VIDEO_SOUND_MONO) | |
197 | pcm20_stereo(pcm20, 0); | |
198 | if(v->flags&VIDEO_SOUND_STEREO) | |
199 | pcm20_stereo(pcm20, 1); | |
200 | ||
201 | return 0; | |
202 | } | |
203 | default: | |
204 | return -ENOIOCTLCMD; | |
205 | } | |
206 | } | |
207 | ||
208 | static int pcm20_ioctl(struct inode *inode, struct file *file, | |
209 | unsigned int cmd, unsigned long arg) | |
210 | { | |
211 | return video_usercopy(inode, file, cmd, arg, pcm20_do_ioctl); | |
212 | } | |
213 | ||
214 | static struct pcm20_device pcm20_unit = { | |
215 | .freq = 87*16000, | |
216 | .muted = 1, | |
217 | }; | |
218 | ||
fa027c2a | 219 | static const struct file_operations pcm20_fops = { |
1da177e4 LT |
220 | .owner = THIS_MODULE, |
221 | .open = video_exclusive_open, | |
222 | .release = video_exclusive_release, | |
223 | .ioctl = pcm20_ioctl, | |
0d0fbf81 | 224 | .compat_ioctl = v4l_compat_ioctl32, |
1da177e4 LT |
225 | .llseek = no_llseek, |
226 | }; | |
227 | ||
228 | static struct video_device pcm20_radio = { | |
229 | .owner = THIS_MODULE, | |
230 | .name = "Miro PCM 20 radio", | |
231 | .type = VID_TYPE_TUNER, | |
1da177e4 LT |
232 | .fops = &pcm20_fops, |
233 | .priv = &pcm20_unit | |
234 | }; | |
235 | ||
236 | static int __init pcm20_init(void) | |
237 | { | |
238 | if(video_register_device(&pcm20_radio, VFL_TYPE_RADIO, radio_nr)==-1) | |
239 | goto video_register_device; | |
4286c6f6 | 240 | |
1da177e4 LT |
241 | if(attach_aci_rds()<0) |
242 | goto attach_aci_rds; | |
243 | ||
244 | printk(KERN_INFO "Miro PCM20 radio card driver.\n"); | |
245 | ||
246 | return 0; | |
247 | ||
248 | attach_aci_rds: | |
249 | video_unregister_device(&pcm20_radio); | |
250 | video_register_device: | |
251 | return -EINVAL; | |
252 | } | |
253 | ||
254 | MODULE_AUTHOR("Ruurd Reitsma"); | |
255 | MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); | |
256 | MODULE_LICENSE("GPL"); | |
257 | ||
258 | static void __exit pcm20_cleanup(void) | |
259 | { | |
260 | unload_aci_rds(); | |
261 | video_unregister_device(&pcm20_radio); | |
262 | } | |
263 | ||
264 | module_init(pcm20_init); | |
265 | module_exit(pcm20_cleanup); |