前言:
在nxp-imx8mm相关项目中用,使用龙讯的桥接芯片lt9611。在调试过程中,发现,audio挑显示器,在一些显示器上没有声音,主控的i2s clk,data信号都是输出到lt9611上的。于是在排查了一些必要条件后,把audio部分的问题归结到了lt9611 mcu的i2s接口上,即、lt9611 i2s 相关寄存器上。
1.原理图:
i2s:mclk,sclk,ws , data用示波器量都是有信号的。
2.官方提供的单片机mcu参考代码
audio部分:可以看到整个代码中,只有在函数入口初始化了lt9611的i2s相关寄存器。由于lt9611的datasheet没有找到,不知具体的寄存器代表什么意思,有点可惜,但是很确定的是,对i2s寄存器配置,打开了i2s通道。所以重点就是下面的i2s寄存器配置。
void LT9611_Audio_Init(void) //sujin
{
#if 1
if(lt9611.audio_out==audio_i2s)
{
printf("\r\nAudio inut = I2S 2ch");
HDMI_WriteI2C_Byte(0xff,0x84);
HDMI_WriteI2C_Byte(0x06,0x08);
HDMI_WriteI2C_Byte(0x07,0x10);
//48K sampling frequency
HDMI_WriteI2C_Byte(0x0f,0x2b); //0x2b: 48K, 0xab:96K
HDMI_WriteI2C_Byte(0x34,0xd4); //CTS_N 20180823 0xd5: sclk = 32fs, 0xd4: sclk = 64fs
HDMI_WriteI2C_Byte(0x35,0x00); // N value = 6144
HDMI_WriteI2C_Byte(0x36,0x18);
HDMI_WriteI2C_Byte(0x37,0x00);
//96K sampling frequency
//HDMI_WriteI2C_Byte(0x0f,0xab); //0x2b: 48K, 0xab:96K
//HDMI_WriteI2C_Byte(0x34,0xd4); //CTS_N 20180823 0xd5: sclk = 32fs, 0xd4: sclk = 64fs
//HDMI_WriteI2C_Byte(0x35,0x00); // N value = 12288
//HDMI_WriteI2C_Byte(0x36,0x30);
//HDMI_WriteI2C_Byte(0x37,0x00);
//44.1K sampling frequency
/*
HDMI_WriteI2C_Byte(0x0f,0x0b); //0x2b: 48K, 0xab:96K
HDMI_WriteI2C_Byte(0x34,0xd4); //CTS_N 20180823 0xd5: sclk = 32fs, 0xd4: sclk = 64fs
HDMI_WriteI2C_Byte(0x35,0x00); // N value = 6272
HDMI_WriteI2C_Byte(0x36,0x18);
HDMI_WriteI2C_Byte(0x37,0x80);
*/
}
if(lt9611.audio_out==audio_spdif)
{
printf("\r\nAudio inut = SPDIF");
HDMI_WriteI2C_Byte(0xff,0x84);
HDMI_WriteI2C_Byte(0x06,0x0c);
HDMI_WriteI2C_Byte(0x07,0x10);
HDMI_WriteI2C_Byte(0x34,0xd4); //CTS_N
}
#endif
}
void LT9611_Init(void)
{
u32 cnt = 0;
//RESET_LT9611();
LT9611_Chip_ID();
LT9611_System_Init();
//LT9611_RST_PD_Init();
LT9611_MIPI_Input_Analog();
LT9611_MIPI_Input_Digtal();
Timer0_Delay1ms(1000);
LT9611_Video_Check();
LT9611_PLL(video);
LT9611_MIPI_Pcr(video); //pcr setup
LT9611_Audio_Init();
LT9611_CSC();
LT9611_HDCP_Init();
LT9611_HDMI_TX_Digital(video);
LT9611_HDMI_TX_Phy();
LT9611_IRQ_Init();;
//LT9611_Read_EDID();
// LT9611_HDMI_CEC_ON(1);
// lt9611_cec_msg_set_logical_address();
lt9611_cec_msg_init(<9611_cec_msg);
LT9611_Enable_Interrupts(HPD_INTERRUPT_ENABLE, 1);
LT9611_Enable_Interrupts(VID_CHG_INTERRUPT_ENABLE, 0);
LT9611_Enable_Interrupts(CEC_INTERRUPT_ENABLE, 1);
LT9611_Frequency_Meter_Byte_Clk();
LT9611_Dphy_debug();
LT9611_Htotal_Sysclk();
LT9611_Pcr_MK_Print();
printf("\r\n==========================LT9611 Initial End===============================");
Timer0_Delay1ms(200); //HPD have debounce, wait HPD irq.
//while(1);
LT9611_HDP_Interrupt_Handle();
//lt9611_cec_msg_write_demo();
while(1)
{
if(irq_task_flag)
{
//print("\r\nirq task...");
LT9611_IRQ_Task();
irq_task_flag = 0;
set_EPI;
}
// lt9611_cec_msg_mainloop(<9611_cec_msg);
}
}
3.linux lt9611 驱动代码
驱动在probe后,上层调用的时候,先init部分寄存器,在hw有设置了相关的寄存器,并且对比官方参考代码,audio部分设置的寄存器还是有很大差别的。
static int rk_lt9611_audio_init(struct lt9611 *lt)
{
int ret = 0;
printk(KERN_ERR"\r\nAudio input = I2S 2ch");
ret = lt9611_write_byte(lt, BANK_SELECT_ADDR, SYSTEM_CTRL0_BLOCK);
if (ret) {
DRM_ERROR("Failed to select SYSTEM_CTRL0_BLOCK, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0xff,0x82);
ret = lt9611_write_byte(lt, RGO_HDMITX_CTRL0_OFFSET, 0x8e); //0x8e: HDMI, 0x0e:DVI
if (ret) {
DRM_ERROR("Failed to set RGO_HDMITX_CTRL0_OFFSET, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0xd6,0x8c);
ret = lt9611_write_byte(lt, RGO_HDMITX_CTRL1_OFFSET, 0x04);
if (ret) {
DRM_ERROR("Failed to set RGO_HDMITX_CTRL1_OFFSET, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0xd7,0x04); //sync polarity
ret = lt9611_write_byte(lt, BANK_SELECT_ADDR, SYSTEM_CTRL1_BLOCK);
if (ret) {
DRM_ERROR("Failed to select SYSTEM_CTRL1_BLOCK, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0xff,0x84);
ret = lt9611_write_byte(lt, RGO_AUDIO_CTRL0_OFFSET, 0x08);
if (ret) {
DRM_ERROR("Failed to set RGO_AUDIO_CTRL0_OFFSET, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0x06,0x08);
ret = lt9611_write_byte(lt, RGO_AUDIO_CTRL1_OFFSET, 0x10);
if (ret) {
DRM_ERROR("Failed to set RGO_AUDIO_CTRL1_OFFSET, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0x07,0x10);
ret = lt9611_write_byte(lt, RGO_840F_OFFSET_NA, 0x2b); //20190425
if (ret) {
DRM_ERROR("Failed to set RGO_840F_OFFSET_NA, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0x0f,0x2b); //0x29: 48K, 20bit
ret = lt9611_write_byte(lt, RGO_CST_CTRL1_OFFSET, 0xd4);
if (ret) {
DRM_ERROR("Failed to set RGO_CST_CTRL1_OFFSET, ret=%d\n", ret);
return ret;
}
// HDMI_WriteI2C_Byte(0x34,0xd4); //CTS_N
}
static int lt9611_hdmi_hw_params(struct device *dev, void *data,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct lt9611 *lt9611 = data;
pr_info("%s:start hparms->sample_rate =%d\n", __FUNCTION__,hparms->sample_rate);
if (hparms->sample_rate == 48000 || hparms->sample_rate == 44100)
regmap_write(lt9611->regmap, 0x840f, 0x2b);
else if (hparms->sample_rate == 96000)
regmap_write(lt9611->regmap, 0x840f, 0xab);
else
return -EINVAL;
regmap_write(lt9611->regmap, 0x8435, 0x00);
regmap_write(lt9611->regmap, 0x8436, 0x18);
regmap_write(lt9611->regmap, 0x8437, 0x00);
return 0;
}
static int lt9611_audio_startup(struct device *dev, void *data)
{
struct lt9611 *lt9611 = data;
pr_info("%s:start \n", __FUNCTION__);
regmap_write(lt9611->regmap, 0x82d6, 0x8c);
regmap_write(lt9611->regmap, 0x82d7, 0x04);
regmap_write(lt9611->regmap, 0x8406, 0x08);
regmap_write(lt9611->regmap, 0x8407, 0x10);
regmap_write(lt9611->regmap, 0x8434, 0xd5);
return 0;
}
static void lt9611_audio_shutdown(struct device *dev, void *data)
{
struct lt9611 *lt9611 = data;
pr_info("%s:start \n", __FUNCTION__);
regmap_write(lt9611->regmap, 0x8406, 0x00);
regmap_write(lt9611->regmap, 0x8407, 0x00);
}
static int lt9611_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
{
struct of_endpoint of_ep;
int ret;
pr_info("%s:start \n", __FUNCTION__);
ret = of_graph_parse_endpoint(endpoint, &of_ep);
if (ret < 0)
return ret;
/*
* HDMI sound should be located as reg = <2>
* Then, it is sound port 0
*/
if (of_ep.port == 2)
return 0;
return -EINVAL;
}
static const struct hdmi_codec_ops lt9611_codec_ops = {
.hw_params = lt9611_hdmi_hw_params,
.audio_shutdown = lt9611_audio_shutdown,
.audio_startup = lt9611_audio_startup,
.get_dai_id = lt9611_hdmi_i2s_get_dai_id,
};
static struct hdmi_codec_pdata codec_data = {
.ops = <9611_codec_ops,
.max_i2s_channels = 2,
.i2s = 1,
};
static int lt9611_audio_init(struct device *dev, struct lt9611 *lt9611)
{
rk_lt9611_audio_init(lt9611);
codec_data.data = lt9611;
lt9611->audio_pdev =
platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
1,
&codec_data, sizeof(codec_data));
return PTR_ERR_OR_ZERO(lt9611->audio_pdev);
}
static void lt9611_audio_exit(struct lt9611 *lt9611)
{
if (lt9611->audio_pdev) {
platform_device_unregister(lt9611->audio_pdev);
lt9611->audio_pdev = NULL;
}
}
static int lt9611_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
...
return lt9611_audio_init(&client->dev, pdata);
err_sysfs_init:
disable_irq(pdata->irq);
free_irq(pdata->irq, pdata);
err_i2c_prog:
lt9611_gpio_configure(pdata, false);
err_dt_supply:
lt9611_put_dt_supply(&client->dev, pdata);
err_dt_parse:
devm_kfree(&client->dev, pdata);
return ret;
}
4.调试
所以调试过程中,按照官方提示的代码,在linux 驱动中设置了相同的寄存器,并把init,hw相关设置寄存器代码注掉,如 代码1 所示。在系统启动的时候,lt9611_power_on ->lt9611_init_setup会init i2s寄存器,用aplay 播放音频文件,有声音,再次的播放没有声音了。用i2c调试工具,对比了前后的寄存器配置,有差别。
差别产生的原因:系统启动,lt9611_power_on ->lt9611_init_setup 只设置了一次i2s的寄存器,然后在shutdown时,又设置了i2s寄存器,关闭了i2s通道。
所以i2s init 初始化这部分寄存器设置,需要在上层执行aplay的时候,让它每次都设置进去,打开i2s通道。更改代码2 ,这样上层调用的时候,就会每次init一下i2s寄存器,打开i2s,声音就出来了,并且在之前没有声音的显示器上,声音也是正常的。
代码1
static struct lt9611_reg_cfg lt9611_init_setup[] = {
/* LT9611_System_Init */
{0xFF, 0x81, 0},
{0x01, 0x18, 0}, /* sel xtal clock */
/* timer for frequency meter */
{0xff, 0x82, 0},
{0x1b, 0x69, 0}, /*timer 2*/
{0x1c, 0x78, 0},
{0xcb, 0x69, 0}, /*timer 1 */
{0xcc, 0x78, 0},
/* irq init */
{0xff, 0x82, 0},
{0x51, 0x01, 0},
{0x58, 0x0a, 0}, /* hpd irq */
{0x59, 0x80, 0}, /* hpd debounce width */
{0x9e, 0xf7, 0}, /* video check irq */
/* power consumption for work */
{0xff, 0x80, 0},
{0x04, 0xf0, 0},
{0x06, 0xf0, 0},
{0x0a, 0x80, 0},
{0x0b, 0x40, 0},
{0x0d, 0xef, 0},
{0x11, 0xfa, 0},
//+ i2s_init
{0xff, 0x84 ,0},
{0x06, 0x08 ,0},
{0x07, 0x10 ,0},
{0x0f, 0x2b ,0},
{0x34, 0xd4 ,0},
{0x35, 0x00 ,0},
{0x36, 0x18 ,0},
{0x37, 0x00 ,0},
};//static int lt9611_power_on(struct lt9611 *pdata, bool on) probe 调到
static int lt9611_hdmi_hw_params(struct device *dev, void *data,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct lt9611 *lt9611 = data;
#if 0
pr_info("%s:start hparms->sample_rate =%d\n", __FUNCTION__,hparms->sample_rate);
if (hparms->sample_rate == 48000 || hparms->sample_rate == 44100)
regmap_write(lt9611->regmap, 0x840f, 0x2b);
else if (hparms->sample_rate == 96000)
regmap_write(lt9611->regmap, 0x840f, 0xab);
else
return -EINVAL;
regmap_write(lt9611->regmap, 0x8435, 0x00);
regmap_write(lt9611->regmap, 0x8436, 0x18);
regmap_write(lt9611->regmap, 0x8437, 0x00);
#endif
return 0;
}
static int lt9611_audio_startup(struct device *dev, void *data)
{
struct lt9611 *lt9611 = data;
pr_info("%s:start \n", __FUNCTION__);
#if 0
regmap_write(lt9611->regmap, 0x82d6, 0x8c);
regmap_write(lt9611->regmap, 0x82d7, 0x04);
regmap_write(lt9611->regmap, 0x8406, 0x08);
regmap_write(lt9611->regmap, 0x8407, 0x10);
regmap_write(lt9611->regmap, 0x8434, 0xd5);
#endif
return 0;
}
static void lt9611_audio_shutdown(struct device *dev, void *data)
{
struct lt9611 *lt9611 = data;
pr_info("%s:start \n", __FUNCTION__);
regmap_write(lt9611->regmap, 0x8406, 0x00);
regmap_write(lt9611->regmap, 0x8407, 0x00);
}
static int lt9611_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
struct device_node *endpoint)
{
struct of_endpoint of_ep;
int ret;
pr_info("%s:start \n", __FUNCTION__);
ret = of_graph_parse_endpoint(endpoint, &of_ep);
if (ret < 0)
return ret;
/*
* HDMI sound should be located as reg = <2>
* Then, it is sound port 0
*/
if (of_ep.port == 2)
return 0;
return -EINVAL;
}
static const struct hdmi_codec_ops lt9611_codec_ops = {
.hw_params = lt9611_hdmi_hw_params,
.audio_shutdown = lt9611_audio_shutdown,
.audio_startup = lt9611_audio_startup,
.get_dai_id = lt9611_hdmi_i2s_get_dai_id,
};
static struct hdmi_codec_pdata codec_data = {
.ops = <9611_codec_ops,
.max_i2s_channels = 2,
.i2s = 1,
};
static int lt9611_audio_init(struct device *dev, struct lt9611 *lt9611)
{
#if 0
rk_lt9611_audio_init(lt9611);
#endif
codec_data.data = lt9611;
lt9611->audio_pdev =
platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,
1,
&codec_data, sizeof(codec_data));
return PTR_ERR_OR_ZERO(lt9611->audio_pdev);
}
代码2
diff --git a/drivers/gpu/drm/bridge/lt9611.c b/drivers/gpu/drm/bridge/lt9611.c
index 5e3023dddabf..460892f4bd1b 100755
--- a/drivers/gpu/drm/bridge/lt9611.c
+++ b/drivers/gpu/drm/bridge/lt9611.c
@@ -212,6 +212,27 @@ static struct lt9611_reg_cfg lt9611_init_setup[] = {
{0x0b, 0x40, 0},
{0x0d, 0xef, 0},
{0x11, 0xfa, 0},
+
+ //i2s_init
+ {0xff, 0x84 ,0},
+ {0x06, 0x08 ,0},
+ {0x07, 0x10 ,0},
+ {0x0f, 0x2b ,0},
+ {0x34, 0xd4 ,0},
+ {0x35, 0x00 ,0},
+ {0x36, 0x18 ,0},
+ {0x37, 0x00 ,0},
+};
+
+static struct lt9611_reg_cfg i2s_init_set[]={
+ //i2s_init
+ {0xff, 0x84 ,0},
+ {0x06, 0x08 ,0},
+ {0x07, 0x10 ,0},
+ {0x0f, 0x2b ,0},
+ {0x34, 0xd4 ,0},
+ {0x35, 0x00 ,0},
+ {0x36, 0x18 ,0},
};
struct lt9611_timing_info {
@@ -931,7 +952,6 @@ static int lt9611_pcr_setup(struct lt9611 *pdata,
static int rk_lt9611_audio_init(struct lt9611 *lt)
{
int ret = 0;
-
printk(KERN_ERR"\r\nAudio input = I2S 2ch");
ret = lt9611_write_byte(lt, BANK_SELECT_ADDR, SYSTEM_CTRL0_BLOCK);
if (ret) {
@@ -3175,6 +3195,7 @@ static int lt9611_hdmi_hw_params(struct device *dev, void *data,
{
struct lt9611 *lt9611 = data;
+#if 0
pr_info("%s:start hparms->sample_rate =%d\n", __FUNCTION__,hparms->sample_rate);
if (hparms->sample_rate == 48000 || hparms->sample_rate == 44100)
regmap_write(lt9611->regmap, 0x840f, 0x2b);
@@ -3186,7 +3207,7 @@ static int lt9611_hdmi_hw_params(struct device *dev, void *data,
regmap_write(lt9611->regmap, 0x8435, 0x00);
regmap_write(lt9611->regmap, 0x8436, 0x18);
regmap_write(lt9611->regmap, 0x8437, 0x00);
-
+#endif
return 0;
}
@@ -3195,6 +3216,7 @@ static int lt9611_audio_startup(struct device *dev, void *data)
struct lt9611 *lt9611 = data;
pr_info("%s:start \n", __FUNCTION__);
+#if 0
regmap_write(lt9611->regmap, 0x82d6, 0x8c);
regmap_write(lt9611->regmap, 0x82d7, 0x04);
@@ -3202,7 +3224,9 @@ static int lt9611_audio_startup(struct device *dev, void *data)
regmap_write(lt9611->regmap, 0x8407, 0x10);
regmap_write(lt9611->regmap, 0x8434, 0xd5);
-
+#endif
+ //printk("i2s_init \r\n");
+ lt9611_write_array(lt9611, i2s_init_set,sizeof(i2s_init_set));//上层每次都会调到
return 0;
}
@@ -3251,7 +3275,9 @@ static struct hdmi_codec_pdata codec_data = {
static int lt9611_audio_init(struct device *dev, struct lt9611 *lt9611)
{
+#if 0
rk_lt9611_audio_init(lt9611);
+#endif
codec_data.data = lt9611;
lt9611->audio_pdev =
platform_device_register_data(dev, HDMI_CODEC_DRV_NAME,