基于DWC2的USB驱动开发-0x0D PHY寄存器读写代码编写与测试
一.
PHY
寄存器读写
1.1
前言
我们前面重点介绍了
ULPI
接口和
PHY
的寄存器,这一篇来进行
PHY
寄存器读写的代码编写与测试。从这一篇开始就正真进入了驱动编写的过程了。
1.2 GPVNDCTL
寄存器介绍
DWC2
提供了读写
PHY
寄存器的机制,对于应用来说就是操作一个寄存器
GPVNDCTL:PHY
供应商控制寄存器。能够读写
PHY
寄存器这在有些底层问题分析时很重要。
《
DesignWare Cores USB 2.0 Hi-Speed On-TheGo (OTG) Databook
》的
P391 5.4.14 GPVNDCTL
有该寄存器的介绍。
IP
必须配置
OTG_VENDOR_CTL_INTERFACE = 1
才支持该功能。
GHWCFG3
寄存器的
b9
查看该配置值。
对于
UTMI+PHY
,
DWC_otg
核心使用
UTMI+
供应商控制接口进行
PHY
寄存器访问。对于
ULPI PHY
,核心使用
ULPI
接口进行
PHY
寄存器访问。应用程序设置
GPVNDCTL
来访问
PHY
寄存器,并对
PHY
寄存器的访问进行计时
,应用程序轮询此寄存器中的
VStatus Done
位来确认是否完成
PHY
寄存器的访问。
该寄存器的描述如下
偏移
0x34
访问大小
32位
我们重点关注和
PHY
读写有关的位域
,
其他的位暂时用不到
,
先不管。
NewRegReq:
软件写
1
触发一次操作。
VStsDone
:
当应用程序设置
NewRegReq
为
1
时,硬件清除该位,操作完成后硬件再置位该位。软件查询该位以判断是否完成,注意代码中需要考虑查询超时机制。
VStsBsy
:
忙标志
,
正在进行操作时硬件置位该位,操作完成时硬件清零,可以认为是和
VStsDone
相反的标志。
RegWr
: 0
表示读
PHY
寄存器,
1
表示写
PHY
寄存器
RegAddr
: PHY
寄存器地址
,PHY
立即寄存器的
6
位地址。设置为
6'h2F
用于扩展
PHY
寄存器集访问。
VCtrl
:UTMI+
供应商控制寄存器地址(
VCtrl
)供应商定义的
4
位并行输出总线的
4
位寄存器地址。该字段的位
11:8
被置于
utmi_vontrol[3:0]
上。
ULPI
扩展寄存器地址(
ExtRegAddr
)
PHY
扩展寄存器地址。
RegData
:
写寄存器时写入寄存器的数据。读寄存器时读到的寄存器内容,设置
VStatus Done
时有效。
DisUlpiDrvr
:
读写
PHY
寄存器无关,
应用程序在完成
ULPI Carkit
中断(
GINTSTS.ULPICKINT
)处理后设置此位。设置后,控制器将禁用输出信号的驱动器,并屏蔽
ULPI
接口的输入信号。控制器在启用
ULPI
接口之前清除该位。
1.3
代码编写
读
PHY
寄存器
1.
RegWr
设置为
0
2.
RegAddr
设置为立即寄存器的
6
位地址,如果是扩展寄存器则设置为
0x2F
且设置
VCtrl
为扩展寄存器的值
。
3.
NewRegReq
置
1
启动操作
4.
等待
VStsDone
变为
1
5.
读出
RegData
写
PHY
寄存器
1.
RegWr
设置为
1
2.
RegAddr
设置为立即寄存器的
6
位地址,如果是扩展寄存器则设置为
0x2F
且设置
VCtrl
为扩展寄存器的值
。
3.
RegData
设置为待写入寄存器的值
4.
NewRegReq
置
1
启动操作
5. 等待
VStsDone
变为
1
代码如下
/**
* \fn static uint8_t hw_dwc2_is_vndctlsupt(void)
* 判断是否支持供应商控制接口(VndctlSupt)
* \retval 0 不支持 供应商控制接口(VndctlSupt)
* \retval 1 支持
*/
static uint8_t hw_dwc2_is_vndctlsupt(void)
{
return ((USB_OTG_READ_REG(CFG_GHWCFG3_ADDR) & VNDCTLSUPT_MASK) >> VNDCTLSUPT_OFFSET) & 0x01;
//return (uint8_t)(s_dwc2_reg_t->ghwcfg3._b.vndctlsupt);
}
/**
* \fn int hw_dwc2_read_phyreg(uint8_t regaddr, uint8_t* regval, uint32_t timeout)
* \param[in] regaddr PHY寄存器,立即寄存器和扩展寄存器统一编码,高2位为0表示立即寄存器,
* 高两位不为0表示扩展寄存器。
* \param[out] regval 存储读出的寄存器值
* \param[in] timeout 查询是否完成的次数
* \retval -1 不支持供应商控制接口(VndctlSupt)
* \retval -2 读超时
* \retval 0 读成功
*/
int hw_dwc2_read_phyreg(uint8_t regaddr, uint8_t* regval, uint32_t timeout)
{
/* 判断是否支持供应商控制接口(VndctlSupt) */
if(hw_dwc2_is_vndctlsupt() == 0)
{
return -1;
}
s_dwc2_reg_t->gpvndctl._b.regwr = 0; /* 读模式 */
if((regaddr & 0xC0) != 0)
{
/* 扩展寄存器 */
s_dwc2_reg_t->gpvndctl._b.regaddr = 0x2F;
s_dwc2_reg_t->gpvndctl._b.vctrl = regaddr;
}
else
{
/* 立即寄存器 */
s_dwc2_reg_t->gpvndctl._b.regaddr = regaddr;
s_dwc2_reg_t->gpvndctl._b.vctrl = 0;
}
s_dwc2_reg_t->gpvndctl._b.newregreq = 1; /* 启动操作 */
while ((s_dwc2_reg_t->gpvndctl._b.vstsdone == 0) && (timeout-- > 0)); /* 等待完成 */
/* 根据标志返回值或者错误 */
if(s_dwc2_reg_t->gpvndctl._b.vstsdone != 0)
{
*regval = s_dwc2_reg_t->gpvndctl._b.regdata;
//s_dwc2_reg_t->gpvndctl._b.vstsdone = 1; /* newregreq =1时硬件清0,没必要手动清零 */
return 0;
}
else
{
return -2;
}
uint32_t tmp = 0;
tmp &= ~REGWR_MASK; /* 读模式 */
if((regaddr & 0xC0) != 0)
{
/* 扩展寄存器 */
tmp |= ((uint32_t)0x2F << REGADDR_OFFSET);
tmp |= ((uint32_t)regaddr << VCTRL_OFFSET);
}
else
{
/* 立即寄存器 */
tmp |= ((uint32_t)regaddr << REGADDR_OFFSET);
tmp |= ((uint32_t)0 << VCTRL_OFFSET);
}
tmp |= NEWREGREQ_MASK; /* 启动操作 */
USB_OTG_WRITE_REG(CFG_GPVNDCTL_ADDR,tmp);
while(((USB_OTG_READ_REG(CFG_GPVNDCTL_ADDR)&VSTSDONE_MASK) == 0) && (timeout-- > 0)); /* 等待完成 */
if(((USB_OTG_READ_REG(CFG_GPVNDCTL_ADDR)&VSTSDONE_MASK) != 0))
{
*regval = (USB_OTG_READ_REG(CFG_GPVNDCTL_ADDR) >> REGDATA_OFFSET) & 0xFF; /* 读出寄存器的值 */
USB_OTG_WRITE_REG(CFG_GPVNDCTL_ADDR,USB_OTG_READ_REG(CFG_GPVNDCTL_ADDR) | VSTSDONE_MASK);
/* newregreq =1时硬件清0,没必要手动清零 */
return 0;
}
else
{
return -2;
}
}
/**
* \fn int hw_dwc2_write_phyreg(uint8_t regaddr, uint8_t regval, uint32_t timeout)
* \param[in] regaddr PHY寄存器,立即寄存器和扩展寄存器统一编码,高2位为0表示立即寄存器,
* 高两位不为0表示扩展寄存器。
* \param[out] regval 写入寄存器的值
* \param[in] timeout 查询是否完成的次数
* \retval -1 不支持供应商控制接口(VndctlSupt)
* \retval -2 读超时
* \retval 0 读成功
*/
int hw_dwc2_write_phyreg(uint8_t regaddr, uint8_t regval, uint32_t timeout)
{
/* 判断是否支持供应商控制接口(VndctlSupt) */
if(hw_dwc2_is_vndctlsupt() == 0)
{
return -1;
}
/* 不能使用结构体单位域操作,因为regdata需要vstsdone=1才能访问
* 且vstsdone是W1C,所以单位域读-修改-写时vstsdone写入1导致清0,导致后面regdata不能写入
*/
s_dwc2_reg_t->gpvndctl._b.regwr = 1; /* 写模式 */
if((regaddr & 0xC0) != 0)
{
/* 扩展寄存器 */
s_dwc2_reg_t->gpvndctl._b.regaddr = 0x2F;
s_dwc2_reg_t->gpvndctl._b.vctrl = regaddr;
}
else
{
/* 立即寄存器 */
s_dwc2_reg_t->gpvndctl._b.regaddr = regaddr;
s_dwc2_reg_t->gpvndctl._b.vctrl = 0;
}
s_dwc2_reg_t->gpvndctl._b.regdata = regval; /* 写入寄存器的值 */
s_dwc2_reg_t->gpvndctl._b.newregreq = 1; /* 启动操作 此时regdata回读未0,导致回写为0 不对*/
while ((s_dwc2_reg_t->gpvndctl._b.vstsdone == 0) && (timeout-- > 0)); /* 等待完成 */
/* 根据标志返回值或者错误 */
if(s_dwc2_reg_t->gpvndctl._b.vstsdone != 0)
{
// s_dwc2_reg_t->gpvndctl._b.vstsdone = 1; /* newregreq =1时硬件清0,没必要手动清零 */
return 0;
}
else
{
return -2;
}
uint32_t tmp = 0;
tmp |= REGWR_MASK; /* 写模式 */
if((regaddr & 0xC0) != 0)
{
/* 扩展寄存器 */
tmp |= ((uint32_t)0x2F << REGADDR_OFFSET);
tmp |= ((uint32_t)regaddr << VCTRL_OFFSET);
}
else
{
/* 立即寄存器 */
tmp |= ((uint32_t)regaddr << REGADDR_OFFSET);
tmp |= ((uint32_t)0 << VCTRL_OFFSET);
}
tmp |= ((uint32_t)regval << REGDATA_OFFSET); /* 写入寄存器的值 */
tmp |= NEWREGREQ_MASK; /* 启动操作 */
USB_OTG_WRITE_REG(CFG_GPVNDCTL_ADDR,tmp);
while(((USB_OTG_READ_REG(CFG_GPVNDCTL_ADDR)&VSTSDONE_MASK) == 0) && (timeout-- > 0)); /* 等待完成 */
if(((USB_OTG_READ_REG(CFG_GPVNDCTL_ADDR)&VSTSDONE_MASK) != 0))
{
USB_OTG_WRITE_REG(CFG_GPVNDCTL_ADDR,USB_OTG_READ_REG(CFG_GPVNDCTL_ADDR) | VSTSDONE_MASK);
/* newregreq =1时硬件清0,没必要手动清零 */
return 0;
}
else
{
return -2;
}
}
1.4
测试
1.4.1
立即寄存器读
VID PID
这里使用的
PHY
型号是
USB3343
VID PID 寄存器值如下
测试代码如下
uint8_t regval;
int res = 0;
/* 读VID PID */
res = hw_dwc2_read_phyreg(0x00,®val,1000);
if(res != 0)
{
return res;
}
usb_hal_info("[VIDL]:0x%x\r\n",regval);
res = hw_dwc2_read_phyreg(0x01,®val,1000);
if(res != 0)
{
return res;
}
usb_hal_info("[VIDH]:0x%x\r\n",regval);
res = hw_dwc2_read_phyreg(0x02,®val,1000);
if(res != 0)
{
return res;
}
usb_hal_info("[PIDL]:0x%x\r\n",regval);
res = hw_dwc2_read_phyreg(0x03,®val,1000);
if(res != 0)
{
return res;
}
usb_hal_info("[PIDH]:0x%x\r\n",regval);
打印如下,可以看到读出的值是正确的
也可以直接使用仿真器修改寄存器操作
(gdb) set *(unsigned int*)0x03000034=0x02000000
//
读
0
寄存器
(gdb) x /1xw 0x03000034
0x3000034: 0x08000024
(gdb) set *(unsigned int*)0x03000034=0x02010000
//
读
1
寄存器
(gdb) x /1xw 0x03000034
0x3000034: 0x08010004
(gdb) set *(unsigned int*)0x03000034=0x02020000
//
读
2
寄存器
(gdb) x /1xw 0x03000034
0x3000034: 0x08020009
(gdb) set *(unsigned int*)0x03000034=0x02030000
//
读
3
寄存器
(gdb) x /1xw 0x03000034
0x3000034: 0x08030000
1.4.2
立即寄存器读写寄存器
0x16
处的
Scratch
寄存器是用户可以自由使用的寄存器,可以使用该寄存器测试
0x16
对应读写
0x17
对应
Set
0x18
对应
Clr
测试代码如下
/* 读Scratch Register初始值 */
res = hw_dwc2_read_phyreg(0x16,®val,1000);
if(res != 0)
{
return res;
}
usb_hal_info("[Scratch Register]:0x%x\r\n",regval);
/* 写入0x55回读是否正确 */
hw_dwc2_write_phyreg(0x16,0x55,1000);
if(res != 0)
{
return res;
}
res = hw_dwc2_read_phyreg(0x16,®val,1000);
if(res != 0)
{
return res;
}
if(regval != 0x55)
{
usb_hal_info("[Scratch Register Write Err]:0x%x\r\n",regval);
}
else
{
usb_hal_info("[Scratch Register Write OK]:0x%x\r\n",regval);
}
/* 0x55置位0xAA变为0xFF */
hw_dwc2_write_phyreg(0x17,0xAA,1000);
if(res != 0)
{
return res;
}
res = hw_dwc2_read_phyreg(0x16,®val,1000);
if(res != 0)
{
return res;
}
if(regval != 0xFF)
{
usb_hal_info("[Scratch Register Set Err]:0x%x\r\n",regval);
}
else
{
usb_hal_info("[Scratch Register Set OK]:0x%x\r\n",regval);
}
/* 0xFF清除0x55变为0xAA */
hw_dwc2_write_phyreg(0x18,0x55,1000);
if(res != 0)
{
return res;
}
res = hw_dwc2_read_phyreg(0x16,®val,1000);
if(res != 0)
{
return res;
}
if(regval != 0xAA)
{
usb_hal_info("[Scratch Register Clr Err]:0x%x\r\n",regval);
}
else
{
usb_hal_info("[Scratch Register Clr OK]:0x%x\r\n",regval);
}
打印如下
直接使用仿真器操作
(gdb) set *(unsigned int*)0x03000034=0x02560055
//
写
0x16
寄存器为
0x55
(gdb) set *(unsigned int*)0x03000034=0x02160000
//
读
0x16
寄存器
(gdb) x /1xw 0x03000034
//
查看读回来的值为
0x55
正确
0x3000034: 0x08160055
(gdb) set *(unsigned int*)0x03000034=0x025700AA
//
设置
0x16
寄存器,即操作
0x17
置位
0xAA
位
(gdb) set *(unsigned int*)0x03000034=0x02160000
//
读
0x16
寄存器
(gdb) x /1xw 0x03000034
0x3000034: 0x081600ff
//
查看读回来的值为
0xFF
正确
(gdb)
1.4.3
问题
位域操作和宏操作
1.
写
regwr
时
done
标志被清零了,因为是读
-
修改
-
写的方式,
VstsDone
读出来为
1
再修改其他位回写时
VstsDone
写入
1
,但是该位为
W1C
,写
1
清
0
,所以回写时会清除标志。虽然这里不会有什么问题,但是这里有编程者未意料到的副作用,在其他地方可能会导致问题,所以不建议使用位域一步步的操作。
2.
位域的操作可能被优化,比如这里位于
regdata
等因为是字节对齐的,所以使用了字节操作指令,但是按照
DWC2
手册应该要求都是按照
32
位访问的,所以这里带来了未预料的问题。
3.
写入
regdata
后,实际上回读的值是
0
,所以最后
s_dwc2_reg_t->gpvndctl._b.newregreq = 1;
读
-
写
0
修改后读出
regdata
是
0
,再写入就是
0
了,而不是上一步写入的
s_dwc2_reg_t->gpvndctl._b.regdata = regval;
了,所以不能使用这种位域一步步设置的方式。
以下验证
regdata
写入是不能回读的,写入
0x55
回读是
0
所以
regdata
在手册中描述的
rw
不完全是可读可写,而是写时只写不可读,读时只有
Vstsdone
才能读。
4.
位域操作有太多的副作用,比如上面的
W1C
,写入不能回读,位域访问宽度被优化修改,等等都可能导致问题,而且位域一步步操作都需要读
-
修改
-
写,所以效率也低,最好使用直接的寄存器宏一次读出,临时变量存储修改,最后一次写入的方式。
所以不能使用结构体一步一步修改的方式,而是使用宏,一次性修改整个寄存器。
1.5
总结
以上测试了 PHY立即 寄存器的读写,由于使用的 PHY USB3343 没有扩展寄存器,所以扩展寄存器读写没有测试。注意上面也介绍了尽量使用宏的方式进行寄存器操作,而不要使用位域的方式,位域的方式存在诸多副作用。