全志A20的板子,記錄下來,方便自己隨時復習。
自己按照以前熟悉的驅動框架模型來寫,發現測試時 echo 1 > /dev/KT0810SG write()函數會一直被調用。
測試I2C讀寫時,發現讀芯片ID的數值總是 0xFF 經過一上午的折騰,確定是硬件問題,I2C的上拉電壓只有2.5V,改成3V 成功讀取ID值 == 0xb002
下午的時候,在執行初始化 KT0810SG 函數時,需要檢測晶振和System PLL 時鐘是否準備好,但總是發現 System PLL 未準備好,
原因為 A20的提供的 32.768KHz 時鐘沒有送到 KT0810SG 芯片里,是硬件焊接錯誤。 已經解決。
現在 遇到的問題是,芯片沒有聲音輸出。
昨晚回去之后懷疑I2C寫寄存器 的函數有問題,沒有把數據寫進去。
早上調試了很多次,發現有一個奇怪的現象,那就是寫的時候 高低字節不需要調換,但是讀取出來的時候需要調換高低字節。
中午,設置了8980頻率,居然有聲音了。后來發現有些頻率是沒有聲音的,所以最好多試試幾個頻率。
下午遇到的問題是,收到的臺數很少且噪音很大。懷疑是硬件問題,因為目前只是調試芯片,所以芯片都是直接在板子上飛線到芯片上的。干擾性會很大。
總結:
完全可以按照自己以前的驅動框架寫fops方式:
一開始 write()會一直被調用可能是因為 echo命令會判斷 如果寫入失敗就會一直寫,而write()剛好被我寫成返回0。
1、先拿到原廠代碼,最好要原廠提供完整的 demo 源碼工程。
2、大概了解原廠代碼,分析 demo 源碼的執行流程以及各個函數的功能
3、修改sys_config.fex 啟用使能I2C通道2
4、編寫I2C驅動框架,修改 原廠代碼的I2C讀寫函數(移植過程)
5、在驅動中根據 demo 源碼分析出來的流程去調用原廠代碼 若不知道流程,最好詢問原廠。
6、預留接口給上層。如設置收音頻率等 可以用 echo 123 >設備節點文件 來調試
問題點:
1、KT0810SG 是采用I2C協議通訊,在測試I2C讀寫時,發現讀取芯片ID總是0xFF,讀取其他的寄存器的值也不對。
硬件問題:I2C的SDA、SCL線電壓只有2.5V,需要提升至3V,MCU的高電平在3.3V左右
2、KT0810SG 在 KT_FMInit(void) 初始化總是失敗。
uchar KT_FMInit(void) //0->Fail 1->Success
{
...
...
for (i=0;i<INIT_FAIL_TH;i++)
{
Delay_ms(500);
regx=KT_Bus_Read(0x12);
if ((regx&0x8800)!=0x8800) // 查看芯片手冊,這里檢測的是晶振和System PLL 是否準備好
continue;
break;
}
if (i==INIT_FAIL_TH)
return(0);
...
...
}
KT0810SG 的時鐘是由 A20 提供,32.768KHz 。
硬件問題:時鐘線接錯位置,導致 A20 提供的時鐘沒法送到 KT0810SG 。
檢測方法:將時鐘線斷開,示波器去測 A20 引腳看是否有頻率,現場是有的,但是接回芯片頻率就不正常。
3、根據原廠流程以及寫好驅動,但是沒有雜音輸出。
可能的原因:I2C 寫寄存器不成功,導致初始化失敗。 還有一個可能性就是 設置的頻率剛剛好沒聲音輸出,可以多設置幾個頻率試試。
注意點:
在修改原廠驅動的I2C讀寫函數時,因為 KT0810SG芯片的寄存器是16位 ,I2C讀函數讀出的數據是兩個字節
需要將兩個字節的數據調換即高字節和低字節互換 0x1234 => 0x3412 而I2C寫函數則不需要交換
修改增加裸板程序的I2C 讀寫函數:(奇怪的是讀的時候高八位和低八位互換才能用。而寫的時候則不需要調換)
static unsigned char i2c_write_reg(unsigned char reg,unsigned short data) // 可用
{
unsigned char buf[3] = {0};
unsigned short a, b;
printk("%s - %s reg = 0x%X data = 0x%X o(∩_∩)o~~!\n", FMMSG, __func__, reg, data);
/*
// 高低八位互換
buf[0] = reg;
buf[1] = ((data << 8) & 0xFF00) >> 8; // 因為data是兩個字節 而buf[] 是一個字節
buf[2] = (data >> 8) & 0x00FF;
*/
// 高低不換 這里不能對換 否則無法收到臺。
buf[0] = reg; // 寄存器
buf[2] = ((data << 8) & 0xFF00) >> 8;
buf[1] = (data >> 8) & 0x00FF;
printk("+++write true buf[0]=0x%X buf[1]=0x%X buf[2]=0x%X\n", buf[0], buf[1], buf[2]);
i2c_master_send(FM_dev->FM_client, buf, sizeof(buf));
return 0;
}
static unsigned int i2c_read_reg(int reg) // 可用
{
int ret;
unsigned short data = 0, a = 0, b = 0;
struct i2c_msg msgs[] = {
{
.addr = FM_dev->FM_client->addr,
.flags = 0,
.len = 1, // 1個字節
.buf = ®, // 寄存器
},
{
.addr = FM_dev->FM_client->addr,
.flags = I2C_M_RD,
.len = 2, // 兩個字節的空間
.buf = &data, // 用于存放讀取出來的數據
}
};
ret = i2c_transfer(FM_dev->FM_client->adapter, msgs, 2);
if(ret < 0)
printk("read error~~~~~~~~~~~~~~~");
else
{
printk("read ok reg = 0x%x data = 0x%x!!!\n", reg, data);
a = (data << 8) & 0xFF00; // 有些奇怪,需要互換高低八位
b = (data >> 8) & 0x00FF;
data = a | b;
printk("read ok reg = 0x%x data = %d | %d = 0x%x!!!\n", reg, a, b , data);
}
return data;
}
意外收獲:
FM5807驅動流程分析(這種方式并沒有實現fops結構體來給上層提供接口,有些地方不是很明白,也算是一種有趣的實現方式):
原來的驅動寫的有些亂,我簡化了一些。
#define FM_CHRDEV_NAME "KT0810SG"
module_init(FM_init); // 定義驅動入口方式
module_exit(FM_exit); // 定義驅動出口方式
//函數入口
static int __init FM_init(void)
{
// 讀取配置文件
// 保存從配置文件里面選擇的I2C通道
// 設置輸出32.768KHz時鐘
twi_id = 2; // 會用在FM_detect()
// 加載I2C驅動
i2c_add_driver(&FM_drv);
return 0;
}
// 全局變量
static __u32 twi_id = 0;
// 定義FM I2C驅動結構體
struct i2c_driver FM_drv = {
.class = I2C_CLASS_HWMON, // 不能忽略,表示去哪些適配器上找設備
.detect = FM_detect, // 該函數確定能否找到address_list里的設備
.probe = FM_probe, // 如果匹配就調用probe
.remove = __devexit_p(FM_remove),
.driver = {
.name = "FM_drivce",
.owner = THIS_MODULE,
},
.id_table = FM_id, //支持的設備列表
.address_list = normal_i2c, // I2C設備地址列表
};
// FM芯片的設備地址
static const unsigned short normal_i2c[2] = {0x37, I2C_CLIENT_END};
// 實現FM_detect()
static int FM_detect(struct i2c_client *client, struct i2c_board_info *info)
{
struct i2c_adapter *adapter = client->adapter;
int ret = 0, i =0;
printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
// 檢查功能
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -ENODEV;
if(twi_id != adapter->nr) // 匹配FM芯片所在的I2C通道 匹配成功才能回調Proc()
return -ENODEV;
else
strlcpy(info->type, FM_CHRDEV_NAME, I2C_NAME_SIZE);// 若匹配則拷貝名字 成功匹配會調用probe()函數
return 0;
}
// 實現FM_probe()
int FM_probe(struct i2c_client *client, const struct i2c_device_id *device_id)
{
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
{
printk("%s_check_functionality_failed!\n", __func__);
return -ENODEV;
}
FM_dev = kmalloc(sizeof(struct FM_device*), GFP_KERNEL);
if( NULL == FM_dev)
{
printk("%s - %s no memory for kmalloc!\n", FMMSG, __func__);
return -ENOMEM;
}
// 獲取Client 保存到全局變量中
FM_dev->FM_client = client;
FM_dev->FM_client->driver = &FM_drv;
class_register(&FM_attrs_class); // 重點,這里會注冊在/sys/class/設備名 具體看 FM_attrs_class
INIT_DELAYED_WORK(&open_pa, open_pa_func); // 這里是工作隊列 初始化
__cancel_delayed_work(&open_pa.work); // 取消調度
schedule_delayed_work(&open_pa.work, 100); // 100ms后調用 open_pa_func()
return 0;
}
struct delayed_work open_pa;
// 定義
struct FM_device{
struct i2c_client *FM_client;
struct device *FM_device;
struct class *FM_class;
};
struct FM_device *FM_dev;
// echo 10 >/sys/class/kt0810sg/cmd 命令會將參數傳遞到 FM_cmd_store()
// 這里不太明白 FM_status_sho()、FM_cmd_show() 作用
static struct class_attribute FM_attrs[] = {
__ATTR(status, 0777, FM_status_show, FM_status_store), // 構成 /sys/class/kt0810sg/status 節點
__ATTR(cmd, 0777, FM_cmd_show, FM_cmd_store), // 構成 /sys/class/kt0810sg/cmd 節點
__ATTR_NULL
};
static struct class FM_attrs_class = {
.name = "kt0810sg", // 構成/sys/class/kt0810sg 目錄
.class_attrs = FM_attrs,
};
// 工作隊列
static void open_pa_func(struct work_struct *work)
{
printk("-------------------open pa \n");
// 這里用于關pa 于框架沒有實際意義。
}
// 實現上面需的回調函數 echo 10 >/sys/class/kt0810sg/cmd 會傳進來
static ssize_t FM_cmd_store(struct device *dev, struct device_attribute *attr, const char *buf, ssize_t count)
{
unsigned long data = 0;
int command = 0;
printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
strict_strtoul(buf, 10, &data);
command = data;
printk("%s_Revc = %d\n", __func__, command);
// 在 0-15 之間則表示直接設定音量
if(command >= FM_CMD_SETVOLUMEMIN && command <= FM_CMD_SETVOLUMEMAX )
{
printk("Cmd is Set Volume %d \n", command);
KT_FMVolumeSet(command);
goto retu;
}
// 在 8700-10800 之間則表示直接設定頻率
if(command >= FM_CMD_FREQMIN && command <= FM_CMD_FREQMAX )
{
printk("Cmd is Set Freq %d \n", command);
KT_User_FMTune(command);
KT_FMUnMute();
goto retu;
}
// 剩余的表示各種命令
switch(command)
{
case FM_CMD_AUTOSCAN: // 自動搜臺
printk("cmd = FM_CMD_AUTOSCAN ---> OK\n");
ScanFM();
break;
case FM_CMD_SELECTUP: // 上一個臺
printk("cmd = FM_CMD_SELECTUP ---> OK\n");
break;
case FM_CMD_SELECTDOWN: // 下一個臺
printk("cmd = FM_CMD_SELECTDOWN ---> OK\n");
break;
}
retu:
return count; // 返回值必須指定成功接收了多少個字節,若返回0 那么此函數會一直被回調
}
static ssize_t FM_cmd_show(struct device *dev, struct device_attribute *attr, char *buf)
{
printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
return 1;
}
static ssize_t FM_status_store(struct device *dev, struct device_attribute *attr, const char *buf, ssize_t count)
{
printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
return count;
}
static ssize_t FM_status_show(struct device *dev, struct device_attribute *attr, char *buf)
{
printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
return 1;
}
// 實現 退出函數
int FM_remove(struct i2c_client *client)
{
printk("%s - %s Test o(∩_∩)o~~~ !\n", FMMSG, __func__);
return 0;
}
// 這很關鍵,是否能重復加載 該ko文件取決于它。
static void __exit FM_exit(void)
{
printk("%s - %s Test11 o(∩_∩)o~~~ !\n", FMMSG, __func__);
__cancel_delayed_work(&open_pa.work);
class_unregister(&FM_attrs_class);// 必須卸載 會刪除 /sys/class/kt0810sg 目錄 不然下次加載就無法創建
i2c_del_driver(&FM_drv); // 刪除I2C驅動
kfree(FM_dev); // 釋放。
}
|