基于 NXP i.MX8QM Bumblebee 智能座舱 SAF775C Radio + Audio 功能调试

一. 前言


SAF775C 是恩智浦汽车推出的将汽车收音机和音频 DSP 系统完全集成在单个芯片上的第三代产品 ,接下来我们将从功放调试 ,FM 调试 ,这两个方面来进行介绍


二. SAF775C Audio 功能调试

首先介绍一下功放的数据流程图 :


                                                             图1 SAF775C 功放数据流程图

i.MX8QM 和 SAF775C 使用 I2S 信号进行数据传输 ,SAF775C 作为 Master 模式 ,i.MX8QM 作为 Slaver 模式 ,SAF775C 不需要 MCLK ,SAF775C发出 BCLK 和 WCLK ,i.MX8QM 接收BCLK 和 WCLK后发送 Data 数据 ,SAF775C 把收到的数据通过 I2S 信号发送给音频放大器 TDF8532 ,TDF8532 接收数据信号后把多路声音数据输出到喇叭。

1) SAF775C I2C 通信

SAF775C 开机后默认上电 ,不需要进行 Enable 操作 ,通过 i2cdetect 命令查看 I2C 地址为 0x1C


                                              图2  i2cdetect 命令查看 I2C 地址

2)SAF775C初始化

+static s32 saf775c_reg_init(struct i2c_client *i2c_client)

+{

+   struct device *dev = &i2c_client->dev;

+   u8 i2cAddr;

+   int i,j,k,num;

+   char i2cValue[30] ;

+   i2cAddr =  i2c_client->addr ;

+   i2c_client->addr = 0x1c;    /* change i2c address from ov9716 to ti964 */  

+    i2c_client_vol = i2c_client;

+   k = 0;

+   i = 0;

+   j = 0;

+   num = sizeof (saf775c_reg_defaults)/sizeof(char);

+   printk("=== sizeof (&saf775c_reg_defaults[0])= %d ===\r\n" , num);

+   for ( j= 0;j < num ;j++)

+   {     

+     if (saf775c_reg_defaults[j] == 256)

+     {

+       if (i2c_master_send(i2c_client,i2cValue, k ) < 0)

+       {       

+           dev_err(dev, "Write saf775c reg error: reg=%x \n", saf775c_reg_defaults[i]);     

+           i2c_client->addr = i2cAddr ;

+           return -1;   

+       }      

+       k = 0;

+       memset(i2cValue,0,30);         

+       i++;

+       if (i >= 803)

+       {

+         break;

+       }

+       continue;

+     }

+     else

+     {

+         i2cValue[k++] = saf775c_reg_defaults[j] ;

+     }

+   }

++

+    i2c_client->addr = i2cAddr ;

+}

 

由于 SAF775C 初始化参数太多 ,仅列出部分

+static const  int saf775c_reg_defaults[] =

+{

+ 0xE1,0x1A,0x10,0x74,0x80,0x5A,0xB3,0x7E,0xAA,0xC6,0x6B,0xB0,0x93,0x28,0xD1,

+ 0x5F,0x28,0x01,0x07,0xF8,0x68,0x67,0xDA,0x6B,0xE5,256,

+ 0xEF,0x00,256,

+ 0xE8,256,

+ 0xC7,0x01,0x00,0x32,0x00,0x32,256,

+ 0xC7,0x02,0x1C,0x24,0x34,0x54,256,

+ 0xC7,0x03,0x20,256,

+ 0x09,0xC1,256,

+ 0xC2,0x01,0x00,0x51,256,

+ 0xC2,0x05,0x00,256,

……………………………………………..

+ 0xF4,0x00,0x79,0x00,0x0B,0xE3,256,

+ 0xF4,0x00,0x78,0x00,0x76,0xE3,256,

+ 0xF4,0x40,0x9C,0x07,0xFF,256,

+ 0xF4,0x40,0x9D,0x07,0xFF,256,

+ 0xF4,0x40,0x9E,0x07,0xFF,256,

+ 0xF4,0x40,0x9F,0x07,0xFF,256,

+ 0xF4,0x40,0xA0,0x07,0xFF,256,

+ 0xF4,0x40,0xA1,0x07,0xFF,256,

+ 0xF4,0x00,0x5A,0x00,0x17,0xC7,256,

+ 0xF4,0x00,0x59,0x00,0xED,0xC6,256,

 

TDF8532 初始化 :

+static s32 TDF8532_reg_init(struct i2c_client *i2c_client)

+{

+     struct device *dev = &i2c_client->dev;

+     u8 i2cAddr;

+     int i,j,k,num;

+     char i2cValue[55] ;

+

+     i2cAddr =  i2c_client->addr ;

+     i2c_client->addr = 0x6c;

+    

+     k = 0;

+     i = 0;

+     j = 0;

+     num = sizeof (tdf8532_reg_defaults)/sizeof(char);

+     printk("=== sizeof (&tdf8532_reg_defaults[0])= %d ===\r\n" , num);

+     for ( j= 0;j < num ;j++)

+     {        

+       if (tdf8532_reg_defaults[j] == 256)

+       {

+            //printk("== i2cValue[%d] len = %d : 0x%02x ==\r\n" ,i,k,i2cValue[0]);

+            if (i2c_master_send(i2c_client,i2cValue, k ) < 0)

+            {       

+                   dev_err(dev, "Write tdf8532 reg error: reg=%x \n", tdf8532_reg_defaults[i]);     

+                   i2c_client->addr = i2cAddr ;

+                   return -1;   

+            }            

+            k = 0;

+            memset(i2cValue,0,55);                 

+            i++;

+            if (i >= 13)

+            {

+              break;

+            }

+            continue;

+       }

+       else

+       {

+              i2cValue[k++] = tdf8532_reg_defaults[j] ;

+       }

+     }

+     i2c_client->addr = i2cAddr ;

+}

 

TDF8532 参数部分

+static const int tdf8532_reg_defaults[] =

+{

+    0x02 ,0x00 ,0x31 ,0x80 ,0x2A ,0x04 ,0x01 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x01 ,0x02 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x01 ,0x03 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x01 ,0x04 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x01 ,0x61 ,0xFE , 256 ,

+     0x02 ,0x00 ,0x09 ,0x80 ,0x12 ,0x01 ,0x03 ,0x40 ,0x01 ,0x03 ,0x01 ,0x00 , 256 ,

+     0x02 ,0x00 ,0x04 ,0x80 ,0x8B ,0x00 ,0x1A , 256 ,

+     0x02 ,0x00 ,0x0B ,0x80 ,0x20 ,0x04 ,0x01 ,0x01 ,0x02 ,0x01 ,0x03 ,0x01 ,0x04 ,0x01 , 256 ,

+     0x02 ,0x00 ,0x05 ,0x80 ,0x16 ,0x05 ,0x00 ,0x00 , 256 ,

+     0x02 ,0x00 ,0x0A ,0x80 ,0x28 ,0x00 ,0x01 ,0x01 ,0x01 ,0x00 ,0x00 ,0x00 ,0x01 , 256 ,

+ 0x02 ,0x00 ,0x13 ,0x80 ,0x22 ,0x04 ,0x01 ,0x03 ,0x00 ,0x00 ,0x02 ,0x03 ,0x00 ,0x00 ,0x03 ,0x03 ,0x00 ,0x00 ,0x04 ,0x03 ,0x00 ,0x00 , 256 ,

+ 0x02 ,0x00 ,0x18 ,0x80 ,0x23 ,0x04 ,0x09 ,0x01 ,0x02 ,0x00 ,0x00 ,0x00 ,0x02 ,0x02 ,0x00 ,0x00 ,0x00 ,0x03 ,0x02 ,0x00 ,0x00 ,0x00 ,0x04 ,0x02 ,0x00 ,0x00 ,0x00 , 256 ,

+     0x02 ,0x00 ,0x05 ,0x80 ,0x32 ,0x03 ,0x00 ,0x0F , 256 ,

+ 0x02 ,0x00 ,0x31 ,0x80 ,0x2A ,0x04 ,0x01 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x02 ,0x02 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x02 ,0x03 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x02 ,0x04 ,0x05 ,0x0B ,0x5B ,0x0A ,0x14 ,0x8A ,0x05 ,0x09 ,0x3F ,0x02 ,0x4F ,0x51 , 256 ,

+     0x02 ,0x00 ,0x08 ,0x80 ,0x1C ,0x00 ,0x00 ,0x00 ,0x04 ,0x00 ,0x01 , 256 ,

+     0x02 ,0x00 ,0x08 ,0x80 ,0x1C ,0x00 ,0x01 ,0x00 ,0x04 ,0x00 ,0x01 , 256 ,

+     0x02 ,0x00 ,0x03 ,0x80 ,0x1A ,0x01 , 256     

+};


saf775c_i2c_probe()函数

+static int saf775c_i2c_probe(struct i2c_client *i2c,

+     const struct i2c_device_id *id)

+{

+     struct saf775c_priv *saf775c;

+     struct device_node *np = i2c->dev.of_node;

+     const struct regmap_config *regmap_config = &saf775c_i2c_regmap;

+     int ret;

+     printk("[pual_audio] Enter %s - %d -- \n",__func__,__LINE__);

+     saf775c = devm_kzalloc(&i2c->dev, sizeof(*saf775c), GFP_KERNEL);

+     if (saf775c == NULL)

+            return -ENOMEM;

+     saf775c_i2c = devm_kzalloc(&i2c->dev, sizeof(struct saf775c_i2c), GFP_KERNEL);

+     if (!saf775c_i2c)

+            return -ENOMEM;

+     saf775c_i2c->i2c_client = i2c;

+     saf775c_i2c->dev = &saf775c_i2c->i2c_client->dev;

+    Tdf8532_StartUp();

+     saf775c->regmap = devm_regmap_init_i2c(i2c, regmap_config);

+     if (IS_ERR(saf775c->regmap)) {

+            ret = PTR_ERR(saf775c->regmap);

+            dev_err(&i2c->dev, "Failed to allocate register map: %d\n",

+                   ret);

+            return ret;

+     }

+    saf775c_reg_init(i2c);

+    TDF8532_reg_init(i2c);

+     /*ret = of_get_named_gpio(np, "gpio-reset", 0);

+     if ((ret > 0) && gpio_is_valid(ret)) {

+            devm_gpio_request_one(&i2c->dev, ret, GPIOF_OUT_INIT_HIGH, "reset");

+     }*/

+     saf775c->dev = &i2c->dev;

+     dev_set_drvdata(saf775c->dev, saf775c);

+     major = register_chrdev(0,"myled",&myled_drv_fops);

+     myled_class = class_create(THIS_MODULE, "myled");

+     myled_class_devs = device_create(myled_class, NULL, MKDEV(major, 0), NULL, "myled");

+     return snd_soc_register_codec( &i2c->dev, &soc_codec_driver_saf775c,

+            saf775c_dai_driver, ARRAY_SIZE(saf775c_dai_driver));

+}

 

saf775c_of_match ,与 DTS 匹配进行驱动加载

+static const struct of_device_id saf775c_of_match[] = {

+     { .compatible = "nxp, saf775c" },

+     {},

+};

+MODULE_DEVICE_TABLE(of, saf775c_of_match);

 

在 sound/soc/fsl/ 目录下增加imx-saf775c.c 文件 ,这个文件使用 devm_snd_soc_register_card()来注册声卡,驱动编写可以参考 imx-wm8960.c ,

由于整个驱动内容较多,我只列出平台设备 “compatible” 部分的代码

+static const struct of_device_id imx_saf775c_dt_ids[] = {

+     { .compatible = "fsl,imx-audio-saf775c", },

+     { /* sentinel */ }

+};

+MODULE_DEVICE_TABLE(of, imx_saf775c_dt_ids);

+

+static struct platform_driver imx_saf775c_driver = {

+     .driver = {

+            .name = "imx-saf775c",

+            .pm = &snd_soc_pm_ops,

+            .of_match_table = imx_saf775c_dt_ids,

+     },

+     .probe = imx_saf775c_probe,

+     .remove = imx_saf775c_remove,

+};

+module_platform_driver(imx_saf775c_driver);


3)DTS 设置

--- a/arch/arm64/boot/dts/freescale/fsl-imx8qm-mek.dtsi

+++ b/arch/arm64/boot/dts/freescale/fsl-imx8qm-mek.dtsi

 

@@ -158,10 +158,13 @@

      sound: sound {

             compatible = "fsl,imx7d-evk-wm8960",

-                       "fsl,imx-audio-wm8960";

-             model = "wm8960-audio";

+                      "fsl,imx-audio-saf775c";

+            model = "saf775c-audio";

             cpu-dai = <&sai1>;

-             audio-codec = <&wm8960>;

+            /*audio-codec = <&wm8960>;*/

+            audio-codec = <&saf775c>;

             codec-master;

             /*

              * hp-det = ;

 

@@ -888,6 +892,20 @@

      pinctrl-0 = <&pinctrl_i2c0>;

      status = "okay";

 

+     saf775c: saf775c@1d {

+            compatible = " nxp, saf775c";

+            reg = <0x1d>;

+            clocks = <&clk IMX8QM_AUD_MCLKOUT0>;

+            clock-names = "mclk";

+            wlf,shared-lrclk;

+            power-domains = <&pd_mclk_out0>;

+            assigned-clocks = <&clk IMX8QM_AUD_PLL0_DIV>,

+                          <&clk IMX8QM_AUD_ACM_AUD_PLL_CLK0_DIV>,

+                          <&clk IMX8QM_AUD_ACM_AUD_REC_CLK0_DIV>,

+                          <&clk IMX8QM_AUD_MCLKOUT0>;

+            assigned-clock-rates = <786432000>, <49152000>, <12288000>, <12288000>;

+     };

+

 

4)音量设置

主要是通过 0xF4 ,0x40,0x84 和 0xF4 ,0x40,0x85 这两组寄存器对音量进行控制

+void Audio_DrvSetVolume(int Volume)

+{

+     int Volume_Data01,Volume_Data02;

+     char i2cValue[5] ;

+    

+     i2c_client_vol->addr = 0x1c;

+    

+     Volume_Data01=table_dB2Lin[Volume][0];

+     Volume_Data02=table_dB2Lin[Volume][1];                

+    

+     i2cValue[0] = 0xF4;

+     i2cValue[1] = 0x40;

+     i2cValue[2] = 0x84;

+     i2cValue[3] = (char)(Volume_Data01>>8);

+     i2cValue[4] = (char)(Volume_Data01&0xFF);

+     printk("Audio_DrvSetVolume Volume_Data01 : %02x ,%02x\r\n",i2cValue[3],i2cValue[4]);

+     if (i2c_master_send(i2c_client_vol,i2cValue, 5 ) < 0)

+     {       

+            printk("Volume_Data01 i2c_master_send err\r\n");

+            return;   

+     }

+    

+     i2cValue[0] = 0xF4;

+     i2cValue[1] = 0x40;

+     i2cValue[2] = 0x85;

+     i2cValue[3] = (char)(Volume_Data02>>8);

+     i2cValue[4] = (char)(Volume_Data02&0xFF);

+     printk("Audio_DrvSetVolume Volume_Data02 : %02x ,%02x\r\n",i2cValue[3],i2cValue[4]);

+     if (i2c_master_send(i2c_client_vol,i2cValue, 5 ) < 0)

+     {       

+            printk("Volume_Data02 i2c_master_send err\r\n");

+            return;  

+     }     

+}

+

 

音量控制的 Table ,把音量划分为 21 级

+const int table_dB2Lin[21][2] =

+{

+     {0x005F , 0x0008} ,

+     {0x00F0 , 0x0008} ,

+     {0x00E6 , 0x0012} ,

+     {0x00E6 , 0x0032} ,

+     {0x00E6 , 0x0052} ,

+     {0x00E6 , 0x0072} ,

+     {0x00E6 , 0x0092} ,

+     {0x00E6 , 0x00BB} ,

+     {0x00E6 , 0x00D6} ,

+     {0x00E6 , 0x00F6} ,

+     {0x00E6 , 0x01B1} ,

+     {0x00E6 , 0x0145} ,

+     {0x0109 , 0x0145} ,

+     {0x0212 , 0x0145} ,

+     {0x0323 , 0x0145} ,

+     {0x04C3 , 0x0145} ,

+     {0x0649 , 0x0145} ,

+     {0x07FF , 0x0145} ,

+     {0x07FF , 0x0307} ,

+     {0x07FF , 0x0497} ,

+     {0x07FF , 0x060F}

+};

 

5) 声音测试

下面 HILI 的地方就是接喇叭的地方 ,四个声音通道分别是 :FL/FR/RL/RR

                                         图3 喇叭接线位置图

在 external/tinyalsa带有 tinyplay 播放工具 ,通过 mmm external/tinyalsa 编译后在下面目录out/target/product/mek_8q/system/bin/tinyplay生成 tinyplay 应用

通过 tinyplay test.wav 命令进行播放


三. FM 调试

1)  FM 通道切换

对 0x20 寄存器写 0x00 是 FM通道 ,对 0x20 寄存器写 0x14 是切换到 i,MX8QM 通道

2)频率设置

+static s32 saf775c_frequence_set(u8 *Buf);

+

+void Audio_DrvSetVolume(int Volume)

+{

+     int Volume_Data01,Volume_Data02;

+     char i2cValue[5] ;

+    

+     i2c_client_vol->addr = 0x1c;

+    

+     Volume_Data01=table_dB2Lin[Volume][0];

+     Volume_Data02=table_dB2Lin[Volume][1];                

+    

+     i2cValue[0] = 0xF4;

+     i2cValue[1] = 0x40;

+     i2cValue[2] = 0x84;

+     i2cValue[3] = (char)(Volume_Data01>>8);

+     i2cValue[4] = (char)(Volume_Data01&0xFF);

+     printk("Audio_DrvSetVolume Volume_Data01 : %02x ,%02x\r\n",i2cValue[3],i2cValue[4]);

+     if (i2c_master_send(i2c_client_vol,i2cValue, 5 ) < 0)

+     {       

+            printk("Volume_Data01 i2c_master_send err\r\n");

+            return;   

+     }

+    

+     i2cValue[0] = 0xF4;

+     i2cValue[1] = 0x40;

+     i2cValue[2] = 0x85;

+     i2cValue[3] = (char)(Volume_Data02>>8);

+     i2cValue[4] = (char)(Volume_Data02&0xFF);

+     printk("Audio_DrvSetVolume Volume_Data02 : %02x ,%02x\r\n",i2cValue[3],i2cValue[4]);

+     if (i2c_master_send(i2c_client_vol,i2cValue, 5 ) < 0)

+     {       

+            printk("Volume_Data02 i2c_master_send err\r\n");

+            return;  

+     }     

+}

+


3)自动搜台

根据devSAF775x_Radio_Get_Quality_Data 读取信号质量 ,然后通过 bool Radio_CheckStation(void) 来进行判断是否有电台存在

+int devSAF775x_Radio_Get_Quality_Data (unsigned char *usn,unsigned char *wam,int16_t *offset)

+{

+     char buf[3] = {0};

+

+     if(saf775c_read_reg_1(0x02,buf,3) == SUCCESS)

+     {

+            *usn = ((int16_t)(100 * (unsigned short int)buf[0])>>8);

+            *wam = ((int16_t)(100 * (unsigned short int)buf[1])>>8);

+            *offset = 10 * (int16_t)(0x7f & buf[2]);

+     }

+     return SUCCESS;

+}

+

+static int Radio_Get_Data(unsigned char *usn,unsigned char *wam,int16_t *offset)

+{

+     // if(Is_Radio_Dirana3)

+     {

+            if(SUCCESS == devSAF775x_Radio_Get_Quality_Data (usn,wam,offset))

+            {

+                   return SUCCESS;

+            }

+     }

+    

+     return !SUCCESS;

+}

+

+bool Radio_CheckStation(void)

+{

+     unsigned char threshold;

+     char usn, wam;

+     int16_t offset;

+     char CheckIfStep;

+     char fm_level = 0;

+     msleep(5);

+     fm_level = Radio_Get_Level();

+     if ((fm_level < FM_SCAN_LEVEL)||(fm_level >FM_SCAN_LEVEL_HI))

+     {//exit check

+            CheckIfStep = NO_STATION;

+            return 0;

+     }

+     else

+     {

+            msleep(5);

+     }

+

+     if(Radio_Get_QRS() < RADIO_USN_AVAILABLE_TIME)    //wait usn... available

+            return 0;

+     if(SUCCESS == Radio_Get_Data(&usn,&wam,&offset))

+     {

+            if (((usn<FM_USN_DISTURBANCE)&&(wam < FM_WAM_DISTURBANCE)&&(offset < FM_FREQ_OFFSET)))

+            {

+              CheckIfStep = PRESENT_STATION ;

+              printk("====Radio_CheckStation is lock  ====\r\n");

+              msleep(5);

+              return SUCCESS;

+            }

+     }

+     return 0;

+}

+


调通 SAF775C 这颗 IC ,首先要实现 i.MX8QM 和 SAF775C 的 I2C 通信 ,及实现 i.MX8QM 和  TDF8532 的 I2C 通信 , 然后逐步实现 i.MX8QM 和 SAF775C 的 I2S 通信 ,及  SAF775C 和 TDF8532 的   I2S 通信 ,待声音正常输出后开始调试 SAF775C 的 FM 功能 ,SAF775C 的 FM 功能主要实现了 STEP UP/DOWN ,SCAN  UP/DOWN ,及 VOLUME UP/DOWN  ,通过以上的内容介绍 ,相信大家已经了解了如何调试 SAF775C 的 Audio 功能和 FM 功能。



四 参考文档

【1】 SAF775D_UM_Radio.pdf    SAF775D User Manual    Firmware R8.0    Rev. 1.00 — 20 June 2017 

【2】 SAF775D_UM_Audio.pdf    Audio Software version 1.0 User Manual  Rev. 1.0 — 01 June 2017

★博文内容均由个人提供,与平台无关,如有违法或侵权,请与网站管理员联系。

★文明上网,请理性发言。内容一周内被举报5次,发文人进小黑屋喔~

评论