418 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * saa7706.c Philips SAA7706H Car Radio DSP driver4 * Copyright (c) 2009 Intel Corporation5 */6 7#include <linux/module.h>8#include <linux/init.h>9#include <linux/delay.h>10#include <linux/errno.h>11#include <linux/kernel.h>12#include <linux/interrupt.h>13#include <linux/i2c.h>14#include <linux/slab.h>15#include <media/v4l2-device.h>16#include <media/v4l2-ctrls.h>17 18#define DRIVER_NAME "saa7706h"19 20/* the I2C memory map looks like this21 22 $1C00 - $FFFF Not Used23 $2200 - $3FFF Reserved YRAM (DSP2) space24 $2000 - $21FF YRAM (DSP2)25 $1FF0 - $1FFF Hardware Registers26 $1280 - $1FEF Reserved XRAM (DSP2) space27 $1000 - $127F XRAM (DSP2)28 $0FFF DSP CONTROL29 $0A00 - $0FFE Reserved30 $0980 - $09FF Reserved YRAM (DSP1) space31 $0800 - $097F YRAM (DSP1)32 $0200 - $07FF Not Used33 $0180 - $01FF Reserved XRAM (DSP1) space34 $0000 - $017F XRAM (DSP1)35*/36 37#define SAA7706H_REG_CTRL 0x0fff38#define SAA7706H_CTRL_BYP_PLL 0x000139#define SAA7706H_CTRL_PLL_DIV_MASK 0x003e40#define SAA7706H_CTRL_PLL3_62975MHZ 0x003e41#define SAA7706H_CTRL_DSP_TURBO 0x004042#define SAA7706H_CTRL_PC_RESET_DSP1 0x008043#define SAA7706H_CTRL_PC_RESET_DSP2 0x010044#define SAA7706H_CTRL_DSP1_ROM_EN_MASK 0x060045#define SAA7706H_CTRL_DSP1_FUNC_PROM 0x000046#define SAA7706H_CTRL_DSP2_ROM_EN_MASK 0x180047#define SAA7706H_CTRL_DSP2_FUNC_PROM 0x000048#define SAA7706H_CTRL_DIG_SIL_INTERPOL 0x800049 50#define SAA7706H_REG_EVALUATION 0x1ff051#define SAA7706H_EVAL_DISABLE_CHARGE_PUMP 0x00000152#define SAA7706H_EVAL_DCS_CLOCK 0x00000253#define SAA7706H_EVAL_GNDRC1_ENABLE 0x00000454#define SAA7706H_EVAL_GNDRC2_ENABLE 0x00000855 56#define SAA7706H_REG_CL_GEN1 0x1ff357#define SAA7706H_CL_GEN1_MIN_LOOPGAIN_MASK 0x00000f58#define SAA7706H_CL_GEN1_LOOPGAIN_MASK 0x0000f059#define SAA7706H_CL_GEN1_COARSE_RATION 0xffff0060 61#define SAA7706H_REG_CL_GEN2 0x1ff462#define SAA7706H_CL_GEN2_WSEDGE_FALLING 0x00000163#define SAA7706H_CL_GEN2_STOP_VCO 0x00000264#define SAA7706H_CL_GEN2_FRERUN 0x00000465#define SAA7706H_CL_GEN2_ADAPTIVE 0x00000866#define SAA7706H_CL_GEN2_FINE_RATIO_MASK 0x0ffff067 68#define SAA7706H_REG_CL_GEN4 0x1ff669#define SAA7706H_CL_GEN4_BYPASS_PLL1 0x00100070#define SAA7706H_CL_GEN4_PLL1_DIV_MASK 0x03e00071#define SAA7706H_CL_GEN4_DSP1_TURBO 0x04000072 73#define SAA7706H_REG_SEL 0x1ff774#define SAA7706H_SEL_DSP2_SRCA_MASK 0x00000775#define SAA7706H_SEL_DSP2_FMTA_MASK 0x00003176#define SAA7706H_SEL_DSP2_SRCB_MASK 0x0001c077#define SAA7706H_SEL_DSP2_FMTB_MASK 0x000e0078#define SAA7706H_SEL_DSP1_SRC_MASK 0x00300079#define SAA7706H_SEL_DSP1_FMT_MASK 0x01c00380#define SAA7706H_SEL_SPDIF2 0x02000081#define SAA7706H_SEL_HOST_IO_FMT_MASK 0x1c000082#define SAA7706H_SEL_EN_HOST_IO 0x20000083 84#define SAA7706H_REG_IAC 0x1ff885#define SAA7706H_REG_CLK_SET 0x1ff986#define SAA7706H_REG_CLK_COEFF 0x1ffa87#define SAA7706H_REG_INPUT_SENS 0x1ffb88#define SAA7706H_INPUT_SENS_RDS_VOL_MASK 0x0003f89#define SAA7706H_INPUT_SENS_FM_VOL_MASK 0x00fc090#define SAA7706H_INPUT_SENS_FM_MPX 0x0100091#define SAA7706H_INPUT_SENS_OFF_FILTER_A_EN 0x0200092#define SAA7706H_INPUT_SENS_OFF_FILTER_B_EN 0x0400093#define SAA7706H_REG_PHONE_NAV_AUDIO 0x1ffc94#define SAA7706H_REG_IO_CONF_DSP2 0x1ffd95#define SAA7706H_REG_STATUS_DSP2 0x1ffe96#define SAA7706H_REG_PC_DSP2 0x1fff97 98#define SAA7706H_DSP1_MOD0 0x080099#define SAA7706H_DSP1_ROM_VER 0x097f100#define SAA7706H_DSP2_MPTR0 0x1000101 102#define SAA7706H_DSP1_MODPNTR 0x0000103 104#define SAA7706H_DSP2_XMEM_CONTLLCW 0x113e105#define SAA7706H_DSP2_XMEM_BUSAMP 0x114a106#define SAA7706H_DSP2_XMEM_FDACPNTR 0x11f9107#define SAA7706H_DSP2_XMEM_IIS1PNTR 0x11fb108 109#define SAA7706H_DSP2_YMEM_PVGA 0x212a110#define SAA7706H_DSP2_YMEM_PVAT1 0x212b111#define SAA7706H_DSP2_YMEM_PVAT 0x212c112#define SAA7706H_DSP2_YMEM_ROM_VER 0x21ff113 114#define SUPPORTED_DSP1_ROM_VER 0x667115 116struct saa7706h_state {117 struct v4l2_subdev sd;118 struct v4l2_ctrl_handler hdl;119 unsigned muted;120};121 122static inline struct saa7706h_state *to_state(struct v4l2_subdev *sd)123{124 return container_of(sd, struct saa7706h_state, sd);125}126 127static int saa7706h_i2c_send(struct i2c_client *client, const u8 *data, int len)128{129 int err = i2c_master_send(client, data, len);130 if (err == len)131 return 0;132 return err > 0 ? -EIO : err;133}134 135static int saa7706h_i2c_transfer(struct i2c_client *client,136 struct i2c_msg *msgs, int num)137{138 int err = i2c_transfer(client->adapter, msgs, num);139 if (err == num)140 return 0;141 return err > 0 ? -EIO : err;142}143 144static int saa7706h_set_reg24(struct v4l2_subdev *sd, u16 reg, u32 val)145{146 struct i2c_client *client = v4l2_get_subdevdata(sd);147 u8 buf[5];148 int pos = 0;149 150 buf[pos++] = reg >> 8;151 buf[pos++] = reg;152 buf[pos++] = val >> 16;153 buf[pos++] = val >> 8;154 buf[pos++] = val;155 156 return saa7706h_i2c_send(client, buf, pos);157}158 159static int saa7706h_set_reg24_err(struct v4l2_subdev *sd, u16 reg, u32 val,160 int *err)161{162 return *err ? *err : saa7706h_set_reg24(sd, reg, val);163}164 165static int saa7706h_set_reg16(struct v4l2_subdev *sd, u16 reg, u16 val)166{167 struct i2c_client *client = v4l2_get_subdevdata(sd);168 u8 buf[4];169 int pos = 0;170 171 buf[pos++] = reg >> 8;172 buf[pos++] = reg;173 buf[pos++] = val >> 8;174 buf[pos++] = val;175 176 return saa7706h_i2c_send(client, buf, pos);177}178 179static int saa7706h_set_reg16_err(struct v4l2_subdev *sd, u16 reg, u16 val,180 int *err)181{182 return *err ? *err : saa7706h_set_reg16(sd, reg, val);183}184 185static int saa7706h_get_reg16(struct v4l2_subdev *sd, u16 reg)186{187 struct i2c_client *client = v4l2_get_subdevdata(sd);188 u8 buf[2];189 int err;190 u8 regaddr[] = {reg >> 8, reg};191 struct i2c_msg msg[] = {192 {193 .addr = client->addr,194 .len = sizeof(regaddr),195 .buf = regaddr196 },197 {198 .addr = client->addr,199 .flags = I2C_M_RD,200 .len = sizeof(buf),201 .buf = buf202 }203 };204 205 err = saa7706h_i2c_transfer(client, msg, ARRAY_SIZE(msg));206 if (err)207 return err;208 209 return buf[0] << 8 | buf[1];210}211 212static int saa7706h_unmute(struct v4l2_subdev *sd)213{214 struct saa7706h_state *state = to_state(sd);215 int err = 0;216 217 err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,218 SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |219 SAA7706H_CTRL_PC_RESET_DSP2, &err);220 221 /* newer versions of the chip requires a small sleep after reset */222 msleep(1);223 224 err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,225 SAA7706H_CTRL_PLL3_62975MHZ, &err);226 227 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_EVALUATION, 0, &err);228 229 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN1, 0x040022, &err);230 231 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN2,232 SAA7706H_CL_GEN2_WSEDGE_FALLING, &err);233 234 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN4, 0x024080, &err);235 236 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_SEL, 0x200080, &err);237 238 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IAC, 0xf4caed, &err);239 240 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_SET, 0x124334, &err);241 242 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_COEFF, 0x004a1a,243 &err);244 245 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_INPUT_SENS, 0x0071c7,246 &err);247 248 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PHONE_NAV_AUDIO,249 0x0e22ff, &err);250 251 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IO_CONF_DSP2, 0x001ff8,252 &err);253 254 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_STATUS_DSP2, 0x080003,255 &err);256 257 err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PC_DSP2, 0x000004, &err);258 259 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP1_MOD0, 0x0c6c, &err);260 261 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_MPTR0, 0x000b4b, &err);262 263 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x000600, &err);264 265 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x0000c0, &err);266 267 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000819,268 &err);269 270 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x00085a,271 &err);272 273 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_BUSAMP, 0x7fffff,274 &err);275 276 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_FDACPNTR, 0x2000cb,277 &err);278 279 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_IIS1PNTR, 0x2000cb,280 &err);281 282 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVGA, 0x0f80, &err);283 284 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT1, 0x0800,285 &err);286 287 err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT, 0x0800, &err);288 289 err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000905,290 &err);291 if (!err)292 state->muted = 0;293 return err;294}295 296static int saa7706h_mute(struct v4l2_subdev *sd)297{298 struct saa7706h_state *state = to_state(sd);299 int err;300 301 err = saa7706h_set_reg16(sd, SAA7706H_REG_CTRL,302 SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |303 SAA7706H_CTRL_PC_RESET_DSP2);304 if (!err)305 state->muted = 1;306 return err;307}308 309static int saa7706h_s_ctrl(struct v4l2_ctrl *ctrl)310{311 struct saa7706h_state *state =312 container_of(ctrl->handler, struct saa7706h_state, hdl);313 314 switch (ctrl->id) {315 case V4L2_CID_AUDIO_MUTE:316 if (ctrl->val)317 return saa7706h_mute(&state->sd);318 return saa7706h_unmute(&state->sd);319 }320 return -EINVAL;321}322 323static const struct v4l2_ctrl_ops saa7706h_ctrl_ops = {324 .s_ctrl = saa7706h_s_ctrl,325};326 327static const struct v4l2_subdev_ops empty_ops = {};328 329/*330 * Generic i2c probe331 * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'332 */333 334static int saa7706h_probe(struct i2c_client *client)335{336 struct saa7706h_state *state;337 struct v4l2_subdev *sd;338 int err;339 340 /* Check if the adapter supports the needed features */341 if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))342 return -EIO;343 344 v4l_info(client, "chip found @ 0x%02x (%s)\n",345 client->addr << 1, client->adapter->name);346 347 state = kzalloc(sizeof(struct saa7706h_state), GFP_KERNEL);348 if (state == NULL)349 return -ENOMEM;350 sd = &state->sd;351 v4l2_i2c_subdev_init(sd, client, &empty_ops);352 353 v4l2_ctrl_handler_init(&state->hdl, 4);354 v4l2_ctrl_new_std(&state->hdl, &saa7706h_ctrl_ops,355 V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);356 sd->ctrl_handler = &state->hdl;357 err = state->hdl.error;358 if (err)359 goto err;360 361 /* check the rom versions */362 err = saa7706h_get_reg16(sd, SAA7706H_DSP1_ROM_VER);363 if (err < 0)364 goto err;365 if (err != SUPPORTED_DSP1_ROM_VER)366 v4l2_warn(sd, "Unknown DSP1 ROM code version: 0x%x\n", err);367 state->muted = 1;368 369 /* startup in a muted state */370 err = saa7706h_mute(sd);371 if (err)372 goto err;373 374 return 0;375 376err:377 v4l2_device_unregister_subdev(sd);378 v4l2_ctrl_handler_free(&state->hdl);379 kfree(to_state(sd));380 381 printk(KERN_ERR DRIVER_NAME ": Failed to probe: %d\n", err);382 383 return err;384}385 386static void saa7706h_remove(struct i2c_client *client)387{388 struct v4l2_subdev *sd = i2c_get_clientdata(client);389 struct saa7706h_state *state = to_state(sd);390 391 saa7706h_mute(sd);392 v4l2_device_unregister_subdev(sd);393 v4l2_ctrl_handler_free(&state->hdl);394 kfree(to_state(sd));395}396 397static const struct i2c_device_id saa7706h_id[] = {398 { DRIVER_NAME },399 {}400};401 402MODULE_DEVICE_TABLE(i2c, saa7706h_id);403 404static struct i2c_driver saa7706h_driver = {405 .driver = {406 .name = DRIVER_NAME,407 },408 .probe = saa7706h_probe,409 .remove = saa7706h_remove,410 .id_table = saa7706h_id,411};412 413module_i2c_driver(saa7706h_driver);414 415MODULE_DESCRIPTION("SAA7706H Car Radio DSP driver");416MODULE_AUTHOR("Mocean Laboratories");417MODULE_LICENSE("GPL v2");418