162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * drivers/media/radio/radio-si4713.c
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Platform Driver for Silicon Labs Si4713 FM Radio Transmitter:
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Copyright (c) 2008 Instituto Nokia de Tecnologia - INdT
862306a36Sopenharmony_ci * Contact: Eduardo Valentin <eduardo.valentin@nokia.com>
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/module.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/platform_device.h>
1562306a36Sopenharmony_ci#include <linux/i2c.h>
1662306a36Sopenharmony_ci#include <linux/videodev2.h>
1762306a36Sopenharmony_ci#include <linux/slab.h>
1862306a36Sopenharmony_ci#include <media/v4l2-device.h>
1962306a36Sopenharmony_ci#include <media/v4l2-common.h>
2062306a36Sopenharmony_ci#include <media/v4l2-ioctl.h>
2162306a36Sopenharmony_ci#include <media/v4l2-fh.h>
2262306a36Sopenharmony_ci#include <media/v4l2-ctrls.h>
2362306a36Sopenharmony_ci#include <media/v4l2-event.h>
2462306a36Sopenharmony_ci#include "si4713.h"
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci/* module parameters */
2762306a36Sopenharmony_cistatic int radio_nr = -1;	/* radio device minor (-1 ==> auto assign) */
2862306a36Sopenharmony_cimodule_param(radio_nr, int, 0);
2962306a36Sopenharmony_ciMODULE_PARM_DESC(radio_nr,
3062306a36Sopenharmony_ci		 "Minor number for radio device (-1 ==> auto assign)");
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
3362306a36Sopenharmony_ciMODULE_AUTHOR("Eduardo Valentin <eduardo.valentin@nokia.com>");
3462306a36Sopenharmony_ciMODULE_DESCRIPTION("Platform driver for Si4713 FM Radio Transmitter");
3562306a36Sopenharmony_ciMODULE_VERSION("0.0.1");
3662306a36Sopenharmony_ciMODULE_ALIAS("platform:radio-si4713");
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci/* Driver state struct */
3962306a36Sopenharmony_cistruct radio_si4713_device {
4062306a36Sopenharmony_ci	struct v4l2_device		v4l2_dev;
4162306a36Sopenharmony_ci	struct video_device		radio_dev;
4262306a36Sopenharmony_ci	struct mutex lock;
4362306a36Sopenharmony_ci};
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci/* radio_si4713_fops - file operations interface */
4662306a36Sopenharmony_cistatic const struct v4l2_file_operations radio_si4713_fops = {
4762306a36Sopenharmony_ci	.owner		= THIS_MODULE,
4862306a36Sopenharmony_ci	.open = v4l2_fh_open,
4962306a36Sopenharmony_ci	.release = v4l2_fh_release,
5062306a36Sopenharmony_ci	.poll = v4l2_ctrl_poll,
5162306a36Sopenharmony_ci	/* Note: locking is done at the subdev level in the i2c driver. */
5262306a36Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
5362306a36Sopenharmony_ci};
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci/* Video4Linux Interface */
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci/* radio_si4713_querycap - query device capabilities */
5862306a36Sopenharmony_cistatic int radio_si4713_querycap(struct file *file, void *priv,
5962306a36Sopenharmony_ci					struct v4l2_capability *capability)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	strscpy(capability->driver, "radio-si4713", sizeof(capability->driver));
6262306a36Sopenharmony_ci	strscpy(capability->card, "Silicon Labs Si4713 Modulator",
6362306a36Sopenharmony_ci		sizeof(capability->card));
6462306a36Sopenharmony_ci	strscpy(capability->bus_info, "platform:radio-si4713",
6562306a36Sopenharmony_ci		sizeof(capability->bus_info));
6662306a36Sopenharmony_ci	return 0;
6762306a36Sopenharmony_ci}
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/*
7062306a36Sopenharmony_ci * v4l2 ioctl call backs.
7162306a36Sopenharmony_ci * we are just a wrapper for v4l2_sub_devs.
7262306a36Sopenharmony_ci */
7362306a36Sopenharmony_cistatic inline struct v4l2_device *get_v4l2_dev(struct file *file)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	return &((struct radio_si4713_device *)video_drvdata(file))->v4l2_dev;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_cistatic int radio_si4713_g_modulator(struct file *file, void *p,
7962306a36Sopenharmony_ci				    struct v4l2_modulator *vm)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
8262306a36Sopenharmony_ci					  g_modulator, vm);
8362306a36Sopenharmony_ci}
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_cistatic int radio_si4713_s_modulator(struct file *file, void *p,
8662306a36Sopenharmony_ci				    const struct v4l2_modulator *vm)
8762306a36Sopenharmony_ci{
8862306a36Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
8962306a36Sopenharmony_ci					  s_modulator, vm);
9062306a36Sopenharmony_ci}
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_cistatic int radio_si4713_g_frequency(struct file *file, void *p,
9362306a36Sopenharmony_ci				    struct v4l2_frequency *vf)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
9662306a36Sopenharmony_ci					  g_frequency, vf);
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic int radio_si4713_s_frequency(struct file *file, void *p,
10062306a36Sopenharmony_ci				    const struct v4l2_frequency *vf)
10162306a36Sopenharmony_ci{
10262306a36Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, tuner,
10362306a36Sopenharmony_ci					  s_frequency, vf);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic long radio_si4713_default(struct file *file, void *p,
10762306a36Sopenharmony_ci				 bool valid_prio, unsigned int cmd, void *arg)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	return v4l2_device_call_until_err(get_v4l2_dev(file), 0, core,
11062306a36Sopenharmony_ci					  ioctl, cmd, arg);
11162306a36Sopenharmony_ci}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops radio_si4713_ioctl_ops = {
11462306a36Sopenharmony_ci	.vidioc_querycap	= radio_si4713_querycap,
11562306a36Sopenharmony_ci	.vidioc_g_modulator	= radio_si4713_g_modulator,
11662306a36Sopenharmony_ci	.vidioc_s_modulator	= radio_si4713_s_modulator,
11762306a36Sopenharmony_ci	.vidioc_g_frequency	= radio_si4713_g_frequency,
11862306a36Sopenharmony_ci	.vidioc_s_frequency	= radio_si4713_s_frequency,
11962306a36Sopenharmony_ci	.vidioc_log_status      = v4l2_ctrl_log_status,
12062306a36Sopenharmony_ci	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
12162306a36Sopenharmony_ci	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
12262306a36Sopenharmony_ci	.vidioc_default		= radio_si4713_default,
12362306a36Sopenharmony_ci};
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci/* radio_si4713_vdev_template - video device interface */
12662306a36Sopenharmony_cistatic const struct video_device radio_si4713_vdev_template = {
12762306a36Sopenharmony_ci	.fops			= &radio_si4713_fops,
12862306a36Sopenharmony_ci	.name			= "radio-si4713",
12962306a36Sopenharmony_ci	.release		= video_device_release_empty,
13062306a36Sopenharmony_ci	.ioctl_ops		= &radio_si4713_ioctl_ops,
13162306a36Sopenharmony_ci	.vfl_dir		= VFL_DIR_TX,
13262306a36Sopenharmony_ci};
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci/* Platform driver interface */
13562306a36Sopenharmony_ci/* radio_si4713_pdriver_probe - probe for the device */
13662306a36Sopenharmony_cistatic int radio_si4713_pdriver_probe(struct platform_device *pdev)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	struct radio_si4713_platform_data *pdata = pdev->dev.platform_data;
13962306a36Sopenharmony_ci	struct radio_si4713_device *rsdev;
14062306a36Sopenharmony_ci	struct v4l2_subdev *sd;
14162306a36Sopenharmony_ci	int rval = 0;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	if (!pdata) {
14462306a36Sopenharmony_ci		dev_err(&pdev->dev, "Cannot proceed without platform data.\n");
14562306a36Sopenharmony_ci		rval = -EINVAL;
14662306a36Sopenharmony_ci		goto exit;
14762306a36Sopenharmony_ci	}
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	rsdev = devm_kzalloc(&pdev->dev, sizeof(*rsdev), GFP_KERNEL);
15062306a36Sopenharmony_ci	if (!rsdev) {
15162306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to alloc video device.\n");
15262306a36Sopenharmony_ci		rval = -ENOMEM;
15362306a36Sopenharmony_ci		goto exit;
15462306a36Sopenharmony_ci	}
15562306a36Sopenharmony_ci	mutex_init(&rsdev->lock);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	rval = v4l2_device_register(&pdev->dev, &rsdev->v4l2_dev);
15862306a36Sopenharmony_ci	if (rval) {
15962306a36Sopenharmony_ci		dev_err(&pdev->dev, "Failed to register v4l2 device.\n");
16062306a36Sopenharmony_ci		goto exit;
16162306a36Sopenharmony_ci	}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	sd = i2c_get_clientdata(pdata->subdev);
16462306a36Sopenharmony_ci	rval = v4l2_device_register_subdev(&rsdev->v4l2_dev, sd);
16562306a36Sopenharmony_ci	if (rval) {
16662306a36Sopenharmony_ci		dev_err(&pdev->dev, "Cannot get v4l2 subdevice\n");
16762306a36Sopenharmony_ci		goto unregister_v4l2_dev;
16862306a36Sopenharmony_ci	}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	rsdev->radio_dev = radio_si4713_vdev_template;
17162306a36Sopenharmony_ci	rsdev->radio_dev.v4l2_dev = &rsdev->v4l2_dev;
17262306a36Sopenharmony_ci	rsdev->radio_dev.ctrl_handler = sd->ctrl_handler;
17362306a36Sopenharmony_ci	/* Serialize all access to the si4713 */
17462306a36Sopenharmony_ci	rsdev->radio_dev.lock = &rsdev->lock;
17562306a36Sopenharmony_ci	rsdev->radio_dev.device_caps = V4L2_CAP_MODULATOR | V4L2_CAP_RDS_OUTPUT;
17662306a36Sopenharmony_ci	video_set_drvdata(&rsdev->radio_dev, rsdev);
17762306a36Sopenharmony_ci	if (video_register_device(&rsdev->radio_dev, VFL_TYPE_RADIO, radio_nr)) {
17862306a36Sopenharmony_ci		dev_err(&pdev->dev, "Could not register video device.\n");
17962306a36Sopenharmony_ci		rval = -EIO;
18062306a36Sopenharmony_ci		goto unregister_v4l2_dev;
18162306a36Sopenharmony_ci	}
18262306a36Sopenharmony_ci	dev_info(&pdev->dev, "New device successfully probed\n");
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	goto exit;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ciunregister_v4l2_dev:
18762306a36Sopenharmony_ci	v4l2_device_unregister(&rsdev->v4l2_dev);
18862306a36Sopenharmony_ciexit:
18962306a36Sopenharmony_ci	return rval;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci/* radio_si4713_pdriver_remove - remove the device */
19362306a36Sopenharmony_cistatic void radio_si4713_pdriver_remove(struct platform_device *pdev)
19462306a36Sopenharmony_ci{
19562306a36Sopenharmony_ci	struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev);
19662306a36Sopenharmony_ci	struct radio_si4713_device *rsdev;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	rsdev = container_of(v4l2_dev, struct radio_si4713_device, v4l2_dev);
19962306a36Sopenharmony_ci	video_unregister_device(&rsdev->radio_dev);
20062306a36Sopenharmony_ci	v4l2_device_unregister(&rsdev->v4l2_dev);
20162306a36Sopenharmony_ci}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_cistatic struct platform_driver radio_si4713_pdriver = {
20462306a36Sopenharmony_ci	.driver		= {
20562306a36Sopenharmony_ci		.name	= "radio-si4713",
20662306a36Sopenharmony_ci	},
20762306a36Sopenharmony_ci	.probe		= radio_si4713_pdriver_probe,
20862306a36Sopenharmony_ci	.remove_new     = radio_si4713_pdriver_remove,
20962306a36Sopenharmony_ci};
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cimodule_platform_driver(radio_si4713_pdriver);
212