大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE稳定放心使用
SDIO-WiFi即基于SDIO接口符合WiFi标准的嵌入式模块,内置802.11协议栈以及TCP/IP协议栈,可实现主平台铜鼓SDIO到无线网络之间转换
SDIO:传输数据块,兼容SD,MMC接口等
先以SDIO设备注册,然后检测到再注册WiFi功能,即用SDIO协议发送命令和数据
sdio
基本概念
接口
1.SD的IO接口,透过SD的IO接口连接外设,透过SD卡的IO数据接位与外设传递数据,具备完整的SDIO stack驱动,热门好用
常见SDIO外设:WiFi card/CMOS sensor card /GPS card/GSM,GPRS moderm card/Bluetooth card / SDIO将会取代GPIO式的SPI接口
总线
与USB总线类似,有两端,host端,device端,host端发命令,device端解析命令,然后可以互通
CLK:host给device的时钟信号,一个时钟周期一个命令
CMD:双向信号,用于传输命令和反应
DAT0-DAT3:四条数据线
VDD:电源信号
VSS1-VSS2:电源地信号
热插拔原理
方法:设置定时器检查/插拔中断检测
硬件:例如GPG10(EINT18)用于检测SD卡,GPG10为高电平,即未插入SD卡,GPG10为低电平,即插入了SD卡
命令
总线上host发请求,device回应,SDIO命令由6个字节组成
a–command: 用于开始传输命令,
b–response:device返回给host的命令,作为command的回应也是cmd传送
c–data:双向,可以设置1线也可设置4线模式,通过data0–data4线
读命令:host->device device–>host(握手信号),host收到回应,通过数据线传送,包含CRC校验码,读完host发送命令通知device,device返回响应
写命令: host->device 发送命令,device回应握手,host收到握手发送数据,同时包含CRC,写入完毕,发送命令通知device操作完毕,device回应
驱动(分层&&分离思想) 设备驱动层(wifi设备)–>核心层(向上向下提供接口)—>主机驱动层(SDIO驱动)
Linux-5.4.rc8源码
文件路径 /include/linux/mmc/host.h
struct mmc_host SD控制器结构定义
struct mmc_card SD卡定义
struct mmc_driver mmc驱动定义
struct sdio_func 功能设备定义
struct mmc_host_ops 操作接口函数定义,从主机控制器向core层注册操作函数,将core层与具体的控制器隔离,即core通过他操作控制器,不直接调用具体主控制器函数
1.host驱动
sdhci-s3c定义的host驱动程序 /driver/mmc/host/sdhci-s3c.c
static struct platform_driver sdhci_s3c_driver = {
.probe = sdhci_s3c_probe,
.remove = sdhci_s3c_remove,
.id_table = sdhci_s3c_driver_ids,
.driver = {
.name = “s3c-sdhci”,
.of_match_table = of_match_ptr(sdhci_s3c_dt_match),
.pm = &sdhci_s3c_pmops,
},
};
static int sdhci_s3c_probe(struct platform_device *pdev)
{
struct s3c_sdhci_platdata *pdata;
struct sdhci_s3c_drv_data *drv_data;
struct device *dev = &pdev->dev;
struct sdhci_host *host;
struct sdhci_s3c *sc;
struct resource *res;
int ret, irq, ptr, clks;
if (!pdev->dev.platform_data && !pdev->dev.of_node) {
dev_err(dev, “no device data specified\n”);
return -ENOENT;
}
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
host = sdhci_alloc_host(dev, sizeof(struct sdhci_s3c));
if (IS_ERR(host)) {
dev_err(dev, “sdhci_alloc_host() failed\n”);
return PTR_ERR(host);
}
sc = sdhci_priv(host);
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
ret = -ENOMEM;
goto err_pdata_io_clk;
}
if (pdev->dev.of_node) {
ret = sdhci_s3c_parse_dt(&pdev->dev, host, pdata);
if (ret)
goto err_pdata_io_clk;
} else {
memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata));
sc->ext_cd_gpio = -1; /* invalid gpio number */
}
drv_data = sdhci_s3c_get_driver_data(pdev);
sc->host = host;
sc->pdev = pdev;
sc->pdata = pdata;
sc->cur_clk = -1;
platform_set_drvdata(pdev, host);
sc->clk_io = devm_clk_get(dev, “hsmmc”);
if (IS_ERR(sc->clk_io)) {
dev_err(dev, “failed to get io clock\n”);
ret = PTR_ERR(sc->clk_io);
goto err_pdata_io_clk;
}
/* enable the local io clock and keep it running for the moment. */
clk_prepare_enable(sc->clk_io);
for (clks = 0, ptr = 0; ptr < MAX_BUS_CLK; ptr++) {
char name[14];
snprintf(name, 14, “mmc_busclk.%d”, ptr);
sc->clk_bus[ptr] = devm_clk_get(dev, name);
if (IS_ERR(sc->clk_bus[ptr]))
continue;
clks++;
sc->clk_rates[ptr] = clk_get_rate(sc->clk_bus[ptr]);
dev_info(dev, “clock source %d: %s (%ld Hz)\n”,
ptr, name, sc->clk_rates[ptr]);
}
if (clks == 0) {
dev_err(dev, “failed to find any bus clocks\n”);
ret = -ENOENT;
goto err_no_busclks;
}
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
host->ioaddr = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(host->ioaddr)) {
ret = PTR_ERR(host->ioaddr);
goto err_req_regs;
}
/* Ensure we have minimal gpio selected CMD/CLK/Detect */
if (pdata->cfg_gpio)
pdata->cfg_gpio(pdev, pdata->max_width);
host->hw_name = “samsung-hsmmc”;
host->ops = &sdhci_s3c_ops;
host->quirks = 0;
host->quirks2 = 0;
host->irq = irq;
/* Setup quirks for the controller */
host->quirks |= SDHCI_QUIRK_NO_ENDATTR_IN_NOPDESC;
host->quirks |= SDHCI_QUIRK_NO_HISPD_BIT;
if (drv_data) {
host->quirks |= drv_data->sdhci_quirks;
sc->no_divider = drv_data->no_divider;
}
#ifndef CONFIG_MMC_SDHCI_S3C_DMA
/* we currently see overruns on errors, so disable the SDMA
* support as well. */
host->quirks |= SDHCI_QUIRK_BROKEN_DMA;
#endif /* CONFIG_MMC_SDHCI_S3C_DMA */
/* It seems we do not get an DATA transfer complete on non-busy
* transfers, not sure if this is a problem with this specific
* SDHCI block, or a missing configuration that needs to be set. */
host->quirks |= SDHCI_QUIRK_NO_BUSY_IRQ;
/* This host supports the Auto CMD12 */
host->quirks |= SDHCI_QUIRK_MULTIBLOCK_READ_ACMD12;
/* Samsung SoCs need BROKEN_ADMA_ZEROLEN_DESC */
host->quirks |= SDHCI_QUIRK_BROKEN_ADMA_ZEROLEN_DESC;
if (pdata->cd_type == S3C_SDHCI_CD_NONE ||
pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
host->quirks |= SDHCI_QUIRK_BROKEN_CARD_DETECTION;
if (pdata->cd_type == S3C_SDHCI_CD_PERMANENT)
host->mmc->caps = MMC_CAP_NONREMOVABLE;
switch (pdata->max_width) {
case 8:
host->mmc->caps |= MMC_CAP_8_BIT_DATA;
/* Fall through */
case 4:
host->mmc->caps |= MMC_CAP_4_BIT_DATA;
break;
}
if (pdata->pm_caps)
host->mmc->pm_caps |= pdata->pm_caps;
host->quirks |= (SDHCI_QUIRK_32BIT_DMA_ADDR |
SDHCI_QUIRK_32BIT_DMA_SIZE);
/* HSMMC on Samsung SoCs uses SDCLK as timeout clock */
host->quirks |= SDHCI_QUIRK_DATA_TIMEOUT_USES_SDCLK;
/*
* If controller does not have internal clock divider,
* we can use overriding functions instead of default.
*/
if (sc->no_divider) {
sdhci_s3c_ops.set_clock = sdhci_cmu_set_clock;
sdhci_s3c_ops.get_min_clock = sdhci_cmu_get_min_clock;
sdhci_s3c_ops.get_max_clock = sdhci_cmu_get_max_clock;
}
/* It supports additional host capabilities if needed */
if (pdata->host_caps)
host->mmc->caps |= pdata->host_caps;
if (pdata->host_caps2)
host->mmc->caps2 |= pdata->host_caps2;
pm_runtime_enable(&pdev->dev);
pm_runtime_set_autosuspend_delay(&pdev->dev, 50);
pm_runtime_use_autosuspend(&pdev->dev);
pm_suspend_ignore_children(&pdev->dev, 1);
ret = mmc_of_parse(host->mmc);
if (ret)
goto err_req_regs;
ret = sdhci_add_host(host);
if (ret)
goto err_req_regs;
#ifdef CONFIG_PM
if (pdata->cd_type != S3C_SDHCI_CD_INTERNAL)
clk_disable_unprepare(sc->clk_io);
#endif
return 0;
err_req_regs:
pm_runtime_disable(&pdev->dev);
err_no_busclks:
clk_disable_unprepare(sc->clk_io);
err_pdata_io_clk:
sdhci_free_host(host);
return ret;
}
host->detected得到 /drivers/mmc/core/host.c
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
INIT_DELAYED_WORK(&host->sdio_irq_work, sdio_irq_work);
mmc_start_host(host);
mmc_register_pm_notifier(host);
/drivers/mmc/core/core.c
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
pr_debug(“%s: %s: trying to init card at %u Hz\n”,
mmc_hostname(host), __func__, host->f_init);
mmc_power_up(host, host->ocr_avail);
/*
* Some eMMCs (with VCCQ always on) may not be reset after power up, so
* do a hardware reset if possible.
*/
mmc_hw_reset_for_init(host);
/*
* sdio_reset sends CMD52 to reset card. Since we do not know
* if the card is being re-initialized, just send it. CMD52
* should be ignored by SD/eMMC cards.
* Skip it if we already know that we do not support SDIO commands
*/
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
sdio_reset(host);
mmc_go_idle(host);
if (!(host->caps2 & MMC_CAP2_NO_SD))
mmc_send_if_cond(host, host->ocr_avail);
/* Order’s important: probe SDIO, then SD, then MMC */
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
if (!mmc_attach_sdio(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_SD))
if (!mmc_attach_sd(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_MMC))
if (!mmc_attach_mmc(host))
return 0;
mmc_power_off(host);
return -EIO;
}
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
pr_debug(“%s: %s: trying to init card at %u Hz\n”,
mmc_hostname(host), __func__, host->f_init);
mmc_power_up(host, host->ocr_avail);
/*
* Some eMMCs (with VCCQ always on) may not be reset after power up, so
* do a hardware reset if possible.
*/
mmc_hw_reset_for_init(host);
/*
* sdio_reset sends CMD52 to reset card. Since we do not know
* if the card is being re-initialized, just send it. CMD52
* should be ignored by SD/eMMC cards.
* Skip it if we already know that we do not support SDIO commands
*/
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
sdio_reset(host);
mmc_go_idle(host);
if (!(host->caps2 & MMC_CAP2_NO_SD))
mmc_send_if_cond(host, host->ocr_avail);
/* Order’s important: probe SDIO, then SD, then MMC */
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
if (!mmc_attach_sdio(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_SD))
if (!mmc_attach_sd(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_MMC))
if (!mmc_attach_mmc(host))
return 0;
mmc_power_off(host);
return -EIO;
}
/driver/mmc/core/stdio.c
int mmc_attach_sdio(struct mmc_host *host)
{
int err, i, funcs;
u32 ocr, rocr;
struct mmc_card *card;
WARN_ON(!host->claimed);
err = mmc_send_io_op_cond(host, 0, &ocr);
if (err)
return err;
mmc_attach_bus(host, &mmc_sdio_ops);
if (host->ocr_avail_sdio)
host->ocr_avail = host->ocr_avail_sdio;
rocr = mmc_select_voltage(host, ocr);
/drivers/mmc/core/mmc.c
static int mmc_init_card(struct mmc_host *host, u32 ocr,
struct mmc_card *oldcard)
{
struct mmc_card *card;
int err;
u32 cid[4];
u32 rocr;
WARN_ON(!host->claimed);
/* Set correct bus mode for MMC before attempting init */
if (!mmc_host_is_spi(host))
mmc_set_bus_mode(host, MMC_BUSMODE_OPENDRAIN);
/*
* Since we’re changing the OCR value, we seem to
* need to tell some cards to go back to the idle
* state. We wait 1ms to give cards time to
* respond.
* mmc_go_idle is needed for eMMC that are asleep
*/
mmc_go_idle(host);
/* The extra bit indicates that we support high capacity */
err =
/drivers/mmc/core/stdio.c
for (i = 0; i < funcs; i++, card->sdio_funcs++) {
err = sdio_init_func(host->card, i + 1);
if (err)
goto remove;
/*
* Enable Runtime PM for this func (if supported)
*/
if (host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_enable(&card->sdio_func[i]->dev);
}
2.设备热插拔
SDIO插拔会触发中断通知cpu,执行卡检测中断处理函数在这个中断服务函数中
mmc_detect_change->mmc_schedule_delayed_work(&host->detect,delay)
INIT_DELAY_WORK(&host->detect,mmc_rescan)调度mmc_rescan,触发初始化流程,检测到有效SDIO设备,将他注册都系统中
static irqreturn_t msdc_irq(int irq, void *dev_id)
{
struct msdc_host *host = (struct msdc_host *) dev_id;
while (true) {
unsigned long flags;
struct mmc_request *mrq;
struct mmc_command *cmd;
struct mmc_data *data;
u32 events, event_mask;
spin_lock_irqsave(&host->lock, flags);
events = readl(host->base + MSDC_INT);
event_mask = readl(host->base + MSDC_INTEN);
if ((events & event_mask) & MSDC_INT_SDIOIRQ)
__msdc_enable_sdio_irq(host, 0);
/* clear interrupts */
writel(events & event_mask, host->base + MSDC_INT);
mrq = host->mrq;
cmd = host->cmd;
data = host->data;
spin_unlock_irqrestore(&host->lock, flags);
if ((events & event_mask) & MSDC_INT_SDIOIRQ)
sdio_signal_irq(host->mmc);
if ((events & event_mask) & MSDC_INT_CDSC) {
if (host->internal_cd)
mmc_detect_change(host->mmc, msecs_to_jiffies(20));
events &= ~MSDC_INT_CDSC;
}
if (!(events & (event_mask & ~MSDC_INT_SDIOIRQ)))
break;
if (!mrq) {
dev_err(host->dev,
“%s: MRQ=NULL; events=%08X; event_mask=%08X\n”,
__func__, events, event_mask);
WARN_ON(1);
break;
}
dev_dbg(host->dev, “%s: events=%08X\n”, __func__, events);
if (cmd)
msdc_cmd_done(host, events, mrq, cmd);
else if (data)
msdc_data_xfer_done(host, events, mrq, data);
}
return IRQ_HANDLED;
驱动分析(主机端和SDIO设备)
天线接收802.11帧的数据,主机接收802.3帧,固件负责转换
天线收到数据,firmware处理放在buffer中,产生中断,主机收到中断去读取buffer数据
1)注册&&匹配
/include/linux/mmc/stdio_func.h
/*
* SDIO function device driver
*/
struct sdio_driver {
char *name;
const struct sdio_device_id *id_table;
int (*probe)(struct sdio_func *, const struct sdio_device_id *);
void (*remove)(struct sdio_func *);
struct device_driver drv;
};
具体函数填充(不同WiFi对应的wireless文件夹下的接口文件夹不同)
static struct sdio_driver if_sdio_driver = {
.name = “libertas_sdio”,
.id_table = if_sdio_ids,
.probe = if_sdio_probe,
.remove = if_sdio_remove,
.drv = {
.pm = &if_sdio_pm_ops,
},
};
设备注册函数 /driver/mmc/core/sdio_bus.c
/**
* sdio_register_driver – register a function driver
* @drv: SDIO function driver
*/
int sdio_register_driver(struct sdio_driver *drv)
{
drv->drv.name = drv->name;
drv->drv.bus = &sdio_bus_type;
return driver_register(&drv->drv);
}
EXPORT_SYMBOL_GPL(sdio_register_driver);
总线函数 /include/linux/device.h
/**
* struct bus_type – The bus type of the device
*
* @name: The name of the bus.
* @dev_name: Used for subsystems to enumerate devices like (“foo%u”, dev->id).
* @dev_root: Default device to use as the parent.
* @bus_groups: Default attributes of the bus.
* @dev_groups: Default attributes of the devices on the bus.
* @drv_groups: Default attributes of the device drivers on the bus.
* @match: Called, perhaps multiple times, whenever a new device or driver
* is added for this bus. It should return a positive value if the
* given device can be handled by the given driver and zero
* otherwise. It may also return error code if determining that
* the driver supports the device is not possible. In case of
* -EPROBE_DEFER it will queue the device for deferred probing.
* @uevent: Called when a device is added, removed, or a few other things
* that generate uevents to add the environment variables.
* @probe: Called when a new device or driver add to this bus, and callback
* the specific driver’s probe to initial the matched device.
* @remove: Called when a device removed from this bus.
* @shutdown: Called at shut-down time to quiesce the device.
*
* @online: Called to put the device back online (after offlining it).
* @offline: Called to put the device offline for hot-removal. May fail.
*
* @suspend: Called when a device on this bus wants to go to sleep mode.
* @resume: Called to bring a device on this bus out of sleep mode.
* @num_vf: Called to find out how many virtual functions a device on this
* bus supports.
* @dma_configure: Called to setup DMA configuration on a device on
* this bus.
* @pm: Power management operations of this bus, callback the specific
* device driver’s pm-ops.
* @iommu_ops: IOMMU specific operations for this bus, used to attach IOMMU
* driver implementations to a bus and allow the driver to do
* bus-specific setup
* @p: The private data of the driver core, only the driver core can
* touch this.
* @lock_key: Lock class key for use by the lock validator
* @need_parent_lock: When probing or removing a device on this bus, the
* device core should lock the device’s parent.
*
* A bus is a channel between the processor and one or more devices. For the
* purposes of the device model, all devices are connected via a bus, even if
* it is an internal, virtual, “platform” bus. Buses can plug into each other.
* A USB controller is usually a PCI device, for example. The device model
* represents the actual connections between buses and the devices they control.
* A bus is represented by the bus_type structure. It contains the name, the
* default attributes, the bus’ methods, PM operations, and the driver core’s
* private data.
*/
struct bus_type {
const char *name;
const char *dev_name;
struct device *dev_root;
const struct attribute_group **bus_groups;
const struct attribute_group **dev_groups;
const struct attribute_group **drv_groups;
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);
int (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
int (*num_vf)(struct device *dev);
int (*dma_configure)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p;
struct lock_class_key lock_key;
bool need_parent_lock;
};
//填充函数
/drivers/mmc/core/sdio_bus.c
static struct bus_type sdio_bus_type = {
.name = “sdio”,
.dev_groups = sdio_dev_groups,
.match = sdio_bus_match,
.uevent = sdio_bus_uevent,
.probe = sdio_bus_probe,
.remove = sdio_bus_remove,
.pm = &sdio_bus_pm_ops,
};
static int sdio_bus_match(struct device *dev, struct device_driver *drv)
{
struct sdio_func *func = dev_to_sdio_func(dev);
struct sdio_driver *sdrv = to_sdio_driver(drv);
if (sdio_match_device(func, sdrv))
return 1;
return 0;
}
static const struct sdio_device_id *sdio_match_device(struct sdio_func *func,
struct sdio_driver *sdrv)
{
const struct sdio_device_id *ids;
ids = sdrv->id_table;
if (ids) {
while (ids->class || ids->vendor || ids->device) {
if (sdio_match_one(func, ids))
return ids;
ids++;
}
}
return NULL;
}
2)if_sdio_probe函数 /driver/mmc/core/sdio_bus.c
static int sdio_bus_probe(struct device *dev)
{
struct sdio_driver *drv = to_sdio_driver(dev->driver);
struct sdio_func *func = dev_to_sdio_func(dev);
const struct sdio_device_id *id;
int ret;
id = sdio_match_device(func, drv);
if (!id)
return -ENODEV;
ret = dev_pm_domain_attach(dev, false);
if (ret)
return ret;
/* Unbound SDIO functions are always suspended.
* During probe, the function is set active and the usage count
* is incremented. If the driver supports runtime PM,
* it should call pm_runtime_put_noidle() in its probe routine and
}
/* Set the default block size so the driver is sure it’s something
* sensible. */
sdio_claim_host(func);
ret = sdio_set_block_size(func, 0);
sdio_release_host(func);
if (ret)
goto disable_runtimepm;
ret = drv->probe(func, id);
if (ret)
goto disable_runtimepm;
return 0;
disable_runtimepm:
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_noidle(dev);
dev_pm_domain_detach(dev, false);
return ret;
}
chunk = sdio_align_size(card->func, size);
ret = sdio_readsb(card->func, card->buffer, card->ioport, chunk);
if (ret)
goto out;
switch (type) {
case MVMS_CMD:
ret = if_sdio_handle_cmd(card, card->buffer + 4, chunk – 4);
if (ret)
goto out;
break;
case MVMS_DAT:
ret = if_sdio_handle_data(card, card->buffer + 4, chunk – 4);
if (ret)
goto out;
break;
case MVMS_EVENT:
ret = if_sdio_handle_event(card, card->buffer + 4, chunk – 4);
if (ret)
goto out;
break;
default:
lbs_deb_sdio(“invalid type (%d) from firmware\n”,
(int)type);
ret = -EINVAL;
goto out;
}
3)通过中断方式接收数据
int sdio_readsb(struct sdio_func *func, void *dst, unsigned int addr,
int count)
{
return sdio_io_rw_ext_helper(func, 0, addr, 0, dst, count);
}
EXPORT_SYMBOL_GPL(sdio_readsb);
4)数据发送
5)移除SDIO卡
static int sdio_bus_remove(struct device *dev)
{
struct sdio_driver *drv = to_sdio_driver(dev->driver);
struct sdio_func *func = dev_to_sdio_func(dev);
/* Make sure card is powered before invoking ->remove() */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_get_sync(dev);
drv->remove(func);
if (func->irq_handler) {
pr_warn(“WARNING: driver %s did not remove its interrupt handler!\n”,
drv->name);
sdio_claim_host(func);
sdio_release_irq(func);
sdio_release_host(func);
}
/* First, undo the increment made directly above */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_noidle(dev);
/* Then undo the runtime PM settings in sdio_bus_probe() */
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_sync(dev);
dev_pm_domain_detach(dev, false);
return 0;
} * pm_runtime_get_noresume() in its remove routine.
}
/* Set the default block size so the driver is sure it’s something
* sensible. */
sdio_claim_host(func);
ret = sdio_set_block_size(func, 0);
sdio_release_host(func);
if (ret)
goto disable_runtimepm;
ret = drv->probe(func, id);
if (ret)
goto disable_runtimepm;
return 0;
disable_runtimepm:
if (func->card->host->caps & MMC_CAP_POWER_OFF_CARD)
pm_runtime_put_noidle(dev);
dev_pm_domain_detach(dev, false);
return ret;
}
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/186284.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...