Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/mmc/host/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 29 kB image not shown  

Quelle  loongson2-mmc.c   Sprache: C

 
// 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 */

/* 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)
#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 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)
#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)

/* 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 */

#define LOONGSON2_MMC_DLLVAL_TIMEOUT_US  4000
#define LOONGSON2_MMC_TXFULL_TIMEOUT_US  500

/* 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;
 u32 current_clk;
 void *sg_cpu;
 dma_addr_t sg_dma;
 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);
 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);
};

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;
 }

 if (host->pdata->fix_data_timeout)
  host->pdata->fix_data_timeout(host, cmd);

 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_dll_mode_init(struct loongson2_mmc_host *host)
{
 u32 val, pad_delay, delay;
 int 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,
           LOONGSON2_MMC_DLLVAL_TIMEOUT_US);
 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;

 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);

 /* 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)
{
 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 ls2k0500_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 ls2k0500_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  = &ls2k0500_mmc_regmap_config,
 .reorder_cmd_data = ls2k0500_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  = &ls2k0500_mmc_regmap_config,
 .reorder_cmd_data = ls2k0500_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 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,
     LOONGSON2_MMC_TXFULL_TIMEOUT_US);
}

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 ls2k2000_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  = ls2k2000_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)
{
 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 ret;

  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 },
 { .compatible = "loongson,ls2k2000-mmc", .data = &ls2k2000_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");

Messung V0.5
C=96 H=93 G=94

¤ Dauer der Verarbeitung: 0.8 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.