diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2013-03-17 08:26:40 -0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2013-03-24 12:06:39 -0400 |
commit | a000e9a02b5885b1b69f691c80e346d102f94a88 (patch) | |
tree | 440abffa89736696ff004cec2a3a45e1089f2e91 | |
parent | 0a6ecbb40ebe372028088b3e6b71a3adf9b1b674 (diff) |
[media] tw9906: add Techwell tw9906 video decoder
Taken from the 0.9.8-5 version of the out-of-tree wis-go7007 driver.
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
-rw-r--r-- | drivers/media/i2c/Kconfig | 10 | ||||
-rw-r--r-- | drivers/media/i2c/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/i2c/tw9906.c | 241 |
3 files changed, 252 insertions, 0 deletions
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 07d9c11e91aa..9e7ce8bff4e9 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig | |||
@@ -320,6 +320,16 @@ config VIDEO_TW9903 | |||
320 | To compile this driver as a module, choose M here: the | 320 | To compile this driver as a module, choose M here: the |
321 | module will be called tw9903. | 321 | module will be called tw9903. |
322 | 322 | ||
323 | config VIDEO_TW9906 | ||
324 | tristate "Techwell TW9906 video decoder" | ||
325 | depends on VIDEO_V4L2 && I2C | ||
326 | ---help--- | ||
327 | Support for the Techwell tw9906 enhanced multi-standard comb filter | ||
328 | video decoder with YCbCr input support. | ||
329 | |||
330 | To compile this driver as a module, choose M here: the | ||
331 | module will be called tw9906. | ||
332 | |||
323 | config VIDEO_VPX3220 | 333 | config VIDEO_VPX3220 |
324 | tristate "vpx3220a, vpx3216b & vpx3214c video decoders" | 334 | tristate "vpx3220a, vpx3216b & vpx3214c video decoders" |
325 | depends on VIDEO_V4L2 && I2C | 335 | depends on VIDEO_V4L2 && I2C |
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile index 399050a99b19..720f42d9d9f4 100644 --- a/drivers/media/i2c/Makefile +++ b/drivers/media/i2c/Makefile | |||
@@ -39,6 +39,7 @@ obj-$(CONFIG_VIDEO_TVP514X) += tvp514x.o | |||
39 | obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o | 39 | obj-$(CONFIG_VIDEO_TVP7002) += tvp7002.o |
40 | obj-$(CONFIG_VIDEO_TW2804) += tw2804.o | 40 | obj-$(CONFIG_VIDEO_TW2804) += tw2804.o |
41 | obj-$(CONFIG_VIDEO_TW9903) += tw9903.o | 41 | obj-$(CONFIG_VIDEO_TW9903) += tw9903.o |
42 | obj-$(CONFIG_VIDEO_TW9906) += tw9906.o | ||
42 | obj-$(CONFIG_VIDEO_CS5345) += cs5345.o | 43 | obj-$(CONFIG_VIDEO_CS5345) += cs5345.o |
43 | obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o | 44 | obj-$(CONFIG_VIDEO_CS53L32A) += cs53l32a.o |
44 | obj-$(CONFIG_VIDEO_M52790) += m52790.o | 45 | obj-$(CONFIG_VIDEO_M52790) += m52790.o |
diff --git a/drivers/media/i2c/tw9906.c b/drivers/media/i2c/tw9906.c new file mode 100644 index 000000000000..2263a914cd08 --- /dev/null +++ b/drivers/media/i2c/tw9906.c | |||
@@ -0,0 +1,241 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2005-2006 Micronas USA Inc. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or modify | ||
5 | * it under the terms of the GNU General Public License (Version 2) as | ||
6 | * published by the Free Software Foundation. | ||
7 | * | ||
8 | * This program is distributed in the hope that it will be useful, | ||
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
11 | * GNU General Public License for more details. | ||
12 | * | ||
13 | * You should have received a copy of the GNU General Public License | ||
14 | * along with this program; if not, write to the Free Software Foundation, | ||
15 | * Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. | ||
16 | */ | ||
17 | |||
18 | #include <linux/module.h> | ||
19 | #include <linux/init.h> | ||
20 | #include <linux/version.h> | ||
21 | #include <linux/i2c.h> | ||
22 | #include <linux/videodev2.h> | ||
23 | #include <linux/ioctl.h> | ||
24 | #include <linux/slab.h> | ||
25 | #include <media/v4l2-device.h> | ||
26 | #include <media/v4l2-ctrls.h> | ||
27 | |||
28 | MODULE_DESCRIPTION("TW9906 I2C subdev driver"); | ||
29 | MODULE_LICENSE("GPL v2"); | ||
30 | |||
31 | struct tw9906 { | ||
32 | struct v4l2_subdev sd; | ||
33 | struct v4l2_ctrl_handler hdl; | ||
34 | v4l2_std_id norm; | ||
35 | }; | ||
36 | |||
37 | static inline struct tw9906 *to_state(struct v4l2_subdev *sd) | ||
38 | { | ||
39 | return container_of(sd, struct tw9906, sd); | ||
40 | } | ||
41 | |||
42 | static const u8 initial_registers[] = { | ||
43 | 0x02, 0x40, /* input 0, composite */ | ||
44 | 0x03, 0xa2, /* correct digital format */ | ||
45 | 0x05, 0x81, /* or 0x01 for PAL */ | ||
46 | 0x07, 0x02, /* window */ | ||
47 | 0x08, 0x14, /* window */ | ||
48 | 0x09, 0xf0, /* window */ | ||
49 | 0x0a, 0x10, /* window */ | ||
50 | 0x0b, 0xd0, /* window */ | ||
51 | 0x0d, 0x00, /* scaling */ | ||
52 | 0x0e, 0x11, /* scaling */ | ||
53 | 0x0f, 0x00, /* scaling */ | ||
54 | 0x10, 0x00, /* brightness */ | ||
55 | 0x11, 0x60, /* contrast */ | ||
56 | 0x12, 0x11, /* sharpness */ | ||
57 | 0x13, 0x7e, /* U gain */ | ||
58 | 0x14, 0x7e, /* V gain */ | ||
59 | 0x15, 0x00, /* hue */ | ||
60 | 0x19, 0x57, /* vbi */ | ||
61 | 0x1a, 0x0f, | ||
62 | 0x1b, 0x40, | ||
63 | 0x29, 0x03, | ||
64 | 0x55, 0x00, | ||
65 | 0x6b, 0x26, | ||
66 | 0x6c, 0x36, | ||
67 | 0x6d, 0xf0, | ||
68 | 0x6e, 0x41, | ||
69 | 0x6f, 0x13, | ||
70 | 0xad, 0x70, | ||
71 | 0x00, 0x00, /* Terminator (reg 0x00 is read-only) */ | ||
72 | }; | ||
73 | |||
74 | static int write_reg(struct v4l2_subdev *sd, u8 reg, u8 value) | ||
75 | { | ||
76 | struct i2c_client *client = v4l2_get_subdevdata(sd); | ||
77 | |||
78 | return i2c_smbus_write_byte_data(client, reg, value); | ||
79 | } | ||
80 | |||
81 | static int write_regs(struct v4l2_subdev *sd, const u8 *regs) | ||
82 | { | ||
83 | int i; | ||
84 | |||
85 | for (i = 0; regs[i] != 0x00; i += 2) | ||
86 | if (write_reg(sd, regs[i], regs[i + 1]) < 0) | ||
87 | return -1; | ||
88 | return 0; | ||
89 | } | ||
90 | |||
91 | static int tw9906_s_video_routing(struct v4l2_subdev *sd, u32 input, | ||
92 | u32 output, u32 config) | ||
93 | { | ||
94 | write_reg(sd, 0x02, 0x40 | (input << 1)); | ||
95 | return 0; | ||
96 | } | ||
97 | |||
98 | static int tw9906_s_std(struct v4l2_subdev *sd, v4l2_std_id norm) | ||
99 | { | ||
100 | struct tw9906 *dec = to_state(sd); | ||
101 | bool is_60hz = norm & V4L2_STD_525_60; | ||
102 | u8 regs[] = { | ||
103 | 0x05, is_60hz ? 0x81 : 0x01, | ||
104 | 0x07, is_60hz ? 0x02 : 0x12, | ||
105 | 0x08, is_60hz ? 0x14 : 0x18, | ||
106 | 0x09, is_60hz ? 0xf0 : 0x20, | ||
107 | 0, 0, | ||
108 | }; | ||
109 | |||
110 | write_regs(sd, regs); | ||
111 | dec->norm = norm; | ||
112 | return 0; | ||
113 | } | ||
114 | |||
115 | static int tw9906_s_ctrl(struct v4l2_ctrl *ctrl) | ||
116 | { | ||
117 | struct tw9906 *dec = container_of(ctrl->handler, struct tw9906, hdl); | ||
118 | struct v4l2_subdev *sd = &dec->sd; | ||
119 | |||
120 | switch (ctrl->id) { | ||
121 | case V4L2_CID_BRIGHTNESS: | ||
122 | write_reg(sd, 0x10, ctrl->val); | ||
123 | break; | ||
124 | case V4L2_CID_CONTRAST: | ||
125 | write_reg(sd, 0x11, ctrl->val); | ||
126 | break; | ||
127 | case V4L2_CID_HUE: | ||
128 | write_reg(sd, 0x15, ctrl->val); | ||
129 | break; | ||
130 | default: | ||
131 | return -EINVAL; | ||
132 | } | ||
133 | return 0; | ||
134 | } | ||
135 | |||
136 | static int tw9906_log_status(struct v4l2_subdev *sd) | ||
137 | { | ||
138 | struct tw9906 *dec = to_state(sd); | ||
139 | bool is_60hz = dec->norm & V4L2_STD_525_60; | ||
140 | |||
141 | v4l2_info(sd, "Standard: %d Hz\n", is_60hz ? 60 : 50); | ||
142 | v4l2_ctrl_subdev_log_status(sd); | ||
143 | return 0; | ||
144 | } | ||
145 | |||
146 | /* --------------------------------------------------------------------------*/ | ||
147 | |||
148 | static const struct v4l2_ctrl_ops tw9906_ctrl_ops = { | ||
149 | .s_ctrl = tw9906_s_ctrl, | ||
150 | }; | ||
151 | |||
152 | static const struct v4l2_subdev_core_ops tw9906_core_ops = { | ||
153 | .log_status = tw9906_log_status, | ||
154 | .s_std = tw9906_s_std, | ||
155 | }; | ||
156 | |||
157 | static const struct v4l2_subdev_video_ops tw9906_video_ops = { | ||
158 | .s_routing = tw9906_s_video_routing, | ||
159 | }; | ||
160 | |||
161 | static const struct v4l2_subdev_ops tw9906_ops = { | ||
162 | .core = &tw9906_core_ops, | ||
163 | .video = &tw9906_video_ops, | ||
164 | }; | ||
165 | |||
166 | static int tw9906_probe(struct i2c_client *client, | ||
167 | const struct i2c_device_id *id) | ||
168 | { | ||
169 | struct tw9906 *dec; | ||
170 | struct v4l2_subdev *sd; | ||
171 | struct v4l2_ctrl_handler *hdl; | ||
172 | |||
173 | /* Check if the adapter supports the needed features */ | ||
174 | if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) | ||
175 | return -EIO; | ||
176 | |||
177 | v4l_info(client, "chip found @ 0x%02x (%s)\n", | ||
178 | client->addr << 1, client->adapter->name); | ||
179 | |||
180 | dec = kzalloc(sizeof(struct tw9906), GFP_KERNEL); | ||
181 | if (dec == NULL) | ||
182 | return -ENOMEM; | ||
183 | sd = &dec->sd; | ||
184 | v4l2_i2c_subdev_init(sd, client, &tw9906_ops); | ||
185 | hdl = &dec->hdl; | ||
186 | v4l2_ctrl_handler_init(hdl, 4); | ||
187 | v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops, | ||
188 | V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); | ||
189 | v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops, | ||
190 | V4L2_CID_CONTRAST, 0, 255, 1, 0x60); | ||
191 | v4l2_ctrl_new_std(hdl, &tw9906_ctrl_ops, | ||
192 | V4L2_CID_HUE, -128, 127, 1, 0); | ||
193 | sd->ctrl_handler = hdl; | ||
194 | if (hdl->error) { | ||
195 | int err = hdl->error; | ||
196 | |||
197 | v4l2_ctrl_handler_free(hdl); | ||
198 | kfree(dec); | ||
199 | return err; | ||
200 | } | ||
201 | |||
202 | /* Initialize tw9906 */ | ||
203 | dec->norm = V4L2_STD_NTSC; | ||
204 | |||
205 | if (write_regs(sd, initial_registers) < 0) { | ||
206 | v4l2_err(client, "error initializing TW9906\n"); | ||
207 | kfree(dec); | ||
208 | return -EINVAL; | ||
209 | } | ||
210 | |||
211 | return 0; | ||
212 | } | ||
213 | |||
214 | static int tw9906_remove(struct i2c_client *client) | ||
215 | { | ||
216 | struct v4l2_subdev *sd = i2c_get_clientdata(client); | ||
217 | |||
218 | v4l2_device_unregister_subdev(sd); | ||
219 | v4l2_ctrl_handler_free(&to_state(sd)->hdl); | ||
220 | kfree(to_state(sd)); | ||
221 | return 0; | ||
222 | } | ||
223 | |||
224 | /* ----------------------------------------------------------------------- */ | ||
225 | |||
226 | static const struct i2c_device_id tw9906_id[] = { | ||
227 | { "tw9906", 0 }, | ||
228 | { } | ||
229 | }; | ||
230 | MODULE_DEVICE_TABLE(i2c, tw9906_id); | ||
231 | |||
232 | static struct i2c_driver tw9906_driver = { | ||
233 | .driver = { | ||
234 | .owner = THIS_MODULE, | ||
235 | .name = "tw9906", | ||
236 | }, | ||
237 | .probe = tw9906_probe, | ||
238 | .remove = tw9906_remove, | ||
239 | .id_table = tw9906_id, | ||
240 | }; | ||
241 | module_i2c_driver(tw9906_driver); | ||