diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/media/radio/Kconfig | 18 | ||||
-rw-r--r-- | drivers/media/radio/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/radio/radio-miropcm20.c | 270 | ||||
-rw-r--r-- | drivers/mfd/Kconfig | 6 | ||||
-rw-r--r-- | drivers/mfd/Makefile | 1 | ||||
-rw-r--r-- | drivers/mfd/twl4030-codec.c | 276 | ||||
-rw-r--r-- | drivers/mfd/twl4030-core.c | 16 |
7 files changed, 587 insertions, 1 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig index a87a477c87f..b134553eb3b 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -195,6 +195,24 @@ config RADIO_MAESTRO To compile this driver as a module, choose M here: the module will be called radio-maestro. +config RADIO_MIROPCM20 + tristate "miroSOUND PCM20 radio" + depends on ISA && VIDEO_V4L2 + select SND_MIRO + ---help--- + Choose Y here if you have this FM radio card. You also need to enable + the ALSA sound system. This choice automatically selects the ALSA + sound card driver "Miro miroSOUND PCM1pro/PCM12/PCM20radio" as this + is required for the radio-miropcm20. + + In order to control your radio card, you will need to use programs + that are compatible with the Video For Linux API. Information on + this API and pointers to "v4l" programs may be found at + <file:Documentation/video4linux/API.html>. + + To compile this driver as a module, choose M here: the + module will be called radio-miropcm20. + config RADIO_SF16FMI tristate "SF16FMI Radio" depends on ISA && VIDEO_V4L2 diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile index 2a1be3bf4f7..8a63d543ae4 100644 --- a/drivers/media/radio/Makefile +++ b/drivers/media/radio/Makefile @@ -18,6 +18,7 @@ obj-$(CONFIG_RADIO_TRUST) += radio-trust.o obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o +obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o obj-$(CONFIG_USB_DSBR) += dsbr100.o obj-$(CONFIG_RADIO_SI470X) += si470x/ obj-$(CONFIG_USB_MR800) += radio-mr800.o diff --git a/drivers/media/radio/radio-miropcm20.c b/drivers/media/radio/radio-miropcm20.c new file mode 100644 index 00000000000..4ff885445fd --- /dev/null +++ b/drivers/media/radio/radio-miropcm20.c @@ -0,0 +1,270 @@ +/* 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... + */ + +/* What ever you think about the ACI, version 0x07 is not very well! + * I can't get frequency, 'tuner status', 'tuner flags' or mute/mono + * conditions... Robert + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/videodev2.h> +#include <media/v4l2-device.h> +#include <media/v4l2-ioctl.h> +#include <sound/aci.h> + +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)"); + +static int mono; +module_param(mono, bool, 0); +MODULE_PARM_DESC(mono, "Force tuner into mono mode."); + +struct pcm20 { + struct v4l2_device v4l2_dev; + struct video_device vdev; + unsigned long freq; + int muted; + struct snd_miro_aci *aci; +}; + +static struct pcm20 pcm20_card = { + .freq = 87*16000, + .muted = 1, +}; + +static int pcm20_mute(struct pcm20 *dev, unsigned char mute) +{ + dev->muted = mute; + return snd_aci_cmd(dev->aci, ACI_SET_TUNERMUTE, mute, -1); +} + +static int pcm20_stereo(struct pcm20 *dev, unsigned char stereo) +{ + return snd_aci_cmd(dev->aci, ACI_SET_TUNERMONO, !stereo, -1); +} + +static int pcm20_setfreq(struct pcm20 *dev, unsigned long freq) +{ + unsigned char freql; + unsigned char freqh; + struct snd_miro_aci *aci = dev->aci; + + dev->freq = freq; + + 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; + + pcm20_stereo(dev, !mono); + return snd_aci_cmd(aci, ACI_WRITE_TUNE, freql, freqh); +} + +static const struct v4l2_file_operations pcm20_fops = { + .owner = THIS_MODULE, + .ioctl = video_ioctl2, +}; + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + strlcpy(v->driver, "Miro PCM20", sizeof(v->driver)); + strlcpy(v->card, "Miro PCM20", sizeof(v->card)); + strlcpy(v->bus_info, "ISA", sizeof(v->bus_info)); + v->version = 0x1; + v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + return 0; +} + +static int vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + if (v->index) /* Only 1 tuner */ + return -EINVAL; + strlcpy(v->name, "FM", sizeof(v->name)); + v->type = V4L2_TUNER_RADIO; + v->rangelow = 87*16000; + v->rangehigh = 108*16000; + v->signal = 0xffff; + v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; + v->capability = V4L2_TUNER_CAP_LOW; + v->audmode = V4L2_TUNER_MODE_MONO; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *v) +{ + return v->index ? -EINVAL : 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, + struct v4l2_frequency *f) +{ + struct pcm20 *dev = video_drvdata(file); + + if (f->tuner != 0 || f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + dev->freq = f->frequency; + pcm20_setfreq(dev, f->frequency); + return 0; +} + +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + switch (qc->id) { + case V4L2_CID_AUDIO_MUTE: + return v4l2_ctrl_query_fill(qc, 0, 1, 1, 1); + } + return -EINVAL; +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct pcm20 *dev = video_drvdata(file); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + ctrl->value = dev->muted; + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct pcm20 *dev = video_drvdata(file); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + pcm20_mute(dev, ctrl->value); + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) +{ + return i ? -EINVAL : 0; +} + +static int vidioc_g_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + a->index = 0; + strlcpy(a->name, "Radio", sizeof(a->name)); + a->capability = V4L2_AUDCAP_STEREO; + return 0; +} + +static int vidioc_s_audio(struct file *file, void *priv, + struct v4l2_audio *a) +{ + return a->index ? -EINVAL : 0; +} + +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_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_audio = vidioc_g_audio, + .vidioc_s_audio = vidioc_s_audio, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, +}; + +static int __init pcm20_init(void) +{ + struct pcm20 *dev = &pcm20_card; + struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + 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; + } + strlcpy(v4l2_dev->name, "miropcm20", sizeof(v4l2_dev->name)); + + + res = v4l2_device_register(NULL, v4l2_dev); + if (res < 0) { + v4l2_err(v4l2_dev, "could not register v4l2_device\n"); + return -EINVAL; + } + + strlcpy(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; + video_set_drvdata(&dev->vdev, dev); + + if (video_register_device(&dev->vdev, VFL_TYPE_RADIO, radio_nr) < 0) + goto fail; + + v4l2_info(v4l2_dev, "Mirosound PCM20 Radio tuner\n"); + return 0; +fail: + 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); + v4l2_device_unregister(&dev->v4l2_dev); +} + +module_init(pcm20_init); +module_exit(pcm20_cleanup); diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig index 570be139f9d..08f2d07bf56 100644 --- a/drivers/mfd/Kconfig +++ b/drivers/mfd/Kconfig @@ -121,6 +121,12 @@ config TWL4030_POWER and load scripts controling which resources are switched off/on or reset when a sleep, wakeup or warm reset event occurs. +config TWL4030_CODEC + bool + depends on TWL4030_CORE + select MFD_CORE + default n + config MFD_TMIO bool default n diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile index f3b277b90d4..af0fc903cec 100644 --- a/drivers/mfd/Makefile +++ b/drivers/mfd/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_MENELAUS) += menelaus.o obj-$(CONFIG_TWL4030_CORE) += twl4030-core.o twl4030-irq.o obj-$(CONFIG_TWL4030_POWER) += twl4030-power.o +obj-$(CONFIG_TWL4030_CODEC) += twl4030-codec.o obj-$(CONFIG_MFD_MC13783) += mc13783-core.o diff --git a/drivers/mfd/twl4030-codec.c b/drivers/mfd/twl4030-codec.c new file mode 100644 index 00000000000..77b914907d7 --- /dev/null +++ b/drivers/mfd/twl4030-codec.c @@ -0,0 +1,276 @@ +/* + * MFD driver for twl4030 codec submodule + * + * Author: Peter Ujfalusi <peter.ujfalusi@nokia.com> + * + * Copyright: (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/platform_device.h> +#include <linux/i2c/twl4030.h> +#include <linux/mfd/core.h> +#include <linux/mfd/twl4030-codec.h> + +#define TWL4030_CODEC_CELLS 2 + +static struct platform_device *twl4030_codec_dev; + +struct twl4030_codec_resource { + int request_count; + u8 reg; + u8 mask; +}; + +struct twl4030_codec { + unsigned int audio_mclk; + struct mutex mutex; + struct twl4030_codec_resource resource[TWL4030_CODEC_RES_MAX]; + struct mfd_cell cells[TWL4030_CODEC_CELLS]; +}; + +/* + * Modify the resource, the function returns the content of the register + * after the modification. + */ +static int twl4030_codec_set_resource(enum twl4030_codec_res id, int enable) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + u8 val; + + twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, + codec->resource[id].reg); + + if (enable) + val |= codec->resource[id].mask; + else + val &= ~codec->resource[id].mask; + + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + val, codec->resource[id].reg); + + return val; +} + +static inline int twl4030_codec_get_resource(enum twl4030_codec_res id) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + u8 val; + + twl4030_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, &val, + codec->resource[id].reg); + + return val; +} + +/* + * Enable the resource. + * The function returns with error or the content of the register + */ +int twl4030_codec_enable_resource(enum twl4030_codec_res id) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + int val; + + if (id >= TWL4030_CODEC_RES_MAX) { + dev_err(&twl4030_codec_dev->dev, + "Invalid resource ID (%u)\n", id); + return -EINVAL; + } + + mutex_lock(&codec->mutex); + if (!codec->resource[id].request_count) + /* Resource was disabled, enable it */ + val = twl4030_codec_set_resource(id, 1); + else + val = twl4030_codec_get_resource(id); + + codec->resource[id].request_count++; + mutex_unlock(&codec->mutex); + + return val; +} +EXPORT_SYMBOL_GPL(twl4030_codec_enable_resource); + +/* + * Disable the resource. + * The function returns with error or the content of the register + */ +int twl4030_codec_disable_resource(unsigned id) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + int val; + + if (id >= TWL4030_CODEC_RES_MAX) { + dev_err(&twl4030_codec_dev->dev, + "Invalid resource ID (%u)\n", id); + return -EINVAL; + } + + mutex_lock(&codec->mutex); + if (!codec->resource[id].request_count) { + dev_err(&twl4030_codec_dev->dev, + "Resource has been disabled already (%u)\n", id); + mutex_unlock(&codec->mutex); + return -EPERM; + } + codec->resource[id].request_count--; + + if (!codec->resource[id].request_count) + /* Resource can be disabled now */ + val = twl4030_codec_set_resource(id, 0); + else + val = twl4030_codec_get_resource(id); + + mutex_unlock(&codec->mutex); + + return val; +} +EXPORT_SYMBOL_GPL(twl4030_codec_disable_resource); + +unsigned int twl4030_codec_get_mclk(void) +{ + struct twl4030_codec *codec = platform_get_drvdata(twl4030_codec_dev); + + return codec->audio_mclk; +} +EXPORT_SYMBOL_GPL(twl4030_codec_get_mclk); + +static int __devinit twl4030_codec_probe(struct platform_device *pdev) +{ + struct twl4030_codec *codec; + struct twl4030_codec_data *pdata = pdev->dev.platform_data; + struct mfd_cell *cell = NULL; + int ret, childs = 0; + u8 val; + + if (!pdata) { + dev_err(&pdev->dev, "Platform data is missing\n"); + return -EINVAL; + } + + /* Configure APLL_INFREQ and disable APLL if enabled */ + val = 0; + switch (pdata->audio_mclk) { + case 19200000: + val |= TWL4030_APLL_INFREQ_19200KHZ; + break; + case 26000000: + val |= TWL4030_APLL_INFREQ_26000KHZ; + break; + case 38400000: + val |= TWL4030_APLL_INFREQ_38400KHZ; + break; + default: + dev_err(&pdev->dev, "Invalid audio_mclk\n"); + return -EINVAL; + } + twl4030_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + val, TWL4030_REG_APLL_CTL); + + codec = kzalloc(sizeof(struct twl4030_codec), GFP_KERNEL); + if (!codec) + return -ENOMEM; + + platform_set_drvdata(pdev, codec); + + twl4030_codec_dev = pdev; + mutex_init(&codec->mutex); + codec->audio_mclk = pdata->audio_mclk; + + /* Codec power */ + codec->resource[TWL4030_CODEC_RES_POWER].reg = TWL4030_REG_CODEC_MODE; + codec->resource[TWL4030_CODEC_RES_POWER].mask = TWL4030_CODECPDZ; + + /* PLL */ + codec->resource[TWL4030_CODEC_RES_APLL].reg = TWL4030_REG_APLL_CTL; + codec->resource[TWL4030_CODEC_RES_APLL].mask = TWL4030_APLL_EN; + + if (pdata->audio) { + cell = &codec->cells[childs]; + cell->name = "twl4030_codec_audio"; + cell->platform_data = pdata->audio; + cell->data_size = sizeof(*pdata->audio); + childs++; + } + if (pdata->vibra) { + cell = &codec->cells[childs]; + cell->name = "twl4030_codec_vibra"; + cell->platform_data = pdata->vibra; + cell->data_size = sizeof(*pdata->vibra); + childs++; + } + + if (childs) + ret = mfd_add_devices(&pdev->dev, pdev->id, codec->cells, + childs, NULL, 0); + else { + dev_err(&pdev->dev, "No platform data found for childs\n"); + ret = -ENODEV; + } + + if (!ret) + return 0; + + platform_set_drvdata(pdev, NULL); + kfree(codec); + twl4030_codec_dev = NULL; + return ret; +} + +static int __devexit twl4030_codec_remove(struct platform_device *pdev) +{ + struct twl4030_codec *codec = platform_get_drvdata(pdev); + + mfd_remove_devices(&pdev->dev); + platform_set_drvdata(pdev, NULL); + kfree(codec); + twl4030_codec_dev = NULL; + + return 0; +} + +MODULE_ALIAS("platform:twl4030_codec"); + +static struct platform_driver twl4030_codec_driver = { + .probe = twl4030_codec_probe, + .remove = __devexit_p(twl4030_codec_remove), + .driver = { + .owner = THIS_MODULE, + .name = "twl4030_codec", + }, +}; + +static int __devinit twl4030_codec_init(void) +{ + return platform_driver_register(&twl4030_codec_driver); +} +module_init(twl4030_codec_init); + +static void __devexit twl4030_codec_exit(void) +{ + platform_driver_unregister(&twl4030_codec_driver); +} +module_exit(twl4030_codec_exit); + +MODULE_AUTHOR("Peter Ujfalusi <peter.ujfalusi@nokia.com>"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/mfd/twl4030-core.c b/drivers/mfd/twl4030-core.c index a1c47ee95c0..98b984e191d 100644 --- a/drivers/mfd/twl4030-core.c +++ b/drivers/mfd/twl4030-core.c @@ -114,6 +114,12 @@ #define twl_has_watchdog() false #endif +#if defined(CONFIG_TWL4030_CODEC) || defined(CONFIG_TWL4030_CODEC_MODULE) +#define twl_has_codec() true +#else +#define twl_has_codec() false +#endif + /* Triton Core internal information (BEGIN) */ /* Last - for index max*/ @@ -601,6 +607,14 @@ add_children(struct twl4030_platform_data *pdata, unsigned long features) return PTR_ERR(child); } + if (twl_has_codec() && pdata->codec) { + child = add_child(1, "twl4030_codec", + pdata->codec, sizeof(*pdata->codec), + false, 0, 0); + if (IS_ERR(child)) + return PTR_ERR(child); + } + if (twl_has_regulator()) { /* child = add_regulator(TWL4030_REG_VPLL1, pdata->vpll1); @@ -763,7 +777,7 @@ static int twl4030_remove(struct i2c_client *client) } /* NOTE: this driver only handles a single twl4030/tps659x0 chip */ -static int +static int __init twl4030_probe(struct i2c_client *client, const struct i2c_device_id *id) { int status; |