diff mbox series

[27/38] ra6w: add sdio.c

Message ID 20250417135236.52410-28-oleksandr.savchenko.dn@bp.renesas.com
State New
Headers show
Series wireless: ra6w driver for Renesas IEEE 802.11ax devices | expand

Commit Message

Alexander Savchenko April 17, 2025, 1:52 p.m. UTC
Part of the split. Please, take a look at the cover letter for more details

Reviewed-by: Viktor Barna <viktor.barna.rj@bp.renesas.com>
Reviewed-by: Gal Gur <gal.gur.jx@renesas.com>
Signed-off-by: Alexander Savchenko <oleksandr.savchenko.dn@bp.renesas.com>
---
 drivers/net/wireless/renesas/ra6w/sdio.c | 505 +++++++++++++++++++++++
 1 file changed, 505 insertions(+)
 create mode 100644 drivers/net/wireless/renesas/ra6w/sdio.c
diff mbox series

Patch

diff --git a/drivers/net/wireless/renesas/ra6w/sdio.c b/drivers/net/wireless/renesas/ra6w/sdio.c
new file mode 100644
index 000000000000..b2f31a4019f2
--- /dev/null
+++ b/drivers/net/wireless/renesas/ra6w/sdio.c
@@ -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");