// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2018, The Linux Foundation. All rights reserved.
 */
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <sound/core.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/tlv.h>
#include <sound/soc.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include "csra66x0.h"

#define DRV_NAME "csra66x0_codec"

/* CSRA66X0 register default values */
static struct reg_default csra66x0_reg_defaults[] = {
	{CSRA66X0_AUDIO_IF_RX_CONFIG1,           0x00},
	{CSRA66X0_AUDIO_IF_RX_CONFIG2,           0x0B},
	{CSRA66X0_AUDIO_IF_RX_CONFIG3,           0x0F},
	{CSRA66X0_AUDIO_IF_TX_EN,                0x00},
	{CSRA66X0_AUDIO_IF_TX_CONFIG1,           0x6B},
	{CSRA66X0_AUDIO_IF_TX_CONFIG2,           0x02},
	{CSRA66X0_I2C_DEVICE_ADDRESS,            0x0D},
	{CSRA66X0_CHIP_ID_FA,                    0x39},
	{CSRA66X0_ROM_VER_FA,                    0x08},
	{CSRA66X0_CHIP_REV_0_FA,                 0x05},
	{CSRA66X0_CHIP_REV_1_FA,                 0x03},
	{CSRA66X0_CH1_MIX_SEL,                   0x01},
	{CSRA66X0_CH2_MIX_SEL,                   0x10},
	{CSRA66X0_CH1_SAMPLE1_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE1_SCALE_1,           0x20},
	{CSRA66X0_CH1_SAMPLE3_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE3_SCALE_1,           0x20},
	{CSRA66X0_CH1_SAMPLE5_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE5_SCALE_1,           0x20},
	{CSRA66X0_CH1_SAMPLE7_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE7_SCALE_1,           0x20},
	{CSRA66X0_CH1_SAMPLE2_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE2_SCALE_1,           0x20},
	{CSRA66X0_CH1_SAMPLE4_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE4_SCALE_1,           0x20},
	{CSRA66X0_CH1_SAMPLE6_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE6_SCALE_1,           0x20},
	{CSRA66X0_CH1_SAMPLE8_SCALE_0,           0x00},
	{CSRA66X0_CH1_SAMPLE8_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE1_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE1_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE3_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE3_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE5_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE5_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE7_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE7_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE2_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE2_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE4_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE4_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE6_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE6_SCALE_1,           0x20},
	{CSRA66X0_CH2_SAMPLE8_SCALE_0,           0x00},
	{CSRA66X0_CH2_SAMPLE8_SCALE_1,           0x20},
	{CSRA66X0_VOLUME_CONFIG_FA,              0x26},
	{CSRA66X0_STARTUP_DELAY_FA,              0x00},
	{CSRA66X0_CH1_VOLUME_0_FA,               0x19},
	{CSRA66X0_CH1_VOLUME_1_FA,               0x01},
	{CSRA66X0_CH2_VOLUME_0_FA,               0x19},
	{CSRA66X0_CH2_VOLUME_1_FA,               0x01},
	{CSRA66X0_QUAD_ENC_COUNT_0_FA,           0x00},
	{CSRA66X0_QUAD_ENC_COUNT_1_FA,           0x00},
	{CSRA66X0_SOFT_CLIP_CONFIG,              0x00},
	{CSRA66X0_CH1_HARD_CLIP_THRESH,          0x00},
	{CSRA66X0_CH2_HARD_CLIP_THRESH,          0x00},
	{CSRA66X0_SOFT_CLIP_THRESH,              0x00},
	{CSRA66X0_DS_ENABLE_THRESH_0,            0x05},
	{CSRA66X0_DS_ENABLE_THRESH_1,            0x00},
	{CSRA66X0_DS_TARGET_COUNT_0,             0x00},
	{CSRA66X0_DS_TARGET_COUNT_1,             0xFF},
	{CSRA66X0_DS_TARGET_COUNT_2,             0xFF},
	{CSRA66X0_DS_DISABLE_THRESH_0,           0x0F},
	{CSRA66X0_DS_DISABLE_THRESH_1,           0x00},
	{CSRA66X0_DCA_CTRL,                      0x07},
	{CSRA66X0_CH1_DCA_THRESH,                0x40},
	{CSRA66X0_CH2_DCA_THRESH,                0x40},
	{CSRA66X0_DCA_ATTACK_RATE,               0x00},
	{CSRA66X0_DCA_RELEASE_RATE,              0x00},
	{CSRA66X0_CH1_OUTPUT_INVERT_EN,          0x00},
	{CSRA66X0_CH2_OUTPUT_INVERT_EN,          0x00},
	{CSRA66X0_CH1_176P4K_DELAY,              0x00},
	{CSRA66X0_CH2_176P4K_DELAY,              0x00},
	{CSRA66X0_CH1_192K_DELAY,                0x00},
	{CSRA66X0_CH2_192K_DELAY,                0x00},
	{CSRA66X0_DEEMP_CONFIG_FA,               0x00},
	{CSRA66X0_CH1_TREBLE_GAIN_CTRL_FA,       0x00},
	{CSRA66X0_CH2_TREBLE_GAIN_CTRL_FA,       0x00},
	{CSRA66X0_CH1_TREBLE_FC_CTRL_FA,         0x00},
	{CSRA66X0_CH2_TREBLE_FC_CTRL_FA,         0x00},
	{CSRA66X0_CH1_BASS_GAIN_CTRL_FA,         0x00},
	{CSRA66X0_CH2_BASS_GAIN_CTRL_FA,         0x00},
	{CSRA66X0_CH1_BASS_FC_CTRL_FA,           0x00},
	{CSRA66X0_CH2_BASS_FC_CTRL_FA,           0x00},
	{CSRA66X0_FILTER_SEL_8K,                 0x00},
	{CSRA66X0_FILTER_SEL_11P025K,            0x00},
	{CSRA66X0_FILTER_SEL_16K,                0x00},
	{CSRA66X0_FILTER_SEL_22P05K,             0x00},
	{CSRA66X0_FILTER_SEL_32K,                0x00},
	{CSRA66X0_FILTER_SEL_44P1K_48K,          0x00},
	{CSRA66X0_FILTER_SEL_88P2K_96K,          0x00},
	{CSRA66X0_FILTER_SEL_176P4K_192K,        0x00},
	/* RESERVED */
	{CSRA66X0_USER_DSP_CTRL,                 0x00},
	{CSRA66X0_TEST_TONE_CTRL,                0x00},
	{CSRA66X0_TEST_TONE_FREQ_0,              0x00},
	{CSRA66X0_TEST_TONE_FREQ_1,              0x00},
	{CSRA66X0_TEST_TONE_FREQ_2,              0x00},
	{CSRA66X0_AUDIO_RATE_CTRL_FA,            0x08},
	{CSRA66X0_MODULATION_INDEX_CTRL,         0x3F},
	{CSRA66X0_MODULATION_INDEX_COUNT,        0x10},
	{CSRA66X0_MIN_MODULATION_PULSE_WIDTH,    0x7A},
	{CSRA66X0_DEAD_TIME_CTRL,                0x00},
	{CSRA66X0_DEAD_TIME_THRESHOLD_0,         0xE7},
	{CSRA66X0_DEAD_TIME_THRESHOLD_1,         0x26},
	{CSRA66X0_DEAD_TIME_THRESHOLD_2,         0x40},
	{CSRA66X0_CH1_LOW_SIDE_DLY,              0x00},
	{CSRA66X0_CH2_LOW_SIDE_DLY,              0x00},
	{CSRA66X0_SPECTRUM_CTRL,                 0x00},
	/* RESERVED */
	{CSRA66X0_SPECTRUM_SPREAD_CTRL,          0x0C},
	/* RESERVED */
	{CSRA66X0_EXT_PA_PROTECT_POLARITY,       0x03},
	{CSRA66X0_TEMP0_BACKOFF_COMP_VALUE,      0x98},
	{CSRA66X0_TEMP0_SHUTDOWN_COMP_VALUE,     0xA3},
	{CSRA66X0_TEMP1_BACKOFF_COMP_VALUE,      0x98},
	{CSRA66X0_TEMP1_SHUTDOWN_COMP_VALUE,     0xA3},
	{CSRA66X0_TEMP_PROT_BACKOFF,             0x00},
	{CSRA66X0_TEMP_READ0_FA,                 0x00},
	{CSRA66X0_TEMP_READ1_FA,                 0x00},
	{CSRA66X0_CHIP_STATE_CTRL_FA,            0x02},
	/* RESERVED */
	{CSRA66X0_PWM_OUTPUT_CONFIG,             0x00},
	{CSRA66X0_MISC_CONTROL_STATUS_0,         0x08},
	{CSRA66X0_MISC_CONTROL_STATUS_1_FA,      0x40},
	{CSRA66X0_PIO0_SELECT,                   0x00},
	{CSRA66X0_PIO1_SELECT,                   0x00},
	{CSRA66X0_PIO2_SELECT,                   0x00},
	{CSRA66X0_PIO3_SELECT,                   0x00},
	{CSRA66X0_PIO4_SELECT,                   0x00},
	{CSRA66X0_PIO5_SELECT,                   0x00},
	{CSRA66X0_PIO6_SELECT,                   0x00},
	{CSRA66X0_PIO7_SELECT,                   0x00},
	{CSRA66X0_PIO8_SELECT,                   0x00},
	{CSRA66X0_PIO_DIRN0,                     0xFF},
	{CSRA66X0_PIO_DIRN1,                     0x01},
	{CSRA66X0_PIO_PULL_EN0,                  0xFF},
	{CSRA66X0_PIO_PULL_EN1,                  0x01},
	{CSRA66X0_PIO_PULL_DIR0,                 0x00},
	{CSRA66X0_PIO_PULL_DIR1,                 0x00},
	{CSRA66X0_PIO_DRIVE_OUT0_FA,             0x00},
	{CSRA66X0_PIO_DRIVE_OUT1_FA,             0x00},
	{CSRA66X0_PIO_STATUS_IN0_FA,             0x00},
	{CSRA66X0_PIO_STATUS_IN1_FA,             0x00},
	/* RESERVED */
	{CSRA66X0_IRQ_OUTPUT_ENABLE,             0x00},
	{CSRA66X0_IRQ_OUTPUT_POLARITY,           0x01},
	{CSRA66X0_IRQ_OUTPUT_STATUS_FA,          0x00},
	{CSRA66X0_CLIP_DCA_STATUS_FA,            0x00},
	{CSRA66X0_CHIP_STATE_STATUS_FA,          0x02},
	{CSRA66X0_FAULT_STATUS_FA,               0x00},
	{CSRA66X0_OTP_STATUS_FA,                 0x00},
	{CSRA66X0_AUDIO_IF_STATUS_FA,            0x00},
	/* RESERVED */
	{CSRA66X0_DSP_SATURATION_STATUS_FA,      0x00},
	{CSRA66X0_AUDIO_RATE_STATUS_FA,          0x00},
	/* RESERVED */
	{CSRA66X0_DISABLE_PWM_OUTPUT,            0x00},
	/* RESERVED */
	{CSRA66X0_OTP_VER_FA,                    0x03},
	{CSRA66X0_RAM_VER_FA,                    0x02},
	/* RESERVED */
	{CSRA66X0_AUDIO_SATURATION_FLAGS_FA,     0x00},
	{CSRA66X0_DCOFFSET_CHAN_1_01_FA,         0x00},
	{CSRA66X0_DCOFFSET_CHAN_1_02_FA,         0x00},
	{CSRA66X0_DCOFFSET_CHAN_1_03_FA,         0x00},
	{CSRA66X0_DCOFFSET_CHAN_2_01_FA,         0x00},
	{CSRA66X0_DCOFFSET_CHAN_2_02_FA,         0x00},
	{CSRA66X0_DCOFFSET_CHAN_2_03_FA,         0x00},
	{CSRA66X0_FORCED_PA_SWITCHING_CTRL,      0x90},
	{CSRA66X0_PA_FORCE_PULSE_WIDTH,          0x07},
	{CSRA66X0_PA_HIGH_MODULATION_CTRL_CH1,   0x00},
	/* RESERVED */
	{CSRA66X0_HIGH_MODULATION_THRESHOLD_LOW, 0xD4},
	{CSRA66X0_HIGH_MODULATION_THRESHOLD_HIGH, 0x78},
	/* RESERVED */
	{CSRA66X0_PA_FREEZE_CTRL,                0x00},
	{CSRA66X0_DCA_FREEZE_CTRL,               0x3C},
	/* RESERVED */
};

static bool csra66x0_volatile_register(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case CSRA66X0_CHIP_ID_FA:
	case CSRA66X0_ROM_VER_FA:
	case CSRA66X0_CHIP_REV_0_FA:
	case CSRA66X0_CHIP_REV_1_FA:
	case CSRA66X0_TEMP_READ0_FA:
	case CSRA66X0_TEMP_READ1_FA:
	case CSRA66X0_MISC_CONTROL_STATUS_1_FA:
	case CSRA66X0_IRQ_OUTPUT_STATUS_FA:
	case CSRA66X0_CLIP_DCA_STATUS_FA:
	case CSRA66X0_CHIP_STATE_STATUS_FA:
	case CSRA66X0_FAULT_STATUS_FA:
	case CSRA66X0_OTP_STATUS_FA:
	case CSRA66X0_AUDIO_IF_STATUS_FA:
	case CSRA66X0_DSP_SATURATION_STATUS_FA:
	case CSRA66X0_AUDIO_RATE_STATUS_FA:
		return true;
	default:
		return false;
	}
}

static bool csra66x0_writeable_registers(struct device *dev, unsigned int reg)
{
	if ((reg >= CSRA66X0_AUDIO_IF_RX_CONFIG1)
			&& (reg <= CSRA66X0_MAX_REGISTER_ADDR))
		return true;

	return false;
}

static bool csra66x0_readable_registers(struct device *dev, unsigned int reg)
{
	if ((reg >= CSRA66X0_AUDIO_IF_RX_CONFIG1)
			&& (reg <= CSRA66X0_MAX_REGISTER_ADDR))
		return true;

	return false;
}

/* codec private data */
struct csra66x0_priv {
	struct regmap *regmap;
	struct snd_soc_component *component;
	int spk_volume_ch1;
	int spk_volume_ch2;
	int irq;
	int vreg_gpio;
	u32 irq_active_low;
	u32 in_cluster;
	u32 is_master;
#if IS_ENABLED(CONFIG_DEBUG_FS)
	struct dentry *debugfs_dir;
	struct dentry *debugfs_file_wo;
	struct dentry *debugfs_file_ro;
#endif /* CONFIG_DEBUG_FS */
};

struct csra66x0_cluster_device {
	struct csra66x0_priv *csra66x0_ptr;
	const char *csra66x0_prefix;
};

struct csra66x0_cluster_device csra_clust_dev_tbl[] = {
	{NULL, "CSRA_12"},
	{NULL, "CSRA_34"},
	{NULL, "CSRA_56"},
	{NULL, "CSRA_78"},
	{NULL, "CSRA_9A"},
	{NULL, "CSRA_BC"}
};

#if IS_ENABLED(CONFIG_DEBUG_FS)
static int debugfs_codec_open_op(struct inode *inode, struct file *file)
{
	file->private_data = inode->i_private;
	return 0;
}

static int debugfs_get_parameters(char *buf, u32 *param1, int num_of_par)
{
	char *token;
	int base, cnt;

	token = strsep(&buf, " ");
	for (cnt = 0; cnt < num_of_par; cnt++) {
		if (token) {
			if ((token[1] == 'x') || (token[1] == 'X'))
				base = 16;
			else
				base = 10;

			if (kstrtou32(token, base, &param1[cnt]) != 0)
				return -EINVAL;

			token = strsep(&buf, " ");
		} else {
			return -EINVAL;
		}
	}
	return 0;
}

static ssize_t debugfs_codec_write_op(struct file *filp,
		const char __user *ubuf, size_t cnt, loff_t *ppos)
{
	struct csra66x0_priv *csra66x0 =
			(struct csra66x0_priv *) filp->private_data;
	struct snd_soc_component *component = csra66x0->component;
	char lbuf[32];
	int rc;
	u32 param[2];

	if (!filp || !ppos || !ubuf)
		return -EINVAL;
	if (cnt > sizeof(lbuf) - 1)
		return -EINVAL;
	rc = copy_from_user(lbuf, ubuf, cnt);
	if (rc)
		return -EFAULT;
	lbuf[cnt] = '\0';
	rc = debugfs_get_parameters(lbuf, param, 2);
	if ((param[0] < CSRA66X0_AUDIO_IF_RX_CONFIG1)
		|| (param[0] > CSRA66X0_MAX_REGISTER_ADDR)) {
		dev_err(component->dev, "%s: register address 0x%04X out of range\n",
			__func__, param[0]);
		return -EINVAL;
	}
	if ((param[1] < 0) || (param[1] > 255)) {
		dev_err(component->dev, "%s: register data 0x%02X out of range\n",
			__func__, param[1]);
		return -EINVAL;
	}
	if (rc == 0)
	{
		rc = cnt;
		dev_info(component->dev, "%s: reg[0x%04X]=0x%02X\n",
			__func__, param[0], param[1]);
		snd_soc_component_write(component, param[0], param[1]);
	} else {
		dev_err(component->dev, "%s: write to register addr=0x%04X failed\n",
			__func__, param[0]);
	}
	return rc;
}

static ssize_t debugfs_csra66x0_reg_show(struct snd_soc_component *component,
		char __user *ubuf, size_t count, loff_t *ppos)
{
	int i, reg_val, len;
	ssize_t total = 0;
	char tmp_buf[20];

	if (!ubuf || !ppos || !component || *ppos < 0)
		return -EINVAL;

	for (i = ((int) *ppos + CSRA66X0_BASE);
		i <= CSRA66X0_MAX_REGISTER_ADDR; i++) {
		reg_val = snd_soc_component_read32(component, i);
		len = snprintf(tmp_buf, 20, "0x%04X: 0x%02X\n", i, (reg_val & 0xFF));
		if ((total + len) >= count - 1)
			break;
		if (copy_to_user((ubuf + total), tmp_buf, len)) {
			dev_err(component->dev, "%s: fail to copy reg dump\n",
				__func__);
			total = -EFAULT;
			goto copy_err;
		}
		*ppos += len;
		total += len;
	}

copy_err:
	return total;
}

static ssize_t debugfs_codec_read_op(struct file *filp,
		char __user *ubuf, size_t cnt, loff_t *ppos)
{
	struct csra66x0_priv *csra66x0 =
		(struct csra66x0_priv *) filp->private_data;
	struct snd_soc_component *component = csra66x0->component;
	ssize_t ret_cnt;

	if (!filp || !ppos || !ubuf || *ppos < 0)
		return -EINVAL;
	ret_cnt = debugfs_csra66x0_reg_show(component, ubuf, cnt, ppos);
	return ret_cnt;
}

static const struct file_operations debugfs_codec_ops = {
	.open = debugfs_codec_open_op,
	.write = debugfs_codec_write_op,
	.read = debugfs_codec_read_op,
};
#endif /* CONFIG_DEBUG_FS */

/*
 * CSRA66X0 Controls
 */
static const DECLARE_TLV_DB_SCALE(csra66x0_volume_tlv, -9000, 25, 0);
static const DECLARE_TLV_DB_RANGE(csra66x0_bass_treble_tlv,
		0,  0, TLV_DB_SCALE_ITEM(0,   0, 0),
		1, 15, TLV_DB_SCALE_ITEM(-1500, 100, 0),
		16, 30, TLV_DB_SCALE_ITEM(100, 100, 0)
);

static int csra66x0_get_volume(struct snd_kcontrol *kcontrol,
			struct snd_ctl_elem_value *ucontrol)
{
	struct soc_mixer_control *mc =
			(struct soc_mixer_control *)kcontrol->private_value;
	struct snd_soc_component *component =
			snd_soc_kcontrol_component(kcontrol);
	unsigned int reg_l = mc->reg;
	unsigned int reg_r = mc->rreg;
	unsigned int val_l, val_r;

	val_l = (snd_soc_component_read32(component, reg_l) & 0xff) |
			((snd_soc_component_read32(component,
			CSRA66X0_CH1_VOLUME_1_FA) & (0x01)) << 8);
	val_r = (snd_soc_component_read32(component, reg_r) & 0xff) |
			((snd_soc_component_read32(component,
			CSRA66X0_CH2_VOLUME_1_FA) & (0x01)) << 8);
	ucontrol->value.integer.value[0] = val_l;
	ucontrol->value.integer.value[1] = val_r;
	return 0;
}

static int csra66x0_set_volume(struct snd_kcontrol *kcontrol,
			struct snd_ctl_elem_value *ucontrol)
{
	struct soc_mixer_control *mc =
			(struct soc_mixer_control *)kcontrol->private_value;
	struct snd_soc_component *component =
			snd_soc_kcontrol_component(kcontrol);
	struct csra66x0_priv *csra66x0 =
			snd_soc_component_get_drvdata(component);
	unsigned int reg_l = mc->reg;
	unsigned int reg_r = mc->rreg;
	unsigned int val_l[2];
	unsigned int val_r[2];

	csra66x0->spk_volume_ch1 = (ucontrol->value.integer.value[0]);
	csra66x0->spk_volume_ch2 = (ucontrol->value.integer.value[1]);
	val_l[0] = csra66x0->spk_volume_ch1 & SPK_VOLUME_LSB_MSK;
	val_l[1] = (csra66x0->spk_volume_ch1 & SPK_VOLUME_MSB_MSK) ? 1 : 0;
	val_r[0] = csra66x0->spk_volume_ch2 & SPK_VOLUME_LSB_MSK;
	val_r[1] = (csra66x0->spk_volume_ch2 & SPK_VOLUME_MSB_MSK) ? 1 : 0;
	snd_soc_component_write(component, reg_l, val_l[0]);
	snd_soc_component_write(component, reg_r, val_r[0]);
	snd_soc_component_write(component, CSRA66X0_CH1_VOLUME_1_FA, val_l[1]);
	snd_soc_component_write(component, CSRA66X0_CH2_VOLUME_1_FA, val_r[1]);
	return 0;
}

/* enumerated controls */
static const char * const csra66x0_mute_output_text[] = {"PLAY", "MUTE"};
static const char * const csra66x0_output_invert_text[] = {
		"UNCHANGED", "INVERTED"};
static const char * const csra66x0_deemp_config_text[] = {
		"DISABLED", "ENABLED"};

SOC_ENUM_SINGLE_DECL(csra66x0_mute_output_enum,
			CSRA66X0_MISC_CONTROL_STATUS_1_FA, 2,
			csra66x0_mute_output_text);
SOC_ENUM_SINGLE_DECL(csra66x0_ch1_output_invert_enum,
			CSRA66X0_CH1_OUTPUT_INVERT_EN, 0,
			csra66x0_output_invert_text);
SOC_ENUM_SINGLE_DECL(csra66x0_ch2_output_invert_enum,
			CSRA66X0_CH2_OUTPUT_INVERT_EN, 0,
			csra66x0_output_invert_text);
SOC_ENUM_DOUBLE_DECL(csra66x0_deemp_config_enum,
			CSRA66X0_DEEMP_CONFIG_FA, 0, 1,
			csra66x0_deemp_config_text);

static const struct snd_kcontrol_new csra66x0_snd_controls[] = {
	/* volume */
	SOC_DOUBLE_R_EXT_TLV("PA VOLUME", CSRA66X0_CH1_VOLUME_0_FA,
			CSRA66X0_CH2_VOLUME_0_FA, 0, 0x1C9, 0,
			csra66x0_get_volume, csra66x0_set_volume,
			csra66x0_volume_tlv),

	/* bass treble */
	SOC_DOUBLE_R_TLV("PA BASS GAIN", CSRA66X0_CH1_BASS_GAIN_CTRL_FA,
			CSRA66X0_CH2_BASS_GAIN_CTRL_FA, 0, 0x1E, 0,
			csra66x0_bass_treble_tlv),
	SOC_DOUBLE_R_TLV("PA TREBLE GAIN", CSRA66X0_CH1_TREBLE_GAIN_CTRL_FA,
			CSRA66X0_CH2_TREBLE_GAIN_CTRL_FA, 0, 0x1E, 0,
			csra66x0_bass_treble_tlv),
	SOC_DOUBLE_R("PA BASS_XOVER FREQ", CSRA66X0_CH1_BASS_FC_CTRL_FA,
			CSRA66X0_CH2_BASS_FC_CTRL_FA, 0, 2, 0),
	SOC_DOUBLE_R("PA TREBLE_XOVER FREQ", CSRA66X0_CH1_TREBLE_FC_CTRL_FA,
			CSRA66X0_CH2_TREBLE_FC_CTRL_FA, 0, 2, 0),

	/* switch */
	SOC_ENUM("PA MUTE_OUTPUT SWITCH", csra66x0_mute_output_enum),
	SOC_ENUM("PA DE-EMPHASIS SWITCH", csra66x0_deemp_config_enum),
};

static const struct snd_kcontrol_new csra_mix_switch[] = {
	SOC_DAPM_SINGLE("Switch", SND_SOC_NOPM, 0, 1, 0)
};

static const struct snd_soc_dapm_widget csra66x0_dapm_widgets[] = {
	SND_SOC_DAPM_INPUT("IN"),
	SND_SOC_DAPM_MIXER("MIXER", SND_SOC_NOPM, 0, 0,
			csra_mix_switch, ARRAY_SIZE(csra_mix_switch)),
	SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0),
	SND_SOC_DAPM_PGA("PGA", CSRA66X0_CHIP_STATE_CTRL_FA, 0, 0, NULL, 0),
	SND_SOC_DAPM_OUTPUT("SPKR"),

	SND_SOC_DAPM_SUPPLY("POWER", CSRA66X0_CHIP_STATE_CTRL_FA,
			1, 1, NULL, 0),
};

static const struct snd_soc_dapm_route csra66x0_dapm_routes[] = {
	{"MIXER", "Switch", "IN"},
	{"DAC", NULL, "MIXER"},
	{"PGA", NULL, "DAC"},
	{"SPKR", NULL, "PGA"},
	{"SPKR", NULL, "POWER"},
};

static int csra66x0_init(struct csra66x0_priv *csra66x0)
{
	struct snd_soc_component *component = csra66x0->component;

	dev_dbg(component->dev, "%s: initialize %s\n",
		__func__, component->name);
	/* config */
	snd_soc_component_write(component, CSRA66X0_CHIP_STATE_CTRL_FA,
				CONFIG_STATE);
	/* settle time in HW is min. 500ms before proceeding */
	msleep(500);

	/* setup */
	snd_soc_component_write(component, CSRA66X0_MISC_CONTROL_STATUS_0,
				0x09);
	snd_soc_component_write(component, CSRA66X0_TEMP_PROT_BACKOFF, 0x0C);
	snd_soc_component_write(component, CSRA66X0_EXT_PA_PROTECT_POLARITY,
				0x03);
	snd_soc_component_write(component, CSRA66X0_PWM_OUTPUT_CONFIG, 0xC8);
	csra66x0->spk_volume_ch1 = SPK_VOLUME_M20DB;
	csra66x0->spk_volume_ch2 = SPK_VOLUME_M20DB;
	snd_soc_component_write(component, CSRA66X0_CH1_VOLUME_0_FA,
				SPK_VOLUME_M20DB_LSB);
	snd_soc_component_write(component, CSRA66X0_CH2_VOLUME_0_FA,
				SPK_VOLUME_M20DB_LSB);
	snd_soc_component_write(component, CSRA66X0_CH1_VOLUME_1_FA,
				SPK_VOLUME_M20DB_MSB);
	snd_soc_component_write(component, CSRA66X0_CH2_VOLUME_1_FA,
				SPK_VOLUME_M20DB_MSB);

	snd_soc_component_write(component, CSRA66X0_DEAD_TIME_CTRL, 0x0);
	snd_soc_component_write(component, CSRA66X0_DEAD_TIME_THRESHOLD_0,
				0xE7);
	snd_soc_component_write(component, CSRA66X0_DEAD_TIME_THRESHOLD_1,
				0x26);
	snd_soc_component_write(component, CSRA66X0_DEAD_TIME_THRESHOLD_2,
				0x40);

	snd_soc_component_write(component, CSRA66X0_MIN_MODULATION_PULSE_WIDTH,
				0x7A);
	snd_soc_component_write(component, CSRA66X0_CH1_HARD_CLIP_THRESH, 0x00);
	snd_soc_component_write(component, CSRA66X0_CH2_HARD_CLIP_THRESH, 0x00);

	snd_soc_component_write(component, CSRA66X0_CH1_DCA_THRESH, 0x40);
	snd_soc_component_write(component, CSRA66X0_CH2_DCA_THRESH, 0x40);
	snd_soc_component_write(component, CSRA66X0_DCA_ATTACK_RATE, 0x00);
	snd_soc_component_write(component, CSRA66X0_DCA_RELEASE_RATE, 0x00);

	if (csra66x0->irq) {
		snd_soc_component_write(component, CSRA66X0_PIO0_SELECT, 0x1);
		if (csra66x0->irq_active_low)
			snd_soc_component_write(component,
				CSRA66X0_IRQ_OUTPUT_POLARITY, 0x0);
		else
			snd_soc_component_write(component,
				CSRA66X0_IRQ_OUTPUT_POLARITY, 0x1);

		snd_soc_component_write(component,
				CSRA66X0_IRQ_OUTPUT_ENABLE, 0x01);
	} else {
		snd_soc_component_write(component,
				CSRA66X0_IRQ_OUTPUT_ENABLE, 0x00);
	}
	/* settle time in HW is min. 500ms before slave initializing */
	msleep(500);
	return 0;
}

static int csra66x0_reset(struct csra66x0_priv *csra66x0)
{
	struct snd_soc_component *component = csra66x0->component;
	u16 val;

	val = snd_soc_component_read32(component, CSRA66X0_FAULT_STATUS_FA);
	if (val & FAULT_STATUS_INTERNAL)
		dev_dbg(component->dev, "%s: FAULT_STATUS_INTERNAL 0x%X\n",
			__func__, val);
	if (val & FAULT_STATUS_OTP_INTEGRITY)
		dev_dbg(component->dev, "%s: FAULT_STATUS_OTP_INTEGRITY 0x%X\n",
			__func__, val);
	if (val & FAULT_STATUS_PADS2)
		dev_dbg(component->dev, "%s: FAULT_STATUS_PADS2 0x%X\n",
			__func__, val);
	if (val & FAULT_STATUS_SMPS)
		dev_dbg(component->dev, "%s: FAULT_STATUS_SMPS 0x%X\n",
			__func__, val);
	if (val & FAULT_STATUS_TEMP)
		dev_dbg(component->dev, "%s: FAULT_STATUS_TEMP 0x%X\n",
			__func__, val);
	if (val & FAULT_STATUS_PROTECT)
		dev_dbg(component->dev, "%s: FAULT_STATUS_PROTECT 0x%X\n",
			__func__, val);

	dev_dbg(component->dev, "%s: reset %s\n",
		__func__, component->name);
	/* clear fault state and re-init */
	snd_soc_component_write(component, CSRA66X0_FAULT_STATUS_FA, 0x00);
	snd_soc_component_write(component, CSRA66X0_IRQ_OUTPUT_STATUS_FA, 0x00);
	/* apply reset to CSRA66X0 */
	val = snd_soc_component_read32(component,
			CSRA66X0_MISC_CONTROL_STATUS_1_FA);
	snd_soc_component_write(component, CSRA66X0_MISC_CONTROL_STATUS_1_FA,
			val | 0x08);
	/* wait 500ms after reset to recover CSRA66X0 */
	msleep(500);
	return 0;
}

static int csra66x0_msconfig(struct csra66x0_priv *csra66x0)
{
	struct snd_soc_component *component = csra66x0->component;

	dev_dbg(component->dev, "%s: configure %s\n",
		__func__, component->name);
	/* config */
	snd_soc_component_write(component, CSRA66X0_CHIP_STATE_CTRL_FA,
		CONFIG_STATE);
	/* settle time in HW is min. 500ms before proceeding */
	msleep(500);
	snd_soc_component_write(component, CSRA66X0_PIO7_SELECT, 0x04);
	snd_soc_component_write(component, CSRA66X0_PIO8_SELECT, 0x04);
	if (csra66x0->is_master) {
		/* Master specific config */
		snd_soc_component_write(component,
				CSRA66X0_PIO_PULL_EN0, 0xFF);
		snd_soc_component_write(component,
				CSRA66X0_PIO_PULL_DIR0, 0x80);
		snd_soc_component_write(component,
				CSRA66X0_PIO_PULL_EN1, 0x01);
		snd_soc_component_write(component,
				CSRA66X0_PIO_PULL_DIR1, 0x01);
	} else {
		/* Slave specific config */
		snd_soc_component_write(component,
				CSRA66X0_PIO_PULL_EN0, 0x7F);
		snd_soc_component_write(component,
				CSRA66X0_PIO_PULL_EN1, 0x00);
	}
	snd_soc_component_write(component, CSRA66X0_DCA_CTRL, 0x05);
	return 0;
}

static int csra66x0_soc_probe(struct snd_soc_component *component)
{
	struct csra66x0_priv *csra66x0 =
				snd_soc_component_get_drvdata(component);
	struct snd_soc_dapm_context *dapm;
	char name[50];
	unsigned int i, max_num_cluster_devices;

	csra66x0->component = component;
	if (csra66x0->in_cluster) {
		dapm = snd_soc_component_get_dapm(component);
		dev_dbg(component->dev, "%s: assign prefix %s to codec device %s\n",
			__func__, component->name_prefix,
			component->name);

		/* add device to cluster table */
		max_num_cluster_devices = sizeof(csra_clust_dev_tbl)/
			sizeof(csra_clust_dev_tbl[0]);
		for (i = 0; i < max_num_cluster_devices; i++) {
			if (!strncmp(component->name_prefix,
				  csra_clust_dev_tbl[i].csra66x0_prefix,
				  strlen(
				  csra_clust_dev_tbl[i].csra66x0_prefix))) {
				csra_clust_dev_tbl[i].csra66x0_ptr = csra66x0;
				break;
			}
			if (i == max_num_cluster_devices-1)
				dev_warn(component->dev,
					"%s: Unknown prefix %s of cluster device %s\n",
					__func__, component->name_prefix,
					component->name);
		}

		/* master slave config */
		csra66x0_msconfig(csra66x0);
		if (dapm->component) {
			strlcpy(name, dapm->component->name_prefix,
					sizeof(name));
			strlcat(name, " IN", sizeof(name));
			snd_soc_dapm_ignore_suspend(dapm, name);
			strlcpy(name, dapm->component->name_prefix,
					sizeof(name));
			strlcat(name, " SPKR", sizeof(name));
			snd_soc_dapm_ignore_suspend(dapm, name);
		}
	}

	/* common initialization */
	csra66x0_init(csra66x0);
	return 0;
}

static void csra66x0_soc_remove(struct snd_soc_component *component)
{
	snd_soc_component_write(component, CSRA66X0_CHIP_STATE_CTRL_FA,
				STDBY_STATE);
	return;
}

static int csra66x0_soc_suspend(struct snd_soc_component *component)
{
	u16 state_reg = snd_soc_component_read32(component,
				CSRA66X0_CHIP_STATE_CTRL_FA) & 0xFC;

	snd_soc_component_write(component, CSRA66X0_CHIP_STATE_CTRL_FA,
				state_reg | STDBY_STATE);
	return 0;
}

static int csra66x0_soc_resume(struct snd_soc_component *component)
{
	u16 state_reg = snd_soc_component_read32(
				component, CSRA66X0_CHIP_STATE_CTRL_FA) & 0xFC;

	snd_soc_component_write(component, CSRA66X0_CHIP_STATE_CTRL_FA,
				state_reg | RUN_STATE);
	return 0;
}

static const struct snd_soc_component_driver soc_codec_drv_csra66x0 = {
	.name = DRV_NAME,
	.probe  = csra66x0_soc_probe,
	.remove = csra66x0_soc_remove,
	.suspend = csra66x0_soc_suspend,
	.resume = csra66x0_soc_resume,
	.controls = csra66x0_snd_controls,
	.num_controls = ARRAY_SIZE(csra66x0_snd_controls),
	.dapm_widgets = csra66x0_dapm_widgets,
	.num_dapm_widgets = ARRAY_SIZE(csra66x0_dapm_widgets),
	.dapm_routes = csra66x0_dapm_routes,
	.num_dapm_routes = ARRAY_SIZE(csra66x0_dapm_routes),
};

static struct regmap_config csra66x0_regmap_config = {
	.reg_bits = 16,
	.val_bits = 8,
	.cache_type = REGCACHE_RBTREE,
	.reg_defaults = csra66x0_reg_defaults,
	.num_reg_defaults = ARRAY_SIZE(csra66x0_reg_defaults),
	.max_register = CSRA66X0_MAX_REGISTER_ADDR,
	.volatile_reg = csra66x0_volatile_register,
	.writeable_reg = csra66x0_writeable_registers,
	.readable_reg = csra66x0_readable_registers,
};

static irqreturn_t csra66x0_irq(int irq, void *data)
{
	struct csra66x0_priv *csra66x0 = (struct csra66x0_priv *) data;
	struct snd_soc_component  *component = csra66x0->component;
	u16    val;
	unsigned int i, max_num_cluster_devices;

	/* Treat interrupt before component is initialized as spurious */
	if (component == NULL)
		return IRQ_NONE;

	dev_dbg(component->dev, "%s: csra66x0_interrupt triggered by %s\n",
		__func__, component->name);

	/* fault  indication */
	val = snd_soc_component_read32(component, CSRA66X0_IRQ_OUTPUT_STATUS_FA)
		& 0x1;
	if (!val)
		return IRQ_HANDLED;

	if (csra66x0->in_cluster) {
		/* reset all slave codecs */
		max_num_cluster_devices =
			sizeof(csra_clust_dev_tbl) /
			sizeof(csra_clust_dev_tbl[0]);
		for (i = 0; i < max_num_cluster_devices; i++) {
			if (i >= component->card->num_aux_devs)
				break;
			if (csra_clust_dev_tbl[i].csra66x0_ptr == NULL)
				continue;
			if (csra_clust_dev_tbl[i].csra66x0_ptr->is_master)
				continue;
			csra66x0_reset(csra_clust_dev_tbl[i].csra66x0_ptr);
		}
		/* reset all master codecs */
		for (i = 0; i < max_num_cluster_devices; i++) {
			if (i >= component->card->num_aux_devs)
				break;
			if (csra_clust_dev_tbl[i].csra66x0_ptr == NULL)
				continue;
			if (csra_clust_dev_tbl[i].csra66x0_ptr->is_master)
				csra66x0_reset(
					csra_clust_dev_tbl[i].csra66x0_ptr);
		}
		/* recover all codecs */
		for (i = 0; i < max_num_cluster_devices; i++) {
			if (i >= component->card->num_aux_devs)
				break;
			if (csra_clust_dev_tbl[i].csra66x0_ptr == NULL)
				continue;
			csra66x0_msconfig(csra_clust_dev_tbl[i].csra66x0_ptr);
			csra66x0_init(csra_clust_dev_tbl[i].csra66x0_ptr);
		}
	} else {
		csra66x0_reset(csra66x0);
		csra66x0_init(csra66x0);
	}
	return IRQ_HANDLED;
};

static const struct of_device_id csra66x0_of_match[] = {
	{ .compatible = "qcom,csra66x0", },
	{ }
};
MODULE_DEVICE_TABLE(of, csra66x0_of_match);

#if IS_ENABLED(CONFIG_I2C)
static int csra66x0_i2c_probe(struct i2c_client *client_i2c,
			const struct i2c_device_id *id)
{
	struct csra66x0_priv *csra66x0;
	int ret, irq_trigger;
	char debugfs_dir_name[32];

	csra66x0 = devm_kzalloc(&client_i2c->dev, sizeof(struct csra66x0_priv),
			GFP_KERNEL);
	if (csra66x0 == NULL)
		return -ENOMEM;

	csra66x0->regmap = devm_regmap_init_i2c(client_i2c,
			&csra66x0_regmap_config);
	if (IS_ERR(csra66x0->regmap)) {
		ret = PTR_ERR(csra66x0->regmap);
		dev_err(&client_i2c->dev,
				"%s %d: Failed to allocate register map for I2C device: %d\n",
				__func__, __LINE__, ret);
		return ret;
	}

	i2c_set_clientdata(client_i2c, csra66x0);

	/* get data from device tree */
	if (client_i2c->dev.of_node) {
		/* cluster of multiple devices */
		ret = of_property_read_u32(
			client_i2c->dev.of_node, "qcom,csra-cluster",
			&csra66x0->in_cluster);
		if (ret) {
			dev_info(&client_i2c->dev,
			"%s: qcom,csra-cluster property not defined in DT\n", __func__);
			csra66x0->in_cluster = 0;
		}
		/* master or slave device */
		ret = of_property_read_u32(
			client_i2c->dev.of_node, "qcom,csra-cluster-master",
			&csra66x0->is_master);
		if (ret) {
			dev_info(&client_i2c->dev,
			"%s: qcom,csra-cluster-master property not defined in DT, slave assumed\n",
			__func__);
			csra66x0->is_master = 0;
		}

		/* gpio setup for vreg */
		csra66x0->vreg_gpio = of_get_named_gpio(client_i2c->dev.of_node,
			"qcom,csra-vreg-en-gpio", 0);
		if (!gpio_is_valid(csra66x0->vreg_gpio)) {
			dev_err(&client_i2c->dev, "%s: %s property is not found %d\n",
					__func__, "qcom,csra-vreg-en-gpio",
					csra66x0->vreg_gpio);
			return -ENODEV;
		}
		dev_dbg(&client_i2c->dev, "%s: vreg_en gpio %d\n", __func__,
			csra66x0->vreg_gpio);
		ret = gpio_request(csra66x0->vreg_gpio, dev_name(&client_i2c->dev));
		if (ret) {
			if (ret == -EBUSY) {
				/* GPIO was already requested */
				dev_dbg(&client_i2c->dev,
				"%s: gpio %d is already set\n",
				__func__, csra66x0->vreg_gpio);
			} else {
				dev_err(&client_i2c->dev, "%s: Failed to request gpio %d, err: %d\n",
					__func__, csra66x0->vreg_gpio, ret);
			}
		} else {
			gpio_direction_output(csra66x0->vreg_gpio, 1);
			gpio_set_value(csra66x0->vreg_gpio, 0);
		}

		/* register interrupt handle */
		if (client_i2c->irq) {
			csra66x0->irq = client_i2c->irq;
			/* interrupt polarity */
			ret = of_property_read_u32(
				client_i2c->dev.of_node, "irq-active-low",
				&csra66x0->irq_active_low);
			if (ret) {
				dev_info(&client_i2c->dev,
				"%s: irq-active-low property not defined in DT\n", __func__);
				csra66x0->irq_active_low = 0;
			}
			if (csra66x0->irq_active_low)
				irq_trigger = IRQF_TRIGGER_LOW;
			else
				irq_trigger = IRQF_TRIGGER_HIGH;

			ret = devm_request_threaded_irq(&client_i2c->dev,
					csra66x0->irq, NULL, csra66x0_irq,
					irq_trigger | IRQF_ONESHOT,
					"csra66x0_irq", csra66x0);
			if (ret) {
				dev_err(&client_i2c->dev,
				"%s: Failed to request IRQ %d: %d\n",
				__func__, csra66x0->irq, ret);
				csra66x0->irq = 0;
			}
		}
	}

#if IS_ENABLED(CONFIG_DEBUG_FS)
	/* debugfs interface */
	snprintf(debugfs_dir_name, sizeof(debugfs_dir_name), "%s-%s",
		client_i2c->name, dev_name(&client_i2c->dev));
	csra66x0->debugfs_dir = debugfs_create_dir(debugfs_dir_name, NULL);
	if (!csra66x0->debugfs_dir) {
		dev_dbg(&client_i2c->dev,
			"%s: Failed to create /sys/kernel/debug/%s for debugfs\n",
			__func__, debugfs_dir_name);
		return -ENOMEM;
	}
	csra66x0->debugfs_file_wo = debugfs_create_file(
		"write_reg_val", S_IFREG | S_IRUGO, csra66x0->debugfs_dir,
		(void *) csra66x0,
		&debugfs_codec_ops);
	if (!csra66x0->debugfs_file_wo) {
		dev_dbg(&client_i2c->dev,
			"%s: Failed to create /sys/kernel/debug/%s/write_reg_val\n",
			__func__, debugfs_dir_name);
		return -ENOMEM;
	}
	csra66x0->debugfs_file_ro = debugfs_create_file(
		"show_reg_dump", S_IFREG | S_IRUGO, csra66x0->debugfs_dir,
		(void *) csra66x0,
		&debugfs_codec_ops);
	if (!csra66x0->debugfs_file_ro) {
		dev_dbg(&client_i2c->dev,
			"%s: Failed to create /sys/kernel/debug/%s/show_reg_dump\n",
			__func__, debugfs_dir_name);
		return -ENOMEM;
	}
#endif /* CONFIG_DEBUG_FS */

	/* register codec */
	ret = snd_soc_register_component(&client_i2c->dev,
			&soc_codec_drv_csra66x0, NULL, 0);
	if (ret != 0) {
		dev_err(&client_i2c->dev, "%s %d: Failed to register codec: %d\n",
			__func__, __LINE__, ret);
		if (gpio_is_valid(csra66x0->vreg_gpio)) {
			gpio_set_value(csra66x0->vreg_gpio, 0);
			gpio_free(csra66x0->vreg_gpio);
		}
		return ret;
	}
	return 0;
}

static int csra66x0_i2c_remove(struct i2c_client *i2c_client)
{
	struct csra66x0_priv *csra66x0 = i2c_get_clientdata(i2c_client);

	if (csra66x0) {
		if (gpio_is_valid(csra66x0->vreg_gpio)) {
			gpio_set_value(csra66x0->vreg_gpio, 0);
			gpio_free(csra66x0->vreg_gpio);
		}
#if IS_ENABLED(CONFIG_DEBUG_FS)
		debugfs_remove_recursive(csra66x0->debugfs_dir);
#endif
	}
	snd_soc_unregister_component(&i2c_client->dev);
	return 0;
}

static const struct i2c_device_id csra66x0_i2c_id[] = {
	{ "csra66x0", 0},
	{ }
};
MODULE_DEVICE_TABLE(i2c, csra66x0_i2c_id);

static struct i2c_driver csra66x0_i2c_driver = {
	.probe =    csra66x0_i2c_probe,
	.remove =   csra66x0_i2c_remove,
	.id_table = csra66x0_i2c_id,
	.driver = {
		.name = "csra66x0",
		.owner = THIS_MODULE,
		.of_match_table = csra66x0_of_match
	},
};
#endif

static int __init csra66x0_codec_init(void)
{
	int ret = 0;
#if IS_ENABLED(CONFIG_I2C)
	ret = i2c_add_driver(&csra66x0_i2c_driver);
	if (ret != 0)
		pr_err("%s: Failed to register CSRA66X0 I2C driver, ret = %d\n",
			__func__, ret);
#endif
	return ret;
}
module_init(csra66x0_codec_init);

static void __exit csra66x0_codec_exit(void)
{
#if IS_ENABLED(CONFIG_I2C)
	i2c_del_driver(&csra66x0_i2c_driver);
#endif
}
module_exit(csra66x0_codec_exit);

MODULE_DESCRIPTION("CSRA66X0 Codec driver");
MODULE_LICENSE("GPL v2");
