new file mode 100644
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This file contains SDIO operations.
+ *
+ * Copyright (C) [2022-2025] Renesas Electronics Corporation and/or its affiliates.
+ */
+
+#include <linux/module.h>
+#include <linux/mmc/sdio.h>
+#include <linux/mmc/host.h>
+#include <linux/firmware.h>
+#include <linux/delay.h>
+
+#include "core.h"
+#include "if.h"
+#include "cfg80211.h"
+#include "params.h"
+#include "dbg.h"
+
+void ra6w_sdio_reprobe(struct sdio_func *func)
+{
+ func->card->host->rescan_disable = 0;
+
+ mmc_detect_change(func->card->host, msecs_to_jiffies(100));
+}
+
+static void ra6w_sdio_irq_handler(struct sdio_func *func)
+{
+ struct ra6w_if *ifp = sdio_get_drvdata(func);
+ struct ra6w_core *core = &ifp->core;
+ int ret = 0;
+ u8 reg = 0;
+
+ reg = sdio_readb(func, SDIO_CCCR_CAPS, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_CAP read failed: %d\n", ret);
+ return;
+ }
+
+ sdio_writeb(func, reg, SDIO_CCCR_CAPS, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_CAPS write failed: %d\n", ret);
+ return;
+ }
+
+ ra6w_q_event_set(&core->event, BIT(RA6W_CORE_EVENT_DATA));
+}
+
+static int ra6w_sdio_enable_irq(struct sdio_func *func)
+{
+ int ret = 0;
+ u8 reg = 0;
+
+ sdio_claim_host(func);
+
+ reg = sdio_f0_readb(func, SDIO_CCCR_IENx, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_IENx read failed: %d\n", ret);
+ goto release_host;
+ }
+
+ func->num = 0;
+ reg |= BIT(func->num);
+ sdio_writeb(func, reg, SDIO_CCCR_IENx, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_IENx write failed: reg=0x%x ret=%d\n", reg, ret);
+ goto release_host;
+ }
+
+ sdio_writeb(func, SDIO_CCCR_CAP_E4MI, SDIO_CCCR_CAPS, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_CAPS write failed: SDIO_CCCR_CAP_E4MI ret=%d\n", ret);
+ goto release_host;
+ }
+
+ func->num = 1;
+ sdio_writeb(func, SDIO_CCCR_CAP_SRW, SDIO_CCCR_CIS, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_CIS write failed: SDIO_CCCR_CAP_SRW ret=%d\n", ret);
+ goto release_host;
+ }
+
+ ret = sdio_claim_irq(func, ra6w_sdio_irq_handler);
+ if (ret)
+ ra6w_err("sdio_claim_irq failed: %d\n", ret);
+
+release_host:
+ sdio_release_host(func);
+
+ return ret;
+}
+
+static void ra6w_sdio_disable_irq(struct sdio_func *func)
+{
+ sdio_claim_host(func);
+ sdio_release_irq(func);
+ sdio_release_host(func);
+}
+
+static int ra6w_sdio_init(struct sdio_func *func)
+{
+ int ret = 0;
+ u8 reg = 0;
+
+ sdio_claim_host(func);
+
+ ret = sdio_enable_func(func);
+ if (ret) {
+ ra6w_err("sdio func enable failed: %d\n", ret);
+ goto release_host;
+ }
+
+ ret = sdio_set_block_size(func, RA6W_SDIO_BLOCK_SIZE);
+ if (ret) {
+ ra6w_err("sdio func set block size failed: %d\n", ret);
+ goto release_host;
+ }
+
+ reg = sdio_f0_readb(func, SDIO_CCCR_CAPS, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_CAPS read failed: %d\n", ret);
+ goto release_host;
+ }
+
+ reg = sdio_f0_readb(func, SDIO_CCCR_IF, &ret);
+ if (ret) {
+ ra6w_err("SDIO_CCCR_IF read failed: %d\n", ret);
+ goto release_host;
+ }
+
+ reg = SDIO_BUS_WIDTH_4BIT | SDIO_BUS_CD_DISABLE;
+ func->num = 0;
+ sdio_writeb(func, reg, SDIO_CCCR_IF, &ret);
+
+release_host:
+ sdio_release_host(func);
+
+ return ret;
+}
+
+static int ra6w_sdio_read(struct sdio_func *func, void *data, int len)
+{
+ int ret;
+ int chunk;
+
+ sdio_claim_host(func);
+
+ chunk = sdio_align_size(func, len);
+ ret = sdio_readsb(func, data, RA6W_SDIO_ADDR_READ, chunk);
+
+ sdio_release_host(func);
+
+ return ret;
+}
+
+static int ra6w_sdio_write(struct sdio_func *func, void *data, int len)
+{
+ int ret;
+ int chunk;
+
+ sdio_claim_host(func);
+
+ chunk = sdio_align_size(func, len);
+ ret = sdio_writesb(func, RA6W_SDIO_ADDR_WRITE, data, chunk);
+
+ sdio_release_host(func);
+
+ return ret;
+}
+
+static int ra6w_sdio_write_crc32(struct sdio_func *func, const struct firmware *fw)
+{
+ u32 img_crc;
+ u32 msg_data;
+ int i;
+ int ret = 0;
+
+ /* send CRC */
+ img_crc = crc32_le(~0U, fw->data, fw->size);
+ img_crc ^= ~0U;
+ ra6w_dbg("wifi FW CRC is %08x\n", img_crc);
+
+ sdio_writel(func, img_crc, RA6W_SDIO_HOST_GP_REG, &ret);
+ if (ret) {
+ ra6w_err("CRC32 write failed: %d\n", ret);
+ return ret;
+ }
+
+ /* Check CRC */
+ for (i = 0; i < RA6W_SDIO_CRC_CHECK_RETRY_MAX; i++) {
+ msg_data = sdio_readl(func, RA6W_SDIO_CHIP_GP_REG, &ret);
+ if (ret)
+ return ret;
+
+ if (msg_data == RA6W_SDIO_CODE_CRC_CHECKING) {
+ usleep_range(RA6W_SDIO_CRC_CHECK_SLEEP_MIN_US,
+ RA6W_SDIO_CRC_CHECK_SLEEP_MAX_US);
+ continue;
+ }
+
+ if (msg_data == RA6W_SDIO_CODE_SUCCESS)
+ return 0;
+
+ if (msg_data == RA6W_SDIO_CODE_BOOT_NG) {
+ ra6w_info("CRC32 write failed: 0x%x\n", RA6W_SDIO_CODE_BOOT_NG);
+ return -ENOENT;
+ }
+ }
+
+ return 0;
+}
+
+static int ra6w_sdio_fw_ack(struct sdio_func *func)
+{
+ u32 msg_data;
+ int i;
+ int ret;
+
+ for (i = 0; i < RA6W_SDIO_ACK_CNT_MAX; i++) {
+ msg_data = sdio_readl(func, RA6W_SDIO_CHIP_GP_REG, &ret);
+ if (ret)
+ return ret;
+
+ if (msg_data == RA6W_SDIO_CODE_SUCCESS)
+ break;
+ }
+
+ return 0;
+}
+
+static int ra6w_sdio_fw_wait_sync(struct sdio_func *func)
+{
+ u16 blk_size;
+ u8 reg;
+ int i;
+ int ret;
+
+ for (i = 0; i < RA6W_SDIO_WAIT_SYNC_CNT_MAX; i++) {
+ reg = sdio_f0_readb(func, 0x110, &ret);
+ if (ret)
+ return ret;
+
+ blk_size = reg;
+ reg = sdio_f0_readb(func, 0x111, &ret);
+ if (ret)
+ return ret;
+
+ blk_size |= reg << 8;
+ msleep(50);
+ if (blk_size == RA6W_SDIO_BLOCK_SIZE)
+ break;
+ }
+
+ return 0;
+}
+
+static int _ra6w_sdio_fw_upload(struct sdio_func *func, const struct firmware *fw)
+{
+ u32 iteration;
+ u32 remain_len;
+ u8 *fw_buf = NULL;
+ int i;
+ int ret = 0;
+
+ fw_buf = devm_kmalloc(&func->dev, RA6W_SDIO_BLOCK_SIZE, GFP_KERNEL);
+ if (!fw_buf)
+ return -ENOMEM;
+
+ func->num = 1;
+ sdio_claim_host(func);
+
+ sdio_writel(func, fw->size, RA6W_SDIO_HOST_GP_REG, &ret);
+ if (ret) {
+ ra6w_err("Write fw size failed: %d\n", ret);
+ goto release_host;
+ }
+
+ ret = ra6w_sdio_fw_ack(func);
+ if (ret)
+ goto release_host;
+
+ iteration = RA6W_SDIO_GET_CNT(fw->size, RA6W_SDIO_BLOCK_SIZE);
+ for (i = 0; i < iteration; i++) {
+ memcpy(fw_buf, &fw->data[i * RA6W_SDIO_BLOCK_SIZE], RA6W_SDIO_BLOCK_SIZE);
+ ret = sdio_writesb(func, 0, fw_buf, RA6W_SDIO_BLOCK_SIZE);
+ if (ret)
+ goto release_host;
+ }
+
+ remain_len = RA6W_SDIO_GET_REMAIN(fw->size, RA6W_SDIO_BLOCK_SIZE);
+ memcpy(fw_buf, &fw->data[i * RA6W_SDIO_BLOCK_SIZE], remain_len);
+ ret = sdio_writesb(func, 0, fw_buf, remain_len);
+ if (ret)
+ goto release_host;
+
+ ret = ra6w_sdio_write_crc32(func, fw);
+ if (ret)
+ goto release_host;
+
+ func->num = 0;
+
+ ra6w_sdio_fw_wait_sync(func);
+
+release_host:
+ sdio_release_host(func);
+ devm_kfree(&func->dev, fw_buf);
+
+ return ret;
+}
+
+static int ra6w_sdio_fw_upload(struct sdio_func *func)
+{
+ int ret;
+ char path[100] = { 0 };
+ const struct firmware *fw = NULL;
+
+ snprintf(path, sizeof(path), "%s/%s", KBUILD_MODNAME, RA6W_SDIO_FW_NAME);
+
+ ra6w_info("F/W uploading...\n");
+
+ ret = request_firmware(&fw, path, &func->dev);
+ if (ret) {
+ ra6w_err("[%s] request_firmware %s failed: %d\n", __func__, path, ret);
+ return ret;
+ }
+
+ ret = _ra6w_sdio_fw_upload(func, fw);
+
+ ra6w_info("F/W uploading %s\n", ret ? "failed" : "done");
+
+ release_firmware(fw);
+
+ return ret;
+}
+
+static void ra6w_sdio_host_reset(struct sdio_func *func)
+{
+ int err = 0;
+
+ sdio_claim_host(func);
+ sdio_writel(func, RA6W_SDIO_ADDR_RESET, RA6W_SDIO_HOST_GP_REG, &err);
+ sdio_release_host(func);
+}
+
+static const struct ra6w_if_ops ra6w_if_sdio_ops = {
+ .read = ra6w_sdio_read,
+ .write = ra6w_sdio_write,
+};
+
+static int ra6w_sdio_probe(struct sdio_func *func, const struct sdio_device_id *id)
+{
+ int ret;
+ struct ra6w_if *ifp = NULL;
+ struct ra6w_core *core = NULL;
+
+ ifp = kzalloc(sizeof(*ifp), GFP_KERNEL);
+ if (!ifp)
+ return -ENOMEM;
+
+ ifp->dev.func = func;
+
+ ifp->ops = &ra6w_if_sdio_ops;
+
+ sdio_set_drvdata(func, ifp);
+
+ ret = ra6w_sdio_init(func);
+ if (ret)
+ goto free_ifp;
+
+ sdio_claim_host(func);
+ func->card->host->rescan_disable = 1;
+ sdio_release_host(func);
+
+ ret = ra6w_sdio_fw_upload(func);
+ if (ret)
+ goto free_ifp;
+
+ core = &ifp->core;
+
+ ret = ra6w_core_init(core);
+ if (ret)
+ goto free_ifp;
+
+ ret = ra6w_sdio_enable_irq(func);
+ if (ret)
+ goto core_deinit;
+
+ ret = ra6w_core_post_init(core);
+ if (ret)
+ goto core_deinit;
+
+ ret = ra6w_params_init(core);
+ if (ret)
+ goto core_deinit;
+
+ ret = ra6w_cfg80211_init(core, &func->dev);
+ if (ret)
+ goto core_deinit;
+
+ ret = ra6w_recovery_init(&core->recovery);
+ if (ret)
+ goto cfg80211_deinit;
+
+ return 0;
+
+cfg80211_deinit:
+ ra6w_cfg80211_deinit(core);
+
+core_deinit:
+ ra6w_core_deinit(core);
+ ra6w_sdio_host_reset(func);
+ ra6w_sdio_disable_irq(func);
+
+free_ifp:
+ kfree(ifp);
+
+ return ret;
+}
+
+static void ra6w_sdio_remove(struct sdio_func *func)
+{
+ struct ra6w_if *ifp = sdio_get_drvdata(func);
+ struct ra6w_core *core = &ifp->core;
+
+ ra6w_recovery_deinit(&core->recovery);
+
+ ra6w_cfg80211_deinit(core);
+ ra6w_sdio_disable_irq(func);
+ ra6w_core_deinit(core);
+ ra6w_sdio_host_reset(func);
+
+ sdio_claim_host(func);
+ func->card->host->rescan_disable = 0;
+ sdio_disable_func(func);
+ sdio_release_host(func);
+
+ mmc_detect_change(func->card->host, msecs_to_jiffies(100));
+
+ ifp->dev.func = NULL;
+
+ kfree(ifp);
+}
+
+static struct sdio_device_id ra6w_sdio_device_id[] = {
+ { SDIO_DEVICE(RA6W_SDIO_VENDOR_ID_RENESAS, RA6W_SDIO_DEVICE_ID_RA6W) },
+ {},
+};
+
+MODULE_DEVICE_TABLE(sdio, ra6w_sdio_device_id);
+
+#ifdef CONFIG_PM
+static int ra6w_sdio_suspend(struct device *device)
+{
+ struct sdio_func *func = dev_to_sdio_func(device);
+ struct ra6w_if *ifp = sdio_get_drvdata(func);
+ mmc_pm_flag_t flags;
+ s32 ret = 0;
+
+ ifp->dev.dev_on_resume = false;
+
+ flags = sdio_get_host_pm_caps(func);
+ if (!(flags & MMC_PM_KEEP_POWER)) {
+ dev_err(device, "%s: cannot remain alive while host is suspended\n",
+ sdio_func_id(func));
+ return -EINVAL;
+ }
+
+ ret = sdio_set_host_pm_flags(func, MMC_PM_KEEP_POWER);
+ if (ret)
+ return ret;
+
+ return sdio_set_host_pm_flags(func, MMC_PM_WAKE_SDIO_IRQ);
+}
+
+static int ra6w_sdio_resume(struct device *device)
+{
+ const struct sdio_func *func = dev_to_sdio_func(device);
+ struct ra6w_if *ifp = sdio_get_drvdata(func);
+
+ ifp->dev.dev_on_resume = true;
+
+ return 0;
+}
+#endif /* CONFIG_PM */
+
+static const struct dev_pm_ops ra6w_sdio_pm_ops = {
+ SYSTEM_SLEEP_PM_OPS(ra6w_sdio_suspend, ra6w_sdio_resume)
+};
+
+static struct sdio_driver ra6w_sdio_driver = {
+ .name = KBUILD_MODNAME,
+ .id_table = ra6w_sdio_device_id,
+ .probe = ra6w_sdio_probe,
+ .remove = ra6w_sdio_remove,
+ .drv = {
+ .owner = THIS_MODULE,
+ .pm = &ra6w_sdio_pm_ops
+ }
+};
+
+module_sdio_driver(ra6w_sdio_driver);
+
+MODULE_DESCRIPTION("Renesas 11ax driver for Linux");
+MODULE_AUTHOR("Alexander Savchenko <oleksandr.savchenko.dn@bp.renesas.com>");
+MODULE_LICENSE("GPL");