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 provided by the snd-miro 6 * ALSA driver. 7 * Look there for further info... 8 */ 9 10 /* What ever you think about the ACI, version 0x07 is not very well! 11 * I can't get frequency, 'tuner status', 'tuner flags' or mute/mono 12 * conditions... Robert 13 */ 14 15 #include <linux/module.h> 16 #include <linux/init.h> 17 #include <linux/videodev2.h> 18 #include <media/v4l2-device.h> 19 #include <media/v4l2-ioctl.h> 20 #include <sound/aci.h> 21 22 static int radio_nr = -1; 23 module_param(radio_nr, int, 0); 24 MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)"); 25 26 static bool mono; 27 module_param(mono, bool, 0); 28 MODULE_PARM_DESC(mono, "Force tuner into mono mode."); 29 30 struct pcm20 { 31 struct v4l2_device v4l2_dev; 32 struct video_device vdev; 33 unsigned long freq; 34 int muted; 35 struct snd_miro_aci *aci; 36 struct mutex lock; 37 }; 38 39 static struct pcm20 pcm20_card = { 40 .freq = 87*16000, 41 .muted = 1, 42 }; 43 44 static int pcm20_mute(struct pcm20 *dev, unsigned char mute) 45 { 46 dev->muted = mute; 47 return snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, mute, -1); 48 } 49 50 static int pcm20_stereo(struct pcm20 *dev, unsigned char stereo) 51 { 52 return snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, !stereo, -1); 53 } 54 55 static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq) 56 { 57 unsigned char freql; 58 unsigned char freqh; 59 struct snd_miro_aci *aci = dev->aci; 60 61 dev->freq = freq; 62 63 freq /= 160; 64 if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0)) 65 freq /= 10; /* I don't know exactly which version 66 * needs this hack */ 67 freql = freq & 0xff; 68 freqh = freq >> 8; 69 70 pcm20_stereo(dev, !mono); 71 return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh); 72 } 73 74 static const struct v4l2_file_operations pcm20_fops = { 75 .owner = THIS_MODULE, 76 .unlocked_ioctl = video_ioctl2, 77 }; 78 79 static int vidioc_querycap(struct file *file, void *priv, 80 struct v4l2_capability *v) 81 { 82 strlcpy(v->driver, "Miro PCM20", sizeof(v->driver)); 83 strlcpy(v->card, "Miro PCM20", sizeof(v->card)); 84 strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); 85 v->version = 0x1; 86 v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 87 return 0; 88 } 89 90 static int vidioc_g_tuner(struct file *file, void *priv, 91 struct v4l2_tuner *v) 92 { 93 if (v->index) /* Only 1 tuner */ 94 return -EINVAL; 95 strlcpy(v->name, "FM", sizeof(v->name)); 96 v->type = V4L2_TUNER_RADIO; 97 v->rangelow = 87*16000; 98 v->rangehigh = 108*16000; 99 v->signal = 0xffff; 100 v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; 101 v->capability = V4L2_TUNER_CAP_LOW; 102 v->audmode = V4L2_TUNER_MODE_MONO; 103 return 0; 104 } 105 106 static int vidioc_s_tuner(struct file *file, void *priv, 107 struct v4l2_tuner *v) 108 { 109 return v->index ? -EINVAL : 0; 110 } 111 112 static int vidioc_g_frequency(struct file *file, void *priv, 113 struct v4l2_frequency *f) 114 { 115 struct pcm20 *dev = video_drvdata(file); 116 117 if (f->tuner != 0) 118 return -EINVAL; 119 120 f->type = V4L2_TUNER_RADIO; 121 f->frequency = dev->freq; 122 return 0; 123 } 124 125 126 static int vidioc_s_frequency(struct file *file, void *priv, 127 struct v4l2_frequency *f) 128 { 129 struct pcm20 *dev = video_drvdata(file); 130 131 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 132 return -EINVAL; 133 134 dev->freq = f->frequency; 135 pcm20_setfreq(dev, f->frequency); 136 return 0; 137 } 138 139 static int vidioc_queryctrl(struct file *file, void *priv, 140 struct v4l2_queryctrl *qc) 141 { 142 switch (qc->id) { 143 case V4L2_CID_AUDIO_MUTE: 144 return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); 145 } 146 return -EINVAL; 147 } 148 149 static int vidioc_g_ctrl(struct file *file, void *priv, 150 struct v4l2_control *ctrl) 151 { 152 struct pcm20 *dev = video_drvdata(file); 153 154 switch (ctrl->id) { 155 case V4L2_CID_AUDIO_MUTE: 156 ctrl->value = dev->muted; 157 break; 158 default: 159 return -EINVAL; 160 } 161 return 0; 162 } 163 164 static int vidioc_s_ctrl(struct file *file, void *priv, 165 struct v4l2_control *ctrl) 166 { 167 struct pcm20 *dev = video_drvdata(file); 168 169 switch (ctrl->id) { 170 case V4L2_CID_AUDIO_MUTE: 171 pcm20_mute(dev, ctrl->value); 172 break; 173 default: 174 return -EINVAL; 175 } 176 return 0; 177 } 178 179 static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 180 { 181 *i = 0; 182 return 0; 183 } 184 185 static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 186 { 187 return i ? -EINVAL : 0; 188 } 189 190 static int vidioc_g_audio(struct file *file, void *priv, 191 struct v4l2_audio *a) 192 { 193 a->index = 0; 194 strlcpy(a->name, "Radio", sizeof(a->name)); 195 a->capability = V4L2_AUDCAP_STEREO; 196 return 0; 197 } 198 199 static int vidioc_s_audio(struct file *file, void *priv, 200 const struct v4l2_audio *a) 201 { 202 return a->index ? -EINVAL : 0; 203 } 204 205 static const struct v4l2_ioctl_ops pcm20_ioctl_ops = { 206 .vidioc_querycap = vidioc_querycap, 207 .vidioc_g_tuner = vidioc_g_tuner, 208 .vidioc_s_tuner = vidioc_s_tuner, 209 .vidioc_g_frequency = vidioc_g_frequency, 210 .vidioc_s_frequency = vidioc_s_frequency, 211 .vidioc_queryctrl = vidioc_queryctrl, 212 .vidioc_g_ctrl = vidioc_g_ctrl, 213 .vidioc_s_ctrl = vidioc_s_ctrl, 214 .vidioc_g_audio = vidioc_g_audio, 215 .vidioc_s_audio = vidioc_s_audio, 216 .vidioc_g_input = vidioc_g_input, 217 .vidioc_s_input = vidioc_s_input, 218 }; 219 220 static int __init pcm20_init(void) 221 { 222 struct pcm20 *dev = &pcm20_card; 223 struct v4l2_device *v4l2_dev = &dev->v4l2_dev; 224 int res; 225 226 dev->aci = snd_aci_get_aci(); 227 if (dev->aci == NULL) { 228 v4l2_err(v4l2_dev, 229 "you must load the snd-miro driver first!\n"); 230 return -ENODEV; 231 } 232 strlcpy(v4l2_dev->name, "miropcm20", sizeof(v4l2_dev->name)); 233 mutex_init(&dev->lock); 234 235 res = v4l2_device_register(NULL, v4l2_dev); 236 if (res < 0) { 237 v4l2_err(v4l2_dev, "could not register v4l2_device\n"); 238 return -EINVAL; 239 } 240 241 strlcpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); 242 dev->vdev.v4l2_dev = v4l2_dev; 243 dev->vdev.fops = &pcm20_fops; 244 dev->vdev.ioctl_ops = &pcm20_ioctl_ops; 245 dev->vdev.release = video_device_release_empty; 246 dev->vdev.lock = &dev->lock; 247 video_set_drvdata(&dev->vdev, dev); 248 249 if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) 250 goto fail; 251 252 v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n"); 253 return 0; 254 fail: 255 v4l2_device_unregister(v4l2_dev); 256 return -EINVAL; 257 } 258 259 MODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt"); 260 MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); 261 MODULE_LICENSE("GPL"); 262 263 static void __exit pcm20_cleanup(void) 264 { 265 struct pcm20 *dev = &pcm20_card; 266 267 video_unregister_device(&dev->vdev); 268 v4l2_device_unregister(&dev->v4l2_dev); 269 } 270 271 module_init(pcm20_init); 272 module_exit(pcm20_cleanup); 273