18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * radio-timb.c Timberdale FPGA Radio driver 48c2ecf20Sopenharmony_ci * Copyright (c) 2009 Intel Corporation 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <linux/io.h> 88c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h> 98c2ecf20Sopenharmony_ci#include <media/v4l2-device.h> 108c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h> 118c2ecf20Sopenharmony_ci#include <media/v4l2-event.h> 128c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 138c2ecf20Sopenharmony_ci#include <linux/interrupt.h> 148c2ecf20Sopenharmony_ci#include <linux/slab.h> 158c2ecf20Sopenharmony_ci#include <linux/i2c.h> 168c2ecf20Sopenharmony_ci#include <linux/module.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_data/media/timb_radio.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define DRIVER_NAME "timb-radio" 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistruct timbradio { 228c2ecf20Sopenharmony_ci struct timb_radio_platform_data pdata; 238c2ecf20Sopenharmony_ci struct v4l2_subdev *sd_tuner; 248c2ecf20Sopenharmony_ci struct v4l2_subdev *sd_dsp; 258c2ecf20Sopenharmony_ci struct video_device video_dev; 268c2ecf20Sopenharmony_ci struct v4l2_device v4l2_dev; 278c2ecf20Sopenharmony_ci struct mutex lock; 288c2ecf20Sopenharmony_ci}; 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_cistatic int timbradio_vidioc_querycap(struct file *file, void *priv, 328c2ecf20Sopenharmony_ci struct v4l2_capability *v) 338c2ecf20Sopenharmony_ci{ 348c2ecf20Sopenharmony_ci strscpy(v->driver, DRIVER_NAME, sizeof(v->driver)); 358c2ecf20Sopenharmony_ci strscpy(v->card, "Timberdale Radio", sizeof(v->card)); 368c2ecf20Sopenharmony_ci snprintf(v->bus_info, sizeof(v->bus_info), "platform:"DRIVER_NAME); 378c2ecf20Sopenharmony_ci return 0; 388c2ecf20Sopenharmony_ci} 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_cistatic int timbradio_vidioc_g_tuner(struct file *file, void *priv, 418c2ecf20Sopenharmony_ci struct v4l2_tuner *v) 428c2ecf20Sopenharmony_ci{ 438c2ecf20Sopenharmony_ci struct timbradio *tr = video_drvdata(file); 448c2ecf20Sopenharmony_ci return v4l2_subdev_call(tr->sd_tuner, tuner, g_tuner, v); 458c2ecf20Sopenharmony_ci} 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_cistatic int timbradio_vidioc_s_tuner(struct file *file, void *priv, 488c2ecf20Sopenharmony_ci const struct v4l2_tuner *v) 498c2ecf20Sopenharmony_ci{ 508c2ecf20Sopenharmony_ci struct timbradio *tr = video_drvdata(file); 518c2ecf20Sopenharmony_ci return v4l2_subdev_call(tr->sd_tuner, tuner, s_tuner, v); 528c2ecf20Sopenharmony_ci} 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_cistatic int timbradio_vidioc_s_frequency(struct file *file, void *priv, 558c2ecf20Sopenharmony_ci const struct v4l2_frequency *f) 568c2ecf20Sopenharmony_ci{ 578c2ecf20Sopenharmony_ci struct timbradio *tr = video_drvdata(file); 588c2ecf20Sopenharmony_ci return v4l2_subdev_call(tr->sd_tuner, tuner, s_frequency, f); 598c2ecf20Sopenharmony_ci} 608c2ecf20Sopenharmony_ci 618c2ecf20Sopenharmony_cistatic int timbradio_vidioc_g_frequency(struct file *file, void *priv, 628c2ecf20Sopenharmony_ci struct v4l2_frequency *f) 638c2ecf20Sopenharmony_ci{ 648c2ecf20Sopenharmony_ci struct timbradio *tr = video_drvdata(file); 658c2ecf20Sopenharmony_ci return v4l2_subdev_call(tr->sd_tuner, tuner, g_frequency, f); 668c2ecf20Sopenharmony_ci} 678c2ecf20Sopenharmony_ci 688c2ecf20Sopenharmony_cistatic const struct v4l2_ioctl_ops timbradio_ioctl_ops = { 698c2ecf20Sopenharmony_ci .vidioc_querycap = timbradio_vidioc_querycap, 708c2ecf20Sopenharmony_ci .vidioc_g_tuner = timbradio_vidioc_g_tuner, 718c2ecf20Sopenharmony_ci .vidioc_s_tuner = timbradio_vidioc_s_tuner, 728c2ecf20Sopenharmony_ci .vidioc_g_frequency = timbradio_vidioc_g_frequency, 738c2ecf20Sopenharmony_ci .vidioc_s_frequency = timbradio_vidioc_s_frequency, 748c2ecf20Sopenharmony_ci .vidioc_log_status = v4l2_ctrl_log_status, 758c2ecf20Sopenharmony_ci .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, 768c2ecf20Sopenharmony_ci .vidioc_unsubscribe_event = v4l2_event_unsubscribe, 778c2ecf20Sopenharmony_ci}; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations timbradio_fops = { 808c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 818c2ecf20Sopenharmony_ci .open = v4l2_fh_open, 828c2ecf20Sopenharmony_ci .release = v4l2_fh_release, 838c2ecf20Sopenharmony_ci .poll = v4l2_ctrl_poll, 848c2ecf20Sopenharmony_ci .unlocked_ioctl = video_ioctl2, 858c2ecf20Sopenharmony_ci}; 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_cistatic int timbradio_probe(struct platform_device *pdev) 888c2ecf20Sopenharmony_ci{ 898c2ecf20Sopenharmony_ci struct timb_radio_platform_data *pdata = pdev->dev.platform_data; 908c2ecf20Sopenharmony_ci struct timbradio *tr; 918c2ecf20Sopenharmony_ci int err; 928c2ecf20Sopenharmony_ci 938c2ecf20Sopenharmony_ci if (!pdata) { 948c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Platform data missing\n"); 958c2ecf20Sopenharmony_ci err = -EINVAL; 968c2ecf20Sopenharmony_ci goto err; 978c2ecf20Sopenharmony_ci } 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci tr = devm_kzalloc(&pdev->dev, sizeof(*tr), GFP_KERNEL); 1008c2ecf20Sopenharmony_ci if (!tr) { 1018c2ecf20Sopenharmony_ci err = -ENOMEM; 1028c2ecf20Sopenharmony_ci goto err; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci tr->pdata = *pdata; 1068c2ecf20Sopenharmony_ci mutex_init(&tr->lock); 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_ci strscpy(tr->video_dev.name, "Timberdale Radio", 1098c2ecf20Sopenharmony_ci sizeof(tr->video_dev.name)); 1108c2ecf20Sopenharmony_ci tr->video_dev.fops = &timbradio_fops; 1118c2ecf20Sopenharmony_ci tr->video_dev.ioctl_ops = &timbradio_ioctl_ops; 1128c2ecf20Sopenharmony_ci tr->video_dev.release = video_device_release_empty; 1138c2ecf20Sopenharmony_ci tr->video_dev.minor = -1; 1148c2ecf20Sopenharmony_ci tr->video_dev.lock = &tr->lock; 1158c2ecf20Sopenharmony_ci tr->video_dev.device_caps = V4L2_CAP_TUNER | V4L2_CAP_RADIO; 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci strscpy(tr->v4l2_dev.name, DRIVER_NAME, sizeof(tr->v4l2_dev.name)); 1188c2ecf20Sopenharmony_ci err = v4l2_device_register(NULL, &tr->v4l2_dev); 1198c2ecf20Sopenharmony_ci if (err) 1208c2ecf20Sopenharmony_ci goto err; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci tr->video_dev.v4l2_dev = &tr->v4l2_dev; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci tr->sd_tuner = v4l2_i2c_new_subdev_board(&tr->v4l2_dev, 1258c2ecf20Sopenharmony_ci i2c_get_adapter(pdata->i2c_adapter), pdata->tuner, NULL); 1268c2ecf20Sopenharmony_ci tr->sd_dsp = v4l2_i2c_new_subdev_board(&tr->v4l2_dev, 1278c2ecf20Sopenharmony_ci i2c_get_adapter(pdata->i2c_adapter), pdata->dsp, NULL); 1288c2ecf20Sopenharmony_ci if (tr->sd_tuner == NULL || tr->sd_dsp == NULL) { 1298c2ecf20Sopenharmony_ci err = -ENODEV; 1308c2ecf20Sopenharmony_ci goto err_video_req; 1318c2ecf20Sopenharmony_ci } 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci tr->v4l2_dev.ctrl_handler = tr->sd_dsp->ctrl_handler; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci err = video_register_device(&tr->video_dev, VFL_TYPE_RADIO, -1); 1368c2ecf20Sopenharmony_ci if (err) { 1378c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Error reg video\n"); 1388c2ecf20Sopenharmony_ci goto err_video_req; 1398c2ecf20Sopenharmony_ci } 1408c2ecf20Sopenharmony_ci 1418c2ecf20Sopenharmony_ci video_set_drvdata(&tr->video_dev, tr); 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, tr); 1448c2ecf20Sopenharmony_ci return 0; 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_cierr_video_req: 1478c2ecf20Sopenharmony_ci v4l2_device_unregister(&tr->v4l2_dev); 1488c2ecf20Sopenharmony_cierr: 1498c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Failed to register: %d\n", err); 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci return err; 1528c2ecf20Sopenharmony_ci} 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_cistatic int timbradio_remove(struct platform_device *pdev) 1558c2ecf20Sopenharmony_ci{ 1568c2ecf20Sopenharmony_ci struct timbradio *tr = platform_get_drvdata(pdev); 1578c2ecf20Sopenharmony_ci 1588c2ecf20Sopenharmony_ci video_unregister_device(&tr->video_dev); 1598c2ecf20Sopenharmony_ci v4l2_device_unregister(&tr->v4l2_dev); 1608c2ecf20Sopenharmony_ci return 0; 1618c2ecf20Sopenharmony_ci} 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_cistatic struct platform_driver timbradio_platform_driver = { 1648c2ecf20Sopenharmony_ci .driver = { 1658c2ecf20Sopenharmony_ci .name = DRIVER_NAME, 1668c2ecf20Sopenharmony_ci }, 1678c2ecf20Sopenharmony_ci .probe = timbradio_probe, 1688c2ecf20Sopenharmony_ci .remove = timbradio_remove, 1698c2ecf20Sopenharmony_ci}; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_cimodule_platform_driver(timbradio_platform_driver); 1728c2ecf20Sopenharmony_ci 1738c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Timberdale Radio driver"); 1748c2ecf20Sopenharmony_ciMODULE_AUTHOR("Mocean Laboratories <info@mocean-labs.com>"); 1758c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 1768c2ecf20Sopenharmony_ciMODULE_VERSION("0.0.2"); 1778c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:"DRIVER_NAME); 178