1fdcfd4e7SHans Verkuil /* 2fdcfd4e7SHans Verkuil * Miro PCM20 radio driver for Linux radio support 38366fc39SKrzysztof Helt * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> 48366fc39SKrzysztof Helt * Thanks to Norberto Pellici for the ACI device interface specification 58366fc39SKrzysztof Helt * The API part is based on the radiotrack driver by M. Kirkwood 68366fc39SKrzysztof Helt * This driver relies on the aci mixer provided by the snd-miro 78366fc39SKrzysztof Helt * ALSA driver. 88366fc39SKrzysztof Helt * Look there for further info... 9fdcfd4e7SHans Verkuil * 10fdcfd4e7SHans Verkuil * From the original miro RDS sources: 11fdcfd4e7SHans Verkuil * 12fdcfd4e7SHans Verkuil * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de> 13fdcfd4e7SHans Verkuil * 14fdcfd4e7SHans Verkuil * Many thanks to Fred Seidel <seidel@metabox.de>, the 15fdcfd4e7SHans Verkuil * designer of the RDS decoder hardware. With his help 16fdcfd4e7SHans Verkuil * I was able to code this driver. 17fdcfd4e7SHans Verkuil * Thanks also to Norberto Pellicci, Dominic Mounteney 18fdcfd4e7SHans Verkuil * <DMounteney@pinnaclesys.com> and www.teleauskunft.de 19fdcfd4e7SHans Verkuil * for good hints on finding Fred. It was somewhat hard 20fdcfd4e7SHans Verkuil * to locate him here in Germany... [: 21fdcfd4e7SHans Verkuil * 22fdcfd4e7SHans Verkuil * This code has been reintroduced and converted to use 23fdcfd4e7SHans Verkuil * the new V4L2 RDS API by: 24fdcfd4e7SHans Verkuil * 25fdcfd4e7SHans Verkuil * Hans Verkuil <hans.verkuil@cisco.com> 268366fc39SKrzysztof Helt */ 278366fc39SKrzysztof Helt 288366fc39SKrzysztof Helt #include <linux/module.h> 298366fc39SKrzysztof Helt #include <linux/init.h> 307102076cSRandy Dunlap #include <linux/io.h> 31fdcfd4e7SHans Verkuil #include <linux/delay.h> 328366fc39SKrzysztof Helt #include <linux/videodev2.h> 33fdcfd4e7SHans Verkuil #include <linux/kthread.h> 348366fc39SKrzysztof Helt #include <media/v4l2-device.h> 358366fc39SKrzysztof Helt #include <media/v4l2-ioctl.h> 367f51a610SHans Verkuil #include <media/v4l2-ctrls.h> 37f7c096f7SHans Verkuil #include <media/v4l2-fh.h> 38f7c096f7SHans Verkuil #include <media/v4l2-event.h> 398366fc39SKrzysztof Helt #include <sound/aci.h> 408366fc39SKrzysztof Helt 41fdcfd4e7SHans Verkuil #define RDS_DATASHIFT 2 /* Bit 2 */ 42fdcfd4e7SHans Verkuil #define RDS_DATAMASK (1 << RDS_DATASHIFT) 43fdcfd4e7SHans Verkuil #define RDS_BUSYMASK 0x10 /* Bit 4 */ 44fdcfd4e7SHans Verkuil #define RDS_CLOCKMASK 0x08 /* Bit 3 */ 45fdcfd4e7SHans Verkuil #define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1) 46fdcfd4e7SHans Verkuil 47fdcfd4e7SHans Verkuil #define RDS_STATUS 0x01 48fdcfd4e7SHans Verkuil #define RDS_STATIONNAME 0x02 49fdcfd4e7SHans Verkuil #define RDS_TEXT 0x03 50fdcfd4e7SHans Verkuil #define RDS_ALTFREQ 0x04 51fdcfd4e7SHans Verkuil #define RDS_TIMEDATE 0x05 52fdcfd4e7SHans Verkuil #define RDS_PI_CODE 0x06 53fdcfd4e7SHans Verkuil #define RDS_PTYTATP 0x07 54fdcfd4e7SHans Verkuil #define RDS_RESET 0x08 55fdcfd4e7SHans Verkuil #define RDS_RXVALUE 0x09 56fdcfd4e7SHans Verkuil 578366fc39SKrzysztof Helt static int radio_nr = -1; 588366fc39SKrzysztof Helt module_param(radio_nr, int, 0); 598366fc39SKrzysztof Helt MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)"); 608366fc39SKrzysztof Helt 618366fc39SKrzysztof Helt struct pcm20 { 628366fc39SKrzysztof Helt struct v4l2_device v4l2_dev; 638366fc39SKrzysztof Helt struct video_device vdev; 647f51a610SHans Verkuil struct v4l2_ctrl_handler ctrl_handler; 65fdcfd4e7SHans Verkuil struct v4l2_ctrl *rds_pty; 66fdcfd4e7SHans Verkuil struct v4l2_ctrl *rds_ps_name; 67fdcfd4e7SHans Verkuil struct v4l2_ctrl *rds_radio_test; 68fdcfd4e7SHans Verkuil struct v4l2_ctrl *rds_ta; 69fdcfd4e7SHans Verkuil struct v4l2_ctrl *rds_tp; 70fdcfd4e7SHans Verkuil struct v4l2_ctrl *rds_ms; 71fdcfd4e7SHans Verkuil /* thread for periodic RDS status checking */ 72fdcfd4e7SHans Verkuil struct task_struct *kthread; 738366fc39SKrzysztof Helt unsigned long freq; 74f5e7cc4aSHans Verkuil u32 audmode; 758366fc39SKrzysztof Helt struct snd_miro_aci *aci; 7632958fddSHans Verkuil struct mutex lock; 778366fc39SKrzysztof Helt }; 788366fc39SKrzysztof Helt 798366fc39SKrzysztof Helt static struct pcm20 pcm20_card = { 808366fc39SKrzysztof Helt .freq = 87 * 16000, 81f5e7cc4aSHans Verkuil .audmode = V4L2_TUNER_MODE_STEREO, 828366fc39SKrzysztof Helt }; 838366fc39SKrzysztof Helt 84fdcfd4e7SHans Verkuil 85fdcfd4e7SHans Verkuil static int rds_waitread(struct snd_miro_aci *aci) 86fdcfd4e7SHans Verkuil { 87fdcfd4e7SHans Verkuil u8 byte; 88fdcfd4e7SHans Verkuil int i = 2000; 89fdcfd4e7SHans Verkuil 90fdcfd4e7SHans Verkuil do { 91fdcfd4e7SHans Verkuil byte = inb(aci->aci_port + ACI_REG_RDS); 92fdcfd4e7SHans Verkuil i--; 93fdcfd4e7SHans Verkuil } while ((byte & RDS_BUSYMASK) && i); 94fdcfd4e7SHans Verkuil 95fdcfd4e7SHans Verkuil /* 96fdcfd4e7SHans Verkuil * It's magic, but without this the data that you read later on 97fdcfd4e7SHans Verkuil * is unreliable and full of bit errors. With this 1 usec delay 98fdcfd4e7SHans Verkuil * everything is fine. 99fdcfd4e7SHans Verkuil */ 100fdcfd4e7SHans Verkuil udelay(1); 101fdcfd4e7SHans Verkuil return i ? byte : -1; 102fdcfd4e7SHans Verkuil } 103fdcfd4e7SHans Verkuil 104fdcfd4e7SHans Verkuil static int rds_rawwrite(struct snd_miro_aci *aci, u8 byte) 105fdcfd4e7SHans Verkuil { 106fdcfd4e7SHans Verkuil if (rds_waitread(aci) >= 0) { 107fdcfd4e7SHans Verkuil outb(byte, aci->aci_port + ACI_REG_RDS); 108fdcfd4e7SHans Verkuil return 0; 109fdcfd4e7SHans Verkuil } 110fdcfd4e7SHans Verkuil return -1; 111fdcfd4e7SHans Verkuil } 112fdcfd4e7SHans Verkuil 113fdcfd4e7SHans Verkuil static int rds_write(struct snd_miro_aci *aci, u8 byte) 114fdcfd4e7SHans Verkuil { 115fdcfd4e7SHans Verkuil u8 sendbuffer[8]; 116fdcfd4e7SHans Verkuil int i; 117fdcfd4e7SHans Verkuil 118fdcfd4e7SHans Verkuil for (i = 7; i >= 0; i--) 119fdcfd4e7SHans Verkuil sendbuffer[7 - i] = (byte & (1 << i)) ? RDS_DATAMASK : 0; 120fdcfd4e7SHans Verkuil sendbuffer[0] |= RDS_CLOCKMASK; 121fdcfd4e7SHans Verkuil 122fdcfd4e7SHans Verkuil for (i = 0; i < 8; i++) 123fdcfd4e7SHans Verkuil rds_rawwrite(aci, sendbuffer[i]); 124fdcfd4e7SHans Verkuil return 0; 125fdcfd4e7SHans Verkuil } 126fdcfd4e7SHans Verkuil 127fdcfd4e7SHans Verkuil static int rds_readcycle_nowait(struct snd_miro_aci *aci) 128fdcfd4e7SHans Verkuil { 129fdcfd4e7SHans Verkuil outb(0, aci->aci_port + ACI_REG_RDS); 130fdcfd4e7SHans Verkuil return rds_waitread(aci); 131fdcfd4e7SHans Verkuil } 132fdcfd4e7SHans Verkuil 133fdcfd4e7SHans Verkuil static int rds_readcycle(struct snd_miro_aci *aci) 134fdcfd4e7SHans Verkuil { 135fdcfd4e7SHans Verkuil if (rds_rawwrite(aci, 0) < 0) 136fdcfd4e7SHans Verkuil return -1; 137fdcfd4e7SHans Verkuil return rds_waitread(aci); 138fdcfd4e7SHans Verkuil } 139fdcfd4e7SHans Verkuil 140fdcfd4e7SHans Verkuil static int rds_ack(struct snd_miro_aci *aci) 141fdcfd4e7SHans Verkuil { 142fdcfd4e7SHans Verkuil int i = rds_readcycle(aci); 143fdcfd4e7SHans Verkuil 144fdcfd4e7SHans Verkuil if (i < 0) 145fdcfd4e7SHans Verkuil return -1; 146fdcfd4e7SHans Verkuil if (i & RDS_DATAMASK) 147fdcfd4e7SHans Verkuil return 0; /* ACK */ 148fdcfd4e7SHans Verkuil return 1; /* NACK */ 149fdcfd4e7SHans Verkuil } 150fdcfd4e7SHans Verkuil 151fdcfd4e7SHans Verkuil static int rds_cmd(struct snd_miro_aci *aci, u8 cmd, u8 databuffer[], u8 datasize) 152fdcfd4e7SHans Verkuil { 153fdcfd4e7SHans Verkuil int i, j; 154fdcfd4e7SHans Verkuil 155fdcfd4e7SHans Verkuil rds_write(aci, cmd); 156fdcfd4e7SHans Verkuil 157fdcfd4e7SHans Verkuil /* RDS_RESET doesn't need further processing */ 158fdcfd4e7SHans Verkuil if (cmd == RDS_RESET) 159fdcfd4e7SHans Verkuil return 0; 160fdcfd4e7SHans Verkuil if (rds_ack(aci)) 161fdcfd4e7SHans Verkuil return -EIO; 162fdcfd4e7SHans Verkuil if (datasize == 0) 163fdcfd4e7SHans Verkuil return 0; 164fdcfd4e7SHans Verkuil 165fdcfd4e7SHans Verkuil /* to be able to use rds_readcycle_nowait() 166fdcfd4e7SHans Verkuil I have to waitread() here */ 167fdcfd4e7SHans Verkuil if (rds_waitread(aci) < 0) 168fdcfd4e7SHans Verkuil return -1; 169fdcfd4e7SHans Verkuil 170fdcfd4e7SHans Verkuil memset(databuffer, 0, datasize); 171fdcfd4e7SHans Verkuil 172fdcfd4e7SHans Verkuil for (i = 0; i < 8 * datasize; i++) { 173fdcfd4e7SHans Verkuil j = rds_readcycle_nowait(aci); 174fdcfd4e7SHans Verkuil if (j < 0) 175fdcfd4e7SHans Verkuil return -EIO; 176fdcfd4e7SHans Verkuil databuffer[i / 8] |= RDS_DATA(j) << (7 - (i % 8)); 177fdcfd4e7SHans Verkuil } 178fdcfd4e7SHans Verkuil return 0; 179fdcfd4e7SHans Verkuil } 180fdcfd4e7SHans Verkuil 1818366fc39SKrzysztof Helt static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq) 1828366fc39SKrzysztof Helt { 1838366fc39SKrzysztof Helt unsigned char freql; 1848366fc39SKrzysztof Helt unsigned char freqh; 1858366fc39SKrzysztof Helt struct snd_miro_aci *aci = dev->aci; 1868366fc39SKrzysztof Helt 1878366fc39SKrzysztof Helt freq /= 160; 1888366fc39SKrzysztof Helt if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0)) 1898366fc39SKrzysztof Helt freq /= 10; /* I don't know exactly which version 1908366fc39SKrzysztof Helt * needs this hack */ 1918366fc39SKrzysztof Helt freql = freq & 0xff; 1928366fc39SKrzysztof Helt freqh = freq >> 8; 1938366fc39SKrzysztof Helt 19427dcb00dSWei Yongjun rds_cmd(aci, RDS_RESET, NULL, 0); 1958366fc39SKrzysztof Helt return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh); 1968366fc39SKrzysztof Helt } 1978366fc39SKrzysztof Helt 1988366fc39SKrzysztof Helt static int vidioc_querycap(struct file *file, void *priv, 1998366fc39SKrzysztof Helt struct v4l2_capability *v) 2008366fc39SKrzysztof Helt { 201f122d9a8SHans Verkuil struct pcm20 *dev = video_drvdata(file); 202f122d9a8SHans Verkuil 203c0decac1SMauro Carvalho Chehab strscpy(v->driver, "Miro PCM20", sizeof(v->driver)); 204c0decac1SMauro Carvalho Chehab strscpy(v->card, "Miro PCM20", sizeof(v->card)); 205f122d9a8SHans Verkuil snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", dev->v4l2_dev.name); 206fdcfd4e7SHans Verkuil v->device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE; 207f122d9a8SHans Verkuil v->capabilities = v->device_caps | V4L2_CAP_DEVICE_CAPS; 2088366fc39SKrzysztof Helt return 0; 2098366fc39SKrzysztof Helt } 2108366fc39SKrzysztof Helt 211fdcfd4e7SHans Verkuil static bool sanitize(char *p, int size) 212fdcfd4e7SHans Verkuil { 213fdcfd4e7SHans Verkuil int i; 214fdcfd4e7SHans Verkuil bool ret = true; 215fdcfd4e7SHans Verkuil 216fdcfd4e7SHans Verkuil for (i = 0; i < size; i++) { 2179c46c7faSMauro Carvalho Chehab if (p[i] < 32) { 218fdcfd4e7SHans Verkuil p[i] = ' '; 219fdcfd4e7SHans Verkuil ret = false; 220fdcfd4e7SHans Verkuil } 221fdcfd4e7SHans Verkuil } 222fdcfd4e7SHans Verkuil return ret; 223fdcfd4e7SHans Verkuil } 224fdcfd4e7SHans Verkuil 2258366fc39SKrzysztof Helt static int vidioc_g_tuner(struct file *file, void *priv, 2268366fc39SKrzysztof Helt struct v4l2_tuner *v) 2278366fc39SKrzysztof Helt { 228f5e7cc4aSHans Verkuil struct pcm20 *dev = video_drvdata(file); 2299bbc5820SHans Verkuil int res; 230fdcfd4e7SHans Verkuil u8 buf; 231f5e7cc4aSHans Verkuil 232f5e7cc4aSHans Verkuil if (v->index) 2338366fc39SKrzysztof Helt return -EINVAL; 234c0decac1SMauro Carvalho Chehab strscpy(v->name, "FM", sizeof(v->name)); 2358366fc39SKrzysztof Helt v->type = V4L2_TUNER_RADIO; 2368366fc39SKrzysztof Helt v->rangelow = 87*16000; 2378366fc39SKrzysztof Helt v->rangehigh = 108*16000; 2389bbc5820SHans Verkuil res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTATION, -1, -1); 2399bbc5820SHans Verkuil v->signal = (res & 0x80) ? 0 : 0xffff; 2409bbc5820SHans Verkuil /* Note: stereo detection does not work if the audio is muted, 2419bbc5820SHans Verkuil it will default to mono in that case. */ 2429bbc5820SHans Verkuil res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTEREO, -1, -1); 2439bbc5820SHans Verkuil v->rxsubchans = (res & 0x40) ? V4L2_TUNER_SUB_MONO : 2449bbc5820SHans Verkuil V4L2_TUNER_SUB_STEREO; 245fdcfd4e7SHans Verkuil v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 246fdcfd4e7SHans Verkuil V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_CONTROLS; 247f5e7cc4aSHans Verkuil v->audmode = dev->audmode; 248fdcfd4e7SHans Verkuil res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1); 249fdcfd4e7SHans Verkuil if (res >= 0 && buf) 250fdcfd4e7SHans Verkuil v->rxsubchans |= V4L2_TUNER_SUB_RDS; 2518366fc39SKrzysztof Helt return 0; 2528366fc39SKrzysztof Helt } 2538366fc39SKrzysztof Helt 2548366fc39SKrzysztof Helt static int vidioc_s_tuner(struct file *file, void *priv, 2552f73c7c5SHans Verkuil const struct v4l2_tuner *v) 2568366fc39SKrzysztof Helt { 257f5e7cc4aSHans Verkuil struct pcm20 *dev = video_drvdata(file); 258f5e7cc4aSHans Verkuil 259f5e7cc4aSHans Verkuil if (v->index) 260f5e7cc4aSHans Verkuil return -EINVAL; 261f5e7cc4aSHans Verkuil if (v->audmode > V4L2_TUNER_MODE_STEREO) 2622f73c7c5SHans Verkuil dev->audmode = V4L2_TUNER_MODE_STEREO; 2632f73c7c5SHans Verkuil else 2642f73c7c5SHans Verkuil dev->audmode = v->audmode; 265f5e7cc4aSHans Verkuil snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, 2662f73c7c5SHans Verkuil dev->audmode == V4L2_TUNER_MODE_MONO, -1); 267f5e7cc4aSHans Verkuil return 0; 2688366fc39SKrzysztof Helt } 2698366fc39SKrzysztof Helt 2708366fc39SKrzysztof Helt static int vidioc_g_frequency(struct file *file, void *priv, 2718366fc39SKrzysztof Helt struct v4l2_frequency *f) 2728366fc39SKrzysztof Helt { 2738366fc39SKrzysztof Helt struct pcm20 *dev = video_drvdata(file); 2748366fc39SKrzysztof Helt 2758366fc39SKrzysztof Helt if (f->tuner != 0) 2768366fc39SKrzysztof Helt return -EINVAL; 2778366fc39SKrzysztof Helt 2788366fc39SKrzysztof Helt f->type = V4L2_TUNER_RADIO; 2798366fc39SKrzysztof Helt f->frequency = dev->freq; 2808366fc39SKrzysztof Helt return 0; 2818366fc39SKrzysztof Helt } 2828366fc39SKrzysztof Helt 2838366fc39SKrzysztof Helt 2848366fc39SKrzysztof Helt static int vidioc_s_frequency(struct file *file, void *priv, 285b530a447SHans Verkuil const struct v4l2_frequency *f) 2868366fc39SKrzysztof Helt { 2878366fc39SKrzysztof Helt struct pcm20 *dev = video_drvdata(file); 2888366fc39SKrzysztof Helt 2898366fc39SKrzysztof Helt if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 2908366fc39SKrzysztof Helt return -EINVAL; 2918366fc39SKrzysztof Helt 292b530a447SHans Verkuil dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U); 293f5e7cc4aSHans Verkuil pcm20_setfreq(dev, dev->freq); 2948366fc39SKrzysztof Helt return 0; 2958366fc39SKrzysztof Helt } 2968366fc39SKrzysztof Helt 2977f51a610SHans Verkuil static int pcm20_s_ctrl(struct v4l2_ctrl *ctrl) 2988366fc39SKrzysztof Helt { 2997f51a610SHans Verkuil struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler); 3008366fc39SKrzysztof Helt 3018366fc39SKrzysztof Helt switch (ctrl->id) { 3028366fc39SKrzysztof Helt case V4L2_CID_AUDIO_MUTE: 303f5e7cc4aSHans Verkuil snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -1); 3048366fc39SKrzysztof Helt return 0; 3058366fc39SKrzysztof Helt } 3068366fc39SKrzysztof Helt return -EINVAL; 3078366fc39SKrzysztof Helt } 3088366fc39SKrzysztof Helt 309fdcfd4e7SHans Verkuil static int pcm20_thread(void *data) 310fdcfd4e7SHans Verkuil { 311fdcfd4e7SHans Verkuil struct pcm20 *dev = data; 312fdcfd4e7SHans Verkuil const unsigned no_rds_start_counter = 5; 313fdcfd4e7SHans Verkuil const unsigned sleep_msecs = 2000; 314fdcfd4e7SHans Verkuil unsigned no_rds_counter = no_rds_start_counter; 315fdcfd4e7SHans Verkuil 316fdcfd4e7SHans Verkuil for (;;) { 317fdcfd4e7SHans Verkuil char text_buffer[66]; 318fdcfd4e7SHans Verkuil u8 buf; 319fdcfd4e7SHans Verkuil int res; 320fdcfd4e7SHans Verkuil 321fdcfd4e7SHans Verkuil msleep_interruptible(sleep_msecs); 322fdcfd4e7SHans Verkuil 323fdcfd4e7SHans Verkuil if (kthread_should_stop()) 324fdcfd4e7SHans Verkuil break; 325fdcfd4e7SHans Verkuil 326fdcfd4e7SHans Verkuil res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1); 327fdcfd4e7SHans Verkuil if (res) 328fdcfd4e7SHans Verkuil continue; 329fdcfd4e7SHans Verkuil if (buf == 0) { 330fdcfd4e7SHans Verkuil if (no_rds_counter == 0) 331fdcfd4e7SHans Verkuil continue; 332fdcfd4e7SHans Verkuil no_rds_counter--; 333fdcfd4e7SHans Verkuil if (no_rds_counter) 334fdcfd4e7SHans Verkuil continue; 335fdcfd4e7SHans Verkuil 336fdcfd4e7SHans Verkuil /* 337fdcfd4e7SHans Verkuil * No RDS seen for no_rds_start_counter * sleep_msecs 338fdcfd4e7SHans Verkuil * milliseconds, clear all RDS controls to their 339fdcfd4e7SHans Verkuil * default values. 340fdcfd4e7SHans Verkuil */ 341fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, ""); 342fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_ms, 1); 343fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_ta, 0); 344fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_tp, 0); 345fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_pty, 0); 346fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, ""); 347fdcfd4e7SHans Verkuil continue; 348fdcfd4e7SHans Verkuil } 349fdcfd4e7SHans Verkuil no_rds_counter = no_rds_start_counter; 350fdcfd4e7SHans Verkuil 351fdcfd4e7SHans Verkuil res = rds_cmd(dev->aci, RDS_STATUS, &buf, 1); 352fdcfd4e7SHans Verkuil if (res) 353fdcfd4e7SHans Verkuil continue; 354fdcfd4e7SHans Verkuil if ((buf >> 3) & 1) { 355fdcfd4e7SHans Verkuil res = rds_cmd(dev->aci, RDS_STATIONNAME, text_buffer, 8); 356fdcfd4e7SHans Verkuil text_buffer[8] = 0; 357fdcfd4e7SHans Verkuil if (!res && sanitize(text_buffer, 8)) 358fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, text_buffer); 359fdcfd4e7SHans Verkuil } 360fdcfd4e7SHans Verkuil if ((buf >> 6) & 1) { 361fdcfd4e7SHans Verkuil u8 pty; 362fdcfd4e7SHans Verkuil 363fdcfd4e7SHans Verkuil res = rds_cmd(dev->aci, RDS_PTYTATP, &pty, 1); 364fdcfd4e7SHans Verkuil if (!res) { 365fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_ms, !!(pty & 0x01)); 366fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_ta, !!(pty & 0x02)); 367fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_tp, !!(pty & 0x80)); 368fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl(dev->rds_pty, (pty >> 2) & 0x1f); 369fdcfd4e7SHans Verkuil } 370fdcfd4e7SHans Verkuil } 371fdcfd4e7SHans Verkuil if ((buf >> 4) & 1) { 372fdcfd4e7SHans Verkuil res = rds_cmd(dev->aci, RDS_TEXT, text_buffer, 65); 373fdcfd4e7SHans Verkuil text_buffer[65] = 0; 374fdcfd4e7SHans Verkuil if (!res && sanitize(text_buffer + 1, 64)) 375fdcfd4e7SHans Verkuil v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, text_buffer + 1); 376fdcfd4e7SHans Verkuil } 377fdcfd4e7SHans Verkuil } 378fdcfd4e7SHans Verkuil return 0; 379fdcfd4e7SHans Verkuil } 380fdcfd4e7SHans Verkuil 381fdcfd4e7SHans Verkuil static int pcm20_open(struct file *file) 382fdcfd4e7SHans Verkuil { 383fdcfd4e7SHans Verkuil struct pcm20 *dev = video_drvdata(file); 384fdcfd4e7SHans Verkuil int res = v4l2_fh_open(file); 385fdcfd4e7SHans Verkuil 386fdcfd4e7SHans Verkuil if (!res && v4l2_fh_is_singular_file(file) && 387fdcfd4e7SHans Verkuil IS_ERR_OR_NULL(dev->kthread)) { 388fdcfd4e7SHans Verkuil dev->kthread = kthread_run(pcm20_thread, dev, "%s", 389fdcfd4e7SHans Verkuil dev->v4l2_dev.name); 390fdcfd4e7SHans Verkuil if (IS_ERR(dev->kthread)) { 391fdcfd4e7SHans Verkuil v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n"); 392fdcfd4e7SHans Verkuil v4l2_fh_release(file); 393fdcfd4e7SHans Verkuil return PTR_ERR(dev->kthread); 394fdcfd4e7SHans Verkuil } 395fdcfd4e7SHans Verkuil } 396fdcfd4e7SHans Verkuil return res; 397fdcfd4e7SHans Verkuil } 398fdcfd4e7SHans Verkuil 399fdcfd4e7SHans Verkuil static int pcm20_release(struct file *file) 400fdcfd4e7SHans Verkuil { 401fdcfd4e7SHans Verkuil struct pcm20 *dev = video_drvdata(file); 402fdcfd4e7SHans Verkuil 403fdcfd4e7SHans Verkuil if (v4l2_fh_is_singular_file(file) && !IS_ERR_OR_NULL(dev->kthread)) { 404fdcfd4e7SHans Verkuil kthread_stop(dev->kthread); 405fdcfd4e7SHans Verkuil dev->kthread = NULL; 406fdcfd4e7SHans Verkuil } 407fdcfd4e7SHans Verkuil return v4l2_fh_release(file); 408fdcfd4e7SHans Verkuil } 409fdcfd4e7SHans Verkuil 410fdcfd4e7SHans Verkuil static const struct v4l2_file_operations pcm20_fops = { 411fdcfd4e7SHans Verkuil .owner = THIS_MODULE, 412fdcfd4e7SHans Verkuil .open = pcm20_open, 413fdcfd4e7SHans Verkuil .poll = v4l2_ctrl_poll, 414fdcfd4e7SHans Verkuil .release = pcm20_release, 415fdcfd4e7SHans Verkuil .unlocked_ioctl = video_ioctl2, 416fdcfd4e7SHans Verkuil }; 417fdcfd4e7SHans Verkuil 4188366fc39SKrzysztof Helt static const struct v4l2_ioctl_ops pcm20_ioctl_ops = { 4198366fc39SKrzysztof Helt .vidioc_querycap = vidioc_querycap, 4208366fc39SKrzysztof Helt .vidioc_g_tuner = vidioc_g_tuner, 4218366fc39SKrzysztof Helt .vidioc_s_tuner = vidioc_s_tuner, 4228366fc39SKrzysztof Helt .vidioc_g_frequency = vidioc_g_frequency, 4238366fc39SKrzysztof Helt .vidioc_s_frequency = vidioc_s_frequency, 424f7c096f7SHans Verkuil .vidioc_log_status = v4l2_ctrl_log_status, 425f7c096f7SHans Verkuil .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 426f7c096f7SHans Verkuil .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 4277f51a610SHans Verkuil }; 4287f51a610SHans Verkuil 4297f51a610SHans Verkuil static const struct v4l2_ctrl_ops pcm20_ctrl_ops = { 4307f51a610SHans Verkuil .s_ctrl = pcm20_s_ctrl, 4318366fc39SKrzysztof Helt }; 4328366fc39SKrzysztof Helt 4338366fc39SKrzysztof Helt static int __init pcm20_init(void) 4348366fc39SKrzysztof Helt { 4358366fc39SKrzysztof Helt struct pcm20 *dev = &pcm20_card; 4368366fc39SKrzysztof Helt struct v4l2_device *v4l2_dev = &dev->v4l2_dev; 4377f51a610SHans Verkuil struct v4l2_ctrl_handler *hdl; 4388366fc39SKrzysztof Helt int res; 4398366fc39SKrzysztof Helt 4408366fc39SKrzysztof Helt dev->aci = snd_aci_get_aci(); 4418366fc39SKrzysztof Helt if (dev->aci == NULL) { 4428366fc39SKrzysztof Helt v4l2_err(v4l2_dev, 4438366fc39SKrzysztof Helt "you must load the snd-miro driver first!\n"); 4448366fc39SKrzysztof Helt return -ENODEV; 4458366fc39SKrzysztof Helt } 446c0decac1SMauro Carvalho Chehab strscpy(v4l2_dev->name, "radio-miropcm20", sizeof(v4l2_dev->name)); 44732958fddSHans Verkuil mutex_init(&dev->lock); 4488366fc39SKrzysztof Helt 4498366fc39SKrzysztof Helt res = v4l2_device_register(NULL, v4l2_dev); 4508366fc39SKrzysztof Helt if (res < 0) { 4518366fc39SKrzysztof Helt v4l2_err(v4l2_dev, "could not register v4l2_device\n"); 4528366fc39SKrzysztof Helt return -EINVAL; 4538366fc39SKrzysztof Helt } 4548366fc39SKrzysztof Helt 4557f51a610SHans Verkuil hdl = &dev->ctrl_handler; 456fdcfd4e7SHans Verkuil v4l2_ctrl_handler_init(hdl, 7); 4577f51a610SHans Verkuil v4l2_ctrl_new_std(hdl, &pcm20_ctrl_ops, 4587f51a610SHans Verkuil V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 459fdcfd4e7SHans Verkuil dev->rds_pty = v4l2_ctrl_new_std(hdl, NULL, 460fdcfd4e7SHans Verkuil V4L2_CID_RDS_RX_PTY, 0, 0x1f, 1, 0); 461fdcfd4e7SHans Verkuil dev->rds_ps_name = v4l2_ctrl_new_std(hdl, NULL, 462fdcfd4e7SHans Verkuil V4L2_CID_RDS_RX_PS_NAME, 0, 8, 8, 0); 463fdcfd4e7SHans Verkuil dev->rds_radio_test = v4l2_ctrl_new_std(hdl, NULL, 464fdcfd4e7SHans Verkuil V4L2_CID_RDS_RX_RADIO_TEXT, 0, 64, 64, 0); 465fdcfd4e7SHans Verkuil dev->rds_ta = v4l2_ctrl_new_std(hdl, NULL, 466fdcfd4e7SHans Verkuil V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT, 0, 1, 1, 0); 467fdcfd4e7SHans Verkuil dev->rds_tp = v4l2_ctrl_new_std(hdl, NULL, 468fdcfd4e7SHans Verkuil V4L2_CID_RDS_RX_TRAFFIC_PROGRAM, 0, 1, 1, 0); 469fdcfd4e7SHans Verkuil dev->rds_ms = v4l2_ctrl_new_std(hdl, NULL, 470fdcfd4e7SHans Verkuil V4L2_CID_RDS_RX_MUSIC_SPEECH, 0, 1, 1, 1); 4717f51a610SHans Verkuil v4l2_dev->ctrl_handler = hdl; 4727f51a610SHans Verkuil if (hdl->error) { 4737f51a610SHans Verkuil res = hdl->error; 4747f51a610SHans Verkuil v4l2_err(v4l2_dev, "Could not register control\n"); 4757f51a610SHans Verkuil goto err_hdl; 4767f51a610SHans Verkuil } 477c0decac1SMauro Carvalho Chehab strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); 4788366fc39SKrzysztof Helt dev->vdev.v4l2_dev = v4l2_dev; 4798366fc39SKrzysztof Helt dev->vdev.fops = &pcm20_fops; 4808366fc39SKrzysztof Helt dev->vdev.ioctl_ops = &pcm20_ioctl_ops; 4818366fc39SKrzysztof Helt dev->vdev.release = video_device_release_empty; 48232958fddSHans Verkuil dev->vdev.lock = &dev->lock; 4838366fc39SKrzysztof Helt video_set_drvdata(&dev->vdev, dev); 484f5e7cc4aSHans Verkuil snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, 485f5e7cc4aSHans Verkuil dev->audmode == V4L2_TUNER_MODE_MONO, -1); 486f5e7cc4aSHans Verkuil pcm20_setfreq(dev, dev->freq); 4878366fc39SKrzysztof Helt 4888366fc39SKrzysztof Helt if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) 4897f51a610SHans Verkuil goto err_hdl; 4908366fc39SKrzysztof Helt 4918366fc39SKrzysztof Helt v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n"); 4928366fc39SKrzysztof Helt return 0; 4937f51a610SHans Verkuil err_hdl: 4947f51a610SHans Verkuil v4l2_ctrl_handler_free(hdl); 4958366fc39SKrzysztof Helt v4l2_device_unregister(v4l2_dev); 4968366fc39SKrzysztof Helt return -EINVAL; 4978366fc39SKrzysztof Helt } 4988366fc39SKrzysztof Helt 4998366fc39SKrzysztof Helt MODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt"); 5008366fc39SKrzysztof Helt MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); 5018366fc39SKrzysztof Helt MODULE_LICENSE("GPL"); 5028366fc39SKrzysztof Helt 5038366fc39SKrzysztof Helt static void __exit pcm20_cleanup(void) 5048366fc39SKrzysztof Helt { 5058366fc39SKrzysztof Helt struct pcm20 *dev = &pcm20_card; 5068366fc39SKrzysztof Helt 5078366fc39SKrzysztof Helt video_unregister_device(&dev->vdev); 508f5e7cc4aSHans Verkuil snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, 1, -1); 5097f51a610SHans Verkuil v4l2_ctrl_handler_free(&dev->ctrl_handler); 5108366fc39SKrzysztof Helt v4l2_device_unregister(&dev->v4l2_dev); 5118366fc39SKrzysztof Helt } 5128366fc39SKrzysztof Helt 5138366fc39SKrzysztof Helt module_init(pcm20_init); 5148366fc39SKrzysztof Helt module_exit(pcm20_cleanup); 515