162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Miro PCM20 radio driver for Linux radio support 462306a36Sopenharmony_ci * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl> 562306a36Sopenharmony_ci * Thanks to Norberto Pellici for the ACI device interface specification 662306a36Sopenharmony_ci * The API part is based on the radiotrack driver by M. Kirkwood 762306a36Sopenharmony_ci * This driver relies on the aci mixer provided by the snd-miro 862306a36Sopenharmony_ci * ALSA driver. 962306a36Sopenharmony_ci * Look there for further info... 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * From the original miro RDS sources: 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * (c) 2001 Robert Siemer <Robert.Siemer@gmx.de> 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * Many thanks to Fred Seidel <seidel@metabox.de>, the 1662306a36Sopenharmony_ci * designer of the RDS decoder hardware. With his help 1762306a36Sopenharmony_ci * I was able to code this driver. 1862306a36Sopenharmony_ci * Thanks also to Norberto Pellicci, Dominic Mounteney 1962306a36Sopenharmony_ci * <DMounteney@pinnaclesys.com> and www.teleauskunft.de 2062306a36Sopenharmony_ci * for good hints on finding Fred. It was somewhat hard 2162306a36Sopenharmony_ci * to locate him here in Germany... [: 2262306a36Sopenharmony_ci * 2362306a36Sopenharmony_ci * This code has been reintroduced and converted to use 2462306a36Sopenharmony_ci * the new V4L2 RDS API by: 2562306a36Sopenharmony_ci * 2662306a36Sopenharmony_ci * Hans Verkuil <hans.verkuil@cisco.com> 2762306a36Sopenharmony_ci */ 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_ci#include <linux/module.h> 3062306a36Sopenharmony_ci#include <linux/init.h> 3162306a36Sopenharmony_ci#include <linux/io.h> 3262306a36Sopenharmony_ci#include <linux/delay.h> 3362306a36Sopenharmony_ci#include <linux/videodev2.h> 3462306a36Sopenharmony_ci#include <linux/kthread.h> 3562306a36Sopenharmony_ci#include <media/v4l2-device.h> 3662306a36Sopenharmony_ci#include <media/v4l2-ioctl.h> 3762306a36Sopenharmony_ci#include <media/v4l2-ctrls.h> 3862306a36Sopenharmony_ci#include <media/v4l2-fh.h> 3962306a36Sopenharmony_ci#include <media/v4l2-event.h> 4062306a36Sopenharmony_ci#include <sound/aci.h> 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci#define RDS_DATASHIFT 2 /* Bit 2 */ 4362306a36Sopenharmony_ci#define RDS_DATAMASK (1 << RDS_DATASHIFT) 4462306a36Sopenharmony_ci#define RDS_BUSYMASK 0x10 /* Bit 4 */ 4562306a36Sopenharmony_ci#define RDS_CLOCKMASK 0x08 /* Bit 3 */ 4662306a36Sopenharmony_ci#define RDS_DATA(x) (((x) >> RDS_DATASHIFT) & 1) 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci#define RDS_STATUS 0x01 4962306a36Sopenharmony_ci#define RDS_STATIONNAME 0x02 5062306a36Sopenharmony_ci#define RDS_TEXT 0x03 5162306a36Sopenharmony_ci#define RDS_ALTFREQ 0x04 5262306a36Sopenharmony_ci#define RDS_TIMEDATE 0x05 5362306a36Sopenharmony_ci#define RDS_PI_CODE 0x06 5462306a36Sopenharmony_ci#define RDS_PTYTATP 0x07 5562306a36Sopenharmony_ci#define RDS_RESET 0x08 5662306a36Sopenharmony_ci#define RDS_RXVALUE 0x09 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_cistatic int radio_nr = -1; 5962306a36Sopenharmony_cimodule_param(radio_nr, int, 0); 6062306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)"); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_cistruct pcm20 { 6362306a36Sopenharmony_ci struct v4l2_device v4l2_dev; 6462306a36Sopenharmony_ci struct video_device vdev; 6562306a36Sopenharmony_ci struct v4l2_ctrl_handler ctrl_handler; 6662306a36Sopenharmony_ci struct v4l2_ctrl *rds_pty; 6762306a36Sopenharmony_ci struct v4l2_ctrl *rds_ps_name; 6862306a36Sopenharmony_ci struct v4l2_ctrl *rds_radio_test; 6962306a36Sopenharmony_ci struct v4l2_ctrl *rds_ta; 7062306a36Sopenharmony_ci struct v4l2_ctrl *rds_tp; 7162306a36Sopenharmony_ci struct v4l2_ctrl *rds_ms; 7262306a36Sopenharmony_ci /* thread for periodic RDS status checking */ 7362306a36Sopenharmony_ci struct task_struct *kthread; 7462306a36Sopenharmony_ci unsigned long freq; 7562306a36Sopenharmony_ci u32 audmode; 7662306a36Sopenharmony_ci struct snd_miro_aci *aci; 7762306a36Sopenharmony_ci struct mutex lock; 7862306a36Sopenharmony_ci}; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_cistatic struct pcm20 pcm20_card = { 8162306a36Sopenharmony_ci .freq = 87 * 16000, 8262306a36Sopenharmony_ci .audmode = V4L2_TUNER_MODE_STEREO, 8362306a36Sopenharmony_ci}; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_cistatic int rds_waitread(struct snd_miro_aci *aci) 8762306a36Sopenharmony_ci{ 8862306a36Sopenharmony_ci u8 byte; 8962306a36Sopenharmony_ci int i = 2000; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci do { 9262306a36Sopenharmony_ci byte = inb(aci->aci_port + ACI_REG_RDS); 9362306a36Sopenharmony_ci i--; 9462306a36Sopenharmony_ci } while ((byte & RDS_BUSYMASK) && i); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci /* 9762306a36Sopenharmony_ci * It's magic, but without this the data that you read later on 9862306a36Sopenharmony_ci * is unreliable and full of bit errors. With this 1 usec delay 9962306a36Sopenharmony_ci * everything is fine. 10062306a36Sopenharmony_ci */ 10162306a36Sopenharmony_ci udelay(1); 10262306a36Sopenharmony_ci return i ? byte : -1; 10362306a36Sopenharmony_ci} 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_cistatic int rds_rawwrite(struct snd_miro_aci *aci, u8 byte) 10662306a36Sopenharmony_ci{ 10762306a36Sopenharmony_ci if (rds_waitread(aci) >= 0) { 10862306a36Sopenharmony_ci outb(byte, aci->aci_port + ACI_REG_RDS); 10962306a36Sopenharmony_ci return 0; 11062306a36Sopenharmony_ci } 11162306a36Sopenharmony_ci return -1; 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic int rds_write(struct snd_miro_aci *aci, u8 byte) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci u8 sendbuffer[8]; 11762306a36Sopenharmony_ci int i; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci for (i = 7; i >= 0; i--) 12062306a36Sopenharmony_ci sendbuffer[7 - i] = (byte & (1 << i)) ? RDS_DATAMASK : 0; 12162306a36Sopenharmony_ci sendbuffer[0] |= RDS_CLOCKMASK; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci for (i = 0; i < 8; i++) 12462306a36Sopenharmony_ci rds_rawwrite(aci, sendbuffer[i]); 12562306a36Sopenharmony_ci return 0; 12662306a36Sopenharmony_ci} 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_cistatic int rds_readcycle_nowait(struct snd_miro_aci *aci) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci outb(0, aci->aci_port + ACI_REG_RDS); 13162306a36Sopenharmony_ci return rds_waitread(aci); 13262306a36Sopenharmony_ci} 13362306a36Sopenharmony_ci 13462306a36Sopenharmony_cistatic int rds_readcycle(struct snd_miro_aci *aci) 13562306a36Sopenharmony_ci{ 13662306a36Sopenharmony_ci if (rds_rawwrite(aci, 0) < 0) 13762306a36Sopenharmony_ci return -1; 13862306a36Sopenharmony_ci return rds_waitread(aci); 13962306a36Sopenharmony_ci} 14062306a36Sopenharmony_ci 14162306a36Sopenharmony_cistatic int rds_ack(struct snd_miro_aci *aci) 14262306a36Sopenharmony_ci{ 14362306a36Sopenharmony_ci int i = rds_readcycle(aci); 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci if (i < 0) 14662306a36Sopenharmony_ci return -1; 14762306a36Sopenharmony_ci if (i & RDS_DATAMASK) 14862306a36Sopenharmony_ci return 0; /* ACK */ 14962306a36Sopenharmony_ci return 1; /* NACK */ 15062306a36Sopenharmony_ci} 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_cistatic int rds_cmd(struct snd_miro_aci *aci, u8 cmd, u8 databuffer[], u8 datasize) 15362306a36Sopenharmony_ci{ 15462306a36Sopenharmony_ci int i, j; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci rds_write(aci, cmd); 15762306a36Sopenharmony_ci 15862306a36Sopenharmony_ci /* RDS_RESET doesn't need further processing */ 15962306a36Sopenharmony_ci if (cmd == RDS_RESET) 16062306a36Sopenharmony_ci return 0; 16162306a36Sopenharmony_ci if (rds_ack(aci)) 16262306a36Sopenharmony_ci return -EIO; 16362306a36Sopenharmony_ci if (datasize == 0) 16462306a36Sopenharmony_ci return 0; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci /* to be able to use rds_readcycle_nowait() 16762306a36Sopenharmony_ci I have to waitread() here */ 16862306a36Sopenharmony_ci if (rds_waitread(aci) < 0) 16962306a36Sopenharmony_ci return -1; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ci memset(databuffer, 0, datasize); 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci for (i = 0; i < 8 * datasize; i++) { 17462306a36Sopenharmony_ci j = rds_readcycle_nowait(aci); 17562306a36Sopenharmony_ci if (j < 0) 17662306a36Sopenharmony_ci return -EIO; 17762306a36Sopenharmony_ci databuffer[i / 8] |= RDS_DATA(j) << (7 - (i % 8)); 17862306a36Sopenharmony_ci } 17962306a36Sopenharmony_ci return 0; 18062306a36Sopenharmony_ci} 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_cistatic int pcm20_setfreq(struct pcm20 *dev, unsigned long freq) 18362306a36Sopenharmony_ci{ 18462306a36Sopenharmony_ci unsigned char freql; 18562306a36Sopenharmony_ci unsigned char freqh; 18662306a36Sopenharmony_ci struct snd_miro_aci *aci = dev->aci; 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci freq /= 160; 18962306a36Sopenharmony_ci if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0)) 19062306a36Sopenharmony_ci freq /= 10; /* I don't know exactly which version 19162306a36Sopenharmony_ci * needs this hack */ 19262306a36Sopenharmony_ci freql = freq & 0xff; 19362306a36Sopenharmony_ci freqh = freq >> 8; 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci rds_cmd(aci, RDS_RESET, NULL, 0); 19662306a36Sopenharmony_ci return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh); 19762306a36Sopenharmony_ci} 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_cistatic int vidioc_querycap(struct file *file, void *priv, 20062306a36Sopenharmony_ci struct v4l2_capability *v) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci strscpy(v->driver, "Miro PCM20", sizeof(v->driver)); 20562306a36Sopenharmony_ci strscpy(v->card, "Miro PCM20", sizeof(v->card)); 20662306a36Sopenharmony_ci snprintf(v->bus_info, sizeof(v->bus_info), "ISA:%s", dev->v4l2_dev.name); 20762306a36Sopenharmony_ci return 0; 20862306a36Sopenharmony_ci} 20962306a36Sopenharmony_ci 21062306a36Sopenharmony_cistatic bool sanitize(char *p, int size) 21162306a36Sopenharmony_ci{ 21262306a36Sopenharmony_ci int i; 21362306a36Sopenharmony_ci bool ret = true; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci for (i = 0; i < size; i++) { 21662306a36Sopenharmony_ci if (p[i] < 32) { 21762306a36Sopenharmony_ci p[i] = ' '; 21862306a36Sopenharmony_ci ret = false; 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci } 22162306a36Sopenharmony_ci return ret; 22262306a36Sopenharmony_ci} 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_cistatic int vidioc_g_tuner(struct file *file, void *priv, 22562306a36Sopenharmony_ci struct v4l2_tuner *v) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 22862306a36Sopenharmony_ci int res; 22962306a36Sopenharmony_ci u8 buf; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci if (v->index) 23262306a36Sopenharmony_ci return -EINVAL; 23362306a36Sopenharmony_ci strscpy(v->name, "FM", sizeof(v->name)); 23462306a36Sopenharmony_ci v->type = V4L2_TUNER_RADIO; 23562306a36Sopenharmony_ci v->rangelow = 87*16000; 23662306a36Sopenharmony_ci v->rangehigh = 108*16000; 23762306a36Sopenharmony_ci res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTATION, -1, -1); 23862306a36Sopenharmony_ci v->signal = (res & 0x80) ? 0 : 0xffff; 23962306a36Sopenharmony_ci /* Note: stereo detection does not work if the audio is muted, 24062306a36Sopenharmony_ci it will default to mono in that case. */ 24162306a36Sopenharmony_ci res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTEREO, -1, -1); 24262306a36Sopenharmony_ci v->rxsubchans = (res & 0x40) ? V4L2_TUNER_SUB_MONO : 24362306a36Sopenharmony_ci V4L2_TUNER_SUB_STEREO; 24462306a36Sopenharmony_ci v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO | 24562306a36Sopenharmony_ci V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_CONTROLS; 24662306a36Sopenharmony_ci v->audmode = dev->audmode; 24762306a36Sopenharmony_ci res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1); 24862306a36Sopenharmony_ci if (res >= 0 && buf) 24962306a36Sopenharmony_ci v->rxsubchans |= V4L2_TUNER_SUB_RDS; 25062306a36Sopenharmony_ci return 0; 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_cistatic int vidioc_s_tuner(struct file *file, void *priv, 25462306a36Sopenharmony_ci const struct v4l2_tuner *v) 25562306a36Sopenharmony_ci{ 25662306a36Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci if (v->index) 25962306a36Sopenharmony_ci return -EINVAL; 26062306a36Sopenharmony_ci if (v->audmode > V4L2_TUNER_MODE_STEREO) 26162306a36Sopenharmony_ci dev->audmode = V4L2_TUNER_MODE_STEREO; 26262306a36Sopenharmony_ci else 26362306a36Sopenharmony_ci dev->audmode = v->audmode; 26462306a36Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, 26562306a36Sopenharmony_ci dev->audmode == V4L2_TUNER_MODE_MONO, -1); 26662306a36Sopenharmony_ci return 0; 26762306a36Sopenharmony_ci} 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_cistatic int vidioc_g_frequency(struct file *file, void *priv, 27062306a36Sopenharmony_ci struct v4l2_frequency *f) 27162306a36Sopenharmony_ci{ 27262306a36Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (f->tuner != 0) 27562306a36Sopenharmony_ci return -EINVAL; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci f->type = V4L2_TUNER_RADIO; 27862306a36Sopenharmony_ci f->frequency = dev->freq; 27962306a36Sopenharmony_ci return 0; 28062306a36Sopenharmony_ci} 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci 28362306a36Sopenharmony_cistatic int vidioc_s_frequency(struct file *file, void *priv, 28462306a36Sopenharmony_ci const struct v4l2_frequency *f) 28562306a36Sopenharmony_ci{ 28662306a36Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) 28962306a36Sopenharmony_ci return -EINVAL; 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U); 29262306a36Sopenharmony_ci pcm20_setfreq(dev, dev->freq); 29362306a36Sopenharmony_ci return 0; 29462306a36Sopenharmony_ci} 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_cistatic int pcm20_s_ctrl(struct v4l2_ctrl *ctrl) 29762306a36Sopenharmony_ci{ 29862306a36Sopenharmony_ci struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler); 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci switch (ctrl->id) { 30162306a36Sopenharmony_ci case V4L2_CID_AUDIO_MUTE: 30262306a36Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -1); 30362306a36Sopenharmony_ci return 0; 30462306a36Sopenharmony_ci } 30562306a36Sopenharmony_ci return -EINVAL; 30662306a36Sopenharmony_ci} 30762306a36Sopenharmony_ci 30862306a36Sopenharmony_cistatic int pcm20_thread(void *data) 30962306a36Sopenharmony_ci{ 31062306a36Sopenharmony_ci struct pcm20 *dev = data; 31162306a36Sopenharmony_ci const unsigned no_rds_start_counter = 5; 31262306a36Sopenharmony_ci const unsigned sleep_msecs = 2000; 31362306a36Sopenharmony_ci unsigned no_rds_counter = no_rds_start_counter; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci for (;;) { 31662306a36Sopenharmony_ci char text_buffer[66]; 31762306a36Sopenharmony_ci u8 buf; 31862306a36Sopenharmony_ci int res; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci msleep_interruptible(sleep_msecs); 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (kthread_should_stop()) 32362306a36Sopenharmony_ci break; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1); 32662306a36Sopenharmony_ci if (res) 32762306a36Sopenharmony_ci continue; 32862306a36Sopenharmony_ci if (buf == 0) { 32962306a36Sopenharmony_ci if (no_rds_counter == 0) 33062306a36Sopenharmony_ci continue; 33162306a36Sopenharmony_ci no_rds_counter--; 33262306a36Sopenharmony_ci if (no_rds_counter) 33362306a36Sopenharmony_ci continue; 33462306a36Sopenharmony_ci 33562306a36Sopenharmony_ci /* 33662306a36Sopenharmony_ci * No RDS seen for no_rds_start_counter * sleep_msecs 33762306a36Sopenharmony_ci * milliseconds, clear all RDS controls to their 33862306a36Sopenharmony_ci * default values. 33962306a36Sopenharmony_ci */ 34062306a36Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, ""); 34162306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ms, 1); 34262306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ta, 0); 34362306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_tp, 0); 34462306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_pty, 0); 34562306a36Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, ""); 34662306a36Sopenharmony_ci continue; 34762306a36Sopenharmony_ci } 34862306a36Sopenharmony_ci no_rds_counter = no_rds_start_counter; 34962306a36Sopenharmony_ci 35062306a36Sopenharmony_ci res = rds_cmd(dev->aci, RDS_STATUS, &buf, 1); 35162306a36Sopenharmony_ci if (res) 35262306a36Sopenharmony_ci continue; 35362306a36Sopenharmony_ci if ((buf >> 3) & 1) { 35462306a36Sopenharmony_ci res = rds_cmd(dev->aci, RDS_STATIONNAME, text_buffer, 8); 35562306a36Sopenharmony_ci text_buffer[8] = 0; 35662306a36Sopenharmony_ci if (!res && sanitize(text_buffer, 8)) 35762306a36Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, text_buffer); 35862306a36Sopenharmony_ci } 35962306a36Sopenharmony_ci if ((buf >> 6) & 1) { 36062306a36Sopenharmony_ci u8 pty; 36162306a36Sopenharmony_ci 36262306a36Sopenharmony_ci res = rds_cmd(dev->aci, RDS_PTYTATP, &pty, 1); 36362306a36Sopenharmony_ci if (!res) { 36462306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ms, !!(pty & 0x01)); 36562306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_ta, !!(pty & 0x02)); 36662306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_tp, !!(pty & 0x80)); 36762306a36Sopenharmony_ci v4l2_ctrl_s_ctrl(dev->rds_pty, (pty >> 2) & 0x1f); 36862306a36Sopenharmony_ci } 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci if ((buf >> 4) & 1) { 37162306a36Sopenharmony_ci res = rds_cmd(dev->aci, RDS_TEXT, text_buffer, 65); 37262306a36Sopenharmony_ci text_buffer[65] = 0; 37362306a36Sopenharmony_ci if (!res && sanitize(text_buffer + 1, 64)) 37462306a36Sopenharmony_ci v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, text_buffer + 1); 37562306a36Sopenharmony_ci } 37662306a36Sopenharmony_ci } 37762306a36Sopenharmony_ci return 0; 37862306a36Sopenharmony_ci} 37962306a36Sopenharmony_ci 38062306a36Sopenharmony_cistatic int pcm20_open(struct file *file) 38162306a36Sopenharmony_ci{ 38262306a36Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 38362306a36Sopenharmony_ci int res = v4l2_fh_open(file); 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (!res && v4l2_fh_is_singular_file(file) && 38662306a36Sopenharmony_ci IS_ERR_OR_NULL(dev->kthread)) { 38762306a36Sopenharmony_ci dev->kthread = kthread_run(pcm20_thread, dev, "%s", 38862306a36Sopenharmony_ci dev->v4l2_dev.name); 38962306a36Sopenharmony_ci if (IS_ERR(dev->kthread)) { 39062306a36Sopenharmony_ci v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n"); 39162306a36Sopenharmony_ci v4l2_fh_release(file); 39262306a36Sopenharmony_ci return PTR_ERR(dev->kthread); 39362306a36Sopenharmony_ci } 39462306a36Sopenharmony_ci } 39562306a36Sopenharmony_ci return res; 39662306a36Sopenharmony_ci} 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_cistatic int pcm20_release(struct file *file) 39962306a36Sopenharmony_ci{ 40062306a36Sopenharmony_ci struct pcm20 *dev = video_drvdata(file); 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci if (v4l2_fh_is_singular_file(file) && !IS_ERR_OR_NULL(dev->kthread)) { 40362306a36Sopenharmony_ci kthread_stop(dev->kthread); 40462306a36Sopenharmony_ci dev->kthread = NULL; 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci return v4l2_fh_release(file); 40762306a36Sopenharmony_ci} 40862306a36Sopenharmony_ci 40962306a36Sopenharmony_cistatic const struct v4l2_file_operations pcm20_fops = { 41062306a36Sopenharmony_ci .owner = THIS_MODULE, 41162306a36Sopenharmony_ci .open = pcm20_open, 41262306a36Sopenharmony_ci .poll = v4l2_ctrl_poll, 41362306a36Sopenharmony_ci .release = pcm20_release, 41462306a36Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 41562306a36Sopenharmony_ci}; 41662306a36Sopenharmony_ci 41762306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops pcm20_ioctl_ops = { 41862306a36Sopenharmony_ci .vidioc_querycap = vidioc_querycap, 41962306a36Sopenharmony_ci .vidioc_g_tuner = vidioc_g_tuner, 42062306a36Sopenharmony_ci .vidioc_s_tuner = vidioc_s_tuner, 42162306a36Sopenharmony_ci .vidioc_g_frequency = vidioc_g_frequency, 42262306a36Sopenharmony_ci .vidioc_s_frequency = vidioc_s_frequency, 42362306a36Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 42462306a36Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 42562306a36Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 42662306a36Sopenharmony_ci}; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops pcm20_ctrl_ops = { 42962306a36Sopenharmony_ci .s_ctrl = pcm20_s_ctrl, 43062306a36Sopenharmony_ci}; 43162306a36Sopenharmony_ci 43262306a36Sopenharmony_cistatic int __init pcm20_init(void) 43362306a36Sopenharmony_ci{ 43462306a36Sopenharmony_ci struct pcm20 *dev = &pcm20_card; 43562306a36Sopenharmony_ci struct v4l2_device *v4l2_dev = &dev->v4l2_dev; 43662306a36Sopenharmony_ci struct v4l2_ctrl_handler *hdl; 43762306a36Sopenharmony_ci int res; 43862306a36Sopenharmony_ci 43962306a36Sopenharmony_ci dev->aci = snd_aci_get_aci(); 44062306a36Sopenharmony_ci if (dev->aci == NULL) { 44162306a36Sopenharmony_ci v4l2_err(v4l2_dev, 44262306a36Sopenharmony_ci "you must load the snd-miro driver first!\n"); 44362306a36Sopenharmony_ci return -ENODEV; 44462306a36Sopenharmony_ci } 44562306a36Sopenharmony_ci strscpy(v4l2_dev->name, "radio-miropcm20", sizeof(v4l2_dev->name)); 44662306a36Sopenharmony_ci mutex_init(&dev->lock); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci res = v4l2_device_register(NULL, v4l2_dev); 44962306a36Sopenharmony_ci if (res < 0) { 45062306a36Sopenharmony_ci v4l2_err(v4l2_dev, "could not register v4l2_device\n"); 45162306a36Sopenharmony_ci return -EINVAL; 45262306a36Sopenharmony_ci } 45362306a36Sopenharmony_ci 45462306a36Sopenharmony_ci hdl = &dev->ctrl_handler; 45562306a36Sopenharmony_ci v4l2_ctrl_handler_init(hdl, 7); 45662306a36Sopenharmony_ci v4l2_ctrl_new_std(hdl, &pcm20_ctrl_ops, 45762306a36Sopenharmony_ci V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1); 45862306a36Sopenharmony_ci dev->rds_pty = v4l2_ctrl_new_std(hdl, NULL, 45962306a36Sopenharmony_ci V4L2_CID_RDS_RX_PTY, 0, 0x1f, 1, 0); 46062306a36Sopenharmony_ci dev->rds_ps_name = v4l2_ctrl_new_std(hdl, NULL, 46162306a36Sopenharmony_ci V4L2_CID_RDS_RX_PS_NAME, 0, 8, 8, 0); 46262306a36Sopenharmony_ci dev->rds_radio_test = v4l2_ctrl_new_std(hdl, NULL, 46362306a36Sopenharmony_ci V4L2_CID_RDS_RX_RADIO_TEXT, 0, 64, 64, 0); 46462306a36Sopenharmony_ci dev->rds_ta = v4l2_ctrl_new_std(hdl, NULL, 46562306a36Sopenharmony_ci V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT, 0, 1, 1, 0); 46662306a36Sopenharmony_ci dev->rds_tp = v4l2_ctrl_new_std(hdl, NULL, 46762306a36Sopenharmony_ci V4L2_CID_RDS_RX_TRAFFIC_PROGRAM, 0, 1, 1, 0); 46862306a36Sopenharmony_ci dev->rds_ms = v4l2_ctrl_new_std(hdl, NULL, 46962306a36Sopenharmony_ci V4L2_CID_RDS_RX_MUSIC_SPEECH, 0, 1, 1, 1); 47062306a36Sopenharmony_ci v4l2_dev->ctrl_handler = hdl; 47162306a36Sopenharmony_ci if (hdl->error) { 47262306a36Sopenharmony_ci res = hdl->error; 47362306a36Sopenharmony_ci v4l2_err(v4l2_dev, "Could not register control\n"); 47462306a36Sopenharmony_ci goto err_hdl; 47562306a36Sopenharmony_ci } 47662306a36Sopenharmony_ci strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name)); 47762306a36Sopenharmony_ci dev->vdev.v4l2_dev = v4l2_dev; 47862306a36Sopenharmony_ci dev->vdev.fops = &pcm20_fops; 47962306a36Sopenharmony_ci dev->vdev.ioctl_ops = &pcm20_ioctl_ops; 48062306a36Sopenharmony_ci dev->vdev.release = video_device_release_empty; 48162306a36Sopenharmony_ci dev->vdev.lock = &dev->lock; 48262306a36Sopenharmony_ci dev->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO | 48362306a36Sopenharmony_ci V4L2_CAP_RDS_CAPTURE; 48462306a36Sopenharmony_ci video_set_drvdata(&dev->vdev, dev); 48562306a36Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, 48662306a36Sopenharmony_ci dev->audmode == V4L2_TUNER_MODE_MONO, -1); 48762306a36Sopenharmony_ci pcm20_setfreq(dev, dev->freq); 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) 49062306a36Sopenharmony_ci goto err_hdl; 49162306a36Sopenharmony_ci 49262306a36Sopenharmony_ci v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n"); 49362306a36Sopenharmony_ci return 0; 49462306a36Sopenharmony_cierr_hdl: 49562306a36Sopenharmony_ci v4l2_ctrl_handler_free(hdl); 49662306a36Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 49762306a36Sopenharmony_ci return -EINVAL; 49862306a36Sopenharmony_ci} 49962306a36Sopenharmony_ci 50062306a36Sopenharmony_ciMODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt"); 50162306a36Sopenharmony_ciMODULE_DESCRIPTION("A driver for the Miro PCM20 radio card."); 50262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 50362306a36Sopenharmony_ci 50462306a36Sopenharmony_cistatic void __exit pcm20_cleanup(void) 50562306a36Sopenharmony_ci{ 50662306a36Sopenharmony_ci struct pcm20 *dev = &pcm20_card; 50762306a36Sopenharmony_ci 50862306a36Sopenharmony_ci video_unregister_device(&dev->vdev); 50962306a36Sopenharmony_ci snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, 1, -1); 51062306a36Sopenharmony_ci v4l2_ctrl_handler_free(&dev->ctrl_handler); 51162306a36Sopenharmony_ci v4l2_device_unregister(&dev->v4l2_dev); 51262306a36Sopenharmony_ci} 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_cimodule_init(pcm20_init); 51562306a36Sopenharmony_cimodule_exit(pcm20_cleanup); 516