Path: blob/master/drivers/media/radio/radio-si4713.c
15112 views
/*1* drivers/media/radio/radio-si4713.c2*3* Platform Driver for Silicon Labs Si4713 FM Radio Transmitter:4*5* Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT6* Contact: Eduardo Valentin <[email protected]>7*8* This program is free software; you can redistribute it and/or modify9* it under the terms of the GNU General Public License as published by10* the Free Software Foundation; either version 2 of the License, or11* (at your option) any later version.12*13* This program is distributed in the hope that it will be useful,14* but WITHOUT ANY WARRANTY; without even the implied warranty of15* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the16* GNU General Public License for more details.17*18* You should have received a copy of the GNU General Public License19* along with this program; if not, write to the Free Software20* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA21*/2223#include <linux/kernel.h>24#include <linux/module.h>25#include <linux/init.h>26#include <linux/platform_device.h>27#include <linux/i2c.h>28#include <linux/videodev2.h>29#include <linux/slab.h>30#include <media/v4l2-device.h>31#include <media/v4l2-common.h>32#include <media/v4l2-ioctl.h>33#include <media/radio-si4713.h>3435/* module parameters */36static int radio_nr = -1; /* radio device minor (-1 ==> auto assign) */37module_param(radio_nr, int, 0);38MODULE_PARM_DESC(radio_nr,39"Minor number for radio device (-1 ==> auto assign)");4041MODULE_LICENSE("GPL");42MODULE_AUTHOR("Eduardo Valentin <[email protected]>");43MODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter");44MODULE_VERSION("0.0.1");4546/* Driver state struct */47struct radio_si4713_device {48struct v4l2_device v4l2_dev;49struct video_device *radio_dev;50};5152/* radio_si4713_fops - file operations interface */53static const struct v4l2_file_operations radio_si4713_fops = {54.owner = THIS_MODULE,55/* Note: locking is done at the subdev level in the i2c driver. */56.unlocked_ioctl = video_ioctl2,57};5859/* Video4Linux Interface */60static int radio_si4713_fill_audout(struct v4l2_audioout *vao)61{62/* TODO: check presence of audio output */63strlcpy(vao->name, "FM Modulator Audio Out", 32);6465return 0;66}6768static int radio_si4713_enumaudout(struct file *file, void *priv,69struct v4l2_audioout *vao)70{71return radio_si4713_fill_audout(vao);72}7374static int radio_si4713_g_audout(struct file *file, void *priv,75struct v4l2_audioout *vao)76{77int rval = radio_si4713_fill_audout(vao);7879vao->index = 0;8081return rval;82}8384static int radio_si4713_s_audout(struct file *file, void *priv,85struct v4l2_audioout *vao)86{87return vao->index ? -EINVAL : 0;88}8990/* radio_si4713_querycap - query device capabilities */91static int radio_si4713_querycap(struct file *file, void *priv,92struct v4l2_capability *capability)93{94struct radio_si4713_device *rsdev;9596rsdev = video_get_drvdata(video_devdata(file));9798strlcpy(capability->driver, "radio-si4713", sizeof(capability->driver));99strlcpy(capability->card, "Silicon Labs Si4713 Modulator",100sizeof(capability->card));101capability->capabilities = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT;102103return 0;104}105106/* radio_si4713_queryctrl - enumerate control items */107static int radio_si4713_queryctrl(struct file *file, void *priv,108struct v4l2_queryctrl *qc)109{110/* Must be sorted from low to high control ID! */111static const u32 user_ctrls[] = {112V4L2_CID_USER_CLASS,113V4L2_CID_AUDIO_MUTE,1140115};116117/* Must be sorted from low to high control ID! */118static const u32 fmtx_ctrls[] = {119V4L2_CID_FM_TX_CLASS,120V4L2_CID_RDS_TX_DEVIATION,121V4L2_CID_RDS_TX_PI,122V4L2_CID_RDS_TX_PTY,123V4L2_CID_RDS_TX_PS_NAME,124V4L2_CID_RDS_TX_RADIO_TEXT,125V4L2_CID_AUDIO_LIMITER_ENABLED,126V4L2_CID_AUDIO_LIMITER_RELEASE_TIME,127V4L2_CID_AUDIO_LIMITER_DEVIATION,128V4L2_CID_AUDIO_COMPRESSION_ENABLED,129V4L2_CID_AUDIO_COMPRESSION_GAIN,130V4L2_CID_AUDIO_COMPRESSION_THRESHOLD,131V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME,132V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME,133V4L2_CID_PILOT_TONE_ENABLED,134V4L2_CID_PILOT_TONE_DEVIATION,135V4L2_CID_PILOT_TONE_FREQUENCY,136V4L2_CID_TUNE_PREEMPHASIS,137V4L2_CID_TUNE_POWER_LEVEL,138V4L2_CID_TUNE_ANTENNA_CAPACITOR,1390140};141static const u32 *ctrl_classes[] = {142user_ctrls,143fmtx_ctrls,144NULL145};146struct radio_si4713_device *rsdev;147148rsdev = video_get_drvdata(video_devdata(file));149150qc->id = v4l2_ctrl_next(ctrl_classes, qc->id);151if (qc->id == 0)152return -EINVAL;153154if (qc->id == V4L2_CID_USER_CLASS || qc->id == V4L2_CID_FM_TX_CLASS)155return v4l2_ctrl_query_fill(qc, 0, 0, 0, 0);156157return v4l2_device_call_until_err(&rsdev->v4l2_dev, 0, core,158queryctrl, qc);159}160161/*162* v4l2 ioctl call backs.163* we are just a wrapper for v4l2_sub_devs.164*/165static inline struct v4l2_device *get_v4l2_dev(struct file *file)166{167return &((struct radio_si4713_device *)video_drvdata(file))->v4l2_dev;168}169170static int radio_si4713_g_ext_ctrls(struct file *file, void *p,171struct v4l2_ext_controls *vecs)172{173return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,174g_ext_ctrls, vecs);175}176177static int radio_si4713_s_ext_ctrls(struct file *file, void *p,178struct v4l2_ext_controls *vecs)179{180return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,181s_ext_ctrls, vecs);182}183184static int radio_si4713_g_ctrl(struct file *file, void *p,185struct v4l2_control *vc)186{187return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,188g_ctrl, vc);189}190191static int radio_si4713_s_ctrl(struct file *file, void *p,192struct v4l2_control *vc)193{194return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,195s_ctrl, vc);196}197198static int radio_si4713_g_modulator(struct file *file, void *p,199struct v4l2_modulator *vm)200{201return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,202g_modulator, vm);203}204205static int radio_si4713_s_modulator(struct file *file, void *p,206struct v4l2_modulator *vm)207{208return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,209s_modulator, vm);210}211212static int radio_si4713_g_frequency(struct file *file, void *p,213struct v4l2_frequency *vf)214{215return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,216g_frequency, vf);217}218219static int radio_si4713_s_frequency(struct file *file, void *p,220struct v4l2_frequency *vf)221{222return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,223s_frequency, vf);224}225226static long radio_si4713_default(struct file *file, void *p,227bool valid_prio, int cmd, void *arg)228{229return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,230ioctl, cmd, arg);231}232233static struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {234.vidioc_enumaudout = radio_si4713_enumaudout,235.vidioc_g_audout = radio_si4713_g_audout,236.vidioc_s_audout = radio_si4713_s_audout,237.vidioc_querycap = radio_si4713_querycap,238.vidioc_queryctrl = radio_si4713_queryctrl,239.vidioc_g_ext_ctrls = radio_si4713_g_ext_ctrls,240.vidioc_s_ext_ctrls = radio_si4713_s_ext_ctrls,241.vidioc_g_ctrl = radio_si4713_g_ctrl,242.vidioc_s_ctrl = radio_si4713_s_ctrl,243.vidioc_g_modulator = radio_si4713_g_modulator,244.vidioc_s_modulator = radio_si4713_s_modulator,245.vidioc_g_frequency = radio_si4713_g_frequency,246.vidioc_s_frequency = radio_si4713_s_frequency,247.vidioc_default = radio_si4713_default,248};249250/* radio_si4713_vdev_template - video device interface */251static struct video_device radio_si4713_vdev_template = {252.fops = &radio_si4713_fops,253.name = "radio-si4713",254.release = video_device_release,255.ioctl_ops = &radio_si4713_ioctl_ops,256};257258/* Platform driver interface */259/* radio_si4713_pdriver_probe - probe for the device */260static int radio_si4713_pdriver_probe(struct platform_device *pdev)261{262struct radio_si4713_platform_data *pdata = pdev->dev.platform_data;263struct radio_si4713_device *rsdev;264struct i2c_adapter *adapter;265struct v4l2_subdev *sd;266int rval = 0;267268if (!pdata) {269dev_err(&pdev->dev, "Cannot proceed without platform data.\n");270rval = -EINVAL;271goto exit;272}273274rsdev = kzalloc(sizeof *rsdev, GFP_KERNEL);275if (!rsdev) {276dev_err(&pdev->dev, "Failed to alloc video device.\n");277rval = -ENOMEM;278goto exit;279}280281rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev);282if (rval) {283dev_err(&pdev->dev, "Failed to register v4l2 device.\n");284goto free_rsdev;285}286287adapter = i2c_get_adapter(pdata->i2c_bus);288if (!adapter) {289dev_err(&pdev->dev, "Cannot get i2c adapter %d\n",290pdata->i2c_bus);291rval = -ENODEV;292goto unregister_v4l2_dev;293}294295sd = v4l2_i2c_new_subdev_board(&rsdev->v4l2_dev, adapter,296pdata->subdev_board_info, NULL);297if (!sd) {298dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n");299rval = -ENODEV;300goto put_adapter;301}302303rsdev->radio_dev = video_device_alloc();304if (!rsdev->radio_dev) {305dev_err(&pdev->dev, "Failed to alloc video device.\n");306rval = -ENOMEM;307goto put_adapter;308}309310memcpy(rsdev->radio_dev, &radio_si4713_vdev_template,311sizeof(radio_si4713_vdev_template));312video_set_drvdata(rsdev->radio_dev, rsdev);313if (video_register_device(rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) {314dev_err(&pdev->dev, "Could not register video device.\n");315rval = -EIO;316goto free_vdev;317}318dev_info(&pdev->dev, "New device successfully probed\n");319320goto exit;321322free_vdev:323video_device_release(rsdev->radio_dev);324put_adapter:325i2c_put_adapter(adapter);326unregister_v4l2_dev:327v4l2_device_unregister(&rsdev->v4l2_dev);328free_rsdev:329kfree(rsdev);330exit:331return rval;332}333334/* radio_si4713_pdriver_remove - remove the device */335static int __exit radio_si4713_pdriver_remove(struct platform_device *pdev)336{337struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);338struct radio_si4713_device *rsdev = container_of(v4l2_dev,339struct radio_si4713_device,340v4l2_dev);341struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next,342struct v4l2_subdev, list);343struct i2c_client *client = v4l2_get_subdevdata(sd);344345video_unregister_device(rsdev->radio_dev);346i2c_put_adapter(client->adapter);347v4l2_device_unregister(&rsdev->v4l2_dev);348kfree(rsdev);349350return 0;351}352353static struct platform_driver radio_si4713_pdriver = {354.driver = {355.name = "radio-si4713",356},357.probe = radio_si4713_pdriver_probe,358.remove = __exit_p(radio_si4713_pdriver_remove),359};360361/* Module Interface */362static int __init radio_si4713_module_init(void)363{364return platform_driver_register(&radio_si4713_pdriver);365}366367static void __exit radio_si4713_module_exit(void)368{369platform_driver_unregister(&radio_si4713_pdriver);370}371372module_init(radio_si4713_module_init);373module_exit(radio_si4713_module_exit);374375376377