457 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * saa7110 - Philips SAA7110(A) video decoder driver4 *5 * Copyright (C) 1998 Pauline Middelink <middelin@polyware.nl>6 *7 * Copyright (C) 1999 Wolfgang Scherr <scherr@net4you.net>8 * Copyright (C) 2000 Serguei Miridonov <mirsev@cicese.mx>9 * - some corrections for Pinnacle Systems Inc. DC10plus card.10 *11 * Changes by Ronald Bultje <rbultje@ronald.bitfreak.net>12 * - moved over to linux>=2.4.x i2c protocol (1/1/2003)13 */14 15#include <linux/module.h>16#include <linux/init.h>17#include <linux/types.h>18#include <linux/delay.h>19#include <linux/slab.h>20#include <linux/wait.h>21#include <linux/uaccess.h>22#include <linux/i2c.h>23#include <linux/videodev2.h>24#include <media/v4l2-device.h>25#include <media/v4l2-ctrls.h>26 27MODULE_DESCRIPTION("Philips SAA7110 video decoder driver");28MODULE_AUTHOR("Pauline Middelink");29MODULE_LICENSE("GPL");30 31 32static int debug;33module_param(debug, int, 0);34MODULE_PARM_DESC(debug, "Debug level (0-1)");35 36#define SAA7110_MAX_INPUT 9 /* 6 CVBS, 3 SVHS */37#define SAA7110_MAX_OUTPUT 1 /* 1 YUV */38 39#define SAA7110_NR_REG 0x3540 41struct saa7110 {42 struct v4l2_subdev sd;43 struct v4l2_ctrl_handler hdl;44 u8 reg[SAA7110_NR_REG];45 46 v4l2_std_id norm;47 int input;48 int enable;49 50 wait_queue_head_t wq;51};52 53static inline struct saa7110 *to_saa7110(struct v4l2_subdev *sd)54{55 return container_of(sd, struct saa7110, sd);56}57 58static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl)59{60 return &container_of(ctrl->handler, struct saa7110, hdl)->sd;61}62 63/* ----------------------------------------------------------------------- */64/* I2C support functions */65/* ----------------------------------------------------------------------- */66 67static int saa7110_write(struct v4l2_subdev *sd, u8 reg, u8 value)68{69 struct i2c_client *client = v4l2_get_subdevdata(sd);70 struct saa7110 *decoder = to_saa7110(sd);71 72 decoder->reg[reg] = value;73 return i2c_smbus_write_byte_data(client, reg, value);74}75 76static int saa7110_write_block(struct v4l2_subdev *sd, const u8 *data, unsigned int len)77{78 struct i2c_client *client = v4l2_get_subdevdata(sd);79 struct saa7110 *decoder = to_saa7110(sd);80 int ret = -1;81 u8 reg = *data; /* first register to write to */82 83 /* Sanity check */84 if (reg + (len - 1) > SAA7110_NR_REG)85 return ret;86 87 /* the saa7110 has an autoincrement function, use it if88 * the adapter understands raw I2C */89 if (i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {90 ret = i2c_master_send(client, data, len);91 92 /* Cache the written data */93 memcpy(decoder->reg + reg, data + 1, len - 1);94 } else {95 for (++data, --len; len; len--) {96 ret = saa7110_write(sd, reg++, *data++);97 if (ret < 0)98 break;99 }100 }101 102 return ret;103}104 105static inline int saa7110_read(struct v4l2_subdev *sd)106{107 struct i2c_client *client = v4l2_get_subdevdata(sd);108 109 return i2c_smbus_read_byte(client);110}111 112/* ----------------------------------------------------------------------- */113/* SAA7110 functions */114/* ----------------------------------------------------------------------- */115 116#define FRESP_06H_COMPST 0x03 /*0x13*/117#define FRESP_06H_SVIDEO 0x83 /*0xC0*/118 119 120static int saa7110_selmux(struct v4l2_subdev *sd, int chan)121{122 static const unsigned char modes[9][8] = {123 /* mode 0 */124 {FRESP_06H_COMPST, 0xD9, 0x17, 0x40, 0x03,125 0x44, 0x75, 0x16},126 /* mode 1 */127 {FRESP_06H_COMPST, 0xD8, 0x17, 0x40, 0x03,128 0x44, 0x75, 0x16},129 /* mode 2 */130 {FRESP_06H_COMPST, 0xBA, 0x07, 0x91, 0x03,131 0x60, 0xB5, 0x05},132 /* mode 3 */133 {FRESP_06H_COMPST, 0xB8, 0x07, 0x91, 0x03,134 0x60, 0xB5, 0x05},135 /* mode 4 */136 {FRESP_06H_COMPST, 0x7C, 0x07, 0xD2, 0x83,137 0x60, 0xB5, 0x03},138 /* mode 5 */139 {FRESP_06H_COMPST, 0x78, 0x07, 0xD2, 0x83,140 0x60, 0xB5, 0x03},141 /* mode 6 */142 {FRESP_06H_SVIDEO, 0x59, 0x17, 0x42, 0xA3,143 0x44, 0x75, 0x12},144 /* mode 7 */145 {FRESP_06H_SVIDEO, 0x9A, 0x17, 0xB1, 0x13,146 0x60, 0xB5, 0x14},147 /* mode 8 */148 {FRESP_06H_SVIDEO, 0x3C, 0x27, 0xC1, 0x23,149 0x44, 0x75, 0x21}150 };151 struct saa7110 *decoder = to_saa7110(sd);152 const unsigned char *ptr = modes[chan];153 154 saa7110_write(sd, 0x06, ptr[0]); /* Luminance control */155 saa7110_write(sd, 0x20, ptr[1]); /* Analog Control #1 */156 saa7110_write(sd, 0x21, ptr[2]); /* Analog Control #2 */157 saa7110_write(sd, 0x22, ptr[3]); /* Mixer Control #1 */158 saa7110_write(sd, 0x2C, ptr[4]); /* Mixer Control #2 */159 saa7110_write(sd, 0x30, ptr[5]); /* ADCs gain control */160 saa7110_write(sd, 0x31, ptr[6]); /* Mixer Control #3 */161 saa7110_write(sd, 0x21, ptr[7]); /* Analog Control #2 */162 decoder->input = chan;163 164 return 0;165}166 167static const unsigned char initseq[1 + SAA7110_NR_REG] = {168 0, 0x4C, 0x3C, 0x0D, 0xEF, 0xBD, 0xF2, 0x03, 0x00,169 /* 0x08 */ 0xF8, 0xF8, 0x60, 0x60, 0x00, 0x86, 0x18, 0x90,170 /* 0x10 */ 0x00, 0x59, 0x40, 0x46, 0x42, 0x1A, 0xFF, 0xDA,171 /* 0x18 */ 0xF2, 0x8B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,172 /* 0x20 */ 0xD9, 0x16, 0x40, 0x41, 0x80, 0x41, 0x80, 0x4F,173 /* 0x28 */ 0xFE, 0x01, 0xCF, 0x0F, 0x03, 0x01, 0x03, 0x0C,174 /* 0x30 */ 0x44, 0x71, 0x02, 0x8C, 0x02175};176 177static v4l2_std_id determine_norm(struct v4l2_subdev *sd)178{179 DEFINE_WAIT(wait);180 struct saa7110 *decoder = to_saa7110(sd);181 int status;182 183 /* mode changed, start automatic detection */184 saa7110_write_block(sd, initseq, sizeof(initseq));185 saa7110_selmux(sd, decoder->input);186 prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE);187 schedule_timeout(msecs_to_jiffies(250));188 finish_wait(&decoder->wq, &wait);189 status = saa7110_read(sd);190 if (status & 0x40) {191 v4l2_dbg(1, debug, sd, "status=0x%02x (no signal)\n", status);192 return V4L2_STD_UNKNOWN;193 }194 if ((status & 3) == 0) {195 saa7110_write(sd, 0x06, 0x83);196 if (status & 0x20) {197 v4l2_dbg(1, debug, sd, "status=0x%02x (NTSC/no color)\n", status);198 /*saa7110_write(sd,0x2E,0x81);*/199 return V4L2_STD_NTSC;200 }201 v4l2_dbg(1, debug, sd, "status=0x%02x (PAL/no color)\n", status);202 /*saa7110_write(sd,0x2E,0x9A);*/203 return V4L2_STD_PAL;204 }205 /*saa7110_write(sd,0x06,0x03);*/206 if (status & 0x20) { /* 60Hz */207 v4l2_dbg(1, debug, sd, "status=0x%02x (NTSC)\n", status);208 saa7110_write(sd, 0x0D, 0x86);209 saa7110_write(sd, 0x0F, 0x50);210 saa7110_write(sd, 0x11, 0x2C);211 /*saa7110_write(sd,0x2E,0x81);*/212 return V4L2_STD_NTSC;213 }214 215 /* 50Hz -> PAL/SECAM */216 saa7110_write(sd, 0x0D, 0x86);217 saa7110_write(sd, 0x0F, 0x10);218 saa7110_write(sd, 0x11, 0x59);219 /*saa7110_write(sd,0x2E,0x9A);*/220 221 prepare_to_wait(&decoder->wq, &wait, TASK_UNINTERRUPTIBLE);222 schedule_timeout(msecs_to_jiffies(250));223 finish_wait(&decoder->wq, &wait);224 225 status = saa7110_read(sd);226 if ((status & 0x03) == 0x01) {227 v4l2_dbg(1, debug, sd, "status=0x%02x (SECAM)\n", status);228 saa7110_write(sd, 0x0D, 0x87);229 return V4L2_STD_SECAM;230 }231 v4l2_dbg(1, debug, sd, "status=0x%02x (PAL)\n", status);232 return V4L2_STD_PAL;233}234 235static int saa7110_g_input_status(struct v4l2_subdev *sd, u32 *pstatus)236{237 struct saa7110 *decoder = to_saa7110(sd);238 int res = V4L2_IN_ST_NO_SIGNAL;239 int status = saa7110_read(sd);240 241 v4l2_dbg(1, debug, sd, "status=0x%02x norm=%llx\n",242 status, (unsigned long long)decoder->norm);243 if (!(status & 0x40))244 res = 0;245 if (!(status & 0x03))246 res |= V4L2_IN_ST_NO_COLOR;247 248 *pstatus = res;249 return 0;250}251 252static int saa7110_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)253{254 *std &= determine_norm(sd);255 return 0;256}257 258static int saa7110_s_std(struct v4l2_subdev *sd, v4l2_std_id std)259{260 struct saa7110 *decoder = to_saa7110(sd);261 262 if (decoder->norm != std) {263 decoder->norm = std;264 /*saa7110_write(sd, 0x06, 0x03);*/265 if (std & V4L2_STD_NTSC) {266 saa7110_write(sd, 0x0D, 0x86);267 saa7110_write(sd, 0x0F, 0x50);268 saa7110_write(sd, 0x11, 0x2C);269 /*saa7110_write(sd, 0x2E, 0x81);*/270 v4l2_dbg(1, debug, sd, "switched to NTSC\n");271 } else if (std & V4L2_STD_PAL) {272 saa7110_write(sd, 0x0D, 0x86);273 saa7110_write(sd, 0x0F, 0x10);274 saa7110_write(sd, 0x11, 0x59);275 /*saa7110_write(sd, 0x2E, 0x9A);*/276 v4l2_dbg(1, debug, sd, "switched to PAL\n");277 } else if (std & V4L2_STD_SECAM) {278 saa7110_write(sd, 0x0D, 0x87);279 saa7110_write(sd, 0x0F, 0x10);280 saa7110_write(sd, 0x11, 0x59);281 /*saa7110_write(sd, 0x2E, 0x9A);*/282 v4l2_dbg(1, debug, sd, "switched to SECAM\n");283 } else {284 return -EINVAL;285 }286 }287 return 0;288}289 290static int saa7110_s_routing(struct v4l2_subdev *sd,291 u32 input, u32 output, u32 config)292{293 struct saa7110 *decoder = to_saa7110(sd);294 295 if (input >= SAA7110_MAX_INPUT) {296 v4l2_dbg(1, debug, sd, "input=%d not available\n", input);297 return -EINVAL;298 }299 if (decoder->input != input) {300 saa7110_selmux(sd, input);301 v4l2_dbg(1, debug, sd, "switched to input=%d\n", input);302 }303 return 0;304}305 306static int saa7110_s_stream(struct v4l2_subdev *sd, int enable)307{308 struct saa7110 *decoder = to_saa7110(sd);309 310 if (decoder->enable != enable) {311 decoder->enable = enable;312 saa7110_write(sd, 0x0E, enable ? 0x18 : 0x80);313 v4l2_dbg(1, debug, sd, "YUV %s\n", enable ? "on" : "off");314 }315 return 0;316}317 318static int saa7110_s_ctrl(struct v4l2_ctrl *ctrl)319{320 struct v4l2_subdev *sd = to_sd(ctrl);321 322 switch (ctrl->id) {323 case V4L2_CID_BRIGHTNESS:324 saa7110_write(sd, 0x19, ctrl->val);325 break;326 case V4L2_CID_CONTRAST:327 saa7110_write(sd, 0x13, ctrl->val);328 break;329 case V4L2_CID_SATURATION:330 saa7110_write(sd, 0x12, ctrl->val);331 break;332 case V4L2_CID_HUE:333 saa7110_write(sd, 0x07, ctrl->val);334 break;335 default:336 return -EINVAL;337 }338 return 0;339}340 341/* ----------------------------------------------------------------------- */342 343static const struct v4l2_ctrl_ops saa7110_ctrl_ops = {344 .s_ctrl = saa7110_s_ctrl,345};346 347static const struct v4l2_subdev_video_ops saa7110_video_ops = {348 .s_std = saa7110_s_std,349 .s_routing = saa7110_s_routing,350 .s_stream = saa7110_s_stream,351 .querystd = saa7110_querystd,352 .g_input_status = saa7110_g_input_status,353};354 355static const struct v4l2_subdev_ops saa7110_ops = {356 .video = &saa7110_video_ops,357};358 359/* ----------------------------------------------------------------------- */360 361static int saa7110_probe(struct i2c_client *client)362{363 struct saa7110 *decoder;364 struct v4l2_subdev *sd;365 int rv;366 367 /* Check if the adapter supports the needed features */368 if (!i2c_check_functionality(client->adapter,369 I2C_FUNC_SMBUS_READ_BYTE | I2C_FUNC_SMBUS_WRITE_BYTE_DATA))370 return -ENODEV;371 372 v4l_info(client, "chip found @ 0x%x (%s)\n",373 client->addr << 1, client->adapter->name);374 375 decoder = devm_kzalloc(&client->dev, sizeof(*decoder), GFP_KERNEL);376 if (!decoder)377 return -ENOMEM;378 sd = &decoder->sd;379 v4l2_i2c_subdev_init(sd, client, &saa7110_ops);380 decoder->norm = V4L2_STD_PAL;381 decoder->input = 0;382 decoder->enable = 1;383 v4l2_ctrl_handler_init(&decoder->hdl, 2);384 v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops,385 V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);386 v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops,387 V4L2_CID_CONTRAST, 0, 127, 1, 64);388 v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops,389 V4L2_CID_SATURATION, 0, 127, 1, 64);390 v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops,391 V4L2_CID_HUE, -128, 127, 1, 0);392 sd->ctrl_handler = &decoder->hdl;393 if (decoder->hdl.error) {394 int err = decoder->hdl.error;395 396 v4l2_ctrl_handler_free(&decoder->hdl);397 return err;398 }399 v4l2_ctrl_handler_setup(&decoder->hdl);400 401 init_waitqueue_head(&decoder->wq);402 403 rv = saa7110_write_block(sd, initseq, sizeof(initseq));404 if (rv < 0) {405 v4l2_dbg(1, debug, sd, "init status %d\n", rv);406 } else {407 int ver, status;408 saa7110_write(sd, 0x21, 0x10);409 saa7110_write(sd, 0x0e, 0x18);410 saa7110_write(sd, 0x0D, 0x04);411 ver = saa7110_read(sd);412 saa7110_write(sd, 0x0D, 0x06);413 /*mdelay(150);*/414 status = saa7110_read(sd);415 v4l2_dbg(1, debug, sd, "version %x, status=0x%02x\n",416 ver, status);417 saa7110_write(sd, 0x0D, 0x86);418 saa7110_write(sd, 0x0F, 0x10);419 saa7110_write(sd, 0x11, 0x59);420 /*saa7110_write(sd, 0x2E, 0x9A);*/421 }422 423 /*saa7110_selmux(sd,0);*/424 /*determine_norm(sd);*/425 /* setup and implicit mode 0 select has been performed */426 427 return 0;428}429 430static void saa7110_remove(struct i2c_client *client)431{432 struct v4l2_subdev *sd = i2c_get_clientdata(client);433 struct saa7110 *decoder = to_saa7110(sd);434 435 v4l2_device_unregister_subdev(sd);436 v4l2_ctrl_handler_free(&decoder->hdl);437}438 439/* ----------------------------------------------------------------------- */440 441static const struct i2c_device_id saa7110_id[] = {442 { "saa7110" },443 { }444};445MODULE_DEVICE_TABLE(i2c, saa7110_id);446 447static struct i2c_driver saa7110_driver = {448 .driver = {449 .name = "saa7110",450 },451 .probe = saa7110_probe,452 .remove = saa7110_remove,453 .id_table = saa7110_id,454};455 456module_i2c_driver(saa7110_driver);457