From 75e3349fd265324145e92c9c3a474bb02c5568cd Mon Sep 17 00:00:00 2001 From: XiaoTan Luo Date: Mon, 9 Aug 2021 16:15:33 +0800 Subject: [PATCH] media: i2c: techpoint: add support audio feature Signed-off-by: Xing Zheng Signed-off-by: XiaoTan Luo Change-Id: Ie5fdd39284575e89f8242c12bc6c14033ee5e80e --- .../media/i2c/techpoint/techpoint_common.h | 20 +- drivers/media/i2c/techpoint/techpoint_v4l2.c | 528 ++++++++++++++++++ 2 files changed, 547 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/techpoint/techpoint_common.h b/drivers/media/i2c/techpoint/techpoint_common.h index f6bbc9a5fe0f..2ac5f26581e7 100644 --- a/drivers/media/i2c/techpoint/techpoint_common.h +++ b/drivers/media/i2c/techpoint/techpoint_common.h @@ -8,7 +8,6 @@ #ifndef _TECHPOINT_COMMON_H #define _TECHPOINT_COMMON_H -// #define DEBUG #include #include #include @@ -89,7 +88,23 @@ struct techpoint_video_modes { enum techpoint_support_reso channel_reso[PAD_MAX]; }; +/* Audio output port formats */ +enum techpoint_audfmts { + AUDFMT_I2S = 0, + AUDFMT_DSP, +}; + +struct techpoint_audio { + enum techpoint_audfmts audfmt; + int mclk_fs; + int cascade_num; + int cascade_order; + int slave_num; + struct techpoint *slave_tp[3]; +}; + struct techpoint { + struct device dev; struct i2c_client *client; struct clk *xvclk; struct gpio_desc *reset_gpio; @@ -105,6 +120,9 @@ struct techpoint { struct mutex mutex; struct regulator_bulk_data *supplies; u32 xvclk_freq_value; + struct techpoint_audio *audio_in; + struct techpoint_audio *audio_out; + int i2c_idx; u32 channel_nums; enum techpoint_chips chip_id; diff --git a/drivers/media/i2c/techpoint/techpoint_v4l2.c b/drivers/media/i2c/techpoint/techpoint_v4l2.c index 8144640070ec..bab8436f2ed6 100644 --- a/drivers/media/i2c/techpoint/techpoint_v4l2.c +++ b/drivers/media/i2c/techpoint/techpoint_v4l2.c @@ -6,6 +6,11 @@ * */ +#include +#include +#include +#include +#include #include "techpoint_dev.h" #define TECHPOINT_NAME "techpoint" @@ -13,6 +18,28 @@ #define OF_CAMERA_PINCTRL_STATE_DEFAULT "rockchip,camera_default" #define OF_CAMERA_PINCTRL_STATE_SLEEP "rockchip,camera_sleep" +#define I2S 0 +#define DSP 1 +#define AUDIO_FORMAT I2S + +#define SAMPLE_8K 0 +#define SAMPLE_16K 1 +#define SAMPLE_RATE SAMPLE_8K + +#define DATA_16BIT 0 +#define DATA_8BIT 1 +#define DATA_BIT DATA_16BIT + +#define AUDIO_CHN 8 +#define MAX_CHIPS 4 +#define MAX_SLAVES (MAX_CHIPS - 1) + +#define TECHPOINT_I2C_CHIP_ADDRESS_0 0x44 +#define TECHPOINT_I2C_CHIP_ADDRESS_1 0x45 + +static int g_idx; +static struct techpoint *g_techpoints[MAX_CHIPS]; + static const char *const techpoint_supply_names[] = { "dovdd", /* Digital I/O power */ "avdd", /* Analog power */ @@ -809,6 +836,496 @@ static const struct i2c_device_id techpoint_match_id[] = { { }, }; +static int techpoint_9930_audio_init(struct techpoint *techpoint); + +static struct snd_soc_dai_driver techpoint_audio_dai = { + .name = "techpoint", + .playback = { + .stream_name = "Playback", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_384000, + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE), + }, + .capture = { + .stream_name = "Capture", + .channels_min = 1, + .channels_max = 16, + .rates = SNDRV_PCM_RATE_8000_384000, + .formats = (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE), + }, +}; + +static ssize_t i2c_rdwr_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct techpoint *techpoint = + container_of(dev, struct techpoint, dev); + unsigned char op_type; + unsigned int reg, v; + int ret; + + ret = sscanf(buf, "%c %x %x", &op_type, ®, &v); + if (ret != 3) { + dev_err(&techpoint->client->dev, "%s sscanf failed: %d\n", + __func__, ret); + return -EFAULT; + } + + if (op_type == 'r') + techpoint_read_reg(techpoint->client, reg, (unsigned char *)&v); + else if (op_type == 'w') + techpoint_write_reg(techpoint->client, reg, v); + else if (op_type == 'd') + techpoint_9930_audio_init(techpoint); + + return count; +} + +static const struct device_attribute techpoint_attrs[] = { + __ATTR_WO(i2c_rdwr), +}; + +static int techpoint_codec_probe(struct snd_soc_component *component) +{ + return 0; +} + +static void techpoint_codec_remove(struct snd_soc_component *component) +{ +} + +static const struct snd_soc_component_driver techpoint_codec_driver = { + .probe = techpoint_codec_probe, + .remove = techpoint_codec_remove, + .idle_bias_on = 1, + .use_pmdown_time = 1, + .endianness = 1, + .non_legacy_dai_naming = 1, +}; + +static int tp2833_audio_config_rmpos(struct i2c_client *client, + unsigned int chip, unsigned int format, unsigned int chn_num) +{ + int i = 0; + unsigned char v; + + /* clear first */ + for (i = 0; i < 20; i++) + techpoint_write_reg(client, i, 0x00); + + switch (chn_num) { + + case 2: + if (format == DSP) { + techpoint_write_reg(client, 0x0, 1); + techpoint_write_reg(client, 0x1, 2); + } else { + techpoint_write_reg(client, 0x0, 1); + techpoint_write_reg(client, 0x8, 2); + } + break; + + case 4: + if (format == DSP) { + techpoint_write_reg(client, 0x0, 1); + techpoint_write_reg(client, 0x1, 2); + techpoint_write_reg(client, 0x2, 3); + techpoint_write_reg(client, 0x3, 4); + } else { + techpoint_write_reg(client, 0x0, 1); + techpoint_write_reg(client, 0x1, 3); + techpoint_write_reg(client, 0x8, 2); + techpoint_write_reg(client, 0x9, 4); + } + break; + + case 8: + if (chip % 4 == 0) { + if (format == DSP) { + techpoint_write_reg(client, 0x0, 1); + techpoint_write_reg(client, 0x1, 2); + techpoint_write_reg(client, 0x2, 3); + techpoint_write_reg(client, 0x3, 4); + techpoint_write_reg(client, 0x4, 5); + techpoint_write_reg(client, 0x5, 6); + techpoint_write_reg(client, 0x6, 7); + techpoint_write_reg(client, 0x7, 8); + } else { + techpoint_write_reg(client, 0x0, 1); + techpoint_write_reg(client, 0x1, 2); + techpoint_write_reg(client, 0x2, 3); + techpoint_write_reg(client, 0x3, 4); + techpoint_write_reg(client, 0x8, 5); + techpoint_write_reg(client, 0x9, 6); + techpoint_write_reg(client, 0xa, 7); + techpoint_write_reg(client, 0xb, 8); + } + } else if (chip % 4 == 1) { + if (format == DSP) { + techpoint_write_reg(client, 0x0, 0); + techpoint_write_reg(client, 0x1, 0); + techpoint_write_reg(client, 0x2, 0); + techpoint_write_reg(client, 0x3, 0); + techpoint_write_reg(client, 0x4, 1); + techpoint_write_reg(client, 0x5, 2); + techpoint_write_reg(client, 0x6, 3); + techpoint_write_reg(client, 0x7, 4); + } else { + techpoint_write_reg(client, 0x0, 0); + techpoint_write_reg(client, 0x1, 0); + techpoint_write_reg(client, 0x2, 1); + techpoint_write_reg(client, 0x3, 2); + techpoint_write_reg(client, 0x8, 0); + techpoint_write_reg(client, 0x9, 0); + techpoint_write_reg(client, 0xa, 3); + techpoint_write_reg(client, 0xb, 4); + techpoint_read_reg(client, 0x3, &v); + } + } + break; + + case 16: + if (chip % 4 == 0) { + for (i = 0; i < 16; i++) + techpoint_write_reg(client, i, i+1); + } else if (chip % 4 == 1) { + for (i = 4; i < 16; i++) + techpoint_write_reg(client, i, i+1 - 4); + } else if (chip % 4 == 2) { + for (i = 8; i < 16; i++) + techpoint_write_reg(client, i, i+1 - 8); + } else { + for (i = 12; i < 16; i++) + techpoint_write_reg(client, i, i+1 - 12); + } + break; + + case 20: + for (i = 0; i < 20; i++) + techpoint_write_reg(client, i, i+1); + break; + + default: + for (i = 0; i < 20; i++) + techpoint_write_reg(client, i, i+1); + break; + } + + mdelay(10); + return 0; +} + +static int techpoint_2855_audio_init(struct techpoint *techpoint) +{ + struct i2c_client *client = techpoint->client; + + unsigned char bank; + unsigned char chip_id_h = 0xFF, chip_id_l = 0xFF; + + techpoint_read_reg(client, CHIP_ID_H_REG, &chip_id_h); + techpoint_read_reg(client, CHIP_ID_L_REG, &chip_id_l); + + techpoint_read_reg(client, 0x40, &bank); + techpoint_write_reg(client, 0x40, 0x40); + + tp2833_audio_config_rmpos(client, 0, AUDIO_FORMAT, AUDIO_CHN); + + techpoint_write_reg(client, 0x17, 0x00|(DATA_BIT<<2)); + techpoint_write_reg(client, 0x1B, 0x01|(DATA_BIT<<6)); + +#if (AUDIO_CHN == 20) + techpoint_write_reg(client, 0x18, 0x90|(SAMPLE_RATE)); +#else + techpoint_write_reg(client, 0x18, 0x80|(SAMPLE_RATE)); +#endif + +#if (AUDIO_CHN >= 8) + techpoint_write_reg(client, 0x19, 0x1F); +#else + techpoint_write_reg(client, 0x19, 0x0F); +#endif + + techpoint_write_reg(client, 0x1A, 0x15); + + techpoint_write_reg(client, 0x37, 0x20); + techpoint_write_reg(client, 0x38, 0x38); + techpoint_write_reg(client, 0x3E, 0x00); + + /* audio reset */ + techpoint_write_reg(client, 0x3d, 0x01); + + techpoint_write_reg(client, 0x40, bank); + + return 0; +} + +static int techpoint_9930_audio_init(struct techpoint *techpoint) +{ + struct i2c_client *client = techpoint->client; + + unsigned char bank; + unsigned char chip_id_h = 0xFF; + unsigned char chip_id_l = 0xFF; + + techpoint_read_reg(client, CHIP_ID_H_REG, &chip_id_h); + techpoint_read_reg(client, CHIP_ID_L_REG, &chip_id_l); + + techpoint_read_reg(client, 0x40, &bank); + techpoint_write_reg(client, 0x40, 0x40); + + tp2833_audio_config_rmpos(client, 1, AUDIO_FORMAT, AUDIO_CHN); + + techpoint_write_reg(client, 0x17, 0x00|(DATA_BIT<<2)); + techpoint_write_reg(client, 0x1B, 0x01|(DATA_BIT<<6)); + +#if (AUDIO_CHN == 20) + techpoint_write_reg(client, 0x18, 0x90|(SAMPLE_RATE)); +#else + techpoint_write_reg(client, 0x18, 0x80|(SAMPLE_RATE)); +#endif + +#if (AUDIO_CHN >= 8) + techpoint_write_reg(client, 0x19, 0x1F); +#else + techpoint_write_reg(client, 0x19, 0x0F); +#endif + + techpoint_write_reg(client, 0x1A, 0x15); + techpoint_write_reg(client, 0x37, 0x20); + techpoint_write_reg(client, 0x38, 0x38); + techpoint_write_reg(client, 0x3E, 0x00); + + /* reset audio */ + techpoint_write_reg(client, 0x3d, 0x01); + + techpoint_write_reg(client, 0x40, bank); + + return 0; +} + +static int techpoint_audio_init(struct techpoint *techpoint) +{ + if (techpoint) + techpoint_2855_audio_init(techpoint); + + if (techpoint && techpoint->audio_in) + techpoint_9930_audio_init(techpoint->audio_in->slave_tp[0]); + + return 0; +} + +static int techpoint_audio_dt_parse(struct techpoint *techpoint) +{ + struct device *dev = &techpoint->client->dev; + struct device_node *node = dev->of_node; + const char *str; + u32 v; + + /* Parse audio parts */ + techpoint->audio_in = NULL; + if (!of_property_read_string(node, "techpoint,audio-in-format", &str)) { + struct techpoint_audio *audio_stream; + + techpoint->audio_in = devm_kzalloc(dev, sizeof(struct techpoint_audio), + GFP_KERNEL); + if (!techpoint->audio_in) + return -ENOMEM; + + audio_stream = techpoint->audio_in; + + if (strcmp(str, "i2s") == 0) + audio_stream->audfmt = AUDFMT_I2S; + else if (strcmp(str, "dsp") == 0) + audio_stream->audfmt = AUDFMT_DSP; + else { + dev_err(dev, "techpoint,audio-in-format invalid\n"); + return -EINVAL; + } + + if (!of_property_read_u32(node, "techpoint,audio-in-mclk-fs", &v)) { + switch (v) { + case 256: + break; + default: + dev_err(dev, + "techpoint,audio-in-mclk-fs invalid\n"); + return -EINVAL; + } + audio_stream->mclk_fs = v; + } + + if (!of_property_read_u32(node, "techpoint,audio-in-cascade-num", &v)) + audio_stream->cascade_num = v; + + if (!of_property_read_u32(node, "techpoint,audio-in-cascade-order", &v)) { + if (v > 1) + dev_err(dev, + "audio-in-cascade-order should be 1st chip, otherwise without cascade (is 0)\n"); + else + audio_stream->cascade_order = v; + } + + if (audio_stream->cascade_order == 1) { + struct device_node *np; + int i, count; + + count = of_count_phandle_with_args(node, "techpoint,audio-in-cascade-slaves", NULL); + if (count < 0 || count > MAX_SLAVES) + return -EINVAL; + + for (i = 0; i < count; i++) { + np = of_parse_phandle(node, "techpoint,audio-in-cascade-slaves", i); + if (!np) + return -ENODEV; + } + + for (i = 0; i < g_idx; i++) { + struct techpoint *tp = g_techpoints[i]; + + if (tp->i2c_idx != techpoint->i2c_idx) { + audio_stream->slave_tp[i] = tp; + audio_stream->slave_num++; + } + } + } + } + + techpoint->audio_out = NULL; + if (!of_property_read_string(node, "techpoint,audio-out-format", &str)) { + struct techpoint_audio *audio_stream; + + techpoint->audio_out = devm_kzalloc(dev, sizeof(struct techpoint_audio), + GFP_KERNEL); + if (!techpoint->audio_out) + return -ENOMEM; + + audio_stream = techpoint->audio_out; + + if (strcmp(str, "i2s") == 0) + audio_stream->audfmt = AUDFMT_I2S; + else if (strcmp(str, "dsp") == 0) + audio_stream->audfmt = AUDFMT_DSP; + else { + dev_err(dev, "techpoint,audio-out-format invalid\n"); + return -EINVAL; + } + + if (!of_property_read_u32(node, "techpoint,audio-out-mclk-fs", &v)) { + switch (v) { + case 256: + break; + default: + dev_err(dev, + "techpoint,audio-out-mclk-fs invalid\n"); + return -EINVAL; + } + audio_stream->mclk_fs = v; + } + + if (!of_property_read_u32(node, "techpoint,audio-out-cascade-num", &v)) + audio_stream->cascade_num = v; + + if (!of_property_read_u32(node, "techpoint,audio-out-cascade-order", &v)) { + if (v > 1) + dev_err(dev, + "audio-out-cascade-order should be 1st chip, otherwise without cascade (is 0)\n"); + else + audio_stream->cascade_order = v; + } + } + + if (!techpoint->audio_in && !techpoint->audio_out) + return -ENODEV; + + return 0; +} + +static int techpoint_audio_probe(struct techpoint *techpoint) +{ + struct device *dev = &techpoint->client->dev; + int ret; + unsigned char i; + + switch (techpoint->chip_id) { + case CHIP_TP9930: + techpoint_9930_audio_init(techpoint); + break; + case CHIP_TP2855: + techpoint_2855_audio_init(techpoint); + break; + default: + break; + } + + if (techpoint->chip_id == CHIP_TP9930) { + + techpoint_write_reg(techpoint->client, 0x40, 0x00); + for (i = 0; i < 0xff; i++) + techpoint_write_reg(techpoint->client, i, 0xbb); + } + + ret = techpoint_audio_dt_parse(techpoint); + if (ret) { + dev_info(dev, "hasn't audio DT nodes\n"); + return 0; + } + + ret = techpoint_audio_init(techpoint); + if (ret) { + dev_info(dev, "audio init failed(%d)\n", ret); + return 0; + } + + ret = devm_snd_soc_register_component(dev, + &techpoint_codec_driver, + &techpoint_audio_dai, 1); + if (ret) { + dev_err(dev, "register audio codec failed\n"); + return -EINVAL; + } + + dev_info(dev, "registered audio codec\n"); + + return 0; +} + +static void techpoint_device_release(struct device *dev) +{ + +} + +static int techpoint_sysfs_init(struct i2c_client *client, + struct techpoint *techpoint) +{ + struct device *dev = &techpoint->dev; + int i; + + dev->release = techpoint_device_release; + dev->parent = &client->dev; + set_dev_node(dev, dev_to_node(&client->dev)); + dev_set_name(dev, "techpoint-dev"); + + if (device_register(dev)) { + dev_err(&client->dev, + "Register 'techpoint-dev' failed\n"); + dev->parent = NULL; + return -ENOMEM; + } + + for (i = 0; i < ARRAY_SIZE(techpoint_attrs); i++) { + if (device_create_file(dev, &techpoint_attrs[i])) { + dev_err(&client->dev, + "Create 'techpoint-dev' attr failed\n"); + device_unregister(dev); + return -ENOMEM; + } + } + + return 0; +} + static int techpoint_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -829,6 +1346,8 @@ static int techpoint_probe(struct i2c_client *client, techpoint->client = client; techpoint->supplies = NULL; + techpoint_sysfs_init(client, techpoint); + mutex_init(&techpoint->mutex); sd = &techpoint->subdev; @@ -883,6 +1402,15 @@ static int techpoint_probe(struct i2c_client *client, goto err_clean_entity; } + techpoint->i2c_idx = g_idx; + g_techpoints[g_idx++] = techpoint; + + ret = techpoint_audio_probe(techpoint); + if (ret) { + dev_err(dev, "sound audio probe failed\n"); + goto err_clean_entity; + } + pm_runtime_set_active(dev); pm_runtime_enable(dev); pm_runtime_idle(dev);