* [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip @ 2010-06-19 5:08 Lars-Peter Clausen 2010-06-19 5:08 ` [PATCH v2 18/26] MMC: Add JZ4740 mmc driver Lars-Peter Clausen 2010-06-20 9:26 ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Thomas Bogendoerfer 0 siblings, 2 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-06-19 5:08 UTC (permalink / raw) To: Ralf Baechle Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Alessandro Zummo, Andrew Morton, Anton Vorontsov, David Brownell, David Woodhouse, Greg Kroah-Hartman, Liam Girdwood, Mark Brown, Paul Gortmaker, Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc, linux-mtd, linux-usb, lm-sensors, rtc-linux Foreword: Ralf suggested that it might be a good idea in order to allow for reasonable testing and to avoid build failures due to two-way dependencies in different parts of the kernel, that he applies all the patches once they have been acked by their respective maintainers, feeds them into -next and eventually sends the whole series to Linus. One exception will be the ASoC patches which will, due to major changes in the ASoC subsystem, go through the ASoC tree. So if you are a maintainer for one of the subsystem touched by this series and would rather see the patch going through your tree (given the patch is ok) please tell. This patch series adds support for the Ingenic JZ4740 System-on-a-Chip. The JZ4740 has a mostly MIPS32 compatible core (no on cpu timers) and many on chip peripherals like RTC, NAND, MMC, OHCI, UDC, ADC, I2C, SPI, AC97, I2S, I2S Codec, UART and LCD controller. The JZ4740 is mostly used in eBooks, PMPs and hand-held consoles. This series contains patches for the Qi Ben NanoNote clamshell device as the inital supported device. Changes since v1: There have been some minor changes since v1, mostly code cleanup and some functional changes. One bigger change is that there is now a MFD driver for the ADC core which does IRQ demultiplexing for the ADC unit and synchronizes access to shared registers between the different users of the ADC core. The patch adding a defconfig for the Qi LB60 has been dropped. A detailed list of changes is present in each patch. - Lars Cc: Alessandro Zummo <a.zummo@towertech.it> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Anton Vorontsov <cbouatmailru@gmail.com> Cc: David Brownell <dbrownell@users.sourceforge.net> Cc: David Woodhouse <dwmw2@infradead.org> Cc: Greg Kroah-Hartman <gregkh@suse.de> Cc: Liam Girdwood <lrg@slimlogic.co.uk> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com> Cc: Paul Gortmaker <p_gortmaker@yahoo.com> Cc: Samuel Ortiz <sameo@linux.intel.com> Cc: alsa-devel@alsa-project.org Cc: linux-fbdev@vger.kernel.org Cc: linux-mmc@vger.kernel.org Cc: linux-mtd@lists.infradead.org Cc: linux-usb@vger.kernel.org Cc: lm-sensors@lm-sensors.org Cc: rtc-linux@googlegroups.com Lars-Peter Clausen (26): MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip MIPS: jz4740: Add IRQ handler code MIPS: JZ4740: Add clock API support. MIPS: JZ4740: Add timer support MIPS: JZ4740: Add clocksource/clockevent support. MIPS: JZ4740: Add power-management and system reset support MIPS: JZ4740: Add setup code MIPS: JZ4740: Add gpio support MIPS: JZ4740: Add DMA support. MIPS: JZ4740: Add PWM support MIPS: JZ4740: Add serial support MIPS: JZ4740: Add prom support MIPS: JZ4740: Add platform devices MIPS: JZ4740: Add Kbuild files RTC: Add JZ4740 RTC driver fbdev: Add JZ4740 framebuffer driver MTD: Nand: Add JZ4740 NAND driver MMC: Add JZ4740 mmc driver USB: Add JZ4740 ohci support alsa: ASoC: Add JZ4740 codec driver alsa: ASoC: Add JZ4740 ASoC support MFD: Add JZ4740 ADC driver hwmon: Add JZ4740 ADC driver power: Add JZ4740 battery driver. MIPS: JZ4740: Add qi_lb60 board support alsa: ASoC: JZ4740: Add qi_lb60 board driver arch/mips/Kbuild.platforms | 1 + arch/mips/Kconfig | 13 + arch/mips/include/asm/bootinfo.h | 6 + arch/mips/include/asm/cpu.h | 9 +- arch/mips/include/asm/mach-jz4740/base.h | 26 + arch/mips/include/asm/mach-jz4740/clock.h | 28 + arch/mips/include/asm/mach-jz4740/dma.h | 90 +++ arch/mips/include/asm/mach-jz4740/gpio.h | 398 +++++++++++ arch/mips/include/asm/mach-jz4740/irq.h | 57 ++ arch/mips/include/asm/mach-jz4740/platform.h | 36 + arch/mips/include/asm/mach-jz4740/timer.h | 22 + arch/mips/include/asm/mach-jz4740/war.h | 25 + arch/mips/jz4740/Kconfig | 12 + arch/mips/jz4740/Makefile | 20 + arch/mips/jz4740/Platform | 5 + arch/mips/jz4740/board-qi_lb60.c | 483 +++++++++++++ arch/mips/jz4740/clock-debugfs.c | 109 +++ arch/mips/jz4740/clock.c | 920 ++++++++++++++++++++++++ arch/mips/jz4740/clock.h | 76 ++ arch/mips/jz4740/dma.c | 289 ++++++++ arch/mips/jz4740/gpio.c | 601 ++++++++++++++++ arch/mips/jz4740/irq.c | 169 +++++ arch/mips/jz4740/irq.h | 21 + arch/mips/jz4740/platform.c | 284 ++++++++ arch/mips/jz4740/pm.c | 56 ++ arch/mips/jz4740/prom.c | 68 ++ arch/mips/jz4740/pwm.c | 169 +++++ arch/mips/jz4740/reset.c | 79 ++ arch/mips/jz4740/reset.h | 7 + arch/mips/jz4740/serial.c | 33 + arch/mips/jz4740/serial.h | 21 + arch/mips/jz4740/setup.c | 29 + arch/mips/jz4740/time.c | 144 ++++ arch/mips/jz4740/timer.c | 48 ++ arch/mips/jz4740/timer.h | 136 ++++ arch/mips/kernel/cpu-probe.c | 20 + arch/mips/mm/tlbex.c | 5 + drivers/hwmon/Kconfig | 11 + drivers/hwmon/Makefile | 1 + drivers/hwmon/jz4740-hwmon.c | 206 ++++++ drivers/mfd/Kconfig | 8 + drivers/mfd/Makefile | 1 + drivers/mfd/jz4740-adc.c | 392 ++++++++++ drivers/mmc/host/Kconfig | 8 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/jz4740_mmc.c | 993 ++++++++++++++++++++++++++ drivers/mtd/nand/Kconfig | 6 + drivers/mtd/nand/Makefile | 1 + drivers/mtd/nand/jz4740_nand.c | 474 ++++++++++++ drivers/power/Kconfig | 11 + drivers/power/Makefile | 1 + drivers/power/jz4740-battery.c | 445 ++++++++++++ drivers/rtc/Kconfig | 11 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-jz4740.c | 341 +++++++++ drivers/usb/Kconfig | 1 + drivers/usb/host/ohci-hcd.c | 5 + drivers/usb/host/ohci-jz4740.c | 276 +++++++ drivers/video/Kconfig | 9 + drivers/video/Makefile | 1 + drivers/video/jz4740_fb.c | 817 +++++++++++++++++++++ include/linux/jz4740-adc.h | 32 + include/linux/jz4740_fb.h | 58 ++ include/linux/mmc/jz4740_mmc.h | 15 + include/linux/mtd/jz4740_nand.h | 34 + include/linux/power/jz4740-battery.h | 24 + sound/soc/Kconfig | 1 + sound/soc/Makefile | 1 + sound/soc/codecs/Kconfig | 4 + sound/soc/codecs/Makefile | 2 + sound/soc/codecs/jz4740-codec.c | 514 +++++++++++++ sound/soc/codecs/jz4740-codec.h | 20 + sound/soc/jz4740/Kconfig | 23 + sound/soc/jz4740/Makefile | 13 + sound/soc/jz4740/jz4740-i2s.c | 540 ++++++++++++++ sound/soc/jz4740/jz4740-i2s.h | 18 + sound/soc/jz4740/jz4740-pcm.c | 373 ++++++++++ sound/soc/jz4740/jz4740-pcm.h | 22 + sound/soc/jz4740/qi_lb60.c | 167 +++++ 79 files changed, 10396 insertions(+), 1 deletions(-) create mode 100644 arch/mips/include/asm/mach-jz4740/base.h create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h create mode 100644 arch/mips/include/asm/mach-jz4740/dma.h create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h create mode 100644 arch/mips/include/asm/mach-jz4740/timer.h create mode 100644 arch/mips/include/asm/mach-jz4740/war.h create mode 100644 arch/mips/jz4740/Kconfig create mode 100644 arch/mips/jz4740/Makefile create mode 100644 arch/mips/jz4740/Platform create mode 100644 arch/mips/jz4740/board-qi_lb60.c create mode 100644 arch/mips/jz4740/clock-debugfs.c create mode 100644 arch/mips/jz4740/clock.c create mode 100644 arch/mips/jz4740/clock.h create mode 100644 arch/mips/jz4740/dma.c create mode 100644 arch/mips/jz4740/gpio.c create mode 100644 arch/mips/jz4740/irq.c create mode 100644 arch/mips/jz4740/irq.h create mode 100644 arch/mips/jz4740/platform.c create mode 100644 arch/mips/jz4740/pm.c create mode 100644 arch/mips/jz4740/prom.c create mode 100644 arch/mips/jz4740/pwm.c create mode 100644 arch/mips/jz4740/reset.c create mode 100644 arch/mips/jz4740/reset.h create mode 100644 arch/mips/jz4740/serial.c create mode 100644 arch/mips/jz4740/serial.h create mode 100644 arch/mips/jz4740/setup.c create mode 100644 arch/mips/jz4740/time.c create mode 100644 arch/mips/jz4740/timer.c create mode 100644 arch/mips/jz4740/timer.h create mode 100644 drivers/hwmon/jz4740-hwmon.c create mode 100644 drivers/mfd/jz4740-adc.c create mode 100644 drivers/mmc/host/jz4740_mmc.c create mode 100644 drivers/mtd/nand/jz4740_nand.c create mode 100644 drivers/power/jz4740-battery.c create mode 100644 drivers/rtc/rtc-jz4740.c create mode 100644 drivers/usb/host/ohci-jz4740.c create mode 100644 drivers/video/jz4740_fb.c create mode 100644 include/linux/jz4740-adc.h create mode 100644 include/linux/jz4740_fb.h create mode 100644 include/linux/mmc/jz4740_mmc.h create mode 100644 include/linux/mtd/jz4740_nand.h create mode 100644 include/linux/power/jz4740-battery.h create mode 100644 sound/soc/codecs/jz4740-codec.c create mode 100644 sound/soc/codecs/jz4740-codec.h create mode 100644 sound/soc/jz4740/Kconfig create mode 100644 sound/soc/jz4740/Makefile create mode 100644 sound/soc/jz4740/jz4740-i2s.c create mode 100644 sound/soc/jz4740/jz4740-i2s.h create mode 100644 sound/soc/jz4740/jz4740-pcm.c create mode 100644 sound/soc/jz4740/jz4740-pcm.h create mode 100644 sound/soc/jz4740/qi_lb60.c ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v2 18/26] MMC: Add JZ4740 mmc driver 2010-06-19 5:08 [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Lars-Peter Clausen @ 2010-06-19 5:08 ` Lars-Peter Clausen 2010-06-19 14:46 ` Matt Fleming 2010-06-28 1:20 ` [PATCH v3] " Lars-Peter Clausen 2010-06-20 9:26 ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Thomas Bogendoerfer 1 sibling, 2 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-06-19 5:08 UTC (permalink / raw) To: Ralf Baechle Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-mmc This patch adds support for the mmc controller on JZ4740 SoCs. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: linux-mmc@vger.kernel.org --- Changes since v1 - Do not request IRQ with IRQF_DISABLED since it is a noop now - Use a generous slack for the timeout timer. It does not need to be accurate. --- drivers/mmc/host/Kconfig | 8 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/jz4740_mmc.c | 993 ++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/jz4740_mmc.h | 15 + 4 files changed, 1017 insertions(+), 0 deletions(-) create mode 100644 drivers/mmc/host/jz4740_mmc.c create mode 100644 include/linux/mmc/jz4740_mmc.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f06d06e..546fc49 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -81,6 +81,14 @@ config MMC_RICOH_MMC If unsure, say Y. +config MMC_JZ4740 + tristate "JZ4740 SD/Multimedia Card Interface support" + depends on MACH_JZ4740 + help + This selects the Ingenic Z4740 SD/Multimedia card Interface. + If you have an ngenic platform with a Multimedia Card slot, + say Y or M here. + config MMC_SDHCI_OF tristate "SDHCI support on OpenFirmware platforms" depends on MMC_SDHCI && PPC_OF diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e30c2ee..f4e53c9 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o sdhci-of-y := sdhci-of-core.o diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c new file mode 100644 index 0000000..5b73944 --- /dev/null +++ b/drivers/mmc/host/jz4740_mmc.c @@ -0,0 +1,993 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SD/MMC controller driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mmc/host.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/scatterlist.h> +#include <linux/clk.h> +#include <linux/mmc/jz4740_mmc.h> + +#include <linux/gpio.h> +#include <asm/mach-jz4740/gpio.h> +#include <asm/cacheflush.h> +#include <linux/dma-mapping.h> + +#define JZ_REG_MMC_STRPCL 0x00 +#define JZ_REG_MMC_STATUS 0x04 +#define JZ_REG_MMC_CLKRT 0x08 +#define JZ_REG_MMC_CMDAT 0x0C +#define JZ_REG_MMC_RESTO 0x10 +#define JZ_REG_MMC_RDTO 0x14 +#define JZ_REG_MMC_BLKLEN 0x18 +#define JZ_REG_MMC_NOB 0x1C +#define JZ_REG_MMC_SNOB 0x20 +#define JZ_REG_MMC_IMASK 0x24 +#define JZ_REG_MMC_IREG 0x28 +#define JZ_REG_MMC_CMD 0x2C +#define JZ_REG_MMC_ARG 0x30 +#define JZ_REG_MMC_RESP_FIFO 0x34 +#define JZ_REG_MMC_RXFIFO 0x38 +#define JZ_REG_MMC_TXFIFO 0x3C + +#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7) +#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6) +#define JZ_MMC_STRPCL_START_READWAIT BIT(5) +#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4) +#define JZ_MMC_STRPCL_RESET BIT(3) +#define JZ_MMC_STRPCL_START_OP BIT(2) +#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0)) +#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0) +#define JZ_MMC_STRPCL_CLOCK_START BIT(1) + + +#define JZ_MMC_STATUS_IS_RESETTING BIT(15) +#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14) +#define JZ_MMC_STATUS_PRG_DONE BIT(13) +#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12) +#define JZ_MMC_STATUS_END_CMD_RES BIT(11) +#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10) +#define JZ_MMC_STATUS_IS_READWAIT BIT(9) +#define JZ_MMC_STATUS_CLK_EN BIT(8) +#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7) +#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6) +#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5) +#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4) +#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3) +#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2) +#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1) +#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0) + +#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0)) +#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2)) + + +#define JZ_MMC_CMDAT_IO_ABORT BIT(11) +#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10) +#define JZ_MMC_CMDAT_DMA_EN BIT(8) +#define JZ_MMC_CMDAT_INIT BIT(7) +#define JZ_MMC_CMDAT_BUSY BIT(6) +#define JZ_MMC_CMDAT_STREAM BIT(5) +#define JZ_MMC_CMDAT_WRITE BIT(4) +#define JZ_MMC_CMDAT_DATA_EN BIT(3) +#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0)) +#define JZ_MMC_CMDAT_RSP_R1 1 +#define JZ_MMC_CMDAT_RSP_R2 2 +#define JZ_MMC_CMDAT_RSP_R3 3 + +#define JZ_MMC_IRQ_SDIO BIT(7) +#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6) +#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5) +#define JZ_MMC_IRQ_END_CMD_RES BIT(2) +#define JZ_MMC_IRQ_PRG_DONE BIT(1) +#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0) + + +#define JZ_MMC_CLK_RATE 24000000 + +#define JZ4740_MMC_MAX_TIMEOUT 10000000 + +struct jz4740_mmc_host { + struct mmc_host *mmc; + struct platform_device *pdev; + struct jz4740_mmc_platform_data *pdata; + struct clk *clk; + + int irq; + int card_detect_irq; + + struct resource *mem; + void __iomem *base; + struct mmc_request *req; + struct mmc_command *cmd; + + int max_clock; + uint32_t cmdat; + + uint16_t irq_mask; + + spinlock_t lock; + + struct timer_list timeout_timer; + unsigned waiting:1; +}; + +static void jz4740_mmc_cmd_done(struct jz4740_mmc_host *host); + +static void jz4740_mmc_enable_irq(struct jz4740_mmc_host *host, unsigned int irq) +{ + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + + host->irq_mask &= ~irq; + writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK); + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void jz4740_mmc_disable_irq(struct jz4740_mmc_host *host, unsigned int irq) +{ + unsigned long flags; + spin_lock_irqsave(&host->lock, flags); + + host->irq_mask |= irq; + writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK); + + spin_unlock_irqrestore(&host->lock, flags); +} + +static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host, + bool start_transfer) +{ + uint16_t val = JZ_MMC_STRPCL_CLOCK_START; + + if (start_transfer) + val |= JZ_MMC_STRPCL_START_OP; + + writew(val, host->base + JZ_REG_MMC_STRPCL); +} + +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host) +{ + uint32_t status; + + writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_CLK_EN); +} + +static void jz4740_mmc_reset(struct jz4740_mmc_host *host) +{ + uint32_t status; + + writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL); + udelay(10); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_IS_RESETTING); +} + +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host) +{ + struct mmc_request *req; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + req = host->req; + host->req = NULL; + host->waiting = 0; + spin_unlock_irqrestore(&host->lock, flags); + + if (!unlikely(req)) + return; + + mmc_request_done(host->mmc, req); +} + +static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host, + unsigned int irq) +{ + unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT; + uint16_t status; + + do { + status = readw(host->base + JZ_REG_MMC_IREG); + } while (!(status & irq) && --timeout); + + return timeout; +} + +static void jz4740_mmc_write_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct scatterlist *sg; + uint32_t *sg_pointer; + int status; + unsigned int timeout; + size_t i, j; + + for (sg = data->sg; sg; sg = sg_next(sg)) { + sg_pointer = sg_virt(sg); + i = sg->length / 4; + j = i >> 3; + i = i & 0x7; + while (j) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout == 0)) + goto err_timeout; + + writel(sg_pointer[0], host->base + JZ_REG_MMC_TXFIFO); + writel(sg_pointer[1], host->base + JZ_REG_MMC_TXFIFO); + writel(sg_pointer[2], host->base + JZ_REG_MMC_TXFIFO); + writel(sg_pointer[3], host->base + JZ_REG_MMC_TXFIFO); + writel(sg_pointer[4], host->base + JZ_REG_MMC_TXFIFO); + writel(sg_pointer[5], host->base + JZ_REG_MMC_TXFIFO); + writel(sg_pointer[6], host->base + JZ_REG_MMC_TXFIFO); + writel(sg_pointer[7], host->base + JZ_REG_MMC_TXFIFO); + sg_pointer += 8; + --j; + } + if (i) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout == 0)) + goto err_timeout; + + while (i) { + writel(*sg_pointer, host->base + JZ_REG_MMC_TXFIFO); + ++sg_pointer; + --i; + } + } + data->bytes_xfered += sg->length; + } + + status = readl(host->base + JZ_REG_MMC_STATUS); + if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) { + if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) { + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } else { + host->req->cmd->error = -EIO; + data->error = -EIO; + } + return; + } + + timeout = JZ4740_MMC_MAX_TIMEOUT; + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while ((status & JZ_MMC_STATUS_DATA_TRAN_DONE) == 0 && --timeout); + + if (unlikely(timeout == 0)) + goto err_timeout; + writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG); + + return; + +err_timeout: + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; +} + +static void jz4740_mmc_timeout(unsigned long data) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data; + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (!host->waiting) { + spin_unlock_irqrestore(&host->lock, flags); + return; + } + + host->waiting = 0; + + spin_unlock_irqrestore(&host->lock, flags); + + host->req->cmd->error = -ETIMEDOUT; + jz4740_mmc_request_done(host); +} + +static void jz4740_mmc_read_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct scatterlist *sg; + uint32_t *sg_pointer; + uint32_t d; + uint16_t status = 0; + size_t i, j; + unsigned int timeout; + + for (sg = data->sg; sg; sg = sg_next(sg)) { + sg_pointer = sg_virt(sg); + i = sg->length; + j = i >> 5; + i = i & 0x1f; + while (j) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout == 0)) + goto err_timeout; + + sg_pointer[0] = readl(host->base + JZ_REG_MMC_RXFIFO); + sg_pointer[1] = readl(host->base + JZ_REG_MMC_RXFIFO); + sg_pointer[2] = readl(host->base + JZ_REG_MMC_RXFIFO); + sg_pointer[3] = readl(host->base + JZ_REG_MMC_RXFIFO); + sg_pointer[4] = readl(host->base + JZ_REG_MMC_RXFIFO); + sg_pointer[5] = readl(host->base + JZ_REG_MMC_RXFIFO); + sg_pointer[6] = readl(host->base + JZ_REG_MMC_RXFIFO); + sg_pointer[7] = readl(host->base + JZ_REG_MMC_RXFIFO); + + sg_pointer += 8; + --j; + } + + while (i >= 4) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout == 0)) + goto err_timeout; + + *sg_pointer = readl(host->base + JZ_REG_MMC_RXFIFO); + ++sg_pointer; + i -= 4; + } + if (i > 0) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + memcpy(sg_pointer, &d, i); + } + data->bytes_xfered += sg->length; + + flush_dcache_page(sg_page(sg)); + } + + status = readl(host->base + JZ_REG_MMC_STATUS); + if (status & JZ_MMC_STATUS_READ_ERROR_MASK) { + if (status & JZ_MMC_STATUS_TIMEOUT_READ) { + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } else { + host->req->cmd->error = -EIO; + data->error = -EIO; + } + return; + } + + /* For whatever reason there is sometime one word more in the fifo then + * requested */ + while ((status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) == 0 && --timeout) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + status = readl(host->base + JZ_REG_MMC_STATUS); + } + return; + +err_timeout: + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; +} + +static irqreturn_t jz_mmc_irq_worker(int irq, void *devid) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid; + + if (host->cmd->error) + jz4740_mmc_request_done(host); + else + jz4740_mmc_cmd_done(host); + + return IRQ_HANDLED; +} + +static irqreturn_t jz_mmc_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + uint16_t irq_reg, status, tmp; + unsigned long flags; + irqreturn_t ret = IRQ_HANDLED; + + irq_reg = readw(host->base + JZ_REG_MMC_IREG); + + tmp = irq_reg; + spin_lock_irqsave(&host->lock, flags); + irq_reg &= ~host->irq_mask; + spin_unlock_irqrestore(&host->lock, flags); + + tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ | + JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE); + + if (tmp != irq_reg) + writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG); + + if (irq_reg & JZ_MMC_IRQ_SDIO) { + writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG); + mmc_signal_sdio_irq(host->mmc); + } + + if (!host->req || !host->cmd) + goto handled; + + spin_lock_irqsave(&host->lock, flags); + if (!host->waiting) { + spin_unlock_irqrestore(&host->lock, flags); + goto handled; + } + + host->waiting = 0; + spin_unlock_irqrestore(&host->lock, flags); + + del_timer(&host->timeout_timer); + + status = readl(host->base + JZ_REG_MMC_STATUS); + + if (status & JZ_MMC_STATUS_TIMEOUT_RES) { + host->cmd->error = -ETIMEDOUT; + } else if (status & JZ_MMC_STATUS_CRC_RES_ERR) { + host->cmd->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } + + if (irq_reg & JZ_MMC_IRQ_END_CMD_RES) { + jz4740_mmc_disable_irq(host, JZ_MMC_IRQ_END_CMD_RES); + writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG); + ret = IRQ_WAKE_THREAD; + } + + return ret; + +handled: + writew(0xff, host->base + JZ_REG_MMC_IREG); + return IRQ_HANDLED; +} + +static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate) +{ + int div = 0; + int real_rate; + + jz4740_mmc_clock_disable(host); + clk_set_rate(host->clk, JZ_MMC_CLK_RATE); + + real_rate = clk_get_rate(host->clk); + + while (real_rate > rate && div < 7) { + ++div; + real_rate >>= 1; + } + + writew(div, host->base + JZ_REG_MMC_CLKRT); + return real_rate; +} + +static void jz4740_mmc_read_response(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + int i; + uint16_t tmp; + + if (cmd->flags & MMC_RSP_136) { + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + for (i = 0; i < 4; ++i) { + cmd->resp[i] = tmp << 24; + cmd->resp[i] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8; + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + cmd->resp[i] |= tmp >> 8; + } + } else { + cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff; + } +} + +static void jz4740_mmc_send_command(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + uint32_t cmdat = host->cmdat; + + host->cmdat &= ~JZ_MMC_CMDAT_INIT; + jz4740_mmc_clock_disable(host); + + host->cmd = cmd; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= JZ_MMC_CMDAT_BUSY; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1B: + case MMC_RSP_R1: + cmdat |= JZ_MMC_CMDAT_RSP_R1; + break; + case MMC_RSP_R2: + cmdat |= JZ_MMC_CMDAT_RSP_R2; + break; + case MMC_RSP_R3: + cmdat |= JZ_MMC_CMDAT_RSP_R3; + break; + default: + break; + } + + if (cmd->data) { + cmdat |= JZ_MMC_CMDAT_DATA_EN; + if (cmd->data->flags & MMC_DATA_WRITE) + cmdat |= JZ_MMC_CMDAT_WRITE; + if (cmd->data->flags & MMC_DATA_STREAM) + cmdat |= JZ_MMC_CMDAT_STREAM; + + writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN); + writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB); + } + + writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD); + writel(cmd->arg, host->base + JZ_REG_MMC_ARG); + writel(cmdat, host->base + JZ_REG_MMC_CMDAT); + + host->waiting = 1; + jz4740_mmc_clock_enable(host, 1); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); +} + +static void jz4740_mmc_cmd_done(struct jz4740_mmc_host *host) +{ + uint32_t status; + struct mmc_command *cmd = host->req->cmd; + struct mmc_request *req = host->req; + unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT; + + if (cmd->flags & MMC_RSP_PRESENT) + jz4740_mmc_read_response(host, cmd); + + if (cmd->data) { + if (cmd->data->flags & MMC_DATA_READ) + jz4740_mmc_read_data(host, cmd->data); + else + jz4740_mmc_write_data(host, cmd->data); + } + + if (req->stop) { + jz4740_mmc_send_command(host, req->stop); + do { + status = readw(host->base + JZ_REG_MMC_IREG); + } while ((status & JZ_MMC_IRQ_PRG_DONE) == 0 && --timeout); + writew(JZ_MMC_IRQ_PRG_DONE, host->base + JZ_REG_MMC_IREG); + } + + if (unlikely(timeout == 0)) + req->stop->error = -ETIMEDOUT; + + jz4740_mmc_request_done(host); +} + +static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + + host->req = req; + + writew(0xffff, host->base + JZ_REG_MMC_IREG); + + writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG); + jz4740_mmc_enable_irq(host, JZ_MMC_IRQ_END_CMD_RES); + jz4740_mmc_send_command(host, req->cmd); +} + +static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (ios->clock) + jz4740_mmc_set_clock_rate(host, ios->clock); + + switch (ios->power_mode) { + case MMC_POWER_UP: + jz4740_mmc_reset(host); + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + !host->pdata->power_active_low); + host->cmdat |= JZ_MMC_CMDAT_INIT; + clk_enable(host->clk); + break; + case MMC_POWER_ON: + break; + default: + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + host->pdata->power_active_low); + clk_disable(host->clk); + break; + } + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + case MMC_BUS_WIDTH_4: + host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + default: + break; + } +} + +static int jz4740_mmc_get_ro(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_read_only)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_read_only) ^ + host->pdata->read_only_active_low; +} + +static int jz4740_mmc_get_cd(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_card_detect)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_card_detect) ^ + host->pdata->card_detect_active_low; +} + +static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + + mmc_detect_change(host->mmc, HZ / 3); + + return IRQ_HANDLED; +} + +static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + + if (enable) + jz4740_mmc_enable_irq(host, JZ_MMC_IRQ_SDIO); + else + jz4740_mmc_disable_irq(host, JZ_MMC_IRQ_SDIO); +} + +static const struct mmc_host_ops jz4740_mmc_ops = { + .request = jz4740_mmc_request, + .set_ios = jz4740_mmc_set_ios, + .get_ro = jz4740_mmc_get_ro, + .get_cd = jz4740_mmc_get_cd, + .enable_sdio_irq = jz4740_mmc_enable_sdio_irq, +}; + +static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = { + JZ_GPIO_BULK_PIN(MSC_CMD), + JZ_GPIO_BULK_PIN(MSC_CLK), + JZ_GPIO_BULK_PIN(MSC_DATA0), + JZ_GPIO_BULK_PIN(MSC_DATA1), + JZ_GPIO_BULK_PIN(MSC_DATA2), + JZ_GPIO_BULK_PIN(MSC_DATA3), +}; + +static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return 0; + + if (gpio_is_valid(pdata->gpio_card_detect)) { + ret = gpio_request(pdata->gpio_card_detect, "MMC detect change"); + if (ret) { + dev_err(&pdev->dev, "Failed to request detect change gpio\n"); + goto err; + } + gpio_direction_input(pdata->gpio_card_detect); + } + + if (gpio_is_valid(pdata->gpio_read_only)) { + ret = gpio_request(pdata->gpio_read_only, "MMC read only"); + if (ret) { + dev_err(&pdev->dev, "Failed to request read only gpio: %d\n", ret); + goto err_free_gpio_card_detect; + } + gpio_direction_input(pdata->gpio_read_only); + } + + if (gpio_is_valid(pdata->gpio_power)) { + ret = gpio_request(pdata->gpio_power, "MMC power"); + if (ret) { + dev_err(&pdev->dev, "Failed to request power gpio: %d\n", ret); + goto err_free_gpio_read_only; + } + gpio_direction_output(pdata->gpio_power, pdata->power_active_low); + } + + return 0; + +err_free_gpio_read_only: + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); +err_free_gpio_card_detect: + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +err: + return ret; +} + +static void jz4740_mmc_free_gpios(struct platform_device *pdev) +{ + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return; + + if (gpio_is_valid(pdata->gpio_power)) + gpio_free(pdata->gpio_power); + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +} + +static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host) +{ + size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins); + if (host->pdata && host->pdata->data_1bit) + num_pins -= 3; + + return num_pins; +} + +static int __devinit jz4740_mmc_probe(struct platform_device* pdev) +{ + int ret; + struct mmc_host *mmc; + struct jz4740_mmc_host *host; + struct jz4740_mmc_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev); + if (!mmc) { + dev_err(&pdev->dev, "Failed to alloc mmc host structure\n"); + return -ENOMEM; + } + + host = mmc_priv(mmc); + host->pdata = pdata; + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + ret = host->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free_host; + } + + host->clk = clk_get(&pdev->dev, "mmc"); + if (!host->clk) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get mmc clock\n"); + goto err_free_host; + } + + host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get base platform memory\n"); + goto err_clk_put; + } + + host->mem = request_mem_region(host->mem->start, + resource_size(host->mem), pdev->name); + if (!host->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request base memory region\n"); + goto err_clk_put; + } + + host->base = ioremap_nocache(host->mem->start, resource_size(host->mem)); + if (!host->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap base memory\n"); + goto err_release_mem_region; + } + + ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + if (ret) { + dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret); + goto err_iounmap; + } + + ret = jz4740_mmc_request_gpios(pdev); + if (ret) + goto err_gpio_bulk_free; + + mmc->ops = &jz4740_mmc_ops; + mmc->f_min = JZ_MMC_CLK_RATE / 128; + mmc->f_max = JZ_MMC_CLK_RATE; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA; + mmc->caps |= MMC_CAP_SDIO_IRQ; + + mmc->max_blk_size = (1 << 10) - 1; + mmc->max_blk_count = (1 << 15) - 1; + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + mmc->max_seg_size = mmc->max_req_size; + + host->mmc = mmc; + host->pdev = pdev; + host->max_clock = JZ_MMC_CLK_RATE; + spin_lock_init(&host->lock); + host->irq_mask = 0xffff; + + host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect); + + if (host->card_detect_irq < 0) { + dev_warn(&pdev->dev, "Failed to get irq for card detect gpio\n"); + } else { + ret = request_irq(host->card_detect_irq, + jz4740_mmc_card_detect_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "MMC card detect", host); + + if (ret) { + dev_err(&pdev->dev, "Failed to request card detect irq"); + goto err_free_gpios; + } + } + + ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0, + dev_name(&pdev->dev), host); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_free_card_detect_irq; + } + + jz4740_mmc_reset(host); + jz4740_mmc_clock_disable(host); + setup_timer(&host->timeout_timer, jz4740_mmc_timeout, + (unsigned long)host); + /* It is not that important when it times out, it just needs to timeout. */ + set_timer_slack(&host->timeout_timer, HZ); + + platform_set_drvdata(pdev, host); + ret = mmc_add_host(mmc); + + if (ret) { + dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret); + goto err_free_irq; + } + dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n"); + + return 0; + +err_free_irq: + free_irq(host->irq, host); +err_free_card_detect_irq: + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); +err_free_gpios: + jz4740_mmc_free_gpios(pdev); +err_gpio_bulk_free: + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); +err_iounmap: + iounmap(host->base); +err_release_mem_region: + release_mem_region(host->mem->start, resource_size(host->mem)); +err_clk_put: + clk_put(host->clk); +err_free_host: + platform_set_drvdata(pdev, NULL); + mmc_free_host(mmc); + + return ret; +} + +static int __devexit jz4740_mmc_remove(struct platform_device *pdev) +{ + struct jz4740_mmc_host *host = platform_get_drvdata(pdev); + + del_timer_sync(&host->timeout_timer); + jz4740_mmc_disable_irq(host, 0xff); + jz4740_mmc_reset(host); + + mmc_remove_host(host->mmc); + + free_irq(host->irq, host); + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); + + jz4740_mmc_free_gpios(pdev); + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + iounmap(host->base); + release_mem_region(host->mem->start, resource_size(host->mem)); + + clk_put(host->clk); + + platform_set_drvdata(pdev, NULL); + mmc_free_host(host->mmc); + + return 0; +} + +#ifdef CONFIG_PM +static int jz4740_mmc_suspend(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + mmc_suspend_host(host->mmc); + + jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + return 0; +} + +static int jz4740_mmc_resume(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + mmc_resume_host(host->mmc); + + return 0; +} + +const struct dev_pm_ops jz4740_mmc_pm_ops = { + .suspend = jz4740_mmc_suspend, + .resume = jz4740_mmc_resume, + .poweroff = jz4740_mmc_suspend, + .restore = jz4740_mmc_resume, +}; + +#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops) +#else +#define JZ4740_MMC_PM_OPS NULL +#endif + +static struct platform_driver jz4740_mmc_driver = { + .probe = jz4740_mmc_probe, + .remove = __devexit_p(jz4740_mmc_remove), + .driver = { + .name = "jz4740-mmc", + .owner = THIS_MODULE, + .pm = JZ4740_MMC_PM_OPS, + }, +}; + +static int __init jz4740_mmc_init(void) +{ + return platform_driver_register(&jz4740_mmc_driver); +} +module_init(jz4740_mmc_init); + +static void __exit jz4740_mmc_exit(void) +{ + platform_driver_unregister(&jz4740_mmc_driver); +} +module_exit(jz4740_mmc_exit); + +MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); diff --git a/include/linux/mmc/jz4740_mmc.h b/include/linux/mmc/jz4740_mmc.h new file mode 100644 index 0000000..8543f43 --- /dev/null +++ b/include/linux/mmc/jz4740_mmc.h @@ -0,0 +1,15 @@ +#ifndef __LINUX_MMC_JZ4740_MMC +#define __LINUX_MMC_JZ4740_MMC + +struct jz4740_mmc_platform_data { + int gpio_power; + int gpio_card_detect; + int gpio_read_only; + unsigned card_detect_active_low:1; + unsigned read_only_active_low:1; + unsigned power_active_low:1; + + unsigned data_1bit:1; +}; + +#endif -- 1.5.6.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver 2010-06-19 5:08 ` [PATCH v2 18/26] MMC: Add JZ4740 mmc driver Lars-Peter Clausen @ 2010-06-19 14:46 ` Matt Fleming 2010-06-19 15:29 ` Lars-Peter Clausen 2010-06-28 1:20 ` [PATCH v3] " Lars-Peter Clausen 1 sibling, 1 reply; 20+ messages in thread From: Matt Fleming @ 2010-06-19 14:46 UTC (permalink / raw) Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-mmc, Ralf Baechle On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote: > This patch adds support for the mmc controller on JZ4740 SoCs. > Hey Lars-Peter, I had a quick look over this patch and it looks OK. Just a few comments. > +static void jz4740_mmc_timeout(unsigned long data) > +{ > + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data; > + unsigned long flags; > + > + spin_lock_irqsave(&host->lock, flags); > + if (!host->waiting) { > + spin_unlock_irqrestore(&host->lock, flags); > + return; > + } > + > + host->waiting = 0; > + > + spin_unlock_irqrestore(&host->lock, flags); > + > + host->req->cmd->error = -ETIMEDOUT; > + jz4740_mmc_request_done(host); > +} > + Taking a spinlock and disabling interrupts seems like too much overhead to simply test and clear a bit. Wouldn't it be better to implement this with test_and_clear_bit(), which on MIPS will likely be implemented with ll/sc instructions? It's particularly important to keep this low-overhead since this bit is modified in the interrupt handler. > +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host) > +{ > + struct mmc_request *req; > + unsigned long flags; > + > + spin_lock_irqsave(&host->lock, flags); > + req = host->req; > + host->req = NULL; > + host->waiting = 0; > + spin_unlock_irqrestore(&host->lock, flags); > + > + if (!unlikely(req)) > + return; > + > + mmc_request_done(host->mmc, req); > +} > + Am I right in thinking that this spinlock guards against the interrupt handler and the timeout function running at the same time? So it's not really possible to drop the spinlock from here? ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver 2010-06-19 14:46 ` Matt Fleming @ 2010-06-19 15:29 ` Lars-Peter Clausen 0 siblings, 0 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-06-19 15:29 UTC (permalink / raw) To: Matt Fleming Cc: linux-mips, linux-kernel, Andrew Morton, linux-mmc, Ralf Baechle Hi Matt Fleming wrote: > On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote: >> This patch adds support for the mmc controller on JZ4740 SoCs. >> > > Hey Lars-Peter, > > I had a quick look over this patch and it looks OK. Just a few comments. > >> +static void jz4740_mmc_timeout(unsigned long data) >> +{ >> + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data; >> + unsigned long flags; >> + >> + spin_lock_irqsave(&host->lock, flags); >> + if (!host->waiting) { >> + spin_unlock_irqrestore(&host->lock, flags); >> + return; >> + } >> + >> + host->waiting = 0; >> + >> + spin_unlock_irqrestore(&host->lock, flags); >> + >> + host->req->cmd->error = -ETIMEDOUT; >> + jz4740_mmc_request_done(host); >> +} >> + > > Taking a spinlock and disabling interrupts seems like too much overhead > to simply test and clear a bit. Wouldn't it be better to implement this > with test_and_clear_bit(), which on MIPS will likely be implemented with > ll/sc instructions? It's particularly important to keep this > low-overhead since this bit is modified in the interrupt handler. > Sounds like a good idea :) >> +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host) >> +{ >> + struct mmc_request *req; >> + unsigned long flags; >> + >> + spin_lock_irqsave(&host->lock, flags); >> + req = host->req; >> + host->req = NULL; >> + host->waiting = 0; >> + spin_unlock_irqrestore(&host->lock, flags); >> + >> + if (!unlikely(req)) >> + return; >> + >> + mmc_request_done(host->mmc, req); >> +} >> + > > Am I right in thinking that this spinlock guards against the interrupt > handler and the timeout function running at the same time? So it's not > really possible to drop the spinlock from here? > Yes, at least that is what it was meant for. But it was there before the waiting bit and right now I can not construct any code paths that could lead to jz4740_mmc_request_done from two paths at the same time. The timer wont call it if the waiting bit is not set and the irq handler won't wake the threaded irq handler if the waiting bit is not set. I'll think a bit more about it and eventually drop the spinlock here. Thanks for your review :) - Lars ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3] MMC: Add JZ4740 mmc driver 2010-06-19 5:08 ` [PATCH v2 18/26] MMC: Add JZ4740 mmc driver Lars-Peter Clausen 2010-06-19 14:46 ` Matt Fleming @ 2010-06-28 1:20 ` Lars-Peter Clausen 2010-06-29 20:17 ` Matt Fleming ` (2 more replies) 1 sibling, 3 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-06-28 1:20 UTC (permalink / raw) To: Ralf Baechle Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, Matt Fleming, linux-mmc This patch adds support for the mmc controller on JZ4740 SoCs. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Matt Fleming <matt@console-pimps.org> Cc: linux-mmc@vger.kernel.org --- Changes since v1 - Do not request IRQ with IRQF_DISABLED since it is a noop now - Use a generous slack for the timeout timer. It does not need to be accurate. Changes since v2 - Use sg_mapping_to iterate over sg elements in mmc read and write functions - Use bitops instead of a spinlock and a variable for testing whether a request has been finished. - Rework irq and timeout handling in order to get rid of locking in hot paths --- drivers/mmc/host/Kconfig | 8 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/jz4740_mmc.c | 969 ++++++++++++++++++++++++++++++++++++++++ include/linux/mmc/jz4740_mmc.h | 15 + 4 files changed, 993 insertions(+), 0 deletions(-) create mode 100644 drivers/mmc/host/jz4740_mmc.c create mode 100644 include/linux/mmc/jz4740_mmc.h diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f06d06e..546fc49 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -81,6 +81,14 @@ config MMC_RICOH_MMC If unsure, say Y. +config MMC_JZ4740 + tristate "JZ4740 SD/Multimedia Card Interface support" + depends on MACH_JZ4740 + help + This selects the Ingenic Z4740 SD/Multimedia card Interface. + If you have an ngenic platform with a Multimedia Card slot, + say Y or M here. + config MMC_SDHCI_OF tristate "SDHCI support on OpenFirmware platforms" depends on MMC_SDHCI && PPC_OF diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e30c2ee..f4e53c9 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o sdhci-of-y := sdhci-of-core.o diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c new file mode 100644 index 0000000..b34f25b --- /dev/null +++ b/drivers/mmc/host/jz4740_mmc.c @@ -0,0 +1,969 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SD/MMC controller driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mmc/host.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/scatterlist.h> +#include <linux/clk.h> +#include <linux/mmc/jz4740_mmc.h> + +#include <linux/bitops.h> +#include <linux/gpio.h> +#include <asm/mach-jz4740/gpio.h> +#include <asm/cacheflush.h> +#include <linux/dma-mapping.h> + + +#define JZ_REG_MMC_STRPCL 0x00 +#define JZ_REG_MMC_STATUS 0x04 +#define JZ_REG_MMC_CLKRT 0x08 +#define JZ_REG_MMC_CMDAT 0x0C +#define JZ_REG_MMC_RESTO 0x10 +#define JZ_REG_MMC_RDTO 0x14 +#define JZ_REG_MMC_BLKLEN 0x18 +#define JZ_REG_MMC_NOB 0x1C +#define JZ_REG_MMC_SNOB 0x20 +#define JZ_REG_MMC_IMASK 0x24 +#define JZ_REG_MMC_IREG 0x28 +#define JZ_REG_MMC_CMD 0x2C +#define JZ_REG_MMC_ARG 0x30 +#define JZ_REG_MMC_RESP_FIFO 0x34 +#define JZ_REG_MMC_RXFIFO 0x38 +#define JZ_REG_MMC_TXFIFO 0x3C + +#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7) +#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6) +#define JZ_MMC_STRPCL_START_READWAIT BIT(5) +#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4) +#define JZ_MMC_STRPCL_RESET BIT(3) +#define JZ_MMC_STRPCL_START_OP BIT(2) +#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0)) +#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0) +#define JZ_MMC_STRPCL_CLOCK_START BIT(1) + + +#define JZ_MMC_STATUS_IS_RESETTING BIT(15) +#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14) +#define JZ_MMC_STATUS_PRG_DONE BIT(13) +#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12) +#define JZ_MMC_STATUS_END_CMD_RES BIT(11) +#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10) +#define JZ_MMC_STATUS_IS_READWAIT BIT(9) +#define JZ_MMC_STATUS_CLK_EN BIT(8) +#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7) +#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6) +#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5) +#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4) +#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3) +#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2) +#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1) +#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0) + +#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0)) +#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2)) + + +#define JZ_MMC_CMDAT_IO_ABORT BIT(11) +#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10) +#define JZ_MMC_CMDAT_DMA_EN BIT(8) +#define JZ_MMC_CMDAT_INIT BIT(7) +#define JZ_MMC_CMDAT_BUSY BIT(6) +#define JZ_MMC_CMDAT_STREAM BIT(5) +#define JZ_MMC_CMDAT_WRITE BIT(4) +#define JZ_MMC_CMDAT_DATA_EN BIT(3) +#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0)) +#define JZ_MMC_CMDAT_RSP_R1 1 +#define JZ_MMC_CMDAT_RSP_R2 2 +#define JZ_MMC_CMDAT_RSP_R3 3 + +#define JZ_MMC_IRQ_SDIO BIT(7) +#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6) +#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5) +#define JZ_MMC_IRQ_END_CMD_RES BIT(2) +#define JZ_MMC_IRQ_PRG_DONE BIT(1) +#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0) + + +#define JZ_MMC_CLK_RATE 24000000 + +#define JZ4740_MMC_MAX_TIMEOUT 10000000 + +struct jz4740_mmc_host { + struct mmc_host *mmc; + struct platform_device *pdev; + struct jz4740_mmc_platform_data *pdata; + struct clk *clk; + + int irq; + int card_detect_irq; + + struct resource *mem; + void __iomem *base; + struct mmc_request *req; + struct mmc_command *cmd; + + unsigned long waiting; + + int max_clock; + uint32_t cmdat; + + uint16_t irq_mask; + + spinlock_t lock; + + struct timer_list timeout_timer; +}; + +static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host, + unsigned int irq, bool enabled) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (enabled) + host->irq_mask &= ~irq; + else + host->irq_mask |= irq; + spin_unlock_irqrestore(&host->lock, flags); + + writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK); +} + +static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host, + bool start_transfer) +{ + uint16_t val = JZ_MMC_STRPCL_CLOCK_START; + + if (start_transfer) + val |= JZ_MMC_STRPCL_START_OP; + + writew(val, host->base + JZ_REG_MMC_STRPCL); +} + +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host) +{ + uint32_t status; + + writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_CLK_EN); +} + +static void jz4740_mmc_reset(struct jz4740_mmc_host *host) +{ + uint32_t status; + + writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL); + udelay(10); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_IS_RESETTING); +} + +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host) +{ + struct mmc_request *req; + + req = host->req; + host->req = NULL; + + mmc_request_done(host->mmc, req); +} + +static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host, + unsigned int irq) +{ + unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT; + uint16_t status; + + do { + status = readw(host->base + JZ_REG_MMC_IREG); + } while (!(status & irq) && --timeout); + + return timeout; +} + +static void jz4740_mmc_write_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint32_t *buf; + int status; + unsigned int timeout; + size_t i, j; + + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG); + while (sg_miter_next(&miter)) { + buf = miter.addr; + i = miter.length / 4; + j = i >> 3; + i = i & 0x7; + while (j) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout == 0)) { + sg_miter_stop(&miter); + goto err_timeout; + } + + writel(buf[0], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[1], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[2], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[3], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[4], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[5], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[6], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[7], host->base + JZ_REG_MMC_TXFIFO); + buf += 8; + --j; + } + if (unlikely(i)) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout == 0)) { + sg_miter_stop(&miter); + goto err_timeout; + } + + while (i) { + writel(*buf, host->base + JZ_REG_MMC_TXFIFO); + ++buf; + --i; + } + } + data->bytes_xfered += miter.length; + } + sg_miter_stop(&miter); + + status = readl(host->base + JZ_REG_MMC_STATUS); + if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) { + if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) { + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } else { + host->req->cmd->error = -EIO; + data->error = -EIO; + } + return; + } + + timeout = JZ4740_MMC_MAX_TIMEOUT; + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while ((status & JZ_MMC_STATUS_DATA_TRAN_DONE) == 0 && --timeout); + + if (unlikely(timeout == 0)) + goto err_timeout; + writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG); + + return; + +err_timeout: + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; +} + +static void jz4740_mmc_read_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter miter; + uint32_t *buf; + uint32_t d; + uint16_t status = 0; + size_t i, j; + unsigned int timeout; + + sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG); + while (sg_miter_next(&miter)) { + buf = miter.addr; + i = miter.length; + j = i >> 5; + i = i & 0x1f; + while (j) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout == 0)) { + sg_miter_stop(&miter); + goto err_timeout; + } + + buf[0] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[1] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[2] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[3] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[4] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[5] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[6] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[7] = readl(host->base + JZ_REG_MMC_RXFIFO); + + buf += 8; + --j; + } + + while (i >= 4) { + timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout == 0)) { + sg_miter_stop(&miter); + goto err_timeout; + } + + *buf++ = readl(host->base + JZ_REG_MMC_RXFIFO); + i -= 4; + } + if (unlikely(i > 0)) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + memcpy(buf, &d, i); + } + data->bytes_xfered += miter.length; + + /* This can go away once MIPS implements flush_kernel_dcache_page */ + flush_dcache_page(miter.page); + } + sg_miter_stop(&miter); + + status = readl(host->base + JZ_REG_MMC_STATUS); + if (status & JZ_MMC_STATUS_READ_ERROR_MASK) { + if (status & JZ_MMC_STATUS_TIMEOUT_READ) { + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } else { + host->req->cmd->error = -EIO; + data->error = -EIO; + } + return; + } + + /* For whatever reason there is sometime one word more in the fifo then + * requested */ + while ((status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) == 0 && --timeout) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + status = readl(host->base + JZ_REG_MMC_STATUS); + } + return; + +err_timeout: + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; +} + +static void jz4740_mmc_timeout(unsigned long data) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data; + + if (!test_and_clear_bit(0, &host->waiting)) + return; + + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false); + + host->req->cmd->error = -ETIMEDOUT; + jz4740_mmc_request_done(host); +} + +static void jz4740_mmc_read_response(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + int i; + uint16_t tmp; + + if (cmd->flags & MMC_RSP_136) { + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + for (i = 0; i < 4; ++i) { + cmd->resp[i] = tmp << 24; + cmd->resp[i] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8; + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + cmd->resp[i] |= tmp >> 8; + } + } else { + cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff; + } +} + +static void jz4740_mmc_send_command(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + uint32_t cmdat = host->cmdat; + + host->cmdat &= ~JZ_MMC_CMDAT_INIT; + jz4740_mmc_clock_disable(host); + + host->cmd = cmd; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= JZ_MMC_CMDAT_BUSY; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1B: + case MMC_RSP_R1: + cmdat |= JZ_MMC_CMDAT_RSP_R1; + break; + case MMC_RSP_R2: + cmdat |= JZ_MMC_CMDAT_RSP_R2; + break; + case MMC_RSP_R3: + cmdat |= JZ_MMC_CMDAT_RSP_R3; + break; + default: + break; + } + + if (cmd->data) { + cmdat |= JZ_MMC_CMDAT_DATA_EN; + if (cmd->data->flags & MMC_DATA_WRITE) + cmdat |= JZ_MMC_CMDAT_WRITE; + if (cmd->data->flags & MMC_DATA_STREAM) + cmdat |= JZ_MMC_CMDAT_STREAM; + + writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN); + writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB); + } + + writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD); + writel(cmd->arg, host->base + JZ_REG_MMC_ARG); + writel(cmdat, host->base + JZ_REG_MMC_CMDAT); + + jz4740_mmc_clock_enable(host, 1); +} + + +static irqreturn_t jz_mmc_irq_worker(int irq, void *devid) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid; + struct mmc_command *cmd = host->req->cmd; + struct mmc_request *req = host->req; + unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT; + uint32_t status; + + if (cmd->error) + goto done; + + if (cmd->flags & MMC_RSP_PRESENT) + jz4740_mmc_read_response(host, cmd); + + if (cmd->data) { + if (cmd->data->flags & MMC_DATA_READ) + jz4740_mmc_read_data(host, cmd->data); + else + jz4740_mmc_write_data(host, cmd->data); + } + + if (req->stop) { + jz4740_mmc_send_command(host, req->stop); + do { + status = readw(host->base + JZ_REG_MMC_IREG); + } while ((status & JZ_MMC_IRQ_PRG_DONE) == 0 && --timeout); + writew(JZ_MMC_IRQ_PRG_DONE, host->base + JZ_REG_MMC_IREG); + } + + if (unlikely(timeout == 0)) + req->stop->error = -ETIMEDOUT; + +done: + jz4740_mmc_request_done(host); + + return IRQ_HANDLED; +} + +static irqreturn_t jz_mmc_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + uint16_t irq_reg, status, tmp; + irqreturn_t ret = IRQ_HANDLED; + + irq_reg = readw(host->base + JZ_REG_MMC_IREG); + + tmp = irq_reg; + irq_reg &= ~host->irq_mask; + + tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ | + JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE); + + if (tmp != irq_reg) + writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG); + + if (irq_reg & JZ_MMC_IRQ_SDIO) { + writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG); + mmc_signal_sdio_irq(host->mmc); + } + + if (!host->req || !host->cmd) + goto handled; + + if (!(irq_reg & JZ_MMC_IRQ_END_CMD_RES)) + goto handled; + + if (test_and_clear_bit(0, &host->waiting)) { + del_timer(&host->timeout_timer); + + status = readl(host->base + JZ_REG_MMC_STATUS); + + if (status & JZ_MMC_STATUS_TIMEOUT_RES) { + host->cmd->error = -ETIMEDOUT; + } else if (status & JZ_MMC_STATUS_CRC_RES_ERR) { + host->cmd->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } + + ret = IRQ_WAKE_THREAD; + } + + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false); + writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG); + +handled: + return ret; +} + +static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate) +{ + int div = 0; + int real_rate; + + jz4740_mmc_clock_disable(host); + clk_set_rate(host->clk, JZ_MMC_CLK_RATE); + + real_rate = clk_get_rate(host->clk); + + while (real_rate > rate && div < 7) { + ++div; + real_rate >>= 1; + } + + writew(div, host->base + JZ_REG_MMC_CLKRT); + return real_rate; +} + +static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + + host->req = req; + + writew(0xffff, host->base + JZ_REG_MMC_IREG); + + writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true); + + set_bit(0, &host->waiting); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); + jz4740_mmc_send_command(host, req->cmd); +} + +static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (ios->clock) + jz4740_mmc_set_clock_rate(host, ios->clock); + + switch (ios->power_mode) { + case MMC_POWER_UP: + jz4740_mmc_reset(host); + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + !host->pdata->power_active_low); + host->cmdat |= JZ_MMC_CMDAT_INIT; + clk_enable(host->clk); + break; + case MMC_POWER_ON: + break; + default: + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + host->pdata->power_active_low); + clk_disable(host->clk); + break; + } + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + case MMC_BUS_WIDTH_4: + host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + default: + break; + } +} + +static int jz4740_mmc_get_ro(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_read_only)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_read_only) ^ + host->pdata->read_only_active_low; +} + +static int jz4740_mmc_get_cd(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_card_detect)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_card_detect) ^ + host->pdata->card_detect_active_low; +} + +static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + + mmc_detect_change(host->mmc, HZ / 3); + + return IRQ_HANDLED; +} + +static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable); +} + +static const struct mmc_host_ops jz4740_mmc_ops = { + .request = jz4740_mmc_request, + .set_ios = jz4740_mmc_set_ios, + .get_ro = jz4740_mmc_get_ro, + .get_cd = jz4740_mmc_get_cd, + .enable_sdio_irq = jz4740_mmc_enable_sdio_irq, +}; + +static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = { + JZ_GPIO_BULK_PIN(MSC_CMD), + JZ_GPIO_BULK_PIN(MSC_CLK), + JZ_GPIO_BULK_PIN(MSC_DATA0), + JZ_GPIO_BULK_PIN(MSC_DATA1), + JZ_GPIO_BULK_PIN(MSC_DATA2), + JZ_GPIO_BULK_PIN(MSC_DATA3), +}; + +static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return 0; + + if (gpio_is_valid(pdata->gpio_card_detect)) { + ret = gpio_request(pdata->gpio_card_detect, "MMC detect change"); + if (ret) { + dev_err(&pdev->dev, "Failed to request detect change gpio\n"); + goto err; + } + gpio_direction_input(pdata->gpio_card_detect); + } + + if (gpio_is_valid(pdata->gpio_read_only)) { + ret = gpio_request(pdata->gpio_read_only, "MMC read only"); + if (ret) { + dev_err(&pdev->dev, "Failed to request read only gpio: %d\n", ret); + goto err_free_gpio_card_detect; + } + gpio_direction_input(pdata->gpio_read_only); + } + + if (gpio_is_valid(pdata->gpio_power)) { + ret = gpio_request(pdata->gpio_power, "MMC power"); + if (ret) { + dev_err(&pdev->dev, "Failed to request power gpio: %d\n", ret); + goto err_free_gpio_read_only; + } + gpio_direction_output(pdata->gpio_power, pdata->power_active_low); + } + + return 0; + +err_free_gpio_read_only: + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); +err_free_gpio_card_detect: + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +err: + return ret; +} + +static void jz4740_mmc_free_gpios(struct platform_device *pdev) +{ + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return; + + if (gpio_is_valid(pdata->gpio_power)) + gpio_free(pdata->gpio_power); + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +} + +static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host) +{ + size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins); + if (host->pdata && host->pdata->data_1bit) + num_pins -= 3; + + return num_pins; +} + +static int __devinit jz4740_mmc_probe(struct platform_device* pdev) +{ + int ret; + struct mmc_host *mmc; + struct jz4740_mmc_host *host; + struct jz4740_mmc_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev); + if (!mmc) { + dev_err(&pdev->dev, "Failed to alloc mmc host structure\n"); + return -ENOMEM; + } + + host = mmc_priv(mmc); + host->pdata = pdata; + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + ret = host->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free_host; + } + + host->clk = clk_get(&pdev->dev, "mmc"); + if (!host->clk) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get mmc clock\n"); + goto err_free_host; + } + + host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get base platform memory\n"); + goto err_clk_put; + } + + host->mem = request_mem_region(host->mem->start, + resource_size(host->mem), pdev->name); + if (!host->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request base memory region\n"); + goto err_clk_put; + } + + host->base = ioremap_nocache(host->mem->start, resource_size(host->mem)); + if (!host->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap base memory\n"); + goto err_release_mem_region; + } + + ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + if (ret) { + dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret); + goto err_iounmap; + } + + ret = jz4740_mmc_request_gpios(pdev); + if (ret) + goto err_gpio_bulk_free; + + mmc->ops = &jz4740_mmc_ops; + mmc->f_min = JZ_MMC_CLK_RATE / 128; + mmc->f_max = JZ_MMC_CLK_RATE; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA; + mmc->caps |= MMC_CAP_SDIO_IRQ; + + mmc->max_blk_size = (1 << 10) - 1; + mmc->max_blk_count = (1 << 15) - 1; + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + mmc->max_seg_size = mmc->max_req_size; + + host->mmc = mmc; + host->pdev = pdev; + host->max_clock = JZ_MMC_CLK_RATE; + spin_lock_init(&host->lock); + host->irq_mask = 0xffff; + + host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect); + + if (host->card_detect_irq < 0) { + dev_warn(&pdev->dev, "Failed to get irq for card detect gpio\n"); + } else { + ret = request_irq(host->card_detect_irq, + jz4740_mmc_card_detect_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "MMC card detect", host); + + if (ret) { + dev_err(&pdev->dev, "Failed to request card detect irq"); + goto err_free_gpios; + } + } + + ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0, + dev_name(&pdev->dev), host); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_free_card_detect_irq; + } + + jz4740_mmc_reset(host); + jz4740_mmc_clock_disable(host); + setup_timer(&host->timeout_timer, jz4740_mmc_timeout, + (unsigned long)host); + /* It is not that important when it times out, it just needs to timeout. */ + set_timer_slack(&host->timeout_timer, HZ); + + platform_set_drvdata(pdev, host); + ret = mmc_add_host(mmc); + + if (ret) { + dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret); + goto err_free_irq; + } + dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n"); + + return 0; + +err_free_irq: + free_irq(host->irq, host); +err_free_card_detect_irq: + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); +err_free_gpios: + jz4740_mmc_free_gpios(pdev); +err_gpio_bulk_free: + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); +err_iounmap: + iounmap(host->base); +err_release_mem_region: + release_mem_region(host->mem->start, resource_size(host->mem)); +err_clk_put: + clk_put(host->clk); +err_free_host: + platform_set_drvdata(pdev, NULL); + mmc_free_host(mmc); + + return ret; +} + +static int __devexit jz4740_mmc_remove(struct platform_device *pdev) +{ + struct jz4740_mmc_host *host = platform_get_drvdata(pdev); + + del_timer_sync(&host->timeout_timer); + jz4740_mmc_set_irq_enabled(host, 0xff, false); + jz4740_mmc_reset(host); + + mmc_remove_host(host->mmc); + + free_irq(host->irq, host); + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); + + jz4740_mmc_free_gpios(pdev); + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + iounmap(host->base); + release_mem_region(host->mem->start, resource_size(host->mem)); + + clk_put(host->clk); + + platform_set_drvdata(pdev, NULL); + mmc_free_host(host->mmc); + + return 0; +} + +#ifdef CONFIG_PM + +static int jz4740_mmc_suspend(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + mmc_suspend_host(host->mmc); + + jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + return 0; +} + +static int jz4740_mmc_resume(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + mmc_resume_host(host->mmc); + + return 0; +} + +const struct dev_pm_ops jz4740_mmc_pm_ops = { + .suspend = jz4740_mmc_suspend, + .resume = jz4740_mmc_resume, + .poweroff = jz4740_mmc_suspend, + .restore = jz4740_mmc_resume, +}; + +#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops) +#else +#define JZ4740_MMC_PM_OPS NULL +#endif + +static struct platform_driver jz4740_mmc_driver = { + .probe = jz4740_mmc_probe, + .remove = __devexit_p(jz4740_mmc_remove), + .driver = { + .name = "jz4740-mmc", + .owner = THIS_MODULE, + .pm = JZ4740_MMC_PM_OPS, + }, +}; + +static int __init jz4740_mmc_init(void) +{ + return platform_driver_register(&jz4740_mmc_driver); +} +module_init(jz4740_mmc_init); + +static void __exit jz4740_mmc_exit(void) +{ + platform_driver_unregister(&jz4740_mmc_driver); +} +module_exit(jz4740_mmc_exit); + +MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); diff --git a/include/linux/mmc/jz4740_mmc.h b/include/linux/mmc/jz4740_mmc.h new file mode 100644 index 0000000..8543f43 --- /dev/null +++ b/include/linux/mmc/jz4740_mmc.h @@ -0,0 +1,15 @@ +#ifndef __LINUX_MMC_JZ4740_MMC +#define __LINUX_MMC_JZ4740_MMC + +struct jz4740_mmc_platform_data { + int gpio_power; + int gpio_card_detect; + int gpio_read_only; + unsigned card_detect_active_low:1; + unsigned read_only_active_low:1; + unsigned power_active_low:1; + + unsigned data_1bit:1; +}; + +#endif -- 1.5.6.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v3] MMC: Add JZ4740 mmc driver 2010-06-28 1:20 ` [PATCH v3] " Lars-Peter Clausen @ 2010-06-29 20:17 ` Matt Fleming 2010-07-01 15:47 ` Lars-Peter Clausen 2010-06-30 20:55 ` Andrew Morton 2010-07-12 21:33 ` [PATCH v4] " Lars-Peter Clausen 2 siblings, 1 reply; 20+ messages in thread From: Matt Fleming @ 2010-06-29 20:17 UTC (permalink / raw) Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-mmc, Ralf Baechle On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote: > This patch adds support for the mmc controller on JZ4740 SoCs. > > Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> > Cc: Andrew Morton <akpm@linux-foundation.org> > Cc: Matt Fleming <matt@console-pimps.org> > Cc: linux-mmc@vger.kernel.org > > --- > Changes since v1 > - Do not request IRQ with IRQF_DISABLED since it is a noop now > - Use a generous slack for the timeout timer. It does not need to be accurate. > Changes since v2 > - Use sg_mapping_to iterate over sg elements in mmc read and write functions > - Use bitops instead of a spinlock and a variable for testing whether a request > has been finished. > - Rework irq and timeout handling in order to get rid of locking in hot paths Acked-by: Matt Fleming <matt@console-pimps.org> Are you planning on maintaining this driver? If so, it'd be a good idea to update MAINTAINERS. ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3] MMC: Add JZ4740 mmc driver 2010-06-29 20:17 ` Matt Fleming @ 2010-07-01 15:47 ` Lars-Peter Clausen 0 siblings, 0 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-01 15:47 UTC (permalink / raw) To: Matt Fleming Cc: linux-mips, linux-kernel, Andrew Morton, linux-mmc, Ralf Baechle -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Matt Fleming wrote: > On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote: >> This patch adds support for the mmc controller on JZ4740 SoCs. >> >> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> >> Cc: Andrew Morton <akpm@linux-foundation.org> >> Cc: Matt Fleming <matt@console-pimps.org> >> Cc: linux-mmc@vger.kernel.org >> >> --- >> Changes since v1 >> - Do not request IRQ with IRQF_DISABLED since it is a noop now >> - Use a generous slack for the timeout timer. It does not need to be accurate. >> Changes since v2 >> - Use sg_mapping_to iterate over sg elements in mmc read and write functions >> - Use bitops instead of a spinlock and a variable for testing whether a request >> has been finished. >> - Rework irq and timeout handling in order to get rid of locking in hot paths > > Acked-by: Matt Fleming <matt@console-pimps.org> > > Are you planning on maintaining this driver? If so, it'd be a good idea > to update MAINTAINERS. Hi Thanks for reviewing the patch. I guess I should send a MAINTAINERS patch which adds entries for all of the JZ4740 drivers. - - Lars -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkwsuJUACgkQBX4mSR26RiNHVgCfTq+tc2I1QBniqijyUjNDxPIX GsEAn1xgPWz+L0uqHWthzJ+lMtFaUBtY =nVt9 -----END PGP SIGNATURE----- ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3] MMC: Add JZ4740 mmc driver 2010-06-28 1:20 ` [PATCH v3] " Lars-Peter Clausen 2010-06-29 20:17 ` Matt Fleming @ 2010-06-30 20:55 ` Andrew Morton 2010-07-01 15:45 ` Lars-Peter Clausen 2010-07-12 21:33 ` [PATCH v4] " Lars-Peter Clausen 2 siblings, 1 reply; 20+ messages in thread From: Andrew Morton @ 2010-06-30 20:55 UTC (permalink / raw) To: Lars-Peter Clausen Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc On Mon, 28 Jun 2010 03:20:41 +0200 Lars-Peter Clausen <lars@metafoo.de> wrote: > This patch adds support for the mmc controller on JZ4740 SoCs. > > Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> > Cc: Andrew Morton <akpm@linux-foundation.org> > Cc: Matt Fleming <matt@console-pimps.org> > Cc: linux-mmc@vger.kernel.org > > ... > > +#define JZ4740_MMC_MAX_TIMEOUT 10000000 That was a really big timeout. How long do 1e7 readw's take? Oh well. > > ... > > +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host) > +{ > + uint32_t status; > + > + writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL); > + do { > + status = readl(host->base + JZ_REG_MMC_STATUS); > + } while (status & JZ_MMC_STATUS_CLK_EN); > +} > + > +static void jz4740_mmc_reset(struct jz4740_mmc_host *host) > +{ > + uint32_t status; > + > + writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL); > + udelay(10); > + do { > + status = readl(host->base + JZ_REG_MMC_STATUS); > + } while (status & JZ_MMC_STATUS_IS_RESETTING); > +} Maybe these should have a timeout too? > > ... > > +static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host, > + unsigned int irq) > +{ > + unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT; > + uint16_t status; > + > + do { > + status = readw(host->base + JZ_REG_MMC_IREG); > + } while (!(status & irq) && --timeout); > + > + return timeout; > +} This guy's too big to inline. Recent gcc's know that and they tend to uninline such things behind your back anwyay. > > ... > > +struct jz4740_mmc_platform_data { > + int gpio_power; > + int gpio_card_detect; > + int gpio_read_only; > + unsigned card_detect_active_low:1; > + unsigned read_only_active_low:1; > + unsigned power_active_low:1; > + > + unsigned data_1bit:1; > +}; The bitfields will all share the same word, so modification of one field can race against modification of another field. Hence some form of locking which covers *all* the bitfields is needed. Is that a problem in this driver? ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v3] MMC: Add JZ4740 mmc driver 2010-06-30 20:55 ` Andrew Morton @ 2010-07-01 15:45 ` Lars-Peter Clausen 0 siblings, 0 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-01 15:45 UTC (permalink / raw) To: Andrew Morton Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Hi Andrew Morton wrote: > On Mon, 28 Jun 2010 03:20:41 +0200 > Lars-Peter Clausen <lars@metafoo.de> wrote: > >> This patch adds support for the mmc controller on JZ4740 SoCs. >> >> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> >> Cc: Andrew Morton <akpm@linux-foundation.org> >> Cc: Matt Fleming <matt@console-pimps.org> >> Cc: linux-mmc@vger.kernel.org >> >> ... >> >> +#define JZ4740_MMC_MAX_TIMEOUT 10000000 > > That was a really big timeout. How long do 1e7 readw's take? Oh well. > Hm, right. It doesn't hurt though. I'll do some tests and to try to come up with a more realistic value. >> ... >> >> +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host) >> +{ >> + uint32_t status; >> + >> + writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL); >> + do { >> + status = readl(host->base + JZ_REG_MMC_STATUS); >> + } while (status & JZ_MMC_STATUS_CLK_EN); >> +} >> + >> +static void jz4740_mmc_reset(struct jz4740_mmc_host *host) >> +{ >> + uint32_t status; >> + >> + writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL); >> + udelay(10); >> + do { >> + status = readl(host->base + JZ_REG_MMC_STATUS); >> + } while (status & JZ_MMC_STATUS_IS_RESETTING); >> +} > > Maybe these should have a timeout too? Its very unlikely that these will ever timeout. But right, to be on the safe, there should be a timeout as well. > >> ... >> >> +static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host, >> + unsigned int irq) >> +{ >> + unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT; >> + uint16_t status; >> + >> + do { >> + status = readw(host->base + JZ_REG_MMC_IREG); >> + } while (!(status & irq) && --timeout); >> + >> + return timeout; >> +} > > This guy's too big to inline. Recent gcc's know that and they tend to > uninline such things behind your back anwyay. Actually, even without the inline attribute and compiling with -Os gcc will inline this function by itself. > >> ... >> >> +struct jz4740_mmc_platform_data { >> + int gpio_power; >> + int gpio_card_detect; >> + int gpio_read_only; >> + unsigned card_detect_active_low:1; >> + unsigned read_only_active_low:1; >> + unsigned power_active_low:1; >> + >> + unsigned data_1bit:1; >> +}; > > The bitfields will all share the same word, so modification of one > field can race against modification of another field. Hence some form > of locking which covers *all* the bitfields is needed. > > Is that a problem in this driver? > They are all read-only in the driver. Thanks for reviewing the patch - - Lars -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEYEARECAAYFAkwsuDQACgkQBX4mSR26RiOiMACeMMNj4koCYFAUnxyM0LBr+wOZ x6oAnizk5CaAvZjQ2doXrD6ZYgDeNjSr =92D2 -----END PGP SIGNATURE----- ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v4] MMC: Add JZ4740 mmc driver 2010-06-28 1:20 ` [PATCH v3] " Lars-Peter Clausen 2010-06-29 20:17 ` Matt Fleming 2010-06-30 20:55 ` Andrew Morton @ 2010-07-12 21:33 ` Lars-Peter Clausen 2010-07-12 21:41 ` Randy Dunlap 2010-07-12 22:20 ` [PATCH v5] " Lars-Peter Clausen 2 siblings, 2 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-12 21:33 UTC (permalink / raw) To: Ralf Baechle Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, Matt Fleming, linux-mmc This patch adds support for the mmc controller on JZ4740 SoCs. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Acked-by: Matt Fleming <matt@console-pimps.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Matt Fleming <matt@console-pimps.org> Cc: linux-mmc@vger.kernel.org --- Changes since v1 - Do not request IRQ with IRQF_DISABLED since it is a noop now - Use a generous slack for the timeout timer. It does not need to be accurate. Changes since v2 - Use sg_mapping_to iterate over sg elements in mmc read and write functions - Use bitops instead of a spinlock and a variable for testing whether a request has been finished. - Rework irq and timeout handling in order to get rid of locking in hot paths Changes since v3 - Drastically decrease IRQ poll timeout. Now when the poll timeout is reached the IRQ which was polled is enabled. A variable keeps track of what state the driver is in and what has to be done next when the IRQ handler is entered again. By doing so busy looping is reduced, but overall performance can be maintained. - Move header file from include/linux/mmc to arch/mips/include/asm/mach-jz4740 --- arch/mips/include/asm/mach-jz4740/jz4740_mmc.h | 15 + drivers/mmc/host/Kconfig | 8 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/jz4740_mmc.c | 1024 ++++++++++++++++++++++++ 4 files changed, 1048 insertions(+), 0 deletions(-) create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h create mode 100644 drivers/mmc/host/jz4740_mmc.c diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h new file mode 100644 index 0000000..8543f43 --- /dev/null +++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h @@ -0,0 +1,15 @@ +#ifndef __LINUX_MMC_JZ4740_MMC +#define __LINUX_MMC_JZ4740_MMC + +struct jz4740_mmc_platform_data { + int gpio_power; + int gpio_card_detect; + int gpio_read_only; + unsigned card_detect_active_low:1; + unsigned read_only_active_low:1; + unsigned power_active_low:1; + + unsigned data_1bit:1; +}; + +#endif diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f06d06e..546fc49 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -81,6 +81,14 @@ config MMC_RICOH_MMC If unsure, say Y. +config MMC_JZ4740 + tristate "JZ4740 SD/Multimedia Card Interface support" + depends on MACH_JZ4740 + help + This selects the Ingenic Z4740 SD/Multimedia card Interface. + If you have an ngenic platform with a Multimedia Card slot, + say Y or M here. + config MMC_SDHCI_OF tristate "SDHCI support on OpenFirmware platforms" depends on MMC_SDHCI && PPC_OF diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e30c2ee..f4e53c9 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o sdhci-of-y := sdhci-of-core.o diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c new file mode 100644 index 0000000..82a449a --- /dev/null +++ b/drivers/mmc/host/jz4740_mmc.c @@ -0,0 +1,1024 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SD/MMC controller driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mmc/host.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/scatterlist.h> +#include <linux/clk.h> + +#include <linux/bitops.h> +#include <linux/gpio.h> +#include <asm/mach-jz4740/gpio.h> +#include <asm/cacheflush.h> +#include <linux/dma-mapping.h> + +#include <asm/mach-jz4740/jz4740_mmc.h> + +#define JZ_REG_MMC_STRPCL 0x00 +#define JZ_REG_MMC_STATUS 0x04 +#define JZ_REG_MMC_CLKRT 0x08 +#define JZ_REG_MMC_CMDAT 0x0C +#define JZ_REG_MMC_RESTO 0x10 +#define JZ_REG_MMC_RDTO 0x14 +#define JZ_REG_MMC_BLKLEN 0x18 +#define JZ_REG_MMC_NOB 0x1C +#define JZ_REG_MMC_SNOB 0x20 +#define JZ_REG_MMC_IMASK 0x24 +#define JZ_REG_MMC_IREG 0x28 +#define JZ_REG_MMC_CMD 0x2C +#define JZ_REG_MMC_ARG 0x30 +#define JZ_REG_MMC_RESP_FIFO 0x34 +#define JZ_REG_MMC_RXFIFO 0x38 +#define JZ_REG_MMC_TXFIFO 0x3C + +#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7) +#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6) +#define JZ_MMC_STRPCL_START_READWAIT BIT(5) +#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4) +#define JZ_MMC_STRPCL_RESET BIT(3) +#define JZ_MMC_STRPCL_START_OP BIT(2) +#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0)) +#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0) +#define JZ_MMC_STRPCL_CLOCK_START BIT(1) + + +#define JZ_MMC_STATUS_IS_RESETTING BIT(15) +#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14) +#define JZ_MMC_STATUS_PRG_DONE BIT(13) +#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12) +#define JZ_MMC_STATUS_END_CMD_RES BIT(11) +#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10) +#define JZ_MMC_STATUS_IS_READWAIT BIT(9) +#define JZ_MMC_STATUS_CLK_EN BIT(8) +#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7) +#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6) +#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5) +#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4) +#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3) +#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2) +#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1) +#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0) + +#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0)) +#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2)) + + +#define JZ_MMC_CMDAT_IO_ABORT BIT(11) +#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10) +#define JZ_MMC_CMDAT_DMA_EN BIT(8) +#define JZ_MMC_CMDAT_INIT BIT(7) +#define JZ_MMC_CMDAT_BUSY BIT(6) +#define JZ_MMC_CMDAT_STREAM BIT(5) +#define JZ_MMC_CMDAT_WRITE BIT(4) +#define JZ_MMC_CMDAT_DATA_EN BIT(3) +#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0)) +#define JZ_MMC_CMDAT_RSP_R1 1 +#define JZ_MMC_CMDAT_RSP_R2 2 +#define JZ_MMC_CMDAT_RSP_R3 3 + +#define JZ_MMC_IRQ_SDIO BIT(7) +#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6) +#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5) +#define JZ_MMC_IRQ_END_CMD_RES BIT(2) +#define JZ_MMC_IRQ_PRG_DONE BIT(1) +#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0) + + +#define JZ_MMC_CLK_RATE 24000000 + +enum jz4740_mmc_state { + JZ4740_MMC_STATE_READ_RESPONSE, + JZ4740_MMC_STATE_TRANSFER_DATA, + JZ4740_MMC_STATE_SEND_STOP, + JZ4740_MMC_STATE_DONE, +}; + +struct jz4740_mmc_host { + struct mmc_host *mmc; + struct platform_device *pdev; + struct jz4740_mmc_platform_data *pdata; + struct clk *clk; + + int irq; + int card_detect_irq; + + struct resource *mem; + void __iomem *base; + struct mmc_request *req; + struct mmc_command *cmd; + + unsigned long waiting; + + uint32_t cmdat; + + uint16_t irq_mask; + + spinlock_t lock; + + struct timer_list timeout_timer; + struct sg_mapping_iter miter; + enum jz4740_mmc_state state; +}; + +static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host, + unsigned int irq, bool enabled) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (enabled) + host->irq_mask &= ~irq; + else + host->irq_mask |= irq; + spin_unlock_irqrestore(&host->lock, flags); + + writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK); +} + +static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host, + bool start_transfer) +{ + uint16_t val = JZ_MMC_STRPCL_CLOCK_START; + + if (start_transfer) + val |= JZ_MMC_STRPCL_START_OP; + + writew(val, host->base + JZ_REG_MMC_STRPCL); +} + +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host) +{ + uint32_t status; + unsigned int timeout = 1000; + + writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_CLK_EN && --timeout); +} + +static void jz4740_mmc_reset(struct jz4740_mmc_host *host) +{ + uint32_t status; + unsigned int timeout = 1000; + + writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL); + udelay(10); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_IS_RESETTING && --timeout); +} + +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host) +{ + struct mmc_request *req; + + req = host->req; + host->req = NULL; + + mmc_request_done(host->mmc, req); +} +static unsigned int jz4740_mmc_poll_irq(struct jz4740_mmc_host *host, + unsigned int irq) +{ + unsigned int timeout = 1000; + uint16_t status; + + do { + status = readw(host->base + JZ_REG_MMC_IREG); + } while (!(status & irq) && --timeout); + + if (timeout == 0) { + set_bit(0, &host->waiting); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); + jz4740_mmc_set_irq_enabled(host, irq, true); + return true; + } + + return false; +} + +static void jz4740_mmc_transfer_check_state(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + int status; + + status = readl(host->base + JZ_REG_MMC_STATUS); + if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) { + if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) { + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } else { + host->req->cmd->error = -EIO; + data->error = -EIO; + } + } +} + +static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter *miter = &host->miter; + uint32_t *buf; + bool timeout; + size_t i, j; + + while (sg_miter_next(miter)) { + buf = miter->addr; + i = miter->length / 4; + j = i / 8; + i = i & 0x7; + while (j) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + writel(buf[0], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[1], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[2], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[3], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[4], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[5], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[6], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[7], host->base + JZ_REG_MMC_TXFIFO); + buf += 8; + --j; + } + if (unlikely(i)) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + while (i) { + writel(*buf, host->base + JZ_REG_MMC_TXFIFO); + ++buf; + --i; + } + } + data->bytes_xfered += miter->length; + } + sg_miter_stop(miter); + + return false; + +poll_timeout: + miter->consumed = (void *)buf - miter->addr; + data->bytes_xfered += miter->consumed; + sg_miter_stop(miter); + + return true; +} + +static bool jz4740_mmc_read_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter *miter = &host->miter; + uint32_t *buf; + uint32_t d; + uint16_t status; + size_t i, j; + unsigned int timeout; + + while (sg_miter_next(miter)) { + buf = miter->addr; + i = miter->length; + j = i / 32; + i = i & 0x1f; + while (j) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + buf[0] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[1] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[2] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[3] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[4] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[5] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[6] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[7] = readl(host->base + JZ_REG_MMC_RXFIFO); + + buf += 8; + --j; + } + + if (unlikely(i)) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + while (i >= 4) { + *buf++ = readl(host->base + JZ_REG_MMC_RXFIFO); + i -= 4; + } + if (unlikely(i > 0)) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + memcpy(buf, &d, i); + } + } + data->bytes_xfered += miter->length; + + /* This can go away once MIPS implements + * flush_kernel_dcache_page */ + flush_dcache_page(miter->page); + } + sg_miter_stop(miter); + + /* For whatever reason there is sometime one word more in the fifo then + * requested */ + timeout = 1000; + status = readl(host->base + JZ_REG_MMC_STATUS); + while (!(status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) && --timeout) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + status = readl(host->base + JZ_REG_MMC_STATUS); + } + + return false; + +poll_timeout: + miter->consumed = (void *)buf - miter->addr; + data->bytes_xfered += miter->consumed; + sg_miter_stop(miter); + + return true; +} + +static void jz4740_mmc_timeout(unsigned long data) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data; + + if (!test_and_clear_bit(0, &host->waiting)) + return; + + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false); + + host->req->cmd->error = -ETIMEDOUT; + jz4740_mmc_request_done(host); +} + +static void jz4740_mmc_read_response(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + int i; + uint16_t tmp; + + if (cmd->flags & MMC_RSP_136) { + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + for (i = 0; i < 4; ++i) { + cmd->resp[i] = tmp << 24; + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + cmd->resp[i] |= tmp << 8; + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + cmd->resp[i] |= tmp >> 8; + } + } else { + cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff; + } +} + +static void jz4740_mmc_send_command(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + uint32_t cmdat = host->cmdat; + + host->cmdat &= ~JZ_MMC_CMDAT_INIT; + jz4740_mmc_clock_disable(host); + + host->cmd = cmd; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= JZ_MMC_CMDAT_BUSY; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1B: + case MMC_RSP_R1: + cmdat |= JZ_MMC_CMDAT_RSP_R1; + break; + case MMC_RSP_R2: + cmdat |= JZ_MMC_CMDAT_RSP_R2; + break; + case MMC_RSP_R3: + cmdat |= JZ_MMC_CMDAT_RSP_R3; + break; + default: + break; + } + + if (cmd->data) { + cmdat |= JZ_MMC_CMDAT_DATA_EN; + if (cmd->data->flags & MMC_DATA_WRITE) + cmdat |= JZ_MMC_CMDAT_WRITE; + if (cmd->data->flags & MMC_DATA_STREAM) + cmdat |= JZ_MMC_CMDAT_STREAM; + + writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN); + writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB); + } + + writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD); + writel(cmd->arg, host->base + JZ_REG_MMC_ARG); + writel(cmdat, host->base + JZ_REG_MMC_CMDAT); + + jz4740_mmc_clock_enable(host, 1); +} + +static void jz_mmc_prepare_data_transfer(struct jz4740_mmc_host *host) +{ + struct mmc_command *cmd = host->req->cmd; + struct mmc_data *data = cmd->data; + int direction; + + if (cmd->data->flags & MMC_DATA_READ) + direction = SG_MITER_TO_SG; + else + direction = SG_MITER_FROM_SG; + + sg_miter_start(&host->miter, data->sg, data->sg_len, direction); +} + + +static irqreturn_t jz_mmc_irq_worker(int irq, void *devid) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid; + struct mmc_command *cmd = host->req->cmd; + struct mmc_request *req = host->req; + bool timedout = false; + + if (cmd->error) + host->state = JZ4740_MMC_STATE_DONE; + + switch (host->state) { + case JZ4740_MMC_STATE_READ_RESPONSE: + if (cmd->flags & MMC_RSP_PRESENT) + jz4740_mmc_read_response(host, cmd); + + if (!cmd->data) + break; + + jz_mmc_prepare_data_transfer(host); + + case JZ4740_MMC_STATE_TRANSFER_DATA: + if (cmd->data->flags & MMC_DATA_READ) + timedout = jz4740_mmc_read_data(host, cmd->data); + else + timedout = jz4740_mmc_write_data(host, cmd->data); + + if (unlikely(timedout)) { + host->state = JZ4740_MMC_STATE_TRANSFER_DATA; + break; + } + + jz4740_mmc_transfer_check_state(host, cmd->data); + + timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE); + if (unlikely(timedout)) { + host->state = JZ4740_MMC_STATE_SEND_STOP; + break; + } + writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG); + + case JZ4740_MMC_STATE_SEND_STOP: + if (!req->stop) + break; + + jz4740_mmc_send_command(host, req->stop); + + timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_PRG_DONE); + if (timedout) { + host->state = JZ4740_MMC_STATE_DONE; + break; + } + case JZ4740_MMC_STATE_DONE: + break; + } + + if (!timedout) + jz4740_mmc_request_done(host); + + return IRQ_HANDLED; +} + +static irqreturn_t jz_mmc_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + uint16_t irq_reg, status, tmp; + + irq_reg = readw(host->base + JZ_REG_MMC_IREG); + + tmp = irq_reg; + irq_reg &= ~host->irq_mask; + + tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ | + JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE); + + if (tmp != irq_reg) + writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG); + + if (irq_reg & JZ_MMC_IRQ_SDIO) { + writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG); + mmc_signal_sdio_irq(host->mmc); + irq_reg &= ~JZ_MMC_IRQ_SDIO; + } + + if (host->req && host->cmd && irq_reg) { + if (test_and_clear_bit(0, &host->waiting)) { + del_timer(&host->timeout_timer); + + status = readl(host->base + JZ_REG_MMC_STATUS); + + if (status & JZ_MMC_STATUS_TIMEOUT_RES) { + host->cmd->error = -ETIMEDOUT; + } else if (status & JZ_MMC_STATUS_CRC_RES_ERR) { + host->cmd->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } + + jz4740_mmc_set_irq_enabled(host, irq_reg, false); + writew(irq_reg, host->base + JZ_REG_MMC_IREG); + + return IRQ_WAKE_THREAD; + } + } + + return IRQ_HANDLED; +} + +static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate) +{ + int div = 0; + int real_rate; + + jz4740_mmc_clock_disable(host); + clk_set_rate(host->clk, JZ_MMC_CLK_RATE); + + real_rate = clk_get_rate(host->clk); + + while (real_rate > rate && div < 7) { + ++div; + real_rate >>= 1; + } + + writew(div, host->base + JZ_REG_MMC_CLKRT); + return real_rate; +} + +static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + + host->req = req; + + writew(0xffff, host->base + JZ_REG_MMC_IREG); + + writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true); + + host->state = JZ4740_MMC_STATE_READ_RESPONSE; + set_bit(0, &host->waiting); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); + jz4740_mmc_send_command(host, req->cmd); +} + +static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (ios->clock) + jz4740_mmc_set_clock_rate(host, ios->clock); + + switch (ios->power_mode) { + case MMC_POWER_UP: + jz4740_mmc_reset(host); + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + !host->pdata->power_active_low); + host->cmdat |= JZ_MMC_CMDAT_INIT; + clk_enable(host->clk); + break; + case MMC_POWER_ON: + break; + default: + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + host->pdata->power_active_low); + clk_disable(host->clk); + break; + } + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + case MMC_BUS_WIDTH_4: + host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + default: + break; + } +} + +static int jz4740_mmc_get_ro(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_read_only)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_read_only) ^ + host->pdata->read_only_active_low; +} + +static int jz4740_mmc_get_cd(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_card_detect)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_card_detect) ^ + host->pdata->card_detect_active_low; +} + +static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + + mmc_detect_change(host->mmc, HZ / 2); + + return IRQ_HANDLED; +} + +static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable); +} + +static const struct mmc_host_ops jz4740_mmc_ops = { + .request = jz4740_mmc_request, + .set_ios = jz4740_mmc_set_ios, + .get_ro = jz4740_mmc_get_ro, + .get_cd = jz4740_mmc_get_cd, + .enable_sdio_irq = jz4740_mmc_enable_sdio_irq, +}; + +static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = { + JZ_GPIO_BULK_PIN(MSC_CMD), + JZ_GPIO_BULK_PIN(MSC_CLK), + JZ_GPIO_BULK_PIN(MSC_DATA0), + JZ_GPIO_BULK_PIN(MSC_DATA1), + JZ_GPIO_BULK_PIN(MSC_DATA2), + JZ_GPIO_BULK_PIN(MSC_DATA3), +}; + +static int __devinit jz4740_mmc_request_gpio(struct device *dev, int gpio, + const char *name, bool output, int value) +{ + int ret; + + if (!gpio_is_valid(gpio)) + return 0; + + ret = gpio_request(gpio, name); + if (ret) { + dev_err(dev, "Failed to request %s gpio: %d\n", name, ret); + return ret; + } + + if (output) + gpio_direction_output(gpio, value); + else + gpio_direction_input(gpio); + + return 0; +} + +static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return 0; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_card_detect, + "MMC detect change", false, 0); + if (ret) + goto err; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_read_only, + "MMC read only", false, 0); + if (ret) + goto err_free_gpio_card_detect; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_power, + "MMC read only", true, pdata->power_active_low); + if (ret) + goto err_free_gpio_read_only; + + return 0; + +err_free_gpio_read_only: + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); +err_free_gpio_card_detect: + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +err: + return ret; +} + +static int __devinit jz4740_mmc_request_cd_irq(struct platform_device *pdev, + struct jz4740_mmc_host *host) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (gpio_is_valid(pdata->gpio_card_detect)) + return 0; + + host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect); + + if (host->card_detect_irq < 0) { + dev_warn(&pdev->dev, "Failed to get card detect irq\n"); + return 0; + } + return request_irq(host->card_detect_irq, jz4740_mmc_card_detect_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "MMC card detect", host); + + + return ret; +} + +static void jz4740_mmc_free_gpios(struct platform_device *pdev) +{ + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return; + + if (gpio_is_valid(pdata->gpio_power)) + gpio_free(pdata->gpio_power); + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +} + +static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host) +{ + size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins); + if (host->pdata && host->pdata->data_1bit) + num_pins -= 3; + + return num_pins; +} + +static int __devinit jz4740_mmc_probe(struct platform_device* pdev) +{ + int ret; + struct mmc_host *mmc; + struct jz4740_mmc_host *host; + struct jz4740_mmc_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev); + if (!mmc) { + dev_err(&pdev->dev, "Failed to alloc mmc host structure\n"); + return -ENOMEM; + } + + host = mmc_priv(mmc); + host->pdata = pdata; + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + ret = host->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free_host; + } + + host->clk = clk_get(&pdev->dev, "mmc"); + if (!host->clk) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get mmc clock\n"); + goto err_free_host; + } + + host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get base platform memory\n"); + goto err_clk_put; + } + + host->mem = request_mem_region(host->mem->start, + resource_size(host->mem), pdev->name); + if (!host->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request base memory region\n"); + goto err_clk_put; + } + + host->base = ioremap_nocache(host->mem->start, resource_size(host->mem)); + if (!host->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap base memory\n"); + goto err_release_mem_region; + } + + ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + if (ret) { + dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret); + goto err_iounmap; + } + + ret = jz4740_mmc_request_gpios(pdev); + if (ret) + goto err_gpio_bulk_free; + + mmc->ops = &jz4740_mmc_ops; + mmc->f_min = JZ_MMC_CLK_RATE / 128; + mmc->f_max = JZ_MMC_CLK_RATE; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA; + mmc->caps |= MMC_CAP_SDIO_IRQ; + + mmc->max_blk_size = (1 << 10) - 1; + mmc->max_blk_count = (1 << 15) - 1; + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + mmc->max_seg_size = mmc->max_req_size; + + host->mmc = mmc; + host->pdev = pdev; + spin_lock_init(&host->lock); + host->irq_mask = 0xffff; + + ret = jz4740_mmc_request_cd_irq(pdev, host); + if (ret) { + dev_err(&pdev->dev, "Failed to request card detect irq\n"); + goto err_free_gpios; + } + + ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0, + dev_name(&pdev->dev), host); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_free_card_detect_irq; + } + + jz4740_mmc_reset(host); + jz4740_mmc_clock_disable(host); + setup_timer(&host->timeout_timer, jz4740_mmc_timeout, + (unsigned long)host); + /* It is not important when it times out, it just needs to timeout. */ + set_timer_slack(&host->timeout_timer, HZ); + + platform_set_drvdata(pdev, host); + ret = mmc_add_host(mmc); + + if (ret) { + dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret); + goto err_free_irq; + } + dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n"); + + return 0; + +err_free_irq: + free_irq(host->irq, host); +err_free_card_detect_irq: + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); +err_free_gpios: + jz4740_mmc_free_gpios(pdev); +err_gpio_bulk_free: + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); +err_iounmap: + iounmap(host->base); +err_release_mem_region: + release_mem_region(host->mem->start, resource_size(host->mem)); +err_clk_put: + clk_put(host->clk); +err_free_host: + platform_set_drvdata(pdev, NULL); + mmc_free_host(mmc); + + return ret; +} + +static int __devexit jz4740_mmc_remove(struct platform_device *pdev) +{ + struct jz4740_mmc_host *host = platform_get_drvdata(pdev); + + del_timer_sync(&host->timeout_timer); + jz4740_mmc_set_irq_enabled(host, 0xff, false); + jz4740_mmc_reset(host); + + mmc_remove_host(host->mmc); + + free_irq(host->irq, host); + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); + + jz4740_mmc_free_gpios(pdev); + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + iounmap(host->base); + release_mem_region(host->mem->start, resource_size(host->mem)); + + clk_put(host->clk); + + platform_set_drvdata(pdev, NULL); + mmc_free_host(host->mmc); + + return 0; +} + +#ifdef CONFIG_PM + +static int jz4740_mmc_suspend(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + mmc_suspend_host(host->mmc); + + jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + return 0; +} + +static int jz4740_mmc_resume(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + mmc_resume_host(host->mmc); + + return 0; +} + +const struct dev_pm_ops jz4740_mmc_pm_ops = { + .suspend = jz4740_mmc_suspend, + .resume = jz4740_mmc_resume, + .poweroff = jz4740_mmc_suspend, + .restore = jz4740_mmc_resume, +}; + +#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops) +#else +#define JZ4740_MMC_PM_OPS NULL +#endif + +static struct platform_driver jz4740_mmc_driver = { + .probe = jz4740_mmc_probe, + .remove = __devexit_p(jz4740_mmc_remove), + .driver = { + .name = "jz4740-mmc", + .owner = THIS_MODULE, + .pm = JZ4740_MMC_PM_OPS, + }, +}; + +static int __init jz4740_mmc_init(void) +{ + return platform_driver_register(&jz4740_mmc_driver); +} +module_init(jz4740_mmc_init); + +static void __exit jz4740_mmc_exit(void) +{ + platform_driver_unregister(&jz4740_mmc_driver); +} +module_exit(jz4740_mmc_exit); + +MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -- 1.5.6.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v4] MMC: Add JZ4740 mmc driver 2010-07-12 21:33 ` [PATCH v4] " Lars-Peter Clausen @ 2010-07-12 21:41 ` Randy Dunlap 2010-07-12 22:07 ` Lars-Peter Clausen 2010-07-12 22:20 ` [PATCH v5] " Lars-Peter Clausen 1 sibling, 1 reply; 20+ messages in thread From: Randy Dunlap @ 2010-07-12 21:41 UTC (permalink / raw) To: Lars-Peter Clausen Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton, Matt Fleming, linux-mmc Lars-Peter Clausen wrote: > This patch adds support for the mmc controller on JZ4740 SoCs. > > Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> > Acked-by: Matt Fleming <matt@console-pimps.org> > Cc: Andrew Morton <akpm@linux-foundation.org> > Cc: Matt Fleming <matt@console-pimps.org> > Cc: linux-mmc@vger.kernel.org > > --- > arch/mips/include/asm/mach-jz4740/jz4740_mmc.h | 15 + > drivers/mmc/host/Kconfig | 8 + > drivers/mmc/host/Makefile | 1 + > drivers/mmc/host/jz4740_mmc.c | 1024 ++++++++++++++++++++++++ > 4 files changed, 1048 insertions(+), 0 deletions(-) > create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h > create mode 100644 drivers/mmc/host/jz4740_mmc.c > > diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h > new file mode 100644 > index 0000000..8543f43 > --- /dev/null > +++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h > @@ -0,0 +1,15 @@ > +#ifndef __LINUX_MMC_JZ4740_MMC > +#define __LINUX_MMC_JZ4740_MMC > + > +struct jz4740_mmc_platform_data { > + int gpio_power; > + int gpio_card_detect; > + int gpio_read_only; > + unsigned card_detect_active_low:1; > + unsigned read_only_active_low:1; > + unsigned power_active_low:1; > + > + unsigned data_1bit:1; > +}; > + > +#endif > diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig > index f06d06e..546fc49 100644 > --- a/drivers/mmc/host/Kconfig > +++ b/drivers/mmc/host/Kconfig > @@ -81,6 +81,14 @@ config MMC_RICOH_MMC > > If unsure, say Y. > > +config MMC_JZ4740 > + tristate "JZ4740 SD/Multimedia Card Interface support" > + depends on MACH_JZ4740 What tree has the kconfig symbol MACH_JZ4740 in it? I can't seem to find it... Should the depends also say anything about GPIO? I only ask because the header file above mentions gpio. > + help > + This selects the Ingenic Z4740 SD/Multimedia card Interface. > + If you have an ngenic platform with a Multimedia Card slot, Ingenic ? > + say Y or M here. > + > config MMC_SDHCI_OF > tristate "SDHCI support on OpenFirmware platforms" > depends on MMC_SDHCI && PPC_OF ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v4] MMC: Add JZ4740 mmc driver 2010-07-12 21:41 ` Randy Dunlap @ 2010-07-12 22:07 ` Lars-Peter Clausen 0 siblings, 0 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-12 22:07 UTC (permalink / raw) To: Randy Dunlap Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton, Matt Fleming, linux-mmc Hi Randy Dunlap wrote: > Lars-Peter Clausen wrote: >> This patch adds support for the mmc controller on JZ4740 SoCs. >> >> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> >> Acked-by: Matt Fleming <matt@console-pimps.org> >> Cc: Andrew Morton <akpm@linux-foundation.org> >> Cc: Matt Fleming <matt@console-pimps.org> >> Cc: linux-mmc@vger.kernel.org >> >> --- >> arch/mips/include/asm/mach-jz4740/jz4740_mmc.h | 15 + >> drivers/mmc/host/Kconfig | 8 + >> drivers/mmc/host/Makefile | 1 + >> drivers/mmc/host/jz4740_mmc.c | 1024 ++++++++++++++++++++++++ >> 4 files changed, 1048 insertions(+), 0 deletions(-) >> create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h >> create mode 100644 drivers/mmc/host/jz4740_mmc.c >> >> diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h >> new file mode 100644 >> index 0000000..8543f43 >> --- /dev/null >> +++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h >> @@ -0,0 +1,15 @@ >> +#ifndef __LINUX_MMC_JZ4740_MMC >> +#define __LINUX_MMC_JZ4740_MMC >> + >> +struct jz4740_mmc_platform_data { >> + int gpio_power; >> + int gpio_card_detect; >> + int gpio_read_only; >> + unsigned card_detect_active_low:1; >> + unsigned read_only_active_low:1; >> + unsigned power_active_low:1; >> + >> + unsigned data_1bit:1; >> +}; >> + >> +#endif >> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig >> index f06d06e..546fc49 100644 >> --- a/drivers/mmc/host/Kconfig >> +++ b/drivers/mmc/host/Kconfig >> @@ -81,6 +81,14 @@ config MMC_RICOH_MMC >> >> If unsure, say Y. >> >> +config MMC_JZ4740 >> + tristate "JZ4740 SD/Multimedia Card Interface support" >> + depends on MACH_JZ4740 > > What tree has the kconfig symbol MACH_JZ4740 in it? > I can't seem to find it... > > Should the depends also say anything about GPIO? > I only ask because the header file above mentions gpio. > None yet, mips hopefully soon. Version 1 of this patch was part of a series adding support for the JZ4740. Since the jz4740 platform code provides the gpio functions I don't think it is necessary to add an additional depends on GENERIC_GPIO. >> + help >> + This selects the Ingenic Z4740 SD/Multimedia card Interface. >> + If you have an ngenic platform with a Multimedia Card slot, > > Ingenic ? > Woops, yes. >> + say Y or M here. >> + >> config MMC_SDHCI_OF >> tristate "SDHCI support on OpenFirmware platforms" >> depends on MMC_SDHCI && PPC_OF > > Thanks for reviewing - Lars ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v5] MMC: Add JZ4740 mmc driver 2010-07-12 21:33 ` [PATCH v4] " Lars-Peter Clausen 2010-07-12 21:41 ` Randy Dunlap @ 2010-07-12 22:20 ` Lars-Peter Clausen 2010-07-12 22:45 ` Joe Perches 2010-07-15 21:06 ` [PATCH v6] " Lars-Peter Clausen 1 sibling, 2 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-12 22:20 UTC (permalink / raw) To: Ralf Baechle Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, Matt Fleming, linux-mmc This patch adds support for the mmc controller on JZ4740 SoCs. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Acked-by: Matt Fleming <matt@console-pimps.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Matt Fleming <matt@console-pimps.org> Cc: linux-mmc@vger.kernel.org --- Changes since v1 - Do not request IRQ with IRQF_DISABLED since it is a noop now - Use a generous slack for the timeout timer. It does not need to be accurate. Changes since v2 - Use sg_mapping_to iterate over sg elements in mmc read and write functions - Use bitops instead of a spinlock and a variable for testing whether a request has been finished. - Rework irq and timeout handling in order to get rid of locking in hot paths Changes since v3 - Drastically decrease IRQ poll timeout. Now when the poll timeout is reached the IRQ which was polled is enabled. A variable keeps track of what state the driver is in and what has to be done next when the IRQ handler is entered again. By doing so busy looping is reduced, but overall performance can be maintained. - Move header file from include/linux/mmc to arch/mips/include/asm/mach-jz4740 Changes since v4 - Rework Kconfig entry --- arch/mips/include/asm/mach-jz4740/jz4740_mmc.h | 15 + drivers/mmc/host/Kconfig | 9 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/jz4740_mmc.c | 1024 ++++++++++++++++++++++++ 4 files changed, 1049 insertions(+), 0 deletions(-) create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h create mode 100644 drivers/mmc/host/jz4740_mmc.c diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h new file mode 100644 index 0000000..8543f43 --- /dev/null +++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h @@ -0,0 +1,15 @@ +#ifndef __LINUX_MMC_JZ4740_MMC +#define __LINUX_MMC_JZ4740_MMC + +struct jz4740_mmc_platform_data { + int gpio_power; + int gpio_card_detect; + int gpio_read_only; + unsigned card_detect_active_low:1; + unsigned read_only_active_low:1; + unsigned power_active_low:1; + + unsigned data_1bit:1; +}; + +#endif diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f06d06e..d25e22c 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -432,3 +432,12 @@ config MMC_SH_MMCIF This selects the MMC Host Interface controler (MMCIF). This driver supports MMCIF in sh7724/sh7757/sh7372. + +config MMC_JZ4740 + tristate "JZ4740 SD/Multimedia Card Interface support" + depends on MACH_JZ4740 + help + This selects support for the SD/MMC controller on Ingenic JZ4740 + SoCs. + If you have a board based on such a SoC and with a SD/MMC slot, + say Y or M here. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e30c2ee..f4e53c9 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o sdhci-of-y := sdhci-of-core.o diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c new file mode 100644 index 0000000..82a449a --- /dev/null +++ b/drivers/mmc/host/jz4740_mmc.c @@ -0,0 +1,1024 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SD/MMC controller driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mmc/host.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/scatterlist.h> +#include <linux/clk.h> + +#include <linux/bitops.h> +#include <linux/gpio.h> +#include <asm/mach-jz4740/gpio.h> +#include <asm/cacheflush.h> +#include <linux/dma-mapping.h> + +#include <asm/mach-jz4740/jz4740_mmc.h> + +#define JZ_REG_MMC_STRPCL 0x00 +#define JZ_REG_MMC_STATUS 0x04 +#define JZ_REG_MMC_CLKRT 0x08 +#define JZ_REG_MMC_CMDAT 0x0C +#define JZ_REG_MMC_RESTO 0x10 +#define JZ_REG_MMC_RDTO 0x14 +#define JZ_REG_MMC_BLKLEN 0x18 +#define JZ_REG_MMC_NOB 0x1C +#define JZ_REG_MMC_SNOB 0x20 +#define JZ_REG_MMC_IMASK 0x24 +#define JZ_REG_MMC_IREG 0x28 +#define JZ_REG_MMC_CMD 0x2C +#define JZ_REG_MMC_ARG 0x30 +#define JZ_REG_MMC_RESP_FIFO 0x34 +#define JZ_REG_MMC_RXFIFO 0x38 +#define JZ_REG_MMC_TXFIFO 0x3C + +#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7) +#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6) +#define JZ_MMC_STRPCL_START_READWAIT BIT(5) +#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4) +#define JZ_MMC_STRPCL_RESET BIT(3) +#define JZ_MMC_STRPCL_START_OP BIT(2) +#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0)) +#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0) +#define JZ_MMC_STRPCL_CLOCK_START BIT(1) + + +#define JZ_MMC_STATUS_IS_RESETTING BIT(15) +#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14) +#define JZ_MMC_STATUS_PRG_DONE BIT(13) +#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12) +#define JZ_MMC_STATUS_END_CMD_RES BIT(11) +#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10) +#define JZ_MMC_STATUS_IS_READWAIT BIT(9) +#define JZ_MMC_STATUS_CLK_EN BIT(8) +#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7) +#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6) +#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5) +#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4) +#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3) +#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2) +#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1) +#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0) + +#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0)) +#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2)) + + +#define JZ_MMC_CMDAT_IO_ABORT BIT(11) +#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10) +#define JZ_MMC_CMDAT_DMA_EN BIT(8) +#define JZ_MMC_CMDAT_INIT BIT(7) +#define JZ_MMC_CMDAT_BUSY BIT(6) +#define JZ_MMC_CMDAT_STREAM BIT(5) +#define JZ_MMC_CMDAT_WRITE BIT(4) +#define JZ_MMC_CMDAT_DATA_EN BIT(3) +#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0)) +#define JZ_MMC_CMDAT_RSP_R1 1 +#define JZ_MMC_CMDAT_RSP_R2 2 +#define JZ_MMC_CMDAT_RSP_R3 3 + +#define JZ_MMC_IRQ_SDIO BIT(7) +#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6) +#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5) +#define JZ_MMC_IRQ_END_CMD_RES BIT(2) +#define JZ_MMC_IRQ_PRG_DONE BIT(1) +#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0) + + +#define JZ_MMC_CLK_RATE 24000000 + +enum jz4740_mmc_state { + JZ4740_MMC_STATE_READ_RESPONSE, + JZ4740_MMC_STATE_TRANSFER_DATA, + JZ4740_MMC_STATE_SEND_STOP, + JZ4740_MMC_STATE_DONE, +}; + +struct jz4740_mmc_host { + struct mmc_host *mmc; + struct platform_device *pdev; + struct jz4740_mmc_platform_data *pdata; + struct clk *clk; + + int irq; + int card_detect_irq; + + struct resource *mem; + void __iomem *base; + struct mmc_request *req; + struct mmc_command *cmd; + + unsigned long waiting; + + uint32_t cmdat; + + uint16_t irq_mask; + + spinlock_t lock; + + struct timer_list timeout_timer; + struct sg_mapping_iter miter; + enum jz4740_mmc_state state; +}; + +static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host, + unsigned int irq, bool enabled) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (enabled) + host->irq_mask &= ~irq; + else + host->irq_mask |= irq; + spin_unlock_irqrestore(&host->lock, flags); + + writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK); +} + +static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host, + bool start_transfer) +{ + uint16_t val = JZ_MMC_STRPCL_CLOCK_START; + + if (start_transfer) + val |= JZ_MMC_STRPCL_START_OP; + + writew(val, host->base + JZ_REG_MMC_STRPCL); +} + +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host) +{ + uint32_t status; + unsigned int timeout = 1000; + + writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_CLK_EN && --timeout); +} + +static void jz4740_mmc_reset(struct jz4740_mmc_host *host) +{ + uint32_t status; + unsigned int timeout = 1000; + + writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL); + udelay(10); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_IS_RESETTING && --timeout); +} + +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host) +{ + struct mmc_request *req; + + req = host->req; + host->req = NULL; + + mmc_request_done(host->mmc, req); +} +static unsigned int jz4740_mmc_poll_irq(struct jz4740_mmc_host *host, + unsigned int irq) +{ + unsigned int timeout = 1000; + uint16_t status; + + do { + status = readw(host->base + JZ_REG_MMC_IREG); + } while (!(status & irq) && --timeout); + + if (timeout == 0) { + set_bit(0, &host->waiting); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); + jz4740_mmc_set_irq_enabled(host, irq, true); + return true; + } + + return false; +} + +static void jz4740_mmc_transfer_check_state(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + int status; + + status = readl(host->base + JZ_REG_MMC_STATUS); + if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) { + if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) { + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } else { + host->req->cmd->error = -EIO; + data->error = -EIO; + } + } +} + +static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter *miter = &host->miter; + uint32_t *buf; + bool timeout; + size_t i, j; + + while (sg_miter_next(miter)) { + buf = miter->addr; + i = miter->length / 4; + j = i / 8; + i = i & 0x7; + while (j) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + writel(buf[0], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[1], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[2], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[3], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[4], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[5], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[6], host->base + JZ_REG_MMC_TXFIFO); + writel(buf[7], host->base + JZ_REG_MMC_TXFIFO); + buf += 8; + --j; + } + if (unlikely(i)) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + while (i) { + writel(*buf, host->base + JZ_REG_MMC_TXFIFO); + ++buf; + --i; + } + } + data->bytes_xfered += miter->length; + } + sg_miter_stop(miter); + + return false; + +poll_timeout: + miter->consumed = (void *)buf - miter->addr; + data->bytes_xfered += miter->consumed; + sg_miter_stop(miter); + + return true; +} + +static bool jz4740_mmc_read_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter *miter = &host->miter; + uint32_t *buf; + uint32_t d; + uint16_t status; + size_t i, j; + unsigned int timeout; + + while (sg_miter_next(miter)) { + buf = miter->addr; + i = miter->length; + j = i / 32; + i = i & 0x1f; + while (j) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + buf[0] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[1] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[2] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[3] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[4] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[5] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[6] = readl(host->base + JZ_REG_MMC_RXFIFO); + buf[7] = readl(host->base + JZ_REG_MMC_RXFIFO); + + buf += 8; + --j; + } + + if (unlikely(i)) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + while (i >= 4) { + *buf++ = readl(host->base + JZ_REG_MMC_RXFIFO); + i -= 4; + } + if (unlikely(i > 0)) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + memcpy(buf, &d, i); + } + } + data->bytes_xfered += miter->length; + + /* This can go away once MIPS implements + * flush_kernel_dcache_page */ + flush_dcache_page(miter->page); + } + sg_miter_stop(miter); + + /* For whatever reason there is sometime one word more in the fifo then + * requested */ + timeout = 1000; + status = readl(host->base + JZ_REG_MMC_STATUS); + while (!(status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) && --timeout) { + d = readl(host->base + JZ_REG_MMC_RXFIFO); + status = readl(host->base + JZ_REG_MMC_STATUS); + } + + return false; + +poll_timeout: + miter->consumed = (void *)buf - miter->addr; + data->bytes_xfered += miter->consumed; + sg_miter_stop(miter); + + return true; +} + +static void jz4740_mmc_timeout(unsigned long data) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data; + + if (!test_and_clear_bit(0, &host->waiting)) + return; + + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false); + + host->req->cmd->error = -ETIMEDOUT; + jz4740_mmc_request_done(host); +} + +static void jz4740_mmc_read_response(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + int i; + uint16_t tmp; + + if (cmd->flags & MMC_RSP_136) { + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + for (i = 0; i < 4; ++i) { + cmd->resp[i] = tmp << 24; + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + cmd->resp[i] |= tmp << 8; + tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO); + cmd->resp[i] |= tmp >> 8; + } + } else { + cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8; + cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff; + } +} + +static void jz4740_mmc_send_command(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + uint32_t cmdat = host->cmdat; + + host->cmdat &= ~JZ_MMC_CMDAT_INIT; + jz4740_mmc_clock_disable(host); + + host->cmd = cmd; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= JZ_MMC_CMDAT_BUSY; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1B: + case MMC_RSP_R1: + cmdat |= JZ_MMC_CMDAT_RSP_R1; + break; + case MMC_RSP_R2: + cmdat |= JZ_MMC_CMDAT_RSP_R2; + break; + case MMC_RSP_R3: + cmdat |= JZ_MMC_CMDAT_RSP_R3; + break; + default: + break; + } + + if (cmd->data) { + cmdat |= JZ_MMC_CMDAT_DATA_EN; + if (cmd->data->flags & MMC_DATA_WRITE) + cmdat |= JZ_MMC_CMDAT_WRITE; + if (cmd->data->flags & MMC_DATA_STREAM) + cmdat |= JZ_MMC_CMDAT_STREAM; + + writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN); + writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB); + } + + writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD); + writel(cmd->arg, host->base + JZ_REG_MMC_ARG); + writel(cmdat, host->base + JZ_REG_MMC_CMDAT); + + jz4740_mmc_clock_enable(host, 1); +} + +static void jz_mmc_prepare_data_transfer(struct jz4740_mmc_host *host) +{ + struct mmc_command *cmd = host->req->cmd; + struct mmc_data *data = cmd->data; + int direction; + + if (cmd->data->flags & MMC_DATA_READ) + direction = SG_MITER_TO_SG; + else + direction = SG_MITER_FROM_SG; + + sg_miter_start(&host->miter, data->sg, data->sg_len, direction); +} + + +static irqreturn_t jz_mmc_irq_worker(int irq, void *devid) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid; + struct mmc_command *cmd = host->req->cmd; + struct mmc_request *req = host->req; + bool timedout = false; + + if (cmd->error) + host->state = JZ4740_MMC_STATE_DONE; + + switch (host->state) { + case JZ4740_MMC_STATE_READ_RESPONSE: + if (cmd->flags & MMC_RSP_PRESENT) + jz4740_mmc_read_response(host, cmd); + + if (!cmd->data) + break; + + jz_mmc_prepare_data_transfer(host); + + case JZ4740_MMC_STATE_TRANSFER_DATA: + if (cmd->data->flags & MMC_DATA_READ) + timedout = jz4740_mmc_read_data(host, cmd->data); + else + timedout = jz4740_mmc_write_data(host, cmd->data); + + if (unlikely(timedout)) { + host->state = JZ4740_MMC_STATE_TRANSFER_DATA; + break; + } + + jz4740_mmc_transfer_check_state(host, cmd->data); + + timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE); + if (unlikely(timedout)) { + host->state = JZ4740_MMC_STATE_SEND_STOP; + break; + } + writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG); + + case JZ4740_MMC_STATE_SEND_STOP: + if (!req->stop) + break; + + jz4740_mmc_send_command(host, req->stop); + + timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_PRG_DONE); + if (timedout) { + host->state = JZ4740_MMC_STATE_DONE; + break; + } + case JZ4740_MMC_STATE_DONE: + break; + } + + if (!timedout) + jz4740_mmc_request_done(host); + + return IRQ_HANDLED; +} + +static irqreturn_t jz_mmc_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + uint16_t irq_reg, status, tmp; + + irq_reg = readw(host->base + JZ_REG_MMC_IREG); + + tmp = irq_reg; + irq_reg &= ~host->irq_mask; + + tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ | + JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE); + + if (tmp != irq_reg) + writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG); + + if (irq_reg & JZ_MMC_IRQ_SDIO) { + writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG); + mmc_signal_sdio_irq(host->mmc); + irq_reg &= ~JZ_MMC_IRQ_SDIO; + } + + if (host->req && host->cmd && irq_reg) { + if (test_and_clear_bit(0, &host->waiting)) { + del_timer(&host->timeout_timer); + + status = readl(host->base + JZ_REG_MMC_STATUS); + + if (status & JZ_MMC_STATUS_TIMEOUT_RES) { + host->cmd->error = -ETIMEDOUT; + } else if (status & JZ_MMC_STATUS_CRC_RES_ERR) { + host->cmd->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + host->cmd->data->error = -EIO; + } + + jz4740_mmc_set_irq_enabled(host, irq_reg, false); + writew(irq_reg, host->base + JZ_REG_MMC_IREG); + + return IRQ_WAKE_THREAD; + } + } + + return IRQ_HANDLED; +} + +static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate) +{ + int div = 0; + int real_rate; + + jz4740_mmc_clock_disable(host); + clk_set_rate(host->clk, JZ_MMC_CLK_RATE); + + real_rate = clk_get_rate(host->clk); + + while (real_rate > rate && div < 7) { + ++div; + real_rate >>= 1; + } + + writew(div, host->base + JZ_REG_MMC_CLKRT); + return real_rate; +} + +static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + + host->req = req; + + writew(0xffff, host->base + JZ_REG_MMC_IREG); + + writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true); + + host->state = JZ4740_MMC_STATE_READ_RESPONSE; + set_bit(0, &host->waiting); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); + jz4740_mmc_send_command(host, req->cmd); +} + +static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (ios->clock) + jz4740_mmc_set_clock_rate(host, ios->clock); + + switch (ios->power_mode) { + case MMC_POWER_UP: + jz4740_mmc_reset(host); + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + !host->pdata->power_active_low); + host->cmdat |= JZ_MMC_CMDAT_INIT; + clk_enable(host->clk); + break; + case MMC_POWER_ON: + break; + default: + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + host->pdata->power_active_low); + clk_disable(host->clk); + break; + } + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + case MMC_BUS_WIDTH_4: + host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + default: + break; + } +} + +static int jz4740_mmc_get_ro(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_read_only)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_read_only) ^ + host->pdata->read_only_active_low; +} + +static int jz4740_mmc_get_cd(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_card_detect)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_card_detect) ^ + host->pdata->card_detect_active_low; +} + +static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + + mmc_detect_change(host->mmc, HZ / 2); + + return IRQ_HANDLED; +} + +static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable); +} + +static const struct mmc_host_ops jz4740_mmc_ops = { + .request = jz4740_mmc_request, + .set_ios = jz4740_mmc_set_ios, + .get_ro = jz4740_mmc_get_ro, + .get_cd = jz4740_mmc_get_cd, + .enable_sdio_irq = jz4740_mmc_enable_sdio_irq, +}; + +static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = { + JZ_GPIO_BULK_PIN(MSC_CMD), + JZ_GPIO_BULK_PIN(MSC_CLK), + JZ_GPIO_BULK_PIN(MSC_DATA0), + JZ_GPIO_BULK_PIN(MSC_DATA1), + JZ_GPIO_BULK_PIN(MSC_DATA2), + JZ_GPIO_BULK_PIN(MSC_DATA3), +}; + +static int __devinit jz4740_mmc_request_gpio(struct device *dev, int gpio, + const char *name, bool output, int value) +{ + int ret; + + if (!gpio_is_valid(gpio)) + return 0; + + ret = gpio_request(gpio, name); + if (ret) { + dev_err(dev, "Failed to request %s gpio: %d\n", name, ret); + return ret; + } + + if (output) + gpio_direction_output(gpio, value); + else + gpio_direction_input(gpio); + + return 0; +} + +static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return 0; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_card_detect, + "MMC detect change", false, 0); + if (ret) + goto err; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_read_only, + "MMC read only", false, 0); + if (ret) + goto err_free_gpio_card_detect; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_power, + "MMC read only", true, pdata->power_active_low); + if (ret) + goto err_free_gpio_read_only; + + return 0; + +err_free_gpio_read_only: + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); +err_free_gpio_card_detect: + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +err: + return ret; +} + +static int __devinit jz4740_mmc_request_cd_irq(struct platform_device *pdev, + struct jz4740_mmc_host *host) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (gpio_is_valid(pdata->gpio_card_detect)) + return 0; + + host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect); + + if (host->card_detect_irq < 0) { + dev_warn(&pdev->dev, "Failed to get card detect irq\n"); + return 0; + } + return request_irq(host->card_detect_irq, jz4740_mmc_card_detect_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "MMC card detect", host); + + + return ret; +} + +static void jz4740_mmc_free_gpios(struct platform_device *pdev) +{ + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return; + + if (gpio_is_valid(pdata->gpio_power)) + gpio_free(pdata->gpio_power); + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +} + +static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host) +{ + size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins); + if (host->pdata && host->pdata->data_1bit) + num_pins -= 3; + + return num_pins; +} + +static int __devinit jz4740_mmc_probe(struct platform_device* pdev) +{ + int ret; + struct mmc_host *mmc; + struct jz4740_mmc_host *host; + struct jz4740_mmc_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev); + if (!mmc) { + dev_err(&pdev->dev, "Failed to alloc mmc host structure\n"); + return -ENOMEM; + } + + host = mmc_priv(mmc); + host->pdata = pdata; + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + ret = host->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free_host; + } + + host->clk = clk_get(&pdev->dev, "mmc"); + if (!host->clk) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get mmc clock\n"); + goto err_free_host; + } + + host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get base platform memory\n"); + goto err_clk_put; + } + + host->mem = request_mem_region(host->mem->start, + resource_size(host->mem), pdev->name); + if (!host->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request base memory region\n"); + goto err_clk_put; + } + + host->base = ioremap_nocache(host->mem->start, resource_size(host->mem)); + if (!host->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap base memory\n"); + goto err_release_mem_region; + } + + ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + if (ret) { + dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret); + goto err_iounmap; + } + + ret = jz4740_mmc_request_gpios(pdev); + if (ret) + goto err_gpio_bulk_free; + + mmc->ops = &jz4740_mmc_ops; + mmc->f_min = JZ_MMC_CLK_RATE / 128; + mmc->f_max = JZ_MMC_CLK_RATE; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA; + mmc->caps |= MMC_CAP_SDIO_IRQ; + + mmc->max_blk_size = (1 << 10) - 1; + mmc->max_blk_count = (1 << 15) - 1; + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + mmc->max_seg_size = mmc->max_req_size; + + host->mmc = mmc; + host->pdev = pdev; + spin_lock_init(&host->lock); + host->irq_mask = 0xffff; + + ret = jz4740_mmc_request_cd_irq(pdev, host); + if (ret) { + dev_err(&pdev->dev, "Failed to request card detect irq\n"); + goto err_free_gpios; + } + + ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0, + dev_name(&pdev->dev), host); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_free_card_detect_irq; + } + + jz4740_mmc_reset(host); + jz4740_mmc_clock_disable(host); + setup_timer(&host->timeout_timer, jz4740_mmc_timeout, + (unsigned long)host); + /* It is not important when it times out, it just needs to timeout. */ + set_timer_slack(&host->timeout_timer, HZ); + + platform_set_drvdata(pdev, host); + ret = mmc_add_host(mmc); + + if (ret) { + dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret); + goto err_free_irq; + } + dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n"); + + return 0; + +err_free_irq: + free_irq(host->irq, host); +err_free_card_detect_irq: + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); +err_free_gpios: + jz4740_mmc_free_gpios(pdev); +err_gpio_bulk_free: + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); +err_iounmap: + iounmap(host->base); +err_release_mem_region: + release_mem_region(host->mem->start, resource_size(host->mem)); +err_clk_put: + clk_put(host->clk); +err_free_host: + platform_set_drvdata(pdev, NULL); + mmc_free_host(mmc); + + return ret; +} + +static int __devexit jz4740_mmc_remove(struct platform_device *pdev) +{ + struct jz4740_mmc_host *host = platform_get_drvdata(pdev); + + del_timer_sync(&host->timeout_timer); + jz4740_mmc_set_irq_enabled(host, 0xff, false); + jz4740_mmc_reset(host); + + mmc_remove_host(host->mmc); + + free_irq(host->irq, host); + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); + + jz4740_mmc_free_gpios(pdev); + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + iounmap(host->base); + release_mem_region(host->mem->start, resource_size(host->mem)); + + clk_put(host->clk); + + platform_set_drvdata(pdev, NULL); + mmc_free_host(host->mmc); + + return 0; +} + +#ifdef CONFIG_PM + +static int jz4740_mmc_suspend(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + mmc_suspend_host(host->mmc); + + jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + return 0; +} + +static int jz4740_mmc_resume(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + mmc_resume_host(host->mmc); + + return 0; +} + +const struct dev_pm_ops jz4740_mmc_pm_ops = { + .suspend = jz4740_mmc_suspend, + .resume = jz4740_mmc_resume, + .poweroff = jz4740_mmc_suspend, + .restore = jz4740_mmc_resume, +}; + +#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops) +#else +#define JZ4740_MMC_PM_OPS NULL +#endif + +static struct platform_driver jz4740_mmc_driver = { + .probe = jz4740_mmc_probe, + .remove = __devexit_p(jz4740_mmc_remove), + .driver = { + .name = "jz4740-mmc", + .owner = THIS_MODULE, + .pm = JZ4740_MMC_PM_OPS, + }, +}; + +static int __init jz4740_mmc_init(void) +{ + return platform_driver_register(&jz4740_mmc_driver); +} +module_init(jz4740_mmc_init); + +static void __exit jz4740_mmc_exit(void) +{ + platform_driver_unregister(&jz4740_mmc_driver); +} +module_exit(jz4740_mmc_exit); + +MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -- 1.5.6.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v5] MMC: Add JZ4740 mmc driver 2010-07-12 22:20 ` [PATCH v5] " Lars-Peter Clausen @ 2010-07-12 22:45 ` Joe Perches 2010-07-12 23:45 ` Lars-Peter Clausen 2010-07-15 21:06 ` [PATCH v6] " Lars-Peter Clausen 1 sibling, 1 reply; 20+ messages in thread From: Joe Perches @ 2010-07-12 22:45 UTC (permalink / raw) To: Lars-Peter Clausen Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton, Matt Fleming, linux-mmc On Tue, 2010-07-13 at 00:20 +0200, Lars-Peter Clausen wrote: > This patch adds support for the mmc controller on JZ4740 SoCs. > +static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host, > + struct mmc_data *data) > +{ > + struct sg_mapping_iter *miter = &host->miter; > + uint32_t *buf; > + bool timeout; > + size_t i, j; > + > + while (sg_miter_next(miter)) { > + buf = miter->addr; > + i = miter->length / 4; > + j = i / 8; > + i = i & 0x7; > + while (j) { > + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); > + if (unlikely(timeout)) > + goto poll_timeout; > + > + writel(buf[0], host->base + JZ_REG_MMC_TXFIFO); Perhaps it'd be better to use a temporary for host->base + JZ_REG_MMC_TXFIFO > + writel(buf[1], host->base + JZ_REG_MMC_TXFIFO); > + writel(buf[2], host->base + JZ_REG_MMC_TXFIFO); > + writel(buf[3], host->base + JZ_REG_MMC_TXFIFO); > + writel(buf[4], host->base + JZ_REG_MMC_TXFIFO); > + writel(buf[5], host->base + JZ_REG_MMC_TXFIFO); > + writel(buf[6], host->base + JZ_REG_MMC_TXFIFO); > + writel(buf[7], host->base + JZ_REG_MMC_TXFIFO); > + buf += 8; > + --j; > + } > + if (unlikely(i)) { > + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); > + if (unlikely(timeout)) > + goto poll_timeout; > + > + while (i) { > + writel(*buf, host->base + JZ_REG_MMC_TXFIFO); > + ++buf; > + --i; > + } > + } > + data->bytes_xfered += miter->length; > + } ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v5] MMC: Add JZ4740 mmc driver 2010-07-12 22:45 ` Joe Perches @ 2010-07-12 23:45 ` Lars-Peter Clausen 0 siblings, 0 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-12 23:45 UTC (permalink / raw) To: Joe Perches Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton, Matt Fleming, linux-mmc Hi Joe Perches wrote: > On Tue, 2010-07-13 at 00:20 +0200, Lars-Peter Clausen wrote: >> This patch adds support for the mmc controller on JZ4740 SoCs. >> +static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host, >> + struct mmc_data *data) >> +{ >> + struct sg_mapping_iter *miter = &host->miter; >> + uint32_t *buf; >> + bool timeout; >> + size_t i, j; >> + >> + while (sg_miter_next(miter)) { >> + buf = miter->addr; >> + i = miter->length / 4; >> + j = i / 8; >> + i = i & 0x7; >> + while (j) { >> + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); >> + if (unlikely(timeout)) >> + goto poll_timeout; >> + >> + writel(buf[0], host->base + JZ_REG_MMC_TXFIFO); > > Perhaps it'd be better to use a temporary for > host->base + JZ_REG_MMC_TXFIFO Hm, indeed host->addr is reloaded before each write. > >> + writel(buf[1], host->base + JZ_REG_MMC_TXFIFO); >> + writel(buf[2], host->base + JZ_REG_MMC_TXFIFO); >> + writel(buf[3], host->base + JZ_REG_MMC_TXFIFO); >> + writel(buf[4], host->base + JZ_REG_MMC_TXFIFO); >> + writel(buf[5], host->base + JZ_REG_MMC_TXFIFO); >> + writel(buf[6], host->base + JZ_REG_MMC_TXFIFO); >> + writel(buf[7], host->base + JZ_REG_MMC_TXFIFO); >> + buf += 8; >> + --j; >> + } >> + if (unlikely(i)) { >> + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); >> + if (unlikely(timeout)) >> + goto poll_timeout; >> + >> + while (i) { >> + writel(*buf, host->base + JZ_REG_MMC_TXFIFO); >> + ++buf; >> + --i; >> + } >> + } >> + data->bytes_xfered += miter->length; >> + } > > Thanks for reviewing - Lars ^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v6] MMC: Add JZ4740 mmc driver 2010-07-12 22:20 ` [PATCH v5] " Lars-Peter Clausen 2010-07-12 22:45 ` Joe Perches @ 2010-07-15 21:06 ` Lars-Peter Clausen 2010-07-15 21:16 ` Andrew Morton 1 sibling, 1 reply; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-15 21:06 UTC (permalink / raw) To: Ralf Baechle Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, Matt Fleming, linux-mmc This patch adds support for the mmc controller on JZ4740 SoCs. Signed-off-by: Lars-Peter Clausen <lars@metafoo.de> Acked-by: Matt Fleming <matt@console-pimps.org> Cc: Andrew Morton <akpm@linux-foundation.org> Cc: Matt Fleming <matt@console-pimps.org> Cc: linux-mmc@vger.kernel.org --- Changes since v1 - Do not request IRQ with IRQF_DISABLED since it is a noop now - Use a generous slack for the timeout timer. It does not need to be accurate. Changes since v2 - Use sg_mapping_to iterate over sg elements in mmc read and write functions - Use bitops instead of a spinlock and a variable for testing whether a request has been finished. - Rework irq and timeout handling in order to get rid of locking in hot paths Changes since v3 - Drastically decrease IRQ poll timeout. Now when the poll timeout is reached the IRQ which was polled is enabled. A variable keeps track of what state the driver is in and what has to be done next when the IRQ handler is entered again. By doing so busy looping is reduced, but overall performance can be maintained. - Move header file from include/linux/mmc to arch/mips/include/asm/mach-jz4740 Changes since v4 - Rework Kconfig entry Changes since v5 - Avoid reloading the fifo address before each read/write --- arch/mips/include/asm/mach-jz4740/jz4740_mmc.h | 15 + drivers/mmc/host/Kconfig | 9 + drivers/mmc/host/Makefile | 1 + drivers/mmc/host/jz4740_mmc.c | 1033 ++++++++++++++++++++++++ 4 files changed, 1058 insertions(+), 0 deletions(-) create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h create mode 100644 drivers/mmc/host/jz4740_mmc.c diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h new file mode 100644 index 0000000..8543f43 --- /dev/null +++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h @@ -0,0 +1,15 @@ +#ifndef __LINUX_MMC_JZ4740_MMC +#define __LINUX_MMC_JZ4740_MMC + +struct jz4740_mmc_platform_data { + int gpio_power; + int gpio_card_detect; + int gpio_read_only; + unsigned card_detect_active_low:1; + unsigned read_only_active_low:1; + unsigned power_active_low:1; + + unsigned data_1bit:1; +}; + +#endif diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index f06d06e..d25e22c 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -432,3 +432,12 @@ config MMC_SH_MMCIF This selects the MMC Host Interface controler (MMCIF). This driver supports MMCIF in sh7724/sh7757/sh7372. + +config MMC_JZ4740 + tristate "JZ4740 SD/Multimedia Card Interface support" + depends on MACH_JZ4740 + help + This selects support for the SD/MMC controller on Ingenic JZ4740 + SoCs. + If you have a board based on such a SoC and with a SD/MMC slot, + say Y or M here. diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile index e30c2ee..f4e53c9 100644 --- a/drivers/mmc/host/Makefile +++ b/drivers/mmc/host/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710) += cb710-mmc.o obj-$(CONFIG_MMC_VIA_SDMMC) += via-sdmmc.o obj-$(CONFIG_SDH_BFIN) += bfin_sdh.o obj-$(CONFIG_MMC_SH_MMCIF) += sh_mmcif.o +obj-$(CONFIG_MMC_JZ4740) += jz4740_mmc.o obj-$(CONFIG_MMC_SDHCI_OF) += sdhci-of.o sdhci-of-y := sdhci-of-core.o diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c new file mode 100644 index 0000000..12efd9c --- /dev/null +++ b/drivers/mmc/host/jz4740_mmc.c @@ -0,0 +1,1033 @@ +/* + * Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de> + * JZ4740 SD/MMC controller driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/mmc/host.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/scatterlist.h> +#include <linux/clk.h> + +#include <linux/bitops.h> +#include <linux/gpio.h> +#include <asm/mach-jz4740/gpio.h> +#include <asm/cacheflush.h> +#include <linux/dma-mapping.h> + +#include <asm/mach-jz4740/jz4740_mmc.h> + +#define JZ_REG_MMC_STRPCL 0x00 +#define JZ_REG_MMC_STATUS 0x04 +#define JZ_REG_MMC_CLKRT 0x08 +#define JZ_REG_MMC_CMDAT 0x0C +#define JZ_REG_MMC_RESTO 0x10 +#define JZ_REG_MMC_RDTO 0x14 +#define JZ_REG_MMC_BLKLEN 0x18 +#define JZ_REG_MMC_NOB 0x1C +#define JZ_REG_MMC_SNOB 0x20 +#define JZ_REG_MMC_IMASK 0x24 +#define JZ_REG_MMC_IREG 0x28 +#define JZ_REG_MMC_CMD 0x2C +#define JZ_REG_MMC_ARG 0x30 +#define JZ_REG_MMC_RESP_FIFO 0x34 +#define JZ_REG_MMC_RXFIFO 0x38 +#define JZ_REG_MMC_TXFIFO 0x3C + +#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7) +#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6) +#define JZ_MMC_STRPCL_START_READWAIT BIT(5) +#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4) +#define JZ_MMC_STRPCL_RESET BIT(3) +#define JZ_MMC_STRPCL_START_OP BIT(2) +#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0)) +#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0) +#define JZ_MMC_STRPCL_CLOCK_START BIT(1) + + +#define JZ_MMC_STATUS_IS_RESETTING BIT(15) +#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14) +#define JZ_MMC_STATUS_PRG_DONE BIT(13) +#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12) +#define JZ_MMC_STATUS_END_CMD_RES BIT(11) +#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10) +#define JZ_MMC_STATUS_IS_READWAIT BIT(9) +#define JZ_MMC_STATUS_CLK_EN BIT(8) +#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7) +#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6) +#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5) +#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4) +#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3) +#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2) +#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1) +#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0) + +#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0)) +#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2)) + + +#define JZ_MMC_CMDAT_IO_ABORT BIT(11) +#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10) +#define JZ_MMC_CMDAT_DMA_EN BIT(8) +#define JZ_MMC_CMDAT_INIT BIT(7) +#define JZ_MMC_CMDAT_BUSY BIT(6) +#define JZ_MMC_CMDAT_STREAM BIT(5) +#define JZ_MMC_CMDAT_WRITE BIT(4) +#define JZ_MMC_CMDAT_DATA_EN BIT(3) +#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0)) +#define JZ_MMC_CMDAT_RSP_R1 1 +#define JZ_MMC_CMDAT_RSP_R2 2 +#define JZ_MMC_CMDAT_RSP_R3 3 + +#define JZ_MMC_IRQ_SDIO BIT(7) +#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6) +#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5) +#define JZ_MMC_IRQ_END_CMD_RES BIT(2) +#define JZ_MMC_IRQ_PRG_DONE BIT(1) +#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0) + + +#define JZ_MMC_CLK_RATE 24000000 + +enum jz4740_mmc_state { + JZ4740_MMC_STATE_READ_RESPONSE, + JZ4740_MMC_STATE_TRANSFER_DATA, + JZ4740_MMC_STATE_SEND_STOP, + JZ4740_MMC_STATE_DONE, +}; + +struct jz4740_mmc_host { + struct mmc_host *mmc; + struct platform_device *pdev; + struct jz4740_mmc_platform_data *pdata; + struct clk *clk; + + int irq; + int card_detect_irq; + + struct resource *mem; + void __iomem *base; + struct mmc_request *req; + struct mmc_command *cmd; + + unsigned long waiting; + + uint32_t cmdat; + + uint16_t irq_mask; + + spinlock_t lock; + + struct timer_list timeout_timer; + struct sg_mapping_iter miter; + enum jz4740_mmc_state state; +}; + +static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host, + unsigned int irq, bool enabled) +{ + unsigned long flags; + + spin_lock_irqsave(&host->lock, flags); + if (enabled) + host->irq_mask &= ~irq; + else + host->irq_mask |= irq; + spin_unlock_irqrestore(&host->lock, flags); + + writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK); +} + +static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host, + bool start_transfer) +{ + uint16_t val = JZ_MMC_STRPCL_CLOCK_START; + + if (start_transfer) + val |= JZ_MMC_STRPCL_START_OP; + + writew(val, host->base + JZ_REG_MMC_STRPCL); +} + +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host) +{ + uint32_t status; + unsigned int timeout = 1000; + + writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_CLK_EN && --timeout); +} + +static void jz4740_mmc_reset(struct jz4740_mmc_host *host) +{ + uint32_t status; + unsigned int timeout = 1000; + + writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL); + udelay(10); + do { + status = readl(host->base + JZ_REG_MMC_STATUS); + } while (status & JZ_MMC_STATUS_IS_RESETTING && --timeout); +} + +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host) +{ + struct mmc_request *req; + + req = host->req; + host->req = NULL; + + mmc_request_done(host->mmc, req); +} + +static unsigned int jz4740_mmc_poll_irq(struct jz4740_mmc_host *host, + unsigned int irq) +{ + unsigned int timeout = 0x800; + uint16_t status; + + do { + status = readw(host->base + JZ_REG_MMC_IREG); + } while (!(status & irq) && --timeout); + + if (timeout == 0) { + set_bit(0, &host->waiting); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); + jz4740_mmc_set_irq_enabled(host, irq, true); + return true; + } + + return false; +} + +static void jz4740_mmc_transfer_check_state(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + int status; + + status = readl(host->base + JZ_REG_MMC_STATUS); + if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) { + if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) { + host->req->cmd->error = -ETIMEDOUT; + data->error = -ETIMEDOUT; + } else { + host->req->cmd->error = -EIO; + data->error = -EIO; + } + } +} + +static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter *miter = &host->miter; + void __iomem *fifo_addr = host->base + JZ_REG_MMC_TXFIFO; + uint32_t *buf; + bool timeout; + size_t i, j; + + while (sg_miter_next(miter)) { + buf = miter->addr; + i = miter->length / 4; + j = i / 8; + i = i & 0x7; + while (j) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + writel(buf[0], fifo_addr); + writel(buf[1], fifo_addr); + writel(buf[2], fifo_addr); + writel(buf[3], fifo_addr); + writel(buf[4], fifo_addr); + writel(buf[5], fifo_addr); + writel(buf[6], fifo_addr); + writel(buf[7], fifo_addr); + buf += 8; + --j; + } + if (unlikely(i)) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + while (i) { + writel(*buf, fifo_addr); + ++buf; + --i; + } + } + data->bytes_xfered += miter->length; + } + sg_miter_stop(miter); + + return false; + +poll_timeout: + miter->consumed = (void *)buf - miter->addr; + data->bytes_xfered += miter->consumed; + sg_miter_stop(miter); + + return true; +} + +static bool jz4740_mmc_read_data(struct jz4740_mmc_host *host, + struct mmc_data *data) +{ + struct sg_mapping_iter *miter = &host->miter; + void __iomem *fifo_addr = host->base + JZ_REG_MMC_RXFIFO; + uint32_t *buf; + uint32_t d; + uint16_t status; + size_t i, j; + unsigned int timeout; + + while (sg_miter_next(miter)) { + buf = miter->addr; + i = miter->length; + j = i / 32; + i = i & 0x1f; + while (j) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + buf[0] = readl(fifo_addr); + buf[1] = readl(fifo_addr); + buf[2] = readl(fifo_addr); + buf[3] = readl(fifo_addr); + buf[4] = readl(fifo_addr); + buf[5] = readl(fifo_addr); + buf[6] = readl(fifo_addr); + buf[7] = readl(fifo_addr); + + buf += 8; + --j; + } + + if (unlikely(i)) { + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ); + if (unlikely(timeout)) + goto poll_timeout; + + while (i >= 4) { + *buf++ = readl(fifo_addr); + i -= 4; + } + if (unlikely(i > 0)) { + d = readl(fifo_addr); + memcpy(buf, &d, i); + } + } + data->bytes_xfered += miter->length; + + /* This can go away once MIPS implements + * flush_kernel_dcache_page */ + flush_dcache_page(miter->page); + } + sg_miter_stop(miter); + + /* For whatever reason there is sometime one word more in the fifo then + * requested */ + timeout = 1000; + status = readl(host->base + JZ_REG_MMC_STATUS); + while (!(status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) && --timeout) { + d = readl(fifo_addr); + status = readl(host->base + JZ_REG_MMC_STATUS); + } + + return false; + +poll_timeout: + miter->consumed = (void *)buf - miter->addr; + data->bytes_xfered += miter->consumed; + sg_miter_stop(miter); + + return true; +} + +static void jz4740_mmc_timeout(unsigned long data) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data; + + if (!test_and_clear_bit(0, &host->waiting)) + return; + + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false); + + host->req->cmd->error = -ETIMEDOUT; + jz4740_mmc_request_done(host); +} + +static void jz4740_mmc_read_response(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + int i; + uint16_t tmp; + void __iomem *fifo_addr = host->base + JZ_REG_MMC_RESP_FIFO; + + if (cmd->flags & MMC_RSP_136) { + tmp = readw(fifo_addr); + for (i = 0; i < 4; ++i) { + cmd->resp[i] = tmp << 24; + tmp = readw(fifo_addr); + cmd->resp[i] |= tmp << 8; + tmp = readw(fifo_addr); + cmd->resp[i] |= tmp >> 8; + } + } else { + cmd->resp[0] = readw(fifo_addr) << 24; + cmd->resp[0] |= readw(fifo_addr) << 8; + cmd->resp[0] |= readw(fifo_addr) & 0xff; + } +} + +static void jz4740_mmc_send_command(struct jz4740_mmc_host *host, + struct mmc_command *cmd) +{ + uint32_t cmdat = host->cmdat; + + host->cmdat &= ~JZ_MMC_CMDAT_INIT; + jz4740_mmc_clock_disable(host); + + host->cmd = cmd; + + if (cmd->flags & MMC_RSP_BUSY) + cmdat |= JZ_MMC_CMDAT_BUSY; + + switch (mmc_resp_type(cmd)) { + case MMC_RSP_R1B: + case MMC_RSP_R1: + cmdat |= JZ_MMC_CMDAT_RSP_R1; + break; + case MMC_RSP_R2: + cmdat |= JZ_MMC_CMDAT_RSP_R2; + break; + case MMC_RSP_R3: + cmdat |= JZ_MMC_CMDAT_RSP_R3; + break; + default: + break; + } + + if (cmd->data) { + cmdat |= JZ_MMC_CMDAT_DATA_EN; + if (cmd->data->flags & MMC_DATA_WRITE) + cmdat |= JZ_MMC_CMDAT_WRITE; + if (cmd->data->flags & MMC_DATA_STREAM) + cmdat |= JZ_MMC_CMDAT_STREAM; + + writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN); + writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB); + } + + writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD); + writel(cmd->arg, host->base + JZ_REG_MMC_ARG); + writel(cmdat, host->base + JZ_REG_MMC_CMDAT); + + jz4740_mmc_clock_enable(host, 1); +} + +static void jz_mmc_prepare_data_transfer(struct jz4740_mmc_host *host) +{ + struct mmc_command *cmd = host->req->cmd; + struct mmc_data *data = cmd->data; + int direction; + + if (data->flags & MMC_DATA_READ) + direction = SG_MITER_TO_SG; + else + direction = SG_MITER_FROM_SG; + + sg_miter_start(&host->miter, data->sg, data->sg_len, direction); +} + + +static irqreturn_t jz_mmc_irq_worker(int irq, void *devid) +{ + struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid; + struct mmc_command *cmd = host->req->cmd; + struct mmc_request *req = host->req; + bool timeout = false; + + if (cmd->error) + host->state = JZ4740_MMC_STATE_DONE; + + switch (host->state) { + case JZ4740_MMC_STATE_READ_RESPONSE: + if (cmd->flags & MMC_RSP_PRESENT) + jz4740_mmc_read_response(host, cmd); + + if (!cmd->data) + break; + + jz_mmc_prepare_data_transfer(host); + + case JZ4740_MMC_STATE_TRANSFER_DATA: + if (cmd->data->flags & MMC_DATA_READ) + timeout = jz4740_mmc_read_data(host, cmd->data); + else + timeout = jz4740_mmc_write_data(host, cmd->data); + + if (unlikely(timeout)) { + host->state = JZ4740_MMC_STATE_TRANSFER_DATA; + break; + } + + jz4740_mmc_transfer_check_state(host, cmd->data); + + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE); + if (unlikely(timeout)) { + host->state = JZ4740_MMC_STATE_SEND_STOP; + break; + } + writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG); + + case JZ4740_MMC_STATE_SEND_STOP: + if (!req->stop) + break; + + jz4740_mmc_send_command(host, req->stop); + + timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_PRG_DONE); + if (timeout) { + host->state = JZ4740_MMC_STATE_DONE; + break; + } + case JZ4740_MMC_STATE_DONE: + break; + } + + if (!timeout) + jz4740_mmc_request_done(host); + + return IRQ_HANDLED; +} + +static irqreturn_t jz_mmc_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + struct mmc_command *cmd = host->cmd; + uint16_t irq_reg, status, tmp; + + irq_reg = readw(host->base + JZ_REG_MMC_IREG); + + tmp = irq_reg; + irq_reg &= ~host->irq_mask; + + tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ | + JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE); + + if (tmp != irq_reg) + writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG); + + if (irq_reg & JZ_MMC_IRQ_SDIO) { + writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG); + mmc_signal_sdio_irq(host->mmc); + irq_reg &= ~JZ_MMC_IRQ_SDIO; + } + + if (host->req && cmd && irq_reg) { + if (test_and_clear_bit(0, &host->waiting)) { + del_timer(&host->timeout_timer); + + status = readl(host->base + JZ_REG_MMC_STATUS); + + if (status & JZ_MMC_STATUS_TIMEOUT_RES) { + cmd->error = -ETIMEDOUT; + } else if (status & JZ_MMC_STATUS_CRC_RES_ERR) { + cmd->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + if (cmd->data) + cmd->data->error = -EIO; + cmd->error = -EIO; + } else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR | + JZ_MMC_STATUS_CRC_WRITE_ERROR)) { + if (cmd->data) + cmd->data->error = -EIO; + cmd->error = -EIO; + } + + jz4740_mmc_set_irq_enabled(host, irq_reg, false); + writew(irq_reg, host->base + JZ_REG_MMC_IREG); + + return IRQ_WAKE_THREAD; + } + } + + return IRQ_HANDLED; +} + +static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate) +{ + int div = 0; + int real_rate; + + jz4740_mmc_clock_disable(host); + clk_set_rate(host->clk, JZ_MMC_CLK_RATE); + + real_rate = clk_get_rate(host->clk); + + while (real_rate > rate && div < 7) { + ++div; + real_rate >>= 1; + } + + writew(div, host->base + JZ_REG_MMC_CLKRT); + return real_rate; +} + +static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + + host->req = req; + + writew(0xffff, host->base + JZ_REG_MMC_IREG); + + writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true); + + host->state = JZ4740_MMC_STATE_READ_RESPONSE; + set_bit(0, &host->waiting); + mod_timer(&host->timeout_timer, jiffies + 5*HZ); + jz4740_mmc_send_command(host, req->cmd); +} + +static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (ios->clock) + jz4740_mmc_set_clock_rate(host, ios->clock); + + switch (ios->power_mode) { + case MMC_POWER_UP: + jz4740_mmc_reset(host); + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + !host->pdata->power_active_low); + host->cmdat |= JZ_MMC_CMDAT_INIT; + clk_enable(host->clk); + break; + case MMC_POWER_ON: + break; + default: + if (gpio_is_valid(host->pdata->gpio_power)) + gpio_set_value(host->pdata->gpio_power, + host->pdata->power_active_low); + clk_disable(host->clk); + break; + } + + switch (ios->bus_width) { + case MMC_BUS_WIDTH_1: + host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + case MMC_BUS_WIDTH_4: + host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT; + break; + default: + break; + } +} + +static int jz4740_mmc_get_ro(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_read_only)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_read_only) ^ + host->pdata->read_only_active_low; +} + +static int jz4740_mmc_get_cd(struct mmc_host *mmc) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + if (!gpio_is_valid(host->pdata->gpio_card_detect)) + return -ENOSYS; + + return gpio_get_value(host->pdata->gpio_card_detect) ^ + host->pdata->card_detect_active_low; +} + +static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid) +{ + struct jz4740_mmc_host *host = devid; + + mmc_detect_change(host->mmc, HZ / 2); + + return IRQ_HANDLED; +} + +static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable) +{ + struct jz4740_mmc_host *host = mmc_priv(mmc); + jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable); +} + +static const struct mmc_host_ops jz4740_mmc_ops = { + .request = jz4740_mmc_request, + .set_ios = jz4740_mmc_set_ios, + .get_ro = jz4740_mmc_get_ro, + .get_cd = jz4740_mmc_get_cd, + .enable_sdio_irq = jz4740_mmc_enable_sdio_irq, +}; + +static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = { + JZ_GPIO_BULK_PIN(MSC_CMD), + JZ_GPIO_BULK_PIN(MSC_CLK), + JZ_GPIO_BULK_PIN(MSC_DATA0), + JZ_GPIO_BULK_PIN(MSC_DATA1), + JZ_GPIO_BULK_PIN(MSC_DATA2), + JZ_GPIO_BULK_PIN(MSC_DATA3), +}; + +static int __devinit jz4740_mmc_request_gpio(struct device *dev, int gpio, + const char *name, bool output, int value) +{ + int ret; + + if (!gpio_is_valid(gpio)) + return 0; + + ret = gpio_request(gpio, name); + if (ret) { + dev_err(dev, "Failed to request %s gpio: %d\n", name, ret); + return ret; + } + + if (output) + gpio_direction_output(gpio, value); + else + gpio_direction_input(gpio); + + return 0; +} + +static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return 0; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_card_detect, + "MMC detect change", false, 0); + if (ret) + goto err; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_read_only, + "MMC read only", false, 0); + if (ret) + goto err_free_gpio_card_detect; + + ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_power, + "MMC read only", true, pdata->power_active_low); + if (ret) + goto err_free_gpio_read_only; + + return 0; + +err_free_gpio_read_only: + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); +err_free_gpio_card_detect: + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +err: + return ret; +} + +static int __devinit jz4740_mmc_request_cd_irq(struct platform_device *pdev, + struct jz4740_mmc_host *host) +{ + int ret; + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (gpio_is_valid(pdata->gpio_card_detect)) + return 0; + + host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect); + + if (host->card_detect_irq < 0) { + dev_warn(&pdev->dev, "Failed to get card detect irq\n"); + return 0; + } + return request_irq(host->card_detect_irq, jz4740_mmc_card_detect_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "MMC card detect", host); + + + return ret; +} + +static void jz4740_mmc_free_gpios(struct platform_device *pdev) +{ + struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data; + + if (!pdata) + return; + + if (gpio_is_valid(pdata->gpio_power)) + gpio_free(pdata->gpio_power); + if (gpio_is_valid(pdata->gpio_read_only)) + gpio_free(pdata->gpio_read_only); + if (gpio_is_valid(pdata->gpio_card_detect)) + gpio_free(pdata->gpio_card_detect); +} + +static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host) +{ + size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins); + if (host->pdata && host->pdata->data_1bit) + num_pins -= 3; + + return num_pins; +} + +static int __devinit jz4740_mmc_probe(struct platform_device* pdev) +{ + int ret; + struct mmc_host *mmc; + struct jz4740_mmc_host *host; + struct jz4740_mmc_platform_data *pdata; + + pdata = pdev->dev.platform_data; + + mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev); + if (!mmc) { + dev_err(&pdev->dev, "Failed to alloc mmc host structure\n"); + return -ENOMEM; + } + + host = mmc_priv(mmc); + host->pdata = pdata; + + host->irq = platform_get_irq(pdev, 0); + if (host->irq < 0) { + ret = host->irq; + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); + goto err_free_host; + } + + host->clk = clk_get(&pdev->dev, "mmc"); + if (!host->clk) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get mmc clock\n"); + goto err_free_host; + } + + host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!host->mem) { + ret = -ENOENT; + dev_err(&pdev->dev, "Failed to get base platform memory\n"); + goto err_clk_put; + } + + host->mem = request_mem_region(host->mem->start, + resource_size(host->mem), pdev->name); + if (!host->mem) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to request base memory region\n"); + goto err_clk_put; + } + + host->base = ioremap_nocache(host->mem->start, resource_size(host->mem)); + if (!host->base) { + ret = -EBUSY; + dev_err(&pdev->dev, "Failed to ioremap base memory\n"); + goto err_release_mem_region; + } + + ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + if (ret) { + dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret); + goto err_iounmap; + } + + ret = jz4740_mmc_request_gpios(pdev); + if (ret) + goto err_gpio_bulk_free; + + mmc->ops = &jz4740_mmc_ops; + mmc->f_min = JZ_MMC_CLK_RATE / 128; + mmc->f_max = JZ_MMC_CLK_RATE; + mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34; + mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA; + mmc->caps |= MMC_CAP_SDIO_IRQ; + + mmc->max_blk_size = (1 << 10) - 1; + mmc->max_blk_count = (1 << 15) - 1; + mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count; + + mmc->max_phys_segs = 128; + mmc->max_hw_segs = 128; + mmc->max_seg_size = mmc->max_req_size; + + host->mmc = mmc; + host->pdev = pdev; + spin_lock_init(&host->lock); + host->irq_mask = 0xffff; + + ret = jz4740_mmc_request_cd_irq(pdev, host); + if (ret) { + dev_err(&pdev->dev, "Failed to request card detect irq\n"); + goto err_free_gpios; + } + + ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0, + dev_name(&pdev->dev), host); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + goto err_free_card_detect_irq; + } + + jz4740_mmc_reset(host); + jz4740_mmc_clock_disable(host); + setup_timer(&host->timeout_timer, jz4740_mmc_timeout, + (unsigned long)host); + /* It is not important when it times out, it just needs to timeout. */ + set_timer_slack(&host->timeout_timer, HZ); + + platform_set_drvdata(pdev, host); + ret = mmc_add_host(mmc); + + if (ret) { + dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret); + goto err_free_irq; + } + dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n"); + + return 0; + +err_free_irq: + free_irq(host->irq, host); +err_free_card_detect_irq: + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); +err_free_gpios: + jz4740_mmc_free_gpios(pdev); +err_gpio_bulk_free: + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); +err_iounmap: + iounmap(host->base); +err_release_mem_region: + release_mem_region(host->mem->start, resource_size(host->mem)); +err_clk_put: + clk_put(host->clk); +err_free_host: + platform_set_drvdata(pdev, NULL); + mmc_free_host(mmc); + + return ret; +} + +static int __devexit jz4740_mmc_remove(struct platform_device *pdev) +{ + struct jz4740_mmc_host *host = platform_get_drvdata(pdev); + + del_timer_sync(&host->timeout_timer); + jz4740_mmc_set_irq_enabled(host, 0xff, false); + jz4740_mmc_reset(host); + + mmc_remove_host(host->mmc); + + free_irq(host->irq, host); + if (host->card_detect_irq >= 0) + free_irq(host->card_detect_irq, host); + + jz4740_mmc_free_gpios(pdev); + jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + iounmap(host->base); + release_mem_region(host->mem->start, resource_size(host->mem)); + + clk_put(host->clk); + + platform_set_drvdata(pdev, NULL); + mmc_free_host(host->mmc); + + return 0; +} + +#ifdef CONFIG_PM + +static int jz4740_mmc_suspend(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + mmc_suspend_host(host->mmc); + + jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + return 0; +} + +static int jz4740_mmc_resume(struct device *dev) +{ + struct jz4740_mmc_host *host = dev_get_drvdata(dev); + + jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host)); + + mmc_resume_host(host->mmc); + + return 0; +} + +const struct dev_pm_ops jz4740_mmc_pm_ops = { + .suspend = jz4740_mmc_suspend, + .resume = jz4740_mmc_resume, + .poweroff = jz4740_mmc_suspend, + .restore = jz4740_mmc_resume, +}; + +#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops) +#else +#define JZ4740_MMC_PM_OPS NULL +#endif + +static struct platform_driver jz4740_mmc_driver = { + .probe = jz4740_mmc_probe, + .remove = __devexit_p(jz4740_mmc_remove), + .driver = { + .name = "jz4740-mmc", + .owner = THIS_MODULE, + .pm = JZ4740_MMC_PM_OPS, + }, +}; + +static int __init jz4740_mmc_init(void) +{ + return platform_driver_register(&jz4740_mmc_driver); +} +module_init(jz4740_mmc_init); + +static void __exit jz4740_mmc_exit(void) +{ + platform_driver_unregister(&jz4740_mmc_driver); +} +module_exit(jz4740_mmc_exit); + +MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); -- 1.5.6.5 ^ permalink raw reply related [flat|nested] 20+ messages in thread
* Re: [PATCH v6] MMC: Add JZ4740 mmc driver 2010-07-15 21:06 ` [PATCH v6] " Lars-Peter Clausen @ 2010-07-15 21:16 ` Andrew Morton 2010-07-15 21:37 ` Lars-Peter Clausen 0 siblings, 1 reply; 20+ messages in thread From: Andrew Morton @ 2010-07-15 21:16 UTC (permalink / raw) To: Lars-Peter Clausen Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc On Thu, 15 Jul 2010 23:06:04 +0200 Lars-Peter Clausen <lars@metafoo.de> wrote: > This patch adds support for the mmc controller on JZ4740 SoCs. > > > ... > > + if (gpio_is_valid(host->pdata->gpio_power)) > + gpio_set_value(host->pdata->gpio_power, > + !host->pdata->power_active_low); > > ... > Should this driver have a `depends on GPIOLIB' in Kconfig? ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v6] MMC: Add JZ4740 mmc driver 2010-07-15 21:16 ` Andrew Morton @ 2010-07-15 21:37 ` Lars-Peter Clausen 0 siblings, 0 replies; 20+ messages in thread From: Lars-Peter Clausen @ 2010-07-15 21:37 UTC (permalink / raw) To: Andrew Morton Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc -----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 Andrew Morton wrote: > On Thu, 15 Jul 2010 23:06:04 +0200 > Lars-Peter Clausen <lars@metafoo.de> wrote: > >> This patch adds support for the mmc controller on JZ4740 SoCs. >> >> >> ... >> >> + if (gpio_is_valid(host->pdata->gpio_power)) >> + gpio_set_value(host->pdata->gpio_power, >> + !host->pdata->power_active_low); >> >> ... >> > > Should this driver have a `depends on GPIOLIB' in Kconfig? > The driver depends on MACH_JZ4740 which selects ARCH_REQUIRE_GPIOLIB, so there already is an implicit depends on GPIOLIB. - - Lars -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org iEUEARECAAYFAkw/f60ACgkQBX4mSR26RiNexQCfUXt0cqiMqEf17k+z+q6XVwRO ImEAmPKxeyX9ANVasUNL60f51GxKofg= =3NrE -----END PGP SIGNATURE----- ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip 2010-06-19 5:08 [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Lars-Peter Clausen 2010-06-19 5:08 ` [PATCH v2 18/26] MMC: Add JZ4740 mmc driver Lars-Peter Clausen @ 2010-06-20 9:26 ` Thomas Bogendoerfer 2010-06-21 2:56 ` Xiangfu Liu 1 sibling, 1 reply; 20+ messages in thread From: Thomas Bogendoerfer @ 2010-06-20 9:26 UTC (permalink / raw) To: Lars-Peter Clausen Cc: linux-mips, Paul Gortmaker, David Brownell, Mark Brown, Samuel Ortiz, alsa-devel, Alessandro Zummo, Greg Kroah-Hartman, linux-mmc, linux-kernel, Ralf Baechle, lm-sensors, linux-usb, linux-mtd, linux-fbdev, Anton Vorontsov, rtc-linux, Andrew Morton, David Woodhouse, Liam Girdwood On Sat, Jun 19, 2010 at 07:08:05AM +0200, Lars-Peter Clausen wrote: > This patch series adds support for the Ingenic JZ4740 System-on-a-Chip. great stuff. I have a JZ4730 based netbook, for which I started magling the provided sources quite some time ago, but I didn't reach the point of submitting patches... there are a lot of common stuff between JZ4730 and JZ4740 so IMHO it would be a good thing not to nail everthing to JZ4740 namewise. It might also a good idea to select something like arch/mips/jzrisc as base directory, put the factored out code there and add JZ4730/JZ4740 in either seperate files or directories. Thomas. -- Crap can work. Given enough thrust pigs will fly, but it's not necessary a good idea. [ RFC1925, 2.3 ] ^ permalink raw reply [flat|nested] 20+ messages in thread
* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip 2010-06-20 9:26 ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Thomas Bogendoerfer @ 2010-06-21 2:56 ` Xiangfu Liu 0 siblings, 0 replies; 20+ messages in thread From: Xiangfu Liu @ 2010-06-21 2:56 UTC (permalink / raw) To: Thomas Bogendoerfer Cc: Lars-Peter Clausen, Ralf Baechle, linux-mips, linux-kernel, Alessandro Zummo, Andrew Morton, Anton Vorontsov, David Brownell, David Woodhouse, Greg Kroah-Hartman, Liam Girdwood, Mark Brown, Paul Gortmaker, Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc, linux-mtd, linux-usb, lm-sensors, rtc-linux On 06/20/2010 05:26 PM, Thomas Bogendoerfer wrote: > great stuff. I have a JZ4730 based netbook, for which I started magling > the provided sources quite some time ago, but I didn't reach the > point of submitting patches... there are a lot of common stuff between > JZ4730 and JZ4740 so IMHO it would be a good thing not to nail > everthing to JZ4740 namewise. It might also a good idea to select > something like arch/mips/jzrisc as base directory, put the Hi Thomas I would advice "xburst" instead jzrisc. because the Ingenic call their cpu "XBurst" series. like: XBurst JZ4740, XBurst JZ4750 ... > factored out code there and add JZ4730/JZ4740 in either seperate > files or directories. -- Best Regards Xiangfu Liu http://www.openmobilefree.net ^ permalink raw reply [flat|nested] 20+ messages in thread
end of thread, other threads:[~2010-07-15 21:38 UTC | newest] Thread overview: 20+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2010-06-19 5:08 [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Lars-Peter Clausen 2010-06-19 5:08 ` [PATCH v2 18/26] MMC: Add JZ4740 mmc driver Lars-Peter Clausen 2010-06-19 14:46 ` Matt Fleming 2010-06-19 15:29 ` Lars-Peter Clausen 2010-06-28 1:20 ` [PATCH v3] " Lars-Peter Clausen 2010-06-29 20:17 ` Matt Fleming 2010-07-01 15:47 ` Lars-Peter Clausen 2010-06-30 20:55 ` Andrew Morton 2010-07-01 15:45 ` Lars-Peter Clausen 2010-07-12 21:33 ` [PATCH v4] " Lars-Peter Clausen 2010-07-12 21:41 ` Randy Dunlap 2010-07-12 22:07 ` Lars-Peter Clausen 2010-07-12 22:20 ` [PATCH v5] " Lars-Peter Clausen 2010-07-12 22:45 ` Joe Perches 2010-07-12 23:45 ` Lars-Peter Clausen 2010-07-15 21:06 ` [PATCH v6] " Lars-Peter Clausen 2010-07-15 21:16 ` Andrew Morton 2010-07-15 21:37 ` Lars-Peter Clausen 2010-06-20 9:26 ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Thomas Bogendoerfer 2010-06-21 2:56 ` Xiangfu Liu
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).