Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  radio-miropcm20.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Miro PCM20 radio driver for Linux radio support
 * (c) 1998 Ruurd Reitsma <R.A.Reitsma@wbmt.tudelft.nl>
 * Thanks to Norberto Pellici for the ACI device interface specification
 * The API part is based on the radiotrack driver by M. Kirkwood
 * This driver relies on the aci mixer provided by the snd-miro
 * ALSA driver.
 * Look there for further info...
 *
 * From the original miro RDS sources:
 *
 *  (c) 2001 Robert Siemer <Robert.Siemer@gmx.de>
 *
 *  Many thanks to Fred Seidel <seidel@metabox.de>, the
 *  designer of the RDS decoder hardware. With his help
 *  I was able to code this driver.
 *  Thanks also to Norberto Pellicci, Dominic Mounteney
 *  <DMounteney@pinnaclesys.com> and www.teleauskunft.de
 *  for good hints on finding Fred. It was somewhat hard
 *  to locate him here in Germany... [:
 *
 * This code has been reintroduced and converted to use
 * the new V4L2 RDS API by:
 *
 * Hans Verkuil <hansverk@cisco.com>
 */


#include <linux/module.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/kthread.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <sound/aci.h>

#define RDS_DATASHIFT          2   /* Bit 2 */
#define RDS_DATAMASK        (1 << RDS_DATASHIFT)
#define RDS_BUSYMASK        0x10   /* Bit 4 */
#define RDS_CLOCKMASK       0x08   /* Bit 3 */
#define RDS_DATA(x)         (((x) >> RDS_DATASHIFT) & 1)

#define RDS_STATUS      0x01
#define RDS_STATIONNAME 0x02
#define RDS_TEXT        0x03
#define RDS_ALTFREQ     0x04
#define RDS_TIMEDATE    0x05
#define RDS_PI_CODE     0x06
#define RDS_PTYTATP     0x07
#define RDS_RESET       0x08
#define RDS_RXVALUE     0x09

static int radio_nr = -1;
module_param(radio_nr, int, 0);
MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX). Default: -1 (autodetect)");

struct pcm20 {
 struct v4l2_device v4l2_dev;
 struct video_device vdev;
 struct v4l2_ctrl_handler ctrl_handler;
 struct v4l2_ctrl *rds_pty;
 struct v4l2_ctrl *rds_ps_name;
 struct v4l2_ctrl *rds_radio_test;
 struct v4l2_ctrl *rds_ta;
 struct v4l2_ctrl *rds_tp;
 struct v4l2_ctrl *rds_ms;
 /* thread for periodic RDS status checking */
 struct task_struct *kthread;
 unsigned long freq;
 u32 audmode;
 struct snd_miro_aci *aci;
 struct mutex lock;
};

static struct pcm20 pcm20_card = {
 .freq = 87 * 16000,
 .audmode = V4L2_TUNER_MODE_STEREO,
};


static int rds_waitread(struct snd_miro_aci *aci)
{
 u8 byte;
 int i = 2000;

 do {
  byte = inb(aci->aci_port + ACI_REG_RDS);
  i--;
 } while ((byte & RDS_BUSYMASK) && i);

 /*
 * It's magic, but without this the data that you read later on
 * is unreliable and full of bit errors. With this 1 usec delay
 * everything is fine.
 */

 udelay(1);
 return i ? byte : -1;
}

static int rds_rawwrite(struct snd_miro_aci *aci, u8 byte)
{
 if (rds_waitread(aci) >= 0) {
  outb(byte, aci->aci_port + ACI_REG_RDS);
  return 0;
 }
 return -1;
}

static int rds_write(struct snd_miro_aci *aci, u8 byte)
{
 u8 sendbuffer[8];
 int i;

 for (i = 7; i >= 0; i--)
  sendbuffer[7 - i] = (byte & (1 << i)) ? RDS_DATAMASK : 0;
 sendbuffer[0] |= RDS_CLOCKMASK;

 for (i = 0; i < 8; i++)
  rds_rawwrite(aci, sendbuffer[i]);
 return 0;
}

static int rds_readcycle_nowait(struct snd_miro_aci *aci)
{
 outb(0, aci->aci_port + ACI_REG_RDS);
 return rds_waitread(aci);
}

static int rds_readcycle(struct snd_miro_aci *aci)
{
 if (rds_rawwrite(aci, 0) < 0)
  return -1;
 return rds_waitread(aci);
}

static int rds_ack(struct snd_miro_aci *aci)
{
 int i = rds_readcycle(aci);

 if (i < 0)
  return -1;
 if (i & RDS_DATAMASK)
  return 0;  /* ACK  */
 return 1;  /* NACK */
}

static int rds_cmd(struct snd_miro_aci *aci, u8 cmd, u8 databuffer[], u8 datasize)
{
 int i, j;

 rds_write(aci, cmd);

 /* RDS_RESET doesn't need further processing */
 if (cmd == RDS_RESET)
  return 0;
 if (rds_ack(aci))
  return -EIO;
 if (datasize == 0)
  return 0;

 /* to be able to use rds_readcycle_nowait()
   I have to waitread() here */

 if (rds_waitread(aci) < 0)
  return -1;

 memset(databuffer, 0, datasize);

 for (i = 0; i < 8 * datasize; i++) {
  j = rds_readcycle_nowait(aci);
  if (j < 0)
   return -EIO;
  databuffer[i / 8] |= RDS_DATA(j) << (7 - (i % 8));
 }
 return 0;
}

static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq)
{
 unsigned char freql;
 unsigned char freqh;
 struct snd_miro_aci *aci = dev->aci;

 freq /= 160;
 if (!(aci->aci_version == 0x07 || aci->aci_version >= 0xb0))
  freq /= 10;  /* I don't know exactly which version
      * needs this hack */

 freql = freq & 0xff;
 freqh = freq >> 8;

 rds_cmd(aci, RDS_RESET, NULL, 0);
 return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh);
}

static int vidioc_querycap(struct file *file, void *priv,
    struct v4l2_capability *v)
{
 strscpy(v->driver, "Miro PCM20"sizeof(v->driver));
 strscpy(v->card, "Miro PCM20"sizeof(v->card));
 strscpy(v->bus_info, "ISA:radio-miropcm20"sizeof(v->bus_info));
 return 0;
}

static bool sanitize(char *p, int size)
{
 int i;
 bool ret = true;

 for (i = 0; i < size; i++) {
  if (p[i] < 32) {
   p[i] = ' ';
   ret = false;
  }
 }
 return ret;
}

static int vidioc_g_tuner(struct file *file, void *priv,
    struct v4l2_tuner *v)
{
 struct pcm20 *dev = video_drvdata(file);
 int res;
 u8 buf;

 if (v->index)
  return -EINVAL;
 strscpy(v->name, "FM"sizeof(v->name));
 v->type = V4L2_TUNER_RADIO;
 v->rangelow = 87*16000;
 v->rangehigh = 108*16000;
 res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTATION, -1, -1);
 v->signal = (res & 0x80) ? 0 : 0xffff;
 /* Note: stereo detection does not work if the audio is muted,
   it will default to mono in that case. */

 res = snd_aci_cmd(dev->aci, ACI_READ_TUNERSTEREO, -1, -1);
 v->rxsubchans = (res & 0x40) ? V4L2_TUNER_SUB_MONO :
     V4L2_TUNER_SUB_STEREO;
 v->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
   V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_CONTROLS;
 v->audmode = dev->audmode;
 res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1);
 if (res >= 0 && buf)
  v->rxsubchans |= V4L2_TUNER_SUB_RDS;
 return 0;
}

static int vidioc_s_tuner(struct file *file, void *priv,
    const struct v4l2_tuner *v)
{
 struct pcm20 *dev = video_drvdata(file);

 if (v->index)
  return -EINVAL;
 if (v->audmode > V4L2_TUNER_MODE_STEREO)
  dev->audmode = V4L2_TUNER_MODE_STEREO;
 else
  dev->audmode = v->audmode;
 snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO,
   dev->audmode == V4L2_TUNER_MODE_MONO, -1);
 return 0;
}

static int vidioc_g_frequency(struct file *file, void *priv,
    struct v4l2_frequency *f)
{
 struct pcm20 *dev = video_drvdata(file);

 if (f->tuner != 0)
  return -EINVAL;

 f->type = V4L2_TUNER_RADIO;
 f->frequency = dev->freq;
 return 0;
}


static int vidioc_s_frequency(struct file *file, void *priv,
    const struct v4l2_frequency *f)
{
 struct pcm20 *dev = video_drvdata(file);

 if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO)
  return -EINVAL;

 dev->freq = clamp_t(u32, f->frequency, 87 * 16000U, 108 * 16000U);
 pcm20_setfreq(dev, dev->freq);
 return 0;
}

static int pcm20_s_ctrl(struct v4l2_ctrl *ctrl)
{
 struct pcm20 *dev = container_of(ctrl->handler, struct pcm20, ctrl_handler);

 switch (ctrl->id) {
 case V4L2_CID_AUDIO_MUTE:
  snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, ctrl->val, -1);
  return 0;
 }
 return -EINVAL;
}

static int pcm20_thread(void *data)
{
 struct pcm20 *dev = data;
 const unsigned no_rds_start_counter = 5;
 const unsigned sleep_msecs = 2000;
 unsigned no_rds_counter = no_rds_start_counter;

 for (;;) {
  char text_buffer[66];
  u8 buf;
  int res;

  msleep_interruptible(sleep_msecs);

  if (kthread_should_stop())
   break;

  res = rds_cmd(dev->aci, RDS_RXVALUE, &buf, 1);
  if (res)
   continue;
  if (buf == 0) {
   if (no_rds_counter == 0)
    continue;
   no_rds_counter--;
   if (no_rds_counter)
    continue;

   /*
 * No RDS seen for no_rds_start_counter * sleep_msecs
 * milliseconds, clear all RDS controls to their
 * default values.
 */

   v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, "");
   v4l2_ctrl_s_ctrl(dev->rds_ms, 1);
   v4l2_ctrl_s_ctrl(dev->rds_ta, 0);
   v4l2_ctrl_s_ctrl(dev->rds_tp, 0);
   v4l2_ctrl_s_ctrl(dev->rds_pty, 0);
   v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, "");
   continue;
  }
  no_rds_counter = no_rds_start_counter;

  res = rds_cmd(dev->aci, RDS_STATUS, &buf, 1);
  if (res)
   continue;
  if ((buf >> 3) & 1) {
   res = rds_cmd(dev->aci, RDS_STATIONNAME, text_buffer, 8);
   text_buffer[8] = 0;
   if (!res && sanitize(text_buffer, 8))
    v4l2_ctrl_s_ctrl_string(dev->rds_ps_name, text_buffer);
  }
  if ((buf >> 6) & 1) {
   u8 pty;

   res = rds_cmd(dev->aci, RDS_PTYTATP, &pty, 1);
   if (!res) {
    v4l2_ctrl_s_ctrl(dev->rds_ms, !!(pty & 0x01));
    v4l2_ctrl_s_ctrl(dev->rds_ta, !!(pty & 0x02));
    v4l2_ctrl_s_ctrl(dev->rds_tp, !!(pty & 0x80));
    v4l2_ctrl_s_ctrl(dev->rds_pty, (pty >> 2) & 0x1f);
   }
  }
  if ((buf >> 4) & 1) {
   res = rds_cmd(dev->aci, RDS_TEXT, text_buffer, 65);
   text_buffer[65] = 0;
   if (!res && sanitize(text_buffer + 1, 64))
    v4l2_ctrl_s_ctrl_string(dev->rds_radio_test, text_buffer + 1);
  }
 }
 return 0;
}

static int pcm20_open(struct file *file)
{
 struct pcm20 *dev = video_drvdata(file);
 int res = v4l2_fh_open(file);

 if (!res && v4l2_fh_is_singular_file(file) &&
     IS_ERR_OR_NULL(dev->kthread)) {
  dev->kthread = kthread_run(pcm20_thread, dev, "%s",
        dev->v4l2_dev.name);
  if (IS_ERR(dev->kthread)) {
   v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
   v4l2_fh_release(file);
   return PTR_ERR(dev->kthread);
  }
 }
 return res;
}

static int pcm20_release(struct file *file)
{
 struct pcm20 *dev = video_drvdata(file);

 if (v4l2_fh_is_singular_file(file) && !IS_ERR_OR_NULL(dev->kthread)) {
  kthread_stop(dev->kthread);
  dev->kthread = NULL;
 }
 return v4l2_fh_release(file);
}

static const struct v4l2_file_operations pcm20_fops = {
 .owner  = THIS_MODULE,
 .open  = pcm20_open,
 .poll  = v4l2_ctrl_poll,
 .release = pcm20_release,
 .unlocked_ioctl = video_ioctl2,
};

static const struct v4l2_ioctl_ops pcm20_ioctl_ops = {
 .vidioc_querycap    = vidioc_querycap,
 .vidioc_g_tuner     = vidioc_g_tuner,
 .vidioc_s_tuner     = vidioc_s_tuner,
 .vidioc_g_frequency = vidioc_g_frequency,
 .vidioc_s_frequency = vidioc_s_frequency,
 .vidioc_log_status  = v4l2_ctrl_log_status,
 .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
 .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};

static const struct v4l2_ctrl_ops pcm20_ctrl_ops = {
 .s_ctrl = pcm20_s_ctrl,
};

static int __init pcm20_init(void)
{
 struct pcm20 *dev = &pcm20_card;
 struct v4l2_device *v4l2_dev = &dev->v4l2_dev;
 struct v4l2_ctrl_handler *hdl;
 int res;

 dev->aci = snd_aci_get_aci();
 if (dev->aci == NULL) {
  v4l2_err(v4l2_dev,
    "you must load the snd-miro driver first!\n");
  return -ENODEV;
 }
 strscpy(v4l2_dev->name, "radio-miropcm20"sizeof(v4l2_dev->name));
 mutex_init(&dev->lock);

 res = v4l2_device_register(NULL, v4l2_dev);
 if (res < 0) {
  v4l2_err(v4l2_dev, "could not register v4l2_device\n");
  return -EINVAL;
 }

 hdl = &dev->ctrl_handler;
 v4l2_ctrl_handler_init(hdl, 7);
 v4l2_ctrl_new_std(hdl, &pcm20_ctrl_ops,
   V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
 dev->rds_pty = v4l2_ctrl_new_std(hdl, NULL,
   V4L2_CID_RDS_RX_PTY, 0, 0x1f, 1, 0);
 dev->rds_ps_name = v4l2_ctrl_new_std(hdl, NULL,
   V4L2_CID_RDS_RX_PS_NAME, 0, 8, 8, 0);
 dev->rds_radio_test = v4l2_ctrl_new_std(hdl, NULL,
   V4L2_CID_RDS_RX_RADIO_TEXT, 0, 64, 64, 0);
 dev->rds_ta = v4l2_ctrl_new_std(hdl, NULL,
   V4L2_CID_RDS_RX_TRAFFIC_ANNOUNCEMENT, 0, 1, 1, 0);
 dev->rds_tp = v4l2_ctrl_new_std(hdl, NULL,
   V4L2_CID_RDS_RX_TRAFFIC_PROGRAM, 0, 1, 1, 0);
 dev->rds_ms = v4l2_ctrl_new_std(hdl, NULL,
   V4L2_CID_RDS_RX_MUSIC_SPEECH, 0, 1, 1, 1);
 v4l2_dev->ctrl_handler = hdl;
 if (hdl->error) {
  res = hdl->error;
  v4l2_err(v4l2_dev, "Could not register control\n");
  goto err_hdl;
 }
 strscpy(dev->vdev.name, v4l2_dev->name, sizeof(dev->vdev.name));
 dev->vdev.v4l2_dev = v4l2_dev;
 dev->vdev.fops = &pcm20_fops;
 dev->vdev.ioctl_ops = &pcm20_ioctl_ops;
 dev->vdev.release = video_device_release_empty;
 dev->vdev.lock = &dev->lock;
 dev->vdev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO |
    V4L2_CAP_RDS_CAPTURE;
 video_set_drvdata(&dev->vdev, dev);
 snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO,
   dev->audmode == V4L2_TUNER_MODE_MONO, -1);
 pcm20_setfreq(dev, dev->freq);

 if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0)
  goto err_hdl;

 v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n");
 return 0;
err_hdl:
 v4l2_ctrl_handler_free(hdl);
 v4l2_device_unregister(v4l2_dev);
 return -EINVAL;
}

MODULE_AUTHOR("Ruurd Reitsma, Krzysztof Helt");
MODULE_DESCRIPTION("A driver for the Miro PCM20 radio card.");
MODULE_LICENSE("GPL");

static void __exit pcm20_cleanup(void)
{
 struct pcm20 *dev = &pcm20_card;

 video_unregister_device(&dev->vdev);
 snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, 1, -1);
 v4l2_ctrl_handler_free(&dev->ctrl_handler);
 v4l2_device_unregister(&dev->v4l2_dev);
}

module_init(pcm20_init);
module_exit(pcm20_cleanup);

Messung V0.5
C=99 H=96 G=97

¤ Dauer der Verarbeitung: 0.6 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge