* [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver
@ 2025-06-18 8:07 Binbin Zhou
2025-06-18 8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Binbin Zhou @ 2025-06-18 8:07 UTC (permalink / raw)
To: Binbin Zhou, Huacai Chen, Ulf Hansson
Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang,
Binbin Zhou
Hi all:
This patchset introduce the MMC host controller on Loongson-2K series
CPUs.
They are similar, except for the interface characteristics and the use of
DMA engine, specifically, the Loongson-2K0500/Loongson-2K1000 use an
externally shared APBDMA engine, while the Loongson-2K2000 uses an
internally exclusive DMA.
Based on this, I'm splitting the driver into two patches.
List of the patchset:
Patch1: bindings for Loongson-2K0500/Loongson-2K1000;
Patch2: driver for MMC controller using externally shared APBDMA engine;
Patch3: bindings for Loongson-2K2000;
Patch4: driver for MMC controller using internally exclusive DMA.
Thanks.
-------
V3:
- Rebase on linux-mmc/next branch.
patch(1/4):
- Rename dt-binding file as loongson,ls2k0500-mmc.yaml.
patch(2/4):
- Fix lkp error;
https://lore.kernel.org/all/202505081845.0NQYX2nS-lkp@intel.com/
https://lore.kernel.org/all/202505130918.uanOGxju-lkp@intel.com/
- Add regulators support for ios ops;
- Add ack_sdio_irq() callback;
- Add MMC_CAP2_SDIO_IRQ_NOTHREAD flag;
patch(3/4):
- Add Ack-by tag.
patch(4/4):
- Update commit for fix_data_timeout().
Link to V2:
https://lore.kernel.org/all/cover.1746581751.git.zhoubinbin@loongson.cn/
V2:
patch(1/4):
- Add reg define for each reg entry.
patch(2/4):
- Put all code in the c-file;
- Use mmc_from_priv() instead of host->mmc;
- Use sdio_signal_irq() instead of mmc_signal_sdio_irq();
- Use devm_mmc_alloc_host() instead of mmc_alloc_host();
- Use mmc_regulator_get_supply();
patch(4/4):
- Add fix_cmd_interrupt function which is needed by Loongson-2K2000.
Link to V1:
https://lore.kernel.org/linux-mmc/cover.1744273956.git.zhoubinbin@loongson.cn/
Binbin Zhou (4):
dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding
mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for
Loongson-2K2000
mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver
.../bindings/mmc/loongson,ls2k0500-mmc.yaml | 112 ++
MAINTAINERS | 7 +
drivers/mmc/host/Kconfig | 13 +
drivers/mmc/host/Makefile | 1 +
drivers/mmc/host/loongson2-mmc.c | 1032 +++++++++++++++++
5 files changed, 1165 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml
create mode 100644 drivers/mmc/host/loongson2-mmc.c
base-commit: 187715cfd12932a528ff3a3952648e2b55381d4c
--
2.47.1
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding 2025-06-18 8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou @ 2025-06-18 8:07 ` Binbin Zhou 2025-06-18 13:44 ` Krzysztof Kozlowski 2025-06-18 8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou ` (2 subsequent siblings) 3 siblings, 1 reply; 11+ messages in thread From: Binbin Zhou @ 2025-06-18 8:07 UTC (permalink / raw) To: Binbin Zhou, Huacai Chen, Ulf Hansson Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang, Binbin Zhou Add the Loongson-2K SoC's SD/SDIO/eMMC controller binding with DT schema format using json-schema. Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> --- .../bindings/mmc/loongson,ls2k0500-mmc.yaml | 69 +++++++++++++++++++ MAINTAINERS | 6 ++ 2 files changed, 75 insertions(+) create mode 100644 Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml new file mode 100644 index 000000000000..f2f10b2c618c --- /dev/null +++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/mmc/loongson,ls2k0500-mmc.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: The SD/SDIO/eMMC host controller for Loongson-2K family SoCs + +description: + The MMC host controller on the Loongson-2K0500/2K1000 (using an externally + shared apbdma controller) provides the SD and SDIO device interfaces. + +maintainers: + - Binbin Zhou <zhoubinbin@loongson.cn> + +allOf: + - $ref: mmc-controller.yaml# + +properties: + compatible: + enum: + - loongson,ls2k0500-mmc + - loongson,ls2k1000-mmc + + reg: + items: + - description: Loongson-2K MMC controller registers. + - description: APB DMA config register for Loongson-2K MMC controller. + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + + dmas: + maxItems: 1 + + dma-names: + const: rx-tx + +required: + - compatible + - reg + - interrupts + - clocks + - dmas + - dma-names + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/clock/loongson,ls2k-clk.h> + + mmc@1fe2c000 { + compatible = "loongson,ls2k1000-mmc"; + reg = <0x1fe2c000 0x68>, + <0x1fe00438 0x8>; + interrupt-parent = <&liointc0>; + interrupts = <31 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk LOONGSON2_APB_CLK>; + dmas = <&apbdma1 0>; + dma-names = "rx-tx"; + bus-width = <4>; + cd-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>; + }; diff --git a/MAINTAINERS b/MAINTAINERS index a92290fffa16..2d1cdd2cfc2b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14175,6 +14175,12 @@ S: Maintained F: Documentation/devicetree/bindings/hwinfo/loongson,ls2k-chipid.yaml F: drivers/soc/loongson/loongson2_guts.c +LOONGSON-2 SOC SERIES MMC/SD/SDIO CONTROLLER DRIVER +M: Binbin Zhou <zhoubinbin@loongson.cn> +L: linux-mmc@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml + LOONGSON-2 SOC SERIES PM DRIVER M: Yinbo Zhu <zhuyinbo@loongson.cn> L: linux-pm@vger.kernel.org -- 2.47.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding 2025-06-18 8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou @ 2025-06-18 13:44 ` Krzysztof Kozlowski 0 siblings, 0 replies; 11+ messages in thread From: Krzysztof Kozlowski @ 2025-06-18 13:44 UTC (permalink / raw) To: Binbin Zhou, Binbin Zhou, Huacai Chen, Ulf Hansson Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang On 18/06/2025 10:07, Binbin Zhou wrote: > Add the Loongson-2K SoC's SD/SDIO/eMMC controller binding with DT schema > format using json-schema. > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> > --- > .../bindings/mmc/loongson,ls2k0500-mmc.yaml | 69 +++++++++++++++++++ > MAINTAINERS | 6 ++ <form letter> Please use scripts/get_maintainers.pl to get a list of necessary people and lists to CC. It might happen, that command when run on an older kernel, gives you outdated entries. Therefore please be sure you base your patches on recent Linux kernel. Tools like b4 or scripts/get_maintainer.pl provide you proper list of people, so fix your workflow. Tools might also fail if you work on some ancient tree (don't, instead use mainline) or work on fork of kernel (don't, instead use mainline). Just use b4 and everything should be fine, although remember about `b4 prep --auto-to-cc` if you added new patches to the patchset. You missed at least devicetree list (maybe more), so this won't be tested by automated tooling. Performing review on untested code might be a waste of time. Please kindly resend and include all necessary To/Cc entries. </form letter> Best regards, Krzysztof ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver 2025-06-18 8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou 2025-06-18 8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou @ 2025-06-18 8:07 ` Binbin Zhou 2025-06-19 4:02 ` Huacai Chen 2025-06-20 13:20 ` kernel test robot 2025-06-18 8:07 ` [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000 Binbin Zhou 2025-06-18 8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou 3 siblings, 2 replies; 11+ messages in thread From: Binbin Zhou @ 2025-06-18 8:07 UTC (permalink / raw) To: Binbin Zhou, Huacai Chen, Ulf Hansson Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang, Binbin Zhou The MMC controllers on the Loongson-2K series CPUs are similar, except for the interface characteristics and the use of DMA controllers. This patch describes the MMC controllers on the Loongson-2K0500/2K1000, with the distinguishing feature being the use of an externally shared APBDMA engine. Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> --- MAINTAINERS | 1 + drivers/mmc/host/Kconfig | 13 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/loongson2-mmc.c | 820 +++++++++++++++++++++++++++++++ 4 files changed, 835 insertions(+) create mode 100644 drivers/mmc/host/loongson2-mmc.c diff --git a/MAINTAINERS b/MAINTAINERS index 2d1cdd2cfc2b..4a4892613c66 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14180,6 +14180,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn> L: linux-mmc@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml +F: drivers/mmc/host/loongson2-mmc.c LOONGSON-2 SOC SERIES PM DRIVER M: Yinbo Zhu <zhuyinbo@loongson.cn> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index c3f0f41a426d..7232de1c0688 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -1111,6 +1111,19 @@ config MMC_OWL This selects support for the SD/MMC Host Controller on Actions Semi Owl SoCs. +config MMC_LOONGSON2 + tristate "Loongson-2K SD/SDIO/eMMC Host Interface support" + depends on LOONGARCH || COMPILE_TEST + depends on HAS_DMA + help + This selects support for the SD/SDIO/eMMC Host Controller on + Loongson-2K series CPUs. + + To compile this driver as a module, choose M here: the + module will be called mmc_loongson2. + + If unsure, say N. + config MMC_SDHCI_EXTERNAL_DMA bool diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index 75bafc7b162b..5057fea8afb6 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -72,6 +72,7 @@ obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o obj-$(CONFIG_MMC_BCM2835) += bcm2835.o obj-$(CONFIG_MMC_OWL) += owl-mmc.o +obj-$(CONFIG_MMC_LOONGSON2) += loongson2-mmc.o obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c new file mode 100644 index 000000000000..872f5dc21b21 --- /dev/null +++ b/drivers/mmc/host/loongson2-mmc.c @@ -0,0 +1,820 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Loongson-2K MMC/SDIO controller driver + * + * Copyright (C) 2018-2025 Loongson Technology Corporation Limited. + * + */ + +#include <linux/bitfield.h> +#include <linux/bitrev.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/mmc/core.h> +#include <linux/mmc/host.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/slot-gpio.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define LOONGSON2_MMC_REG_CTL 0x00 /* Control Register */ +#define LOONGSON2_MMC_REG_PRE 0x04 /* Prescaler Register */ +#define LOONGSON2_MMC_REG_CARG 0x08 /* Command Register */ +#define LOONGSON2_MMC_REG_CCTL 0x0c /* Command Control Register */ +#define LOONGSON2_MMC_REG_CSTS 0x10 /* Command Status Register */ +#define LOONGSON2_MMC_REG_RSP0 0x14 /* Command Response Register 0 */ +#define LOONGSON2_MMC_REG_RSP1 0x18 /* Command Response Register 1 */ +#define LOONGSON2_MMC_REG_RSP2 0x1c /* Command Response Register 2 */ +#define LOONGSON2_MMC_REG_RSP3 0x20 /* Command Response Register 3 */ +#define LOONGSON2_MMC_REG_TIMER 0x24 /* Data Timeout Register */ +#define LOONGSON2_MMC_REG_BSIZE 0x28 /* Block Size Register */ +#define LOONGSON2_MMC_REG_DCTL 0x2c /* Data Control Register */ +#define LOONGSON2_MMC_REG_DCNT 0x30 /* Data Counter Register */ +#define LOONGSON2_MMC_REG_DSTS 0x34 /* Data Status Register */ +#define LOONGSON2_MMC_REG_FSTS 0x38 /* FIFO Status Register */ +#define LOONGSON2_MMC_REG_INT 0x3c /* Interrupt Register */ +#define LOONGSON2_MMC_REG_DATA 0x40 /* Data Register */ +#define LOONGSON2_MMC_REG_IEN 0x64 /* Interrupt Enable Register */ + +/* Bitfields of control register */ +#define LOONGSON2_MMC_CTL_ENCLK BIT(0) +#define LOONGSON2_MMC_CTL_EXTCLK BIT(1) +#define LOONGSON2_MMC_CTL_RESET BIT(8) + +/* Bitfields of prescaler register */ +#define LOONGSON2_MMC_PRE GENMASK(9, 0) +#define LOONGSON2_MMC_PRE_EN BIT(31) + +/* Bitfields of command control register */ +#define LOONGSON2_MMC_CCTL_INDEX GENMASK(5, 0) +#define LOONGSON2_MMC_CCTL_HOST BIT(6) +#define LOONGSON2_MMC_CCTL_START BIT(8) +#define LOONGSON2_MMC_CCTL_WAIT_RSP BIT(9) +#define LOONGSON2_MMC_CCTL_LONG_RSP BIT(10) +#define LOONGSON2_MMC_CCTL_ABORT BIT(12) +#define LOONGSON2_MMC_CCTL_CHECK BIT(13) +#define LOONGSON2_MMC_CCTL_SDIO BIT(14) +#define LOONGSON2_MMC_CCTL_CMD6 BIT(18) + +/* Bitfields of command status register */ +#define LOONGSON2_MMC_CSTS_INDEX GENMASK(7, 0) +#define LOONGSON2_MMC_CSTS_ON BIT(8) +#define LOONGSON2_MMC_CSTS_RSP BIT(9) +#define LOONGSON2_MMC_CSTS_TIMEOUT BIT(10) +#define LOONGSON2_MMC_CSTS_END BIT(11) +#define LOONGSON2_MMC_CSTS_CRC_ERR BIT(12) +#define LOONGSON2_MMC_CSTS_AUTO_STOP BIT(13) +#define LOONGSON2_MMC_CSTS_FIN BIT(14) + +/* Bitfields of data timeout register */ +#define LOONGSON2_MMC_DTIMR GENMASK(23, 0) + +/* Bitfields of block size register */ +#define LOONGSON2_MMC_BSIZE GENMASK(11, 0) + +/* Bitfields of data control register */ +#define LOONGSON2_MMC_DCTL_BNUM GENMASK(11, 0) +#define LOONGSON2_MMC_DCTL_START BIT(14) +#define LOONGSON2_MMC_DCTL_ENDMA BIT(15) +#define LOONGSON2_MMC_DCTL_WIDE BIT(16) +#define LOONGSON2_MMC_DCTL_RWAIT BIT(17) +#define LOONGSON2_MMC_DCTL_IO_SUSPEND BIT(18) +#define LOONGSON2_MMC_DCTL_IO_RESUME BIT(19) +#define LOONGSON2_MMC_DCTL_RW_RESUME BIT(20) +#define LOONGSON2_MMC_DCTL_8BIT_BUS BIT(26) + +/* Bitfields of sata counter register */ +#define LOONGSON2_MMC_DCNT_BNUM GENMASK(11, 0) +#define LOONGSON2_MMC_DCNT_BYTE GENMASK(23, 12) + +/* Bitfields of command status register */ +#define LOONGSON2_MMC_DSTS_RXON BIT(0) +#define LOONGSON2_MMC_DSTS_TXON BIT(1) +#define LOONGSON2_MMC_DSTS_SBITERR BIT(2) +#define LOONGSON2_MMC_DSTS_BUSYFIN BIT(3) +#define LOONGSON2_MMC_DSTS_XFERFIN BIT(4) +#define LOONGSON2_MMC_DSTS_DTIMEOUT BIT(5) +#define LOONGSON2_MMC_DSTS_RXCRC BIT(6) +#define LOONGSON2_MMC_DSTS_TXCRC BIT(7) +#define LOONGSON2_MMC_DSTS_IRQ BIT(8) +#define LOONGSON2_MMC_DSTS_START BIT(13) +#define LOONGSON2_MMC_DSTS_RESUME BIT(15) +#define LOONGSON2_MMC_DSTS_SUSPEND BIT(16) + +/* Bitfields of interrupt register */ +#define LOONGSON2_MMC_INT_DFIN BIT(0) +#define LOONGSON2_MMC_INT_DTIMEOUT BIT(1) +#define LOONGSON2_MMC_INT_RXCRC BIT(2) +#define LOONGSON2_MMC_INT_TXCRC BIT(3) +#define LOONGSON2_MMC_INT_PROGERR BIT(4) +#define LOONGSON2_MMC_INT_SDIOIRQ BIT(5) +#define LOONGSON2_MMC_INT_CSENT BIT(6) +#define LOONGSON2_MMC_INT_CTIMEOUT BIT(7) +#define LOONGSON2_MMC_INT_RESPCRC BIT(8) +#define LOONGSON2_MMC_INT_BUSYEND BIT(9) + +/* Bitfields of interrupt enable register */ +#define LOONGSON2_MMC_IEN_DFIN BIT(0) +#define LOONGSON2_MMC_IEN_DTIMEOUT BIT(1) +#define LOONGSON2_MMC_IEN_RXCRC BIT(2) +#define LOONGSON2_MMC_IEN_TXCRC BIT(3) +#define LOONGSON2_MMC_IEN_PROGERR BIT(4) +#define LOONGSON2_MMC_IEN_SDIOIRQ BIT(5) +#define LOONGSON2_MMC_IEN_CSENT BIT(6) +#define LOONGSON2_MMC_IEN_CTIMEOUT BIT(7) +#define LOONGSON2_MMC_IEN_RESPCRC BIT(8) +#define LOONGSON2_MMC_IEN_BUSYEND BIT(9) + +#define LOONGSON2_MMC_IEN_ALL GENMASK(9, 0) +#define LOONGSON2_MMC_INT_CLEAR GENMASK(9, 0) + +/* Loongson-2K1000 SDIO2 DMA routing register */ +#define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) +#define LS2K1000_DMA0_CONF 0x0 +#define LS2K1000_DMA1_CONF 0x1 +#define LS2K1000_DMA2_CONF 0x2 +#define LS2K1000_DMA3_CONF 0x3 +#define LS2K1000_DMA4_CONF 0x4 + +/* Loongson-2K0500 SDIO2 DMA routing register */ +#define LS2K0500_SDIO_DMA_MASK GENMASK(15, 14) +#define LS2K0500_DMA0_CONF 0x1 +#define LS2K0500_DMA1_CONF 0x2 +#define LS2K0500_DMA2_CONF 0x3 + +enum loongson2_mmc_state { + STATE_NONE, + STATE_FINALIZE, + STATE_CMDSENT, + STATE_RSPFIN, + STATE_XFERFINISH, + STATE_XFERFINISH_RSPFIN, +}; + +struct loongson2_dma_desc { + u32 ndesc_addr; + u32 mem_addr; + u32 apb_addr; + u32 len; + u32 step_len; + u32 step_times; + u32 cmd; + u32 stats; + u32 high_ndesc_addr; + u32 high_mem_addr; + u32 reserved[2]; +} __packed; + +struct loongson2_mmc_host { + struct device *dev; + struct mmc_request *mrq; + struct regmap *regmap; + struct resource *res; + struct clk *clk; + u64 rate; + int dma_complete; + struct dma_chan *chan; + int cmd_is_stop; + int bus_width; + spinlock_t lock; /* Prevent races with irq handler */ + enum loongson2_mmc_state state; + const struct loongson2_mmc_pdata *pdata; +}; + +struct loongson2_mmc_pdata { + const struct regmap_config *regmap_config; + void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd); + int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev); + int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data); + void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev); +}; + +static void loongson2_mmc_send_command(struct loongson2_mmc_host *host, + struct mmc_command *cmd) +{ + u32 cctrl; + + if (cmd->data) + host->state = STATE_XFERFINISH_RSPFIN; + else if (cmd->flags & MMC_RSP_PRESENT) + host->state = STATE_RSPFIN; + else + host->state = STATE_CMDSENT; + + regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, cmd->arg); + + cctrl = FIELD_PREP(LOONGSON2_MMC_CCTL_INDEX, cmd->opcode); + cctrl |= LOONGSON2_MMC_CCTL_HOST | LOONGSON2_MMC_CCTL_START; + + if (cmd->opcode == SD_SWITCH && cmd->data) + cctrl |= LOONGSON2_MMC_CCTL_CMD6; + + if (cmd->flags & MMC_RSP_PRESENT) + cctrl |= LOONGSON2_MMC_CCTL_WAIT_RSP; + + if (cmd->flags & MMC_RSP_136) + cctrl |= LOONGSON2_MMC_CCTL_LONG_RSP; + + regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, cctrl); +} + +static int loongson2_mmc_setup_data(struct loongson2_mmc_host *host, + struct mmc_data *data) +{ + u32 dctrl; + + if ((data->blksz & 3) != 0) + return -EINVAL; + + dctrl = FIELD_PREP(LOONGSON2_MMC_DCTL_BNUM, data->blocks); + dctrl |= LOONGSON2_MMC_DCTL_START | LOONGSON2_MMC_DCTL_ENDMA; + + if (host->bus_width == MMC_BUS_WIDTH_4) + dctrl |= LOONGSON2_MMC_DCTL_WIDE; + else if (host->bus_width == MMC_BUS_WIDTH_8) + dctrl |= LOONGSON2_MMC_DCTL_8BIT_BUS; + + regmap_write(host->regmap, LOONGSON2_MMC_REG_DCTL, dctrl); + regmap_write(host->regmap, LOONGSON2_MMC_REG_BSIZE, data->blksz); + regmap_write(host->regmap, LOONGSON2_MMC_REG_TIMER, U32_MAX); + + return 0; +} + +static int loongson2_mmc_prepare_dma(struct loongson2_mmc_host *host, + struct mmc_data *data) +{ + int ret; + + if (!data) + return 0; + + ret = loongson2_mmc_setup_data(host, data); + if (ret) + return ret; + + host->dma_complete = 0; + + return host->pdata->prepare_dma(host, data); +} + +static void loongson2_mmc_send_request(struct mmc_host *mmc) +{ + int ret; + struct loongson2_mmc_host *host = mmc_priv(mmc); + struct mmc_request *mrq = host->mrq; + struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd; + + ret = loongson2_mmc_prepare_dma(host, cmd->data); + if (ret) { + dev_err(host->dev, "DMA data prepared failed with %d\n", ret); + cmd->error = ret; + cmd->data->error = ret; + mmc_request_done(mmc, mrq); + return; + } + + loongson2_mmc_send_command(host, cmd); + + /* Fix deselect card */ + if (cmd->opcode == MMC_SELECT_CARD && cmd->arg == 0) { + cmd->error = 0; + mmc_request_done(mmc, mrq); + } +} + +static irqreturn_t loongson2_mmc_irq_worker(int irq, void *devid) +{ + struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid; + struct mmc_host *mmc = mmc_from_priv(host); + struct mmc_request *mrq = host->mrq; + struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd; + + if (cmd->data) + dma_unmap_sg(mmc_dev(mmc), cmd->data->sg, cmd->data->sg_len, + mmc_get_dma_dir(cmd->data)); + + if (cmd->data && !cmd->error && + !cmd->data->error && !host->dma_complete) + return IRQ_HANDLED; + + /* Read response from controller. */ + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP0, &cmd->resp[0]); + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP1, &cmd->resp[1]); + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP2, &cmd->resp[2]); + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP3, &cmd->resp[3]); + + /* Cleanup controller */ + regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, 0); + regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, 0); + + if (cmd->data && cmd->error) + cmd->data->error = cmd->error; + + if (cmd->data && cmd->data->stop && !host->cmd_is_stop) { + host->cmd_is_stop = 1; + loongson2_mmc_send_request(mmc); + return IRQ_HANDLED; + } + + /* If we have no data transfer we are finished here */ + if (!mrq->data) + goto request_done; + + /* Calculate the amount of bytes transfer if there was no error */ + if (mrq->data->error == 0) { + mrq->data->bytes_xfered = + (mrq->data->blocks * mrq->data->blksz); + } else { + mrq->data->bytes_xfered = 0; + } + +request_done: + host->state = STATE_NONE; + host->mrq = NULL; + mmc_request_done(mmc, mrq); + return IRQ_HANDLED; +} + +static irqreturn_t loongson2_mmc_irq(int irq, void *devid) +{ + struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid; + struct mmc_host *mmc = mmc_from_priv(host); + struct mmc_command *cmd; + unsigned long iflags; + u32 dsts, imsk; + + regmap_read(host->regmap, LOONGSON2_MMC_REG_INT, &imsk); + regmap_read(host->regmap, LOONGSON2_MMC_REG_DSTS, &dsts); + + if ((dsts & LOONGSON2_MMC_DSTS_IRQ) && + (imsk & LOONGSON2_MMC_INT_SDIOIRQ)) { + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_INT, + LOONGSON2_MMC_INT_SDIOIRQ, + LOONGSON2_MMC_INT_SDIOIRQ); + + sdio_signal_irq(mmc); + return IRQ_HANDLED; + } + + spin_lock_irqsave(&host->lock, iflags); + + if (host->state == STATE_NONE || host->state == STATE_FINALIZE || + !host->mrq) + goto irq_out; + + cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd; + if (!cmd) + goto irq_out; + + cmd->error = 0; + + if (imsk & LOONGSON2_MMC_INT_CTIMEOUT) { + cmd->error = -ETIMEDOUT; + goto close_transfer; + } + + if (imsk & LOONGSON2_MMC_INT_CSENT) { + if (host->state == STATE_RSPFIN || host->state == STATE_CMDSENT) + goto close_transfer; + + if (host->state == STATE_XFERFINISH_RSPFIN) + host->state = STATE_XFERFINISH; + } + + if (!cmd->data) + goto irq_out; + + if (imsk & (LOONGSON2_MMC_INT_RXCRC | LOONGSON2_MMC_INT_TXCRC)) { + cmd->data->error = -EILSEQ; + goto close_transfer; + } + + if (imsk & LOONGSON2_MMC_INT_DTIMEOUT) { + cmd->data->error = -ETIMEDOUT; + goto close_transfer; + } + + if (imsk & LOONGSON2_MMC_INT_DFIN) { + if (host->state == STATE_XFERFINISH) { + host->dma_complete = 1; + goto close_transfer; + } + + if (host->state == STATE_XFERFINISH_RSPFIN) + host->state = STATE_RSPFIN; + } + +irq_out: + regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk); + spin_unlock_irqrestore(&host->lock, iflags); + return IRQ_HANDLED; + +close_transfer: + host->state = STATE_FINALIZE; + host->pdata->reorder_cmd_data(host, cmd); + regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk); + spin_unlock_irqrestore(&host->lock, iflags); + return IRQ_WAKE_THREAD; +} + +static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios) +{ + u32 pre; + + pre = DIV_ROUND_UP(host->rate, ios->clock); + if (pre > 255) + pre = 255; + + regmap_write(host->regmap, LOONGSON2_MMC_REG_PRE, pre | LOONGSON2_MMC_PRE_EN); + + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL, + LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK); +} + +static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct loongson2_mmc_host *host = mmc_priv(mmc); + int ret; + + if (ios->power_mode == MMC_POWER_UP) { + if (!IS_ERR(mmc->supply.vmmc)) { + ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd); + if (ret) { + dev_err(host->dev, "failed to enable vmmc regulator\n"); + /*return, if failed turn on vmmc*/ + return; + } + } + regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_RESET); + mdelay(10); + regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_EXTCLK); + regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, LOONGSON2_MMC_IEN_ALL); + regmap_write(host->regmap, LOONGSON2_MMC_REG_IEN, LOONGSON2_MMC_INT_CLEAR); + } else if (ios->power_mode == MMC_POWER_OFF) { + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL, + LOONGSON2_MMC_CTL_RESET, LOONGSON2_MMC_CTL_RESET); + if (!IS_ERR(mmc->supply.vmmc)) + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); + return; + } + + loongson2_mmc_set_clk(host, ios); + + host->bus_width = ios->bus_width; +} + +static void loongson2_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) +{ + struct loongson2_mmc_host *host = mmc_priv(mmc); + + host->cmd_is_stop = 0; + host->mrq = mrq; + loongson2_mmc_send_request(mmc); +} + +static void loongson2_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct loongson2_mmc_host *host = mmc_priv(mmc); + + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_IEN, + LOONGSON2_MMC_INT_SDIOIRQ, enable); +} + +static void loongson2_mmc_ack_sdio_irq(struct mmc_host *mmc) +{ + loongson2_mmc_enable_sdio_irq(mmc, 1); +} + +static struct mmc_host_ops loongson2_mmc_ops = { + .request = loongson2_mmc_request, + .set_ios = loongson2_mmc_set_ios, + .get_ro = mmc_gpio_get_ro, + .get_cd = mmc_gpio_get_cd, + .enable_sdio_irq = loongson2_mmc_enable_sdio_irq, + .ack_sdio_irq = loongson2_mmc_ack_sdio_irq, +}; + +static const struct regmap_config ls2k1000_mmc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = LOONGSON2_MMC_REG_IEN, +}; + +static int loongson2_reorder_cmd_list[] = { SD_APP_SEND_SCR, SD_APP_SEND_NUM_WR_BLKS, + SD_APP_SD_STATUS, MMC_SEND_WRITE_PROT, + SD_SWITCH }; + +/* + * According to SD spec, ACMD13, ACMD22, ACMD51 and CMD30 + * response datas has different byte order with usual data packets. + * However sdio controller will send these datas in usual data format, + * so we need to adjust these datas to a protocol consistent byte order. + */ +static void loongson2_mmc_reorder_cmd_data(struct loongson2_mmc_host *host, + struct mmc_command *cmd) +{ + struct scatterlist *sg; + u32 *data; + int i, j; + + if (mmc_cmd_type(cmd) != MMC_CMD_ADTC) + return; + + for (i = 0; i < ARRAY_SIZE(loongson2_reorder_cmd_list); i++) + if (cmd->opcode == loongson2_reorder_cmd_list[i]) + break; + + if (i == ARRAY_SIZE(loongson2_reorder_cmd_list)) + return; + + for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) { + data = sg_virt(&sg[i]); + for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++) + if (cmd->opcode == SD_SWITCH) + data[j] = bitrev8x4(data[j]); + else + data[j] = (__force u32)cpu_to_be32(data[j]); + } +} + +static int loongson2_mmc_prepare_external_dma(struct loongson2_mmc_host *host, + struct mmc_data *data) +{ + struct mmc_host *mmc = mmc_from_priv(host); + struct dma_slave_config dma_conf = { }; + struct dma_async_tx_descriptor *desc; + int ret; + + ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); + if (!ret) + return -ENOMEM; + + dma_conf.src_addr = host->res->start + LOONGSON2_MMC_REG_DATA, + dma_conf.dst_addr = host->res->start + LOONGSON2_MMC_REG_DATA, + dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + dma_conf.direction = !(data->flags & MMC_DATA_WRITE) ? + DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; + + dmaengine_slave_config(host->chan, &dma_conf); + desc = dmaengine_prep_slave_sg(host->chan, data->sg, data->sg_len, + dma_conf.direction, + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); + if (!desc) + goto unmap_exit; + + dmaengine_submit(desc); + dma_async_issue_pending(host->chan); + + return 0; + +unmap_exit: + dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); + return -ENOMEM; +} + +static void loongson2_mmc_release_external_dma(struct loongson2_mmc_host *host, + struct device *dev) +{ + dma_release_channel(host->chan); +} + +static int ls2k0500_mmc_set_external_dma(struct loongson2_mmc_host *host, + struct platform_device *pdev) +{ + int ret, val; + void __iomem *regs; + + regs = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + val = readl(regs); + val |= FIELD_PREP(LS2K0500_SDIO_DMA_MASK, LS2K0500_DMA2_CONF); + writel(val, regs); + + host->chan = dma_request_chan(&pdev->dev, "rx-tx"); + ret = PTR_ERR_OR_ZERO(host->chan); + if (ret) { + dev_err(&pdev->dev, "cannot get DMA channel.\n"); + return ret; + } + + return 0; +} + +static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = { + .regmap_config = &ls2k1000_mmc_regmap_config, + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, + .setting_dma = ls2k0500_mmc_set_external_dma, + .prepare_dma = loongson2_mmc_prepare_external_dma, + .release_dma = loongson2_mmc_release_external_dma, +}; + +static int ls2k1000_mmc_set_external_dma(struct loongson2_mmc_host *host, + struct platform_device *pdev) +{ + int ret, val; + void __iomem *regs; + + regs = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + val = readl(regs); + val |= FIELD_PREP(LS2K1000_SDIO_DMA_MASK, LS2K1000_DMA1_CONF); + writel(val, regs); + + host->chan = dma_request_chan(&pdev->dev, "rx-tx"); + ret = PTR_ERR_OR_ZERO(host->chan); + if (ret) { + dev_err(&pdev->dev, "cannot get DMA channel.\n"); + return ret; + } + + return 0; +} + +static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = { + .regmap_config = &ls2k1000_mmc_regmap_config, + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, + .setting_dma = ls2k1000_mmc_set_external_dma, + .prepare_dma = loongson2_mmc_prepare_external_dma, + .release_dma = loongson2_mmc_release_external_dma, +}; + +static int loongson2_mmc_resource_request(struct platform_device *pdev, + struct loongson2_mmc_host *host) +{ + struct device *dev = &pdev->dev; + void __iomem *base; + int ret, irq; + + base = devm_platform_get_and_ioremap_resource(pdev, 0, &host->res); + if (IS_ERR(base)) + return PTR_ERR(base); + + host->regmap = devm_regmap_init_mmio(dev, base, host->pdata->regmap_config); + if (IS_ERR(host->regmap)) + return PTR_ERR(host->regmap); + + host->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(host->clk)) + return PTR_ERR(host->clk); + + if (host->clk) { + ret = devm_clk_rate_exclusive_get(dev, host->clk); + if (ret) + return PTR_ERR(host->clk); + + host->rate = clk_get_rate(host->clk); + } else { + /* For ACPI, we get rate through clock-frequency attribute */ + device_property_read_u64(dev, "clock-frequency", &host->rate); + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(dev, irq, loongson2_mmc_irq, + loongson2_mmc_irq_worker, + IRQF_ONESHOT, "loongson2-mmc", host); + if (ret) + return ret; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); + if (ret) + return ret; + + return host->pdata->setting_dma(host, pdev); +} + +static int loongson2_mmc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct loongson2_mmc_host *host; + struct mmc_host *mmc; + int ret; + + mmc = devm_mmc_alloc_host(dev, sizeof(*host)); + if (!mmc) + return -ENOMEM; + + platform_set_drvdata(pdev, mmc); + + host = mmc_priv(mmc); + host->state = STATE_NONE; + spin_lock_init(&host->lock); + + host->pdata = device_get_match_data(dev); + if (!host->pdata) + return dev_err_probe(dev, -EINVAL, "Failed to get match data\n"); + + ret = loongson2_mmc_resource_request(pdev, host); + if (ret) + return dev_err_probe(dev, ret, "Failed to request resource\n"); + + mmc->ops = &loongson2_mmc_ops; + mmc->f_min = DIV_ROUND_UP(host->rate, 256); + mmc->f_max = host->rate; + mmc->max_blk_count = 4095; + mmc->max_blk_size = 4095; + mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size; + mmc->max_segs = 1; + mmc->max_seg_size = mmc->max_req_size; + + /* Process SDIO IRQs through the sdio_irq_work. */ + if (mmc->caps & MMC_CAP_SDIO_IRQ) + mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD; + + ret = mmc_regulator_get_supply(mmc); + if (ret || mmc->ocr_avail == 0) { + dev_warn(dev, "Can't get voltage, defaulting to 3.3V\n"); + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + } + + ret = mmc_of_parse(mmc); + if (ret) { + dev_err(dev, "Failed to parse device node\n"); + goto free_dma; + } + + ret = mmc_add_host(mmc); + if (ret) { + dev_err(dev, "Failed to add mmc host\n"); + goto free_dma; + } + + return 0; + +free_dma: + host->pdata->release_dma(host, dev); + return ret; +} + +static void loongson2_mmc_remove(struct platform_device *pdev) +{ + struct mmc_host *mmc = platform_get_drvdata(pdev); + struct loongson2_mmc_host *host = mmc_priv(mmc); + + mmc_remove_host(mmc); + host->pdata->release_dma(host, &pdev->dev); +} + +static const struct of_device_id loongson2_mmc_of_ids[] = { + { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata }, + { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata }, + { }, +}; +MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids); + +static int loongson2_mmc_suspend(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct loongson2_mmc_host *host = mmc_priv(mmc); + + clk_disable_unprepare(host->clk); + + return 0; +} + +static int loongson2_mmc_resume(struct device *dev) +{ + struct mmc_host *mmc = dev_get_drvdata(dev); + struct loongson2_mmc_host *host = mmc_priv(mmc); + + return clk_prepare_enable(host->clk); +} + +static DEFINE_SIMPLE_DEV_PM_OPS(loongson2_mmc_pm_ops, loongson2_mmc_suspend, + loongson2_mmc_resume); + +static struct platform_driver loongson2_mmc_driver = { + .driver = { + .name = "loongson2-mmc", + .of_match_table = loongson2_mmc_of_ids, + .pm = pm_ptr(&loongson2_mmc_pm_ops), + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = loongson2_mmc_probe, + .remove = loongson2_mmc_remove, +}; + +module_platform_driver(loongson2_mmc_driver); + +MODULE_DESCRIPTION("Loongson-2K SD/SDIO/eMMC Interface driver"); +MODULE_AUTHOR("Loongson Technology Corporation Limited"); +MODULE_LICENSE("GPL"); -- 2.47.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver 2025-06-18 8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou @ 2025-06-19 4:02 ` Huacai Chen 2025-06-20 13:20 ` kernel test robot 1 sibling, 0 replies; 11+ messages in thread From: Huacai Chen @ 2025-06-19 4:02 UTC (permalink / raw) To: Binbin Zhou Cc: Binbin Zhou, Huacai Chen, Ulf Hansson, Xuerui Wang, loongarch, linux-mmc, wanghongliang Hi, Binbin, On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote: > > The MMC controllers on the Loongson-2K series CPUs are similar, > except for the interface characteristics and the use of DMA controllers. > > This patch describes the MMC controllers on the Loongson-2K0500/2K1000, > with the distinguishing feature being the use of an externally shared > APBDMA engine. > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> > --- > MAINTAINERS | 1 + > drivers/mmc/host/Kconfig | 13 + > drivers/mmc/host/Makefile | 1 + > drivers/mmc/host/loongson2-mmc.c | 820 +++++++++++++++++++++++++++++++ > 4 files changed, 835 insertions(+) > create mode 100644 drivers/mmc/host/loongson2-mmc.c > > diff --git a/MAINTAINERS b/MAINTAINERS > index 2d1cdd2cfc2b..4a4892613c66 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -14180,6 +14180,7 @@ M: Binbin Zhou <zhoubinbin@loongson.cn> > L: linux-mmc@vger.kernel.org > S: Maintained > F: Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml > +F: drivers/mmc/host/loongson2-mmc.c > > LOONGSON-2 SOC SERIES PM DRIVER > M: Yinbo Zhu <zhuyinbo@loongson.cn> > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > index c3f0f41a426d..7232de1c0688 100644 > --- a/drivers/mmc/host/Kconfig > +++ b/drivers/mmc/host/Kconfig > @@ -1111,6 +1111,19 @@ config MMC_OWL > This selects support for the SD/MMC Host Controller on > Actions Semi Owl SoCs. > > +config MMC_LOONGSON2 > + tristate "Loongson-2K SD/SDIO/eMMC Host Interface support" > + depends on LOONGARCH || COMPILE_TEST > + depends on HAS_DMA > + help > + This selects support for the SD/SDIO/eMMC Host Controller on > + Loongson-2K series CPUs. > + > + To compile this driver as a module, choose M here: the > + module will be called mmc_loongson2. > + > + If unsure, say N. > + > config MMC_SDHCI_EXTERNAL_DMA > bool > > diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile > index 75bafc7b162b..5057fea8afb6 100644 > --- a/drivers/mmc/host/Makefile > +++ b/drivers/mmc/host/Makefile > @@ -72,6 +72,7 @@ obj-$(CONFIG_MMC_USDHI6ROL0) += usdhi6rol0.o > obj-$(CONFIG_MMC_TOSHIBA_PCI) += toshsd.o > obj-$(CONFIG_MMC_BCM2835) += bcm2835.o > obj-$(CONFIG_MMC_OWL) += owl-mmc.o > +obj-$(CONFIG_MMC_LOONGSON2) += loongson2-mmc.o > > obj-$(CONFIG_MMC_REALTEK_PCI) += rtsx_pci_sdmmc.o > obj-$(CONFIG_MMC_REALTEK_USB) += rtsx_usb_sdmmc.o > diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c > new file mode 100644 > index 000000000000..872f5dc21b21 > --- /dev/null > +++ b/drivers/mmc/host/loongson2-mmc.c > @@ -0,0 +1,820 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Loongson-2K MMC/SDIO controller driver > + * > + * Copyright (C) 2018-2025 Loongson Technology Corporation Limited. > + * > + */ > + > +#include <linux/bitfield.h> > +#include <linux/bitrev.h> > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/dmaengine.h> > +#include <linux/dma-mapping.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/mmc/core.h> > +#include <linux/mmc/host.h> > +#include <linux/mmc/mmc.h> > +#include <linux/mmc/sd.h> > +#include <linux/mmc/sdio.h> > +#include <linux/mmc/slot-gpio.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/regmap.h> > + > +#define LOONGSON2_MMC_REG_CTL 0x00 /* Control Register */ > +#define LOONGSON2_MMC_REG_PRE 0x04 /* Prescaler Register */ > +#define LOONGSON2_MMC_REG_CARG 0x08 /* Command Register */ > +#define LOONGSON2_MMC_REG_CCTL 0x0c /* Command Control Register */ > +#define LOONGSON2_MMC_REG_CSTS 0x10 /* Command Status Register */ > +#define LOONGSON2_MMC_REG_RSP0 0x14 /* Command Response Register 0 */ > +#define LOONGSON2_MMC_REG_RSP1 0x18 /* Command Response Register 1 */ > +#define LOONGSON2_MMC_REG_RSP2 0x1c /* Command Response Register 2 */ > +#define LOONGSON2_MMC_REG_RSP3 0x20 /* Command Response Register 3 */ > +#define LOONGSON2_MMC_REG_TIMER 0x24 /* Data Timeout Register */ > +#define LOONGSON2_MMC_REG_BSIZE 0x28 /* Block Size Register */ > +#define LOONGSON2_MMC_REG_DCTL 0x2c /* Data Control Register */ > +#define LOONGSON2_MMC_REG_DCNT 0x30 /* Data Counter Register */ > +#define LOONGSON2_MMC_REG_DSTS 0x34 /* Data Status Register */ > +#define LOONGSON2_MMC_REG_FSTS 0x38 /* FIFO Status Register */ > +#define LOONGSON2_MMC_REG_INT 0x3c /* Interrupt Register */ > +#define LOONGSON2_MMC_REG_DATA 0x40 /* Data Register */ > +#define LOONGSON2_MMC_REG_IEN 0x64 /* Interrupt Enable Register */ > + > +/* Bitfields of control register */ > +#define LOONGSON2_MMC_CTL_ENCLK BIT(0) > +#define LOONGSON2_MMC_CTL_EXTCLK BIT(1) > +#define LOONGSON2_MMC_CTL_RESET BIT(8) > + > +/* Bitfields of prescaler register */ > +#define LOONGSON2_MMC_PRE GENMASK(9, 0) > +#define LOONGSON2_MMC_PRE_EN BIT(31) > + > +/* Bitfields of command control register */ > +#define LOONGSON2_MMC_CCTL_INDEX GENMASK(5, 0) > +#define LOONGSON2_MMC_CCTL_HOST BIT(6) > +#define LOONGSON2_MMC_CCTL_START BIT(8) > +#define LOONGSON2_MMC_CCTL_WAIT_RSP BIT(9) > +#define LOONGSON2_MMC_CCTL_LONG_RSP BIT(10) > +#define LOONGSON2_MMC_CCTL_ABORT BIT(12) > +#define LOONGSON2_MMC_CCTL_CHECK BIT(13) > +#define LOONGSON2_MMC_CCTL_SDIO BIT(14) > +#define LOONGSON2_MMC_CCTL_CMD6 BIT(18) > + > +/* Bitfields of command status register */ > +#define LOONGSON2_MMC_CSTS_INDEX GENMASK(7, 0) > +#define LOONGSON2_MMC_CSTS_ON BIT(8) > +#define LOONGSON2_MMC_CSTS_RSP BIT(9) > +#define LOONGSON2_MMC_CSTS_TIMEOUT BIT(10) > +#define LOONGSON2_MMC_CSTS_END BIT(11) > +#define LOONGSON2_MMC_CSTS_CRC_ERR BIT(12) > +#define LOONGSON2_MMC_CSTS_AUTO_STOP BIT(13) > +#define LOONGSON2_MMC_CSTS_FIN BIT(14) > + > +/* Bitfields of data timeout register */ > +#define LOONGSON2_MMC_DTIMR GENMASK(23, 0) > + > +/* Bitfields of block size register */ > +#define LOONGSON2_MMC_BSIZE GENMASK(11, 0) > + > +/* Bitfields of data control register */ > +#define LOONGSON2_MMC_DCTL_BNUM GENMASK(11, 0) > +#define LOONGSON2_MMC_DCTL_START BIT(14) > +#define LOONGSON2_MMC_DCTL_ENDMA BIT(15) > +#define LOONGSON2_MMC_DCTL_WIDE BIT(16) > +#define LOONGSON2_MMC_DCTL_RWAIT BIT(17) > +#define LOONGSON2_MMC_DCTL_IO_SUSPEND BIT(18) > +#define LOONGSON2_MMC_DCTL_IO_RESUME BIT(19) > +#define LOONGSON2_MMC_DCTL_RW_RESUME BIT(20) > +#define LOONGSON2_MMC_DCTL_8BIT_BUS BIT(26) > + > +/* Bitfields of sata counter register */ > +#define LOONGSON2_MMC_DCNT_BNUM GENMASK(11, 0) > +#define LOONGSON2_MMC_DCNT_BYTE GENMASK(23, 12) > + > +/* Bitfields of command status register */ > +#define LOONGSON2_MMC_DSTS_RXON BIT(0) > +#define LOONGSON2_MMC_DSTS_TXON BIT(1) > +#define LOONGSON2_MMC_DSTS_SBITERR BIT(2) > +#define LOONGSON2_MMC_DSTS_BUSYFIN BIT(3) > +#define LOONGSON2_MMC_DSTS_XFERFIN BIT(4) > +#define LOONGSON2_MMC_DSTS_DTIMEOUT BIT(5) > +#define LOONGSON2_MMC_DSTS_RXCRC BIT(6) > +#define LOONGSON2_MMC_DSTS_TXCRC BIT(7) > +#define LOONGSON2_MMC_DSTS_IRQ BIT(8) > +#define LOONGSON2_MMC_DSTS_START BIT(13) > +#define LOONGSON2_MMC_DSTS_RESUME BIT(15) > +#define LOONGSON2_MMC_DSTS_SUSPEND BIT(16) > + > +/* Bitfields of interrupt register */ > +#define LOONGSON2_MMC_INT_DFIN BIT(0) > +#define LOONGSON2_MMC_INT_DTIMEOUT BIT(1) > +#define LOONGSON2_MMC_INT_RXCRC BIT(2) > +#define LOONGSON2_MMC_INT_TXCRC BIT(3) > +#define LOONGSON2_MMC_INT_PROGERR BIT(4) > +#define LOONGSON2_MMC_INT_SDIOIRQ BIT(5) > +#define LOONGSON2_MMC_INT_CSENT BIT(6) > +#define LOONGSON2_MMC_INT_CTIMEOUT BIT(7) > +#define LOONGSON2_MMC_INT_RESPCRC BIT(8) > +#define LOONGSON2_MMC_INT_BUSYEND BIT(9) > + > +/* Bitfields of interrupt enable register */ > +#define LOONGSON2_MMC_IEN_DFIN BIT(0) > +#define LOONGSON2_MMC_IEN_DTIMEOUT BIT(1) > +#define LOONGSON2_MMC_IEN_RXCRC BIT(2) > +#define LOONGSON2_MMC_IEN_TXCRC BIT(3) > +#define LOONGSON2_MMC_IEN_PROGERR BIT(4) > +#define LOONGSON2_MMC_IEN_SDIOIRQ BIT(5) > +#define LOONGSON2_MMC_IEN_CSENT BIT(6) > +#define LOONGSON2_MMC_IEN_CTIMEOUT BIT(7) > +#define LOONGSON2_MMC_IEN_RESPCRC BIT(8) > +#define LOONGSON2_MMC_IEN_BUSYEND BIT(9) > + > +#define LOONGSON2_MMC_IEN_ALL GENMASK(9, 0) > +#define LOONGSON2_MMC_INT_CLEAR GENMASK(9, 0) > + > +/* Loongson-2K1000 SDIO2 DMA routing register */ > +#define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) > +#define LS2K1000_DMA0_CONF 0x0 > +#define LS2K1000_DMA1_CONF 0x1 > +#define LS2K1000_DMA2_CONF 0x2 > +#define LS2K1000_DMA3_CONF 0x3 > +#define LS2K1000_DMA4_CONF 0x4 > + > +/* Loongson-2K0500 SDIO2 DMA routing register */ > +#define LS2K0500_SDIO_DMA_MASK GENMASK(15, 14) > +#define LS2K0500_DMA0_CONF 0x1 > +#define LS2K0500_DMA1_CONF 0x2 > +#define LS2K0500_DMA2_CONF 0x3 > + > +enum loongson2_mmc_state { > + STATE_NONE, > + STATE_FINALIZE, > + STATE_CMDSENT, > + STATE_RSPFIN, > + STATE_XFERFINISH, > + STATE_XFERFINISH_RSPFIN, > +}; > + > +struct loongson2_dma_desc { > + u32 ndesc_addr; > + u32 mem_addr; > + u32 apb_addr; > + u32 len; > + u32 step_len; > + u32 step_times; > + u32 cmd; > + u32 stats; > + u32 high_ndesc_addr; > + u32 high_mem_addr; > + u32 reserved[2]; > +} __packed; > + > +struct loongson2_mmc_host { > + struct device *dev; > + struct mmc_request *mrq; > + struct regmap *regmap; > + struct resource *res; > + struct clk *clk; > + u64 rate; > + int dma_complete; > + struct dma_chan *chan; > + int cmd_is_stop; > + int bus_width; > + spinlock_t lock; /* Prevent races with irq handler */ > + enum loongson2_mmc_state state; > + const struct loongson2_mmc_pdata *pdata; > +}; > + > +struct loongson2_mmc_pdata { > + const struct regmap_config *regmap_config; > + void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd); > + int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev); > + int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data); > + void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev); > +}; > + > +static void loongson2_mmc_send_command(struct loongson2_mmc_host *host, > + struct mmc_command *cmd) > +{ > + u32 cctrl; > + > + if (cmd->data) > + host->state = STATE_XFERFINISH_RSPFIN; > + else if (cmd->flags & MMC_RSP_PRESENT) > + host->state = STATE_RSPFIN; > + else > + host->state = STATE_CMDSENT; > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, cmd->arg); > + > + cctrl = FIELD_PREP(LOONGSON2_MMC_CCTL_INDEX, cmd->opcode); > + cctrl |= LOONGSON2_MMC_CCTL_HOST | LOONGSON2_MMC_CCTL_START; > + > + if (cmd->opcode == SD_SWITCH && cmd->data) > + cctrl |= LOONGSON2_MMC_CCTL_CMD6; > + > + if (cmd->flags & MMC_RSP_PRESENT) > + cctrl |= LOONGSON2_MMC_CCTL_WAIT_RSP; > + > + if (cmd->flags & MMC_RSP_136) > + cctrl |= LOONGSON2_MMC_CCTL_LONG_RSP; > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, cctrl); > +} > + > +static int loongson2_mmc_setup_data(struct loongson2_mmc_host *host, > + struct mmc_data *data) > +{ > + u32 dctrl; > + > + if ((data->blksz & 3) != 0) > + return -EINVAL; > + > + dctrl = FIELD_PREP(LOONGSON2_MMC_DCTL_BNUM, data->blocks); > + dctrl |= LOONGSON2_MMC_DCTL_START | LOONGSON2_MMC_DCTL_ENDMA; > + > + if (host->bus_width == MMC_BUS_WIDTH_4) > + dctrl |= LOONGSON2_MMC_DCTL_WIDE; > + else if (host->bus_width == MMC_BUS_WIDTH_8) > + dctrl |= LOONGSON2_MMC_DCTL_8BIT_BUS; > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_DCTL, dctrl); > + regmap_write(host->regmap, LOONGSON2_MMC_REG_BSIZE, data->blksz); > + regmap_write(host->regmap, LOONGSON2_MMC_REG_TIMER, U32_MAX); > + > + return 0; > +} > + > +static int loongson2_mmc_prepare_dma(struct loongson2_mmc_host *host, > + struct mmc_data *data) > +{ > + int ret; > + > + if (!data) > + return 0; > + > + ret = loongson2_mmc_setup_data(host, data); > + if (ret) > + return ret; > + > + host->dma_complete = 0; > + > + return host->pdata->prepare_dma(host, data); > +} > + > +static void loongson2_mmc_send_request(struct mmc_host *mmc) > +{ > + int ret; > + struct loongson2_mmc_host *host = mmc_priv(mmc); > + struct mmc_request *mrq = host->mrq; > + struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd; > + > + ret = loongson2_mmc_prepare_dma(host, cmd->data); > + if (ret) { > + dev_err(host->dev, "DMA data prepared failed with %d\n", ret); > + cmd->error = ret; > + cmd->data->error = ret; > + mmc_request_done(mmc, mrq); > + return; > + } > + > + loongson2_mmc_send_command(host, cmd); > + > + /* Fix deselect card */ > + if (cmd->opcode == MMC_SELECT_CARD && cmd->arg == 0) { > + cmd->error = 0; > + mmc_request_done(mmc, mrq); > + } > +} > + > +static irqreturn_t loongson2_mmc_irq_worker(int irq, void *devid) > +{ > + struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid; > + struct mmc_host *mmc = mmc_from_priv(host); > + struct mmc_request *mrq = host->mrq; > + struct mmc_command *cmd = host->cmd_is_stop ? mrq->stop : mrq->cmd; > + > + if (cmd->data) > + dma_unmap_sg(mmc_dev(mmc), cmd->data->sg, cmd->data->sg_len, > + mmc_get_dma_dir(cmd->data)); > + > + if (cmd->data && !cmd->error && > + !cmd->data->error && !host->dma_complete) > + return IRQ_HANDLED; > + > + /* Read response from controller. */ > + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP0, &cmd->resp[0]); > + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP1, &cmd->resp[1]); > + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP2, &cmd->resp[2]); > + regmap_read(host->regmap, LOONGSON2_MMC_REG_RSP3, &cmd->resp[3]); > + > + /* Cleanup controller */ > + regmap_write(host->regmap, LOONGSON2_MMC_REG_CARG, 0); > + regmap_write(host->regmap, LOONGSON2_MMC_REG_CCTL, 0); > + > + if (cmd->data && cmd->error) > + cmd->data->error = cmd->error; > + > + if (cmd->data && cmd->data->stop && !host->cmd_is_stop) { > + host->cmd_is_stop = 1; > + loongson2_mmc_send_request(mmc); > + return IRQ_HANDLED; > + } > + > + /* If we have no data transfer we are finished here */ > + if (!mrq->data) > + goto request_done; > + > + /* Calculate the amount of bytes transfer if there was no error */ > + if (mrq->data->error == 0) { > + mrq->data->bytes_xfered = > + (mrq->data->blocks * mrq->data->blksz); > + } else { > + mrq->data->bytes_xfered = 0; > + } > + > +request_done: > + host->state = STATE_NONE; > + host->mrq = NULL; > + mmc_request_done(mmc, mrq); > + return IRQ_HANDLED; > +} > + > +static irqreturn_t loongson2_mmc_irq(int irq, void *devid) > +{ > + struct loongson2_mmc_host *host = (struct loongson2_mmc_host *)devid; > + struct mmc_host *mmc = mmc_from_priv(host); > + struct mmc_command *cmd; > + unsigned long iflags; > + u32 dsts, imsk; > + > + regmap_read(host->regmap, LOONGSON2_MMC_REG_INT, &imsk); > + regmap_read(host->regmap, LOONGSON2_MMC_REG_DSTS, &dsts); > + > + if ((dsts & LOONGSON2_MMC_DSTS_IRQ) && > + (imsk & LOONGSON2_MMC_INT_SDIOIRQ)) { > + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_INT, > + LOONGSON2_MMC_INT_SDIOIRQ, > + LOONGSON2_MMC_INT_SDIOIRQ); The last two lines can be combined. > + > + sdio_signal_irq(mmc); > + return IRQ_HANDLED; > + } > + > + spin_lock_irqsave(&host->lock, iflags); > + > + if (host->state == STATE_NONE || host->state == STATE_FINALIZE || > + !host->mrq) The if condition can be put in one line here. > + goto irq_out; > + > + cmd = host->cmd_is_stop ? host->mrq->stop : host->mrq->cmd; > + if (!cmd) > + goto irq_out; > + > + cmd->error = 0; > + > + if (imsk & LOONGSON2_MMC_INT_CTIMEOUT) { > + cmd->error = -ETIMEDOUT; > + goto close_transfer; > + } > + > + if (imsk & LOONGSON2_MMC_INT_CSENT) { > + if (host->state == STATE_RSPFIN || host->state == STATE_CMDSENT) > + goto close_transfer; > + > + if (host->state == STATE_XFERFINISH_RSPFIN) > + host->state = STATE_XFERFINISH; > + } > + > + if (!cmd->data) > + goto irq_out; > + > + if (imsk & (LOONGSON2_MMC_INT_RXCRC | LOONGSON2_MMC_INT_TXCRC)) { > + cmd->data->error = -EILSEQ; > + goto close_transfer; > + } > + > + if (imsk & LOONGSON2_MMC_INT_DTIMEOUT) { > + cmd->data->error = -ETIMEDOUT; > + goto close_transfer; > + } > + > + if (imsk & LOONGSON2_MMC_INT_DFIN) { > + if (host->state == STATE_XFERFINISH) { > + host->dma_complete = 1; > + goto close_transfer; > + } > + > + if (host->state == STATE_XFERFINISH_RSPFIN) > + host->state = STATE_RSPFIN; > + } > + > +irq_out: > + regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk); > + spin_unlock_irqrestore(&host->lock, iflags); > + return IRQ_HANDLED; > + > +close_transfer: > + host->state = STATE_FINALIZE; > + host->pdata->reorder_cmd_data(host, cmd); > + regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, imsk); > + spin_unlock_irqrestore(&host->lock, iflags); > + return IRQ_WAKE_THREAD; > +} > + > +static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios) > +{ > + u32 pre; > + > + pre = DIV_ROUND_UP(host->rate, ios->clock); > + if (pre > 255) > + pre = 255; > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_PRE, pre | LOONGSON2_MMC_PRE_EN); > + > + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL, > + LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK); > +} > + > +static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) > +{ > + struct loongson2_mmc_host *host = mmc_priv(mmc); > + int ret; > + > + if (ios->power_mode == MMC_POWER_UP) { > + if (!IS_ERR(mmc->supply.vmmc)) { > + ret = mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, ios->vdd); > + if (ret) { > + dev_err(host->dev, "failed to enable vmmc regulator\n"); > + /*return, if failed turn on vmmc*/ There should be spaces between /**/ and the comments, and the whole comments can be put after the "return;" statement. > + return; > + } > + } > + regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_RESET); > + mdelay(10); > + regmap_write(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_EXTCLK); > + regmap_write(host->regmap, LOONGSON2_MMC_REG_INT, LOONGSON2_MMC_IEN_ALL); > + regmap_write(host->regmap, LOONGSON2_MMC_REG_IEN, LOONGSON2_MMC_INT_CLEAR); > + } else if (ios->power_mode == MMC_POWER_OFF) { > + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL, > + LOONGSON2_MMC_CTL_RESET, LOONGSON2_MMC_CTL_RESET); > + if (!IS_ERR(mmc->supply.vmmc)) > + mmc_regulator_set_ocr(mmc, mmc->supply.vmmc, 0); > + return; > + } > + > + loongson2_mmc_set_clk(host, ios); > + > + host->bus_width = ios->bus_width; > +} > + > +static void loongson2_mmc_request(struct mmc_host *mmc, struct mmc_request *mrq) > +{ > + struct loongson2_mmc_host *host = mmc_priv(mmc); > + > + host->cmd_is_stop = 0; > + host->mrq = mrq; > + loongson2_mmc_send_request(mmc); > +} > + > +static void loongson2_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) > +{ > + struct loongson2_mmc_host *host = mmc_priv(mmc); > + > + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_IEN, > + LOONGSON2_MMC_INT_SDIOIRQ, enable); The statement can be put in one line here, too. > +} > + > +static void loongson2_mmc_ack_sdio_irq(struct mmc_host *mmc) > +{ > + loongson2_mmc_enable_sdio_irq(mmc, 1); > +} > + > +static struct mmc_host_ops loongson2_mmc_ops = { > + .request = loongson2_mmc_request, > + .set_ios = loongson2_mmc_set_ios, > + .get_ro = mmc_gpio_get_ro, > + .get_cd = mmc_gpio_get_cd, > + .enable_sdio_irq = loongson2_mmc_enable_sdio_irq, > + .ack_sdio_irq = loongson2_mmc_ack_sdio_irq, > +}; > + > +static const struct regmap_config ls2k1000_mmc_regmap_config = { > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = LOONGSON2_MMC_REG_IEN, > +}; > + > +static int loongson2_reorder_cmd_list[] = { SD_APP_SEND_SCR, SD_APP_SEND_NUM_WR_BLKS, > + SD_APP_SD_STATUS, MMC_SEND_WRITE_PROT, > + SD_SWITCH }; The last two lines can be combined. > + > +/* > + * According to SD spec, ACMD13, ACMD22, ACMD51 and CMD30 > + * response datas has different byte order with usual data packets. > + * However sdio controller will send these datas in usual data format, > + * so we need to adjust these datas to a protocol consistent byte order. > + */ > +static void loongson2_mmc_reorder_cmd_data(struct loongson2_mmc_host *host, > + struct mmc_command *cmd) > +{ > + struct scatterlist *sg; > + u32 *data; > + int i, j; > + > + if (mmc_cmd_type(cmd) != MMC_CMD_ADTC) > + return; > + > + for (i = 0; i < ARRAY_SIZE(loongson2_reorder_cmd_list); i++) > + if (cmd->opcode == loongson2_reorder_cmd_list[i]) > + break; > + > + if (i == ARRAY_SIZE(loongson2_reorder_cmd_list)) > + return; > + > + for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) { > + data = sg_virt(&sg[i]); > + for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++) > + if (cmd->opcode == SD_SWITCH) > + data[j] = bitrev8x4(data[j]); > + else > + data[j] = (__force u32)cpu_to_be32(data[j]); > + } > +} > + > +static int loongson2_mmc_prepare_external_dma(struct loongson2_mmc_host *host, > + struct mmc_data *data) > +{ > + struct mmc_host *mmc = mmc_from_priv(host); > + struct dma_slave_config dma_conf = { }; > + struct dma_async_tx_descriptor *desc; > + int ret; > + > + ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, > + mmc_get_dma_dir(data)); The statement can be put in one line here, too. > + if (!ret) > + return -ENOMEM; > + > + dma_conf.src_addr = host->res->start + LOONGSON2_MMC_REG_DATA, > + dma_conf.dst_addr = host->res->start + LOONGSON2_MMC_REG_DATA, > + dma_conf.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, > + dma_conf.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, > + dma_conf.direction = !(data->flags & MMC_DATA_WRITE) ? > + DMA_DEV_TO_MEM : DMA_MEM_TO_DEV; The statement can be put in one line here, too. > + > + dmaengine_slave_config(host->chan, &dma_conf); > + desc = dmaengine_prep_slave_sg(host->chan, data->sg, data->sg_len, > + dma_conf.direction, > + DMA_CTRL_ACK | DMA_PREP_INTERRUPT); > + if (!desc) > + goto unmap_exit; > + > + dmaengine_submit(desc); > + dma_async_issue_pending(host->chan); > + > + return 0; > + > +unmap_exit: > + dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len, > + mmc_get_dma_dir(data)); The statement can be put in one line here, too. > + return -ENOMEM; > +} > + > +static void loongson2_mmc_release_external_dma(struct loongson2_mmc_host *host, > + struct device *dev) > +{ > + dma_release_channel(host->chan); > +} > + > +static int ls2k0500_mmc_set_external_dma(struct loongson2_mmc_host *host, > + struct platform_device *pdev) > +{ > + int ret, val; > + void __iomem *regs; > + > + regs = devm_platform_ioremap_resource(pdev, 1); > + if (IS_ERR(regs)) > + return PTR_ERR(regs); > + > + val = readl(regs); > + val |= FIELD_PREP(LS2K0500_SDIO_DMA_MASK, LS2K0500_DMA2_CONF); > + writel(val, regs); > + > + host->chan = dma_request_chan(&pdev->dev, "rx-tx"); > + ret = PTR_ERR_OR_ZERO(host->chan); > + if (ret) { > + dev_err(&pdev->dev, "cannot get DMA channel.\n"); > + return ret; Too many spaces here. > + } > + > + return 0; > +} > + > +static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = { > + .regmap_config = &ls2k1000_mmc_regmap_config, > + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, > + .setting_dma = ls2k0500_mmc_set_external_dma, > + .prepare_dma = loongson2_mmc_prepare_external_dma, > + .release_dma = loongson2_mmc_release_external_dma, > +}; > + > +static int ls2k1000_mmc_set_external_dma(struct loongson2_mmc_host *host, > + struct platform_device *pdev) > +{ > + int ret, val; > + void __iomem *regs; > + > + regs = devm_platform_ioremap_resource(pdev, 1); > + if (IS_ERR(regs)) > + return PTR_ERR(regs); > + > + val = readl(regs); > + val |= FIELD_PREP(LS2K1000_SDIO_DMA_MASK, LS2K1000_DMA1_CONF); > + writel(val, regs); > + > + host->chan = dma_request_chan(&pdev->dev, "rx-tx"); > + ret = PTR_ERR_OR_ZERO(host->chan); > + if (ret) { > + dev_err(&pdev->dev, "cannot get DMA channel.\n"); > + return ret; > + } > + > + return 0; > +} > + > +static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = { > + .regmap_config = &ls2k1000_mmc_regmap_config, > + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, > + .setting_dma = ls2k1000_mmc_set_external_dma, > + .prepare_dma = loongson2_mmc_prepare_external_dma, > + .release_dma = loongson2_mmc_release_external_dma, > +}; > + > +static int loongson2_mmc_resource_request(struct platform_device *pdev, > + struct loongson2_mmc_host *host) > +{ > + struct device *dev = &pdev->dev; > + void __iomem *base; > + int ret, irq; > + > + base = devm_platform_get_and_ioremap_resource(pdev, 0, &host->res); > + if (IS_ERR(base)) > + return PTR_ERR(base); > + > + host->regmap = devm_regmap_init_mmio(dev, base, host->pdata->regmap_config); > + if (IS_ERR(host->regmap)) > + return PTR_ERR(host->regmap); > + > + host->clk = devm_clk_get_optional_enabled(dev, NULL); > + if (IS_ERR(host->clk)) > + return PTR_ERR(host->clk); > + > + if (host->clk) { > + ret = devm_clk_rate_exclusive_get(dev, host->clk); > + if (ret) > + return PTR_ERR(host->clk); > + > + host->rate = clk_get_rate(host->clk); > + } else { > + /* For ACPI, we get rate through clock-frequency attribute */ > + device_property_read_u64(dev, "clock-frequency", &host->rate); > + } > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return irq; > + > + ret = devm_request_threaded_irq(dev, irq, loongson2_mmc_irq, > + loongson2_mmc_irq_worker, > + IRQF_ONESHOT, "loongson2-mmc", host); > + if (ret) > + return ret; > + > + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)); > + if (ret) > + return ret; > + > + return host->pdata->setting_dma(host, pdev); > +} > + > +static int loongson2_mmc_probe(struct platform_device *pdev) > +{ > + struct device *dev = &pdev->dev; > + struct loongson2_mmc_host *host; > + struct mmc_host *mmc; > + int ret; > + > + mmc = devm_mmc_alloc_host(dev, sizeof(*host)); > + if (!mmc) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, mmc); > + > + host = mmc_priv(mmc); > + host->state = STATE_NONE; > + spin_lock_init(&host->lock); > + > + host->pdata = device_get_match_data(dev); > + if (!host->pdata) > + return dev_err_probe(dev, -EINVAL, "Failed to get match data\n"); > + > + ret = loongson2_mmc_resource_request(pdev, host); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to request resource\n"); > + > + mmc->ops = &loongson2_mmc_ops; > + mmc->f_min = DIV_ROUND_UP(host->rate, 256); > + mmc->f_max = host->rate; > + mmc->max_blk_count = 4095; > + mmc->max_blk_size = 4095; > + mmc->max_req_size = mmc->max_blk_count * mmc->max_blk_size; > + mmc->max_segs = 1; > + mmc->max_seg_size = mmc->max_req_size; > + > + /* Process SDIO IRQs through the sdio_irq_work. */ > + if (mmc->caps & MMC_CAP_SDIO_IRQ) > + mmc->caps2 |= MMC_CAP2_SDIO_IRQ_NOTHREAD; > + > + ret = mmc_regulator_get_supply(mmc); > + if (ret || mmc->ocr_avail == 0) { > + dev_warn(dev, "Can't get voltage, defaulting to 3.3V\n"); > + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; > + } > + > + ret = mmc_of_parse(mmc); > + if (ret) { > + dev_err(dev, "Failed to parse device node\n"); > + goto free_dma; > + } > + > + ret = mmc_add_host(mmc); > + if (ret) { > + dev_err(dev, "Failed to add mmc host\n"); > + goto free_dma; > + } > + > + return 0; > + > +free_dma: > + host->pdata->release_dma(host, dev); > + return ret; > +} > + > +static void loongson2_mmc_remove(struct platform_device *pdev) > +{ > + struct mmc_host *mmc = platform_get_drvdata(pdev); > + struct loongson2_mmc_host *host = mmc_priv(mmc); > + > + mmc_remove_host(mmc); > + host->pdata->release_dma(host, &pdev->dev); > +} > + > +static const struct of_device_id loongson2_mmc_of_ids[] = { > + { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata }, > + { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata }, > + { }, > +}; > +MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids); > + > +static int loongson2_mmc_suspend(struct device *dev) > +{ > + struct mmc_host *mmc = dev_get_drvdata(dev); Too many spaces here. > + struct loongson2_mmc_host *host = mmc_priv(mmc); > + > + clk_disable_unprepare(host->clk); > + > + return 0; > +} > + > +static int loongson2_mmc_resume(struct device *dev) > +{ > + struct mmc_host *mmc = dev_get_drvdata(dev); Too many spaces here. > + struct loongson2_mmc_host *host = mmc_priv(mmc); > + > + return clk_prepare_enable(host->clk); > +} > + > +static DEFINE_SIMPLE_DEV_PM_OPS(loongson2_mmc_pm_ops, loongson2_mmc_suspend, > + loongson2_mmc_resume); The statement can be put in one line here, too. Huacai > + > +static struct platform_driver loongson2_mmc_driver = { > + .driver = { > + .name = "loongson2-mmc", > + .of_match_table = loongson2_mmc_of_ids, > + .pm = pm_ptr(&loongson2_mmc_pm_ops), > + .probe_type = PROBE_PREFER_ASYNCHRONOUS, > + }, > + .probe = loongson2_mmc_probe, > + .remove = loongson2_mmc_remove, > +}; > + > +module_platform_driver(loongson2_mmc_driver); > + > +MODULE_DESCRIPTION("Loongson-2K SD/SDIO/eMMC Interface driver"); > +MODULE_AUTHOR("Loongson Technology Corporation Limited"); > +MODULE_LICENSE("GPL"); > -- > 2.47.1 > ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver 2025-06-18 8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou 2025-06-19 4:02 ` Huacai Chen @ 2025-06-20 13:20 ` kernel test robot 1 sibling, 0 replies; 11+ messages in thread From: kernel test robot @ 2025-06-20 13:20 UTC (permalink / raw) To: Binbin Zhou, Binbin Zhou, Huacai Chen, Ulf Hansson Cc: oe-kbuild-all, Xuerui Wang, loongarch, linux-mmc, wanghongliang Hi Binbin, kernel test robot noticed the following build errors: [auto build test ERROR on 187715cfd12932a528ff3a3952648e2b55381d4c] url: https://github.com/intel-lab-lkp/linux/commits/Binbin-Zhou/dt-bindings-mmc-Add-Loongson-2K-SD-SDIO-eMMC-controller-binding/20250618-160908 base: 187715cfd12932a528ff3a3952648e2b55381d4c patch link: https://lore.kernel.org/r/abfe54473135df478db14b5ef0e1773326455f21.1750216134.git.zhoubinbin%40loongson.cn patch subject: [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver config: microblaze-randconfig-r133-20250620 (https://download.01.org/0day-ci/archive/20250620/202506202031.TNchn822-lkp@intel.com/config) compiler: microblaze-linux-gcc (GCC) 8.5.0 reproduce: (https://download.01.org/0day-ci/archive/20250620/202506202031.TNchn822-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202506202031.TNchn822-lkp@intel.com/ All errors (new ones prefixed by >>): microblaze-linux-ld: drivers/mmc/host/loongson2-mmc.o: in function `loongson2_mmc_set_ios': >> .tmp_gl_loongson2-mmc.o:(.text+0xec8): undefined reference to `__udivdi3' -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000 2025-06-18 8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou 2025-06-18 8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou 2025-06-18 8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou @ 2025-06-18 8:07 ` Binbin Zhou 2025-06-18 8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou 3 siblings, 0 replies; 11+ messages in thread From: Binbin Zhou @ 2025-06-18 8:07 UTC (permalink / raw) To: Binbin Zhou, Huacai Chen, Ulf Hansson Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang, Binbin Zhou, Conor Dooley Add the devicetree compatible for Loongson-2K2000 EMMC/SD/SDIO controller. Acked-by: Conor Dooley <conor.dooley@microchip.com> Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> --- .../bindings/mmc/loongson,ls2k0500-mmc.yaml | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml index f2f10b2c618c..c142421bc723 100644 --- a/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml +++ b/Documentation/devicetree/bindings/mmc/loongson,ls2k0500-mmc.yaml @@ -9,6 +9,9 @@ title: The SD/SDIO/eMMC host controller for Loongson-2K family SoCs description: The MMC host controller on the Loongson-2K0500/2K1000 (using an externally shared apbdma controller) provides the SD and SDIO device interfaces. + The two MMC host controllers on the Loongson-2K2000 are similar, + except that they use internal exclusive DMA. one controller provides + the eMMC interface and the other provides the SD/SDIO interface. maintainers: - Binbin Zhou <zhoubinbin@loongson.cn> @@ -21,8 +24,10 @@ properties: enum: - loongson,ls2k0500-mmc - loongson,ls2k1000-mmc + - loongson,ls2k2000-mmc reg: + minItems: 1 items: - description: Loongson-2K MMC controller registers. - description: APB DMA config register for Loongson-2K MMC controller. @@ -44,11 +49,31 @@ required: - reg - interrupts - clocks - - dmas - - dma-names unevaluatedProperties: false +if: + properties: + compatible: + contains: + enum: + - loongson,ls2k0500-mmc + - loongson,ls2k1000-mmc + +then: + properties: + reg: + minItems: 2 + + required: + - dmas + - dma-names + +else: + properties: + reg: + maxItems: 1 + examples: - | #include <dt-bindings/gpio/gpio.h> @@ -67,3 +92,21 @@ examples: bus-width = <4>; cd-gpios = <&gpio0 22 GPIO_ACTIVE_LOW>; }; + + - | + #include <dt-bindings/interrupt-controller/irq.h> + #include <dt-bindings/clock/loongson,ls2k-clk.h> + + mmc@79990000 { + compatible = "loongson,ls2k2000-mmc"; + reg = <0x79990000 0x1000>; + interrupt-parent = <&pic>; + interrupts = <51 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clk LOONGSON2_EMMC_CLK>; + bus-width = <8>; + non-removable; + cap-mmc-highspeed; + mmc-hs200-1_8v; + no-sd; + no-sdio; + }; -- 2.47.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver 2025-06-18 8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou ` (2 preceding siblings ...) 2025-06-18 8:07 ` [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000 Binbin Zhou @ 2025-06-18 8:08 ` Binbin Zhou 2025-06-19 4:02 ` Huacai Chen 2025-06-23 11:24 ` Ulf Hansson 3 siblings, 2 replies; 11+ messages in thread From: Binbin Zhou @ 2025-06-18 8:08 UTC (permalink / raw) To: Binbin Zhou, Huacai Chen, Ulf Hansson Cc: Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang, Binbin Zhou This patch describes the two MMC controllers of the Loongson-2K2000 SoC, one providing an eMMC interface and the other exporting an SD/SDIO interface. Compared to the Loongson-2K1000's MMC controllers, their internals are similar, except that we use an internally exclusive DMA engine instead of an externally shared APBDMA engine. Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> --- drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++ 1 file changed, 212 insertions(+) diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c index 872f5dc21b21..75144221a821 100644 --- a/drivers/mmc/host/loongson2-mmc.c +++ b/drivers/mmc/host/loongson2-mmc.c @@ -44,6 +44,18 @@ #define LOONGSON2_MMC_REG_DATA 0x40 /* Data Register */ #define LOONGSON2_MMC_REG_IEN 0x64 /* Interrupt Enable Register */ +/* EMMC DLL Mode Registers */ +#define LOONGSON2_MMC_REG_DLLVAL 0xf0 /* DLL Master Lock-value Register */ +#define LOONGSON2_MMC_REG_DLLCTL 0xf4 /* DLL Control Register */ +#define LOONGSON2_MMC_REG_DELAY 0xf8 /* DLL Delayed Parameter Register */ +#define LOONGSON2_MMC_REG_SEL 0xfc /* Bus Mode Selection Register */ + +/* Exclusive DMA R/W Registers */ +#define LOONGSON2_MMC_REG_WDMA_LO 0x400 +#define LOONGSON2_MMC_REG_WDMA_HI 0x404 +#define LOONGSON2_MMC_REG_RDMA_LO 0x800 +#define LOONGSON2_MMC_REG_RDMA_HI 0x804 + /* Bitfields of control register */ #define LOONGSON2_MMC_CTL_ENCLK BIT(0) #define LOONGSON2_MMC_CTL_EXTCLK BIT(1) @@ -109,6 +121,9 @@ #define LOONGSON2_MMC_DSTS_RESUME BIT(15) #define LOONGSON2_MMC_DSTS_SUSPEND BIT(16) +/* Bitfields of FIFO Status Register */ +#define LOONGSON2_MMC_FSTS_TXFULL BIT(11) + /* Bitfields of interrupt register */ #define LOONGSON2_MMC_INT_DFIN BIT(0) #define LOONGSON2_MMC_INT_DTIMEOUT BIT(1) @@ -136,6 +151,41 @@ #define LOONGSON2_MMC_IEN_ALL GENMASK(9, 0) #define LOONGSON2_MMC_INT_CLEAR GENMASK(9, 0) +/* Bitfields of DLL master lock-value register */ +#define LOONGSON2_MMC_DLLVAL_DONE BIT(8) + +/* Bitfields of DLL control register */ +#define LOONGSON2_MMC_DLLCTL_TIME GENMASK(7, 0) +#define LOONGSON2_MMC_DLLCTL_INCRE GENMASK(15, 8) +#define LOONGSON2_MMC_DLLCTL_START GENMASK(23, 16) +#define LOONGSON2_MMC_DLLCTL_CLK_MODE BIT(24) +#define LOONGSON2_MMC_DLLCTL_START_BIT BIT(25) +#define LOONGSON2_MMC_DLLCTL_TIME_BPASS GENMASK(29, 26) + +#define LOONGSON2_MMC_DELAY_PAD GENMASK(7, 0) +#define LOONGSON2_MMC_DELAY_RD GENMASK(15, 8) + +#define LOONGSON2_MMC_SEL_DATA BIT(0) /* 0: SDR, 1: DDR */ +#define LOONGSON2_MMC_SEL_BUS BIT(0) /* 0: EMMC, 1: SDIO */ + +/* Internal dma controller registers */ + +/* Bitfields of Global Configuration Register */ +#define LOONGSON2_MMC_DMA_64BIT_EN BIT(0) /* 1: 64 bit support */ +#define LOONGSON2_MMC_DMA_UNCOHERENT_EN BIT(1) /* 0: cache, 1: uncache */ +#define LOONGSON2_MMC_DMA_ASK_VALID BIT(2) +#define LOONGSON2_MMC_DMA_START BIT(3) /* DMA start operation */ +#define LOONGSON2_MMC_DMA_STOP BIT(4) /* DMA stop operation */ +#define LOONGSON2_MMC_DMA_CONFIG_MASK GENMASK_ULL(4, 0) /* DMA controller config bits mask */ + +/* Bitfields of ndesc_addr field of HW descriptor */ +#define LOONGSON2_MMC_DMA_DESC_EN BIT(0) /*1: The next descriptor is valid */ +#define LOONGSON2_MMC_DMA_DESC_ADDR_LOW GENMASK(31, 1) + +/* Bitfields of cmd field of HW descriptor */ +#define LOONGSON2_MMC_DMA_INT BIT(1) /* Enable DMA interrupts */ +#define LOONGSON2_MMC_DMA_DATA_DIR BIT(12) /* 1: write to device, 0: read from device */ + /* Loongson-2K1000 SDIO2 DMA routing register */ #define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) #define LS2K1000_DMA0_CONF 0x0 @@ -180,6 +230,8 @@ struct loongson2_mmc_host { struct resource *res; struct clk *clk; u64 rate; + void *sg_cpu; + dma_addr_t sg_dma; int dma_complete; struct dma_chan *chan; int cmd_is_stop; @@ -192,6 +244,7 @@ struct loongson2_mmc_host { struct loongson2_mmc_pdata { const struct regmap_config *regmap_config; void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd); + void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd); int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev); int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data); void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev); @@ -282,6 +335,9 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc) return; } + if (host->pdata->fix_data_timeout) + host->pdata->fix_data_timeout(host, cmd); + loongson2_mmc_send_command(host, cmd); /* Fix deselect card */ @@ -426,6 +482,36 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *devid) return IRQ_WAKE_THREAD; } +static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host) +{ + u32 val, pad_delay, delay, ret; + + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL, + LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA); + + val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8) + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1) + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1) + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1) + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1) + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf); + + regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val); + + ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val, + (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000); + if (ret < 0) + return; + + regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val); + pad_delay = FIELD_GET(GENMASK(7, 1), val); + + delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay) + | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1); + + regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay); +} + static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios) { u32 pre; @@ -438,6 +524,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL, LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK); + + /* EMMC DLL mode setting */ + if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52) + loongson2_mmc_dll_mode_init(host); } static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) @@ -655,6 +745,127 @@ static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = { .release_dma = loongson2_mmc_release_external_dma, }; +static const struct regmap_config ls2k2000_mmc_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = LOONGSON2_MMC_REG_RDMA_HI, +}; + +static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host, + struct mmc_command *cmd) +{ + struct scatterlist *sg; + u32 *data; + int i, j; + + if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC) + return; + + for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) { + data = sg_virt(&sg[i]); + for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++) + data[j] = bitrev8x4(data[j]); + } +} + +/* + * This is a controller hardware defect. Single/multiple block write commands + * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt + * will occur. + */ +static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host, + struct mmc_command *cmd) +{ + int val; + + if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) + return; + + regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val, + (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500); +} + +static int loongson2_mmc_prepare_internal_dma(struct loongson2_mmc_host *host, + struct mmc_data *data) +{ + struct loongson2_dma_desc *pdes = (struct loongson2_dma_desc *)host->sg_cpu; + struct mmc_host *mmc = mmc_from_priv(host); + dma_addr_t next_desc = host->sg_dma; + struct scatterlist *sg; + int reg_lo, reg_hi; + u64 dma_order; + int i, ret; + + ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, + mmc_get_dma_dir(data)); + if (!ret) + return -ENOMEM; + + for_each_sg(data->sg, sg, data->sg_len, i) { + pdes[i].len = sg_dma_len(&sg[i]) / 4; + pdes[i].step_len = 0; + pdes[i].step_times = 1; + pdes[i].mem_addr = lower_32_bits(sg_dma_address(&sg[i])); + pdes[i].high_mem_addr = upper_32_bits(sg_dma_address(&sg[i])); + pdes[i].apb_addr = host->res->start + LOONGSON2_MMC_REG_DATA; + pdes[i].cmd = LOONGSON2_MMC_DMA_INT; + + if (data->flags & MMC_DATA_READ) { + reg_lo = LOONGSON2_MMC_REG_RDMA_LO; + reg_hi = LOONGSON2_MMC_REG_RDMA_HI; + } else { + pdes[i].cmd |= LOONGSON2_MMC_DMA_DATA_DIR; + reg_lo = LOONGSON2_MMC_REG_WDMA_LO; + reg_hi = LOONGSON2_MMC_REG_WDMA_HI; + } + + next_desc += sizeof(struct loongson2_dma_desc); + pdes[i].ndesc_addr = lower_32_bits(next_desc) | + LOONGSON2_MMC_DMA_DESC_EN; + pdes[i].high_ndesc_addr = upper_32_bits(next_desc); + } + + /* Setting the last descriptor enable bit */ + pdes[i - 1].ndesc_addr &= ~LOONGSON2_MMC_DMA_DESC_EN; + + dma_order = (host->sg_dma & ~LOONGSON2_MMC_DMA_CONFIG_MASK) | + LOONGSON2_MMC_DMA_64BIT_EN | + LOONGSON2_MMC_DMA_START; + + regmap_write(host->regmap, reg_hi, upper_32_bits(dma_order)); + regmap_write(host->regmap, reg_lo, lower_32_bits(dma_order)); + + return 0; +} + +static int loongson2_mmc_set_internal_dma(struct loongson2_mmc_host *host, + struct platform_device *pdev) +{ + host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, + &host->sg_dma, GFP_KERNEL); + if (!host->sg_cpu) + return -ENOMEM; + + memset(host->sg_cpu, 0, PAGE_SIZE); + return 0; +} + +static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host, + struct device *dev) +{ + dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); +} + +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = { + .regmap_config = &ls2k2000_mmc_regmap_config, + .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data, + .fix_data_timeout = ls2k2000_mmc_fix_data_timeout, + .setting_dma = loongson2_mmc_set_internal_dma, + .prepare_dma = loongson2_mmc_prepare_internal_dma, + .release_dma = loongson2_mmc_release_internal_dma, +}; + static int loongson2_mmc_resource_request(struct platform_device *pdev, struct loongson2_mmc_host *host) { @@ -777,6 +988,7 @@ static void loongson2_mmc_remove(struct platform_device *pdev) static const struct of_device_id loongson2_mmc_of_ids[] = { { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata }, { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata }, + { .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_mmc_pdata }, { }, }; MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids); -- 2.47.1 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver 2025-06-18 8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou @ 2025-06-19 4:02 ` Huacai Chen 2025-06-20 3:06 ` Binbin Zhou 2025-06-23 11:24 ` Ulf Hansson 1 sibling, 1 reply; 11+ messages in thread From: Huacai Chen @ 2025-06-19 4:02 UTC (permalink / raw) To: Binbin Zhou Cc: Binbin Zhou, Huacai Chen, Ulf Hansson, Xuerui Wang, loongarch, linux-mmc, wanghongliang Hi, Binbin, On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote: > > This patch describes the two MMC controllers of the Loongson-2K2000 SoC, > one providing an eMMC interface and the other exporting an SD/SDIO > interface. > > Compared to the Loongson-2K1000's MMC controllers, their internals are > similar, except that we use an internally exclusive DMA engine instead of > an externally shared APBDMA engine. > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> > --- > drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++ > 1 file changed, 212 insertions(+) > > diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c > index 872f5dc21b21..75144221a821 100644 > --- a/drivers/mmc/host/loongson2-mmc.c > +++ b/drivers/mmc/host/loongson2-mmc.c > @@ -44,6 +44,18 @@ > #define LOONGSON2_MMC_REG_DATA 0x40 /* Data Register */ > #define LOONGSON2_MMC_REG_IEN 0x64 /* Interrupt Enable Register */ > > +/* EMMC DLL Mode Registers */ > +#define LOONGSON2_MMC_REG_DLLVAL 0xf0 /* DLL Master Lock-value Register */ > +#define LOONGSON2_MMC_REG_DLLCTL 0xf4 /* DLL Control Register */ > +#define LOONGSON2_MMC_REG_DELAY 0xf8 /* DLL Delayed Parameter Register */ > +#define LOONGSON2_MMC_REG_SEL 0xfc /* Bus Mode Selection Register */ > + > +/* Exclusive DMA R/W Registers */ > +#define LOONGSON2_MMC_REG_WDMA_LO 0x400 > +#define LOONGSON2_MMC_REG_WDMA_HI 0x404 > +#define LOONGSON2_MMC_REG_RDMA_LO 0x800 > +#define LOONGSON2_MMC_REG_RDMA_HI 0x804 > + > /* Bitfields of control register */ > #define LOONGSON2_MMC_CTL_ENCLK BIT(0) > #define LOONGSON2_MMC_CTL_EXTCLK BIT(1) > @@ -109,6 +121,9 @@ > #define LOONGSON2_MMC_DSTS_RESUME BIT(15) > #define LOONGSON2_MMC_DSTS_SUSPEND BIT(16) > > +/* Bitfields of FIFO Status Register */ > +#define LOONGSON2_MMC_FSTS_TXFULL BIT(11) > + > /* Bitfields of interrupt register */ > #define LOONGSON2_MMC_INT_DFIN BIT(0) > #define LOONGSON2_MMC_INT_DTIMEOUT BIT(1) > @@ -136,6 +151,41 @@ > #define LOONGSON2_MMC_IEN_ALL GENMASK(9, 0) > #define LOONGSON2_MMC_INT_CLEAR GENMASK(9, 0) > > +/* Bitfields of DLL master lock-value register */ > +#define LOONGSON2_MMC_DLLVAL_DONE BIT(8) > + > +/* Bitfields of DLL control register */ > +#define LOONGSON2_MMC_DLLCTL_TIME GENMASK(7, 0) > +#define LOONGSON2_MMC_DLLCTL_INCRE GENMASK(15, 8) > +#define LOONGSON2_MMC_DLLCTL_START GENMASK(23, 16) > +#define LOONGSON2_MMC_DLLCTL_CLK_MODE BIT(24) > +#define LOONGSON2_MMC_DLLCTL_START_BIT BIT(25) > +#define LOONGSON2_MMC_DLLCTL_TIME_BPASS GENMASK(29, 26) > + > +#define LOONGSON2_MMC_DELAY_PAD GENMASK(7, 0) > +#define LOONGSON2_MMC_DELAY_RD GENMASK(15, 8) > + > +#define LOONGSON2_MMC_SEL_DATA BIT(0) /* 0: SDR, 1: DDR */ > +#define LOONGSON2_MMC_SEL_BUS BIT(0) /* 0: EMMC, 1: SDIO */ > + > +/* Internal dma controller registers */ > + > +/* Bitfields of Global Configuration Register */ > +#define LOONGSON2_MMC_DMA_64BIT_EN BIT(0) /* 1: 64 bit support */ > +#define LOONGSON2_MMC_DMA_UNCOHERENT_EN BIT(1) /* 0: cache, 1: uncache */ > +#define LOONGSON2_MMC_DMA_ASK_VALID BIT(2) > +#define LOONGSON2_MMC_DMA_START BIT(3) /* DMA start operation */ > +#define LOONGSON2_MMC_DMA_STOP BIT(4) /* DMA stop operation */ > +#define LOONGSON2_MMC_DMA_CONFIG_MASK GENMASK_ULL(4, 0) /* DMA controller config bits mask */ > + > +/* Bitfields of ndesc_addr field of HW descriptor */ > +#define LOONGSON2_MMC_DMA_DESC_EN BIT(0) /*1: The next descriptor is valid */ > +#define LOONGSON2_MMC_DMA_DESC_ADDR_LOW GENMASK(31, 1) > + > +/* Bitfields of cmd field of HW descriptor */ > +#define LOONGSON2_MMC_DMA_INT BIT(1) /* Enable DMA interrupts */ > +#define LOONGSON2_MMC_DMA_DATA_DIR BIT(12) /* 1: write to device, 0: read from device */ > + > /* Loongson-2K1000 SDIO2 DMA routing register */ > #define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) > #define LS2K1000_DMA0_CONF 0x0 > @@ -180,6 +230,8 @@ struct loongson2_mmc_host { > struct resource *res; > struct clk *clk; > u64 rate; > + void *sg_cpu; > + dma_addr_t sg_dma; > int dma_complete; > struct dma_chan *chan; > int cmd_is_stop; > @@ -192,6 +244,7 @@ struct loongson2_mmc_host { > struct loongson2_mmc_pdata { > const struct regmap_config *regmap_config; > void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd); > + void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd); > int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev); > int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data); > void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev); > @@ -282,6 +335,9 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc) > return; > } > > + if (host->pdata->fix_data_timeout) > + host->pdata->fix_data_timeout(host, cmd); > + > loongson2_mmc_send_command(host, cmd); > > /* Fix deselect card */ > @@ -426,6 +482,36 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *devid) > return IRQ_WAKE_THREAD; > } > > +static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host) > +{ > + u32 val, pad_delay, delay, ret; > + > + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL, > + LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA); > + > + val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf); > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val); > + > + ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val, > + (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000); > + if (ret < 0) > + return; > + > + regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val); > + pad_delay = FIELD_GET(GENMASK(7, 1), val); > + > + delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay) > + | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1); > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay); > +} > + > static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios) > { > u32 pre; > @@ -438,6 +524,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io > > regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL, > LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK); > + > + /* EMMC DLL mode setting */ > + if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52) > + loongson2_mmc_dll_mode_init(host); > } > > static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) > @@ -655,6 +745,127 @@ static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = { > .release_dma = loongson2_mmc_release_external_dma, > }; > > +static const struct regmap_config ls2k2000_mmc_regmap_config = { > + .reg_bits = 32, > + .val_bits = 32, > + .reg_stride = 4, > + .max_register = LOONGSON2_MMC_REG_RDMA_HI, > +}; > + > +static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host, > + struct mmc_command *cmd) > +{ > + struct scatterlist *sg; > + u32 *data; > + int i, j; > + > + if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC) > + return; > + > + for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) { > + data = sg_virt(&sg[i]); > + for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++) > + data[j] = bitrev8x4(data[j]); > + } > +} > + > +/* > + * This is a controller hardware defect. Single/multiple block write commands > + * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt > + * will occur. > + */ > +static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host, > + struct mmc_command *cmd) > +{ > + int val; > + > + if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) > + return; > + > + regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val, > + (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500); > +} > + > +static int loongson2_mmc_prepare_internal_dma(struct loongson2_mmc_host *host, > + struct mmc_data *data) > +{ > + struct loongson2_dma_desc *pdes = (struct loongson2_dma_desc *)host->sg_cpu; > + struct mmc_host *mmc = mmc_from_priv(host); > + dma_addr_t next_desc = host->sg_dma; > + struct scatterlist *sg; > + int reg_lo, reg_hi; > + u64 dma_order; > + int i, ret; > + > + ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, > + mmc_get_dma_dir(data)); > + if (!ret) > + return -ENOMEM; > + > + for_each_sg(data->sg, sg, data->sg_len, i) { > + pdes[i].len = sg_dma_len(&sg[i]) / 4; > + pdes[i].step_len = 0; > + pdes[i].step_times = 1; > + pdes[i].mem_addr = lower_32_bits(sg_dma_address(&sg[i])); > + pdes[i].high_mem_addr = upper_32_bits(sg_dma_address(&sg[i])); > + pdes[i].apb_addr = host->res->start + LOONGSON2_MMC_REG_DATA; > + pdes[i].cmd = LOONGSON2_MMC_DMA_INT; > + > + if (data->flags & MMC_DATA_READ) { > + reg_lo = LOONGSON2_MMC_REG_RDMA_LO; > + reg_hi = LOONGSON2_MMC_REG_RDMA_HI; > + } else { > + pdes[i].cmd |= LOONGSON2_MMC_DMA_DATA_DIR; > + reg_lo = LOONGSON2_MMC_REG_WDMA_LO; > + reg_hi = LOONGSON2_MMC_REG_WDMA_HI; > + } > + > + next_desc += sizeof(struct loongson2_dma_desc); > + pdes[i].ndesc_addr = lower_32_bits(next_desc) | > + LOONGSON2_MMC_DMA_DESC_EN; > + pdes[i].high_ndesc_addr = upper_32_bits(next_desc); > + } > + > + /* Setting the last descriptor enable bit */ > + pdes[i - 1].ndesc_addr &= ~LOONGSON2_MMC_DMA_DESC_EN; > + > + dma_order = (host->sg_dma & ~LOONGSON2_MMC_DMA_CONFIG_MASK) | > + LOONGSON2_MMC_DMA_64BIT_EN | > + LOONGSON2_MMC_DMA_START; > + > + regmap_write(host->regmap, reg_hi, upper_32_bits(dma_order)); > + regmap_write(host->regmap, reg_lo, lower_32_bits(dma_order)); > + > + return 0; > +} > + > +static int loongson2_mmc_set_internal_dma(struct loongson2_mmc_host *host, > + struct platform_device *pdev) > +{ > + host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, > + &host->sg_dma, GFP_KERNEL); > + if (!host->sg_cpu) > + return -ENOMEM; > + > + memset(host->sg_cpu, 0, PAGE_SIZE); > + return 0; > +} > + > +static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host, > + struct device *dev) > +{ > + dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); > +} > + > +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = { > + .regmap_config = &ls2k2000_mmc_regmap_config, > + .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data, > + .fix_data_timeout = ls2k2000_mmc_fix_data_timeout, > + .setting_dma = loongson2_mmc_set_internal_dma, > + .prepare_dma = loongson2_mmc_prepare_internal_dma, > + .release_dma = loongson2_mmc_release_internal_dma, > +}; From the whole series we get: +static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = { + .regmap_config = &ls2k1000_mmc_regmap_config, + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, + .setting_dma = ls2k0500_mmc_set_external_dma, + .prepare_dma = loongson2_mmc_prepare_external_dma, + .release_dma = loongson2_mmc_release_external_dma, +}; +static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = { + .regmap_config = &ls2k1000_mmc_regmap_config, + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, + .setting_dma = ls2k1000_mmc_set_external_dma, + .prepare_dma = loongson2_mmc_prepare_external_dma, + .release_dma = loongson2_mmc_release_external_dma, +}; +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = { + .regmap_config = &ls2k2000_mmc_regmap_config, + .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data, + .fix_data_timeout = ls2k2000_mmc_fix_data_timeout, + .setting_dma = loongson2_mmc_set_internal_dma, + .prepare_dma = loongson2_mmc_prepare_internal_dma, + .release_dma = loongson2_mmc_release_internal_dma, +}; The prefix of function names are confusing, can we rename them better? Huacai > + > static int loongson2_mmc_resource_request(struct platform_device *pdev, > struct loongson2_mmc_host *host) > { > @@ -777,6 +988,7 @@ static void loongson2_mmc_remove(struct platform_device *pdev) > static const struct of_device_id loongson2_mmc_of_ids[] = { > { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata }, > { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata }, > + { .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_mmc_pdata }, > { }, > }; > MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids); > -- > 2.47.1 > > ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver 2025-06-19 4:02 ` Huacai Chen @ 2025-06-20 3:06 ` Binbin Zhou 0 siblings, 0 replies; 11+ messages in thread From: Binbin Zhou @ 2025-06-20 3:06 UTC (permalink / raw) To: Huacai Chen Cc: Binbin Zhou, Huacai Chen, Ulf Hansson, Xuerui Wang, loongarch, linux-mmc, wanghongliang On Thu, Jun 19, 2025 at 12:03 PM Huacai Chen <chenhuacai@kernel.org> wrote: > > Hi, Binbin, > > On Wed, Jun 18, 2025 at 4:08 PM Binbin Zhou <zhoubinbin@loongson.cn> wrote: > > > > This patch describes the two MMC controllers of the Loongson-2K2000 SoC, > > one providing an eMMC interface and the other exporting an SD/SDIO > > interface. > > > > Compared to the Loongson-2K1000's MMC controllers, their internals are > > similar, except that we use an internally exclusive DMA engine instead of > > an externally shared APBDMA engine. > > > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> > > --- > > drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++ > > 1 file changed, 212 insertions(+) > > > > diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c > > index 872f5dc21b21..75144221a821 100644 > > --- a/drivers/mmc/host/loongson2-mmc.c > > +++ b/drivers/mmc/host/loongson2-mmc.c > > @@ -44,6 +44,18 @@ > > #define LOONGSON2_MMC_REG_DATA 0x40 /* Data Register */ > > #define LOONGSON2_MMC_REG_IEN 0x64 /* Interrupt Enable Register */ > > > > +/* EMMC DLL Mode Registers */ > > +#define LOONGSON2_MMC_REG_DLLVAL 0xf0 /* DLL Master Lock-value Register */ > > +#define LOONGSON2_MMC_REG_DLLCTL 0xf4 /* DLL Control Register */ > > +#define LOONGSON2_MMC_REG_DELAY 0xf8 /* DLL Delayed Parameter Register */ > > +#define LOONGSON2_MMC_REG_SEL 0xfc /* Bus Mode Selection Register */ > > + > > +/* Exclusive DMA R/W Registers */ > > +#define LOONGSON2_MMC_REG_WDMA_LO 0x400 > > +#define LOONGSON2_MMC_REG_WDMA_HI 0x404 > > +#define LOONGSON2_MMC_REG_RDMA_LO 0x800 > > +#define LOONGSON2_MMC_REG_RDMA_HI 0x804 > > + > > /* Bitfields of control register */ > > #define LOONGSON2_MMC_CTL_ENCLK BIT(0) > > #define LOONGSON2_MMC_CTL_EXTCLK BIT(1) > > @@ -109,6 +121,9 @@ > > #define LOONGSON2_MMC_DSTS_RESUME BIT(15) > > #define LOONGSON2_MMC_DSTS_SUSPEND BIT(16) > > > > +/* Bitfields of FIFO Status Register */ > > +#define LOONGSON2_MMC_FSTS_TXFULL BIT(11) > > + > > /* Bitfields of interrupt register */ > > #define LOONGSON2_MMC_INT_DFIN BIT(0) > > #define LOONGSON2_MMC_INT_DTIMEOUT BIT(1) > > @@ -136,6 +151,41 @@ > > #define LOONGSON2_MMC_IEN_ALL GENMASK(9, 0) > > #define LOONGSON2_MMC_INT_CLEAR GENMASK(9, 0) > > > > +/* Bitfields of DLL master lock-value register */ > > +#define LOONGSON2_MMC_DLLVAL_DONE BIT(8) > > + > > +/* Bitfields of DLL control register */ > > +#define LOONGSON2_MMC_DLLCTL_TIME GENMASK(7, 0) > > +#define LOONGSON2_MMC_DLLCTL_INCRE GENMASK(15, 8) > > +#define LOONGSON2_MMC_DLLCTL_START GENMASK(23, 16) > > +#define LOONGSON2_MMC_DLLCTL_CLK_MODE BIT(24) > > +#define LOONGSON2_MMC_DLLCTL_START_BIT BIT(25) > > +#define LOONGSON2_MMC_DLLCTL_TIME_BPASS GENMASK(29, 26) > > + > > +#define LOONGSON2_MMC_DELAY_PAD GENMASK(7, 0) > > +#define LOONGSON2_MMC_DELAY_RD GENMASK(15, 8) > > + > > +#define LOONGSON2_MMC_SEL_DATA BIT(0) /* 0: SDR, 1: DDR */ > > +#define LOONGSON2_MMC_SEL_BUS BIT(0) /* 0: EMMC, 1: SDIO */ > > + > > +/* Internal dma controller registers */ > > + > > +/* Bitfields of Global Configuration Register */ > > +#define LOONGSON2_MMC_DMA_64BIT_EN BIT(0) /* 1: 64 bit support */ > > +#define LOONGSON2_MMC_DMA_UNCOHERENT_EN BIT(1) /* 0: cache, 1: uncache */ > > +#define LOONGSON2_MMC_DMA_ASK_VALID BIT(2) > > +#define LOONGSON2_MMC_DMA_START BIT(3) /* DMA start operation */ > > +#define LOONGSON2_MMC_DMA_STOP BIT(4) /* DMA stop operation */ > > +#define LOONGSON2_MMC_DMA_CONFIG_MASK GENMASK_ULL(4, 0) /* DMA controller config bits mask */ > > + > > +/* Bitfields of ndesc_addr field of HW descriptor */ > > +#define LOONGSON2_MMC_DMA_DESC_EN BIT(0) /*1: The next descriptor is valid */ > > +#define LOONGSON2_MMC_DMA_DESC_ADDR_LOW GENMASK(31, 1) > > + > > +/* Bitfields of cmd field of HW descriptor */ > > +#define LOONGSON2_MMC_DMA_INT BIT(1) /* Enable DMA interrupts */ > > +#define LOONGSON2_MMC_DMA_DATA_DIR BIT(12) /* 1: write to device, 0: read from device */ > > + > > /* Loongson-2K1000 SDIO2 DMA routing register */ > > #define LS2K1000_SDIO_DMA_MASK GENMASK(17, 15) > > #define LS2K1000_DMA0_CONF 0x0 > > @@ -180,6 +230,8 @@ struct loongson2_mmc_host { > > struct resource *res; > > struct clk *clk; > > u64 rate; > > + void *sg_cpu; > > + dma_addr_t sg_dma; > > int dma_complete; > > struct dma_chan *chan; > > int cmd_is_stop; > > @@ -192,6 +244,7 @@ struct loongson2_mmc_host { > > struct loongson2_mmc_pdata { > > const struct regmap_config *regmap_config; > > void (*reorder_cmd_data)(struct loongson2_mmc_host *host, struct mmc_command *cmd); > > + void (*fix_data_timeout)(struct loongson2_mmc_host *host, struct mmc_command *cmd); > > int (*setting_dma)(struct loongson2_mmc_host *host, struct platform_device *pdev); > > int (*prepare_dma)(struct loongson2_mmc_host *host, struct mmc_data *data); > > void (*release_dma)(struct loongson2_mmc_host *host, struct device *dev); > > @@ -282,6 +335,9 @@ static void loongson2_mmc_send_request(struct mmc_host *mmc) > > return; > > } > > > > + if (host->pdata->fix_data_timeout) > > + host->pdata->fix_data_timeout(host, cmd); > > + > > loongson2_mmc_send_command(host, cmd); > > > > /* Fix deselect card */ > > @@ -426,6 +482,36 @@ static irqreturn_t loongson2_mmc_irq(int irq, void *devid) > > return IRQ_WAKE_THREAD; > > } > > > > +static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host) > > +{ > > + u32 val, pad_delay, delay, ret; > > + > > + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL, > > + LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA); > > + > > + val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8) > > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1) > > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1) > > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1) > > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1) > > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf); > > + > > + regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val); > > + > > + ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val, > > + (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000); > > + if (ret < 0) > > + return; > > + > > + regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val); > > + pad_delay = FIELD_GET(GENMASK(7, 1), val); > > + > > + delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay) > > + | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1); > > + > > + regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay); > > +} > > + > > static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_ios *ios) > > { > > u32 pre; > > @@ -438,6 +524,10 @@ static void loongson2_mmc_set_clk(struct loongson2_mmc_host *host, struct mmc_io > > > > regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_CTL, > > LOONGSON2_MMC_CTL_ENCLK, LOONGSON2_MMC_CTL_ENCLK); > > + > > + /* EMMC DLL mode setting */ > > + if (ios->timing == MMC_TIMING_UHS_DDR50 || ios->timing == MMC_TIMING_MMC_DDR52) > > + loongson2_mmc_dll_mode_init(host); > > } > > > > static void loongson2_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) > > @@ -655,6 +745,127 @@ static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = { > > .release_dma = loongson2_mmc_release_external_dma, > > }; > > > > +static const struct regmap_config ls2k2000_mmc_regmap_config = { > > + .reg_bits = 32, > > + .val_bits = 32, > > + .reg_stride = 4, > > + .max_register = LOONGSON2_MMC_REG_RDMA_HI, > > +}; > > + > > +static void ls2k2000_mmc_reorder_cmd_data(struct loongson2_mmc_host *host, > > + struct mmc_command *cmd) > > +{ > > + struct scatterlist *sg; > > + u32 *data; > > + int i, j; > > + > > + if (cmd->opcode != SD_SWITCH || mmc_cmd_type(cmd) != MMC_CMD_ADTC) > > + return; > > + > > + for_each_sg(cmd->data->sg, sg, cmd->data->sg_len, i) { > > + data = sg_virt(&sg[i]); > > + for (j = 0; j < (sg_dma_len(&sg[i]) / 4); j++) > > + data[j] = bitrev8x4(data[j]); > > + } > > +} > > + > > +/* > > + * This is a controller hardware defect. Single/multiple block write commands > > + * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt > > + * will occur. > > + */ > > +static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host, > > + struct mmc_command *cmd) > > +{ > > + int val; > > + > > + if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) > > + return; > > + > > + regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val, > > + (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500); > > +} > > + > > +static int loongson2_mmc_prepare_internal_dma(struct loongson2_mmc_host *host, > > + struct mmc_data *data) > > +{ > > + struct loongson2_dma_desc *pdes = (struct loongson2_dma_desc *)host->sg_cpu; > > + struct mmc_host *mmc = mmc_from_priv(host); > > + dma_addr_t next_desc = host->sg_dma; > > + struct scatterlist *sg; > > + int reg_lo, reg_hi; > > + u64 dma_order; > > + int i, ret; > > + > > + ret = dma_map_sg(mmc_dev(mmc), data->sg, data->sg_len, > > + mmc_get_dma_dir(data)); > > + if (!ret) > > + return -ENOMEM; > > + > > + for_each_sg(data->sg, sg, data->sg_len, i) { > > + pdes[i].len = sg_dma_len(&sg[i]) / 4; > > + pdes[i].step_len = 0; > > + pdes[i].step_times = 1; > > + pdes[i].mem_addr = lower_32_bits(sg_dma_address(&sg[i])); > > + pdes[i].high_mem_addr = upper_32_bits(sg_dma_address(&sg[i])); > > + pdes[i].apb_addr = host->res->start + LOONGSON2_MMC_REG_DATA; > > + pdes[i].cmd = LOONGSON2_MMC_DMA_INT; > > + > > + if (data->flags & MMC_DATA_READ) { > > + reg_lo = LOONGSON2_MMC_REG_RDMA_LO; > > + reg_hi = LOONGSON2_MMC_REG_RDMA_HI; > > + } else { > > + pdes[i].cmd |= LOONGSON2_MMC_DMA_DATA_DIR; > > + reg_lo = LOONGSON2_MMC_REG_WDMA_LO; > > + reg_hi = LOONGSON2_MMC_REG_WDMA_HI; > > + } > > + > > + next_desc += sizeof(struct loongson2_dma_desc); > > + pdes[i].ndesc_addr = lower_32_bits(next_desc) | > > + LOONGSON2_MMC_DMA_DESC_EN; > > + pdes[i].high_ndesc_addr = upper_32_bits(next_desc); > > + } > > + > > + /* Setting the last descriptor enable bit */ > > + pdes[i - 1].ndesc_addr &= ~LOONGSON2_MMC_DMA_DESC_EN; > > + > > + dma_order = (host->sg_dma & ~LOONGSON2_MMC_DMA_CONFIG_MASK) | > > + LOONGSON2_MMC_DMA_64BIT_EN | > > + LOONGSON2_MMC_DMA_START; > > + > > + regmap_write(host->regmap, reg_hi, upper_32_bits(dma_order)); > > + regmap_write(host->regmap, reg_lo, lower_32_bits(dma_order)); > > + > > + return 0; > > +} > > + > > +static int loongson2_mmc_set_internal_dma(struct loongson2_mmc_host *host, > > + struct platform_device *pdev) > > +{ > > + host->sg_cpu = dma_alloc_coherent(&pdev->dev, PAGE_SIZE, > > + &host->sg_dma, GFP_KERNEL); > > + if (!host->sg_cpu) > > + return -ENOMEM; > > + > > + memset(host->sg_cpu, 0, PAGE_SIZE); > > + return 0; > > +} > > + > > +static void loongson2_mmc_release_internal_dma(struct loongson2_mmc_host *host, > > + struct device *dev) > > +{ > > + dma_free_coherent(dev, PAGE_SIZE, host->sg_cpu, host->sg_dma); > > +} > > + > > +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = { > > + .regmap_config = &ls2k2000_mmc_regmap_config, > > + .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data, > > + .fix_data_timeout = ls2k2000_mmc_fix_data_timeout, > > + .setting_dma = loongson2_mmc_set_internal_dma, > > + .prepare_dma = loongson2_mmc_prepare_internal_dma, > > + .release_dma = loongson2_mmc_release_internal_dma, > > +}; > From the whole series we get: > > +static struct loongson2_mmc_pdata ls2k0500_mmc_pdata = { > + .regmap_config = &ls2k1000_mmc_regmap_config, > + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, > + .setting_dma = ls2k0500_mmc_set_external_dma, > + .prepare_dma = loongson2_mmc_prepare_external_dma, > + .release_dma = loongson2_mmc_release_external_dma, > +}; > > +static struct loongson2_mmc_pdata ls2k1000_mmc_pdata = { > + .regmap_config = &ls2k1000_mmc_regmap_config, > + .reorder_cmd_data = loongson2_mmc_reorder_cmd_data, > + .setting_dma = ls2k1000_mmc_set_external_dma, > + .prepare_dma = loongson2_mmc_prepare_external_dma, > + .release_dma = loongson2_mmc_release_external_dma, > +}; > > +static struct loongson2_mmc_pdata ls2k2000_mmc_pdata = { > + .regmap_config = &ls2k2000_mmc_regmap_config, > + .reorder_cmd_data = ls2k2000_mmc_reorder_cmd_data, > + .fix_data_timeout = ls2k2000_mmc_fix_data_timeout, > + .setting_dma = loongson2_mmc_set_internal_dma, > + .prepare_dma = loongson2_mmc_prepare_internal_dma, > + .release_dma = loongson2_mmc_release_internal_dma, > +}; > > The prefix of function names are confusing, can we rename them better? OK, for regmap_config, reorder_cmd_data and reorder_cmd_data, I will use ls2k as a prefix. ls2k1000_mmc_regmap_config -> ls2k0500_mmc_regmap_config loongson2_mmc_reorder_cmd_data -> ls2k0500_mmc_reorder_cmd_data. loongson2_mmc_set_internal_dma -> ls2k2000_mmc_set_internal_dma > > > Huacai > > > > > > + > > static int loongson2_mmc_resource_request(struct platform_device *pdev, > > struct loongson2_mmc_host *host) > > { > > @@ -777,6 +988,7 @@ static void loongson2_mmc_remove(struct platform_device *pdev) > > static const struct of_device_id loongson2_mmc_of_ids[] = { > > { .compatible = "loongson,ls2k0500-mmc", .data = &ls2k0500_mmc_pdata }, > > { .compatible = "loongson,ls2k1000-mmc", .data = &ls2k1000_mmc_pdata }, > > + { .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_mmc_pdata }, > > { }, > > }; > > MODULE_DEVICE_TABLE(of, loongson2_mmc_of_ids); > > -- > > 2.47.1 > > > > -- Thanks. Binbin ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver 2025-06-18 8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou 2025-06-19 4:02 ` Huacai Chen @ 2025-06-23 11:24 ` Ulf Hansson 1 sibling, 0 replies; 11+ messages in thread From: Ulf Hansson @ 2025-06-23 11:24 UTC (permalink / raw) To: Binbin Zhou Cc: Binbin Zhou, Huacai Chen, Huacai Chen, Xuerui Wang, loongarch, linux-mmc, wanghongliang On Wed, 18 Jun 2025 at 10:08, Binbin Zhou <zhoubinbin@loongson.cn> wrote: > > This patch describes the two MMC controllers of the Loongson-2K2000 SoC, > one providing an eMMC interface and the other exporting an SD/SDIO > interface. > > Compared to the Loongson-2K1000's MMC controllers, their internals are > similar, except that we use an internally exclusive DMA engine instead of > an externally shared APBDMA engine. > > Signed-off-by: Binbin Zhou <zhoubinbin@loongson.cn> > --- > drivers/mmc/host/loongson2-mmc.c | 212 +++++++++++++++++++++++++++++++ > 1 file changed, 212 insertions(+) > > diff --git a/drivers/mmc/host/loongson2-mmc.c b/drivers/mmc/host/loongson2-mmc.c > index 872f5dc21b21..75144221a821 100644 > --- a/drivers/mmc/host/loongson2-mmc.c > +++ b/drivers/mmc/host/loongson2-mmc.c [...] > +static void loongson2_mmc_dll_mode_init(struct loongson2_mmc_host *host) > +{ > + u32 val, pad_delay, delay, ret; > + > + regmap_update_bits(host->regmap, LOONGSON2_MMC_REG_SEL, > + LOONGSON2_MMC_SEL_DATA, LOONGSON2_MMC_SEL_DATA); > + > + val = FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME, 0xc8) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_INCRE, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_CLK_MODE, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_START_BIT, 0x1) > + | FIELD_PREP(LOONGSON2_MMC_DLLCTL_TIME_BPASS, 0xf); > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_DLLCTL, val); > + > + ret = regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_DLLVAL, val, > + (val & LOONGSON2_MMC_DLLVAL_DONE), 0, 4000); Please use a define to specify the 4000us timeout. > + if (ret < 0) > + return; > + > + regmap_read(host->regmap, LOONGSON2_MMC_REG_DLLVAL, &val); > + pad_delay = FIELD_GET(GENMASK(7, 1), val); > + > + delay = FIELD_PREP(LOONGSON2_MMC_DELAY_PAD, pad_delay) > + | FIELD_PREP(LOONGSON2_MMC_DELAY_RD, pad_delay + 1); > + > + regmap_write(host->regmap, LOONGSON2_MMC_REG_DELAY, delay); > +} [...] > +/* > + * This is a controller hardware defect. Single/multiple block write commands > + * must be sent after the TX FULL flag is set, otherwise a data timeout interrupt > + * will occur. > + */ > +static void ls2k2000_mmc_fix_data_timeout(struct loongson2_mmc_host *host, > + struct mmc_command *cmd) > +{ > + int val; > + > + if (cmd->opcode != MMC_WRITE_BLOCK && cmd->opcode != MMC_WRITE_MULTIPLE_BLOCK) > + return; > + > + regmap_read_poll_timeout(host->regmap, LOONGSON2_MMC_REG_FSTS, val, > + (val & LOONGSON2_MMC_FSTS_TXFULL), 0, 500); Ditto. [...] Kind regards Uffe ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2025-06-23 11:24 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-06-18 8:07 [PATCH v3 0/4] LoongArch: Introduce the Loongson-2K MMC host controller driver Binbin Zhou 2025-06-18 8:07 ` [PATCH v3 1/4] dt-bindings: mmc: Add Loongson-2K SD/SDIO/eMMC controller binding Binbin Zhou 2025-06-18 13:44 ` Krzysztof Kozlowski 2025-06-18 8:07 ` [PATCH v3 2/4] mmc: loongson2: Add Loongson-2K SD/SDIO controller driver Binbin Zhou 2025-06-19 4:02 ` Huacai Chen 2025-06-20 13:20 ` kernel test robot 2025-06-18 8:07 ` [PATCH v3 3/4] dt-bindings: mmc: loongson,ls2k0500-mmc: Add compatible for Loongson-2K2000 Binbin Zhou 2025-06-18 8:08 ` [PATCH v3 4/4] mmc: loongson2: Add Loongson-2K2000 SD/SDIO/eMMC controller driver Binbin Zhou 2025-06-19 4:02 ` Huacai Chen 2025-06-20 3:06 ` Binbin Zhou 2025-06-23 11:24 ` Ulf Hansson
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox