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