概述
CH374 是一个 USB 总线的通用接口芯片,支持 USB-HOST 主机方式和 USB-DEVICE/SLAVE 设备方式,内置 3 端口 HUB 根集线器,支持低速和全速的控制传输、批量传输、中断传输以及同步/等时传输。
在本地端,CH374 具有 8 位数据总线和读、写、片选控制线以及中断输出,可以方便地挂接到单片机
/DSP/MCU/MPU 等控制器的系统总线上。除此之外,CH374 还提供了节约 I/O 引脚的 SPI 串行通讯方式,通过 3 线或者 4 线 SPI 串行接口以及中断输出与单片机/DSP/MCU/MPU 等相连接。
● 支持 1.5Mbps 低速和 12Mbps 全速 USB 通讯,兼容 USB V2.0,外围元器件只需要电容。
● 支持 USB-HOST 主机接口和 USB-DEVICE 设备接口,支持动态切换主机方式与设备方式。
● CH374F/U 芯片内置 3 端口 USB 根集线器 ROOT-HUB,可以同时连接和管理 3 个 USB 设备。
● 支持常用的低速和全速 USB 设备的控制传输、批量传输、中断传输、同步/等时传输。
● 自动检测低速和全速 USB 设备的连接和断开,提供设备连接和断开的中断通知。
● 内置 USB 信号线的阻抗匹配串联电阻、USB 设备端的上拉电阻、USB 主机端的下拉电阻。
● 可选两种单片机接口:6MB 速度的 8 位被动并行接口和 3.5MB/28MHz 速度的 SPI 串行接口。
● 并行接口包含 8 位数据总线,1 位地址,3 线控制:片选输入、写选通以及可选的读选通。
● 并行接口只占用两个地址位:索引地址口和数据口,读写数据口后内部索引地址自动递增。
● SPI 串行接口包含 SPI 片选、串行时钟、串行输入和输出,并且 SPI 输出与输入可以并联。
● 中断输出引脚是可选连接,低电平有效,可以通过查询寄存器中的中断标志位代替。
● 提供辅助功能:可编程时钟输出,上电复位输出以及可选的看门狗复位。
● 提供支持 FAT12/FAT16/FAT32 文件系统的 U 盘文件级子程序库,实现单片机读写 U 盘文件。
● 支持 5V 电源电压和 3.3V 电源电压甚至 3V 电源电压。
● 提供 QFN-28、SSOP-24、SOP-16、SOP-28 和 SSOP-20 无铅封装,兼容 RoHS,可以提供 DIP28转换板,引脚基本兼容 CH375 和 CH372 芯片。
硬件设计
本手册中所指的单片机基本适用于 DSP 或者 SCM/MCU/MPU/CPU 等。CH374 的内部寄存器以及缓冲区分配在地址从 00H 到 0FFH 的范围内,由单片机寻址后访问。复位后的默认值都是以二进制数表示,并可以由若干个字符标志说明其特性.
硬件参数
电气参数
基本时序
SPI 串口时序
原理图
并口方式
晶体 X1、电容 C1 和 C2 用于 CH374 的时钟振荡威廉希尔官方网站
。USB-HOST 主机方式要求时钟频率比较准确,X1 的频率是 24MHz±0.4‰,参考手册中的设置,X1 的频率也可以选用 12MHz。C1 和 C2 是容量约为22pF 的独石或高频瓷片电容。电容 C5 是可选的,仅用于延长电源上电时 CH374 芯片的复位时间,一般的应用威廉希尔官方网站
中可以省去 C5,或者也可以由单片机的普通 I/O 引脚控制 CH374 复位。
CH374 还为单片机系统提供了以下辅助信号:RST 和 RST#引脚可以用于为单片机提供上电复位和
看门狗复位信号;CKO 引脚可以用于为单片机提供频率可动态编程的时钟信号;SLP 引脚可以用于为
单片机或者其它外设提供睡眠断电后的自动唤醒控制。
如果不连接中断请求输出引脚 INT#,那么单片机程序也可以通过查询中断标志寄存器代替。
SPI 串口方式
如果 CH374 芯片的 RD#引脚和 WR#引脚为低电平(接地)并且 CS#引脚为高电平(接正电源),那么 CH374 将工作于 SPI 串口方式。在 SPI 串口方式下,CH374 只需要与单片机/DSP/MCU 连接 5 个信号线:SCS#引脚、SCK 引脚、SDI 引脚和 SDO 引脚以及 INT#引脚,其它引脚都可以悬空。
为了节约引脚,INT#引脚可以不连接,而代之以查询中断标志寄存器,但是查询效率较低。
为了节约引脚,CH374 的 SDO 输出引脚可以在串接 330Ω的电阻 R4 后并联到 SDI 引脚上,再与单片机的 SDI 和 SDO 连接,当然,单片机的 SDO 引脚必须也是三态输出或者是可以关闭输出的。
SPI 串口方式除了连接线比并口方式较少之外,其它外围威廉希尔官方网站
与并口方式基本相同。在软件编程方面,除了硬件抽象层的接口子程序不同之外,所有功能性的程序基本相同。
内置 HUB 连接 3 个设备
CH374F 和 CH374U 芯片内置了三端口根集线器 Root-HUB,作为 USB-Host 主机使用时,可以同时连接 3 个 USB 设备,支持 USB 全速和低速设备混合应用。其中端口 P5 和 P6 只能用于 Host 方式连接外部 USB 设备,端口 P4 既可以用于 Host 方式连接外部 USB 设备,也能用于 Device 方式连接外部 Host
主机。
软件驱动
dts配置
/* kernelmsm-4.9archarm64bootdtsqcommsm8953-mtp.dtsi */
&spi_6{
status = "ok";
spi_ch37x_hcd@0 {
status = "ok";
compatible = "qcom,spi_ch37x_hcd";
//interrupt-parent = <&tlmm>;
//interrupts =
;
//pinctrl-names = "default", "sleep", "inactive";
//pinctrl-0 = <&eint7_function_C_active_pins>;
//pinctrl-1 = <&eint7_function_C_sleep_pins>;
//pinctrl-2 = <&eint7_function_C_inactive_pins>;
#size-cells = <1>;
reg = <0>;
interrupt-parent = <&tlmm>;
interrupts = <98 0x2>;
u***-hcd-int = <&tlmm 98 0x2008>;//中断脚
spi-max-frequency = <960000>;//spi频率可调
//spi-cpha;//设置spi模式
//spi-cpol;
poll_mode = <0>;//polling模式
type = <0>;
enable_dma = <0>;//dma通信
};
};
/* kernelmsm-4.9archarm64bootdtsqcommsm8953.dtsi */
spi_6: spi@7af6000 { /* BLSP2 QUP2 */
compatible = "qcom,spi-qup-v2";
#address-cells = <1>;
#size-cells = <0>;
reg-names = "spi_physical", "spi_bam_physical";
reg = <0x7af6000 0x600>,
<0x7ac4000 0x1f000>;
interrupt-names = "spi_irq", "spi_bam_irq";
interrupts = <0 300 0>, <0 239 0>;
spi-max-frequency = <960000>;//spi频率
pinctrl-names = "spi_default", "spi_sleep";
pinctrl-0 = <&spi6_default &spi6_cs0_active>;
pinctrl-1 = <&spi6_sleep &spi6_cs0_sleep>;
clocks = <&clock_gcc clk_gcc_blsp2_ahb_clk>,
<&clock_gcc clk_gcc_blsp2_qup2_spi_apps_clk>;
clock-names = "iface_clk", "core_clk";
qcom,infinite-mode = <0>;
qcom,use-bam;
qcom,use-pinctrl;
qcom,ver-reg-exists;
qcom,bam-consumer-pipe-index = <6>;
qcom,bam-producer-pipe-index = <7>;
qcom,master-id = <84>;
status = "disabled";
};
驱动配置
ch37x_probe
在probe函数中初始化spi设备,通过u***_create_hcd()创建hcd节点
static int ch37x_probe(struct spi_device *spi)
{
struct ch37x_hcd_data *ch37x_hcd;
struct u***_hcd *hcd = NULL;
int irq ;
//int error;
static struct xgold_spi_chip *spi_chip_data;
struct device_node *np = of_node_get(spi->dev.of_node);
// struct device_state_pm_state *pm_state;
int retval = -ENOMEM;
dev_err(&spi->dev, "ch37x_proben");
#ifndef GPIO_SPI
if(!spi)
return -ENOMEM;
dev_err(&spi->dev, "ch37x_proben");
if (spi_setup(spi) < 0) {
dev_err(&spi->dev, "Unable to setup SPI bus");
return -EFAULT;
}
#endif
if (u***_disabled())
return -ENODEV;
hcd = u***_create_hcd(&ch37x_hcd_desc, &spi->dev,
dev_name(&spi->dev));
if (!hcd) {
dev_err(&spi->dev, "failed to create HCD structuren");
goto error;
}
//set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
ch37x_hcd = hcd_to_ch37x(hcd);
ch37x_hcd->next = ch37x_hcd_list;
ch37x_hcd_list = ch37x_hcd;
INIT_LIST_HEAD(&ch37x_hcd->ep_list);
spin_lock_init(&ch37x_hcd->lock);
spin_lock_init(&spilock);
之后通过spi_ch37x_hcd_parse_dt()获取dts节点信息,设置初始化信息及中断gpio。
static struct xgold_spi_chip *spi_ch37x_hcd_parse_dt(struct u***_hcd *hcd, struct device *dev)
{
u32 temp;
struct xgold_spi_chip *spi_chip_data;
struct ch37x_hcd_data *ch37x_hcd = hcd_to_ch37x(hcd);
spi_chip_data = devm_kzalloc(dev, sizeof(*spi_chip_data), GFP_KERNEL);
if (!spi_chip_data) {
dev_err(dev, "memory allocation for spi_chip_data failedn");
return ERR_PTR(-ENOMEM);
}
if (of_property_read_u32(dev->of_node, "poll_mode", &temp)) {
dev_warn(dev, "fail to get poll_mode, default set 0n");
spi_chip_data->poll_mode = 0;
} else {
spi_chip_data->poll_mode = temp;
}
if (of_property_read_u32(dev->of_node, "type", &temp)) {
dev_warn(dev, "fail to get type, default set 0n");
spi_chip_data->type = 0;
} else {
spi_chip_data->type = temp;
}
if (of_property_read_u32(dev->of_node, "enable_dma", &temp)) {
dev_warn(dev, "fail to get enable_dma, default set 0n");
spi_chip_data->enable_dma = 0;
} else {
spi_chip_data->enable_dma = temp;
}
ch37x_hcd_dbg(ch37x_hcd, "%s: poll_mode=%d, type=%d, enable_dma=%dn",__func__, spi_chip_data->poll_mode, spi_chip_data->type, spi_chip_data->enable_dma);
g_ch37x_ctrl.u***_hcd_int_gpio= of_get_named_gpio(dev->of_node, "u***-hcd-int", 0);
g_ch37x_ctrl.ch37x_ldo_en_gpio= of_get_named_gpio(dev->of_node, "ch37x-ldo-en", 0);
g_ch37x_ctrl.ch37x_vbus_en_gpio= of_get_named_gpio(dev->of_node, "u***-vbus-en", 0);
pr_err( "%s: u***_hcd_int_gpio=%d ch37x_ldo_en_gpio=%d ch37x_vbus_en_gpio=%d n",__func__, g_ch37x_ctrl.u***_hcd_int_gpio,g_ch37x_ctrl.ch37x_ldo_en_gpio, g_ch37x_ctrl.ch37x_vbus_en_gpio);
return spi_chip_data;
}
然后创建工作队列ch37x_spi_thread,通过ch37x_irq_handler()中断触发spi通信。
irq = gpio_request(g_ch37x_ctrl.u***_hcd_int_gpio, "u*** hcd int");
if(!irq) {
gpio_direction_input(g_ch37x_ctrl.u***_hcd_int_gpio);
irq = gpio_to_irq(g_ch37x_ctrl.u***_hcd_int_gpio);
g_ch37x_ctrl.u***_hcd_irq= irq;
} else {
dev_err(&spi->dev, "failed to request u*** hcd int n");
}
g_ch37x_ctrl.hcd=hcd;
spi->irq = irq;
ch37x_hcd->spi = spi;
ch37x_hcd->dev = &spi->dev;
ch37x_hcd->irq = spi->irq;
ch37x_hcd->tx = devm_kzalloc(&spi->dev, sizeof(*ch37x_hcd->tx), GFP_KERNEL);
if (!ch37x_hcd->tx) {
dev_err(&spi->dev, "failed to kmalloc tx buffern");
goto error;
}
ch37x_hcd->rx = devm_kzalloc(&spi->dev, sizeof(*ch37x_hcd->rx), GFP_KERNEL);
if (!ch37x_hcd->rx) {
dev_err(&spi->dev, "failed to kmalloc rx buffern");
goto error;
}
kthread_init_worker(&ch37x_hcd->spi_worker);
ch37x_hcd->spi_thread =
kthread_run(kthread_worker_fn, &ch37x_hcd->spi_worker, "ch37x_spi_thread");
if (IS_ERR(ch37x_hcd->spi_thread)) {
retval = PTR_ERR(ch37x_hcd->spi_thread);
ch37x_hcd->spi_thread = NULL;
pr_info("failed to run spi_threadn");
goto error;
}
kthread_init_work(&ch37x_hcd->spi_work, ch37x_spi_thread);
retval = u***_add_hcd(hcd, 0, 0);
if (retval) {
dev_err(&spi->dev, "failed to add HCDn");
goto error;
}
spi_set_drvdata(spi, ch37x_hcd);
g_ch37x_hcd = ch37x_hcd;
atomic_set(&ch37x_hcd->suspend_flag, 0);
INIT_DELAYED_WORK(&ch37x_hcd->delaywork, ch37x_resume_delaywork_func);
atomic_set(&ch37x_hcd->debug_flag, 0);
atomic_set(&ch37x_hcd->urb_process, 0);
mutex_init(&ch37x_hcd->thread_mutex);
ch37x_debugfs_init(hcd);
retval = devm_request_threaded_irq(&spi->dev, spi->irq, ch37x_irq_handler, ch37x_irq_thread_handler,
IRQF_TRIGGER_LOW | IRQF_ONESHOT , "spi-ch37x-hcd", hcd);
if (retval < 0) {
dev_err(&spi->dev, "failed to request irq %d error %dn", spi->irq, retval);
goto error;
}
ch37x_spi_thread
static void ch37x_spi_thread(struct kthread_work *work)
{
struct ch37x_hcd_data *ch37x_hcd =
container_of(work, struct ch37x_hcd_data, spi_work);
struct u***_hcd *hcd = ch37x_to_hcd(ch37x_hcd);
int i_worked = 1;
//int ret = 0;
int urb_null = 0;
int index = 0;
pr_info("%s:line=%d startn",__func__,__LINE__);
while (!kthread_should_stop()) {
if (ch37x_hcd->rh_state == CH374_RH_RUNNING) {
break;
}
msleep(1000);
}
while (!kthread_should_stop() && !atomic_read(&ch37x_hcd->suspend_flag)) {
if (!i_worked) {
set_current_state(TASK_INTERRUPTIBLE);
if (test_and_clear_bit(ENABLE_IRQ, &ch37x_hcd->todo)) {
enable_irq(ch37x_hcd->irq);
ch37x_hcd_dbg(ch37x_hcd, "%s:enable irq %dn",__func__, ch37x_hcd->irq);
}
// check if urb is really null (enqueue comes)
if ((urb_null == 1) && (atomic_read(&ch37x_hcd->urb_process) == 1)) {
if (!test_and_set_bit(ENABLE_IRQ, &ch37x_hcd->todo)) {
disable_irq_nosync(ch37x_hcd->irq);
__set_current_state(TASK_RUNNING);
}
urb_null = 0;
i_worked = 1;
ch37x_hcd_dbg(ch37x_hcd, "%s:new urb_enqueue, dont schedulen",__func__);
continue;
}
schedule();
__set_current_state(TASK_RUNNING);
ch37x_hcd_dbg(ch37x_hcd, "%s: wakeup! line=%dn",__func__, __LINE__);
}
i_worked = 0;
if (ch37x_hcd->urb_done)
i_worked |= ch37x_urb_done(hcd);
else if (ch37x_handle_irqs(hcd))
i_worked = 1;
else if (!ch37x_hcd->curr_urb) {
i_worked |= ch37x_select_and_start_urb(hcd);
if (i_worked == 0) {
urb_null = 1;
} else {
urb_null = 0;
}
} else if (ch37x_hcd->curr_urb) {
ch37x_hcd_dbg(ch37x_hcd, "%s:line=%d, curr_urb not nulln", __func__, __LINE__);
}
if (test_and_clear_bit(RESET_HCD, &ch37x_hcd->todo))
/* reset the HCD: */
i_worked |= ch37x_reset_hcd(hcd);
for (index = 0; index < NUM_PORTS; index++) {
if (test_and_clear_bit(RESET_PORT, &ch37x_hcd->ports[index].todoport)) {
/* perform a USB bus reset: */
host_reset_bus(hcd, index);
i_worked = 1;
ch37x_hcd_dbg(ch37x_hcd, "%s:line=%d, wPortStatus[%d]=0x%x RESET_PORTn",
__func__, __LINE__, index, ch37x_hcd->ports[index].port_status.wPortStatus);
}
}
if (test_and_clear_bit(CHECK_CONNECT, &ch37x_hcd->todo)) {
pr_info("%s:line=%dn", __func__, __LINE__);
for (index = 0; index < NUM_PORTS; index++)
ch37x_detect_conn(hcd, index);
i_worked = 1;
}
// 处理已经dequeue的URB
if (test_and_clear_bit(CHECK_UNLINK, &ch37x_hcd->todo)) {
pr_info("%s:line=%dn", __func__, __LINE__);
i_worked |= ch37x_check_unlink(hcd);
}
ch37x_hcd_dbg(ch37x_hcd, "%s:line=%d, i_worked=%d continuen",
__func__, __LINE__, i_worked);
}
set_current_state(TASK_RUNNING);
return;
// pr_info("%s:i_worked=0x%x suspend=%d thread=%d exitn", __func__, i_worked,
// atomic_read(&ch37x_hcd->suspend_flag), kthread_should_stop());
}
概述
CH374 是一个 USB 总线的通用接口芯片,支持 USB-HOST 主机方式和 USB-DEVICE/SLAVE 设备方式,内置 3 端口 HUB 根集线器,支持低速和全速的控制传输、批量传输、中断传输以及同步/等时传输。
在本地端,CH374 具有 8 位数据总线和读、写、片选控制线以及中断输出,可以方便地挂接到单片机
/DSP/MCU/MPU 等控制器的系统总线上。除此之外,CH374 还提供了节约 I/O 引脚的 SPI 串行通讯方式,通过 3 线或者 4 线 SPI 串行接口以及中断输出与单片机/DSP/MCU/MPU 等相连接。
● 支持 1.5Mbps 低速和 12Mbps 全速 USB 通讯,兼容 USB V2.0,外围元器件只需要电容。
● 支持 USB-HOST 主机接口和 USB-DEVICE 设备接口,支持动态切换主机方式与设备方式。
● CH374F/U 芯片内置 3 端口 USB 根集线器 ROOT-HUB,可以同时连接和管理 3 个 USB 设备。
● 支持常用的低速和全速 USB 设备的控制传输、批量传输、中断传输、同步/等时传输。
● 自动检测低速和全速 USB 设备的连接和断开,提供设备连接和断开的中断通知。
● 内置 USB 信号线的阻抗匹配串联电阻、USB 设备端的上拉电阻、USB 主机端的下拉电阻。
● 可选两种单片机接口:6MB 速度的 8 位被动并行接口和 3.5MB/28MHz 速度的 SPI 串行接口。
● 并行接口包含 8 位数据总线,1 位地址,3 线控制:片选输入、写选通以及可选的读选通。
● 并行接口只占用两个地址位:索引地址口和数据口,读写数据口后内部索引地址自动递增。
● SPI 串行接口包含 SPI 片选、串行时钟、串行输入和输出,并且 SPI 输出与输入可以并联。
● 中断输出引脚是可选连接,低电平有效,可以通过查询寄存器中的中断标志位代替。
● 提供辅助功能:可编程时钟输出,上电复位输出以及可选的看门狗复位。
● 提供支持 FAT12/FAT16/FAT32 文件系统的 U 盘文件级子程序库,实现单片机读写 U 盘文件。
● 支持 5V 电源电压和 3.3V 电源电压甚至 3V 电源电压。
● 提供 QFN-28、SSOP-24、SOP-16、SOP-28 和 SSOP-20 无铅封装,兼容 RoHS,可以提供 DIP28转换板,引脚基本兼容 CH375 和 CH372 芯片。
硬件设计
本手册中所指的单片机基本适用于 DSP 或者 SCM/MCU/MPU/CPU 等。CH374 的内部寄存器以及缓冲区分配在地址从 00H 到 0FFH 的范围内,由单片机寻址后访问。复位后的默认值都是以二进制数表示,并可以由若干个字符标志说明其特性.
硬件参数
电气参数
基本时序
SPI 串口时序
原理图
并口方式
晶体 X1、电容 C1 和 C2 用于 CH374 的时钟振荡威廉希尔官方网站
。USB-HOST 主机方式要求时钟频率比较准确,X1 的频率是 24MHz±0.4‰,参考手册中的设置,X1 的频率也可以选用 12MHz。C1 和 C2 是容量约为22pF 的独石或高频瓷片电容。电容 C5 是可选的,仅用于延长电源上电时 CH374 芯片的复位时间,一般的应用威廉希尔官方网站
中可以省去 C5,或者也可以由单片机的普通 I/O 引脚控制 CH374 复位。
CH374 还为单片机系统提供了以下辅助信号:RST 和 RST#引脚可以用于为单片机提供上电复位和
看门狗复位信号;CKO 引脚可以用于为单片机提供频率可动态编程的时钟信号;SLP 引脚可以用于为
单片机或者其它外设提供睡眠断电后的自动唤醒控制。
如果不连接中断请求输出引脚 INT#,那么单片机程序也可以通过查询中断标志寄存器代替。
SPI 串口方式
如果 CH374 芯片的 RD#引脚和 WR#引脚为低电平(接地)并且 CS#引脚为高电平(接正电源),那么 CH374 将工作于 SPI 串口方式。在 SPI 串口方式下,CH374 只需要与单片机/DSP/MCU 连接 5 个信号线:SCS#引脚、SCK 引脚、SDI 引脚和 SDO 引脚以及 INT#引脚,其它引脚都可以悬空。
为了节约引脚,INT#引脚可以不连接,而代之以查询中断标志寄存器,但是查询效率较低。
为了节约引脚,CH374 的 SDO 输出引脚可以在串接 330Ω的电阻 R4 后并联到 SDI 引脚上,再与单片机的 SDI 和 SDO 连接,当然,单片机的 SDO 引脚必须也是三态输出或者是可以关闭输出的。
SPI 串口方式除了连接线比并口方式较少之外,其它外围威廉希尔官方网站
与并口方式基本相同。在软件编程方面,除了硬件抽象层的接口子程序不同之外,所有功能性的程序基本相同。
内置 HUB 连接 3 个设备
CH374F 和 CH374U 芯片内置了三端口根集线器 Root-HUB,作为 USB-Host 主机使用时,可以同时连接 3 个 USB 设备,支持 USB 全速和低速设备混合应用。其中端口 P5 和 P6 只能用于 Host 方式连接外部 USB 设备,端口 P4 既可以用于 Host 方式连接外部 USB 设备,也能用于 Device 方式连接外部 Host
主机。
软件驱动
dts配置
/* kernelmsm-4.9archarm64bootdtsqcommsm8953-mtp.dtsi */
&spi_6{
status = "ok";
spi_ch37x_hcd@0 {
status = "ok";
compatible = "qcom,spi_ch37x_hcd";
//interrupt-parent = <&tlmm>;
//interrupts =
;
//pinctrl-names = "default", "sleep", "inactive";
//pinctrl-0 = <&eint7_function_C_active_pins>;
//pinctrl-1 = <&eint7_function_C_sleep_pins>;
//pinctrl-2 = <&eint7_function_C_inactive_pins>;
#size-cells = <1>;
reg = <0>;
interrupt-parent = <&tlmm>;
interrupts = <98 0x2>;
u***-hcd-int = <&tlmm 98 0x2008>;//中断脚
spi-max-frequency = <960000>;//spi频率可调
//spi-cpha;//设置spi模式
//spi-cpol;
poll_mode = <0>;//polling模式
type = <0>;
enable_dma = <0>;//dma通信
};
};
/* kernelmsm-4.9archarm64bootdtsqcommsm8953.dtsi */
spi_6: spi@7af6000 { /* BLSP2 QUP2 */
compatible = "qcom,spi-qup-v2";
#address-cells = <1>;
#size-cells = <0>;
reg-names = "spi_physical", "spi_bam_physical";
reg = <0x7af6000 0x600>,
<0x7ac4000 0x1f000>;
interrupt-names = "spi_irq", "spi_bam_irq";
interrupts = <0 300 0>, <0 239 0>;
spi-max-frequency = <960000>;//spi频率
pinctrl-names = "spi_default", "spi_sleep";
pinctrl-0 = <&spi6_default &spi6_cs0_active>;
pinctrl-1 = <&spi6_sleep &spi6_cs0_sleep>;
clocks = <&clock_gcc clk_gcc_blsp2_ahb_clk>,
<&clock_gcc clk_gcc_blsp2_qup2_spi_apps_clk>;
clock-names = "iface_clk", "core_clk";
qcom,infinite-mode = <0>;
qcom,use-bam;
qcom,use-pinctrl;
qcom,ver-reg-exists;
qcom,bam-consumer-pipe-index = <6>;
qcom,bam-producer-pipe-index = <7>;
qcom,master-id = <84>;
status = "disabled";
};
驱动配置
ch37x_probe
在probe函数中初始化spi设备,通过u***_create_hcd()创建hcd节点
static int ch37x_probe(struct spi_device *spi)
{
struct ch37x_hcd_data *ch37x_hcd;
struct u***_hcd *hcd = NULL;
int irq ;
//int error;
static struct xgold_spi_chip *spi_chip_data;
struct device_node *np = of_node_get(spi->dev.of_node);
// struct device_state_pm_state *pm_state;
int retval = -ENOMEM;
dev_err(&spi->dev, "ch37x_proben");
#ifndef GPIO_SPI
if(!spi)
return -ENOMEM;
dev_err(&spi->dev, "ch37x_proben");
if (spi_setup(spi) < 0) {
dev_err(&spi->dev, "Unable to setup SPI bus");
return -EFAULT;
}
#endif
if (u***_disabled())
return -ENODEV;
hcd = u***_create_hcd(&ch37x_hcd_desc, &spi->dev,
dev_name(&spi->dev));
if (!hcd) {
dev_err(&spi->dev, "failed to create HCD structuren");
goto error;
}
//set_bit(HCD_FLAG_POLL_RH, &hcd->flags);
ch37x_hcd = hcd_to_ch37x(hcd);
ch37x_hcd->next = ch37x_hcd_list;
ch37x_hcd_list = ch37x_hcd;
INIT_LIST_HEAD(&ch37x_hcd->ep_list);
spin_lock_init(&ch37x_hcd->lock);
spin_lock_init(&spilock);
之后通过spi_ch37x_hcd_parse_dt()获取dts节点信息,设置初始化信息及中断gpio。
static struct xgold_spi_chip *spi_ch37x_hcd_parse_dt(struct u***_hcd *hcd, struct device *dev)
{
u32 temp;
struct xgold_spi_chip *spi_chip_data;
struct ch37x_hcd_data *ch37x_hcd = hcd_to_ch37x(hcd);
spi_chip_data = devm_kzalloc(dev, sizeof(*spi_chip_data), GFP_KERNEL);
if (!spi_chip_data) {
dev_err(dev, "memory allocation for spi_chip_data failedn");
return ERR_PTR(-ENOMEM);
}
if (of_property_read_u32(dev->of_node, "poll_mode", &temp)) {
dev_warn(dev, "fail to get poll_mode, default set 0n");
spi_chip_data->poll_mode = 0;
} else {
spi_chip_data->poll_mode = temp;
}
if (of_property_read_u32(dev->of_node, "type", &temp)) {
dev_warn(dev, "fail to get type, default set 0n");
spi_chip_data->type = 0;
} else {
spi_chip_data->type = temp;
}
if (of_property_read_u32(dev->of_node, "enable_dma", &temp)) {
dev_warn(dev, "fail to get enable_dma, default set 0n");
spi_chip_data->enable_dma = 0;
} else {
spi_chip_data->enable_dma = temp;
}
ch37x_hcd_dbg(ch37x_hcd, "%s: poll_mode=%d, type=%d, enable_dma=%dn",__func__, spi_chip_data->poll_mode, spi_chip_data->type, spi_chip_data->enable_dma);
g_ch37x_ctrl.u***_hcd_int_gpio= of_get_named_gpio(dev->of_node, "u***-hcd-int", 0);
g_ch37x_ctrl.ch37x_ldo_en_gpio= of_get_named_gpio(dev->of_node, "ch37x-ldo-en", 0);
g_ch37x_ctrl.ch37x_vbus_en_gpio= of_get_named_gpio(dev->of_node, "u***-vbus-en", 0);
pr_err( "%s: u***_hcd_int_gpio=%d ch37x_ldo_en_gpio=%d ch37x_vbus_en_gpio=%d n",__func__, g_ch37x_ctrl.u***_hcd_int_gpio,g_ch37x_ctrl.ch37x_ldo_en_gpio, g_ch37x_ctrl.ch37x_vbus_en_gpio);
return spi_chip_data;
}
然后创建工作队列ch37x_spi_thread,通过ch37x_irq_handler()中断触发spi通信。
irq = gpio_request(g_ch37x_ctrl.u***_hcd_int_gpio, "u*** hcd int");
if(!irq) {
gpio_direction_input(g_ch37x_ctrl.u***_hcd_int_gpio);
irq = gpio_to_irq(g_ch37x_ctrl.u***_hcd_int_gpio);
g_ch37x_ctrl.u***_hcd_irq= irq;
} else {
dev_err(&spi->dev, "failed to request u*** hcd int n");
}
g_ch37x_ctrl.hcd=hcd;
spi->irq = irq;
ch37x_hcd->spi = spi;
ch37x_hcd->dev = &spi->dev;
ch37x_hcd->irq = spi->irq;
ch37x_hcd->tx = devm_kzalloc(&spi->dev, sizeof(*ch37x_hcd->tx), GFP_KERNEL);
if (!ch37x_hcd->tx) {
dev_err(&spi->dev, "failed to kmalloc tx buffern");
goto error;
}
ch37x_hcd->rx = devm_kzalloc(&spi->dev, sizeof(*ch37x_hcd->rx), GFP_KERNEL);
if (!ch37x_hcd->rx) {
dev_err(&spi->dev, "failed to kmalloc rx buffern");
goto error;
}
kthread_init_worker(&ch37x_hcd->spi_worker);
ch37x_hcd->spi_thread =
kthread_run(kthread_worker_fn, &ch37x_hcd->spi_worker, "ch37x_spi_thread");
if (IS_ERR(ch37x_hcd->spi_thread)) {
retval = PTR_ERR(ch37x_hcd->spi_thread);
ch37x_hcd->spi_thread = NULL;
pr_info("failed to run spi_threadn");
goto error;
}
kthread_init_work(&ch37x_hcd->spi_work, ch37x_spi_thread);
retval = u***_add_hcd(hcd, 0, 0);
if (retval) {
dev_err(&spi->dev, "failed to add HCDn");
goto error;
}
spi_set_drvdata(spi, ch37x_hcd);
g_ch37x_hcd = ch37x_hcd;
atomic_set(&ch37x_hcd->suspend_flag, 0);
INIT_DELAYED_WORK(&ch37x_hcd->delaywork, ch37x_resume_delaywork_func);
atomic_set(&ch37x_hcd->debug_flag, 0);
atomic_set(&ch37x_hcd->urb_process, 0);
mutex_init(&ch37x_hcd->thread_mutex);
ch37x_debugfs_init(hcd);
retval = devm_request_threaded_irq(&spi->dev, spi->irq, ch37x_irq_handler, ch37x_irq_thread_handler,
IRQF_TRIGGER_LOW | IRQF_ONESHOT , "spi-ch37x-hcd", hcd);
if (retval < 0) {
dev_err(&spi->dev, "failed to request irq %d error %dn", spi->irq, retval);
goto error;
}
ch37x_spi_thread
static void ch37x_spi_thread(struct kthread_work *work)
{
struct ch37x_hcd_data *ch37x_hcd =
container_of(work, struct ch37x_hcd_data, spi_work);
struct u***_hcd *hcd = ch37x_to_hcd(ch37x_hcd);
int i_worked = 1;
//int ret = 0;
int urb_null = 0;
int index = 0;
pr_info("%s:line=%d startn",__func__,__LINE__);
while (!kthread_should_stop()) {
if (ch37x_hcd->rh_state == CH374_RH_RUNNING) {
break;
}
msleep(1000);
}
while (!kthread_should_stop() && !atomic_read(&ch37x_hcd->suspend_flag)) {
if (!i_worked) {
set_current_state(TASK_INTERRUPTIBLE);
if (test_and_clear_bit(ENABLE_IRQ, &ch37x_hcd->todo)) {
enable_irq(ch37x_hcd->irq);
ch37x_hcd_dbg(ch37x_hcd, "%s:enable irq %dn",__func__, ch37x_hcd->irq);
}
// check if urb is really null (enqueue comes)
if ((urb_null == 1) && (atomic_read(&ch37x_hcd->urb_process) == 1)) {
if (!test_and_set_bit(ENABLE_IRQ, &ch37x_hcd->todo)) {
disable_irq_nosync(ch37x_hcd->irq);
__set_current_state(TASK_RUNNING);
}
urb_null = 0;
i_worked = 1;
ch37x_hcd_dbg(ch37x_hcd, "%s:new urb_enqueue, dont schedulen",__func__);
continue;
}
schedule();
__set_current_state(TASK_RUNNING);
ch37x_hcd_dbg(ch37x_hcd, "%s: wakeup! line=%dn",__func__, __LINE__);
}
i_worked = 0;
if (ch37x_hcd->urb_done)
i_worked |= ch37x_urb_done(hcd);
else if (ch37x_handle_irqs(hcd))
i_worked = 1;
else if (!ch37x_hcd->curr_urb) {
i_worked |= ch37x_select_and_start_urb(hcd);
if (i_worked == 0) {
urb_null = 1;
} else {
urb_null = 0;
}
} else if (ch37x_hcd->curr_urb) {
ch37x_hcd_dbg(ch37x_hcd, "%s:line=%d, curr_urb not nulln", __func__, __LINE__);
}
if (test_and_clear_bit(RESET_HCD, &ch37x_hcd->todo))
/* reset the HCD: */
i_worked |= ch37x_reset_hcd(hcd);
for (index = 0; index < NUM_PORTS; index++) {
if (test_and_clear_bit(RESET_PORT, &ch37x_hcd->ports[index].todoport)) {
/* perform a USB bus reset: */
host_reset_bus(hcd, index);
i_worked = 1;
ch37x_hcd_dbg(ch37x_hcd, "%s:line=%d, wPortStatus[%d]=0x%x RESET_PORTn",
__func__, __LINE__, index, ch37x_hcd->ports[index].port_status.wPortStatus);
}
}
if (test_and_clear_bit(CHECK_CONNECT, &ch37x_hcd->todo)) {
pr_info("%s:line=%dn", __func__, __LINE__);
for (index = 0; index < NUM_PORTS; index++)
ch37x_detect_conn(hcd, index);
i_worked = 1;
}
// 处理已经dequeue的URB
if (test_and_clear_bit(CHECK_UNLINK, &ch37x_hcd->todo)) {
pr_info("%s:line=%dn", __func__, __LINE__);
i_worked |= ch37x_check_unlink(hcd);
}
ch37x_hcd_dbg(ch37x_hcd, "%s:line=%d, i_worked=%d continuen",
__func__, __LINE__, i_worked);
}
set_current_state(TASK_RUNNING);
return;
// pr_info("%s:i_worked=0x%x suspend=%d thread=%d exitn", __func__, i_worked,
// atomic_read(&ch37x_hcd->suspend_flag), kthread_should_stop());
}
举报