博通wifi驱动详解(三)
1 WLAN驱动结构介绍
1.1 SDIO驱动
1.2 Boardcom无线通讯芯片
1.2.1 概述
1.2.2 源码
这里主要内容到bcmsdio,dhd和wl三个目录下,bcm4330驱动的入口在dhd/sys/dhd_linux.c文件中的dhd_module()函数,设备的初始化和相关驱动注册都从这里开始,
1.3 详细接口及代码分析
1.3.1 WIFI驱动流程分析
以boardcom bcm4329芯片驱动为例,相应的函数流程图如下:
1.3.2 WIFI设备注册流程
Static struct Platform_driverwifi_device={
Static struct Platform_driverwifi_device_legacy={
上面的展示了wifi平台设备驱动的注册过程,那么在平台相关的代码区应该有wifi作为平台设备被初始化和注册的地方:
Path: kernel/arch/arm/mach-msm/msm_
static struct resource mahimahi_wifi_resources[] = {
.start =MSM_GPIO_TO_INT(MAHIMAHI_GPIO_WIFI_IRQ),
.end = MSM_GPIO_TO_INT(MAHIMAHI_GPIO_WIFI_IRQ),
.flags = IORESOURCE_IRQ |IORESOURCE_IRQ_HIGHLEVEL | IORESOURCE_IRQ_SHAREABLE,
static structwifi_platform_data mahimahi_wifi_control = {
.set_power = mahimahi_wifi_power,
.set_reset = mahimahi_wifi_reset,
.set_carddetect = mahimahi_wifi_set_carddetect,
.mem_prealloc = mahimahi_wifi_mem_prealloc,
static struct platform_device mahimahi_wifi_device = {
.num_resources = ARRAY_SIZE(mahimahi_wifi_resources),
.resource = mahimahi_wifi_resources,
.platform_data = &mahimahi_wifi_control,
上面是对wifi_device设备的初始化,下面是对该设备的注册:
static int __initmahimahi_wifi_init(void)
printk("%s: start\n",__func__);
mahimahi_wifi_update_nvs("sd_oobonly=1\r\n", 0);
mahimahi_wifi_update_nvs("btc_params70=0x32\r\n", 1);
ret = platform_device_register(&mahimahi_wifi_device);
late_initcall(mahimahi_wifi_init);//表明在系统启动的后期会自动调用加载该模块
static int wifi_probe(structplatform_device *pdev)
struct wifi_platform_data *wifi_ctrl =
(structwifi_platform_data *)(pdev->dev.platform_data);
DHD_ERROR(("## %s\n",__FUNCTION__));
wifi_irqres = platform_get_resource_byname(pdev,IORESOURCE_IRQ, "bcmdhd_wlan_irq");
wifi_irqres =platform_get_resource_byname(pdev,
IORESOURCE_IRQ,"bcm4329_wlan_irq");
wifi_control_data = wifi_ctrl;
wifi_set_power(1,0); /* Power On */
wifi_set_carddetect(1); /* CardDetect (0->1) */
int wifi_set_power(int on, unsignedlong msec)
DHD_ERROR(("%s = %d\n",__FUNCTION__, on));
if (wifi_control_data &&wifi_control_data->set_power) {
wifi_control_data->set_power(on);
static struct platform_device mahimahi_wifi_device = {
.num_resources = ARRAY_SIZE(mahimahi_wifi_resources),
.resource = mahimahi_wifi_resources,
.platform_data =&mahimahi_wifi_control,
static struct wifi_platform_datamahimahi_wifi_control= {
.set_power = mahimahi_wifi_power,
.set_reset = mahimahi_wifi_reset,
.set_carddetect =mahimahi_wifi_set_carddetect,
.mem_prealloc = mahimahi_wifi_mem_prealloc,
所以它实际调用的是mahimahi_wifi_power函数,该函数的定义在kernel/arch/arm /mach-msm/board-mahimahi-mmc.c之中:
int mahimahi_wifi_power(int on)
printk("%s: %d\n", __func__, on);
config_gpio_table(wifi_on_gpio_table,
ARRAY_SIZE(wifi_on_gpio_table));
config_gpio_table(wifi_off_gpio_table,
ARRAY_SIZE(wifi_off_gpio_table));
gpio_set_value(MAHIMAHI_GPIO_WIFI_SHUTDOWN_N,on); /* WIFI_SHUTDOWN */
mahimahi_wifi_power_state = on;
调用gpio_set_value操作wifi芯片,给wifi芯片上电。那么来看看wifi_set_ carddetect函数究竟干了什么:
static int wifi_set_carddetect(int on)
DHD_ERROR(("%s = %d\n", __FUNCTION__, on));
if(wifi_control_data && wifi_control_data->set_carddetect) {
wifi_control_data->set_carddetect(on);
同样会调用wifi_device的mahimahi_wifi_set_carddetect函数:
Path:kernel/arch/arm/mach-msm/board-mahimahi-mmc.c
int mahimahi_wifi_set_carddetect(int val)
pr_info("%s: %d\n", __func__, val);
wifi_status_cb(val,wifi_status_cb_devid);
pr_warning("%s: Nobody to notify\n", __func__);
static int mahimahi_wifi_status_register(
void (*callback)(intcard_present, void *dev_id),
wifi_status_cb_devid = dev_id;
static unsigned intmahimahi_wifi_status(struct device *dev)
static structmmc_platform_data mahimahi_wifi_data = {
.status = mahimahi_wifi_status,
.register_status_notify= mahimahi_wifi_status_register,
.embedded_sdio = &mahimahi_wifi_emb_data,
int msm_add_sdcc(unsigned intcontroller, struct mmc_platform_data *plat,
unsigned int stat_irq,unsigned long stat_irq_flags);
int __initmahimahi_init_mmc(unsigned int sys_rev, unsigned debug_uart)
msm_add_sdcc(1, &mahimahi_wifi_data, 0, 0);
msm_add_sdcc(2,&mahimahi_sdslot_data, 0, 0);
mahimahi_sdslot_data.status =mahimahi_sdslot_status_rev0;
mahimahi_sdslot_data.register_status_notify = NULL;
set_irq_wake(MSM_GPIO_TO_INT(MAHIMAHI_GPIO_SDMC_CD_REV0_N), 1);
msm_add_sdcc(2, &mahimahi_sdslot_data,
可以跟踪到这里Path:kernel/arch/arm/mach-msm/devices-msm7x30.c
struct platform_device msm_device_sdc1 = {
.num_resources = ARRAY_SIZE(resources_sdc1),
.coherent_dma_mask =0xffffffff,
struct platform_device msm_device_sdc2 = {
.num_resources = ARRAY_SIZE(resources_sdc2),
.coherent_dma_mask =0xffffffff,
struct platform_devicemsm_device_sdc3 = {
.num_resources = ARRAY_SIZE(resources_sdc3),
.coherent_dma_mask = 0xffffffff,
struct platform_device msm_device_sdc4= {
.num_resources = ARRAY_SIZE(resources_sdc4),
.coherent_dma_mask = 0xffffffff,
static struct platform_device *msm_sdcc_devices[] __initdata = {
int __initmsm_add_sdcc(unsigned int controller, struct mmc_platform_data *plat,
unsigned int stat_irq,unsigned long stat_irq_flags)
pdev =msm_sdcc_devices[controller-1]; //因为传过来的controller是1,所以下面注册的是第一个平台设备
pdev->dev.platform_data= plat;//被传递给平台设备的platform_data
res =platform_get_resource_byname(pdev, IORESOURCE_IRQ, "status_irq");
res->start = res->end =stat_irq;
res->flags &=~IORESOURCE_DISABLED;
return platform_device_register(pdev); //如上所述
那么这个平台设备是什么呢,就是sd卡控制器,也就是前面说的host驱动所驱动的主机控制设备。
Path: drivers/mmc/host/msm_sdcc.c
static struct platform_drivermsmsdcc_driver = {
static int __initmsmsdcc_init(void)
return platform_driver_register(&msmsdcc_driver);
msmsdcc_probe(structplatform_device *pdev)
if (stat_irqres &&!(stat_irqres->flags & IORESOURCE_DISABLED)) {
} else if(plat->register_status_notify) {
plat->register_status_notify(msmsdcc_status_notify_cb,host);
msmsdcc_status_notify_cb调用msmsdcc_check_status函数:
msmsdcc_status_notify_cb(intcard_present, void *dev_id)
struct msmsdcc_host *host = dev_id;
printk(KERN_DEBUG "%s:card_present %d\n", mmc_hostname(host->mmc),
msmsdcc_check_status((unsigned long) host);
msmsdcc_check_status调用mmc_detect_change函数:
msmsdcc_check_status(unsignedlong data)
pr_info("%s: Slot statuschange detected (%d -> %d)\n",
mmc_hostname(host->mmc),host->oldstat, status);
if (status &&!host->plat->built_in)
mmc_detect_change(host->mmc, (5 * HZ) / 2);
mmc_detect_change(host->mmc, 0);
mod_timer(&host->timer,jiffies + HZ);
可以看到mmc_detect_change被调用了,这个函数触发了一个延时工作:
void mmc_detect_change(structmmc_host *host, unsigned long delay)
mmc_schedule_delayed_work(&host->detect, delay);
msmsdcc_probe(structplatform_device *pdev)
mmc = mmc_alloc_host(sizeof(struct msmsdcc_host),&pdev->dev);
struct mmc_host*mmc_alloc_host(int extra, struct device *dev)
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
mmc_rescan是core.c中一个很重要的函数,它遵照 SDIO 卡协议的 SDIO 卡启动过程,包括了非激活模式、卡识别模式和数据传输模式三种模式共九种状态的转换,你需要参照相关规范来理解。
void mmc_rescan(structwork_struct *work)
container_of(work, structmmc_host, detect.work);
mmc_send_if_cond(host, host->ocr_avail);
err = mmc_send_io_op_cond(host, 0, &ocr);
if (mmc_attach_sdio(host, ocr))
这个mmc_attach_sdio函数很重要,它是SDIO卡的初始化的起点,主要工作包括:匹配SDIO卡的工作电压,分配并初始化mmc_card结构,然后注册mmc_card到系统中:
* Starting point for SDIO card init.
int mmc_attach_sdio(structmmc_host *host, u32 ocr)
mmc_attach_bus(host,&mmc_sdio_ops); //初始化host的bus_ops
host->ocr = mmc_select_voltage(host, ocr);//匹配SDIO卡工作电压
err = mmc_sdio_init_card(host, host->ocr, NULL, 0);//检测,分配初始化mmc_card
* If needed, disconnect card detectionpull-up resistor.
*Initialize (but don't add) all present functions.
for (i = 0; i < funcs; i++, card->sdio_funcs++) {
#ifdef CONFIG_MMC_EMBEDDED_SDIO
if(host->embedded_sdio_data.funcs) {
tmp = sdio_alloc_func(host->card);
tmp->class = host->embedded_sdio_data.funcs[i].f_class;
tmp->max_blksize = host->embedded_sdio_data.funcs[i].f_maxblksize;
tmp->vendor = card->cis.vendor;
tmp->device = card->cis.device;
err =sdio_init_func(host->card, i + 1);
#ifdefCONFIG_MMC_EMBEDDED_SDIO
* First add the card to the drivermodel...
err = mmc_add_card(host->card); //添加mmc_card
err =sdio_add_func(host->card->sdio_func[i]);//将sdio_func加入系统
1.3.2.1 加载SDIO host驱动模块
Host作为平台设备被注册,前面也有列出相应源码:
static struct platform_drivermsmsdcc_driver = {
.probe = msmsdcc_probe,
.suspend = msmsdcc_suspend,
.resume = msmsdcc_resume,
.driver = {
.name = "msm_sdcc",
},
};
static int __initmsmsdcc_init(void)
{
returnplatform_driver_register(&msmsdcc_driver);
}
Probe函数会调用mmc_alloc_host函数(代码前面已经贴出)来创建mmc_host结构变量,进行必要的初始化之后,调用mmc_add_host函数将它添加到驱动里面:
int mmc_add_host(structmmc_host *host)
{
……
err =device_add(&host->class_dev);
if (err)
return err;
mmc_start_host(host);
if (!(host->pm_flags &MMC_PM_IGNORE_PM_NOTIFY))
register_pm_notifier(&host->pm_notify);
return 0;
}
Mmc_start_host定义如下:
void mmc_start_host(structmmc_host *host)
{
mmc_power_off(host);
mmc_detect_change(host, 0);
}
mmc_power_off中对iOS进行了设置,然后调用 mmc_set_ios(host);
host->ios.power_mode = MMC_POWER_OFF;
host->ios.bus_width = MMC_BUS_WIDTH_1;
host->ios.timing =MMC_TIMING_LEGACY;
mmc_set_ios(host);
mmc_set_ios(host) 中的关键语句 host->ops->set_ios(host, ios),实际上在host驱动的probe函数中就已经对host->ops进行了初始化:
……
/*
* Setup MMC host structure
*/
mmc->ops = &msmsdcc_ops;
……
static const structmmc_host_ops msmsdcc_ops = {
.request = msmsdcc_request,
.set_ios =msmsdcc_set_ios,
.enable_sdio_irq =msmsdcc_enable_sdio_irq,
};
所以实际上调用的是msmsdcc_set_ios,关于这个函数就不介绍了,可以参考源码,再看 mmc_detect_change(host, 0),最后一句是:
mmc_schedule_delayed_work(&host->detect,delay);
实际上就是调用我们前面说的延时函数 mmc_rescan,后面的流程是一样的。
1.3.2.2 SDIO设备中断
SDIO设备通过SDIO总线与host相连,SDIO总线的DAT[1]即pin8可以作为中断线使用,当SDIO设备向host产生中断时,host会对终端做出相应的动作,在host驱动的probe函数中申请并注册相应的中断函数:
static int
msmsdcc_probe(structplatform_device *pdev)
{
......
cmd_irqres = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
"cmd_irq");
pio_irqres =platform_get_resource_byname(pdev, IORESOURCE_IRQ,
"pio_irq");
stat_irqres =platform_get_resource_byname(pdev, IORESOURCE_IRQ,
"status_irq");
......
if (stat_irqres && !(stat_irqres->flags &IORESOURCE_DISABLED)) {
unsigned long irqflags =IRQF_SHARED |
(stat_irqres->flags& IRQF_TRIGGER_MASK);
host->stat_irq = stat_irqres->start;
ret = request_irq(host->stat_irq,
msmsdcc_platform_status_irq,
irqflags,
DRIVER_NAME " (slot)",
host);
if (ret) {
pr_err("%s: Unableto get slot IRQ %d (%d)\n",
mmc_hostname(mmc), host->stat_irq, ret);
goto clk_disable;
}
}
......
}
当产生相应的中断时调用msmsdcc_platform_status_irq中断处理函数,这个函数的处理流程:
msmsdcc_platform_status_irq—>
msmsdcc_check_statusà
mmc_detect_changeà
mmc_rescanà
那么,这里为何调用mmc_rescan呢?因为前面说过mmc_rescanrescan函数主要用于SDIO设备的初始化,如果SDIO设备产生中断不应该是已经初始化可以使用了吗?其实mmc_rescan还有其它的工作,从函数名就能看出来它还有再扫描检测功能,即如果设备产生了中断,mmc_rescan函数一开始就会再次检测所有挂接在该host上的所有SDIO设备,确认是否存在,如果不存在就做相应的释放工作,以确保数据的一致性。如果检测到了新的设备那么它就会创建一个新的mmc_card,初始化并添加该设备。
中断引发的调用mmc_rescan动作的意义:实现了SDIO设备的热插拔功能。
1.3.3 WIFI驱动流程(二)
dhd_mudule_init—> //path:dhd/sys/dhd_linux.c
Dhd_bus_registerà // dhd/sys/dhd_sdio.c
Bcmsdh_registerà // bcmsdio/sys/bcmsdh_linux.c
Sdio_function_inità // bcmsdio/sys/bcmsdh_sdmmc_linux.c
Sdio_register_driverà // bcmsdio/sys/bcmsdh_sdmmc_linux.c
Bcmsdh_sdmmc_probeà//bcmsdio/sys/bcmsdh_sdmmc_linux.c
Bcmsdh_probeà//bcmsdio/sys/bcmsdh_linux.c
Bcmsdio_probeà //dhd/sys/dhd_sdio.c
bcmsdh_register(bcmsdh_driver_t*driver)
drvinfo = *driver; //注意这里,后面会介绍到它的用处
SDLX_MSG(("Linux Kernel SDIO/MMC Driver\n"));
SDLX_MSG(("Intel PXA270 SDIO Driver\n"));
error =driver_register(&bcmsdh_driver);
#endif /* defined(BCMLXSDMMC) */
#endif /*defined(BCMPLATFORM_BUS) */
#if !defined(BCMPLATFORM_BUS)&& !defined(BCMLXSDMMC)
#if (LINUX_VERSION_CODE <KERNEL_VERSION(2, 6, 0))
if (!(error =pci_module_init(&bcmsdh_pci_driver)))
if (!(error =pci_register_driver(&bcmsdh_pci_driver)))
SDLX_MSG(("%s: pci_module_initfailed 0x%x\n", __FUNCTION__, error));
再看看dhdsdio_probe函数调用的玄机,从上面的bcmsdh_register函数可以看出它的参数被传递给了drvinfo,看看bcmsdh_register的调用地方:
static bcmsdh_driver_t dhd_sdio = {
DHD_TRACE(("%s: Enter\n",__FUNCTION__));
return bcmsdh_register(&dhd_sdio);
上面传递的参数是dhd_sdio结构变量,被用两个函数初始化了,那么哪一个是attach呢?需要找到定义bcmsdh_driver_t结构定义的地方:
void *(*attach)(uint16 vend_id, uint16 dev_id, uint16 bus,uint16 slot,
uint16 func, uint bustype, void * regsva, osl_t * osh,
没错,就是第一个dhdsdio_probe函数,再来看看什么地方调用了这个attach函数:
Path:bcmsdio/sys/bcmsdh_linux.c
int bcmsdh_probe(struct device*dev)
if (!(sdhc->ch =drvinfo.attach((vendevid>> 16),
(vendevid & 0xFFFF), 0, 0, 0, 0,
SDLX_MSG(("%s: device attachfailed\n", __FUNCTION__));
1.3.4 网络设备注册流程
dhd_attach(osl_t *osh, structdhd_bus *bus, uint bus_hdrlen)
struct net_device *net = NULL;
/* Allocate etherdev, including spacefor private structure */
if (!(net = alloc_etherdev(sizeof(dhd)))) {//网络设备的创建
DHD_ERROR(("%s: OOM -alloc_etherdev\n", __FUNCTION__));
dhd_state |=DHD_ATTACH_STATE_NET_ALLOC;
/* Allocate primary dhd_info */
if (!(dhd = MALLOC(osh, sizeof(dhd_info_t)))) { //dhd的创建
DHD_ERROR(("%s: OOM -alloc dhd_info\n", __FUNCTION__));
/* Set network interface name if it was provided as moduleparameter */
strncpy(net->name,iface_name, IFNAMSIZ);
if ((ch > '9' || ch <'0') && (len < IFNAMSIZ - 2))
if (dhd_add_if(dhd, 0, (void *)net, net->name, NULL, 0, 0)== DHD_BAD_IF)//将前面创建的net添加到iflist列表中
dhd_state |= DHD_ATTACH_STATE_ADD_IF;
Memcpy(netdev_priv(net), &dhd, sizeof(dhd));//关联dhd和net
dhd_add_if(dhd_info_t *dhd, int ifidx, void *handle, char *name,
uint8 *mac_addr,uint32 flags, uint8 bssidx)
DHD_TRACE(("%s: idx %d,handle->%p\n", __FUNCTION__, ifidx, handle));
ASSERT(dhd && (ifidx <DHD_MAX_IFS));
free_netdev(ifp->net); //如果已经存在,释放net成员
if ((ifp = MALLOC(dhd->pub.osh,sizeof(dhd_if_t))) == NULL) {
DHD_ERROR(("%s: OOM - dhd_if_t\n", __FUNCTION__)); //否则,创建一个dhd_if_t结构变量
memset(ifp, 0, sizeof(dhd_if_t));
ifp->info = dhd;//进行系列初始化,添加工作
strncpy(ifp->name, name, IFNAMSIZ);
memcpy(&ifp->mac_addr, mac_addr,ETHER_ADDR_LEN);
ASSERT(&dhd->thr_sysioc_ctl.thr_pid >= 0);
up(&dhd->thr_sysioc_ctl.sema);
ifp->net = (struct net_device *)handle;//handle即一个net_device变量
这样,一个net_device网路设备就被添加到了接口管理列表中了,但是这是网路设备还没有完成初始化和注册工作,bcmsdio_probe函数随后对dhd_net_attach的调用完成了这个操作:
dhd_net_attach(dhd_pub_t*dhdp, int ifidx)
dhd_info_t *dhd = (dhd_info_t*)dhdp->info;
struct net_device *net = NULL;
uint8 temp_addr[ETHER_ADDR_LEN] = {0x00, 0x90, 0x4c, 0x11, 0x22, 0x33 };
DHD_TRACE(("%s: ifidx %d\n",__FUNCTION__, ifidx));
ASSERT(dhd &&dhd->iflist[ifidx]);
net = dhd->iflist[ifidx]->net;//首先从刚才添加的接口列表中取出net,然后进行下面的系列初始化工作
//根据内核版本信息,选择对net成员函数的初始化方式,假设是2.6.30的版本
#if (LINUX_VERSION_CODE <KERNEL_VERSION(2, 6, 31))
net->get_stats = dhd_get_stats;
net->do_ioctl =dhd_ioctl_entry;
net->hard_start_xmit = dhd_start_xmit;
net->set_mac_address = dhd_set_mac_address;
net->set_multicast_list = dhd_set_multicast_list;
net->netdev_ops = &dhd_ops_virt;
/* Ok, link into the network layer...*/
* device functions for theprimary interface only
#if (LINUX_VERSION_CODE <KERNEL_VERSION(2, 6, 31))
net->netdev_ops = &dhd_ops_pri;
* We have to use the primaryMAC for virtual interfaces
memcpy(temp_addr,dhd->iflist[ifidx]->mac_addr, ETHER_ADDR_LEN);
* android sets the locallyadministered bit to indicate that this is a
* portable hotspot. This will not work in simultaneous AP/STAmode,
* nor with P2P. Need to set the Donlge's MAC address, andthen use that.
if(!memcmp(temp_addr, dhd->iflist[0]->mac_addr,
DHD_ERROR(("%sinterface [%s]: set locally administered bit in MAC\n",
net->hard_header_len = ETH_HLEN + dhd->pub.hdrlen;
#if LINUX_VERSION_CODE >=KERNEL_VERSION(2, 6, 24)
net->ethtool_ops = &dhd_ethtool_ops;
#endif /* LINUX_VERSION_CODE>= KERNEL_VERSION(2, 6, 24) */
#ifdefined(CONFIG_WIRELESS_EXT)
net->get_wireless_stats = dhd_get_wireless_stats;
#endif /*defined(CONFIG_WIRELESS_EXT) */
dhd->pub.rxsz =DBUS_RX_BUFFER_SIZE_DHD(net);
memcpy(net->dev_addr, temp_addr, ETHER_ADDR_LEN);
if ((err =register_netdev(net)) != 0) { //注册net
DHD_ERROR(("couldn'tregister the net device, err %d\n", err));
到这里net网络设备就被注册到系统中了,设备准备好了就好对设备进行访问了
博通wifi驱动详解(三)相关推荐
- 博通wifi驱动详解
1 WLAN技术 WLAN是英文WirelessLAN的缩写,就是无线局域网的意思.无线以太网技术是一种基于无线传输的局域网技术,与有线网络技术相比,具有灵活.建网迅速.个人化等特点.将 ...
- 博通wifi驱动详解(一)
1 WLAN技术 WLAN是英文WirelessLAN的缩写,就是无线局域网的意思.无线以太网技术是一种基于无线传输的局域网技术,与有线网络技术相比,具有灵活.建网迅速.个人化等特点.将 ...
- 博通wifi驱动详解(四)
1 IOCTL的调用逻辑 之所以要分析这个,是因为上层wpa_supplicant和WIFI驱动打交道的方式,多半是通过ioctl的方式进行的,所以看看它的调用逻辑(这里只列出其主要的调 ...
- 博通wifi驱动详解(二)
1 Wifi模块解析和启动流程 1.1 框架分析 WIFI整体框架如图所示: 首先,用户程序使用WifiManager类来管理Wifi模块,它能够获得Wifi模块的状态,配置和 ...
- linux 博通wifi驱动,博通WiFi驱动
补: //调用bcm_wlan_set_power,将ap6335的12脚拉低,让其内部电源稳压器工作.上电完成返回TRUE.上电正常返回值err=0. [cpp] view plain copy 7 ...
- LCD液晶屏驱动详解
开发环境: 开发板:JZ2440V3 CPU:samsunS3C2440 内核:Linux3.4.2 编译工具:arm-linux-gcc 4.3.2 LCD:4.3存液晶屏AT043TN24 参考文 ...
- LCD 液晶屏驱动详解
LCD液晶屏驱动详解 开发环境 开发板:JZ2440V3 CPU:samsunS3C2440 内核:Linux3.4.2 编译工具:arm-linux-gcc 4.3.2 LCD:4.3存液晶屏AT0 ...
- Linux字符设备驱动详解七(“插件“设备树实现RGB灯驱动)
文章目录 系列文章目录 前言 正文 Device Tree Overlays:"插件"设备树 传统设备树 "插件"设备树 使用前提 案例说明 设备树:foo.d ...
- Linux字符设备驱动详解四(使用自属的xbus驱动总线)
文章目录 系列文章目录 前言 驱动目录 正文 驱动总线 总线管理 总线注册 设备注册 驱动注册 代码示例 总结 系列文章目录 Linux字符设备驱动详解 Linux字符设备驱动详解二(使用设备驱动模型 ...
最新文章
- 简明python教程 --C++程序员的视角(九):函数式编程、特殊类方法、测试及其他...
- VC6.0:“Setup was unable to create a DCOM user account“的解决方案
- php源码中如何添加滚动公告,如何给WordPress网站添加滚动公告?
- 用tomcat 发布mule 服务 (转)
- 《H5 移动营销设计指南》 读书笔记整理
- mongo-关于仲裁节点
- eclipse新建服务器项目,使用eclipse快速新建spirngboot项目的方法
- 如何在 Azure 虚拟机里配置条带化
- 基于arduino WS2812b RGB灯带控制程序一
- 电视盒子刷入linux系统
- 修改wav格式音频比特率的标准方法
- 休闲零食生产企业如何做好供应链管理?
- 2020-4-24 Open Web Application Security Project (OWASP)
- Psim仿真_pi输出加限幅(疑惑)
- Linux 配置php
- 批处理bat 延时启动的方法
- 判断三角形是逆时针还是顺时针
- 能运行Linux电脑,国外达人亲手打造可以运行Linux的名片
- Day16-购物车页面-商品列表修改购物车商品的勾选状态
- WPF中GDI+图形图像的绘制:(五)绘制图像——蒙板效果