18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * m52790 i2c ivtv driver.
48c2ecf20Sopenharmony_ci * Copyright (C) 2007  Hans Verkuil
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * A/V source switching Mitsubishi M52790SP/FP
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci
98c2ecf20Sopenharmony_ci
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/types.h>
128c2ecf20Sopenharmony_ci#include <linux/slab.h>
138c2ecf20Sopenharmony_ci#include <linux/ioctl.h>
148c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
158c2ecf20Sopenharmony_ci#include <linux/i2c.h>
168c2ecf20Sopenharmony_ci#include <linux/videodev2.h>
178c2ecf20Sopenharmony_ci#include <media/i2c/m52790.h>
188c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("i2c device driver for m52790 A/V switch");
218c2ecf20Sopenharmony_ciMODULE_AUTHOR("Hans Verkuil");
228c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_cistruct m52790_state {
268c2ecf20Sopenharmony_ci	struct v4l2_subdev sd;
278c2ecf20Sopenharmony_ci	u16 input;
288c2ecf20Sopenharmony_ci	u16 output;
298c2ecf20Sopenharmony_ci};
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_cistatic inline struct m52790_state *to_state(struct v4l2_subdev *sd)
328c2ecf20Sopenharmony_ci{
338c2ecf20Sopenharmony_ci	return container_of(sd, struct m52790_state, sd);
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
378c2ecf20Sopenharmony_ci
388c2ecf20Sopenharmony_cistatic int m52790_write(struct v4l2_subdev *sd)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct m52790_state *state = to_state(sd);
418c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_ci	u8 sw1 = (state->input | state->output) & 0xff;
448c2ecf20Sopenharmony_ci	u8 sw2 = (state->input | state->output) >> 8;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	return i2c_smbus_write_byte_data(client, sw1, sw2);
478c2ecf20Sopenharmony_ci}
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/* Note: audio and video are linked and cannot be switched separately.
508c2ecf20Sopenharmony_ci   So audio and video routing commands are identical for this chip.
518c2ecf20Sopenharmony_ci   In theory the video amplifier and audio modes could be handled
528c2ecf20Sopenharmony_ci   separately for the output, but that seems to be overkill right now.
538c2ecf20Sopenharmony_ci   The same holds for implementing an audio mute control, this is now
548c2ecf20Sopenharmony_ci   part of the audio output routing. The normal case is that another
558c2ecf20Sopenharmony_ci   chip takes care of the actual muting so making it part of the
568c2ecf20Sopenharmony_ci   output routing seems to be the right thing to do for now. */
578c2ecf20Sopenharmony_cistatic int m52790_s_routing(struct v4l2_subdev *sd,
588c2ecf20Sopenharmony_ci			    u32 input, u32 output, u32 config)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	struct m52790_state *state = to_state(sd);
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	state->input = input;
638c2ecf20Sopenharmony_ci	state->output = output;
648c2ecf20Sopenharmony_ci	m52790_write(sd);
658c2ecf20Sopenharmony_ci	return 0;
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
698c2ecf20Sopenharmony_cistatic int m52790_g_register(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct m52790_state *state = to_state(sd);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	if (reg->reg != 0)
748c2ecf20Sopenharmony_ci		return -EINVAL;
758c2ecf20Sopenharmony_ci	reg->size = 1;
768c2ecf20Sopenharmony_ci	reg->val = state->input | state->output;
778c2ecf20Sopenharmony_ci	return 0;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic int m52790_s_register(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	struct m52790_state *state = to_state(sd);
838c2ecf20Sopenharmony_ci
848c2ecf20Sopenharmony_ci	if (reg->reg != 0)
858c2ecf20Sopenharmony_ci		return -EINVAL;
868c2ecf20Sopenharmony_ci	state->input = reg->val & 0x0303;
878c2ecf20Sopenharmony_ci	state->output = reg->val & ~0x0303;
888c2ecf20Sopenharmony_ci	m52790_write(sd);
898c2ecf20Sopenharmony_ci	return 0;
908c2ecf20Sopenharmony_ci}
918c2ecf20Sopenharmony_ci#endif
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_cistatic int m52790_log_status(struct v4l2_subdev *sd)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct m52790_state *state = to_state(sd);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	v4l2_info(sd, "Switch 1: %02x\n",
988c2ecf20Sopenharmony_ci			(state->input | state->output) & 0xff);
998c2ecf20Sopenharmony_ci	v4l2_info(sd, "Switch 2: %02x\n",
1008c2ecf20Sopenharmony_ci			(state->input | state->output) >> 8);
1018c2ecf20Sopenharmony_ci	return 0;
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_core_ops m52790_core_ops = {
1078c2ecf20Sopenharmony_ci	.log_status = m52790_log_status,
1088c2ecf20Sopenharmony_ci#ifdef CONFIG_VIDEO_ADV_DEBUG
1098c2ecf20Sopenharmony_ci	.g_register = m52790_g_register,
1108c2ecf20Sopenharmony_ci	.s_register = m52790_s_register,
1118c2ecf20Sopenharmony_ci#endif
1128c2ecf20Sopenharmony_ci};
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_audio_ops m52790_audio_ops = {
1158c2ecf20Sopenharmony_ci	.s_routing = m52790_s_routing,
1168c2ecf20Sopenharmony_ci};
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_video_ops m52790_video_ops = {
1198c2ecf20Sopenharmony_ci	.s_routing = m52790_s_routing,
1208c2ecf20Sopenharmony_ci};
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_ops m52790_ops = {
1238c2ecf20Sopenharmony_ci	.core = &m52790_core_ops,
1248c2ecf20Sopenharmony_ci	.audio = &m52790_audio_ops,
1258c2ecf20Sopenharmony_ci	.video = &m52790_video_ops,
1268c2ecf20Sopenharmony_ci};
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci/* i2c implementation */
1318c2ecf20Sopenharmony_ci
1328c2ecf20Sopenharmony_cistatic int m52790_probe(struct i2c_client *client,
1338c2ecf20Sopenharmony_ci			const struct i2c_device_id *id)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	struct m52790_state *state;
1368c2ecf20Sopenharmony_ci	struct v4l2_subdev *sd;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	/* Check if the adapter supports the needed features */
1398c2ecf20Sopenharmony_ci	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
1408c2ecf20Sopenharmony_ci		return -EIO;
1418c2ecf20Sopenharmony_ci
1428c2ecf20Sopenharmony_ci	v4l_info(client, "chip found @ 0x%x (%s)\n",
1438c2ecf20Sopenharmony_ci			client->addr << 1, client->adapter->name);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci	state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
1468c2ecf20Sopenharmony_ci	if (state == NULL)
1478c2ecf20Sopenharmony_ci		return -ENOMEM;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_ci	sd = &state->sd;
1508c2ecf20Sopenharmony_ci	v4l2_i2c_subdev_init(sd, client, &m52790_ops);
1518c2ecf20Sopenharmony_ci	state->input = M52790_IN_TUNER;
1528c2ecf20Sopenharmony_ci	state->output = M52790_OUT_STEREO;
1538c2ecf20Sopenharmony_ci	m52790_write(sd);
1548c2ecf20Sopenharmony_ci	return 0;
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic int m52790_remove(struct i2c_client *client)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct v4l2_subdev *sd = i2c_get_clientdata(client);
1608c2ecf20Sopenharmony_ci
1618c2ecf20Sopenharmony_ci	v4l2_device_unregister_subdev(sd);
1628c2ecf20Sopenharmony_ci	return 0;
1638c2ecf20Sopenharmony_ci}
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_cistatic const struct i2c_device_id m52790_id[] = {
1688c2ecf20Sopenharmony_ci	{ "m52790", 0 },
1698c2ecf20Sopenharmony_ci	{ }
1708c2ecf20Sopenharmony_ci};
1718c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, m52790_id);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_cistatic struct i2c_driver m52790_driver = {
1748c2ecf20Sopenharmony_ci	.driver = {
1758c2ecf20Sopenharmony_ci		.name	= "m52790",
1768c2ecf20Sopenharmony_ci	},
1778c2ecf20Sopenharmony_ci	.probe		= m52790_probe,
1788c2ecf20Sopenharmony_ci	.remove		= m52790_remove,
1798c2ecf20Sopenharmony_ci	.id_table	= m52790_id,
1808c2ecf20Sopenharmony_ci};
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cimodule_i2c_driver(m52790_driver);
183