18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * drivers/media/radio/radio-si4713.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Platform Driver for Silicon Labs Si4713 FM Radio Transmitter:
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT
88c2ecf20Sopenharmony_ci * Contact: Eduardo Valentin <eduardo.valentin@nokia.com>
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include <linux/kernel.h>
128c2ecf20Sopenharmony_ci#include <linux/module.h>
138c2ecf20Sopenharmony_ci#include <linux/init.h>
148c2ecf20Sopenharmony_ci#include <linux/platform_device.h>
158c2ecf20Sopenharmony_ci#include <linux/i2c.h>
168c2ecf20Sopenharmony_ci#include <linux/videodev2.h>
178c2ecf20Sopenharmony_ci#include <linux/slab.h>
188c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
198c2ecf20Sopenharmony_ci#include <media/v4l2-common.h>
208c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h>
218c2ecf20Sopenharmony_ci#include <media/v4l2-fh.h>
228c2ecf20Sopenharmony_ci#include <media/v4l2-ctrls.h>
238c2ecf20Sopenharmony_ci#include <media/v4l2-event.h>
248c2ecf20Sopenharmony_ci#include "si4713.h"
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci/* module parameters */
278c2ecf20Sopenharmony_cistatic int radio_nr = -1;	/* radio device minor (-1 ==> auto assign) */
288c2ecf20Sopenharmony_cimodule_param(radio_nr, int, 0);
298c2ecf20Sopenharmony_ciMODULE_PARM_DESC(radio_nr,
308c2ecf20Sopenharmony_ci		 "Minor number for radio device (-1 ==> auto assign)");
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2");
338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>");
348c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter");
358c2ecf20Sopenharmony_ciMODULE_VERSION("0.0.1");
368c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:radio-si4713");
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_ci/* Driver state struct */
398c2ecf20Sopenharmony_cistruct radio_si4713_device {
408c2ecf20Sopenharmony_ci	struct v4l2_device		v4l2_dev;
418c2ecf20Sopenharmony_ci	struct video_device		radio_dev;
428c2ecf20Sopenharmony_ci	struct mutex lock;
438c2ecf20Sopenharmony_ci};
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_ci/* radio_si4713_fops - file operations interface */
468c2ecf20Sopenharmony_cistatic const struct v4l2_file_operations radio_si4713_fops = {
478c2ecf20Sopenharmony_ci	.owner		= THIS_MODULE,
488c2ecf20Sopenharmony_ci	.open = v4l2_fh_open,
498c2ecf20Sopenharmony_ci	.release = v4l2_fh_release,
508c2ecf20Sopenharmony_ci	.poll = v4l2_ctrl_poll,
518c2ecf20Sopenharmony_ci	/* Note: locking is done at the subdev level in the i2c driver. */
528c2ecf20Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
538c2ecf20Sopenharmony_ci};
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci/* Video4Linux Interface */
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci/* radio_si4713_querycap - query device capabilities */
588c2ecf20Sopenharmony_cistatic int radio_si4713_querycap(struct file *file, void *priv,
598c2ecf20Sopenharmony_ci					struct v4l2_capability *capability)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	strscpy(capability->driver, "radio-si4713", sizeof(capability->driver));
628c2ecf20Sopenharmony_ci	strscpy(capability->card, "Silicon Labs Si4713 Modulator",
638c2ecf20Sopenharmony_ci		sizeof(capability->card));
648c2ecf20Sopenharmony_ci	strscpy(capability->bus_info, "platform:radio-si4713",
658c2ecf20Sopenharmony_ci		sizeof(capability->bus_info));
668c2ecf20Sopenharmony_ci	return 0;
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci/*
708c2ecf20Sopenharmony_ci * v4l2 ioctl call backs.
718c2ecf20Sopenharmony_ci * we are just a wrapper for v4l2_sub_devs.
728c2ecf20Sopenharmony_ci */
738c2ecf20Sopenharmony_cistatic inline struct v4l2_device *get_v4l2_dev(struct file *file)
748c2ecf20Sopenharmony_ci{
758c2ecf20Sopenharmony_ci	return &((struct radio_si4713_device *)video_drvdata(file))->v4l2_dev;
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_cistatic int radio_si4713_g_modulator(struct file *file, void *p,
798c2ecf20Sopenharmony_ci				    struct v4l2_modulator *vm)
808c2ecf20Sopenharmony_ci{
818c2ecf20Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
828c2ecf20Sopenharmony_ci					  g_modulator, vm);
838c2ecf20Sopenharmony_ci}
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_cistatic int radio_si4713_s_modulator(struct file *file, void *p,
868c2ecf20Sopenharmony_ci				    const struct v4l2_modulator *vm)
878c2ecf20Sopenharmony_ci{
888c2ecf20Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
898c2ecf20Sopenharmony_ci					  s_modulator, vm);
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_cistatic int radio_si4713_g_frequency(struct file *file, void *p,
938c2ecf20Sopenharmony_ci				    struct v4l2_frequency *vf)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
968c2ecf20Sopenharmony_ci					  g_frequency, vf);
978c2ecf20Sopenharmony_ci}
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_cistatic int radio_si4713_s_frequency(struct file *file, void *p,
1008c2ecf20Sopenharmony_ci				    const struct v4l2_frequency *vf)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
1038c2ecf20Sopenharmony_ci					  s_frequency, vf);
1048c2ecf20Sopenharmony_ci}
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic long radio_si4713_default(struct file *file, void *p,
1078c2ecf20Sopenharmony_ci				 bool valid_prio, unsigned int cmd, void *arg)
1088c2ecf20Sopenharmony_ci{
1098c2ecf20Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
1108c2ecf20Sopenharmony_ci					  ioctl, cmd, arg);
1118c2ecf20Sopenharmony_ci}
1128c2ecf20Sopenharmony_ci
1138c2ecf20Sopenharmony_cistatic struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {
1148c2ecf20Sopenharmony_ci	.vidioc_querycap	= radio_si4713_querycap,
1158c2ecf20Sopenharmony_ci	.vidioc_g_modulator	= radio_si4713_g_modulator,
1168c2ecf20Sopenharmony_ci	.vidioc_s_modulator	= radio_si4713_s_modulator,
1178c2ecf20Sopenharmony_ci	.vidioc_g_frequency	= radio_si4713_g_frequency,
1188c2ecf20Sopenharmony_ci	.vidioc_s_frequency	= radio_si4713_s_frequency,
1198c2ecf20Sopenharmony_ci	.vidioc_log_status      = v4l2_ctrl_log_status,
1208c2ecf20Sopenharmony_ci	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
1218c2ecf20Sopenharmony_ci	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
1228c2ecf20Sopenharmony_ci	.vidioc_default		= radio_si4713_default,
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ci
1258c2ecf20Sopenharmony_ci/* radio_si4713_vdev_template - video device interface */
1268c2ecf20Sopenharmony_cistatic const struct video_device radio_si4713_vdev_template = {
1278c2ecf20Sopenharmony_ci	.fops			= &radio_si4713_fops,
1288c2ecf20Sopenharmony_ci	.name			= "radio-si4713",
1298c2ecf20Sopenharmony_ci	.release		= video_device_release_empty,
1308c2ecf20Sopenharmony_ci	.ioctl_ops		= &radio_si4713_ioctl_ops,
1318c2ecf20Sopenharmony_ci	.vfl_dir		= VFL_DIR_TX,
1328c2ecf20Sopenharmony_ci};
1338c2ecf20Sopenharmony_ci
1348c2ecf20Sopenharmony_ci/* Platform driver interface */
1358c2ecf20Sopenharmony_ci/* radio_si4713_pdriver_probe - probe for the device */
1368c2ecf20Sopenharmony_cistatic int radio_si4713_pdriver_probe(struct platform_device *pdev)
1378c2ecf20Sopenharmony_ci{
1388c2ecf20Sopenharmony_ci	struct radio_si4713_platform_data *pdata = pdev->dev.platform_data;
1398c2ecf20Sopenharmony_ci	struct radio_si4713_device *rsdev;
1408c2ecf20Sopenharmony_ci	struct v4l2_subdev *sd;
1418c2ecf20Sopenharmony_ci	int rval = 0;
1428c2ecf20Sopenharmony_ci
1438c2ecf20Sopenharmony_ci	if (!pdata) {
1448c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Cannot proceed without platform data.\n");
1458c2ecf20Sopenharmony_ci		rval = -EINVAL;
1468c2ecf20Sopenharmony_ci		goto exit;
1478c2ecf20Sopenharmony_ci	}
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	rsdev = devm_kzalloc(&pdev->dev, sizeof(*rsdev), GFP_KERNEL);
1508c2ecf20Sopenharmony_ci	if (!rsdev) {
1518c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to alloc video device.\n");
1528c2ecf20Sopenharmony_ci		rval = -ENOMEM;
1538c2ecf20Sopenharmony_ci		goto exit;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci	mutex_init(&rsdev->lock);
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev);
1588c2ecf20Sopenharmony_ci	if (rval) {
1598c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Failed to register v4l2 device.\n");
1608c2ecf20Sopenharmony_ci		goto exit;
1618c2ecf20Sopenharmony_ci	}
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	sd = i2c_get_clientdata(pdata->subdev);
1648c2ecf20Sopenharmony_ci	rval = v4l2_device_register_subdev(&rsdev->v4l2_dev, sd);
1658c2ecf20Sopenharmony_ci	if (rval) {
1668c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n");
1678c2ecf20Sopenharmony_ci		goto unregister_v4l2_dev;
1688c2ecf20Sopenharmony_ci	}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_ci	rsdev->radio_dev = radio_si4713_vdev_template;
1718c2ecf20Sopenharmony_ci	rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev;
1728c2ecf20Sopenharmony_ci	rsdev->radio_dev.ctrl_handler = sd->ctrl_handler;
1738c2ecf20Sopenharmony_ci	/* Serialize all access to the si4713 */
1748c2ecf20Sopenharmony_ci	rsdev->radio_dev.lock = &rsdev->lock;
1758c2ecf20Sopenharmony_ci	rsdev->radio_dev.device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT;
1768c2ecf20Sopenharmony_ci	video_set_drvdata(&rsdev->radio_dev, rsdev);
1778c2ecf20Sopenharmony_ci	if (video_register_device(&rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) {
1788c2ecf20Sopenharmony_ci		dev_err(&pdev->dev, "Could not register video device.\n");
1798c2ecf20Sopenharmony_ci		rval = -EIO;
1808c2ecf20Sopenharmony_ci		goto unregister_v4l2_dev;
1818c2ecf20Sopenharmony_ci	}
1828c2ecf20Sopenharmony_ci	dev_info(&pdev->dev, "New device successfully probed\n");
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	goto exit;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ciunregister_v4l2_dev:
1878c2ecf20Sopenharmony_ci	v4l2_device_unregister(&rsdev->v4l2_dev);
1888c2ecf20Sopenharmony_ciexit:
1898c2ecf20Sopenharmony_ci	return rval;
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci/* radio_si4713_pdriver_remove - remove the device */
1938c2ecf20Sopenharmony_cistatic int radio_si4713_pdriver_remove(struct platform_device *pdev)
1948c2ecf20Sopenharmony_ci{
1958c2ecf20Sopenharmony_ci	struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
1968c2ecf20Sopenharmony_ci	struct radio_si4713_device *rsdev;
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci	rsdev = container_of(v4l2_dev, struct radio_si4713_device, v4l2_dev);
1998c2ecf20Sopenharmony_ci	video_unregister_device(&rsdev->radio_dev);
2008c2ecf20Sopenharmony_ci	v4l2_device_unregister(&rsdev->v4l2_dev);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	return 0;
2038c2ecf20Sopenharmony_ci}
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_cistatic struct platform_driver radio_si4713_pdriver = {
2068c2ecf20Sopenharmony_ci	.driver		= {
2078c2ecf20Sopenharmony_ci		.name	= "radio-si4713",
2088c2ecf20Sopenharmony_ci	},
2098c2ecf20Sopenharmony_ci	.probe		= radio_si4713_pdriver_probe,
2108c2ecf20Sopenharmony_ci	.remove         = radio_si4713_pdriver_remove,
2118c2ecf20Sopenharmony_ci};
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_cimodule_platform_driver(radio_si4713_pdriver);
214