嵌入式Lee

文章数:196 被阅读:578672

datasheet推荐 换一换
随便看看
账号入驻

基于DWC2的USB驱动开发-0x0D PHY寄存器读写代码编写与测试

最新更新时间:2023-06-06
    阅读数:

一. 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; }#if 0 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; }#else 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; }#endif}
/** * \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; }
#if 0 /* 不能使用结构体单位域操作,因为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; }#else 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; }#endif}

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 没有扩展寄存器,所以扩展寄存器读写没有测试。注意上面也介绍了尽量使用宏的方式进行寄存器操作,而不要使用位域的方式,位域的方式存在诸多副作用。



最新有关嵌入式Lee的文章

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: TI培训

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved