162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * saa7706.c Philips SAA7706H Car Radio DSP driver
462306a36Sopenharmony_ci * Copyright (c) 2009 Intel Corporation
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <linux/module.h>
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/delay.h>
1062306a36Sopenharmony_ci#include <linux/errno.h>
1162306a36Sopenharmony_ci#include <linux/kernel.h>
1262306a36Sopenharmony_ci#include <linux/interrupt.h>
1362306a36Sopenharmony_ci#include <linux/i2c.h>
1462306a36Sopenharmony_ci#include <linux/slab.h>
1562306a36Sopenharmony_ci#include <media/v4l2-device.h>
1662306a36Sopenharmony_ci#include <media/v4l2-ctrls.h>
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define DRIVER_NAME "saa7706h"
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/* the I2C memory map looks like this
2162306a36Sopenharmony_ci
2262306a36Sopenharmony_ci	$1C00 - $FFFF Not Used
2362306a36Sopenharmony_ci	$2200 - $3FFF Reserved YRAM (DSP2) space
2462306a36Sopenharmony_ci	$2000 - $21FF YRAM (DSP2)
2562306a36Sopenharmony_ci	$1FF0 - $1FFF Hardware Registers
2662306a36Sopenharmony_ci	$1280 - $1FEF Reserved XRAM (DSP2) space
2762306a36Sopenharmony_ci	$1000 - $127F XRAM (DSP2)
2862306a36Sopenharmony_ci	$0FFF        DSP CONTROL
2962306a36Sopenharmony_ci	$0A00 - $0FFE Reserved
3062306a36Sopenharmony_ci	$0980 - $09FF Reserved YRAM (DSP1) space
3162306a36Sopenharmony_ci	$0800 - $097F YRAM (DSP1)
3262306a36Sopenharmony_ci	$0200 - $07FF Not Used
3362306a36Sopenharmony_ci	$0180 - $01FF Reserved XRAM (DSP1) space
3462306a36Sopenharmony_ci	$0000 - $017F XRAM (DSP1)
3562306a36Sopenharmony_ci*/
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define SAA7706H_REG_CTRL		0x0fff
3862306a36Sopenharmony_ci#define SAA7706H_CTRL_BYP_PLL		0x0001
3962306a36Sopenharmony_ci#define SAA7706H_CTRL_PLL_DIV_MASK	0x003e
4062306a36Sopenharmony_ci#define SAA7706H_CTRL_PLL3_62975MHZ	0x003e
4162306a36Sopenharmony_ci#define SAA7706H_CTRL_DSP_TURBO		0x0040
4262306a36Sopenharmony_ci#define SAA7706H_CTRL_PC_RESET_DSP1	0x0080
4362306a36Sopenharmony_ci#define SAA7706H_CTRL_PC_RESET_DSP2	0x0100
4462306a36Sopenharmony_ci#define SAA7706H_CTRL_DSP1_ROM_EN_MASK	0x0600
4562306a36Sopenharmony_ci#define SAA7706H_CTRL_DSP1_FUNC_PROM	0x0000
4662306a36Sopenharmony_ci#define SAA7706H_CTRL_DSP2_ROM_EN_MASK	0x1800
4762306a36Sopenharmony_ci#define SAA7706H_CTRL_DSP2_FUNC_PROM	0x0000
4862306a36Sopenharmony_ci#define SAA7706H_CTRL_DIG_SIL_INTERPOL	0x8000
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci#define SAA7706H_REG_EVALUATION			0x1ff0
5162306a36Sopenharmony_ci#define SAA7706H_EVAL_DISABLE_CHARGE_PUMP	0x000001
5262306a36Sopenharmony_ci#define SAA7706H_EVAL_DCS_CLOCK			0x000002
5362306a36Sopenharmony_ci#define SAA7706H_EVAL_GNDRC1_ENABLE		0x000004
5462306a36Sopenharmony_ci#define SAA7706H_EVAL_GNDRC2_ENABLE		0x000008
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ci#define SAA7706H_REG_CL_GEN1			0x1ff3
5762306a36Sopenharmony_ci#define SAA7706H_CL_GEN1_MIN_LOOPGAIN_MASK	0x00000f
5862306a36Sopenharmony_ci#define SAA7706H_CL_GEN1_LOOPGAIN_MASK		0x0000f0
5962306a36Sopenharmony_ci#define SAA7706H_CL_GEN1_COARSE_RATION		0xffff00
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci#define SAA7706H_REG_CL_GEN2			0x1ff4
6262306a36Sopenharmony_ci#define SAA7706H_CL_GEN2_WSEDGE_FALLING		0x000001
6362306a36Sopenharmony_ci#define SAA7706H_CL_GEN2_STOP_VCO		0x000002
6462306a36Sopenharmony_ci#define SAA7706H_CL_GEN2_FRERUN			0x000004
6562306a36Sopenharmony_ci#define SAA7706H_CL_GEN2_ADAPTIVE		0x000008
6662306a36Sopenharmony_ci#define SAA7706H_CL_GEN2_FINE_RATIO_MASK	0x0ffff0
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci#define SAA7706H_REG_CL_GEN4		0x1ff6
6962306a36Sopenharmony_ci#define SAA7706H_CL_GEN4_BYPASS_PLL1	0x001000
7062306a36Sopenharmony_ci#define SAA7706H_CL_GEN4_PLL1_DIV_MASK	0x03e000
7162306a36Sopenharmony_ci#define SAA7706H_CL_GEN4_DSP1_TURBO	0x040000
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci#define SAA7706H_REG_SEL	0x1ff7
7462306a36Sopenharmony_ci#define SAA7706H_SEL_DSP2_SRCA_MASK	0x000007
7562306a36Sopenharmony_ci#define SAA7706H_SEL_DSP2_FMTA_MASK	0x000031
7662306a36Sopenharmony_ci#define SAA7706H_SEL_DSP2_SRCB_MASK	0x0001c0
7762306a36Sopenharmony_ci#define SAA7706H_SEL_DSP2_FMTB_MASK	0x000e00
7862306a36Sopenharmony_ci#define SAA7706H_SEL_DSP1_SRC_MASK	0x003000
7962306a36Sopenharmony_ci#define SAA7706H_SEL_DSP1_FMT_MASK	0x01c003
8062306a36Sopenharmony_ci#define SAA7706H_SEL_SPDIF2		0x020000
8162306a36Sopenharmony_ci#define SAA7706H_SEL_HOST_IO_FMT_MASK	0x1c0000
8262306a36Sopenharmony_ci#define SAA7706H_SEL_EN_HOST_IO		0x200000
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci#define SAA7706H_REG_IAC		0x1ff8
8562306a36Sopenharmony_ci#define SAA7706H_REG_CLK_SET		0x1ff9
8662306a36Sopenharmony_ci#define SAA7706H_REG_CLK_COEFF		0x1ffa
8762306a36Sopenharmony_ci#define SAA7706H_REG_INPUT_SENS		0x1ffb
8862306a36Sopenharmony_ci#define SAA7706H_INPUT_SENS_RDS_VOL_MASK	0x0003f
8962306a36Sopenharmony_ci#define SAA7706H_INPUT_SENS_FM_VOL_MASK		0x00fc0
9062306a36Sopenharmony_ci#define SAA7706H_INPUT_SENS_FM_MPX		0x01000
9162306a36Sopenharmony_ci#define SAA7706H_INPUT_SENS_OFF_FILTER_A_EN	0x02000
9262306a36Sopenharmony_ci#define SAA7706H_INPUT_SENS_OFF_FILTER_B_EN	0x04000
9362306a36Sopenharmony_ci#define SAA7706H_REG_PHONE_NAV_AUDIO	0x1ffc
9462306a36Sopenharmony_ci#define SAA7706H_REG_IO_CONF_DSP2	0x1ffd
9562306a36Sopenharmony_ci#define SAA7706H_REG_STATUS_DSP2	0x1ffe
9662306a36Sopenharmony_ci#define SAA7706H_REG_PC_DSP2		0x1fff
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci#define SAA7706H_DSP1_MOD0	0x0800
9962306a36Sopenharmony_ci#define SAA7706H_DSP1_ROM_VER	0x097f
10062306a36Sopenharmony_ci#define SAA7706H_DSP2_MPTR0	0x1000
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci#define SAA7706H_DSP1_MODPNTR	0x0000
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci#define SAA7706H_DSP2_XMEM_CONTLLCW	0x113e
10562306a36Sopenharmony_ci#define SAA7706H_DSP2_XMEM_BUSAMP	0x114a
10662306a36Sopenharmony_ci#define SAA7706H_DSP2_XMEM_FDACPNTR	0x11f9
10762306a36Sopenharmony_ci#define SAA7706H_DSP2_XMEM_IIS1PNTR	0x11fb
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci#define SAA7706H_DSP2_YMEM_PVGA		0x212a
11062306a36Sopenharmony_ci#define SAA7706H_DSP2_YMEM_PVAT1	0x212b
11162306a36Sopenharmony_ci#define SAA7706H_DSP2_YMEM_PVAT		0x212c
11262306a36Sopenharmony_ci#define SAA7706H_DSP2_YMEM_ROM_VER	0x21ff
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci#define SUPPORTED_DSP1_ROM_VER		0x667
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistruct saa7706h_state {
11762306a36Sopenharmony_ci	struct v4l2_subdev sd;
11862306a36Sopenharmony_ci	struct v4l2_ctrl_handler hdl;
11962306a36Sopenharmony_ci	unsigned muted;
12062306a36Sopenharmony_ci};
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_cistatic inline struct saa7706h_state *to_state(struct v4l2_subdev *sd)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	return container_of(sd, struct saa7706h_state, sd);
12562306a36Sopenharmony_ci}
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_cistatic int saa7706h_i2c_send(struct i2c_client *client, const u8 *data, int len)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	int err = i2c_master_send(client, data, len);
13062306a36Sopenharmony_ci	if (err == len)
13162306a36Sopenharmony_ci		return 0;
13262306a36Sopenharmony_ci	return err > 0 ? -EIO : err;
13362306a36Sopenharmony_ci}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_cistatic int saa7706h_i2c_transfer(struct i2c_client *client,
13662306a36Sopenharmony_ci	struct i2c_msg *msgs, int num)
13762306a36Sopenharmony_ci{
13862306a36Sopenharmony_ci	int err = i2c_transfer(client->adapter, msgs, num);
13962306a36Sopenharmony_ci	if (err == num)
14062306a36Sopenharmony_ci		return 0;
14162306a36Sopenharmony_ci	return err > 0 ? -EIO : err;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic int saa7706h_set_reg24(struct v4l2_subdev *sd, u16 reg, u32 val)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
14762306a36Sopenharmony_ci	u8 buf[5];
14862306a36Sopenharmony_ci	int pos = 0;
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	buf[pos++] = reg >> 8;
15162306a36Sopenharmony_ci	buf[pos++] = reg;
15262306a36Sopenharmony_ci	buf[pos++] = val >> 16;
15362306a36Sopenharmony_ci	buf[pos++] = val >> 8;
15462306a36Sopenharmony_ci	buf[pos++] = val;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	return saa7706h_i2c_send(client, buf, pos);
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic int saa7706h_set_reg24_err(struct v4l2_subdev *sd, u16 reg, u32 val,
16062306a36Sopenharmony_ci	int *err)
16162306a36Sopenharmony_ci{
16262306a36Sopenharmony_ci	return *err ? *err : saa7706h_set_reg24(sd, reg, val);
16362306a36Sopenharmony_ci}
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_cistatic int saa7706h_set_reg16(struct v4l2_subdev *sd, u16 reg, u16 val)
16662306a36Sopenharmony_ci{
16762306a36Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
16862306a36Sopenharmony_ci	u8 buf[4];
16962306a36Sopenharmony_ci	int pos = 0;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	buf[pos++] = reg >> 8;
17262306a36Sopenharmony_ci	buf[pos++] = reg;
17362306a36Sopenharmony_ci	buf[pos++] = val >> 8;
17462306a36Sopenharmony_ci	buf[pos++] = val;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return saa7706h_i2c_send(client, buf, pos);
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_cistatic int saa7706h_set_reg16_err(struct v4l2_subdev *sd, u16 reg, u16 val,
18062306a36Sopenharmony_ci	int *err)
18162306a36Sopenharmony_ci{
18262306a36Sopenharmony_ci	return *err ? *err : saa7706h_set_reg16(sd, reg, val);
18362306a36Sopenharmony_ci}
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_cistatic int saa7706h_get_reg16(struct v4l2_subdev *sd, u16 reg)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
18862306a36Sopenharmony_ci	u8 buf[2];
18962306a36Sopenharmony_ci	int err;
19062306a36Sopenharmony_ci	u8 regaddr[] = {reg >> 8, reg};
19162306a36Sopenharmony_ci	struct i2c_msg msg[] = {
19262306a36Sopenharmony_ci					{
19362306a36Sopenharmony_ci						.addr = client->addr,
19462306a36Sopenharmony_ci						.len = sizeof(regaddr),
19562306a36Sopenharmony_ci						.buf = regaddr
19662306a36Sopenharmony_ci					},
19762306a36Sopenharmony_ci					{
19862306a36Sopenharmony_ci						.addr = client->addr,
19962306a36Sopenharmony_ci						.flags = I2C_M_RD,
20062306a36Sopenharmony_ci						.len = sizeof(buf),
20162306a36Sopenharmony_ci						.buf = buf
20262306a36Sopenharmony_ci					}
20362306a36Sopenharmony_ci				};
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	err = saa7706h_i2c_transfer(client, msg, ARRAY_SIZE(msg));
20662306a36Sopenharmony_ci	if (err)
20762306a36Sopenharmony_ci		return err;
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	return buf[0] << 8 | buf[1];
21062306a36Sopenharmony_ci}
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_cistatic int saa7706h_unmute(struct v4l2_subdev *sd)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	struct saa7706h_state *state = to_state(sd);
21562306a36Sopenharmony_ci	int err = 0;
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,
21862306a36Sopenharmony_ci		SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |
21962306a36Sopenharmony_ci		SAA7706H_CTRL_PC_RESET_DSP2, &err);
22062306a36Sopenharmony_ci
22162306a36Sopenharmony_ci	/* newer versions of the chip requires a small sleep after reset */
22262306a36Sopenharmony_ci	msleep(1);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	err = saa7706h_set_reg16_err(sd, SAA7706H_REG_CTRL,
22562306a36Sopenharmony_ci		SAA7706H_CTRL_PLL3_62975MHZ, &err);
22662306a36Sopenharmony_ci
22762306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_EVALUATION, 0, &err);
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN1, 0x040022, &err);
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN2,
23262306a36Sopenharmony_ci		SAA7706H_CL_GEN2_WSEDGE_FALLING, &err);
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CL_GEN4, 0x024080, &err);
23562306a36Sopenharmony_ci
23662306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_SEL, 0x200080, &err);
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IAC, 0xf4caed, &err);
23962306a36Sopenharmony_ci
24062306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_SET, 0x124334, &err);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_CLK_COEFF, 0x004a1a,
24362306a36Sopenharmony_ci		&err);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_INPUT_SENS, 0x0071c7,
24662306a36Sopenharmony_ci		&err);
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PHONE_NAV_AUDIO,
24962306a36Sopenharmony_ci		0x0e22ff, &err);
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_IO_CONF_DSP2, 0x001ff8,
25262306a36Sopenharmony_ci		&err);
25362306a36Sopenharmony_ci
25462306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_STATUS_DSP2, 0x080003,
25562306a36Sopenharmony_ci		&err);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_REG_PC_DSP2, 0x000004, &err);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	err = saa7706h_set_reg16_err(sd, SAA7706H_DSP1_MOD0, 0x0c6c, &err);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_MPTR0, 0x000b4b, &err);
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x000600, &err);
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP1_MODPNTR, 0x0000c0, &err);
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000819,
26862306a36Sopenharmony_ci		&err);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x00085a,
27162306a36Sopenharmony_ci		&err);
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_BUSAMP, 0x7fffff,
27462306a36Sopenharmony_ci		&err);
27562306a36Sopenharmony_ci
27662306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_FDACPNTR, 0x2000cb,
27762306a36Sopenharmony_ci		&err);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_IIS1PNTR, 0x2000cb,
28062306a36Sopenharmony_ci		&err);
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVGA, 0x0f80, &err);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT1, 0x0800,
28562306a36Sopenharmony_ci		&err);
28662306a36Sopenharmony_ci
28762306a36Sopenharmony_ci	err = saa7706h_set_reg16_err(sd, SAA7706H_DSP2_YMEM_PVAT, 0x0800, &err);
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	err = saa7706h_set_reg24_err(sd, SAA7706H_DSP2_XMEM_CONTLLCW, 0x000905,
29062306a36Sopenharmony_ci		&err);
29162306a36Sopenharmony_ci	if (!err)
29262306a36Sopenharmony_ci		state->muted = 0;
29362306a36Sopenharmony_ci	return err;
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistatic int saa7706h_mute(struct v4l2_subdev *sd)
29762306a36Sopenharmony_ci{
29862306a36Sopenharmony_ci	struct saa7706h_state *state = to_state(sd);
29962306a36Sopenharmony_ci	int err;
30062306a36Sopenharmony_ci
30162306a36Sopenharmony_ci	err = saa7706h_set_reg16(sd, SAA7706H_REG_CTRL,
30262306a36Sopenharmony_ci		SAA7706H_CTRL_PLL3_62975MHZ | SAA7706H_CTRL_PC_RESET_DSP1 |
30362306a36Sopenharmony_ci		SAA7706H_CTRL_PC_RESET_DSP2);
30462306a36Sopenharmony_ci	if (!err)
30562306a36Sopenharmony_ci		state->muted = 1;
30662306a36Sopenharmony_ci	return err;
30762306a36Sopenharmony_ci}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_cistatic int saa7706h_s_ctrl(struct v4l2_ctrl *ctrl)
31062306a36Sopenharmony_ci{
31162306a36Sopenharmony_ci	struct saa7706h_state *state =
31262306a36Sopenharmony_ci		container_of(ctrl->handler, struct saa7706h_state, hdl);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	switch (ctrl->id) {
31562306a36Sopenharmony_ci	case V4L2_CID_AUDIO_MUTE:
31662306a36Sopenharmony_ci		if (ctrl->val)
31762306a36Sopenharmony_ci			return saa7706h_mute(&state->sd);
31862306a36Sopenharmony_ci		return saa7706h_unmute(&state->sd);
31962306a36Sopenharmony_ci	}
32062306a36Sopenharmony_ci	return -EINVAL;
32162306a36Sopenharmony_ci}
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops saa7706h_ctrl_ops = {
32462306a36Sopenharmony_ci	.s_ctrl = saa7706h_s_ctrl,
32562306a36Sopenharmony_ci};
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_cistatic const struct v4l2_subdev_ops empty_ops = {};
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_ci/*
33062306a36Sopenharmony_ci * Generic i2c probe
33162306a36Sopenharmony_ci * concerning the addresses: i2c wants 7 bit (without the r/w bit), so '>>1'
33262306a36Sopenharmony_ci */
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_cistatic int saa7706h_probe(struct i2c_client *client)
33562306a36Sopenharmony_ci{
33662306a36Sopenharmony_ci	struct saa7706h_state *state;
33762306a36Sopenharmony_ci	struct v4l2_subdev *sd;
33862306a36Sopenharmony_ci	int err;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	/* Check if the adapter supports the needed features */
34162306a36Sopenharmony_ci	if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
34262306a36Sopenharmony_ci		return -EIO;
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	v4l_info(client, "chip found @ 0x%02x (%s)\n",
34562306a36Sopenharmony_ci			client->addr << 1, client->adapter->name);
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ci	state = kzalloc(sizeof(struct saa7706h_state), GFP_KERNEL);
34862306a36Sopenharmony_ci	if (state == NULL)
34962306a36Sopenharmony_ci		return -ENOMEM;
35062306a36Sopenharmony_ci	sd = &state->sd;
35162306a36Sopenharmony_ci	v4l2_i2c_subdev_init(sd, client, &empty_ops);
35262306a36Sopenharmony_ci
35362306a36Sopenharmony_ci	v4l2_ctrl_handler_init(&state->hdl, 4);
35462306a36Sopenharmony_ci	v4l2_ctrl_new_std(&state->hdl, &saa7706h_ctrl_ops,
35562306a36Sopenharmony_ci			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
35662306a36Sopenharmony_ci	sd->ctrl_handler = &state->hdl;
35762306a36Sopenharmony_ci	err = state->hdl.error;
35862306a36Sopenharmony_ci	if (err)
35962306a36Sopenharmony_ci		goto err;
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	/* check the rom versions */
36262306a36Sopenharmony_ci	err = saa7706h_get_reg16(sd, SAA7706H_DSP1_ROM_VER);
36362306a36Sopenharmony_ci	if (err < 0)
36462306a36Sopenharmony_ci		goto err;
36562306a36Sopenharmony_ci	if (err != SUPPORTED_DSP1_ROM_VER)
36662306a36Sopenharmony_ci		v4l2_warn(sd, "Unknown DSP1 ROM code version: 0x%x\n", err);
36762306a36Sopenharmony_ci	state->muted = 1;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	/* startup in a muted state */
37062306a36Sopenharmony_ci	err = saa7706h_mute(sd);
37162306a36Sopenharmony_ci	if (err)
37262306a36Sopenharmony_ci		goto err;
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	return 0;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_cierr:
37762306a36Sopenharmony_ci	v4l2_device_unregister_subdev(sd);
37862306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&state->hdl);
37962306a36Sopenharmony_ci	kfree(to_state(sd));
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	printk(KERN_ERR DRIVER_NAME ": Failed to probe: %d\n", err);
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	return err;
38462306a36Sopenharmony_ci}
38562306a36Sopenharmony_ci
38662306a36Sopenharmony_cistatic void saa7706h_remove(struct i2c_client *client)
38762306a36Sopenharmony_ci{
38862306a36Sopenharmony_ci	struct v4l2_subdev *sd = i2c_get_clientdata(client);
38962306a36Sopenharmony_ci	struct saa7706h_state *state = to_state(sd);
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci	saa7706h_mute(sd);
39262306a36Sopenharmony_ci	v4l2_device_unregister_subdev(sd);
39362306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&state->hdl);
39462306a36Sopenharmony_ci	kfree(to_state(sd));
39562306a36Sopenharmony_ci}
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_cistatic const struct i2c_device_id saa7706h_id[] = {
39862306a36Sopenharmony_ci	{DRIVER_NAME, 0},
39962306a36Sopenharmony_ci	{},
40062306a36Sopenharmony_ci};
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, saa7706h_id);
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_cistatic struct i2c_driver saa7706h_driver = {
40562306a36Sopenharmony_ci	.driver = {
40662306a36Sopenharmony_ci		.name	= DRIVER_NAME,
40762306a36Sopenharmony_ci	},
40862306a36Sopenharmony_ci	.probe		= saa7706h_probe,
40962306a36Sopenharmony_ci	.remove		= saa7706h_remove,
41062306a36Sopenharmony_ci	.id_table	= saa7706h_id,
41162306a36Sopenharmony_ci};
41262306a36Sopenharmony_ci
41362306a36Sopenharmony_cimodule_i2c_driver(saa7706h_driver);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ciMODULE_DESCRIPTION("SAA7706H Car Radio DSP driver");
41662306a36Sopenharmony_ciMODULE_AUTHOR("Mocean Laboratories");
41762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
418