mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-08-25 09:29:31 +00:00
mmc: loongson2: Add Loongson-2K SD/SDIO controller driver
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> Reviewed-by: Huacai Chen <chenhuacai@loongson.cn> Link: https://lore.kernel.org/r/c0a9f0c0279d8e09165c6e2d694b0c35f7fc7e31.1750765495.git.zhoubinbin@loongson.cn Signed-off-by: Ulf Hansson <ulf.hansson@linaro.org>
This commit is contained in:
parent
fe62ee33fc
commit
2115772014
@ -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>
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
798
drivers/mmc/host/loongson2-mmc.c
Normal file
798
drivers/mmc/host/loongson2-mmc.c
Normal file
@ -0,0 +1,798 @@
|
||||
// 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_mmc_host {
|
||||
struct device *dev;
|
||||
struct mmc_request *mrq;
|
||||
struct regmap *regmap;
|
||||
struct resource *res;
|
||||
struct clk *clk;
|
||||
u32 current_clk;
|
||||
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->current_clk, 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; /* return, if failed turn on vmmc */
|
||||
}
|
||||
}
|
||||
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->current_clk = clk_get_rate(host->clk);
|
||||
} else {
|
||||
/* For ACPI, the clock is accessed via the clock-frequency attribute. */
|
||||
device_property_read_u32(dev, "clock-frequency", &host->current_clk);
|
||||
}
|
||||
|
||||
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->current_clk, 256);
|
||||
mmc->f_max = host->current_clk;
|
||||
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");
|
Loading…
Reference in New Issue
Block a user