18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Miro PCM20 radio driver for Linux radio support 48c2ecf20Sopenharmony_ci * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> 58c2ecf20Sopenharmony_ci * Thanks to Norberto Pellici for the ACI device interface specification 68c2ecf20Sopenharmony_ci * The API part is based on the radiotrack driver by M. Kirkwood 78c2ecf20Sopenharmony_ci * This driver relies on the aci mixer provided by the snd-miro 88c2ecf20Sopenharmony_ci * ALSA driver. 98c2ecf20Sopenharmony_ci * Look there for further info... 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * From the original miro RDS sources: 128c2ecf20Sopenharmony_ci * 138c2ecf20Sopenharmony_ci * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de> 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * Many thanks to Fred Seidel <seidel@metabox.de>, the 168c2ecf20Sopenharmony_ci * designer of the RDS decoder hardware. With his help 178c2ecf20Sopenharmony_ci * I was able to code this driver. 188c2ecf20Sopenharmony_ci * Thanks also to Norberto Pellicci, Dominic Mounteney 198c2ecf20Sopenharmony_ci * <DMounteney@pinnaclesys.com> and www.teleauskunft.de 208c2ecf20Sopenharmony_ci * for good hints on finding Fred. It was somewhat hard 218c2ecf20Sopenharmony_ci * to locate him here in Germany... [: 228c2ecf20Sopenharmony_ci * 238c2ecf20Sopenharmony_ci * This code has been reintroduced and converted to use 248c2ecf20Sopenharmony_ci * the new V4L2 RDS API by: 258c2ecf20Sopenharmony_ci * 268c2ecf20Sopenharmony_ci * Hans Verkuil <hans.verkuil@cisco.com> 278c2ecf20Sopenharmony_ci */ 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#include <linux/module.h> 308c2ecf20Sopenharmony_ci#include <linux/init.h> 318c2ecf20Sopenharmony_ci#include <linux/io.h> 328c2ecf20Sopenharmony_ci#include <linux/delay.h> 338c2ecf20Sopenharmony_ci#include <linux/videodev2.h> 348c2ecf20Sopenharmony_ci#include <linux/kthread.h> 358c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 368c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 378c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h> 388c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h> 398c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 408c2ecf20Sopenharmony_ci#include <sound/aci.h> 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define RDS_DATASHIFT 2 /* Bit 2 */ 438c2ecf20Sopenharmony_ci#define RDS_DATAMASK (1 << RDS_DATASHIFT) 448c2ecf20Sopenharmony_ci#define RDS_BUSYMASK 0x10 /* Bit 4 */ 458c2ecf20Sopenharmony_ci#define RDS_CLOCKMASK 0x08 /* Bit 3 */ 468c2ecf20Sopenharmony_ci#define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1) 478c2ecf20Sopenharmony_ci 488c2ecf20Sopenharmony_ci#define RDS_STATUS 0x01 498c2ecf20Sopenharmony_ci#define RDS_STATIONNAME 0x02 508c2ecf20Sopenharmony_ci#define RDS_TEXT 0x03 518c2ecf20Sopenharmony_ci#define RDS_ALTFREQ 0x04 528c2ecf20Sopenharmony_ci#define RDS_TIMEDATE 0x05 538c2ecf20Sopenharmony_ci#define RDS_PI_CODE 0x06 548c2ecf20Sopenharmony_ci#define RDS_PTYTATP 0x07 558c2ecf20Sopenharmony_ci#define RDS_RESET 0x08 568c2ecf20Sopenharmony_ci#define RDS_RXVALUE 0x09 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_cistatic int radio_nr = -1; 598c2ecf20Sopenharmony_cimodule_param(radio_nr, int, 0); 608c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)"); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_cistruct pcm20 { 638c2ecf20Sopenharmony_ci struct v4l2_device v4l2_dev; 648c2ecf20Sopenharmony_ci struct video_device vdev; 658c2ecf20Sopenharmony_ci struct v4l2_ctrl_handler ctrl_handler; 668c2ecf20Sopenharmony_ci struct v4l2_ctrl *rds_pty; 678c2ecf20Sopenharmony_ci struct v4l2_ctrl *rds_ps_name; 688c2ecf20Sopenharmony_ci struct v4l2_ctrl *rds_radio_test; 698c2ecf20Sopenharmony_ci struct v4l2_ctrl *rds_ta; 708c2ecf20Sopenharmony_ci struct v4l2_ctrl *rds_tp; 718c2ecf20Sopenharmony_ci struct v4l2_ctrl *rds_ms; 728c2ecf20Sopenharmony_ci /* thread for periodic RDS status checking */ 738c2ecf20Sopenharmony_ci struct task_struct *kthread; 748c2ecf20Sopenharmony_ci unsigned long freq; 758c2ecf20Sopenharmony_ci u32 audmode; 768c2ecf20Sopenharmony_ci struct snd_miro_aci *aci; 778c2ecf20Sopenharmony_ci struct mutex lock; 788c2ecf20Sopenharmony_ci}; 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic struct pcm20 pcm20_card = { 818c2ecf20Sopenharmony_ci .freq = 87 * 16000, 828c2ecf20Sopenharmony_ci .audmode = V4L2_TUNER_MODE_STEREO, 838c2ecf20Sopenharmony_ci}; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_cistatic int rds_waitread(struct snd_miro_aci *aci) 878c2ecf20Sopenharmony_ci{ 888c2ecf20Sopenharmony_ci u8 byte; 898c2ecf20Sopenharmony_ci int i = 2000; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci do { 928c2ecf20Sopenharmony_ci byte = inb(aci->aci_port + ACI_REG_RDS); 938c2ecf20Sopenharmony_ci i--; 948c2ecf20Sopenharmony_ci } while ((byte & RDS_BUSYMASK) && i); 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci /* 978c2ecf20Sopenharmony_ci * It's magic, but without this the data that you read later on 988c2ecf20Sopenharmony_ci * is unreliable and full of bit errors. With this 1 usec delay 998c2ecf20Sopenharmony_ci * everything is fine. 1008c2ecf20Sopenharmony_ci */ 1018c2ecf20Sopenharmony_ci udelay(1); 1028c2ecf20Sopenharmony_ci return i ? byte : -1; 1038c2ecf20Sopenharmony_ci} 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_cistatic int rds_rawwrite(struct snd_miro_aci *aci, u8 byte) 1068c2ecf20Sopenharmony_ci{ 1078c2ecf20Sopenharmony_ci if (rds_waitread(aci) >= 0) { 1088c2ecf20Sopenharmony_ci outb(byte, aci->aci_port + ACI_REG_RDS); 1098c2ecf20Sopenharmony_ci return 0; 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci return -1; 1128c2ecf20Sopenharmony_ci} 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_cistatic int rds_write(struct snd_miro_aci *aci, u8 byte) 1158c2ecf20Sopenharmony_ci{ 1168c2ecf20Sopenharmony_ci u8 sendbuffer[8]; 1178c2ecf20Sopenharmony_ci int i; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci for (i = 7; i >= 0; i--) 1208c2ecf20Sopenharmony_ci sendbuffer[7 - i] = (byte & (1 << i)) ? RDS_DATAMASK : 0; 1218c2ecf20Sopenharmony_ci sendbuffer[0] |= RDS_CLOCKMASK; 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci for (i = 0; i < 8; i++) 1248c2ecf20Sopenharmony_ci rds_rawwrite(aci, sendbuffer[i]); 1258c2ecf20Sopenharmony_ci return 0; 1268c2ecf20Sopenharmony_ci} 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_cistatic int rds_readcycle_nowait(struct snd_miro_aci *aci) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci outb(0, aci->aci_port + ACI_REG_RDS); 1318c2ecf20Sopenharmony_ci return rds_waitread(aci); 1328c2ecf20Sopenharmony_ci} 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cistatic int rds_readcycle(struct snd_miro_aci *aci) 1358c2ecf20Sopenharmony_ci{ 1368c2ecf20Sopenharmony_ci if (rds_rawwrite(aci, 0) < 0) 1378c2ecf20Sopenharmony_ci return -1; 1388c2ecf20Sopenharmony_ci return rds_waitread(aci); 1398c2ecf20Sopenharmony_ci} 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_cistatic int rds_ack(struct snd_miro_aci *aci) 1428c2ecf20Sopenharmony_ci{ 1438c2ecf20Sopenharmony_ci int i = rds_readcycle(aci); 1448c2ecf20Sopenharmony_ci 1458c2ecf20Sopenharmony_ci if (i < 0) 1468c2ecf20Sopenharmony_ci return -1; 1478c2ecf20Sopenharmony_ci if (i & RDS_DATAMASK) 1488c2ecf20Sopenharmony_ci return 0; /* ACK */ 1498c2ecf20Sopenharmony_ci return 1; /* NACK */ 1508c2ecf20Sopenharmony_ci} 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_cistatic int rds_cmd(struct snd_miro_aci *aci, u8 cmd, u8 databuffer[], u8 datasize) 1538c2ecf20Sopenharmony_ci{ 1548c2ecf20Sopenharmony_ci int i, j; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci rds_write(aci, cmd); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci /* RDS_RESET doesn't need further processing */ 1598c2ecf20Sopenharmony_ci if (cmd == RDS_RESET) 1608c2ecf20Sopenharmony_ci return 0; 1618c2ecf20Sopenharmony_ci if (rds_ack(aci)) 1628c2ecf20Sopenharmony_ci return -EIO; 1638c2ecf20Sopenharmony_ci if (datasize == 0) 1648c2ecf20Sopenharmony_ci return 0; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci /* to be able to use rds_readcycle_nowait() 1678c2ecf20Sopenharmony_ci I have to waitread() here */ 1688c2ecf20Sopenharmony_ci if (rds_waitread(aci) < 0) 1698c2ecf20Sopenharmony_ci return -1; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci memset(databuffer, 0, datasize); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ci for (i = 0; i < 8 * datasize; i++) { 1748c2ecf20Sopenharmony_ci j = rds_readcycle_nowait(aci); 1758c2ecf20Sopenharmony_ci if (j < 0) 1768c2ecf20Sopenharmony_ci return -EIO; 1778c2ecf20Sopenharmony_ci databuffer[i / 8] |= RDS_DATA(j) << (7 - (i % 8)); 1788c2ecf20Sopenharmony_ci } 1798c2ecf20Sopenharmony_ci return 0; 1808c2ecf20Sopenharmony_ci} 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_cistatic int pcm20_setfreq(struct pcm20 *dev, unsigned long freq) 1838c2ecf20Sopenharmony_ci{ 1848c2ecf20Sopenharmony_ci unsigned char freql; 1858c2ecf20Sopenharmony_ci unsigned char freqh; 1868c2ecf20Sopenharmony_ci struct snd_miro_aci *aci = dev->aci; 1878c2ecf20Sopenharmony_ci 1888c2ecf20Sopenharmony_ci freq /= 160; 1898c2ecf20Sopenharmony_ci if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0)) 1908c2ecf20Sopenharmony_ci freq /= 10; /* I don't know exactly which version 1918c2ecf20Sopenharmony_ci * needs this hack */ 1928c2ecf20Sopenharmony_ci freql = freq & 0xff; 1938c2ecf20Sopenharmony_ci freqh = freq >> 8; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci rds_cmd(aci, RDS_RESET, NULL, 0); 1968c2ecf20Sopenharmony_ci return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh); 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 2008c2ecf20Sopenharmony_ci struct v4l2_capability *v) 2018c2ecf20Sopenharmony_ci{ 2028c2ecf20Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci strscpy(v->driver, "Miro PCM20", sizeof(v->driver)); 2058c2ecf20Sopenharmony_ci strscpy(v->card, "Miro PCM20", sizeof(v->card)); 2068c2ecf20Sopenharmony_ci snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", dev->v4l2_dev.name); 2078c2ecf20Sopenharmony_ci return 0; 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_cistatic bool sanitize(char *p, int size) 2118c2ecf20Sopenharmony_ci{ 2128c2ecf20Sopenharmony_ci int i; 2138c2ecf20Sopenharmony_ci bool ret = true; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci for (i = 0; i < size; i++) { 2168c2ecf20Sopenharmony_ci if (p[i] < 32) { 2178c2ecf20Sopenharmony_ci p[i] = ' '; 2188c2ecf20Sopenharmony_ci ret = false; 2198c2ecf20Sopenharmony_ci } 2208c2ecf20Sopenharmony_ci } 2218c2ecf20Sopenharmony_ci return ret; 2228c2ecf20Sopenharmony_ci} 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 2258c2ecf20Sopenharmony_ci struct v4l2_tuner *v) 2268c2ecf20Sopenharmony_ci{ 2278c2ecf20Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 2288c2ecf20Sopenharmony_ci int res; 2298c2ecf20Sopenharmony_ci u8 buf; 2308c2ecf20Sopenharmony_ci 2318c2ecf20Sopenharmony_ci if (v->index) 2328c2ecf20Sopenharmony_ci return -EINVAL; 2338c2ecf20Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 2348c2ecf20Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 2358c2ecf20Sopenharmony_ci v->rangelow = 87*16000; 2368c2ecf20Sopenharmony_ci v->rangehigh = 108*16000; 2378c2ecf20Sopenharmony_ci res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTATION, -1, -1); 2388c2ecf20Sopenharmony_ci v->signal = (res & 0x80) ? 0 : 0xffff; 2398c2ecf20Sopenharmony_ci /* Note: stereo detection does not work if the audio is muted, 2408c2ecf20Sopenharmony_ci it will default to mono in that case. */ 2418c2ecf20Sopenharmony_ci res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTEREO, -1, -1); 2428c2ecf20Sopenharmony_ci v->rxsubchans = (res & 0x40) ? V4L2_TUNER_SUB_MONO : 2438c2ecf20Sopenharmony_ci V4L2_TUNER_SUB_STEREO; 2448c2ecf20Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 2458c2ecf20Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_CONTROLS; 2468c2ecf20Sopenharmony_ci v->audmode = dev->audmode; 2478c2ecf20Sopenharmony_ci res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1); 2488c2ecf20Sopenharmony_ci if (res >= 0 && buf) 2498c2ecf20Sopenharmony_ci v->rxsubchans |= V4L2_TUNER_SUB_RDS; 2508c2ecf20Sopenharmony_ci return 0; 2518c2ecf20Sopenharmony_ci} 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 2548c2ecf20Sopenharmony_ci const struct v4l2_tuner *v) 2558c2ecf20Sopenharmony_ci{ 2568c2ecf20Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci if (v->index) 2598c2ecf20Sopenharmony_ci return -EINVAL; 2608c2ecf20Sopenharmony_ci if (v->audmode > V4L2_TUNER_MODE_STEREO) 2618c2ecf20Sopenharmony_ci dev->audmode = V4L2_TUNER_MODE_STEREO; 2628c2ecf20Sopenharmony_ci else 2638c2ecf20Sopenharmony_ci dev->audmode = v->audmode; 2648c2ecf20Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, 2658c2ecf20Sopenharmony_ci dev->audmode == V4L2_TUNER_MODE_MONO, -1); 2668c2ecf20Sopenharmony_ci return 0; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 2708c2ecf20Sopenharmony_ci struct v4l2_frequency *f) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci if (f->tuner != 0) 2758c2ecf20Sopenharmony_ci return -EINVAL; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 2788c2ecf20Sopenharmony_ci f->frequency = dev->freq; 2798c2ecf20Sopenharmony_ci return 0; 2808c2ecf20Sopenharmony_ci} 2818c2ecf20Sopenharmony_ci 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 2848c2ecf20Sopenharmony_ci const struct v4l2_frequency *f) 2858c2ecf20Sopenharmony_ci{ 2868c2ecf20Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 2898c2ecf20Sopenharmony_ci return -EINVAL; 2908c2ecf20Sopenharmony_ci 2918c2ecf20Sopenharmony_ci dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U); 2928c2ecf20Sopenharmony_ci pcm20_setfreq(dev, dev->freq); 2938c2ecf20Sopenharmony_ci return 0; 2948c2ecf20Sopenharmony_ci} 2958c2ecf20Sopenharmony_ci 2968c2ecf20Sopenharmony_cistatic int pcm20_s_ctrl(struct v4l2_ctrl *ctrl) 2978c2ecf20Sopenharmony_ci{ 2988c2ecf20Sopenharmony_ci struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci switch (ctrl->id) { 3018c2ecf20Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 3028c2ecf20Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -1); 3038c2ecf20Sopenharmony_ci return 0; 3048c2ecf20Sopenharmony_ci } 3058c2ecf20Sopenharmony_ci return -EINVAL; 3068c2ecf20Sopenharmony_ci} 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_cistatic int pcm20_thread(void *data) 3098c2ecf20Sopenharmony_ci{ 3108c2ecf20Sopenharmony_ci struct pcm20 *dev = data; 3118c2ecf20Sopenharmony_ci const unsigned no_rds_start_counter = 5; 3128c2ecf20Sopenharmony_ci const unsigned sleep_msecs = 2000; 3138c2ecf20Sopenharmony_ci unsigned no_rds_counter = no_rds_start_counter; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci for (;;) { 3168c2ecf20Sopenharmony_ci char text_buffer[66]; 3178c2ecf20Sopenharmony_ci u8 buf; 3188c2ecf20Sopenharmony_ci int res; 3198c2ecf20Sopenharmony_ci 3208c2ecf20Sopenharmony_ci msleep_interruptible(sleep_msecs); 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci if (kthread_should_stop()) 3238c2ecf20Sopenharmony_ci break; 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1); 3268c2ecf20Sopenharmony_ci if (res) 3278c2ecf20Sopenharmony_ci continue; 3288c2ecf20Sopenharmony_ci if (buf == 0) { 3298c2ecf20Sopenharmony_ci if (no_rds_counter == 0) 3308c2ecf20Sopenharmony_ci continue; 3318c2ecf20Sopenharmony_ci no_rds_counter--; 3328c2ecf20Sopenharmony_ci if (no_rds_counter) 3338c2ecf20Sopenharmony_ci continue; 3348c2ecf20Sopenharmony_ci 3358c2ecf20Sopenharmony_ci /* 3368c2ecf20Sopenharmony_ci * No RDS seen for no_rds_start_counter * sleep_msecs 3378c2ecf20Sopenharmony_ci * milliseconds, clear all RDS controls to their 3388c2ecf20Sopenharmony_ci * default values. 3398c2ecf20Sopenharmony_ci */ 3408c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, ""); 3418c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ms, 1); 3428c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ta, 0); 3438c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_tp, 0); 3448c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_pty, 0); 3458c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, ""); 3468c2ecf20Sopenharmony_ci continue; 3478c2ecf20Sopenharmony_ci } 3488c2ecf20Sopenharmony_ci no_rds_counter = no_rds_start_counter; 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci res = rds_cmd(dev->aci, RDS_STATUS, &buf, 1); 3518c2ecf20Sopenharmony_ci if (res) 3528c2ecf20Sopenharmony_ci continue; 3538c2ecf20Sopenharmony_ci if ((buf >> 3) & 1) { 3548c2ecf20Sopenharmony_ci res = rds_cmd(dev->aci, RDS_STATIONNAME, text_buffer, 8); 3558c2ecf20Sopenharmony_ci text_buffer[8] = 0; 3568c2ecf20Sopenharmony_ci if (!res && sanitize(text_buffer, 8)) 3578c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, text_buffer); 3588c2ecf20Sopenharmony_ci } 3598c2ecf20Sopenharmony_ci if ((buf >> 6) & 1) { 3608c2ecf20Sopenharmony_ci u8 pty; 3618c2ecf20Sopenharmony_ci 3628c2ecf20Sopenharmony_ci res = rds_cmd(dev->aci, RDS_PTYTATP, &pty, 1); 3638c2ecf20Sopenharmony_ci if (!res) { 3648c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ms, !!(pty & 0x01)); 3658c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ta, !!(pty & 0x02)); 3668c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_tp, !!(pty & 0x80)); 3678c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_pty, (pty >> 2) & 0x1f); 3688c2ecf20Sopenharmony_ci } 3698c2ecf20Sopenharmony_ci } 3708c2ecf20Sopenharmony_ci if ((buf >> 4) & 1) { 3718c2ecf20Sopenharmony_ci res = rds_cmd(dev->aci, RDS_TEXT, text_buffer, 65); 3728c2ecf20Sopenharmony_ci text_buffer[65] = 0; 3738c2ecf20Sopenharmony_ci if (!res && sanitize(text_buffer + 1, 64)) 3748c2ecf20Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, text_buffer + 1); 3758c2ecf20Sopenharmony_ci } 3768c2ecf20Sopenharmony_ci } 3778c2ecf20Sopenharmony_ci return 0; 3788c2ecf20Sopenharmony_ci} 3798c2ecf20Sopenharmony_ci 3808c2ecf20Sopenharmony_cistatic int pcm20_open(struct file *file) 3818c2ecf20Sopenharmony_ci{ 3828c2ecf20Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 3838c2ecf20Sopenharmony_ci int res = v4l2_fh_open(file); 3848c2ecf20Sopenharmony_ci 3858c2ecf20Sopenharmony_ci if (!res && v4l2_fh_is_singular_file(file) && 3868c2ecf20Sopenharmony_ci IS_ERR_OR_NULL(dev->kthread)) { 3878c2ecf20Sopenharmony_ci dev->kthread = kthread_run(pcm20_thread, dev, "%s", 3888c2ecf20Sopenharmony_ci dev->v4l2_dev.name); 3898c2ecf20Sopenharmony_ci if (IS_ERR(dev->kthread)) { 3908c2ecf20Sopenharmony_ci v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n"); 3918c2ecf20Sopenharmony_ci v4l2_fh_release(file); 3928c2ecf20Sopenharmony_ci return PTR_ERR(dev->kthread); 3938c2ecf20Sopenharmony_ci } 3948c2ecf20Sopenharmony_ci } 3958c2ecf20Sopenharmony_ci return res; 3968c2ecf20Sopenharmony_ci} 3978c2ecf20Sopenharmony_ci 3988c2ecf20Sopenharmony_cistatic int pcm20_release(struct file *file) 3998c2ecf20Sopenharmony_ci{ 4008c2ecf20Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci if (v4l2_fh_is_singular_file(file) && !IS_ERR_OR_NULL(dev->kthread)) { 4038c2ecf20Sopenharmony_ci kthread_stop(dev->kthread); 4048c2ecf20Sopenharmony_ci dev->kthread = NULL; 4058c2ecf20Sopenharmony_ci } 4068c2ecf20Sopenharmony_ci return v4l2_fh_release(file); 4078c2ecf20Sopenharmony_ci} 4088c2ecf20Sopenharmony_ci 4098c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations pcm20_fops = { 4108c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 4118c2ecf20Sopenharmony_ci .open = pcm20_open, 4128c2ecf20Sopenharmony_ci .poll = v4l2_ctrl_poll, 4138c2ecf20Sopenharmony_ci .release = pcm20_release, 4148c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 4158c2ecf20Sopenharmony_ci}; 4168c2ecf20Sopenharmony_ci 4178c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops pcm20_ioctl_ops = { 4188c2ecf20Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 4198c2ecf20Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 4208c2ecf20Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 4218c2ecf20Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 4228c2ecf20Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 4238c2ecf20Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 4248c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 4258c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 4268c2ecf20Sopenharmony_ci}; 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops pcm20_ctrl_ops = { 4298c2ecf20Sopenharmony_ci .s_ctrl = pcm20_s_ctrl, 4308c2ecf20Sopenharmony_ci}; 4318c2ecf20Sopenharmony_ci 4328c2ecf20Sopenharmony_cistatic int __init pcm20_init(void) 4338c2ecf20Sopenharmony_ci{ 4348c2ecf20Sopenharmony_ci struct pcm20 *dev = &pcm20_card; 4358c2ecf20Sopenharmony_ci struct v4l2_device *v4l2_dev = &dev->v4l2_dev; 4368c2ecf20Sopenharmony_ci struct v4l2_ctrl_handler *hdl; 4378c2ecf20Sopenharmony_ci int res; 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci dev->aci = snd_aci_get_aci(); 4408c2ecf20Sopenharmony_ci if (dev->aci == NULL) { 4418c2ecf20Sopenharmony_ci v4l2_err(v4l2_dev, 4428c2ecf20Sopenharmony_ci "you must load the snd-miro driver first!\n"); 4438c2ecf20Sopenharmony_ci return -ENODEV; 4448c2ecf20Sopenharmony_ci } 4458c2ecf20Sopenharmony_ci strscpy(v4l2_dev->name, "radio-miropcm20", sizeof(v4l2_dev->name)); 4468c2ecf20Sopenharmony_ci mutex_init(&dev->lock); 4478c2ecf20Sopenharmony_ci 4488c2ecf20Sopenharmony_ci res = v4l2_device_register(NULL, v4l2_dev); 4498c2ecf20Sopenharmony_ci if (res < 0) { 4508c2ecf20Sopenharmony_ci v4l2_err(v4l2_dev, "could not register v4l2_device\n"); 4518c2ecf20Sopenharmony_ci return -EINVAL; 4528c2ecf20Sopenharmony_ci } 4538c2ecf20Sopenharmony_ci 4548c2ecf20Sopenharmony_ci hdl = &dev->ctrl_handler; 4558c2ecf20Sopenharmony_ci v4l2_ctrl_handler_init(hdl, 7); 4568c2ecf20Sopenharmony_ci v4l2_ctrl_new_std(hdl, &pcm20_ctrl_ops, 4578c2ecf20Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 4588c2ecf20Sopenharmony_ci dev->rds_pty = v4l2_ctrl_new_std(hdl, NULL, 4598c2ecf20Sopenharmony_ci V4L2_CID_RDS_RX_PTY, 0, 0x1f, 1, 0); 4608c2ecf20Sopenharmony_ci dev->rds_ps_name = v4l2_ctrl_new_std(hdl, NULL, 4618c2ecf20Sopenharmony_ci V4L2_CID_RDS_RX_PS_NAME, 0, 8, 8, 0); 4628c2ecf20Sopenharmony_ci dev->rds_radio_test = v4l2_ctrl_new_std(hdl, NULL, 4638c2ecf20Sopenharmony_ci V4L2_CID_RDS_RX_RADIO_TEXT, 0, 64, 64, 0); 4648c2ecf20Sopenharmony_ci dev->rds_ta = v4l2_ctrl_new_std(hdl, NULL, 4658c2ecf20Sopenharmony_ci V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT, 0, 1, 1, 0); 4668c2ecf20Sopenharmony_ci dev->rds_tp = v4l2_ctrl_new_std(hdl, NULL, 4678c2ecf20Sopenharmony_ci V4L2_CID_RDS_RX_TRAFFIC_PROGRAM, 0, 1, 1, 0); 4688c2ecf20Sopenharmony_ci dev->rds_ms = v4l2_ctrl_new_std(hdl, NULL, 4698c2ecf20Sopenharmony_ci V4L2_CID_RDS_RX_MUSIC_SPEECH, 0, 1, 1, 1); 4708c2ecf20Sopenharmony_ci v4l2_dev->ctrl_handler = hdl; 4718c2ecf20Sopenharmony_ci if (hdl->error) { 4728c2ecf20Sopenharmony_ci res = hdl->error; 4738c2ecf20Sopenharmony_ci v4l2_err(v4l2_dev, "Could not register control\n"); 4748c2ecf20Sopenharmony_ci goto err_hdl; 4758c2ecf20Sopenharmony_ci } 4768c2ecf20Sopenharmony_ci strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); 4778c2ecf20Sopenharmony_ci dev->vdev.v4l2_dev = v4l2_dev; 4788c2ecf20Sopenharmony_ci dev->vdev.fops = &pcm20_fops; 4798c2ecf20Sopenharmony_ci dev->vdev.ioctl_ops = &pcm20_ioctl_ops; 4808c2ecf20Sopenharmony_ci dev->vdev.release = video_device_release_empty; 4818c2ecf20Sopenharmony_ci dev->vdev.lock = &dev->lock; 4828c2ecf20Sopenharmony_ci dev->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | 4838c2ecf20Sopenharmony_ci V4L2_CAP_RDS_CAPTURE; 4848c2ecf20Sopenharmony_ci video_set_drvdata(&dev->vdev, dev); 4858c2ecf20Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, 4868c2ecf20Sopenharmony_ci dev->audmode == V4L2_TUNER_MODE_MONO, -1); 4878c2ecf20Sopenharmony_ci pcm20_setfreq(dev, dev->freq); 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) 4908c2ecf20Sopenharmony_ci goto err_hdl; 4918c2ecf20Sopenharmony_ci 4928c2ecf20Sopenharmony_ci v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n"); 4938c2ecf20Sopenharmony_ci return 0; 4948c2ecf20Sopenharmony_cierr_hdl: 4958c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(hdl); 4968c2ecf20Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 4978c2ecf20Sopenharmony_ci return -EINVAL; 4988c2ecf20Sopenharmony_ci} 4998c2ecf20Sopenharmony_ci 5008c2ecf20Sopenharmony_ciMODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt"); 5018c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); 5028c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 5038c2ecf20Sopenharmony_ci 5048c2ecf20Sopenharmony_cistatic void __exit pcm20_cleanup(void) 5058c2ecf20Sopenharmony_ci{ 5068c2ecf20Sopenharmony_ci struct pcm20 *dev = &pcm20_card; 5078c2ecf20Sopenharmony_ci 5088c2ecf20Sopenharmony_ci video_unregister_device(&dev->vdev); 5098c2ecf20Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, 1, -1); 5108c2ecf20Sopenharmony_ci v4l2_ctrl_handler_free(&dev->ctrl_handler); 5118c2ecf20Sopenharmony_ci v4l2_device_unregister(&dev->v4l2_dev); 5128c2ecf20Sopenharmony_ci} 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_cimodule_init(pcm20_init); 5158c2ecf20Sopenharmony_cimodule_exit(pcm20_cleanup); 516