xref: /linux/drivers/media/i2c/vp27smpx.c (revision 3a39d672e7f48b8d6b91a09afa4b55352773b4b5)
1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2cb7a01acSMauro Carvalho Chehab /*
3cb7a01acSMauro Carvalho Chehab  * vp27smpx - driver version 0.0.1
4cb7a01acSMauro Carvalho Chehab  *
5cb7a01acSMauro Carvalho Chehab  * Copyright (C) 2007 Hans Verkuil <hverkuil@xs4all.nl>
6cb7a01acSMauro Carvalho Chehab  *
7cb7a01acSMauro Carvalho Chehab  * Based on a tvaudio patch from Takahiro Adachi <tadachi@tadachi-net.com>
8cb7a01acSMauro Carvalho Chehab  * and Kazuhiko Kawakami <kazz-0@mail.goo.ne.jp>
9cb7a01acSMauro Carvalho Chehab  */
10cb7a01acSMauro Carvalho Chehab 
11cb7a01acSMauro Carvalho Chehab #include <linux/module.h>
12cb7a01acSMauro Carvalho Chehab #include <linux/types.h>
13cb7a01acSMauro Carvalho Chehab #include <linux/slab.h>
14cb7a01acSMauro Carvalho Chehab #include <linux/ioctl.h>
157c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
16cb7a01acSMauro Carvalho Chehab #include <linux/i2c.h>
17cb7a01acSMauro Carvalho Chehab #include <linux/videodev2.h>
18cb7a01acSMauro Carvalho Chehab #include <media/v4l2-device.h>
19cb7a01acSMauro Carvalho Chehab 
20cb7a01acSMauro Carvalho Chehab MODULE_DESCRIPTION("vp27smpx driver");
21cb7a01acSMauro Carvalho Chehab MODULE_AUTHOR("Hans Verkuil");
22cb7a01acSMauro Carvalho Chehab MODULE_LICENSE("GPL");
23cb7a01acSMauro Carvalho Chehab 
24cb7a01acSMauro Carvalho Chehab 
25cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------- */
26cb7a01acSMauro Carvalho Chehab 
27cb7a01acSMauro Carvalho Chehab struct vp27smpx_state {
28cb7a01acSMauro Carvalho Chehab 	struct v4l2_subdev sd;
29cb7a01acSMauro Carvalho Chehab 	int radio;
30cb7a01acSMauro Carvalho Chehab 	u32 audmode;
31cb7a01acSMauro Carvalho Chehab };
32cb7a01acSMauro Carvalho Chehab 
to_state(struct v4l2_subdev * sd)33cb7a01acSMauro Carvalho Chehab static inline struct vp27smpx_state *to_state(struct v4l2_subdev *sd)
34cb7a01acSMauro Carvalho Chehab {
35cb7a01acSMauro Carvalho Chehab 	return container_of(sd, struct vp27smpx_state, sd);
36cb7a01acSMauro Carvalho Chehab }
37cb7a01acSMauro Carvalho Chehab 
vp27smpx_set_audmode(struct v4l2_subdev * sd,u32 audmode)38cb7a01acSMauro Carvalho Chehab static void vp27smpx_set_audmode(struct v4l2_subdev *sd, u32 audmode)
39cb7a01acSMauro Carvalho Chehab {
40cb7a01acSMauro Carvalho Chehab 	struct vp27smpx_state *state = to_state(sd);
41cb7a01acSMauro Carvalho Chehab 	struct i2c_client *client = v4l2_get_subdevdata(sd);
42cb7a01acSMauro Carvalho Chehab 	u8 data[3] = { 0x00, 0x00, 0x04 };
43cb7a01acSMauro Carvalho Chehab 
44cb7a01acSMauro Carvalho Chehab 	switch (audmode) {
45cb7a01acSMauro Carvalho Chehab 	case V4L2_TUNER_MODE_MONO:
46cb7a01acSMauro Carvalho Chehab 	case V4L2_TUNER_MODE_LANG1:
47cb7a01acSMauro Carvalho Chehab 		break;
48cb7a01acSMauro Carvalho Chehab 	case V4L2_TUNER_MODE_STEREO:
49cb7a01acSMauro Carvalho Chehab 	case V4L2_TUNER_MODE_LANG1_LANG2:
50cb7a01acSMauro Carvalho Chehab 		data[1] = 0x01;
51cb7a01acSMauro Carvalho Chehab 		break;
52cb7a01acSMauro Carvalho Chehab 	case V4L2_TUNER_MODE_LANG2:
53cb7a01acSMauro Carvalho Chehab 		data[1] = 0x02;
54cb7a01acSMauro Carvalho Chehab 		break;
55cb7a01acSMauro Carvalho Chehab 	}
56cb7a01acSMauro Carvalho Chehab 
57cb7a01acSMauro Carvalho Chehab 	if (i2c_master_send(client, data, sizeof(data)) != sizeof(data))
58cb7a01acSMauro Carvalho Chehab 		v4l2_err(sd, "I/O error setting audmode\n");
59cb7a01acSMauro Carvalho Chehab 	else
60cb7a01acSMauro Carvalho Chehab 		state->audmode = audmode;
61cb7a01acSMauro Carvalho Chehab }
62cb7a01acSMauro Carvalho Chehab 
vp27smpx_s_radio(struct v4l2_subdev * sd)63cb7a01acSMauro Carvalho Chehab static int vp27smpx_s_radio(struct v4l2_subdev *sd)
64cb7a01acSMauro Carvalho Chehab {
65cb7a01acSMauro Carvalho Chehab 	struct vp27smpx_state *state = to_state(sd);
66cb7a01acSMauro Carvalho Chehab 
67cb7a01acSMauro Carvalho Chehab 	state->radio = 1;
68cb7a01acSMauro Carvalho Chehab 	return 0;
69cb7a01acSMauro Carvalho Chehab }
70cb7a01acSMauro Carvalho Chehab 
vp27smpx_s_std(struct v4l2_subdev * sd,v4l2_std_id norm)71cb7a01acSMauro Carvalho Chehab static int vp27smpx_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
72cb7a01acSMauro Carvalho Chehab {
73cb7a01acSMauro Carvalho Chehab 	struct vp27smpx_state *state = to_state(sd);
74cb7a01acSMauro Carvalho Chehab 
75cb7a01acSMauro Carvalho Chehab 	state->radio = 0;
76cb7a01acSMauro Carvalho Chehab 	return 0;
77cb7a01acSMauro Carvalho Chehab }
78cb7a01acSMauro Carvalho Chehab 
vp27smpx_s_tuner(struct v4l2_subdev * sd,const struct v4l2_tuner * vt)792f73c7c5SHans Verkuil static int vp27smpx_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt)
80cb7a01acSMauro Carvalho Chehab {
81cb7a01acSMauro Carvalho Chehab 	struct vp27smpx_state *state = to_state(sd);
82cb7a01acSMauro Carvalho Chehab 
83cb7a01acSMauro Carvalho Chehab 	if (!state->radio)
84cb7a01acSMauro Carvalho Chehab 		vp27smpx_set_audmode(sd, vt->audmode);
85cb7a01acSMauro Carvalho Chehab 	return 0;
86cb7a01acSMauro Carvalho Chehab }
87cb7a01acSMauro Carvalho Chehab 
vp27smpx_g_tuner(struct v4l2_subdev * sd,struct v4l2_tuner * vt)88cb7a01acSMauro Carvalho Chehab static int vp27smpx_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
89cb7a01acSMauro Carvalho Chehab {
90cb7a01acSMauro Carvalho Chehab 	struct vp27smpx_state *state = to_state(sd);
91cb7a01acSMauro Carvalho Chehab 
92cb7a01acSMauro Carvalho Chehab 	if (state->radio)
93cb7a01acSMauro Carvalho Chehab 		return 0;
94cb7a01acSMauro Carvalho Chehab 	vt->audmode = state->audmode;
95cb7a01acSMauro Carvalho Chehab 	vt->capability = V4L2_TUNER_CAP_STEREO |
96cb7a01acSMauro Carvalho Chehab 		V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2;
97cb7a01acSMauro Carvalho Chehab 	vt->rxsubchans = V4L2_TUNER_SUB_MONO;
98cb7a01acSMauro Carvalho Chehab 	return 0;
99cb7a01acSMauro Carvalho Chehab }
100cb7a01acSMauro Carvalho Chehab 
vp27smpx_log_status(struct v4l2_subdev * sd)101cb7a01acSMauro Carvalho Chehab static int vp27smpx_log_status(struct v4l2_subdev *sd)
102cb7a01acSMauro Carvalho Chehab {
103cb7a01acSMauro Carvalho Chehab 	struct vp27smpx_state *state = to_state(sd);
104cb7a01acSMauro Carvalho Chehab 
105cb7a01acSMauro Carvalho Chehab 	v4l2_info(sd, "Audio Mode: %u%s\n", state->audmode,
106cb7a01acSMauro Carvalho Chehab 			state->radio ? " (Radio)" : "");
107cb7a01acSMauro Carvalho Chehab 	return 0;
108cb7a01acSMauro Carvalho Chehab }
109cb7a01acSMauro Carvalho Chehab 
110cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------- */
111cb7a01acSMauro Carvalho Chehab 
112cb7a01acSMauro Carvalho Chehab static const struct v4l2_subdev_core_ops vp27smpx_core_ops = {
113cb7a01acSMauro Carvalho Chehab 	.log_status = vp27smpx_log_status,
114cb7a01acSMauro Carvalho Chehab };
115cb7a01acSMauro Carvalho Chehab 
116cb7a01acSMauro Carvalho Chehab static const struct v4l2_subdev_tuner_ops vp27smpx_tuner_ops = {
117cb7a01acSMauro Carvalho Chehab 	.s_radio = vp27smpx_s_radio,
118cb7a01acSMauro Carvalho Chehab 	.s_tuner = vp27smpx_s_tuner,
119cb7a01acSMauro Carvalho Chehab 	.g_tuner = vp27smpx_g_tuner,
120cb7a01acSMauro Carvalho Chehab };
121cb7a01acSMauro Carvalho Chehab 
1228774bed9SLaurent Pinchart static const struct v4l2_subdev_video_ops vp27smpx_video_ops = {
1238774bed9SLaurent Pinchart 	.s_std = vp27smpx_s_std,
1248774bed9SLaurent Pinchart };
1258774bed9SLaurent Pinchart 
126cb7a01acSMauro Carvalho Chehab static const struct v4l2_subdev_ops vp27smpx_ops = {
127cb7a01acSMauro Carvalho Chehab 	.core = &vp27smpx_core_ops,
128cb7a01acSMauro Carvalho Chehab 	.tuner = &vp27smpx_tuner_ops,
1298774bed9SLaurent Pinchart 	.video = &vp27smpx_video_ops,
130cb7a01acSMauro Carvalho Chehab };
131cb7a01acSMauro Carvalho Chehab 
132cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------- */
133cb7a01acSMauro Carvalho Chehab 
134cb7a01acSMauro Carvalho Chehab /* i2c implementation */
135cb7a01acSMauro Carvalho Chehab 
136cb7a01acSMauro Carvalho Chehab /*
137cb7a01acSMauro Carvalho Chehab  * Generic i2c probe
138cb7a01acSMauro Carvalho Chehab  * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
139cb7a01acSMauro Carvalho Chehab  */
140cb7a01acSMauro Carvalho Chehab 
vp27smpx_probe(struct i2c_client * client)141ce263690SUwe Kleine-König static int vp27smpx_probe(struct i2c_client *client)
142cb7a01acSMauro Carvalho Chehab {
143cb7a01acSMauro Carvalho Chehab 	struct vp27smpx_state *state;
144cb7a01acSMauro Carvalho Chehab 	struct v4l2_subdev *sd;
145cb7a01acSMauro Carvalho Chehab 
146cb7a01acSMauro Carvalho Chehab 	/* Check if the adapter supports the needed features */
147cb7a01acSMauro Carvalho Chehab 	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
148cb7a01acSMauro Carvalho Chehab 		return -EIO;
149cb7a01acSMauro Carvalho Chehab 
150cb7a01acSMauro Carvalho Chehab 	v4l_info(client, "chip found @ 0x%x (%s)\n",
151cb7a01acSMauro Carvalho Chehab 			client->addr << 1, client->adapter->name);
152cb7a01acSMauro Carvalho Chehab 
153c02b211dSLaurent Pinchart 	state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
154cb7a01acSMauro Carvalho Chehab 	if (state == NULL)
155cb7a01acSMauro Carvalho Chehab 		return -ENOMEM;
156cb7a01acSMauro Carvalho Chehab 	sd = &state->sd;
157cb7a01acSMauro Carvalho Chehab 	v4l2_i2c_subdev_init(sd, client, &vp27smpx_ops);
158cb7a01acSMauro Carvalho Chehab 	state->audmode = V4L2_TUNER_MODE_STEREO;
159cb7a01acSMauro Carvalho Chehab 
160cb7a01acSMauro Carvalho Chehab 	/* initialize vp27smpx */
161cb7a01acSMauro Carvalho Chehab 	vp27smpx_set_audmode(sd, state->audmode);
162cb7a01acSMauro Carvalho Chehab 	return 0;
163cb7a01acSMauro Carvalho Chehab }
164cb7a01acSMauro Carvalho Chehab 
vp27smpx_remove(struct i2c_client * client)165ed5c2f5fSUwe Kleine-König static void vp27smpx_remove(struct i2c_client *client)
166cb7a01acSMauro Carvalho Chehab {
167cb7a01acSMauro Carvalho Chehab 	struct v4l2_subdev *sd = i2c_get_clientdata(client);
168cb7a01acSMauro Carvalho Chehab 
169cb7a01acSMauro Carvalho Chehab 	v4l2_device_unregister_subdev(sd);
170cb7a01acSMauro Carvalho Chehab }
171cb7a01acSMauro Carvalho Chehab 
172cb7a01acSMauro Carvalho Chehab /* ----------------------------------------------------------------------- */
173cb7a01acSMauro Carvalho Chehab 
174cb7a01acSMauro Carvalho Chehab static const struct i2c_device_id vp27smpx_id[] = {
175*cc4cbd4bSUwe Kleine-König 	{ "vp27smpx" },
176cb7a01acSMauro Carvalho Chehab 	{ }
177cb7a01acSMauro Carvalho Chehab };
178cb7a01acSMauro Carvalho Chehab MODULE_DEVICE_TABLE(i2c, vp27smpx_id);
179cb7a01acSMauro Carvalho Chehab 
180cb7a01acSMauro Carvalho Chehab static struct i2c_driver vp27smpx_driver = {
181cb7a01acSMauro Carvalho Chehab 	.driver = {
182cb7a01acSMauro Carvalho Chehab 		.name	= "vp27smpx",
183cb7a01acSMauro Carvalho Chehab 	},
184aaeb31c0SUwe Kleine-König 	.probe		= vp27smpx_probe,
185cb7a01acSMauro Carvalho Chehab 	.remove		= vp27smpx_remove,
186cb7a01acSMauro Carvalho Chehab 	.id_table	= vp27smpx_id,
187cb7a01acSMauro Carvalho Chehab };
188cb7a01acSMauro Carvalho Chehab 
189cb7a01acSMauro Carvalho Chehab module_i2c_driver(vp27smpx_driver);
190