From patchwork Wed Apr 1 09:43:30 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Eduardo Valentin X-Patchwork-Id: 15601 X-Patchwork-Delegate: dougsland@redhat.com Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n319m8T6019598 for ; Wed, 1 Apr 2009 09:48:08 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752383AbZDAJrx (ORCPT ); Wed, 1 Apr 2009 05:47:53 -0400 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1752677AbZDAJrw (ORCPT ); Wed, 1 Apr 2009 05:47:52 -0400 Received: from smtp.nokia.com ([192.100.105.134]:52894 "EHLO mgw-mx09.nokia.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752059AbZDAJrt (ORCPT ); Wed, 1 Apr 2009 05:47:49 -0400 Received: from esebh105.NOE.Nokia.com (esebh105.ntc.nokia.com [172.21.138.211]) by mgw-mx09.nokia.com (Switch-3.2.6/Switch-3.2.6) with ESMTP id n319l9BA029212 for ; Wed, 1 Apr 2009 04:47:46 -0500 Received: from esebh102.NOE.Nokia.com ([172.21.138.183]) by esebh105.NOE.Nokia.com with Microsoft SMTPSVC(6.0.3790.3959); Wed, 1 Apr 2009 12:47:25 +0300 Received: from vaebe101.NOE.Nokia.com ([10.160.244.11]) by esebh102.NOE.Nokia.com with Microsoft SMTPSVC(6.0.3790.3959); Wed, 1 Apr 2009 12:47:24 +0300 Received: from localhost.localdomain ([172.21.43.220]) by vaebe101.NOE.Nokia.com with Microsoft SMTPSVC(6.0.3790.3959); Wed, 1 Apr 2009 12:47:23 +0300 From: Eduardo Valentin To: "linux-media@vger.kernel.org" Cc: "Nurkkala Eero.An (EXT-Offcode/Oulu)" , Eduardo Valentin Subject: [PATCH 2/3] FMTx: si4713: Add files to add radio interface for si4713 Date: Wed, 1 Apr 2009 12:43:30 +0300 Message-Id: <1238579011-12435-3-git-send-email-eduardo.valentin@nokia.com> X-Mailer: git-send-email 1.6.2.GIT In-Reply-To: <1238579011-12435-2-git-send-email-eduardo.valentin@nokia.com> References: <1238579011-12435-1-git-send-email-eduardo.valentin@nokia.com> <1238579011-12435-2-git-send-email-eduardo.valentin@nokia.com> X-OriginalArrivalTime: 01 Apr 2009 09:47:23.0884 (UTC) FILETIME=[DBD2AAC0:01C9B2AE] X-Nokia-AV: Clean Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org This patch adds files which creates the radio interface for si4713 FM transmitter devices. Signed-off-by: Eduardo Valentin --- drivers/media/radio/radio-si4713.c | 834 ++++++++++++++++++++++++++++++++++++ drivers/media/radio/radio-si4713.h | 32 ++ 2 files changed, 866 insertions(+), 0 deletions(-) create mode 100644 drivers/media/radio/radio-si4713.c create mode 100644 drivers/media/radio/radio-si4713.h diff --git a/drivers/media/radio/radio-si4713.c b/drivers/media/radio/radio-si4713.c new file mode 100644 index 0000000..0a887a3 --- /dev/null +++ b/drivers/media/radio/radio-si4713.c @@ -0,0 +1,834 @@ +/* + * drivers/media/radio/radio-si4713.c + * + * Driver for I2C Silicon Labs Si4713 FM Radio Transmitter: + * + * Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT + * Author: Eduardo Valentin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* driver definitions */ +#define DRIVER_AUTHOR "Eduardo Valentin " +#define DRIVER_NAME SI4713_NAME +#define DRIVER_VERSION KERNEL_VERSION(0, 0, 1) +#define DRIVER_CARD "Silicon Labs Si4713 FM Radio Transmitter" +#define DRIVER_DESC "I2C driver for Si4713 FM Radio Transmitter" + +/* frequency domain transformation (using times 10 to avoid floats) */ +#define FREQDEV_UNIT 100000 +#define FREQV4L2_MULTI 625 +#define dev_to_v4l2(f) ((f * FREQDEV_UNIT) / FREQV4L2_MULTI) +#define v4l2_to_dev(f) ((f * FREQV4L2_MULTI) / FREQDEV_UNIT) + +/* kernel includes */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "radio-si4713.h" +#include "si4713.h" + +/* module parameters */ +static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */ + +/* + * Sysfs properties + * Read and write functions + */ +#define property_write(prop, type, mask, check) \ +static ssize_t si4713_##prop##_write(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ +{ \ + struct si4713_device *sdev = dev_get_drvdata(dev); \ + type value; \ + int rval; \ + \ + if (!sdev) \ + return -ENODEV; \ + \ + sscanf(buf, mask, &value); \ + \ + if (check) \ + return -EDOM; \ + \ + rval = si4713_set_##prop(sdev, value); \ + \ + return rval < 0 ? rval : count; \ +} + +#define property_read(prop, size, mask) \ +static ssize_t si4713_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct si4713_device *sdev = dev_get_drvdata(dev); \ + size value; \ + \ + if (!sdev) \ + return -ENODEV; \ + \ + value = si4713_get_##prop(sdev); \ + \ + if (value >= 0) \ + value = sprintf(buf, mask "\n", value); \ + \ + return value; \ +} + +#define DEFINE_SYSFS_PROPERTY(prop, signal, size, mask, check) \ +property_write(prop, signal size, mask, check) \ +property_read(prop, size, mask) \ +static DEVICE_ATTR(prop, S_IRUGO | S_IWUSR, si4713_##prop##_read, \ + si4713_##prop##_write); +#define DEFINE_SYSFS_PROPERTY_RO(prop, signal, size, mask) \ +property_read(prop, size, mask) \ +static DEVICE_ATTR(prop, S_IRUGO, si4713_##prop##_read, NULL); + + +#define property_str_write(prop, size) \ +static ssize_t si4713_##prop##_write(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ +{ \ + struct si4713_device *sdev = dev_get_drvdata(dev); \ + int rval; \ + u8 *in; \ + \ + if (!sdev) \ + return -ENODEV; \ + \ + in = kzalloc(size + 1, GFP_KERNEL); \ + if (!in) \ + return -ENOMEM; \ + \ + /* We don't want to miss the spaces */ \ + strncpy(in, buf, size); \ + rval = si4713_set_##prop(sdev, in); \ + \ + kfree(in); \ + \ + return rval < 0 ? rval : count; \ +} + +#define property_str_read(prop, size) \ +static ssize_t si4713_##prop##_read(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + struct si4713_device *sdev = dev_get_drvdata(dev); \ + int count; \ + u8 *out; \ + \ + if (!sdev) \ + return -ENODEV; \ + \ + out = kzalloc(size + 1, GFP_KERNEL); \ + if (!out) \ + return -ENOMEM; \ + \ + si4713_get_##prop(sdev, out); \ + count = sprintf(buf, "%s\n", out); \ + \ + kfree(out); \ + \ + return count; \ +} + +#define DEFINE_SYSFS_PROPERTY_STR(prop, size) \ +property_str_write(prop, size) \ +property_str_read(prop, size) \ +static DEVICE_ATTR(prop, S_IRUGO | S_IWUSR, si4713_##prop##_read, \ + si4713_##prop##_write); + +/* + * Power level property + */ +/* power_level (rw) 88 - 115 or 0 */ +static ssize_t si4713_power_level_write(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct si4713_device *sdev = dev_get_drvdata(dev); + unsigned int p; + int rval, pl; + + if (!sdev) { + rval = -ENODEV; + goto exit; + } + + sscanf(buf, "%u", &p); + + pl = si4713_get_power_level(sdev); + if (pl < 0) { + rval = pl; + goto exit; + } + + rval = si4713_set_power_level(sdev, p); + +exit: + return rval < 0 ? rval : count; +} +property_read(power_level, unsigned int, "%u") +static DEVICE_ATTR(power_level, S_IRUGO | S_IWUSR, si4713_power_level_read, + si4713_power_level_write); + +DEFINE_SYSFS_PROPERTY(antenna_capacitor, unsigned, int, "%u", + value > SI4713_MAX_ANTCAP) +/* + * RDS properties + */ +/* rds_pi (rw) 0 - 0xFFFF */ +DEFINE_SYSFS_PROPERTY(rds_pi, unsigned, int, "%x", 0) +/* rds_pty (rw) 0 - 0x1F */ +DEFINE_SYSFS_PROPERTY(rds_pty, unsigned, int, "%u", value > MAX_RDS_PTY) +/* rds_enabled (rw) 0 - 1 */ +DEFINE_SYSFS_PROPERTY(rds_enabled, unsigned, int, "%u", 0) +/* rds_ps_name (rw) strlen (8 - 96) */ +DEFINE_SYSFS_PROPERTY_STR(rds_ps_name, MAX_RDS_PS_NAME) +/* rds_radio_text (rw) strlen (0 - 384) */ +DEFINE_SYSFS_PROPERTY_STR(rds_radio_text, MAX_RDS_RADIO_TEXT) + +/* + * Limiter properties + */ +/* limiter_release_time (rw) 0 - 102390 */ +DEFINE_SYSFS_PROPERTY(limiter_release_time, unsigned, long, "%lu", + value > MAX_LIMITER_RELEASE_TIME) +/* limiter_deviation (rw) 0 - 90000 */ +DEFINE_SYSFS_PROPERTY(limiter_deviation, unsigned, long, "%lu", + value > MAX_LIMITER_DEVIATION) +/* limiter_enabled (rw) 0 - 1 */ +DEFINE_SYSFS_PROPERTY(limiter_enabled, unsigned, int, "%u", 0) + +/* + * Pilot tone properties + */ +/* pilot_frequency (rw) 0 - 19000 */ +DEFINE_SYSFS_PROPERTY(pilot_frequency, unsigned, int, "%u", + value > MAX_PILOT_FREQUENCY) +/* pilot_deviation (rw) 0 - 90000 */ +DEFINE_SYSFS_PROPERTY(pilot_deviation, unsigned, long, "%lu", + value > MAX_PILOT_DEVIATION) +/* pilot_enabled (rw) 0 - 1 */ +DEFINE_SYSFS_PROPERTY(pilot_enabled, unsigned, int, "%u", 0) + +/* + * Stereo properties + */ +/* stereo_enabled (rw) 0 - 1 */ +DEFINE_SYSFS_PROPERTY(stereo_enabled, unsigned, int, "%u", 0) + +/* + * Audio Compression properties + */ +/* acomp_release_time (rw) 0 - 1000000 */ +DEFINE_SYSFS_PROPERTY(acomp_release_time, unsigned, long, "%lu", + value > MAX_ACOMP_RELEASE_TIME) +/* acomp_attack_time (rw) 0 - 5000 */ +DEFINE_SYSFS_PROPERTY(acomp_attack_time, unsigned, int, "%u", + value > MAX_ACOMP_ATTACK_TIME) +/* acomp_threshold (rw) -40 - 0 */ +property_write(acomp_threshold, int, "%d", + value > MAX_ACOMP_THRESHOLD || + value < MIN_ACOMP_THRESHOLD) + +static ssize_t si4713_acomp_threshold_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct si4713_device *sdev = dev_get_drvdata(dev); + int count; + s8 thres; + + if (!sdev) + return -ENODEV; + + count = si4713_get_acomp_threshold(sdev, &thres); + + if (count >= 0) + count = sprintf(buf, "%d\n", thres); + + return count; +} +static DEVICE_ATTR(acomp_threshold, S_IRUGO | S_IWUSR, + si4713_acomp_threshold_read, + si4713_acomp_threshold_write); + +/* acomp_gain (rw) 0 - 20 */ +DEFINE_SYSFS_PROPERTY(acomp_gain, unsigned, int, "%u", value > MAX_ACOMP_GAIN) +/* acomp_enabled (rw) 0 - 1 */ +DEFINE_SYSFS_PROPERTY(acomp_enabled, unsigned, int, "%u", 0) + +/* Tune_measure (rw) */ +DEFINE_SYSFS_PROPERTY(tune_measure, unsigned, int, "%u", 0) + +/* + * Region properties + */ +DEFINE_SYSFS_PROPERTY_RO(region_bottom_frequency, unsigned, int, "%u") +DEFINE_SYSFS_PROPERTY_RO(region_top_frequency, unsigned, int, "%u") +DEFINE_SYSFS_PROPERTY_RO(region_channel_spacing, unsigned, int, "%u") +DEFINE_SYSFS_PROPERTY(region_preemphasis, unsigned, int, "%u", + ((value != PREEMPHASIS_USA) && + (value != PREEMPHASIS_EU) && + (value != PREEMPHASIS_DISABLED))) +DEFINE_SYSFS_PROPERTY(region, unsigned, int, "%u", 0) + +static struct attribute *attrs[] = { + &dev_attr_power_level.attr, + &dev_attr_antenna_capacitor.attr, + &dev_attr_rds_pi.attr, + &dev_attr_rds_pty.attr, + &dev_attr_rds_ps_name.attr, + &dev_attr_rds_radio_text.attr, + &dev_attr_rds_enabled.attr, + &dev_attr_limiter_release_time.attr, + &dev_attr_limiter_deviation.attr, + &dev_attr_limiter_enabled.attr, + &dev_attr_pilot_frequency.attr, + &dev_attr_pilot_deviation.attr, + &dev_attr_pilot_enabled.attr, + &dev_attr_stereo_enabled.attr, + &dev_attr_acomp_release_time.attr, + &dev_attr_acomp_attack_time.attr, + &dev_attr_acomp_threshold.attr, + &dev_attr_acomp_gain.attr, + &dev_attr_acomp_enabled.attr, + &dev_attr_region_bottom_frequency.attr, + &dev_attr_region_top_frequency.attr, + &dev_attr_region_preemphasis.attr, + &dev_attr_region_channel_spacing.attr, + &dev_attr_region.attr, + &dev_attr_tune_measure.attr, + NULL, +}; + +static const struct attribute_group attr_group = { + .attrs = attrs, +}; + +static irqreturn_t si4713_handler(int irq, void *dev) +{ + struct si4713_device *sdev = dev; + + dev_dbg(&sdev->client->dev, "IRQ called, signaling completion work\n"); + complete(&sdev->work); + + return IRQ_HANDLED; +} + +/* + * si4713_fops - file operations interface + */ +static const struct v4l2_file_operations si4713_fops = { + .owner = THIS_MODULE, + .ioctl = video_ioctl2, +}; + +/* + * Video4Linux Interface + */ +/* + * si4713_v4l2_queryctrl - query control + */ +static struct v4l2_queryctrl si4713_v4l2_queryctrl[] = { +/* HINT: the disabled controls are only here to satify kradio and such apps */ + { + .id = V4L2_CID_AUDIO_VOLUME, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, +}; + +/* + * si4713_vidioc_querycap - query device capabilities + */ +static int si4713_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *capability) +{ + struct si4713_device *sdev = video_get_drvdata(video_devdata(file)); + + strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver)); + strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card)); + snprintf(capability->bus_info, 32, "I2C: 0x%X", sdev->client->addr); + capability->version = DRIVER_VERSION; + capability->capabilities = V4L2_CAP_TUNER; + + return 0; +} + +/* + * si4713_vidioc_g_input - get input + */ +static int si4713_vidioc_g_input(struct file *filp, void *priv, + unsigned int *i) +{ + *i = 0; + + return 0; +} + +/* + * si4713_vidioc_s_input - set input + */ +static int si4713_vidioc_s_input(struct file *filp, void *priv, unsigned int i) +{ + if (i) + return -EINVAL; + + return 0; +} + +/* + * si4713_vidioc_queryctrl - enumerate control items + */ +static int si4713_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(si4713_v4l2_queryctrl); i++) { + if (qc->id && qc->id == si4713_v4l2_queryctrl[i].id) { + memcpy(qc, &(si4713_v4l2_queryctrl[i]), sizeof(*qc)); + return 0; + } + } + + return -EINVAL; +} + +/* + * si4713_vidioc_g_ctrl - get the value of a control + */ +static int si4713_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct si4713_device *sdev = video_get_drvdata(video_devdata(file)); + int rval = 0; + + if (!sdev) + return -ENODEV; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + rval = si4713_get_mute(sdev); + if (rval >= 0) + ctrl->value = rval; + break; + } + + return rval; +} + +/* + * si4713_vidioc_s_ctrl - set the value of a control + */ +static int si4713_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct si4713_device *sdev = video_get_drvdata(video_devdata(file)); + int rval = 0; + + if (!sdev) + return -ENODEV; + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + if (ctrl->value) { + rval = si4713_set_mute(sdev, ctrl->value); + if (rval < 0) + goto exit; + + rval = si4713_set_power_state(sdev, POWER_DOWN); + } else { + rval = si4713_set_power_state(sdev, POWER_UP); + if (rval < 0) + goto exit; + + rval = si4713_setup(sdev); + if (rval < 0) + goto exit; + + rval = si4713_set_mute(sdev, ctrl->value); + } + break; + } + +exit: + return rval; +} + +/* + * si4713_vidioc_g_audio - get audio attributes + */ +static int si4713_vidioc_g_audio(struct file *file, void *priv, + struct v4l2_audio *audio) +{ + if (audio->index > 1) + return -EINVAL; + + strncpy(audio->name, "Radio", 32); + audio->capability = V4L2_AUDCAP_STEREO; + + return 0; +} + +/* + * si4713_vidioc_s_audio - set audio attributes + */ +static int si4713_vidioc_s_audio(struct file *file, void *priv, + struct v4l2_audio *audio) +{ + if (audio->index != 0) + return -EINVAL; + + return 0; +} + +/* + * si4713_vidioc_g_tuner - get tuner attributes + */ +static int si4713_vidioc_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct si4713_device *sdev = video_get_drvdata(video_devdata(file)); + + if (!sdev) + return -ENODEV; + + if (tuner->index > 0) + return -EINVAL; + + strncpy(tuner->name, "FM Transmitter", 32); + tuner->type = V4L2_TUNER_RADIO; + tuner->rangelow = + dev_to_v4l2(si4713_get_region_bottom_frequency(sdev) / 10); + tuner->rangehigh = + dev_to_v4l2(si4713_get_region_top_frequency(sdev) / 10); + tuner->rxsubchans = V4L2_TUNER_SUB_STEREO; + tuner->capability = V4L2_TUNER_CAP_STEREO | V4L2_TUNER_CAP_LOW; + tuner->audmode = V4L2_TUNER_MODE_STEREO; + + /* automatic frequency control: -1: freq to low, 1 freq to high */ + tuner->afc = 0; + + return 0; +} + +/* + * si4713_vidioc_s_tuner - set tuner attributes + */ +static int si4713_vidioc_s_tuner(struct file *file, void *priv, + struct v4l2_tuner *tuner) +{ + struct si4713_device *sdev = video_get_drvdata(video_devdata(file)); + + if (!sdev) + return -ENODEV; + + if (tuner->index > 0) + return -EINVAL; + + return 0; +} + +/* + * si4713_vidioc_g_frequency - get tuner or modulator radio frequency + */ +static int si4713_vidioc_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct si4713_device *sdev = video_get_drvdata(video_devdata(file)); + int rval = 0; + int f; + + freq->type = V4L2_TUNER_RADIO; + f = si4713_get_frequency(sdev); + + if (f < 0) + rval = f; + else + freq->frequency = dev_to_v4l2(f); + + return rval; +} + +/* + * si4713_vidioc_s_frequency - set tuner or modulator radio frequency + */ +static int si4713_vidioc_s_frequency(struct file *file, void *priv, + struct v4l2_frequency *freq) +{ + struct si4713_device *sdev = video_get_drvdata(video_devdata(file)); + int rval = 0; + + if (freq->type != V4L2_TUNER_RADIO) { + rval = -EINVAL; + goto exit; + } + + rval = si4713_set_frequency(sdev, v4l2_to_dev(freq->frequency)); + +exit: + return rval; +} + +static struct v4l2_ioctl_ops si4713_ioctl_ops = { + .vidioc_querycap = si4713_vidioc_querycap, + .vidioc_g_input = si4713_vidioc_g_input, + .vidioc_s_input = si4713_vidioc_s_input, + .vidioc_queryctrl = si4713_vidioc_queryctrl, + .vidioc_g_ctrl = si4713_vidioc_g_ctrl, + .vidioc_s_ctrl = si4713_vidioc_s_ctrl, + .vidioc_g_audio = si4713_vidioc_g_audio, + .vidioc_s_audio = si4713_vidioc_s_audio, + .vidioc_g_tuner = si4713_vidioc_g_tuner, + .vidioc_s_tuner = si4713_vidioc_s_tuner, + .vidioc_g_frequency = si4713_vidioc_g_frequency, + .vidioc_s_frequency = si4713_vidioc_s_frequency, +}; + +/* + * si4713_viddev_tamples - video device interface + */ +static struct video_device si4713_viddev_template = { + .fops = &si4713_fops, + .name = DRIVER_NAME, + .release = video_device_release, + .ioctl_ops = &si4713_ioctl_ops, +}; + +/* + * I2C driver interface + */ +/* + * si4713_i2c_driver_probe - probe for the device + */ +static int si4713_i2c_driver_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct si4713_device *sdev; + int rval, skip_release = 0; + + sdev = kzalloc(sizeof *sdev, GFP_KERNEL); + if (!sdev) { + dev_dbg(&client->dev, "Failed to alloc video device.\n"); + rval = -ENOMEM; + goto exit; + } + + sdev->videodev = video_device_alloc(); + if (!sdev->videodev) { + dev_dbg(&client->dev, "Failed to alloc video device.\n"); + rval = -ENOMEM; + goto free_sdev; + } + + sdev->platform_data = client->dev.platform_data; + if (!sdev->platform_data) { + dev_dbg(&client->dev, "No platform data registered.\n"); + rval = -ENODEV; + goto free_vdev; + } + + sdev->client = client; + i2c_set_clientdata(client, sdev); + + mutex_init(&sdev->mutex); + init_completion(&sdev->work); + + if (client->irq) { + rval = request_irq(client->irq, + si4713_handler, IRQF_TRIGGER_FALLING | IRQF_DISABLED, + client->name, sdev); + if (rval < 0) { + dev_err(&client->dev, "Could not request IRQ\n"); + goto free_vdev; + } + dev_dbg(&client->dev, "IRQ requested.\n"); + } else { + dev_dbg(&client->dev, "IRQ not configure. Using timeouts.\n"); + } + + memcpy(sdev->videodev, &si4713_viddev_template, + sizeof(si4713_viddev_template)); + video_set_drvdata(sdev->videodev, sdev); + if (video_register_device(sdev->videodev, VFL_TYPE_RADIO, radio_nr)) { + dev_dbg(&client->dev, "Could not register video device.\n"); + rval = -EIO; + goto free_irq; + } + + rval = sysfs_create_group(&sdev->client->dev.kobj, &attr_group); + if (rval < 0) { + dev_dbg(&client->dev, "Could not register sysfs interface.\n"); + goto free_registration; + } + + rval = si4713_probe(sdev); + if (rval < 0) { + dev_dbg(&client->dev, "Failed to probe device information.\n"); + goto free_sysfs; + } + + return 0; + +free_sysfs: + sysfs_remove_group(&sdev->client->dev.kobj, &attr_group); +free_registration: + video_unregister_device(sdev->videodev); + skip_release = 1; +free_irq: + if (client->irq) + free_irq(client->irq, sdev); +free_vdev: + if (!skip_release) + video_device_release(sdev->videodev); +free_sdev: + kfree(sdev); +exit: + return rval; +} + +/* + * si4713_i2c_driver_remove - remove the device + */ +static int __exit si4713_i2c_driver_remove(struct i2c_client *client) +{ + struct si4713_device *sdev = i2c_get_clientdata(client); + struct video_device *vd; + + /* our client isn't attached */ + if (!client->adapter) + return -ENODEV; + + if (sdev) { + vd = sdev->videodev; + + sysfs_remove_group(&sdev->client->dev.kobj, &attr_group); + + if (vd) + video_unregister_device(vd); + + if (sdev->power_state) + si4713_set_power_state(sdev, POWER_DOWN); + + if (client->irq > 0) + free_irq(client->irq, sdev); + + kfree(sdev); + } + i2c_set_clientdata(client, NULL); + + return 0; +} + +/* + * si4713_i2c_driver - i2c driver interface + */ +static const struct i2c_device_id si4713_id[] = { + { "si4713" , 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, si4713_id); + +static struct i2c_driver si4713_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + }, + .probe = si4713_i2c_driver_probe, + .remove = __exit_p(si4713_i2c_driver_remove), + .id_table = si4713_id, +}; + +/* + * Module Interface + */ +/* + * si4713_module_init - module init + */ +static int __init si4713_module_init(void) +{ + int rval; + + printk(KERN_INFO DRIVER_DESC "\n"); + + rval = i2c_add_driver(&si4713_i2c_driver); + if (rval < 0) + printk(KERN_ERR DRIVER_NAME ": driver registration failed\n"); + + return rval; +} + +/* + * si4713_module_exit - module exit + */ +static void __exit si4713_module_exit(void) +{ + i2c_del_driver(&si4713_i2c_driver); +} + +module_init(si4713_module_init); +module_exit(si4713_module_exit); + +module_param(radio_nr, int, 0); +MODULE_PARM_DESC(radio_nr, + "Minor number for radio device (-1 ==> auto assign)"); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION("0.0.1"); diff --git a/drivers/media/radio/radio-si4713.h b/drivers/media/radio/radio-si4713.h new file mode 100644 index 0000000..85e969b --- /dev/null +++ b/drivers/media/radio/radio-si4713.h @@ -0,0 +1,32 @@ +/* + * drivers/media/radio/radio-si4713.h + * + * Property and commands definitions for Si4713 radio transmitter chip. + * + * Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT + * Author: Eduardo Valentin + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + */ + +#ifndef RADIO_SI4713_H +#define RADIO_SI4713_H + +#define SI4713_NAME "radio-si4713" + +/* The SI4713 I2C sensor chip has a fixed slave address of 0xc6. */ +#define SI4713_I2C_ADDR_BUSEN_HIGH 0x63 +#define SI4713_I2C_ADDR_BUSEN_LOW 0x11 + +/* + * Platform dependent definition + */ +struct si4713_platform_data { + /* Set power state, zero is off, non-zero is on. */ + int (*set_power)(int power); +}; + +#endif /* ifndef RADIO_SI4713_H*/