brintos

brintos / linux-shallow public Read only

0
0
Text · 10.9 KiB · d9eecdd Raw
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