0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

linux的uart驱动示例剖析

嵌入式小生 来源:嵌入式小生 2023-11-09 14:27 次阅读

linux源码相关文件:

serial-core.c

include/linux/serial_core.h

unsetunset

一、底层串行硬件驱动程序unset

底层串行硬件的驱动程序负责向serial核心驱动程序提供由struct uart_port定义的端口信息和一组由struct uart_ops定义的控制方法,底层驱动程序还负责处理端口的中断,并提供对控制台的支持。

unsetunset

二、Console支持unset

serial核心提供了一些助手函数:

uart_get_console()识别正确的端口结构。

uart_parse_options()解析命令行参数

uart_console_write()用于执行逐字符写入,将换行符转换为CRLF序列。在驱动程序编写的时候建议使用此函数,而不是实现新的写入接口

unsetunset

三、锁支持

unset

底层硬件驱动程序负责使用port->lock执行必要的锁定。支持两把锁:一个是端口自旋锁,另一个是overall信号量。从uart核心驱动程序的角度来看,port->lock用于锁定以下的数据:

port->mctrl
port->icount
port->state->xmit.head(circ_buf->head)
port->state->xmit.tail(circ_buf->tail)



底层驱动程序可以自由地使用该锁来实现额外的锁定,port_mutex互斥量用于防止在不适当的时间添加、删除或重新配置端口。

unsetunset

四、核心数据结构unset



1、struct uart_driver


struct uart_driver结构表示具体UART驱动。该结构定义如下(/include/linux/serial_core.h):

structuart_driver{
structmodule*owner;//驱动模块的拥有者
constchar*driver_name;//驱动名称
constchar*dev_name;//设备名称
intmajor;//主设备号
intminor;//从设备号
intnr;
structconsole*cons;//console

/*
*theseareprivate;thelowleveldrivershouldnot
*touchthese;theyshouldbeinitialisedtoNULL
*/
structuart_state*state;//uart状态
structtty_driver*tty_driver;//描述ttydriver
};



2、struct uart_port


struct uart_port表示一个具体的port,该结构定义如下(include/linux/serial_core.h):

structuart_port{
spinlock_tlock;/*port锁*/
unsignedlongiobase;/*输入/输出地址*/
unsignedchar__iomem*membase;/*read/write[bwl]*/
unsignedint(*serial_in)(structuart_port*,int);
void(*serial_out)(structuart_port*,int,int);
void(*set_termios)(structuart_port*,
structktermios*new,
structktermios*old);
void(*set_mctrl)(structuart_port*,unsignedint);
int(*startup)(structuart_port*port);
void(*shutdown)(structuart_port*port);
void(*throttle)(structuart_port*port);
void(*unthrottle)(structuart_port*port);
int(*handle_irq)(structuart_port*);
void(*pm)(structuart_port*,unsignedintstate,
unsignedintold);
void(*handle_break)(structuart_port*);
int(*rs485_config)(structuart_port*,
structserial_rs485*rs485);
unsignedintirq;/*irqnumber*/
unsignedlongirqflags;/*irqflags*/
unsignedintuartclk;/*baseuartclock*/
unsignedintfifosize;/*txfifosize*/
unsignedcharx_char;/*xon/xoffchar*/
unsignedcharregshift;/*regoffsetshift*/
unsignedchariotype;/*ioaccessstyle*/
unsignedcharunused1;

unsignedintread_status_mask;/*driverspecific*/
unsignedintignore_status_mask;/*driverspecific*/
structuart_state*state;/*指向父状态的指针*/
structuart_icounticount;/*通信信息*/

structconsole*cons;/*structconsole,ifany*/
#ifdefined(CONFIG_SERIAL_CORE_CONSOLE)||defined(SUPPORT_SYSRQ)
unsignedlongsysrq;/*sysrqtimeout*/
#endif

/*flagsmustbeupdatedwhileholdingportmutex*/
upf_tflags;

#if__UPF_CHANGE_MASK>ASYNC_FLAGS
#errorChangemasknotequivalenttouserspace-visiblebitdefines
#endif

/*
*Mustholdtermios_rwsem,portmutexandportlocktochange;
*canholdanyonelocktoread.
*/
upstat_tstatus;

inthw_stopped;/*sw-assistedCTSflowstate*/
unsignedintmctrl;/*当前调制解调器CTRL设置*/
unsignedinttimeout;/*character-basedtimeout*/
unsignedinttype;/*port类型*/
conststructuart_ops*ops;
unsignedintcustom_divisor;
unsignedintline;/*port索引号*/
unsignedintminor;
resource_size_tmapbase;/*用于ioremap*/
resource_size_tmapsize;
structdevice*dev;/*父device*/
unsignedcharhub6;/*应该在8250驱动程序中使用*/
unsignedcharsuspended;
unsignedcharirq_wake;
unsignedcharunused[2];
structattribute_group*attr_group;/*port特殊的属性*/
conststructattribute_group**tty_groups;/*所有的属性(仅限于serialcore使用)*/
structserial_rs485rs485;
void*private_data;/*通用platformdata指针*/
};

3、struct uart_ops

struct uart_ops用于描述serial核心驱动程序之间的接口,实现如下:

structuart_ops{
unsignedint(*tx_empty)(structuart_port*);
void(*set_mctrl)(structuart_port*,unsignedintmctrl);
unsignedint(*get_mctrl)(structuart_port*);
void(*stop_tx)(structuart_port*);
void(*start_tx)(structuart_port*);
void(*throttle)(structuart_port*);
void(*unthrottle)(structuart_port*);
void(*send_xchar)(structuart_port*,charch);
void(*stop_rx)(structuart_port*);
void(*start_rx)(structuart_port*);
void(*enable_ms)(structuart_port*);
void(*break_ctl)(structuart_port*,intctl);
int(*startup)(structuart_port*);
void(*shutdown)(structuart_port*);
void(*flush_buffer)(structuart_port*);
void(*set_termios)(structuart_port*,structktermios*new,conststructktermios*old);
void(*set_ldisc)(structuart_port*,structktermios*);
void(*pm)(structuart_port*,unsignedintstate,unsignedintoldstate);
constchar*(*type)(structuart_port*);
void(*release_port)(structuart_port*);
int(*request_port)(structuart_port*);
void(*config_port)(structuart_port*,int);
int(*verify_port)(structuart_port*,structserial_struct*);
int(*ioctl)(structuart_port*,unsignedint,unsignedlong);
#ifdefCONFIG_CONSOLE_POLL;
int(*poll_init)(structuart_port*);
void(*poll_put_char)(structuart_port*,unsignedchar);
int(*poll_get_char)(structuart_port*);
#endif;
};



tx_empty:此函数用于测试端口的发送FIFO和移位器是否为空,如果是空的,这个函数应该返回TIOCSER_TEMT,否则返回0。如果端口不支持此操作,则应该返回TIOCSER_TEMT。没有锁定。中断:依赖于调用者,这个调用不能导致睡眠。



set_mctrl:此功能将端口的调制解调器控制线设置为mcctrl所描述的状态。mctrl支持的参数是:



TIOCM_RTS表示RTS信号,TIOCM_DTR表示DTR信号,TIOCM_OUT1表示OUT1信号,TIOCM_OUT2表示OUT2信号,TIOCM_LOOP表示设置端口为环回模式。如果设置了合适的位,则信号应被驱动激活;如果该位被清除,则信号应被驱动为非激活状态。



在port->lock获取的情况下锁定。禁用本地中断。该调用不能睡眠。

get_mctrl:返回端口调制解调器控制输入的当前状态。输出的状态不应该被返回,因为内核会跟踪它们的状态。状态信息应包括:TIOCM_CAR表示DCD的信号状态。TIOCM_CTS表示CTS的信号状态,TIOCM_DSR表示DSR信号状态,TIOCM_RI表示RI信号状态。



如果设置该位,则将信号驱动为激活状态,如果端口不支持CTS, DCD或DSR,驱动程序应该表明信号是永久激活的。如果RI不可用,信号不应该显示为激活状态。在port->lock获取的情况下锁定。禁用本地中断。调用该函数必须不能睡眠。


stop_tx:停止传输字符。这可能是由于CTS线路没有激活,或者tty层表明由于XOFF字符而停止传输。驱动程序应该尽快停止传输字符。在port->lock获取的情况下锁定,禁用本地中断,该调用不能睡眠。



start_tx:开始传输字符。在port->lock获取的情况下锁定,禁用本地中断,该调用不能睡眠。



throttle:通知串行驱动程序,line规则的输入缓冲区已接近满,并且它应该以某种方式发出信号,不应该再向串行端口发送字符,只有在启用了硬件辅助流控制时才会调用该函数。由tty层通过unthrottle()和termios修改序列化锁定。


unthrottle:通知串行驱动程序,字符现在可以发送到串行端口,而不必担心超出line规则的输入缓冲区。只有在启用了硬件辅助流控制时才会调用该函数。


send_xchar:发送一个高优先级字符,即使端口停止。这是用来实现XON/XOFF流量控制和tcflow()。如果串行驱动程序没有实现这个函数,那么tty内核将把字符附加到循环缓冲区,然后调用start_tx()/stop_tx()来清除数据。如果ch == '0' (__DISABLED_CHAR)则不传输。不需要锁定,中断情况依赖于调用者。


stop_rx:停止接收字符,该端口正在关闭中。在port->lock获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。


start_rx:开始接收字符。在port->lock获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。


enable_ms:启用modem状态中断。这个方法可以被多次调用。在调用shutdown()方法时,应该禁用调制解调器状态中断。在``port->lock```获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。


break_ctl:控制中断信号的传输,如果ctl不为零,则应发送断路信号。当ctl=0进行另一个调用时,信号应该终止。调用者持有tty_port->mutex锁定。


startup:获取中断资源并初始化所有底层驱动程序状态。启用接收端口。该函数不应该激活RTS或DTR;这将通过单独调用set_mctrl()来完成。此方法仅在端口初始化打开时调用。


shutdown:禁用端口,禁用可能生效的中断条件,并释放中断资源。该函数不应该禁用RTS或DTR;这已经通过对set_mctrl()的单独调用完成。一旦调用完成,驱动程序不能访问port->state。此方法仅在该端口没有更多用户时调用。


flush_buffer:刷新所有写缓冲区,重置所有DMA状态,并停止所有正在进行的DMA传输。当清除了port->state->xmit循环缓冲区时,将调用该函数。在port->lock获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。


set_termios:更改端口参数,包括字长,奇偶校验,停止位。更新port->read_status_mask和port->ignore_status_mask,以指示接收的事件类型。


set_ldisc:描述线变更通知。在tty_port->mutex持有的情况下调用锁定。

pm:在指定端口上执行电源管理相关活动。state指示由enum uart_pm_state定义的状态,oldstate 指示前一个状态。该函数不应该用来获取任何资源。该函数将在端口最初打开并最终关闭时被调用,除非端口也是系统控制台。即使没有设置CONFIG_PM,也会发生这种情况。无锁定的情况下调用。


type:返回一个指向描述指定端口的字符串常量的指针,或者返回NULL,在这种情况下,字符串'unknown'被替换。无锁定,中断设置依赖于调用者。


release_port:释放端口当前正在使用的所有内存和IO区域资源。没有锁定,中断设置依赖于调用者。


request_port:请求端口所需的任何内存和IO区域资源。如果任何一个请求失败,当这个函数返回时不应该注册任何资源,并且它应该在失败时返回-EBUSY。无锁定,中断设置依赖于调用者。


config_port:执行port所需的自动配置步骤。type包含所需配置的位掩码。UART_CONFIG_TYPE表示该端口需要检测和识别。port->type应该设置为找到的类型,如果没有检测到端口,则设置为PORT_UNKNOWN。


UART_CONFIG_IRQ表示中断信号的自动配置,应该使用标准内核自动探测技术进行探测。在端口有内部硬连线中断的平台上(例如,片上系统实现),这是不必要的。无锁定,中断设置依赖于调用者。


verify_port:验证serinfo中包含的新串行端口信息是否适合此端口类型。无锁定,中断设置依赖于调用者。


ioctl:执行任何端口特定的ioctl。IOCTL命令必须使用中找到的标准编号系统来定义。无锁定,中断设置依赖于调用者。

unsetunset
四、常用API总结unset

//调度写处理
voiduart_write_wakeup(structuart_port*port)

//更新每个端口帧定时信息
voiduart_update_timeout(structuart_port*port,unsignedintcflag,unsignedintbaud)

//返回特定端口的波特率
unsignedintuart_get_baud_rate(structuart_port*port,structktermios*termios,conststructktermios*old,unsignedintmin,unsignedintmax)

//返回uart的时钟分频系数
unsignedintuart_get_divisor(structuart_port*port,unsignedintbaud)

//获取行状态寄存器信息
intuart_get_lsr_info(structtty_struct*tty,structuart_state*state,unsignedint__user*value)

//将控制台(console)消息写入串口
voiduart_console_write(structuart_port*port,constchar*s,unsignedintcount,void(*putchar)(structuart_port*,unsignedchar))

//获取控制台(console)的端口
structuart_port*uart_get_console(structuart_port*ports,intnr,structconsole*co)

//解析earlycon选项参数
intuart_parse_earlycon(char*p,unsignedchar*iotype,resource_size_t*addr,char**options)

//解析串口baud/parity/bits/flow控制
voiduart_parse_options(constchar*options,int*baud,int*parity,int*bits,int*flow)

//设置串口控制台参数
intuart_set_options(structuart_port*port,structconsole*co,intbaud,intparity,intbits,intflow)


//----------------------------Port/driver注册和移除----------------------------//

//向uart核心层注册一个驱动程序
intuart_register_driver(structuart_driver*drv)

//从uart核心层移除驱动程序。
//如果底层驱动程序在uart_add_one_port()中注册了端口,则必须通过uart_remove_one_port()删除已经注册的端口。
voiduart_unregister_driver(structuart_driver*drv)
intuart_add_one_port(structuart_driver*reg,structuart_port*port);
voiduart_remove_one_port(structuart_driver*reg,structuart_port*port);

//判断两个端口是否相等。
//此函数可用于确定两个uart_port结构是否描述相同的端口。
booluart_match_port(conststructuart_port*port1,conststructuart_port*port2)


//电源管理
intuart_suspend_port(structuart_driver*reg,structuart_port*port);
intuart_resume_port(structuart_driver*reg,structuart_port*port);

底层驱动的助手函数

voiduart_handle_dcd_change(structuart_port*uport,boolactive)
voiduart_handle_cts_change(structuart_port*uport,boolactive)
voiduart_insert_char(structuart_port*port,unsignedintstatus,unsignedintoverrun,u8ch,u8flag);
voiduart_xchar_out(structuart_port*uport,intoffset);

booluart_try_toggle_sysrq(structuart_port*port,u8ch)
uart_port_tx_limited(port,ch,count,tx_ready,put_char,tx_done)

//uart端口的发送助手函数
uart_port_tx(port,ch,tx_ready,put_char)

unsetunset
五、uart驱动示例剖析unset


1、原厂设计的uart驱动


有些芯片原厂会针对自家的芯片设计开发出uart驱动,例如nxp的imx6ull,针对该系列的SOC,NXP原厂设计出了一个名为imx.c的驱动,位于/drivers/tty/serial目录中。该驱动以平台驱动为框架设计:

staticstructplatform_driverserial_imx_driver={
.probe=serial_imx_probe,
.remove=serial_imx_remove,

.suspend=serial_imx_suspend,
.resume=serial_imx_resume,
.id_table=imx_uart_devtype,
.driver={
.name="imx-uart",
.of_match_table=imx_uart_dt_ids,
},
};

设备树匹配表是:

b7638a92-7eae-11ee-939d-92fbcf53809c.png

.probe对应的serial_imx_probe()实现如下:

staticintserial_imx_probe(structplatform_device*pdev)
{
structimx_port*sport;
void__iomem*base;
intret=0;
structresource*res;
inttxirq,rxirq,rtsirq;

sport=devm_kzalloc(&pdev->dev,sizeof(*sport),GFP_KERNEL);
if(!sport)
return-ENOMEM;

ret=serial_imx_probe_dt(sport,pdev);
if(ret>0)
serial_imx_probe_pdata(sport,pdev);
elseif(ret< 0)
  return ret;

 res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 base = devm_ioremap_resource(&pdev->dev,res);
if(IS_ERR(base))
returnPTR_ERR(base);

rxirq=platform_get_irq(pdev,0);
txirq=platform_get_irq(pdev,1);
rtsirq=platform_get_irq(pdev,2);

sport->port.dev=&pdev->dev;
sport->port.mapbase=res->start;
sport->port.membase=base;
sport->port.type=PORT_IMX,
sport->port.iotype=UPIO_MEM;
sport->port.irq=rxirq;
sport->port.fifosize=32;
sport->port.ops=&imx_pops;
sport->port.rs485_config=imx_rs485_config;
sport->port.rs485.flags=
SER_RS485_RTS_ON_SEND|SER_RS485_RX_DURING_TX;
sport->port.flags=UPF_BOOT_AUTOCONF;
init_timer(&sport->timer);
sport->timer.function=imx_timeout;
sport->timer.data=(unsignedlong)sport;

sport->clk_ipg=devm_clk_get(&pdev->dev,"ipg");
if(IS_ERR(sport->clk_ipg)){
ret=PTR_ERR(sport->clk_ipg);
dev_err(&pdev->dev,"failedtogetipgclk:%d
",ret);
returnret;
}

sport->clk_per=devm_clk_get(&pdev->dev,"per");
if(IS_ERR(sport->clk_per)){
ret=PTR_ERR(sport->clk_per);
dev_err(&pdev->dev,"failedtogetperclk:%d
",ret);
returnret;
}

sport->port.uartclk=clk_get_rate(sport->clk_per);
if(sport->port.uartclk>IMX_MODULE_MAX_CLK_RATE){
ret=clk_set_rate(sport->clk_per,IMX_MODULE_MAX_CLK_RATE);
if(ret< 0) {
   dev_err(&pdev->dev,"clk_set_rate()failed
");
returnret;
}
}
sport->port.uartclk=clk_get_rate(sport->clk_per);

/*
*AllocatetheIRQ(s)i.MX1hasthreeinterruptswhereaslater
*chipsonlyhaveoneinterrupt.
*/
if(txirq>0){
ret=devm_request_irq(&pdev->dev,rxirq,imx_rxint,0,
dev_name(&pdev->dev),sport);
if(ret)
returnret;

ret=devm_request_irq(&pdev->dev,txirq,imx_txint,0,
dev_name(&pdev->dev),sport);
if(ret)
returnret;
}else{
ret=devm_request_irq(&pdev->dev,rxirq,imx_int,0,
dev_name(&pdev->dev),sport);
if(ret)
returnret;
}

imx_ports[sport->port.line]=sport;

platform_set_drvdata(pdev,sport);

returnuart_add_one_port(&imx_reg,&sport->port);
}


从上述代码可知,依然是常规驱动程序设计的思路。在.probe中进行的步骤有:

(1)为描述imx的uart的struct imx_port分配内存。

(2)解析设备树中信息,获取resource。

(3)获取中断相关配置参数。

(4)初始化struct imx_port中的组成元素。

(5)uart时钟参数配置和使能。

(6)使用uart_add_one_port()向uart_driver添加uart_port,在这里就是向imx_reg添加sport->port。imx_reg是struct uart_driver的具体实例;sport->port是struct imx_port中关联的struct uart_port。


2、8250标准uart驱动


本小节中的uart驱动指单纯针对一款SOC设计的驱动,该部分驱动一般由芯片原厂提供。除此之外,有些SOC设计公司会基于标准(例如16550A)的uart通信机制设计UART硬件部分。从而软件驱动上也能使用标准的uart驱动进行通信。例如:8250。linux内核中,8250串口通用驱动的主要文件如下:

drivers/tty/serial/8250/8250_core.c :8250串口驱动核心。

drivers/tty/serial/8250/8250_dw.c :Synopsis DesignWare 8250串口驱动。

drivers/tty/serial/8250/8250_dma.c :8250串口DMA驱动。

drivers/tty/serial/8250/8250_port.c :8250串口端口操作。

drivers/tty/serial/8250/8250_early.c :8250串口early console驱动。


例如rk3568,对于rk3568关于uart的设备,是使用设备树进行描述:

b76fe71a-7eae-11ee-939d-92fbcf53809c.png


主机侧对应的驱动程序则是Synopsis DesignWare 8250串口驱动,由drivers/tty/serial/8250/8250_dw.c文件描述。在该驱动程序中,使用platform驱动方案实现驱动的设计:

b77c755c-7eae-11ee-939d-92fbcf53809c.png


当设备和驱动匹配后,会执行dw8250_probe()函数,该函数实现如下:

staticintdw8250_probe(structplatform_device*pdev)
{
structuart_8250_portuart={},*up=&uart;
structuart_port*p=&up->port;
structdevice*dev=&pdev->dev;
structdw8250_data*data;
structresource*regs;
intirq;
interr;
u32val;

regs=platform_get_resource(pdev,IORESOURCE_MEM,0);
if(!regs)
returndev_err_probe(dev,-EINVAL,"noregistersdefined
");

irq=platform_get_irq_optional(pdev,0);
/*nointerrupt->fallbacktopolling*/
if(irq==-ENXIO)
irq=0;
if(irq< 0)
  return irq;

 spin_lock_init(&p->lock);
p->mapbase=regs->start;
p->irq=irq;
p->handle_irq=dw8250_handle_irq;
p->pm=dw8250_do_pm;
p->type=PORT_8250;
p->flags=UPF_SHARE_IRQ|UPF_FIXED_PORT;
p->dev=dev;
p->iotype=UPIO_MEM;
p->serial_in=dw8250_serial_in;
p->serial_out=dw8250_serial_out;
p->set_ldisc=dw8250_set_ldisc;
p->set_termios=dw8250_set_termios;

p->membase=devm_ioremap(dev,regs->start,resource_size(regs));
if(!p->membase)
return-ENOMEM;

data=devm_kzalloc(dev,sizeof(*data),GFP_KERNEL);
if(!data)
return-ENOMEM;

data->data.dma.fn=dw8250_fallback_dma_filter;
data->pdata=device_get_match_data(p->dev);
p->private_data=&data->data;

data->uart_16550_compatible=device_property_read_bool(dev,
"snps,uart-16550-compatible");

err=device_property_read_u32(dev,"reg-shift",&val);
if(!err)
p->regshift=val;

err=device_property_read_u32(dev,"reg-io-width",&val);
if(!err&&val==4){
p->iotype=UPIO_MEM32;
p->serial_in=dw8250_serial_in32;
p->serial_out=dw8250_serial_out32;
}

if(device_property_read_bool(dev,"dcd-override")){
/*AlwaysreportDCDasactive*/
data->msr_mask_on|=UART_MSR_DCD;
data->msr_mask_off|=UART_MSR_DDCD;
}

if(device_property_read_bool(dev,"dsr-override")){
/*AlwaysreportDSRasactive*/
data->msr_mask_on|=UART_MSR_DSR;
data->msr_mask_off|=UART_MSR_DDSR;
}

if(device_property_read_bool(dev,"cts-override")){
/*AlwaysreportCTSasactive*/
data->msr_mask_on|=UART_MSR_CTS;
data->msr_mask_off|=UART_MSR_DCTS;
}

if(device_property_read_bool(dev,"ri-override")){
/*AlwaysreportRingindicatorasinactive*/
data->msr_mask_off|=UART_MSR_RI;
data->msr_mask_off|=UART_MSR_TERI;
}

/*Alwaysaskforfixedclockratefromaproperty.*/
device_property_read_u32(dev,"clock-frequency",&p->uartclk);

/*Ifthereisseparatebaudclk,gettheratefromit.*/
data->clk=devm_clk_get_optional(dev,"baudclk");
if(data->clk==NULL)
data->clk=devm_clk_get_optional(dev,NULL);
if(IS_ERR(data->clk))
returnPTR_ERR(data->clk);

INIT_WORK(&data->clk_work,dw8250_clk_work_cb);
data->clk_notifier.notifier_call=dw8250_clk_notifier_cb;

err=clk_prepare_enable(data->clk);
if(err)
returndev_err_probe(dev,err,"couldnotenableoptionalbaudclk
");

err=devm_add_action_or_reset(dev,dw8250_clk_disable_unprepare,data->clk);
if(err)
returnerr;

if(data->clk)
p->uartclk=clk_get_rate(data->clk);

/*Ifnoclockrateisdefined,fail.*/
if(!p->uartclk)
returndev_err_probe(dev,-EINVAL,"clockratenotdefined
");

data->pclk=devm_clk_get_optional(dev,"apb_pclk");
if(IS_ERR(data->pclk))
returnPTR_ERR(data->pclk);

err=clk_prepare_enable(data->pclk);
if(err)
returndev_err_probe(dev,err,"couldnotenableapb_pclk
");

err=devm_add_action_or_reset(dev,dw8250_clk_disable_unprepare,data->pclk);
if(err)
returnerr;

data->rst=devm_reset_control_get_optional_exclusive(dev,NULL);
if(IS_ERR(data->rst))
returnPTR_ERR(data->rst);

reset_control_deassert(data->rst);

err=devm_add_action_or_reset(dev,dw8250_reset_control_assert,data->rst);
if(err)
returnerr;

dw8250_quirks(p,data);

/*IftheBusyFunctionalityisnotimplemented,don'thandleit*/
if(data->uart_16550_compatible)
p->handle_irq=NULL;

if(!data->skip_autocfg)
dw8250_setup_port(p);

/*Ifwehaveavalidfifosize,tryhookingupDMA*/
if(p->fifosize){
data->data.dma.rxconf.src_maxburst=p->fifosize/4;
data->data.dma.txconf.dst_maxburst=p->fifosize/4;
up->dma=&data->data.dma;
}

data->data.line=serial8250_register_8250_port(up);
if(data->data.line< 0)
  return data->data.line;

/*
*Someplatformsmayprovideareferenceclocksharedbetweenseveral
*devices.Inthiscaseanyclockstatechangemustbeknowntothe
*UARTportatleastpostfactum.
*/
if(data->clk){
err=clk_notifier_register(data->clk,&data->clk_notifier);
if(err)
returndev_err_probe(dev,err,"Failedtosettheclocknotifier
");
queue_work(system_unbound_wq,&data->clk_work);
}

platform_set_drvdata(pdev,data);

pm_runtime_set_active(dev);
pm_runtime_enable(dev);

return0;
}

在上述probe中,主要执行的操作如下:

(1)从platform_device中提取中struct resource。

(2)设置struct uart_port中组成元素的初始化参数值和一些必要的callback。

(3)读取dev中的参数值。

(4)设置时钟。

(5)调用serial8250_register_8250_port()注册8250端口。

unsetunset

六、总结unset

1、本文描述了linux下的uart框架,因uart隶属于tty,故而芯片原厂一般会将与uart相关的驱动放置于/drivers/tty/serial目录中。

2、关于linux下的uart驱动,芯片原厂一般都会去实现,而不用再去开发这一层的驱动。但是uart驱动框架还是值得去了解和学习。本文总结了一些常用的API(以具体linux版本为主),也简要分析了两款芯片的uart驱动程序。

3、基于linux,作为uart的使用者,只需要通过设备树传递uart相关的参数(假如linux支持设备树),这时候uart驱动程序会自动加载运行,向用户空间暴露出设备节点,这时候用户空间就可以方便的使用uart进行通信了。

4、芯片原厂设计的驱动,往往具有兼容性,支持多款同系列或者同类型的芯片!

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206735
  • 驱动程序
    +关注

    关注

    19

    文章

    770

    浏览量

    47234
  • 源码
    +关注

    关注

    8

    文章

    573

    浏览量

    28586
  • 函数
    +关注

    关注

    3

    文章

    3868

    浏览量

    61309

原文标题:玩玩linux的uart,爽歪歪!

文章出处:【微信号:嵌入式小生,微信公众号:嵌入式小生】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    linux内核深度剖析,另附有光盘资料

    linux内核深度剖析,对于想学linux内核的人来说,绝对值得一看,另附有光盘资料。
    发表于 01-15 21:25

    浅析linux UART驱动和tty架构

    关于linux UART驱动和tty架构的理解
    发表于 07-03 09:55

    Linux驱动示例

    Linux设备驱动分为:字符设备、块设备和网络设备。原理图如下:
    发表于 07-26 08:13

    Linux下的UART驱动框架详解

    Linux下的UART驱动框架
    发表于 12-22 07:18

    全面剖析嵌入式Linux开发

    嵌入式Linux基础教程(第2版) 编辑推荐  嵌入式Linux权威著作   Amazon全五星评价  全面剖析嵌入式Linux开发,揭示大量技术内幕基本信息原书名:Embedded
    发表于 11-05 08:35

    基于Arduino无驱动板程序示例

    基于Arduino无驱动板程序示例
    发表于 12-17 21:16 2次下载

    AN-1282:在示例应用中剖析ADuCM350电源电流

    AN-1282:在示例应用中剖析ADuCM350电源电流
    发表于 04-21 20:55 3次下载
    AN-1282:在<b class='flag-5'>示例</b>应用中<b class='flag-5'>剖析</b>ADuCM350电源电流

    嵌入式Linux UART

    文章目录前言串口连接串口测试C代码微信公众号前言这是前篇:嵌入式Linux i.MX开发板嵌入式Linux NFS嵌入式Linux 交叉编译工具链嵌入式Linux LED GPIO嵌入
    发表于 11-01 16:26 8次下载
    嵌入式<b class='flag-5'>Linux</b> <b class='flag-5'>UART</b>

    嵌入式linux读写can收发简单示例基于socket can

    嵌入式linux读写can简单示例
    发表于 11-01 17:07 14次下载
    嵌入式<b class='flag-5'>linux</b>读写can收发简单<b class='flag-5'>示例</b>基于socket can

    Linux设备驱动思想在STM32编程中的应用

    这几天看了一下Linux设备驱动,发现这套思想其实也可以用在普通的单片机编程上。这种思想较好的分割了驱动层和应用层的任务,方便分层开发。以前,我们开发STM32驱动的时候,会给设备写一
    发表于 12-31 18:56 3次下载
    <b class='flag-5'>Linux</b>设备<b class='flag-5'>驱动</b>思想在STM32编程中的应用

    WK系列SPI扩展UART驱动移植参考

    ,WK2132能实现扩展 2 路 UART。目前这几款芯片使用的都是相同的 linux 驱动。WK 系列扩展的子通道的 UART 具备如下功能特点:   每个子通道
    发表于 04-18 09:10 14次下载

    MPC82G516 MCU的串行UART中断示例代码

    MPC82G516 MCU的串行UART中断示例代码
    发表于 06-30 17:32 0次下载
    MPC82G516 MCU的串行<b class='flag-5'>UART</b>中断<b class='flag-5'>示例</b>代码

    MPC82G516 MCU的串行UART示例代码

    MPC82G516 MCU的串行UART示例代码
    发表于 06-30 17:24 0次下载
    MPC82G516 MCU的串行<b class='flag-5'>UART</b><b class='flag-5'>示例</b>代码

    Linux UART开发指南

    介绍 Linux 内核中 UART 驱动的接口及使用方法,为 UART 设备的使用者提供参考。
    的头像 发表于 03-06 10:26 970次阅读
    <b class='flag-5'>Linux</b> <b class='flag-5'>UART</b>开发指南

    全志T507 uart驱动使用文档

    本文档介绍Linux内核中UART驱动的接口及使用方法为UART设备的使用者提供参考。适用于linux4.9内容资料来源:https://w
    发表于 08-14 15:10 0次下载