18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Programming the mspx4xx sound processor family
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * (c) 1997-2001 Gerd Knorr <kraxel@bytesex.org>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * what works and what doesn't:
88c2ecf20Sopenharmony_ci *
98c2ecf20Sopenharmony_ci *  AM-Mono
108c2ecf20Sopenharmony_ci *      Support for Hauppauge cards added (decoding handled by tuner) added by
118c2ecf20Sopenharmony_ci *      Frederic Crozat <fcrozat@mail.dotcom.fr>
128c2ecf20Sopenharmony_ci *
138c2ecf20Sopenharmony_ci *  FM-Mono
148c2ecf20Sopenharmony_ci *      should work. The stereo modes are backward compatible to FM-mono,
158c2ecf20Sopenharmony_ci *      therefore FM-Mono should be always available.
168c2ecf20Sopenharmony_ci *
178c2ecf20Sopenharmony_ci *  FM-Stereo (B/G, used in germany)
188c2ecf20Sopenharmony_ci *      should work, with autodetect
198c2ecf20Sopenharmony_ci *
208c2ecf20Sopenharmony_ci *  FM-Stereo (satellite)
218c2ecf20Sopenharmony_ci *      should work, no autodetect (i.e. default is mono, but you can
228c2ecf20Sopenharmony_ci *      switch to stereo -- untested)
238c2ecf20Sopenharmony_ci *
248c2ecf20Sopenharmony_ci *  NICAM (B/G, L , used in UK, Scandinavia, Spain and France)
258c2ecf20Sopenharmony_ci *      should work, with autodetect. Support for NICAM was added by
268c2ecf20Sopenharmony_ci *      Pekka Pietikainen <pp@netppl.fi>
278c2ecf20Sopenharmony_ci *
288c2ecf20Sopenharmony_ci * TODO:
298c2ecf20Sopenharmony_ci *   - better SAT support
308c2ecf20Sopenharmony_ci *
318c2ecf20Sopenharmony_ci * 980623  Thomas Sailer (sailer@ife.ee.ethz.ch)
328c2ecf20Sopenharmony_ci *         using soundcore instead of OSS
338c2ecf20Sopenharmony_ci */
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci#include <linux/kernel.h>
378c2ecf20Sopenharmony_ci#include <linux/module.h>
388c2ecf20Sopenharmony_ci#include <linux/slab.h>
398c2ecf20Sopenharmony_ci#include <linux/i2c.h>
408c2ecf20Sopenharmony_ci#include <linux/kthread.h>
418c2ecf20Sopenharmony_ci#include <linux/freezer.h>
428c2ecf20Sopenharmony_ci#include <linux/videodev2.h>
438c2ecf20Sopenharmony_ci#include <media/v4l2-device.h>
448c2ecf20Sopenharmony_ci#include <media/v4l2-ioctl.h>
458c2ecf20Sopenharmony_ci#include <media/drv-intf/msp3400.h>
468c2ecf20Sopenharmony_ci#include <media/i2c/tvaudio.h>
478c2ecf20Sopenharmony_ci#include "msp3400-driver.h"
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci/* ---------------------------------------------------------------------- */
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("device driver for msp34xx TV sound processor");
528c2ecf20Sopenharmony_ciMODULE_AUTHOR("Gerd Knorr");
538c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
548c2ecf20Sopenharmony_ci
558c2ecf20Sopenharmony_ci/* module parameters */
568c2ecf20Sopenharmony_cistatic int opmode   = OPMODE_AUTO;
578c2ecf20Sopenharmony_ciint msp_debug;		 /* msp_debug output */
588c2ecf20Sopenharmony_cibool msp_once;		 /* no continuous stereo monitoring */
598c2ecf20Sopenharmony_cibool msp_amsound;	 /* hard-wire AM sound at 6.5 Hz (france),
608c2ecf20Sopenharmony_ci			    the autoscan seems work well only with FM... */
618c2ecf20Sopenharmony_ciint msp_standard = 1;    /* Override auto detect of audio msp_standard,
628c2ecf20Sopenharmony_ci			    if needed. */
638c2ecf20Sopenharmony_cibool msp_dolby;
648c2ecf20Sopenharmony_ci
658c2ecf20Sopenharmony_ciint msp_stereo_thresh = 0x190; /* a2 threshold for stereo/bilingual
668c2ecf20Sopenharmony_ci					(msp34xxg only) 0x00a0-0x03c0 */
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_ci/* read-only */
698c2ecf20Sopenharmony_cimodule_param(opmode,           int, 0444);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci/* read-write */
728c2ecf20Sopenharmony_cimodule_param_named(once, msp_once,                      bool, 0644);
738c2ecf20Sopenharmony_cimodule_param_named(debug, msp_debug,                    int,  0644);
748c2ecf20Sopenharmony_cimodule_param_named(stereo_threshold, msp_stereo_thresh, int,  0644);
758c2ecf20Sopenharmony_cimodule_param_named(standard, msp_standard,              int,  0644);
768c2ecf20Sopenharmony_cimodule_param_named(amsound, msp_amsound,                bool, 0644);
778c2ecf20Sopenharmony_cimodule_param_named(dolby, msp_dolby,                    bool, 0644);
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ciMODULE_PARM_DESC(opmode, "Forces a MSP3400 opmode. 0=Manual, 1=Autodetect, 2=Autodetect and autoselect");
808c2ecf20Sopenharmony_ciMODULE_PARM_DESC(once, "No continuous stereo monitoring");
818c2ecf20Sopenharmony_ciMODULE_PARM_DESC(debug, "Enable debug messages [0-3]");
828c2ecf20Sopenharmony_ciMODULE_PARM_DESC(stereo_threshold, "Sets signal threshold to activate stereo");
838c2ecf20Sopenharmony_ciMODULE_PARM_DESC(standard, "Specify audio standard: 32 = NTSC, 64 = radio, Default: Autodetect");
848c2ecf20Sopenharmony_ciMODULE_PARM_DESC(amsound, "Hardwire AM sound at 6.5Hz (France), FM can autoscan");
858c2ecf20Sopenharmony_ciMODULE_PARM_DESC(dolby, "Activates Dolby processing");
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci/* ---------------------------------------------------------------------- */
888c2ecf20Sopenharmony_ci
898c2ecf20Sopenharmony_ci/* control subaddress */
908c2ecf20Sopenharmony_ci#define I2C_MSP_CONTROL 0x00
918c2ecf20Sopenharmony_ci/* demodulator unit subaddress */
928c2ecf20Sopenharmony_ci#define I2C_MSP_DEM     0x10
938c2ecf20Sopenharmony_ci/* DSP unit subaddress */
948c2ecf20Sopenharmony_ci#define I2C_MSP_DSP     0x12
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
988c2ecf20Sopenharmony_ci/* functions for talking to the MSP3400C Sound processor                   */
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ciint msp_reset(struct i2c_client *client)
1018c2ecf20Sopenharmony_ci{
1028c2ecf20Sopenharmony_ci	/* reset and read revision code */
1038c2ecf20Sopenharmony_ci	static u8 reset_off[3] = { I2C_MSP_CONTROL, 0x80, 0x00 };
1048c2ecf20Sopenharmony_ci	static u8 reset_on[3]  = { I2C_MSP_CONTROL, 0x00, 0x00 };
1058c2ecf20Sopenharmony_ci	static u8 write[3]     = { I2C_MSP_DSP + 1, 0x00, 0x1e };
1068c2ecf20Sopenharmony_ci	u8 read[2];
1078c2ecf20Sopenharmony_ci	struct i2c_msg reset[2] = {
1088c2ecf20Sopenharmony_ci		{
1098c2ecf20Sopenharmony_ci			.addr = client->addr,
1108c2ecf20Sopenharmony_ci			.flags = I2C_M_IGNORE_NAK,
1118c2ecf20Sopenharmony_ci			.len = 3,
1128c2ecf20Sopenharmony_ci			.buf = reset_off
1138c2ecf20Sopenharmony_ci		},
1148c2ecf20Sopenharmony_ci		{
1158c2ecf20Sopenharmony_ci			.addr = client->addr,
1168c2ecf20Sopenharmony_ci			.flags = I2C_M_IGNORE_NAK,
1178c2ecf20Sopenharmony_ci			.len = 3,
1188c2ecf20Sopenharmony_ci			.buf = reset_on
1198c2ecf20Sopenharmony_ci		},
1208c2ecf20Sopenharmony_ci	};
1218c2ecf20Sopenharmony_ci	struct i2c_msg test[2] = {
1228c2ecf20Sopenharmony_ci		{
1238c2ecf20Sopenharmony_ci			.addr = client->addr,
1248c2ecf20Sopenharmony_ci			.len = 3,
1258c2ecf20Sopenharmony_ci			.buf = write
1268c2ecf20Sopenharmony_ci		},
1278c2ecf20Sopenharmony_ci		{
1288c2ecf20Sopenharmony_ci			.addr = client->addr,
1298c2ecf20Sopenharmony_ci			.flags = I2C_M_RD,
1308c2ecf20Sopenharmony_ci			.len = 2,
1318c2ecf20Sopenharmony_ci			.buf = read
1328c2ecf20Sopenharmony_ci		},
1338c2ecf20Sopenharmony_ci	};
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 3, msp_debug, "msp_reset\n");
1368c2ecf20Sopenharmony_ci	if (i2c_transfer(client->adapter, &reset[0], 1) != 1 ||
1378c2ecf20Sopenharmony_ci	    i2c_transfer(client->adapter, &reset[1], 1) != 1 ||
1388c2ecf20Sopenharmony_ci	    i2c_transfer(client->adapter, test, 2) != 2) {
1398c2ecf20Sopenharmony_ci		dev_err(&client->dev, "chip reset failed\n");
1408c2ecf20Sopenharmony_ci		return -1;
1418c2ecf20Sopenharmony_ci	}
1428c2ecf20Sopenharmony_ci	return 0;
1438c2ecf20Sopenharmony_ci}
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_cistatic int msp_read(struct i2c_client *client, int dev, int addr)
1468c2ecf20Sopenharmony_ci{
1478c2ecf20Sopenharmony_ci	int err, retval;
1488c2ecf20Sopenharmony_ci	u8 write[3];
1498c2ecf20Sopenharmony_ci	u8 read[2];
1508c2ecf20Sopenharmony_ci	struct i2c_msg msgs[2] = {
1518c2ecf20Sopenharmony_ci		{
1528c2ecf20Sopenharmony_ci			.addr = client->addr,
1538c2ecf20Sopenharmony_ci			.len = 3,
1548c2ecf20Sopenharmony_ci			.buf = write
1558c2ecf20Sopenharmony_ci		},
1568c2ecf20Sopenharmony_ci		{
1578c2ecf20Sopenharmony_ci			.addr = client->addr,
1588c2ecf20Sopenharmony_ci			.flags = I2C_M_RD,
1598c2ecf20Sopenharmony_ci			.len = 2,
1608c2ecf20Sopenharmony_ci			.buf = read
1618c2ecf20Sopenharmony_ci		}
1628c2ecf20Sopenharmony_ci	};
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_ci	write[0] = dev + 1;
1658c2ecf20Sopenharmony_ci	write[1] = addr >> 8;
1668c2ecf20Sopenharmony_ci	write[2] = addr & 0xff;
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	for (err = 0; err < 3; err++) {
1698c2ecf20Sopenharmony_ci		if (i2c_transfer(client->adapter, msgs, 2) == 2)
1708c2ecf20Sopenharmony_ci			break;
1718c2ecf20Sopenharmony_ci		dev_warn(&client->dev, "I/O error #%d (read 0x%02x/0x%02x)\n", err,
1728c2ecf20Sopenharmony_ci		       dev, addr);
1738c2ecf20Sopenharmony_ci		schedule_timeout_interruptible(msecs_to_jiffies(10));
1748c2ecf20Sopenharmony_ci	}
1758c2ecf20Sopenharmony_ci	if (err == 3) {
1768c2ecf20Sopenharmony_ci		dev_warn(&client->dev, "resetting chip, sound will go off.\n");
1778c2ecf20Sopenharmony_ci		msp_reset(client);
1788c2ecf20Sopenharmony_ci		return -1;
1798c2ecf20Sopenharmony_ci	}
1808c2ecf20Sopenharmony_ci	retval = read[0] << 8 | read[1];
1818c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 3, msp_debug, "msp_read(0x%x, 0x%x): 0x%x\n",
1828c2ecf20Sopenharmony_ci			dev, addr, retval);
1838c2ecf20Sopenharmony_ci	return retval;
1848c2ecf20Sopenharmony_ci}
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ciint msp_read_dem(struct i2c_client *client, int addr)
1878c2ecf20Sopenharmony_ci{
1888c2ecf20Sopenharmony_ci	return msp_read(client, I2C_MSP_DEM, addr);
1898c2ecf20Sopenharmony_ci}
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ciint msp_read_dsp(struct i2c_client *client, int addr)
1928c2ecf20Sopenharmony_ci{
1938c2ecf20Sopenharmony_ci	return msp_read(client, I2C_MSP_DSP, addr);
1948c2ecf20Sopenharmony_ci}
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_cistatic int msp_write(struct i2c_client *client, int dev, int addr, int val)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	int err;
1998c2ecf20Sopenharmony_ci	u8 buffer[5];
2008c2ecf20Sopenharmony_ci
2018c2ecf20Sopenharmony_ci	buffer[0] = dev;
2028c2ecf20Sopenharmony_ci	buffer[1] = addr >> 8;
2038c2ecf20Sopenharmony_ci	buffer[2] = addr &  0xff;
2048c2ecf20Sopenharmony_ci	buffer[3] = val  >> 8;
2058c2ecf20Sopenharmony_ci	buffer[4] = val  &  0xff;
2068c2ecf20Sopenharmony_ci
2078c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 3, msp_debug, "msp_write(0x%x, 0x%x, 0x%x)\n",
2088c2ecf20Sopenharmony_ci			dev, addr, val);
2098c2ecf20Sopenharmony_ci	for (err = 0; err < 3; err++) {
2108c2ecf20Sopenharmony_ci		if (i2c_master_send(client, buffer, 5) == 5)
2118c2ecf20Sopenharmony_ci			break;
2128c2ecf20Sopenharmony_ci		dev_warn(&client->dev, "I/O error #%d (write 0x%02x/0x%02x)\n", err,
2138c2ecf20Sopenharmony_ci		       dev, addr);
2148c2ecf20Sopenharmony_ci		schedule_timeout_interruptible(msecs_to_jiffies(10));
2158c2ecf20Sopenharmony_ci	}
2168c2ecf20Sopenharmony_ci	if (err == 3) {
2178c2ecf20Sopenharmony_ci		dev_warn(&client->dev, "resetting chip, sound will go off.\n");
2188c2ecf20Sopenharmony_ci		msp_reset(client);
2198c2ecf20Sopenharmony_ci		return -1;
2208c2ecf20Sopenharmony_ci	}
2218c2ecf20Sopenharmony_ci	return 0;
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ciint msp_write_dem(struct i2c_client *client, int addr, int val)
2258c2ecf20Sopenharmony_ci{
2268c2ecf20Sopenharmony_ci	return msp_write(client, I2C_MSP_DEM, addr, val);
2278c2ecf20Sopenharmony_ci}
2288c2ecf20Sopenharmony_ci
2298c2ecf20Sopenharmony_ciint msp_write_dsp(struct i2c_client *client, int addr, int val)
2308c2ecf20Sopenharmony_ci{
2318c2ecf20Sopenharmony_ci	return msp_write(client, I2C_MSP_DSP, addr, val);
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- *
2358c2ecf20Sopenharmony_ci * bits  9  8  5 - SCART DSP input Select:
2368c2ecf20Sopenharmony_ci *       0  0  0 - SCART 1 to DSP input (reset position)
2378c2ecf20Sopenharmony_ci *       0  1  0 - MONO to DSP input
2388c2ecf20Sopenharmony_ci *       1  0  0 - SCART 2 to DSP input
2398c2ecf20Sopenharmony_ci *       1  1  1 - Mute DSP input
2408c2ecf20Sopenharmony_ci *
2418c2ecf20Sopenharmony_ci * bits 11 10  6 - SCART 1 Output Select:
2428c2ecf20Sopenharmony_ci *       0  0  0 - undefined (reset position)
2438c2ecf20Sopenharmony_ci *       0  1  0 - SCART 2 Input to SCART 1 Output (for devices with 2 SCARTS)
2448c2ecf20Sopenharmony_ci *       1  0  0 - MONO input to SCART 1 Output
2458c2ecf20Sopenharmony_ci *       1  1  0 - SCART 1 DA to SCART 1 Output
2468c2ecf20Sopenharmony_ci *       0  0  1 - SCART 2 DA to SCART 1 Output
2478c2ecf20Sopenharmony_ci *       0  1  1 - SCART 1 Input to SCART 1 Output
2488c2ecf20Sopenharmony_ci *       1  1  1 - Mute SCART 1 Output
2498c2ecf20Sopenharmony_ci *
2508c2ecf20Sopenharmony_ci * bits 13 12  7 - SCART 2 Output Select (for devices with 2 Output SCART):
2518c2ecf20Sopenharmony_ci *       0  0  0 - SCART 1 DA to SCART 2 Output (reset position)
2528c2ecf20Sopenharmony_ci *       0  1  0 - SCART 1 Input to SCART 2 Output
2538c2ecf20Sopenharmony_ci *       1  0  0 - MONO input to SCART 2 Output
2548c2ecf20Sopenharmony_ci *       0  0  1 - SCART 2 DA to SCART 2 Output
2558c2ecf20Sopenharmony_ci *       0  1  1 - SCART 2 Input to SCART 2 Output
2568c2ecf20Sopenharmony_ci *       1  1  0 - Mute SCART 2 Output
2578c2ecf20Sopenharmony_ci *
2588c2ecf20Sopenharmony_ci * Bits 4 to 0 should be zero.
2598c2ecf20Sopenharmony_ci * ----------------------------------------------------------------------- */
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_cistatic int scarts[3][9] = {
2628c2ecf20Sopenharmony_ci	/* MASK   IN1     IN2     IN3     IN4     IN1_DA  IN2_DA  MONO    MUTE   */
2638c2ecf20Sopenharmony_ci	/* SCART DSP Input select */
2648c2ecf20Sopenharmony_ci	{ 0x0320, 0x0000, 0x0200, 0x0300, 0x0020, -1,     -1,     0x0100, 0x0320 },
2658c2ecf20Sopenharmony_ci	/* SCART1 Output select */
2668c2ecf20Sopenharmony_ci	{ 0x0c40, 0x0440, 0x0400, 0x0000, 0x0840, 0x0c00, 0x0040, 0x0800, 0x0c40 },
2678c2ecf20Sopenharmony_ci	/* SCART2 Output select */
2688c2ecf20Sopenharmony_ci	{ 0x3080, 0x1000, 0x1080, 0x2080, 0x3080, 0x0000, 0x0080, 0x2000, 0x3000 },
2698c2ecf20Sopenharmony_ci};
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_cistatic char *scart_names[] = {
2728c2ecf20Sopenharmony_ci	"in1", "in2", "in3", "in4", "in1 da", "in2 da", "mono", "mute"
2738c2ecf20Sopenharmony_ci};
2748c2ecf20Sopenharmony_ci
2758c2ecf20Sopenharmony_civoid msp_set_scart(struct i2c_client *client, int in, int out)
2768c2ecf20Sopenharmony_ci{
2778c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(i2c_get_clientdata(client));
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	state->in_scart = in;
2808c2ecf20Sopenharmony_ci
2818c2ecf20Sopenharmony_ci	if (in >= 0 && in <= 7 && out >= 0 && out <= 2) {
2828c2ecf20Sopenharmony_ci		if (-1 == scarts[out][in + 1])
2838c2ecf20Sopenharmony_ci			return;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci		state->acb &= ~scarts[out][0];
2868c2ecf20Sopenharmony_ci		state->acb |=  scarts[out][in + 1];
2878c2ecf20Sopenharmony_ci	} else
2888c2ecf20Sopenharmony_ci		state->acb = 0xf60; /* Mute Input and SCART 1 Output */
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 1, msp_debug, "scart switch: %s => %d (ACB=0x%04x)\n",
2918c2ecf20Sopenharmony_ci					scart_names[in], out, state->acb);
2928c2ecf20Sopenharmony_ci	msp_write_dsp(client, 0x13, state->acb);
2938c2ecf20Sopenharmony_ci
2948c2ecf20Sopenharmony_ci	/* Sets I2S speed 0 = 1.024 Mbps, 1 = 2.048 Mbps */
2958c2ecf20Sopenharmony_ci	if (state->has_i2s_conf)
2968c2ecf20Sopenharmony_ci		msp_write_dem(client, 0x40, state->i2s_mode);
2978c2ecf20Sopenharmony_ci}
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------------ */
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_cistatic void msp_wake_thread(struct i2c_client *client)
3028c2ecf20Sopenharmony_ci{
3038c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(i2c_get_clientdata(client));
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci	if (NULL == state->kthread)
3068c2ecf20Sopenharmony_ci		return;
3078c2ecf20Sopenharmony_ci	state->watch_stereo = 0;
3088c2ecf20Sopenharmony_ci	state->restart = 1;
3098c2ecf20Sopenharmony_ci	wake_up_interruptible(&state->wq);
3108c2ecf20Sopenharmony_ci}
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ciint msp_sleep(struct msp_state *state, int timeout)
3138c2ecf20Sopenharmony_ci{
3148c2ecf20Sopenharmony_ci	DECLARE_WAITQUEUE(wait, current);
3158c2ecf20Sopenharmony_ci
3168c2ecf20Sopenharmony_ci	add_wait_queue(&state->wq, &wait);
3178c2ecf20Sopenharmony_ci	if (!kthread_should_stop()) {
3188c2ecf20Sopenharmony_ci		if (timeout < 0) {
3198c2ecf20Sopenharmony_ci			set_current_state(TASK_INTERRUPTIBLE);
3208c2ecf20Sopenharmony_ci			schedule();
3218c2ecf20Sopenharmony_ci		} else {
3228c2ecf20Sopenharmony_ci			schedule_timeout_interruptible
3238c2ecf20Sopenharmony_ci						(msecs_to_jiffies(timeout));
3248c2ecf20Sopenharmony_ci		}
3258c2ecf20Sopenharmony_ci	}
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	remove_wait_queue(&state->wq, &wait);
3288c2ecf20Sopenharmony_ci	try_to_freeze();
3298c2ecf20Sopenharmony_ci	return state->restart;
3308c2ecf20Sopenharmony_ci}
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci/* ------------------------------------------------------------------------ */
3338c2ecf20Sopenharmony_ci
3348c2ecf20Sopenharmony_cistatic int msp_s_ctrl(struct v4l2_ctrl *ctrl)
3358c2ecf20Sopenharmony_ci{
3368c2ecf20Sopenharmony_ci	struct msp_state *state = ctrl_to_state(ctrl);
3378c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(&state->sd);
3388c2ecf20Sopenharmony_ci	int val = ctrl->val;
3398c2ecf20Sopenharmony_ci
3408c2ecf20Sopenharmony_ci	switch (ctrl->id) {
3418c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_VOLUME: {
3428c2ecf20Sopenharmony_ci		/* audio volume cluster */
3438c2ecf20Sopenharmony_ci		int reallymuted = state->muted->val | state->scan_in_progress;
3448c2ecf20Sopenharmony_ci
3458c2ecf20Sopenharmony_ci		if (!reallymuted)
3468c2ecf20Sopenharmony_ci			val = (val * 0x7f / 65535) << 8;
3478c2ecf20Sopenharmony_ci
3488c2ecf20Sopenharmony_ci		dev_dbg_lvl(&client->dev, 1, msp_debug, "mute=%s scanning=%s volume=%d\n",
3498c2ecf20Sopenharmony_ci				state->muted->val ? "on" : "off",
3508c2ecf20Sopenharmony_ci				state->scan_in_progress ? "yes" : "no",
3518c2ecf20Sopenharmony_ci				state->volume->val);
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci		msp_write_dsp(client, 0x0000, val);
3548c2ecf20Sopenharmony_ci		msp_write_dsp(client, 0x0007, reallymuted ? 0x1 : (val | 0x1));
3558c2ecf20Sopenharmony_ci		if (state->has_scart2_out_volume)
3568c2ecf20Sopenharmony_ci			msp_write_dsp(client, 0x0040, reallymuted ? 0x1 : (val | 0x1));
3578c2ecf20Sopenharmony_ci		if (state->has_headphones)
3588c2ecf20Sopenharmony_ci			msp_write_dsp(client, 0x0006, val);
3598c2ecf20Sopenharmony_ci		break;
3608c2ecf20Sopenharmony_ci	}
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_BASS:
3638c2ecf20Sopenharmony_ci		val = ((val - 32768) * 0x60 / 65535) << 8;
3648c2ecf20Sopenharmony_ci		msp_write_dsp(client, 0x0002, val);
3658c2ecf20Sopenharmony_ci		if (state->has_headphones)
3668c2ecf20Sopenharmony_ci			msp_write_dsp(client, 0x0031, val);
3678c2ecf20Sopenharmony_ci		break;
3688c2ecf20Sopenharmony_ci
3698c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_TREBLE:
3708c2ecf20Sopenharmony_ci		val = ((val - 32768) * 0x60 / 65535) << 8;
3718c2ecf20Sopenharmony_ci		msp_write_dsp(client, 0x0003, val);
3728c2ecf20Sopenharmony_ci		if (state->has_headphones)
3738c2ecf20Sopenharmony_ci			msp_write_dsp(client, 0x0032, val);
3748c2ecf20Sopenharmony_ci		break;
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_LOUDNESS:
3778c2ecf20Sopenharmony_ci		val = val ? ((5 * 4) << 8) : 0;
3788c2ecf20Sopenharmony_ci		msp_write_dsp(client, 0x0004, val);
3798c2ecf20Sopenharmony_ci		if (state->has_headphones)
3808c2ecf20Sopenharmony_ci			msp_write_dsp(client, 0x0033, val);
3818c2ecf20Sopenharmony_ci		break;
3828c2ecf20Sopenharmony_ci
3838c2ecf20Sopenharmony_ci	case V4L2_CID_AUDIO_BALANCE:
3848c2ecf20Sopenharmony_ci		val = (u8)((val / 256) - 128);
3858c2ecf20Sopenharmony_ci		msp_write_dsp(client, 0x0001, val << 8);
3868c2ecf20Sopenharmony_ci		if (state->has_headphones)
3878c2ecf20Sopenharmony_ci			msp_write_dsp(client, 0x0030, val << 8);
3888c2ecf20Sopenharmony_ci		break;
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	default:
3918c2ecf20Sopenharmony_ci		return -EINVAL;
3928c2ecf20Sopenharmony_ci	}
3938c2ecf20Sopenharmony_ci	return 0;
3948c2ecf20Sopenharmony_ci}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_civoid msp_update_volume(struct msp_state *state)
3978c2ecf20Sopenharmony_ci{
3988c2ecf20Sopenharmony_ci	/* Force an update of the volume/mute cluster */
3998c2ecf20Sopenharmony_ci	v4l2_ctrl_lock(state->volume);
4008c2ecf20Sopenharmony_ci	state->volume->val = state->volume->cur.val;
4018c2ecf20Sopenharmony_ci	state->muted->val = state->muted->cur.val;
4028c2ecf20Sopenharmony_ci	msp_s_ctrl(state->volume);
4038c2ecf20Sopenharmony_ci	v4l2_ctrl_unlock(state->volume);
4048c2ecf20Sopenharmony_ci}
4058c2ecf20Sopenharmony_ci
4068c2ecf20Sopenharmony_ci/* --- v4l2 ioctls --- */
4078c2ecf20Sopenharmony_cistatic int msp_s_radio(struct v4l2_subdev *sd)
4088c2ecf20Sopenharmony_ci{
4098c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
4108c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	if (state->radio)
4138c2ecf20Sopenharmony_ci		return 0;
4148c2ecf20Sopenharmony_ci	state->radio = 1;
4158c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 1, msp_debug, "switching to radio mode\n");
4168c2ecf20Sopenharmony_ci	state->watch_stereo = 0;
4178c2ecf20Sopenharmony_ci	switch (state->opmode) {
4188c2ecf20Sopenharmony_ci	case OPMODE_MANUAL:
4198c2ecf20Sopenharmony_ci		/* set msp3400 to FM radio mode */
4208c2ecf20Sopenharmony_ci		msp3400c_set_mode(client, MSP_MODE_FM_RADIO);
4218c2ecf20Sopenharmony_ci		msp3400c_set_carrier(client, MSP_CARRIER(10.7),
4228c2ecf20Sopenharmony_ci				MSP_CARRIER(10.7));
4238c2ecf20Sopenharmony_ci		msp_update_volume(state);
4248c2ecf20Sopenharmony_ci		break;
4258c2ecf20Sopenharmony_ci	case OPMODE_AUTODETECT:
4268c2ecf20Sopenharmony_ci	case OPMODE_AUTOSELECT:
4278c2ecf20Sopenharmony_ci		/* the thread will do for us */
4288c2ecf20Sopenharmony_ci		msp_wake_thread(client);
4298c2ecf20Sopenharmony_ci		break;
4308c2ecf20Sopenharmony_ci	}
4318c2ecf20Sopenharmony_ci	return 0;
4328c2ecf20Sopenharmony_ci}
4338c2ecf20Sopenharmony_ci
4348c2ecf20Sopenharmony_cistatic int msp_s_frequency(struct v4l2_subdev *sd, const struct v4l2_frequency *freq)
4358c2ecf20Sopenharmony_ci{
4368c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci	/* new channel -- kick audio carrier scan */
4398c2ecf20Sopenharmony_ci	msp_wake_thread(client);
4408c2ecf20Sopenharmony_ci	return 0;
4418c2ecf20Sopenharmony_ci}
4428c2ecf20Sopenharmony_ci
4438c2ecf20Sopenharmony_cistatic int msp_querystd(struct v4l2_subdev *sd, v4l2_std_id *id)
4448c2ecf20Sopenharmony_ci{
4458c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
4468c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ci	*id &= state->detected_std;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 2, msp_debug,
4518c2ecf20Sopenharmony_ci		"detected standard: %s(0x%08Lx)\n",
4528c2ecf20Sopenharmony_ci		msp_standard_std_name(state->std), state->detected_std);
4538c2ecf20Sopenharmony_ci
4548c2ecf20Sopenharmony_ci	return 0;
4558c2ecf20Sopenharmony_ci}
4568c2ecf20Sopenharmony_ci
4578c2ecf20Sopenharmony_cistatic int msp_s_std(struct v4l2_subdev *sd, v4l2_std_id id)
4588c2ecf20Sopenharmony_ci{
4598c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
4608c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
4618c2ecf20Sopenharmony_ci	int update = state->radio || state->v4l2_std != id;
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_ci	state->v4l2_std = id;
4648c2ecf20Sopenharmony_ci	state->radio = 0;
4658c2ecf20Sopenharmony_ci	if (update)
4668c2ecf20Sopenharmony_ci		msp_wake_thread(client);
4678c2ecf20Sopenharmony_ci	return 0;
4688c2ecf20Sopenharmony_ci}
4698c2ecf20Sopenharmony_ci
4708c2ecf20Sopenharmony_cistatic int msp_s_routing(struct v4l2_subdev *sd,
4718c2ecf20Sopenharmony_ci			 u32 input, u32 output, u32 config)
4728c2ecf20Sopenharmony_ci{
4738c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
4748c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
4758c2ecf20Sopenharmony_ci	int tuner = (input >> 3) & 1;
4768c2ecf20Sopenharmony_ci	int sc_in = input & 0x7;
4778c2ecf20Sopenharmony_ci	int sc1_out = output & 0xf;
4788c2ecf20Sopenharmony_ci	int sc2_out = (output >> 4) & 0xf;
4798c2ecf20Sopenharmony_ci	u16 val, reg;
4808c2ecf20Sopenharmony_ci	int i;
4818c2ecf20Sopenharmony_ci	int extern_input = 1;
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	if (state->route_in == input && state->route_out == output)
4848c2ecf20Sopenharmony_ci		return 0;
4858c2ecf20Sopenharmony_ci	state->route_in = input;
4868c2ecf20Sopenharmony_ci	state->route_out = output;
4878c2ecf20Sopenharmony_ci	/* check if the tuner input is used */
4888c2ecf20Sopenharmony_ci	for (i = 0; i < 5; i++) {
4898c2ecf20Sopenharmony_ci		if (((input >> (4 + i * 4)) & 0xf) == 0)
4908c2ecf20Sopenharmony_ci			extern_input = 0;
4918c2ecf20Sopenharmony_ci	}
4928c2ecf20Sopenharmony_ci	state->mode = extern_input ? MSP_MODE_EXTERN : MSP_MODE_AM_DETECT;
4938c2ecf20Sopenharmony_ci	state->rxsubchans = V4L2_TUNER_SUB_STEREO;
4948c2ecf20Sopenharmony_ci	msp_set_scart(client, sc_in, 0);
4958c2ecf20Sopenharmony_ci	msp_set_scart(client, sc1_out, 1);
4968c2ecf20Sopenharmony_ci	msp_set_scart(client, sc2_out, 2);
4978c2ecf20Sopenharmony_ci	msp_set_audmode(client);
4988c2ecf20Sopenharmony_ci	reg = (state->opmode == OPMODE_AUTOSELECT) ? 0x30 : 0xbb;
4998c2ecf20Sopenharmony_ci	val = msp_read_dem(client, reg);
5008c2ecf20Sopenharmony_ci	msp_write_dem(client, reg, (val & ~0x100) | (tuner << 8));
5018c2ecf20Sopenharmony_ci	/* wake thread when a new input is chosen */
5028c2ecf20Sopenharmony_ci	msp_wake_thread(client);
5038c2ecf20Sopenharmony_ci	return 0;
5048c2ecf20Sopenharmony_ci}
5058c2ecf20Sopenharmony_ci
5068c2ecf20Sopenharmony_cistatic int msp_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt)
5078c2ecf20Sopenharmony_ci{
5088c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
5098c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_ci	if (vt->type != V4L2_TUNER_ANALOG_TV)
5128c2ecf20Sopenharmony_ci		return 0;
5138c2ecf20Sopenharmony_ci	if (!state->radio) {
5148c2ecf20Sopenharmony_ci		if (state->opmode == OPMODE_AUTOSELECT)
5158c2ecf20Sopenharmony_ci			msp_detect_stereo(client);
5168c2ecf20Sopenharmony_ci		vt->rxsubchans = state->rxsubchans;
5178c2ecf20Sopenharmony_ci	}
5188c2ecf20Sopenharmony_ci	vt->audmode = state->audmode;
5198c2ecf20Sopenharmony_ci	vt->capability |= V4L2_TUNER_CAP_STEREO |
5208c2ecf20Sopenharmony_ci		V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2;
5218c2ecf20Sopenharmony_ci	return 0;
5228c2ecf20Sopenharmony_ci}
5238c2ecf20Sopenharmony_ci
5248c2ecf20Sopenharmony_cistatic int msp_s_tuner(struct v4l2_subdev *sd, const struct v4l2_tuner *vt)
5258c2ecf20Sopenharmony_ci{
5268c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
5278c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
5288c2ecf20Sopenharmony_ci
5298c2ecf20Sopenharmony_ci	if (state->radio)  /* TODO: add mono/stereo support for radio */
5308c2ecf20Sopenharmony_ci		return 0;
5318c2ecf20Sopenharmony_ci	if (state->audmode == vt->audmode)
5328c2ecf20Sopenharmony_ci		return 0;
5338c2ecf20Sopenharmony_ci	state->audmode = vt->audmode;
5348c2ecf20Sopenharmony_ci	/* only set audmode */
5358c2ecf20Sopenharmony_ci	msp_set_audmode(client);
5368c2ecf20Sopenharmony_ci	return 0;
5378c2ecf20Sopenharmony_ci}
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_cistatic int msp_s_i2s_clock_freq(struct v4l2_subdev *sd, u32 freq)
5408c2ecf20Sopenharmony_ci{
5418c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
5428c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
5438c2ecf20Sopenharmony_ci
5448c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 1, msp_debug, "Setting I2S speed to %d\n", freq);
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	switch (freq) {
5478c2ecf20Sopenharmony_ci		case 1024000:
5488c2ecf20Sopenharmony_ci			state->i2s_mode = 0;
5498c2ecf20Sopenharmony_ci			break;
5508c2ecf20Sopenharmony_ci		case 2048000:
5518c2ecf20Sopenharmony_ci			state->i2s_mode = 1;
5528c2ecf20Sopenharmony_ci			break;
5538c2ecf20Sopenharmony_ci		default:
5548c2ecf20Sopenharmony_ci			return -EINVAL;
5558c2ecf20Sopenharmony_ci	}
5568c2ecf20Sopenharmony_ci	return 0;
5578c2ecf20Sopenharmony_ci}
5588c2ecf20Sopenharmony_ci
5598c2ecf20Sopenharmony_cistatic int msp_log_status(struct v4l2_subdev *sd)
5608c2ecf20Sopenharmony_ci{
5618c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(sd);
5628c2ecf20Sopenharmony_ci	struct i2c_client *client = v4l2_get_subdevdata(sd);
5638c2ecf20Sopenharmony_ci	const char *p;
5648c2ecf20Sopenharmony_ci	char prefix[V4L2_SUBDEV_NAME_SIZE + 20];
5658c2ecf20Sopenharmony_ci
5668c2ecf20Sopenharmony_ci	if (state->opmode == OPMODE_AUTOSELECT)
5678c2ecf20Sopenharmony_ci		msp_detect_stereo(client);
5688c2ecf20Sopenharmony_ci	dev_info(&client->dev, "%s rev1 = 0x%04x rev2 = 0x%04x\n",
5698c2ecf20Sopenharmony_ci			client->name, state->rev1, state->rev2);
5708c2ecf20Sopenharmony_ci	snprintf(prefix, sizeof(prefix), "%s: Audio:    ", sd->name);
5718c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_log_status(&state->hdl, prefix);
5728c2ecf20Sopenharmony_ci	switch (state->mode) {
5738c2ecf20Sopenharmony_ci		case MSP_MODE_AM_DETECT: p = "AM (for carrier detect)"; break;
5748c2ecf20Sopenharmony_ci		case MSP_MODE_FM_RADIO: p = "FM Radio"; break;
5758c2ecf20Sopenharmony_ci		case MSP_MODE_FM_TERRA: p = "Terrestrial FM-mono/stereo"; break;
5768c2ecf20Sopenharmony_ci		case MSP_MODE_FM_SAT: p = "Satellite FM-mono"; break;
5778c2ecf20Sopenharmony_ci		case MSP_MODE_FM_NICAM1: p = "NICAM/FM (B/G, D/K)"; break;
5788c2ecf20Sopenharmony_ci		case MSP_MODE_FM_NICAM2: p = "NICAM/FM (I)"; break;
5798c2ecf20Sopenharmony_ci		case MSP_MODE_AM_NICAM: p = "NICAM/AM (L)"; break;
5808c2ecf20Sopenharmony_ci		case MSP_MODE_BTSC: p = "BTSC"; break;
5818c2ecf20Sopenharmony_ci		case MSP_MODE_EXTERN: p = "External input"; break;
5828c2ecf20Sopenharmony_ci		default: p = "unknown"; break;
5838c2ecf20Sopenharmony_ci	}
5848c2ecf20Sopenharmony_ci	if (state->mode == MSP_MODE_EXTERN) {
5858c2ecf20Sopenharmony_ci		dev_info(&client->dev, "Mode:     %s\n", p);
5868c2ecf20Sopenharmony_ci	} else if (state->opmode == OPMODE_MANUAL) {
5878c2ecf20Sopenharmony_ci		dev_info(&client->dev, "Mode:     %s (%s%s)\n", p,
5888c2ecf20Sopenharmony_ci				(state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono",
5898c2ecf20Sopenharmony_ci				(state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : "");
5908c2ecf20Sopenharmony_ci	} else {
5918c2ecf20Sopenharmony_ci		if (state->opmode == OPMODE_AUTODETECT)
5928c2ecf20Sopenharmony_ci			dev_info(&client->dev, "Mode:     %s\n", p);
5938c2ecf20Sopenharmony_ci		dev_info(&client->dev, "Standard: %s (%s%s)\n",
5948c2ecf20Sopenharmony_ci				msp_standard_std_name(state->std),
5958c2ecf20Sopenharmony_ci				(state->rxsubchans & V4L2_TUNER_SUB_STEREO) ? "stereo" : "mono",
5968c2ecf20Sopenharmony_ci				(state->rxsubchans & V4L2_TUNER_SUB_LANG2) ? ", dual" : "");
5978c2ecf20Sopenharmony_ci	}
5988c2ecf20Sopenharmony_ci	dev_info(&client->dev, "Audmode:  0x%04x\n", state->audmode);
5998c2ecf20Sopenharmony_ci	dev_info(&client->dev, "Routing:  0x%08x (input) 0x%08x (output)\n",
6008c2ecf20Sopenharmony_ci			state->route_in, state->route_out);
6018c2ecf20Sopenharmony_ci	dev_info(&client->dev, "ACB:      0x%04x\n", state->acb);
6028c2ecf20Sopenharmony_ci	return 0;
6038c2ecf20Sopenharmony_ci}
6048c2ecf20Sopenharmony_ci
6058c2ecf20Sopenharmony_ci#ifdef CONFIG_PM_SLEEP
6068c2ecf20Sopenharmony_cistatic int msp_suspend(struct device *dev)
6078c2ecf20Sopenharmony_ci{
6088c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
6098c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 1, msp_debug, "suspend\n");
6108c2ecf20Sopenharmony_ci	msp_reset(client);
6118c2ecf20Sopenharmony_ci	return 0;
6128c2ecf20Sopenharmony_ci}
6138c2ecf20Sopenharmony_ci
6148c2ecf20Sopenharmony_cistatic int msp_resume(struct device *dev)
6158c2ecf20Sopenharmony_ci{
6168c2ecf20Sopenharmony_ci	struct i2c_client *client = to_i2c_client(dev);
6178c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 1, msp_debug, "resume\n");
6188c2ecf20Sopenharmony_ci	msp_wake_thread(client);
6198c2ecf20Sopenharmony_ci	return 0;
6208c2ecf20Sopenharmony_ci}
6218c2ecf20Sopenharmony_ci#endif
6228c2ecf20Sopenharmony_ci
6238c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
6248c2ecf20Sopenharmony_ci
6258c2ecf20Sopenharmony_cistatic const struct v4l2_ctrl_ops msp_ctrl_ops = {
6268c2ecf20Sopenharmony_ci	.s_ctrl = msp_s_ctrl,
6278c2ecf20Sopenharmony_ci};
6288c2ecf20Sopenharmony_ci
6298c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_core_ops msp_core_ops = {
6308c2ecf20Sopenharmony_ci	.log_status = msp_log_status,
6318c2ecf20Sopenharmony_ci};
6328c2ecf20Sopenharmony_ci
6338c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_video_ops msp_video_ops = {
6348c2ecf20Sopenharmony_ci	.s_std = msp_s_std,
6358c2ecf20Sopenharmony_ci	.querystd = msp_querystd,
6368c2ecf20Sopenharmony_ci};
6378c2ecf20Sopenharmony_ci
6388c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_tuner_ops msp_tuner_ops = {
6398c2ecf20Sopenharmony_ci	.s_frequency = msp_s_frequency,
6408c2ecf20Sopenharmony_ci	.g_tuner = msp_g_tuner,
6418c2ecf20Sopenharmony_ci	.s_tuner = msp_s_tuner,
6428c2ecf20Sopenharmony_ci	.s_radio = msp_s_radio,
6438c2ecf20Sopenharmony_ci};
6448c2ecf20Sopenharmony_ci
6458c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_audio_ops msp_audio_ops = {
6468c2ecf20Sopenharmony_ci	.s_routing = msp_s_routing,
6478c2ecf20Sopenharmony_ci	.s_i2s_clock_freq = msp_s_i2s_clock_freq,
6488c2ecf20Sopenharmony_ci};
6498c2ecf20Sopenharmony_ci
6508c2ecf20Sopenharmony_cistatic const struct v4l2_subdev_ops msp_ops = {
6518c2ecf20Sopenharmony_ci	.core = &msp_core_ops,
6528c2ecf20Sopenharmony_ci	.video = &msp_video_ops,
6538c2ecf20Sopenharmony_ci	.tuner = &msp_tuner_ops,
6548c2ecf20Sopenharmony_ci	.audio = &msp_audio_ops,
6558c2ecf20Sopenharmony_ci};
6568c2ecf20Sopenharmony_ci
6578c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
6588c2ecf20Sopenharmony_ci
6598c2ecf20Sopenharmony_ci
6608c2ecf20Sopenharmony_cistatic const char * const opmode_str[] = {
6618c2ecf20Sopenharmony_ci	[OPMODE_MANUAL] = "manual",
6628c2ecf20Sopenharmony_ci	[OPMODE_AUTODETECT] = "autodetect",
6638c2ecf20Sopenharmony_ci	[OPMODE_AUTOSELECT] = "autodetect and autoselect",
6648c2ecf20Sopenharmony_ci};
6658c2ecf20Sopenharmony_ci
6668c2ecf20Sopenharmony_cistatic int msp_probe(struct i2c_client *client, const struct i2c_device_id *id)
6678c2ecf20Sopenharmony_ci{
6688c2ecf20Sopenharmony_ci	struct msp_state *state;
6698c2ecf20Sopenharmony_ci	struct v4l2_subdev *sd;
6708c2ecf20Sopenharmony_ci	struct v4l2_ctrl_handler *hdl;
6718c2ecf20Sopenharmony_ci	int (*thread_func)(void *data) = NULL;
6728c2ecf20Sopenharmony_ci	int msp_hard;
6738c2ecf20Sopenharmony_ci	int msp_family;
6748c2ecf20Sopenharmony_ci	int msp_revision;
6758c2ecf20Sopenharmony_ci	int msp_product, msp_prod_hi, msp_prod_lo;
6768c2ecf20Sopenharmony_ci	int msp_rom;
6778c2ecf20Sopenharmony_ci#if defined(CONFIG_MEDIA_CONTROLLER)
6788c2ecf20Sopenharmony_ci	int ret;
6798c2ecf20Sopenharmony_ci#endif
6808c2ecf20Sopenharmony_ci
6818c2ecf20Sopenharmony_ci	if (!id)
6828c2ecf20Sopenharmony_ci		strscpy(client->name, "msp3400", sizeof(client->name));
6838c2ecf20Sopenharmony_ci
6848c2ecf20Sopenharmony_ci	if (msp_reset(client) == -1) {
6858c2ecf20Sopenharmony_ci		dev_dbg_lvl(&client->dev, 1, msp_debug, "msp3400 not found\n");
6868c2ecf20Sopenharmony_ci		return -ENODEV;
6878c2ecf20Sopenharmony_ci	}
6888c2ecf20Sopenharmony_ci
6898c2ecf20Sopenharmony_ci	state = devm_kzalloc(&client->dev, sizeof(*state), GFP_KERNEL);
6908c2ecf20Sopenharmony_ci	if (!state)
6918c2ecf20Sopenharmony_ci		return -ENOMEM;
6928c2ecf20Sopenharmony_ci
6938c2ecf20Sopenharmony_ci	sd = &state->sd;
6948c2ecf20Sopenharmony_ci	v4l2_i2c_subdev_init(sd, client, &msp_ops);
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci#if defined(CONFIG_MEDIA_CONTROLLER)
6978c2ecf20Sopenharmony_ci	state->pads[MSP3400_PAD_IF_INPUT].flags = MEDIA_PAD_FL_SINK;
6988c2ecf20Sopenharmony_ci	state->pads[MSP3400_PAD_IF_INPUT].sig_type = PAD_SIGNAL_AUDIO;
6998c2ecf20Sopenharmony_ci	state->pads[MSP3400_PAD_OUT].flags = MEDIA_PAD_FL_SOURCE;
7008c2ecf20Sopenharmony_ci	state->pads[MSP3400_PAD_OUT].sig_type = PAD_SIGNAL_AUDIO;
7018c2ecf20Sopenharmony_ci
7028c2ecf20Sopenharmony_ci	sd->entity.function = MEDIA_ENT_F_IF_AUD_DECODER;
7038c2ecf20Sopenharmony_ci
7048c2ecf20Sopenharmony_ci	ret = media_entity_pads_init(&sd->entity, 2, state->pads);
7058c2ecf20Sopenharmony_ci	if (ret < 0)
7068c2ecf20Sopenharmony_ci		return ret;
7078c2ecf20Sopenharmony_ci#endif
7088c2ecf20Sopenharmony_ci
7098c2ecf20Sopenharmony_ci	state->v4l2_std = V4L2_STD_NTSC;
7108c2ecf20Sopenharmony_ci	state->detected_std = V4L2_STD_ALL;
7118c2ecf20Sopenharmony_ci	state->audmode = V4L2_TUNER_MODE_STEREO;
7128c2ecf20Sopenharmony_ci	state->input = -1;
7138c2ecf20Sopenharmony_ci	state->i2s_mode = 0;
7148c2ecf20Sopenharmony_ci	init_waitqueue_head(&state->wq);
7158c2ecf20Sopenharmony_ci	/* These are the reset input/output positions */
7168c2ecf20Sopenharmony_ci	state->route_in = MSP_INPUT_DEFAULT;
7178c2ecf20Sopenharmony_ci	state->route_out = MSP_OUTPUT_DEFAULT;
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_ci	state->rev1 = msp_read_dsp(client, 0x1e);
7208c2ecf20Sopenharmony_ci	if (state->rev1 != -1)
7218c2ecf20Sopenharmony_ci		state->rev2 = msp_read_dsp(client, 0x1f);
7228c2ecf20Sopenharmony_ci	dev_dbg_lvl(&client->dev, 1, msp_debug, "rev1=0x%04x, rev2=0x%04x\n",
7238c2ecf20Sopenharmony_ci			state->rev1, state->rev2);
7248c2ecf20Sopenharmony_ci	if (state->rev1 == -1 || (state->rev1 == 0 && state->rev2 == 0)) {
7258c2ecf20Sopenharmony_ci		dev_dbg_lvl(&client->dev, 1, msp_debug,
7268c2ecf20Sopenharmony_ci				"not an msp3400 (cannot read chip version)\n");
7278c2ecf20Sopenharmony_ci		return -ENODEV;
7288c2ecf20Sopenharmony_ci	}
7298c2ecf20Sopenharmony_ci
7308c2ecf20Sopenharmony_ci	msp_family = ((state->rev1 >> 4) & 0x0f) + 3;
7318c2ecf20Sopenharmony_ci	msp_product = (state->rev2 >> 8) & 0xff;
7328c2ecf20Sopenharmony_ci	msp_prod_hi = msp_product / 10;
7338c2ecf20Sopenharmony_ci	msp_prod_lo = msp_product % 10;
7348c2ecf20Sopenharmony_ci	msp_revision = (state->rev1 & 0x0f) + '@';
7358c2ecf20Sopenharmony_ci	msp_hard = ((state->rev1 >> 8) & 0xff) + '@';
7368c2ecf20Sopenharmony_ci	msp_rom = state->rev2 & 0x1f;
7378c2ecf20Sopenharmony_ci	/* Rev B=2, C=3, D=4, G=7 */
7388c2ecf20Sopenharmony_ci	state->ident = msp_family * 10000 + 4000 + msp_product * 10 +
7398c2ecf20Sopenharmony_ci			msp_revision - '@';
7408c2ecf20Sopenharmony_ci
7418c2ecf20Sopenharmony_ci	/* Has NICAM support: all mspx41x and mspx45x products have NICAM */
7428c2ecf20Sopenharmony_ci	state->has_nicam =
7438c2ecf20Sopenharmony_ci		msp_prod_hi == 1 || msp_prod_hi == 5;
7448c2ecf20Sopenharmony_ci	/* Has radio support: was added with revision G */
7458c2ecf20Sopenharmony_ci	state->has_radio =
7468c2ecf20Sopenharmony_ci		msp_revision >= 'G';
7478c2ecf20Sopenharmony_ci	/* Has headphones output: not for stripped down products */
7488c2ecf20Sopenharmony_ci	state->has_headphones =
7498c2ecf20Sopenharmony_ci		msp_prod_lo < 5;
7508c2ecf20Sopenharmony_ci	/* Has scart2 input: not in stripped down products of the '3' family */
7518c2ecf20Sopenharmony_ci	state->has_scart2 =
7528c2ecf20Sopenharmony_ci		msp_family >= 4 || msp_prod_lo < 7;
7538c2ecf20Sopenharmony_ci	/* Has scart3 input: not in stripped down products of the '3' family */
7548c2ecf20Sopenharmony_ci	state->has_scart3 =
7558c2ecf20Sopenharmony_ci		msp_family >= 4 || msp_prod_lo < 5;
7568c2ecf20Sopenharmony_ci	/* Has scart4 input: not in pre D revisions, not in stripped D revs */
7578c2ecf20Sopenharmony_ci	state->has_scart4 =
7588c2ecf20Sopenharmony_ci		msp_family >= 4 || (msp_revision >= 'D' && msp_prod_lo < 5);
7598c2ecf20Sopenharmony_ci	/* Has scart2 output: not in stripped down products of
7608c2ecf20Sopenharmony_ci	 * the '3' family */
7618c2ecf20Sopenharmony_ci	state->has_scart2_out =
7628c2ecf20Sopenharmony_ci		msp_family >= 4 || msp_prod_lo < 5;
7638c2ecf20Sopenharmony_ci	/* Has scart2 a volume control? Not in pre-D revisions. */
7648c2ecf20Sopenharmony_ci	state->has_scart2_out_volume =
7658c2ecf20Sopenharmony_ci		msp_revision > 'C' && state->has_scart2_out;
7668c2ecf20Sopenharmony_ci	/* Has a configurable i2s out? */
7678c2ecf20Sopenharmony_ci	state->has_i2s_conf =
7688c2ecf20Sopenharmony_ci		msp_revision >= 'G' && msp_prod_lo < 7;
7698c2ecf20Sopenharmony_ci	/* Has subwoofer output: not in pre-D revs and not in stripped down
7708c2ecf20Sopenharmony_ci	 * products */
7718c2ecf20Sopenharmony_ci	state->has_subwoofer =
7728c2ecf20Sopenharmony_ci		msp_revision >= 'D' && msp_prod_lo < 5;
7738c2ecf20Sopenharmony_ci	/* Has soundprocessing (bass/treble/balance/loudness/equalizer):
7748c2ecf20Sopenharmony_ci	 *  not in stripped down products */
7758c2ecf20Sopenharmony_ci	state->has_sound_processing =
7768c2ecf20Sopenharmony_ci		msp_prod_lo < 7;
7778c2ecf20Sopenharmony_ci	/* Has Virtual Dolby Surround: only in msp34x1 */
7788c2ecf20Sopenharmony_ci	state->has_virtual_dolby_surround =
7798c2ecf20Sopenharmony_ci		msp_revision == 'G' && msp_prod_lo == 1;
7808c2ecf20Sopenharmony_ci	/* Has Virtual Dolby Surround & Dolby Pro Logic: only in msp34x2 */
7818c2ecf20Sopenharmony_ci	state->has_dolby_pro_logic =
7828c2ecf20Sopenharmony_ci		msp_revision == 'G' && msp_prod_lo == 2;
7838c2ecf20Sopenharmony_ci	/* The msp343xG supports BTSC only and cannot do Automatic Standard
7848c2ecf20Sopenharmony_ci	 * Detection. */
7858c2ecf20Sopenharmony_ci	state->force_btsc =
7868c2ecf20Sopenharmony_ci		msp_family == 3 && msp_revision == 'G' && msp_prod_hi == 3;
7878c2ecf20Sopenharmony_ci
7888c2ecf20Sopenharmony_ci	state->opmode = opmode;
7898c2ecf20Sopenharmony_ci	if (state->opmode < OPMODE_MANUAL
7908c2ecf20Sopenharmony_ci	    || state->opmode > OPMODE_AUTOSELECT) {
7918c2ecf20Sopenharmony_ci		/* MSP revision G and up have both autodetect and autoselect */
7928c2ecf20Sopenharmony_ci		if (msp_revision >= 'G')
7938c2ecf20Sopenharmony_ci			state->opmode = OPMODE_AUTOSELECT;
7948c2ecf20Sopenharmony_ci		/* MSP revision D and up have autodetect */
7958c2ecf20Sopenharmony_ci		else if (msp_revision >= 'D')
7968c2ecf20Sopenharmony_ci			state->opmode = OPMODE_AUTODETECT;
7978c2ecf20Sopenharmony_ci		else
7988c2ecf20Sopenharmony_ci			state->opmode = OPMODE_MANUAL;
7998c2ecf20Sopenharmony_ci	}
8008c2ecf20Sopenharmony_ci
8018c2ecf20Sopenharmony_ci	hdl = &state->hdl;
8028c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_init(hdl, 6);
8038c2ecf20Sopenharmony_ci	if (state->has_sound_processing) {
8048c2ecf20Sopenharmony_ci		v4l2_ctrl_new_std(hdl, &msp_ctrl_ops,
8058c2ecf20Sopenharmony_ci			V4L2_CID_AUDIO_BASS, 0, 65535, 65535 / 100, 32768);
8068c2ecf20Sopenharmony_ci		v4l2_ctrl_new_std(hdl, &msp_ctrl_ops,
8078c2ecf20Sopenharmony_ci			V4L2_CID_AUDIO_TREBLE, 0, 65535, 65535 / 100, 32768);
8088c2ecf20Sopenharmony_ci		v4l2_ctrl_new_std(hdl, &msp_ctrl_ops,
8098c2ecf20Sopenharmony_ci			V4L2_CID_AUDIO_LOUDNESS, 0, 1, 1, 0);
8108c2ecf20Sopenharmony_ci	}
8118c2ecf20Sopenharmony_ci	state->volume = v4l2_ctrl_new_std(hdl, &msp_ctrl_ops,
8128c2ecf20Sopenharmony_ci			V4L2_CID_AUDIO_VOLUME, 0, 65535, 65535 / 100, 58880);
8138c2ecf20Sopenharmony_ci	v4l2_ctrl_new_std(hdl, &msp_ctrl_ops,
8148c2ecf20Sopenharmony_ci			V4L2_CID_AUDIO_BALANCE, 0, 65535, 65535 / 100, 32768);
8158c2ecf20Sopenharmony_ci	state->muted = v4l2_ctrl_new_std(hdl, &msp_ctrl_ops,
8168c2ecf20Sopenharmony_ci			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0);
8178c2ecf20Sopenharmony_ci	sd->ctrl_handler = hdl;
8188c2ecf20Sopenharmony_ci	if (hdl->error) {
8198c2ecf20Sopenharmony_ci		int err = hdl->error;
8208c2ecf20Sopenharmony_ci
8218c2ecf20Sopenharmony_ci		v4l2_ctrl_handler_free(hdl);
8228c2ecf20Sopenharmony_ci		return err;
8238c2ecf20Sopenharmony_ci	}
8248c2ecf20Sopenharmony_ci
8258c2ecf20Sopenharmony_ci	v4l2_ctrl_cluster(2, &state->volume);
8268c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_setup(hdl);
8278c2ecf20Sopenharmony_ci
8288c2ecf20Sopenharmony_ci	dev_info(&client->dev,
8298c2ecf20Sopenharmony_ci		 "MSP%d4%02d%c-%c%d found on %s: supports %s%s%s, mode is %s\n",
8308c2ecf20Sopenharmony_ci		 msp_family, msp_product,
8318c2ecf20Sopenharmony_ci		 msp_revision, msp_hard, msp_rom,
8328c2ecf20Sopenharmony_ci		 client->adapter->name,
8338c2ecf20Sopenharmony_ci		 (state->has_nicam) ? "nicam" : "",
8348c2ecf20Sopenharmony_ci		 (state->has_nicam && state->has_radio) ? " and " : "",
8358c2ecf20Sopenharmony_ci		 (state->has_radio) ? "radio" : "",
8368c2ecf20Sopenharmony_ci		 opmode_str[state->opmode]);
8378c2ecf20Sopenharmony_ci
8388c2ecf20Sopenharmony_ci	/* version-specific initialization */
8398c2ecf20Sopenharmony_ci	switch (state->opmode) {
8408c2ecf20Sopenharmony_ci	case OPMODE_MANUAL:
8418c2ecf20Sopenharmony_ci		thread_func = msp3400c_thread;
8428c2ecf20Sopenharmony_ci		break;
8438c2ecf20Sopenharmony_ci	case OPMODE_AUTODETECT:
8448c2ecf20Sopenharmony_ci		thread_func = msp3410d_thread;
8458c2ecf20Sopenharmony_ci		break;
8468c2ecf20Sopenharmony_ci	case OPMODE_AUTOSELECT:
8478c2ecf20Sopenharmony_ci		thread_func = msp34xxg_thread;
8488c2ecf20Sopenharmony_ci		break;
8498c2ecf20Sopenharmony_ci	}
8508c2ecf20Sopenharmony_ci
8518c2ecf20Sopenharmony_ci	/* startup control thread if needed */
8528c2ecf20Sopenharmony_ci	if (thread_func) {
8538c2ecf20Sopenharmony_ci		state->kthread = kthread_run(thread_func, client, "msp34xx");
8548c2ecf20Sopenharmony_ci
8558c2ecf20Sopenharmony_ci		if (IS_ERR(state->kthread))
8568c2ecf20Sopenharmony_ci			dev_warn(&client->dev, "kernel_thread() failed\n");
8578c2ecf20Sopenharmony_ci		msp_wake_thread(client);
8588c2ecf20Sopenharmony_ci	}
8598c2ecf20Sopenharmony_ci	return 0;
8608c2ecf20Sopenharmony_ci}
8618c2ecf20Sopenharmony_ci
8628c2ecf20Sopenharmony_cistatic int msp_remove(struct i2c_client *client)
8638c2ecf20Sopenharmony_ci{
8648c2ecf20Sopenharmony_ci	struct msp_state *state = to_state(i2c_get_clientdata(client));
8658c2ecf20Sopenharmony_ci
8668c2ecf20Sopenharmony_ci	v4l2_device_unregister_subdev(&state->sd);
8678c2ecf20Sopenharmony_ci	/* shutdown control thread */
8688c2ecf20Sopenharmony_ci	if (state->kthread) {
8698c2ecf20Sopenharmony_ci		state->restart = 1;
8708c2ecf20Sopenharmony_ci		kthread_stop(state->kthread);
8718c2ecf20Sopenharmony_ci	}
8728c2ecf20Sopenharmony_ci	msp_reset(client);
8738c2ecf20Sopenharmony_ci
8748c2ecf20Sopenharmony_ci	v4l2_ctrl_handler_free(&state->hdl);
8758c2ecf20Sopenharmony_ci	return 0;
8768c2ecf20Sopenharmony_ci}
8778c2ecf20Sopenharmony_ci
8788c2ecf20Sopenharmony_ci/* ----------------------------------------------------------------------- */
8798c2ecf20Sopenharmony_ci
8808c2ecf20Sopenharmony_cistatic const struct dev_pm_ops msp3400_pm_ops = {
8818c2ecf20Sopenharmony_ci	SET_SYSTEM_SLEEP_PM_OPS(msp_suspend, msp_resume)
8828c2ecf20Sopenharmony_ci};
8838c2ecf20Sopenharmony_ci
8848c2ecf20Sopenharmony_cistatic const struct i2c_device_id msp_id[] = {
8858c2ecf20Sopenharmony_ci	{ "msp3400", 0 },
8868c2ecf20Sopenharmony_ci	{ }
8878c2ecf20Sopenharmony_ci};
8888c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(i2c, msp_id);
8898c2ecf20Sopenharmony_ci
8908c2ecf20Sopenharmony_cistatic struct i2c_driver msp_driver = {
8918c2ecf20Sopenharmony_ci	.driver = {
8928c2ecf20Sopenharmony_ci		.name	= "msp3400",
8938c2ecf20Sopenharmony_ci		.pm	= &msp3400_pm_ops,
8948c2ecf20Sopenharmony_ci	},
8958c2ecf20Sopenharmony_ci	.probe		= msp_probe,
8968c2ecf20Sopenharmony_ci	.remove		= msp_remove,
8978c2ecf20Sopenharmony_ci	.id_table	= msp_id,
8988c2ecf20Sopenharmony_ci};
8998c2ecf20Sopenharmony_ci
9008c2ecf20Sopenharmony_cimodule_i2c_driver(msp_driver);
901