2010-06-19 05:09:09

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

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 <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Anton Vorontsov <[email protected]>
Cc: David Brownell <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Paul Gortmaker <[email protected]>
Cc: Samuel Ortiz <[email protected]>
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]
Cc: [email protected]

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


2010-06-19 05:09:17

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 02/26] MIPS: jz4740: Add IRQ handler code

This patch adds support for IRQ handling on a JZ4740 SoC.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Reserve IRQ numbers for ADC IRQ demultiplexing
---
arch/mips/include/asm/mach-jz4740/irq.h | 57 ++++++++++
arch/mips/jz4740/irq.c | 170 +++++++++++++++++++++++++++++++
arch/mips/jz4740/irq.h | 21 ++++
3 files changed, 248 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h
create mode 100644 arch/mips/jz4740/irq.c
create mode 100644 arch/mips/jz4740/irq.h

diff --git a/arch/mips/include/asm/mach-jz4740/irq.h b/arch/mips/include/asm/mach-jz4740/irq.h
new file mode 100644
index 0000000..a865c98
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/irq.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 IRQ definitions
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_MACH_JZ4740_IRQ_H__
+#define __ASM_MACH_JZ4740_IRQ_H__
+
+#define MIPS_CPU_IRQ_BASE 0
+#define JZ4740_IRQ_BASE 8
+
+/* 1st-level interrupts */
+#define JZ4740_IRQ(x) (JZ4740_IRQ_BASE + (x))
+#define JZ4740_IRQ_I2C JZ4740_IRQ(1)
+#define JZ4740_IRQ_UHC JZ4740_IRQ(3)
+#define JZ4740_IRQ_UART1 JZ4740_IRQ(8)
+#define JZ4740_IRQ_UART0 JZ4740_IRQ(9)
+#define JZ4740_IRQ_SADC JZ4740_IRQ(12)
+#define JZ4740_IRQ_MSC JZ4740_IRQ(14)
+#define JZ4740_IRQ_RTC JZ4740_IRQ(15)
+#define JZ4740_IRQ_SSI JZ4740_IRQ(16)
+#define JZ4740_IRQ_CIM JZ4740_IRQ(17)
+#define JZ4740_IRQ_AIC JZ4740_IRQ(18)
+#define JZ4740_IRQ_ETH JZ4740_IRQ(19)
+#define JZ4740_IRQ_DMAC JZ4740_IRQ(20)
+#define JZ4740_IRQ_TCU2 JZ4740_IRQ(21)
+#define JZ4740_IRQ_TCU1 JZ4740_IRQ(22)
+#define JZ4740_IRQ_TCU0 JZ4740_IRQ(23)
+#define JZ4740_IRQ_UDC JZ4740_IRQ(24)
+#define JZ4740_IRQ_GPIO3 JZ4740_IRQ(25)
+#define JZ4740_IRQ_GPIO2 JZ4740_IRQ(26)
+#define JZ4740_IRQ_GPIO1 JZ4740_IRQ(27)
+#define JZ4740_IRQ_GPIO0 JZ4740_IRQ(28)
+#define JZ4740_IRQ_IPU JZ4740_IRQ(29)
+#define JZ4740_IRQ_LCD JZ4740_IRQ(30)
+
+/* 2nd-level interrupts */
+#define JZ4740_IRQ_DMA(x) (JZ4740_IRQ(32) + (X))
+
+#define JZ4740_IRQ_INTC_GPIO(x) (JZ4740_IRQ_GPIO0 - (x))
+#define JZ4740_IRQ_GPIO(x) (JZ4740_IRQ(48) + (x))
+
+#define JZ4740_IRQ_ADC_BASE JZ4740_IRQ(176)
+
+#define NR_IRQS (JZ4740_IRQ_ADC_BASE + 6)
+
+#endif
diff --git a/arch/mips/jz4740/irq.c b/arch/mips/jz4740/irq.c
new file mode 100644
index 0000000..ea19f0e
--- /dev/null
+++ b/arch/mips/jz4740/irq.c
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform IRQ support
+ *
+ * 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/errno.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/timex.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <asm/io.h>
+#include <asm/mipsregs.h>
+#include <asm/irq_cpu.h>
+
+#include <asm/mach-jz4740/base.h>
+
+static void __iomem *jz_intc_base;
+static uint32_t jz_intc_wakeup;
+static uint32_t jz_intc_saved;
+
+#define JZ_REG_INTC_STATUS 0x00
+#define JZ_REG_INTC_MASK 0x04
+#define JZ_REG_INTC_SET_MASK 0x08
+#define JZ_REG_INTC_CLEAR_MASK 0x0c
+#define JZ_REG_INTC_PENDING 0x10
+
+#define IRQ_BIT(x) BIT((x) - JZ4740_IRQ_BASE)
+
+static void intc_irq_unmask(unsigned int irq)
+{
+ writel(IRQ_BIT(irq), jz_intc_base + JZ_REG_INTC_CLEAR_MASK);
+}
+
+static void intc_irq_mask(unsigned int irq)
+{
+ writel(IRQ_BIT(irq), jz_intc_base + JZ_REG_INTC_SET_MASK);
+}
+
+static int intc_irq_set_wake(unsigned int irq, unsigned int on)
+{
+ if (on)
+ jz_intc_wakeup |= IRQ_BIT(irq);
+ else
+ jz_intc_wakeup &= ~IRQ_BIT(irq);
+
+ return 0;
+}
+
+static struct irq_chip intc_irq_type = {
+ .name = "INTC",
+ .mask = intc_irq_mask,
+ .mask_ack = intc_irq_mask,
+ .unmask = intc_irq_unmask,
+ .set_wake = intc_irq_set_wake,
+};
+
+static irqreturn_t jz4740_cascade(int irq, void *data)
+{
+ uint32_t irq_reg;
+ int intc_irq;
+
+ irq_reg = readl(jz_intc_base + JZ_REG_INTC_PENDING);
+
+ intc_irq = ffs(irq_reg);
+ if (intc_irq)
+ generic_handle_irq(intc_irq - 1 + JZ4740_IRQ_BASE);
+
+ return IRQ_HANDLED;
+}
+
+static struct irqaction jz4740_cascade_action = {
+ .handler = jz4740_cascade,
+ .name = "JZ4740 cascade interrupt",
+ .flags = IRQF_DISABLED,
+};
+
+void __init arch_init_irq(void)
+{
+ int i;
+ mips_cpu_irq_init();
+
+ jz_intc_base = ioremap(JZ4740_INTC_BASE_ADDR, 0x14);
+
+ for (i = JZ4740_IRQ_BASE; i < JZ4740_IRQ_BASE + 32; i++) {
+ intc_irq_mask(i);
+ set_irq_chip_and_handler(i, &intc_irq_type, handle_level_irq);
+ }
+
+ setup_irq(2, &jz4740_cascade_action);
+}
+
+asmlinkage void plat_irq_dispatch(void)
+{
+ unsigned int pending = read_c0_status() & read_c0_cause() & ST0_IM;
+ if (pending & STATUSF_IP2)
+ do_IRQ(2);
+ else if (pending & STATUSF_IP3)
+ do_IRQ(3);
+ else
+ spurious_interrupt();
+}
+
+void jz4740_intc_suspend(void)
+{
+ jz_intc_saved = readl(jz_intc_base + JZ_REG_INTC_MASK);
+ writel(~jz_intc_wakeup, jz_intc_base + JZ_REG_INTC_SET_MASK);
+ writel(jz_intc_wakeup, jz_intc_base + JZ_REG_INTC_CLEAR_MASK);
+}
+
+void jz4740_intc_resume(void)
+{
+ writel(~jz_intc_saved, jz_intc_base + JZ_REG_INTC_CLEAR_MASK);
+ writel(jz_intc_saved, jz_intc_base + JZ_REG_INTC_SET_MASK);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static inline void intc_seq_reg(struct seq_file *s, const char *name,
+ unsigned int reg)
+{
+ seq_printf(s, "%s:\t\t%08x\n", name, readl(jz_intc_base + reg));
+}
+
+static int intc_regs_show(struct seq_file *s, void *unused)
+{
+ intc_seq_reg(s, "Status", JZ_REG_INTC_STATUS);
+ intc_seq_reg(s, "Mask", JZ_REG_INTC_MASK);
+ intc_seq_reg(s, "Pending", JZ_REG_INTC_PENDING);
+
+ return 0;
+}
+
+static int intc_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, intc_regs_show, NULL);
+}
+
+static const struct file_operations intc_regs_operations = {
+ .open = intc_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init intc_debugfs_init(void)
+{
+ (void) debugfs_create_file("jz_regs_intc", S_IFREG | S_IRUGO,
+ NULL, NULL, &intc_regs_operations);
+ return 0;
+}
+subsys_initcall(intc_debugfs_init);
+
+#endif
diff --git a/arch/mips/jz4740/irq.h b/arch/mips/jz4740/irq.h
new file mode 100644
index 0000000..56b5ead
--- /dev/null
+++ b/arch/mips/jz4740/irq.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MIPS_JZ4740_IRQ_H__
+#define __MIPS_JZ4740_IRQ_H__
+
+extern void jz4740_intc_suspend(void);
+extern void jz4740_intc_resume(void);
+
+#endif
--
1.5.6.5

2010-06-19 05:09:24

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 03/26] MIPS: JZ4740: Add clock API support.

This patch adds support for managing the clocks found on JZ4740 SoC through
the Linux clock API.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/include/asm/mach-jz4740/clock.h | 28 +
arch/mips/jz4740/clock-debugfs.c | 109 ++++
arch/mips/jz4740/clock.c | 920 +++++++++++++++++++++++++++++
arch/mips/jz4740/clock.h | 76 +++
4 files changed, 1133 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h
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

diff --git a/arch/mips/include/asm/mach-jz4740/clock.h b/arch/mips/include/asm/mach-jz4740/clock.h
new file mode 100644
index 0000000..1b7408d
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/clock.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_JZ4740_CLOCK_H__
+#define __ASM_JZ4740_CLOCK_H__
+
+enum jz4740_wait_mode {
+ JZ4740_WAIT_MODE_IDLE,
+ JZ4740_WAIT_MODE_SLEEP,
+};
+
+void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode);
+
+void jz4740_clock_udc_enable_auto_suspend(void);
+void jz4740_clock_udc_disable_auto_suspend(void);
+
+#endif
diff --git a/arch/mips/jz4740/clock-debugfs.c b/arch/mips/jz4740/clock-debugfs.c
new file mode 100644
index 0000000..330a0f2
--- /dev/null
+++ b/arch/mips/jz4740/clock-debugfs.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support debugfs entries
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+
+#include <asm/mach-jz4740/clock.h>
+#include "clock.h"
+
+static struct dentry *jz4740_clock_debugfs;
+
+static int jz4740_clock_debugfs_show_enabled(void *data, uint64_t *value)
+{
+ struct clk *clk = data;
+ *value = clk_is_enabled(clk);
+
+ return 0;
+}
+
+static int jz4740_clock_debugfs_set_enabled(void *data, uint64_t value)
+{
+ struct clk *clk = data;
+
+ if (value)
+ return clk_enable(clk);
+ else
+ clk_disable(clk);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(jz4740_clock_debugfs_ops_enabled,
+ jz4740_clock_debugfs_show_enabled,
+ jz4740_clock_debugfs_set_enabled,
+ "%llu\n");
+
+static int jz4740_clock_debugfs_show_rate(void *data, uint64_t *value)
+{
+ struct clk *clk = data;
+ *value = clk_get_rate(clk);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(jz4740_clock_debugfs_ops_rate,
+ jz4740_clock_debugfs_show_rate,
+ NULL,
+ "%llu\n");
+
+void jz4740_clock_debugfs_add_clk(struct clk *clk)
+{
+ if (!jz4740_clock_debugfs)
+ return;
+
+ clk->debugfs_entry = debugfs_create_dir(clk->name, jz4740_clock_debugfs);
+ debugfs_create_file("rate", S_IWUGO | S_IRUGO, clk->debugfs_entry, clk,
+ &jz4740_clock_debugfs_ops_rate);
+ debugfs_create_file("enabled", S_IRUGO, clk->debugfs_entry, clk,
+ &jz4740_clock_debugfs_ops_enabled);
+
+ if (clk->parent) {
+ char parent_path[100];
+ snprintf(parent_path, 100, "../%s", clk->parent->name);
+ clk->debugfs_parent_entry = debugfs_create_symlink("parent",
+ clk->debugfs_entry,
+ parent_path);
+ }
+}
+
+/* TODO: Locking */
+void jz4740_clock_debugfs_update_parent(struct clk *clk)
+{
+ if (clk->debugfs_parent_entry)
+ debugfs_remove(clk->debugfs_parent_entry);
+
+ if (clk->parent) {
+ char parent_path[100];
+ snprintf(parent_path, 100, "../%s", clk->parent->name);
+ clk->debugfs_parent_entry = debugfs_create_symlink("parent",
+ clk->debugfs_entry,
+ parent_path);
+ } else {
+ clk->debugfs_parent_entry = NULL;
+ }
+}
+
+void jz4740_clock_debugfs_init(void)
+{
+ jz4740_clock_debugfs = debugfs_create_dir("jz4740-clock", NULL);
+ if (IS_ERR(jz4740_clock_debugfs))
+ jz4740_clock_debugfs = NULL;
+}
diff --git a/arch/mips/jz4740/clock.c b/arch/mips/jz4740/clock.c
new file mode 100644
index 0000000..3ac4660
--- /dev/null
+++ b/arch/mips/jz4740/clock.c
@@ -0,0 +1,920 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support
+ *
+ * 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/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/err.h>
+
+#include <asm/mach-jz4740/clock.h>
+#include <asm/mach-jz4740/base.h>
+
+#include "clock.h"
+
+#define JZ_REG_CLOCK_CTRL 0x00
+#define JZ_REG_CLOCK_LOW_POWER 0x04
+#define JZ_REG_CLOCK_PLL 0x10
+#define JZ_REG_CLOCK_GATE 0x20
+#define JZ_REG_CLOCK_SLEEP_CTRL 0x24
+#define JZ_REG_CLOCK_I2S 0x60
+#define JZ_REG_CLOCK_LCD 0x64
+#define JZ_REG_CLOCK_MMC 0x68
+#define JZ_REG_CLOCK_UHC 0x6C
+#define JZ_REG_CLOCK_SPI 0x74
+
+#define JZ_CLOCK_CTRL_I2S_SRC_PLL BIT(31)
+#define JZ_CLOCK_CTRL_KO_ENABLE BIT(30)
+#define JZ_CLOCK_CTRL_UDC_SRC_PLL BIT(29)
+#define JZ_CLOCK_CTRL_UDIV_MASK 0x1f800000
+#define JZ_CLOCK_CTRL_CHANGE_ENABLE BIT(22)
+#define JZ_CLOCK_CTRL_PLL_HALF BIT(21)
+#define JZ_CLOCK_CTRL_LDIV_MASK 0x001f0000
+#define JZ_CLOCK_CTRL_UDIV_OFFSET 23
+#define JZ_CLOCK_CTRL_LDIV_OFFSET 16
+#define JZ_CLOCK_CTRL_MDIV_OFFSET 12
+#define JZ_CLOCK_CTRL_PDIV_OFFSET 8
+#define JZ_CLOCK_CTRL_HDIV_OFFSET 4
+#define JZ_CLOCK_CTRL_CDIV_OFFSET 0
+
+#define JZ_CLOCK_GATE_UART0 BIT(0)
+#define JZ_CLOCK_GATE_TCU BIT(1)
+#define JZ_CLOCK_GATE_RTC BIT(2)
+#define JZ_CLOCK_GATE_I2C BIT(3)
+#define JZ_CLOCK_GATE_SPI BIT(4)
+#define JZ_CLOCK_GATE_AIC BIT(5)
+#define JZ_CLOCK_GATE_I2S BIT(6)
+#define JZ_CLOCK_GATE_MMC BIT(7)
+#define JZ_CLOCK_GATE_ADC BIT(8)
+#define JZ_CLOCK_GATE_CIM BIT(9)
+#define JZ_CLOCK_GATE_LCD BIT(10)
+#define JZ_CLOCK_GATE_UDC BIT(11)
+#define JZ_CLOCK_GATE_DMAC BIT(12)
+#define JZ_CLOCK_GATE_IPU BIT(13)
+#define JZ_CLOCK_GATE_UHC BIT(14)
+#define JZ_CLOCK_GATE_UART1 BIT(15)
+
+#define JZ_CLOCK_I2S_DIV_MASK 0x01ff
+
+#define JZ_CLOCK_LCD_DIV_MASK 0x01ff
+
+#define JZ_CLOCK_MMC_DIV_MASK 0x001f
+
+#define JZ_CLOCK_UHC_DIV_MASK 0x000f
+
+#define JZ_CLOCK_SPI_SRC_PLL BIT(31)
+#define JZ_CLOCK_SPI_DIV_MASK 0x000f
+
+#define JZ_CLOCK_PLL_M_MASK 0x01ff
+#define JZ_CLOCK_PLL_N_MASK 0x001f
+#define JZ_CLOCK_PLL_OD_MASK 0x0003
+#define JZ_CLOCK_PLL_STABLE BIT(10)
+#define JZ_CLOCK_PLL_BYPASS BIT(9)
+#define JZ_CLOCK_PLL_ENABLED BIT(8)
+#define JZ_CLOCK_PLL_STABLIZE_MASK 0x000f
+#define JZ_CLOCK_PLL_M_OFFSET 23
+#define JZ_CLOCK_PLL_N_OFFSET 18
+#define JZ_CLOCK_PLL_OD_OFFSET 16
+
+#define JZ_CLOCK_LOW_POWER_MODE_DOZE BIT(2)
+#define JZ_CLOCK_LOW_POWER_MODE_SLEEP BIT(0)
+
+#define JZ_CLOCK_SLEEP_CTRL_SUSPEND_UHC BIT(7)
+#define JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC BIT(6)
+
+static void __iomem *jz_clock_base;
+static spinlock_t jz_clock_lock;
+static LIST_HEAD(jz_clocks);
+
+struct main_clk {
+ struct clk clk;
+ uint32_t div_offset;
+};
+
+struct divided_clk {
+ struct clk clk;
+ uint32_t reg;
+ uint32_t mask;
+};
+
+struct static_clk {
+ struct clk clk;
+ unsigned long rate;
+};
+
+static uint32_t jz_clk_reg_read(int reg)
+{
+ return readl(jz_clock_base + reg);
+}
+
+static void jz_clk_reg_write_mask(int reg, uint32_t val, uint32_t mask)
+{
+ uint32_t val2;
+
+ spin_lock(&jz_clock_lock);
+ val2 = readl(jz_clock_base + reg);
+ val2 &= ~mask;
+ val2 |= val;
+ writel(val2, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static void jz_clk_reg_set_bits(int reg, uint32_t mask)
+{
+ uint32_t val;
+
+ spin_lock(&jz_clock_lock);
+ val = readl(jz_clock_base + reg);
+ val |= mask;
+ writel(val, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static void jz_clk_reg_clear_bits(int reg, uint32_t mask)
+{
+ uint32_t val;
+
+ spin_lock(&jz_clock_lock);
+ val = readl(jz_clock_base + reg);
+ val &= ~mask;
+ writel(val, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static int jz_clk_enable_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return -EINVAL;
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE, clk->gate_bit);
+ return 0;
+}
+
+static int jz_clk_disable_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return -EINVAL;
+
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE, clk->gate_bit);
+ return 0;
+}
+
+static int jz_clk_is_enabled_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return 1;
+
+ return !(jz_clk_reg_read(JZ_REG_CLOCK_GATE) & clk->gate_bit);
+}
+
+static unsigned long jz_clk_static_get_rate(struct clk *clk)
+{
+ return ((struct static_clk *)clk)->rate;
+}
+
+static int jz_clk_ko_enable(struct clk *clk)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_KO_ENABLE);
+ return 0;
+}
+
+static int jz_clk_ko_disable(struct clk *clk)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_KO_ENABLE);
+ return 0;
+}
+
+static int jz_clk_ko_is_enabled(struct clk *clk)
+{
+ return !!(jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_KO_ENABLE);
+}
+
+static const int pllno[] = {1, 2, 2, 4};
+
+static unsigned long jz_clk_pll_get_rate(struct clk *clk)
+{
+ uint32_t val;
+ int m;
+ int n;
+ int od;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_PLL);
+
+ if (val & JZ_CLOCK_PLL_BYPASS)
+ return clk_get_rate(clk->parent);
+
+ m = ((val >> 23) & 0x1ff) + 2;
+ n = ((val >> 18) & 0x1f) + 2;
+ od = (val >> 16) & 0x3;
+
+ return clk_get_rate(clk->parent) * (m / n) / pllno[od];
+}
+
+static unsigned long jz_clk_pll_half_get_rate(struct clk *clk)
+{
+ uint32_t reg;
+
+ reg = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+ if (reg & JZ_CLOCK_CTRL_PLL_HALF)
+ return jz_clk_pll_get_rate(clk->parent);
+ return jz_clk_pll_get_rate(clk->parent) >> 1;
+}
+
+static const int jz_clk_main_divs[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32};
+
+static unsigned long jz_clk_main_round_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long parent_rate = jz_clk_pll_get_rate(clk->parent);
+ int div;
+
+ div = parent_rate / rate;
+ if (div > 32)
+ return parent_rate / 32;
+ else if (div < 1)
+ return parent_rate;
+
+ div &= (0x3 << (ffs(div) - 1));
+
+ return parent_rate / div;
+}
+
+static unsigned long jz_clk_main_get_rate(struct clk *clk)
+{
+ struct main_clk *mclk = (struct main_clk *)clk;
+ uint32_t div;
+
+ div = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+
+ div >>= mclk->div_offset;
+ div &= 0xf;
+
+ if (div >= ARRAY_SIZE(jz_clk_main_divs))
+ div = ARRAY_SIZE(jz_clk_main_divs) - 1;
+
+ return jz_clk_pll_get_rate(clk->parent) / jz_clk_main_divs[div];
+}
+
+static int jz_clk_main_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct main_clk *mclk = (struct main_clk *)clk;
+ int i;
+ int div;
+ unsigned long parent_rate = jz_clk_pll_get_rate(clk->parent);
+
+ rate = jz_clk_main_round_rate(clk, rate);
+
+ div = parent_rate / rate;
+
+ i = (ffs(div) - 1) << 1;
+ if (i > 0 && !(div & BIT(i-1)))
+ i -= 1;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, i << mclk->div_offset,
+ 0xf << mclk->div_offset);
+
+ return 0;
+}
+
+static struct clk_ops jz_clk_static_ops = {
+ .get_rate = jz_clk_static_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct static_clk jz_clk_ext = {
+ .clk = {
+ .name = "ext",
+ .gate_bit = JZ4740_CLK_NOT_GATED,
+ .ops = &jz_clk_static_ops,
+ },
+};
+
+static struct clk_ops jz_clk_pll_ops = {
+ .get_rate = jz_clk_static_get_rate,
+};
+
+static struct clk jz_clk_pll = {
+ .name = "pll",
+ .parent = &jz_clk_ext.clk,
+ .ops = &jz_clk_pll_ops,
+};
+
+static struct clk_ops jz_clk_pll_half_ops = {
+ .get_rate = jz_clk_pll_half_get_rate,
+};
+
+static struct clk jz_clk_pll_half = {
+ .name = "pll half",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_pll_half_ops,
+};
+
+static const struct clk_ops jz_clk_main_ops = {
+ .get_rate = jz_clk_main_get_rate,
+ .set_rate = jz_clk_main_set_rate,
+ .round_rate = jz_clk_main_round_rate,
+};
+
+static struct main_clk jz_clk_cpu = {
+ .clk = {
+ .name = "cclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_CDIV_OFFSET,
+};
+
+static struct main_clk jz_clk_memory = {
+ .clk = {
+ .name = "mclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_MDIV_OFFSET,
+};
+
+static struct main_clk jz_clk_high_speed_peripheral = {
+ .clk = {
+ .name = "hclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_HDIV_OFFSET,
+};
+
+
+static struct main_clk jz_clk_low_speed_peripheral = {
+ .clk = {
+ .name = "pclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_PDIV_OFFSET,
+};
+
+static const struct clk_ops jz_clk_ko_ops = {
+ .enable = jz_clk_ko_enable,
+ .disable = jz_clk_ko_disable,
+ .is_enabled = jz_clk_ko_is_enabled,
+};
+
+static struct clk jz_clk_ko = {
+ .name = "cko",
+ .parent = &jz_clk_memory.clk,
+ .ops = &jz_clk_ko_ops,
+};
+
+static int jz_clk_spi_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll)
+ jz_clk_reg_set_bits(JZ_CLOCK_SPI_SRC_PLL, JZ_REG_CLOCK_SPI);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_CLOCK_SPI_SRC_PLL, JZ_REG_CLOCK_SPI);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_i2s_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll_half)
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_I2S_SRC_PLL);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_I2S_SRC_PLL);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_udc_enable(struct clk *clk)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_SLEEP_CTRL,
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+
+ return 0;
+}
+
+static int jz_clk_udc_disable(struct clk *clk)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_SLEEP_CTRL,
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+
+ return 0;
+}
+
+static int jz_clk_udc_is_enabled(struct clk *clk)
+{
+ return !!(jz_clk_reg_read(JZ_REG_CLOCK_SLEEP_CTRL) &
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+}
+
+static int jz_clk_udc_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll_half)
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_UDC_SRC_PLL);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_UDC_SRC_PLL);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_udc_set_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return -EINVAL;
+
+ div = clk_get_rate(clk->parent) / rate - 1;
+
+ if (div < 0)
+ div = 0;
+ else if (div > 63)
+ div = 63;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, div << JZ_CLOCK_CTRL_UDIV_OFFSET,
+ JZ_CLOCK_CTRL_UDIV_MASK);
+ return 0;
+}
+
+static unsigned long jz_clk_udc_get_rate(struct clk *clk)
+{
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return clk_get_rate(clk->parent);
+
+ div = (jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_UDIV_MASK);
+ div >>= JZ_CLOCK_CTRL_UDIV_OFFSET;
+ div += 1;
+
+ return clk_get_rate(clk->parent) / div;
+}
+
+static unsigned long jz_clk_divided_get_rate(struct clk *clk)
+{
+ struct divided_clk *dclk = (struct divided_clk *)clk;
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return clk_get_rate(clk->parent);
+
+ div = (jz_clk_reg_read(dclk->reg) & dclk->mask) + 1;
+
+ return clk_get_rate(clk->parent) / div;
+}
+
+static int jz_clk_divided_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct divided_clk *dclk = (struct divided_clk *)clk;
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return -EINVAL;
+
+ div = clk_get_rate(clk->parent) / rate - 1;
+
+ if (div < 0)
+ div = 0;
+ else if (div > dclk->mask)
+ div = dclk->mask;
+
+ jz_clk_reg_write_mask(dclk->reg, div, dclk->mask);
+
+ return 0;
+}
+
+static unsigned long jz_clk_ldclk_round_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+ unsigned long parent_rate = jz_clk_pll_half_get_rate(clk->parent);
+
+ if (rate > 150000000)
+ return 150000000;
+
+ div = parent_rate / rate;
+ if (div < 1)
+ div = 1;
+ else if (div > 32)
+ div = 32;
+
+ return parent_rate / div;
+}
+
+static int jz_clk_ldclk_set_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+
+ if (rate > 150000000)
+ return -EINVAL;
+
+ div = jz_clk_pll_half_get_rate(clk->parent) / rate - 1;
+ if (div < 0)
+ div = 0;
+ else if (div > 31)
+ div = 31;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, div << JZ_CLOCK_CTRL_LDIV_OFFSET,
+ JZ_CLOCK_CTRL_LDIV_MASK);
+
+ return 0;
+}
+
+static unsigned long jz_clk_ldclk_get_rate(struct clk *clk)
+{
+ int div;
+
+ div = jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_LDIV_MASK;
+ div >>= JZ_CLOCK_CTRL_LDIV_OFFSET;
+
+ return jz_clk_pll_half_get_rate(clk->parent) / (div + 1);
+}
+
+static const struct clk_ops jz_clk_ops_ld = {
+ .set_rate = jz_clk_ldclk_set_rate,
+ .get_rate = jz_clk_ldclk_get_rate,
+ .round_rate = jz_clk_ldclk_round_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct clk jz_clk_ld = {
+ .name = "lcd",
+ .gate_bit = JZ_CLOCK_GATE_LCD,
+ .parent = &jz_clk_pll_half,
+ .ops = &jz_clk_ops_ld,
+};
+
+static const struct clk_ops jz_clk_i2s_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+ .set_parent = jz_clk_i2s_set_parent,
+};
+
+static const struct clk_ops jz_clk_spi_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+ .set_parent = jz_clk_spi_set_parent,
+};
+
+static const struct clk_ops jz_clk_divided_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct divided_clk jz4740_clock_divided_clks[] = {
+ {
+ .clk = {
+ .name = "lcd_pclk",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ4740_CLK_NOT_GATED,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_LCD,
+ .mask = JZ_CLOCK_LCD_DIV_MASK,
+ },
+ {
+ .clk = {
+ .name = "i2s",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_I2S,
+ .ops = &jz_clk_i2s_ops,
+ },
+ .reg = JZ_REG_CLOCK_I2S,
+ .mask = JZ_CLOCK_I2S_DIV_MASK,
+ },
+ {
+ .clk = {
+ .name = "spi",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_SPI,
+ .ops = &jz_clk_spi_ops,
+ },
+ .reg = JZ_REG_CLOCK_SPI,
+ .mask = JZ_CLOCK_SPI_DIV_MASK,
+ },
+ {
+ .clk = {
+ .name = "mmc",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ_CLOCK_GATE_MMC,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_MMC,
+ .mask = JZ_CLOCK_MMC_DIV_MASK,
+ },
+ {
+ .clk = {
+ .name = "uhc",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ_CLOCK_GATE_UHC,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_UHC,
+ .mask = JZ_CLOCK_UHC_DIV_MASK,
+ },
+};
+
+static const struct clk_ops jz_clk_udc_ops = {
+ .set_parent = jz_clk_udc_set_parent,
+ .set_rate = jz_clk_udc_set_rate,
+ .get_rate = jz_clk_udc_get_rate,
+ .enable = jz_clk_udc_enable,
+ .disable = jz_clk_udc_disable,
+ .is_enabled = jz_clk_udc_is_enabled,
+};
+
+static const struct clk_ops jz_clk_simple_ops = {
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct clk jz4740_clock_simple_clks[] = {
+ {
+ .name = "udc",
+ .parent = &jz_clk_ext.clk,
+ .ops = &jz_clk_udc_ops,
+ },
+ {
+ .name = "uart0",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART0,
+ .ops = &jz_clk_simple_ops,
+ },
+ {
+ .name = "uart1",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART1,
+ .ops = &jz_clk_simple_ops,
+ },
+ {
+ .name = "dma",
+ .parent = &jz_clk_high_speed_peripheral.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART0,
+ .ops = &jz_clk_simple_ops,
+ },
+ {
+ .name = "ipu",
+ .parent = &jz_clk_high_speed_peripheral.clk,
+ .gate_bit = JZ_CLOCK_GATE_IPU,
+ .ops = &jz_clk_simple_ops,
+ },
+ {
+ .name = "adc",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_ADC,
+ .ops = &jz_clk_simple_ops,
+ },
+ {
+ .name = "i2c",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_I2C,
+ .ops = &jz_clk_simple_ops,
+ },
+ {
+ .name = "aic",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_AIC,
+ .ops = &jz_clk_simple_ops,
+ },
+};
+
+static struct static_clk jz_clk_rtc = {
+ .clk = {
+ .name = "rtc",
+ .gate_bit = JZ_CLOCK_GATE_RTC,
+ .ops = &jz_clk_static_ops,
+ },
+ .rate = 32768,
+};
+
+int clk_enable(struct clk *clk)
+{
+ if (!clk->ops->enable)
+ return -EINVAL;
+
+ return clk->ops->enable(clk);
+}
+EXPORT_SYMBOL_GPL(clk_enable);
+
+void clk_disable(struct clk *clk)
+{
+ if (clk->ops->disable)
+ clk->ops->disable(clk);
+}
+EXPORT_SYMBOL_GPL(clk_disable);
+
+int clk_is_enabled(struct clk *clk)
+{
+ if (clk->ops->is_enabled)
+ return clk->ops->is_enabled(clk);
+
+ return 1;
+}
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ if (clk->ops->get_rate)
+ return clk->ops->get_rate(clk);
+ if (clk->parent)
+ return clk_get_rate(clk->parent);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(clk_get_rate);
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ if (!clk->ops->set_rate)
+ return -EINVAL;
+ return clk->ops->set_rate(clk, rate);
+}
+EXPORT_SYMBOL_GPL(clk_set_rate);
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ if (clk->ops->round_rate)
+ return clk->ops->round_rate(clk, rate);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(clk_round_rate);
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ int ret;
+
+ if (!clk->ops->set_parent)
+ return -EINVAL;
+
+ clk_disable(clk);
+ ret = clk->ops->set_parent(clk, parent);
+ clk_enable(clk);
+
+ jz4740_clock_debugfs_update_parent(clk);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(clk_set_parent);
+
+struct clk *clk_get(struct device *dev, const char *name)
+{
+ struct clk *clk;
+
+ list_for_each_entry(clk, &jz_clocks, list) {
+ if (strcmp(clk->name, name) == 0)
+ return clk;
+ }
+ return ERR_PTR(-ENOENT);
+}
+EXPORT_SYMBOL_GPL(clk_get);
+
+void clk_put(struct clk *clk)
+{
+}
+EXPORT_SYMBOL_GPL(clk_put);
+
+static inline void clk_add(struct clk *clk)
+{
+ list_add_tail(&clk->list, &jz_clocks);
+
+ jz4740_clock_debugfs_add_clk(clk);
+}
+
+static void clk_register_clks(void)
+{
+ size_t i;
+
+ clk_add(&jz_clk_ext.clk);
+ clk_add(&jz_clk_pll);
+ clk_add(&jz_clk_pll_half);
+ clk_add(&jz_clk_cpu.clk);
+ clk_add(&jz_clk_high_speed_peripheral.clk);
+ clk_add(&jz_clk_low_speed_peripheral.clk);
+ clk_add(&jz_clk_ko);
+ clk_add(&jz_clk_ld);
+ clk_add(&jz_clk_rtc.clk);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_clock_divided_clks); ++i)
+ clk_add(&jz4740_clock_divided_clks[i].clk);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_clock_simple_clks); ++i)
+ clk_add(&jz4740_clock_simple_clks[i]);
+}
+
+void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode)
+{
+ switch (mode) {
+ case JZ4740_WAIT_MODE_IDLE:
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_LOW_POWER, JZ_CLOCK_LOW_POWER_MODE_SLEEP);
+ break;
+ case JZ4740_WAIT_MODE_SLEEP:
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_LOW_POWER, JZ_CLOCK_LOW_POWER_MODE_SLEEP);
+ break;
+ }
+}
+
+void jz4740_clock_udc_disable_auto_suspend(void)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE, JZ_CLOCK_GATE_UDC);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_disable_auto_suspend);
+
+void jz4740_clock_udc_enable_auto_suspend(void)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE, JZ_CLOCK_GATE_UDC);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_enable_auto_suspend);
+
+void jz4740_clock_suspend(void)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE,
+ JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0);
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_PLL, JZ_CLOCK_PLL_ENABLED);
+}
+
+void jz4740_clock_resume(void)
+{
+ uint32_t pll;
+
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_PLL, JZ_CLOCK_PLL_ENABLED);
+
+ do {
+ pll = jz_clk_reg_read(JZ_REG_CLOCK_PLL);
+ } while (!(pll & JZ_CLOCK_PLL_STABLE));
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE,
+ JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0);
+}
+
+static int jz4740_clock_init(void)
+{
+ uint32_t val;
+
+ jz_clock_base = ioremap(JZ4740_CPM_BASE_ADDR, 0x100);
+ if (!jz_clock_base)
+ return -EBUSY;
+
+ spin_lock_init(&jz_clock_lock);
+
+ jz_clk_ext.rate = jz4740_clock_bdata.ext_rate;
+ jz_clk_rtc.rate = jz4740_clock_bdata.rtc_rate;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_SPI);
+
+ if (val & JZ_CLOCK_SPI_SRC_PLL)
+ jz4740_clock_divided_clks[1].clk.parent = &jz_clk_pll_half;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+
+ if (val & JZ_CLOCK_CTRL_I2S_SRC_PLL)
+ jz4740_clock_divided_clks[0].clk.parent = &jz_clk_pll_half;
+
+ if (val & JZ_CLOCK_CTRL_UDC_SRC_PLL)
+ jz4740_clock_simple_clks[0].parent = &jz_clk_pll_half;
+
+ jz4740_clock_debugfs_init();
+
+ clk_register_clks();
+
+ return 0;
+}
+subsys_initcall(jz4740_clock_init);
diff --git a/arch/mips/jz4740/clock.h b/arch/mips/jz4740/clock.h
new file mode 100644
index 0000000..5d07499
--- /dev/null
+++ b/arch/mips/jz4740/clock.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MIPS_JZ4740_CLOCK_H__
+#define __MIPS_JZ4740_CLOCK_H__
+
+#include <linux/list.h>
+
+struct jz4740_clock_board_data {
+ unsigned long ext_rate;
+ unsigned long rtc_rate;
+};
+
+extern struct jz4740_clock_board_data jz4740_clock_bdata;
+
+void jz4740_clock_suspend(void);
+void jz4740_clock_resume(void);
+
+struct clk;
+
+struct clk_ops {
+ unsigned long (*get_rate)(struct clk *clk);
+ unsigned long (*round_rate)(struct clk *clk, unsigned long rate);
+ int (*set_rate)(struct clk *clk, unsigned long rate);
+ int (*enable)(struct clk *clk);
+ int (*disable)(struct clk *clk);
+ int (*is_enabled)(struct clk *clk);
+
+ int (*set_parent)(struct clk *clk, struct clk *parent);
+
+};
+
+struct clk {
+ const char *name;
+ struct clk *parent;
+
+ uint32_t gate_bit;
+
+ const struct clk_ops *ops;
+
+ struct list_head list;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs_entry;
+ struct dentry *debugfs_parent_entry;
+#endif
+
+};
+
+#define JZ4740_CLK_NOT_GATED ((uint32_t)-1)
+
+int clk_is_enabled(struct clk *clk);
+
+#ifdef CONFIG_DEBUG_FS
+void jz4740_clock_debugfs_init(void);
+void jz4740_clock_debugfs_add_clk(struct clk *clk);
+void jz4740_clock_debugfs_update_parent(struct clk *clk);
+#else
+static inline void jz4740_clock_debugfs_init(void) {};
+static inline void jz4740_clock_debugfs_add_clk(struct clk *clk) {};
+static inline void jz4740_clock_debugfs_update_parent(struct clk *clk) {};
+#endif
+
+#endif
--
1.5.6.5

2010-06-19 05:09:58

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 09/26] MIPS: JZ4740: Add DMA support.

This patch adds support for DMA transfers on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/include/asm/mach-jz4740/dma.h | 90 ++++++++++
arch/mips/jz4740/dma.c | 289 +++++++++++++++++++++++++++++++
2 files changed, 379 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/dma.h
create mode 100644 arch/mips/jz4740/dma.c

diff --git a/arch/mips/include/asm/mach-jz4740/dma.h b/arch/mips/include/asm/mach-jz4740/dma.h
new file mode 100644
index 0000000..a3be121
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/dma.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ7420/JZ4740 DMA definitions
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_MACH_JZ4740_DMA_H__
+#define __ASM_MACH_JZ4740_DMA_H__
+
+struct jz4740_dma_chan;
+
+enum jz4740_dma_request_type {
+ JZ4740_DMA_TYPE_AUTO_REQUEST = 8,
+ JZ4740_DMA_TYPE_UART_TRANSMIT = 20,
+ JZ4740_DMA_TYPE_UART_RECEIVE = 21,
+ JZ4740_DMA_TYPE_SPI_TRANSMIT = 22,
+ JZ4740_DMA_TYPE_SPI_RECEIVE = 23,
+ JZ4740_DMA_TYPE_AIC_TRANSMIT = 24,
+ JZ4740_DMA_TYPE_AIC_RECEIVE = 25,
+ JZ4740_DMA_TYPE_MMC_TRANSMIT = 26,
+ JZ4740_DMA_TYPE_MMC_RECEIVE = 27,
+ JZ4740_DMA_TYPE_TCU = 28,
+ JZ4740_DMA_TYPE_SADC = 29,
+ JZ4740_DMA_TYPE_SLCD = 30,
+};
+
+enum jz4740_dma_width {
+ JZ4740_DMA_WIDTH_32BIT = 0,
+ JZ4740_DMA_WIDTH_8BIT = 1,
+ JZ4740_DMA_WIDTH_16BIT = 2,
+};
+
+enum jz4740_dma_transfer_size {
+ JZ4740_DMA_TRANSFER_SIZE_4BYTE = 0,
+ JZ4740_DMA_TRANSFER_SIZE_1BYTE = 1,
+ JZ4740_DMA_TRANSFER_SIZE_2BYTE = 2,
+ JZ4740_DMA_TRANSFER_SIZE_16BYTE = 3,
+ JZ4740_DMA_TRANSFER_SIZE_32BYTE = 4,
+};
+
+enum jz4740_dma_flags {
+ JZ4740_DMA_SRC_AUTOINC = 0x2,
+ JZ4740_DMA_DST_AUTOINC = 0x1,
+};
+
+enum jz4740_dma_mode {
+ JZ4740_DMA_MODE_SINGLE = 0,
+ JZ4740_DMA_MODE_BLOCK = 1,
+};
+
+struct jz4740_dma_config {
+ enum jz4740_dma_width src_width;
+ enum jz4740_dma_width dst_width;
+ enum jz4740_dma_transfer_size transfer_size;
+ enum jz4740_dma_request_type request_type;
+ enum jz4740_dma_flags flags;
+ enum jz4740_dma_mode mode;
+};
+
+typedef void (*jz4740_dma_complete_callback_t)(struct jz4740_dma_chan *, int, void *);
+
+struct jz4740_dma_chan *jz4740_dma_request(void *dev, const char *name);
+void jz4740_dma_free(struct jz4740_dma_chan *dma);
+
+void jz4740_dma_configure(struct jz4740_dma_chan *dma,
+ const struct jz4740_dma_config *config);
+
+
+void jz4740_dma_enable(struct jz4740_dma_chan *dma);
+void jz4740_dma_disable(struct jz4740_dma_chan *dma);
+
+void jz4740_dma_set_src_addr(struct jz4740_dma_chan *dma, dma_addr_t src);
+void jz4740_dma_set_dst_addr(struct jz4740_dma_chan *dma, dma_addr_t dst);
+void jz4740_dma_set_transfer_count(struct jz4740_dma_chan *dma, uint32_t count);
+
+uint32_t jz4740_dma_get_residue(const struct jz4740_dma_chan *dma);
+
+void jz4740_dma_set_complete_cb(struct jz4740_dma_chan *dma,
+ jz4740_dma_complete_callback_t cb);
+
+#endif /* __ASM_JZ4740_DMA_H__ */
diff --git a/arch/mips/jz4740/dma.c b/arch/mips/jz4740/dma.c
new file mode 100644
index 0000000..5ebe75a
--- /dev/null
+++ b/arch/mips/jz4740/dma.c
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC DMA support
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+
+#include <linux/dma-mapping.h>
+#include <asm/mach-jz4740/dma.h>
+#include <asm/mach-jz4740/base.h>
+
+#define JZ_REG_DMA_SRC_ADDR(x) (0x00 + (x) * 0x20)
+#define JZ_REG_DMA_DST_ADDR(x) (0x04 + (x) * 0x20)
+#define JZ_REG_DMA_TRANSFER_COUNT(x) (0x08 + (x) * 0x20)
+#define JZ_REG_DMA_REQ_TYPE(x) (0x0C + (x) * 0x20)
+#define JZ_REG_DMA_STATUS_CTRL(x) (0x10 + (x) * 0x20)
+#define JZ_REG_DMA_CMD(x) (0x14 + (x) * 0x20)
+#define JZ_REG_DMA_DESC_ADDR(x) (0x18 + (x) * 0x20)
+
+#define JZ_REG_DMA_CTRL 0x300
+#define JZ_REG_DMA_IRQ 0x304
+#define JZ_REG_DMA_DOORBELL 0x308
+#define JZ_REG_DMA_DOORBELL_SET 0x30C
+
+#define JZ_DMA_STATUS_CTRL_NO_DESC BIT(31)
+#define JZ_DMA_STATUS_CTRL_DESC_INV BIT(6)
+#define JZ_DMA_STATUS_CTRL_ADDR_ERR BIT(4)
+#define JZ_DMA_STATUS_CTRL_TRANSFER_DONE BIT(3)
+#define JZ_DMA_STATUS_CTRL_HALT BIT(2)
+#define JZ_DMA_STATUS_CTRL_COUNT_TERMINATE BIT(1)
+#define JZ_DMA_STATUS_CTRL_ENABLE BIT(0)
+
+#define JZ_DMA_CMD_SRC_INC BIT(23)
+#define JZ_DMA_CMD_DST_INC BIT(22)
+#define JZ_DMA_CMD_RDIL_MASK (0xf << 16)
+#define JZ_DMA_CMD_SRC_WIDTH_MASK (0x3 << 14)
+#define JZ_DMA_CMD_DST_WIDTH_MASK (0x3 << 12)
+#define JZ_DMA_CMD_INTERVAL_LENGTH_MASK (0x7 << 8)
+#define JZ_DMA_CMD_BLOCK_MODE BIT(7)
+#define JZ_DMA_CMD_DESC_VALID BIT(4)
+#define JZ_DMA_CMD_DESC_VALID_MODE BIT(3)
+#define JZ_DMA_CMD_VALID_IRQ_ENABLE BIT(2)
+#define JZ_DMA_CMD_TRANSFER_IRQ_ENABLE BIT(1)
+#define JZ_DMA_CMD_LINK_ENABLE BIT(0)
+
+#define JZ_DMA_CMD_FLAGS_OFFSET 22
+#define JZ_DMA_CMD_RDIL_OFFSET 16
+#define JZ_DMA_CMD_SRC_WIDTH_OFFSET 14
+#define JZ_DMA_CMD_DST_WIDTH_OFFSET 12
+#define JZ_DMA_CMD_TRANSFER_SIZE_OFFSET 8
+#define JZ_DMA_CMD_MODE_OFFSET 7
+
+#define JZ_DMA_CTRL_PRIORITY_MASK (0x3 << 8)
+#define JZ_DMA_CTRL_HALT BIT(3)
+#define JZ_DMA_CTRL_ADDRESS_ERROR BIT(2)
+#define JZ_DMA_CTRL_ENABLE BIT(0)
+
+
+static void __iomem *jz4740_dma_base;
+static spinlock_t jz4740_dma_lock;
+
+static inline uint32_t jz4740_dma_read(size_t reg)
+{
+ return readl(jz4740_dma_base + reg);
+}
+
+static inline void jz4740_dma_write(size_t reg, uint32_t val)
+{
+ writel(val, jz4740_dma_base + reg);
+}
+
+static inline void jz4740_dma_write_mask(size_t reg, uint32_t val, uint32_t mask)
+{
+ uint32_t val2;
+ val2 = jz4740_dma_read(reg);
+ val2 &= ~mask;
+ val2 |= val;
+ jz4740_dma_write(reg, val2);
+}
+
+struct jz4740_dma_chan {
+ unsigned int id;
+ void *dev;
+ const char *name;
+
+ enum jz4740_dma_flags flags;
+ uint32_t transfer_shift;
+
+ jz4740_dma_complete_callback_t complete_cb;
+
+ unsigned used:1;
+};
+
+#define JZ4740_DMA_CHANNEL(_id) { .id = _id }
+
+struct jz4740_dma_chan jz4740_dma_channels[] = {
+ JZ4740_DMA_CHANNEL(0),
+ JZ4740_DMA_CHANNEL(1),
+ JZ4740_DMA_CHANNEL(2),
+ JZ4740_DMA_CHANNEL(3),
+ JZ4740_DMA_CHANNEL(4),
+ JZ4740_DMA_CHANNEL(5),
+};
+
+struct jz4740_dma_chan *jz4740_dma_request(void *dev, const char *name)
+{
+ unsigned int i;
+ struct jz4740_dma_chan *dma = NULL;
+
+ spin_lock(&jz4740_dma_lock);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_dma_channels); ++i) {
+ if (!jz4740_dma_channels[i].used) {
+ dma = &jz4740_dma_channels[i];
+ dma->used = 1;
+ break;
+ }
+ }
+
+ spin_unlock(&jz4740_dma_lock);
+
+ if (!dma)
+ return NULL;
+
+ dma->dev = dev;
+ dma->name = name;
+
+ return dma;
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_request);
+
+void jz4740_dma_configure(struct jz4740_dma_chan *dma,
+ const struct jz4740_dma_config *config)
+{
+ uint32_t cmd;
+
+ switch (config->transfer_size) {
+ case JZ4740_DMA_TRANSFER_SIZE_2BYTE:
+ dma->transfer_shift = 1;
+ break;
+ case JZ4740_DMA_TRANSFER_SIZE_4BYTE:
+ dma->transfer_shift = 2;
+ break;
+ case JZ4740_DMA_TRANSFER_SIZE_16BYTE:
+ dma->transfer_shift = 4;
+ break;
+ case JZ4740_DMA_TRANSFER_SIZE_32BYTE:
+ dma->transfer_shift = 5;
+ break;
+ default:
+ dma->transfer_shift = 0;
+ break;
+ }
+
+ cmd = config->flags << JZ_DMA_CMD_FLAGS_OFFSET;
+ cmd |= config->src_width << JZ_DMA_CMD_SRC_WIDTH_OFFSET;
+ cmd |= config->dst_width << JZ_DMA_CMD_DST_WIDTH_OFFSET;
+ cmd |= config->transfer_size << JZ_DMA_CMD_TRANSFER_SIZE_OFFSET;
+ cmd |= config->mode << JZ_DMA_CMD_MODE_OFFSET;
+ cmd |= JZ_DMA_CMD_TRANSFER_IRQ_ENABLE;
+
+ jz4740_dma_write(JZ_REG_DMA_CMD(dma->id), cmd);
+ jz4740_dma_write(JZ_REG_DMA_STATUS_CTRL(dma->id), 0);
+ jz4740_dma_write(JZ_REG_DMA_REQ_TYPE(dma->id), config->request_type);
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_configure);
+
+void jz4740_dma_set_src_addr(struct jz4740_dma_chan *dma, dma_addr_t src)
+{
+ jz4740_dma_write(JZ_REG_DMA_SRC_ADDR(dma->id), src);
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_set_src_addr);
+
+void jz4740_dma_set_dst_addr(struct jz4740_dma_chan *dma, dma_addr_t dst)
+{
+ jz4740_dma_write(JZ_REG_DMA_DST_ADDR(dma->id), dst);
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_set_dst_addr);
+
+void jz4740_dma_set_transfer_count(struct jz4740_dma_chan *dma, uint32_t count)
+{
+ count >>= dma->transfer_shift;
+ jz4740_dma_write(JZ_REG_DMA_TRANSFER_COUNT(dma->id), count);
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_set_transfer_count);
+
+void jz4740_dma_set_complete_cb(struct jz4740_dma_chan *dma,
+ jz4740_dma_complete_callback_t cb)
+{
+ dma->complete_cb = cb;
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_set_complete_cb);
+
+void jz4740_dma_free(struct jz4740_dma_chan *dma)
+{
+ dma->dev = NULL;
+ dma->complete_cb = NULL;
+ dma->used = 0;
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_free);
+
+void jz4740_dma_enable(struct jz4740_dma_chan *dma)
+{
+ jz4740_dma_write_mask(JZ_REG_DMA_STATUS_CTRL(dma->id),
+ JZ_DMA_STATUS_CTRL_NO_DESC | JZ_DMA_STATUS_CTRL_ENABLE,
+ JZ_DMA_STATUS_CTRL_HALT | JZ_DMA_STATUS_CTRL_NO_DESC |
+ JZ_DMA_STATUS_CTRL_ENABLE);
+
+ jz4740_dma_write_mask(JZ_REG_DMA_CTRL,
+ JZ_DMA_CTRL_ENABLE,
+ JZ_DMA_CTRL_HALT | JZ_DMA_CTRL_ENABLE);
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_enable);
+
+void jz4740_dma_disable(struct jz4740_dma_chan *dma)
+{
+ jz4740_dma_write_mask(JZ_REG_DMA_STATUS_CTRL(dma->id), 0,
+ JZ_DMA_STATUS_CTRL_ENABLE);
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_disable);
+
+uint32_t jz4740_dma_get_residue(const struct jz4740_dma_chan *dma)
+{
+ uint32_t residue;
+ residue = jz4740_dma_read(JZ_REG_DMA_TRANSFER_COUNT(dma->id));
+ return residue << dma->transfer_shift;
+}
+EXPORT_SYMBOL_GPL(jz4740_dma_get_residue);
+
+static void jz4740_dma_chan_irq(struct jz4740_dma_chan *dma)
+{
+ uint32_t status;
+
+ status = jz4740_dma_read(JZ_REG_DMA_STATUS_CTRL(dma->id));
+
+ jz4740_dma_write_mask(JZ_REG_DMA_STATUS_CTRL(dma->id), 0,
+ JZ_DMA_STATUS_CTRL_ENABLE | JZ_DMA_STATUS_CTRL_TRANSFER_DONE);
+
+ if (dma->complete_cb)
+ dma->complete_cb(dma, 0, dma->dev);
+}
+
+static irqreturn_t jz4740_dma_irq(int irq, void *dev_id)
+{
+ uint32_t irq_status;
+ unsigned int i;
+
+ irq_status = readl(jz4740_dma_base + JZ_REG_DMA_IRQ);
+
+ for (i = 0; i < 6; ++i) {
+ if (irq_status & (1 << i))
+ jz4740_dma_chan_irq(&jz4740_dma_channels[i]);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int jz4740_dma_init(void)
+{
+ unsigned int ret;
+
+ jz4740_dma_base = ioremap(JZ4740_DMAC_BASE_ADDR, 0x400);
+
+ if (!jz4740_dma_base)
+ return -EBUSY;
+
+ spin_lock_init(&jz4740_dma_lock);
+
+ ret = request_irq(JZ4740_IRQ_DMAC, jz4740_dma_irq, 0, "DMA", NULL);
+
+ if (ret)
+ printk(KERN_ERR "JZ4740 DMA: Failed to request irq: %d\n", ret);
+
+ return ret;
+}
+arch_initcall(jz4740_dma_init);
--
1.5.6.5

2010-06-19 05:09:44

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 07/26] MIPS: JZ4740: Add setup code

This patch adds plat_mem_setup and get_system_type for JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/jz4740/setup.c | 29 +++++++++++++++++++++++++++++
1 files changed, 29 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/setup.c

diff --git a/arch/mips/jz4740/setup.c b/arch/mips/jz4740/setup.c
new file mode 100644
index 0000000..6a9e14d
--- /dev/null
+++ b/arch/mips/jz4740/setup.c
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 setup code
+ *
+ * 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/init.h>
+#include <linux/kernel.h>
+
+#include "reset.h"
+
+void __init plat_mem_setup(void)
+{
+ jz4740_reset_init();
+}
+
+const char *get_system_type(void)
+{
+ return "JZ4740";
+}
--
1.5.6.5

2010-06-19 05:09:28

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 04/26] MIPS: JZ4740: Add timer support

This patch adds support for the timer/counter unit on a JZ4740 SoC.
This code is used as a common base for the JZ4740 clocksource/clockevent
implementation and PWM support.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/include/asm/mach-jz4740/timer.h | 22 +++++
arch/mips/jz4740/timer.c | 48 ++++++++++
arch/mips/jz4740/timer.h | 136 +++++++++++++++++++++++++++++
3 files changed, 206 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/timer.h
create mode 100644 arch/mips/jz4740/timer.c
create mode 100644 arch/mips/jz4740/timer.h

diff --git a/arch/mips/include/asm/mach-jz4740/timer.h b/arch/mips/include/asm/mach-jz4740/timer.h
new file mode 100644
index 0000000..9baa03c
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/timer.h
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform timer support
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_MACH_JZ4740_TIMER
+#define __ASM_MACH_JZ4740_TIMER
+
+void jz4740_timer_enable_watchdog(void);
+void jz4740_timer_disable_watchdog(void);
+
+#endif
diff --git a/arch/mips/jz4740/timer.c b/arch/mips/jz4740/timer.c
new file mode 100644
index 0000000..b2c0151
--- /dev/null
+++ b/arch/mips/jz4740/timer.c
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform timer support
+ *
+ * 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/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+
+#include "timer.h"
+
+#include <asm/mach-jz4740/base.h>
+
+void __iomem *jz4740_timer_base;
+
+void jz4740_timer_enable_watchdog(void)
+{
+ writel(BIT(16), jz4740_timer_base + JZ_REG_TIMER_STOP_CLEAR);
+}
+
+void jz4740_timer_disable_watchdog(void)
+{
+ writel(BIT(16), jz4740_timer_base + JZ_REG_TIMER_STOP_SET);
+}
+
+void __init jz4740_timer_init(void)
+{
+ jz4740_timer_base = ioremap(JZ4740_TCU_BASE_ADDR, 0x100);
+
+ if (!jz4740_timer_base)
+ panic("Failed to ioremap timer registers");
+
+ /* Disable all timer clocks except for those used as system timers */
+ writel(0x000100fc, jz4740_timer_base + JZ_REG_TIMER_STOP_SET);
+
+ /* Timer irqs are unmasked by default, mask them */
+ writel(0x00ff00ff, jz4740_timer_base + JZ_REG_TIMER_MASK_SET);
+}
diff --git a/arch/mips/jz4740/timer.h b/arch/mips/jz4740/timer.h
new file mode 100644
index 0000000..fca3994
--- /dev/null
+++ b/arch/mips/jz4740/timer.h
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform timer support
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MIPS_JZ4740_TIMER_H__
+#define __MIPS_JZ4740_TIMER_H__
+
+#include <linux/module.h>
+#include <linux/io.h>
+
+#define JZ_REG_TIMER_STOP 0x0C
+#define JZ_REG_TIMER_STOP_SET 0x1C
+#define JZ_REG_TIMER_STOP_CLEAR 0x2C
+#define JZ_REG_TIMER_ENABLE 0x00
+#define JZ_REG_TIMER_ENABLE_SET 0x04
+#define JZ_REG_TIMER_ENABLE_CLEAR 0x08
+#define JZ_REG_TIMER_FLAG 0x10
+#define JZ_REG_TIMER_FLAG_SET 0x14
+#define JZ_REG_TIMER_FLAG_CLEAR 0x18
+#define JZ_REG_TIMER_MASK 0x20
+#define JZ_REG_TIMER_MASK_SET 0x24
+#define JZ_REG_TIMER_MASK_CLEAR 0x28
+
+#define JZ_REG_TIMER_DFR(x) (((x) * 0x10) + 0x30)
+#define JZ_REG_TIMER_DHR(x) (((x) * 0x10) + 0x34)
+#define JZ_REG_TIMER_CNT(x) (((x) * 0x10) + 0x38)
+#define JZ_REG_TIMER_CTRL(x) (((x) * 0x10) + 0x3C)
+
+#define JZ_TIMER_IRQ_HALF(x) BIT((x) + 0x10)
+#define JZ_TIMER_IRQ_FULL(x) BIT(x)
+
+#define JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN BIT(9)
+#define JZ_TIMER_CTRL_PWM_ACTIVE_LOW BIT(8)
+#define JZ_TIMER_CTRL_PWM_ENABLE BIT(7)
+#define JZ_TIMER_CTRL_PRESCALE_MASK 0x1c
+#define JZ_TIMER_CTRL_PRESCALE_OFFSET 0x3
+#define JZ_TIMER_CTRL_PRESCALE_1 (0 << 3)
+#define JZ_TIMER_CTRL_PRESCALE_4 (1 << 3)
+#define JZ_TIMER_CTRL_PRESCALE_16 (2 << 3)
+#define JZ_TIMER_CTRL_PRESCALE_64 (3 << 3)
+#define JZ_TIMER_CTRL_PRESCALE_256 (4 << 3)
+#define JZ_TIMER_CTRL_PRESCALE_1024 (5 << 3)
+
+#define JZ_TIMER_CTRL_PRESCALER(x) ((x) << JZ_TIMER_CTRL_PRESCALE_OFFSET)
+
+#define JZ_TIMER_CTRL_SRC_EXT BIT(2)
+#define JZ_TIMER_CTRL_SRC_RTC BIT(1)
+#define JZ_TIMER_CTRL_SRC_PCLK BIT(0)
+
+extern void __iomem *jz4740_timer_base;
+void __init jz4740_timer_init(void);
+
+static inline void jz4740_timer_stop(unsigned int timer)
+{
+ writel(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_STOP_SET);
+}
+
+static inline void jz4740_timer_start(unsigned int timer)
+{
+ writel(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_STOP_CLEAR);
+}
+
+static inline bool jz4740_timer_is_enabled(unsigned int timer)
+{
+ return readb(jz4740_timer_base + JZ_REG_TIMER_ENABLE) & BIT(timer);
+}
+
+static inline void jz4740_timer_enable(unsigned int timer)
+{
+ writeb(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_ENABLE_SET);
+}
+
+static inline void jz4740_timer_disable(unsigned int timer)
+{
+ writeb(BIT(timer), jz4740_timer_base + JZ_REG_TIMER_ENABLE_CLEAR);
+}
+
+
+static inline void jz4740_timer_set_period(unsigned int timer, uint16_t period)
+{
+ writew(period, jz4740_timer_base + JZ_REG_TIMER_DFR(timer));
+}
+
+static inline void jz4740_timer_set_duty(unsigned int timer, uint16_t duty)
+{
+ writew(duty, jz4740_timer_base + JZ_REG_TIMER_DHR(timer));
+}
+
+static inline void jz4740_timer_set_count(unsigned int timer, uint16_t count)
+{
+ writew(count, jz4740_timer_base + JZ_REG_TIMER_CNT(timer));
+}
+
+static inline uint16_t jz4740_timer_get_count(unsigned int timer)
+{
+ return readw(jz4740_timer_base + JZ_REG_TIMER_CNT(timer));
+}
+
+static inline void jz4740_timer_ack_full(unsigned int timer)
+{
+ writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_FLAG_CLEAR);
+}
+
+static inline void jz4740_timer_irq_full_enable(unsigned int timer)
+{
+ writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_FLAG_CLEAR);
+ writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_MASK_CLEAR);
+}
+
+static inline void jz4740_timer_irq_full_disable(unsigned int timer)
+{
+ writel(JZ_TIMER_IRQ_FULL(timer), jz4740_timer_base + JZ_REG_TIMER_MASK_SET);
+}
+
+static inline void jz4740_timer_set_ctrl(unsigned int timer, uint16_t ctrl)
+{
+ writew(ctrl, jz4740_timer_base + JZ_REG_TIMER_CTRL(timer));
+}
+
+static inline uint16_t jz4740_timer_get_ctrl(unsigned int timer)
+{
+ return readw(jz4740_timer_base + JZ_REG_TIMER_CTRL(timer));
+}
+
+#endif
--
1.5.6.5

2010-06-19 05:09:34

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 05/26] MIPS: JZ4740: Add clocksource/clockevent support.

This patch add clocksource and clockevent support for the timer/counter unit on
JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Do not setup timer IRQ with IRQF_DISABLED, since it is a noop now.
---
arch/mips/jz4740/irq.c | 1 -
arch/mips/jz4740/time.c | 144 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 144 insertions(+), 1 deletions(-)
create mode 100644 arch/mips/jz4740/time.c

diff --git a/arch/mips/jz4740/irq.c b/arch/mips/jz4740/irq.c
index ea19f0e..e259d02 100644
--- a/arch/mips/jz4740/irq.c
+++ b/arch/mips/jz4740/irq.c
@@ -88,7 +88,6 @@ static irqreturn_t jz4740_cascade(int irq, void *data)
static struct irqaction jz4740_cascade_action = {
.handler = jz4740_cascade,
.name = "JZ4740 cascade interrupt",
- .flags = IRQF_DISABLED,
};

void __init arch_init_irq(void)
diff --git a/arch/mips/jz4740/time.c b/arch/mips/jz4740/time.c
new file mode 100644
index 0000000..fe01678
--- /dev/null
+++ b/arch/mips/jz4740/time.c
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform time support
+ *
+ * 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/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/time.h>
+
+#include <linux/clockchips.h>
+
+#include <asm/mach-jz4740/irq.h>
+#include <asm/time.h>
+
+#include "clock.h"
+#include "timer.h"
+
+#define TIMER_CLOCKEVENT 0
+#define TIMER_CLOCKSOURCE 1
+
+static uint16_t jz4740_jiffies_per_tick;
+
+static cycle_t jz4740_clocksource_read(struct clocksource *cs)
+{
+ return jz4740_timer_get_count(TIMER_CLOCKSOURCE);
+}
+
+static struct clocksource jz4740_clocksource = {
+ .name = "jz4740-timer",
+ .rating = 200,
+ .read = jz4740_clocksource_read,
+ .mask = CLOCKSOURCE_MASK(16),
+ .flags = CLOCK_SOURCE_IS_CONTINUOUS,
+};
+
+static irqreturn_t jz4740_clockevent_irq(int irq, void *devid)
+{
+ struct clock_event_device *cd = devid;
+
+ jz4740_timer_ack_full(TIMER_CLOCKEVENT);
+
+ if (cd->mode != CLOCK_EVT_MODE_PERIODIC)
+ jz4740_timer_disable(TIMER_CLOCKEVENT);
+
+ cd->event_handler(cd);
+
+ return IRQ_HANDLED;
+}
+
+static void jz4740_clockevent_set_mode(enum clock_event_mode mode,
+ struct clock_event_device *cd)
+{
+ switch (mode) {
+ case CLOCK_EVT_MODE_PERIODIC:
+ jz4740_timer_set_count(TIMER_CLOCKEVENT, 0);
+ jz4740_timer_set_period(TIMER_CLOCKEVENT, jz4740_jiffies_per_tick);
+ case CLOCK_EVT_MODE_RESUME:
+ jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
+ jz4740_timer_enable(TIMER_CLOCKEVENT);
+ break;
+ case CLOCK_EVT_MODE_ONESHOT:
+ case CLOCK_EVT_MODE_SHUTDOWN:
+ jz4740_timer_disable(TIMER_CLOCKEVENT);
+ break;
+ default:
+ break;
+ }
+}
+
+static int jz4740_clockevent_set_next(unsigned long evt,
+ struct clock_event_device *cd)
+{
+ jz4740_timer_set_count(TIMER_CLOCKEVENT, 0);
+ jz4740_timer_set_period(TIMER_CLOCKEVENT, evt);
+ jz4740_timer_enable(TIMER_CLOCKEVENT);
+
+ return 0;
+}
+
+static struct clock_event_device jz4740_clockevent = {
+ .name = "jz4740-timer",
+ .features = CLOCK_EVT_FEAT_PERIODIC,
+ .set_next_event = jz4740_clockevent_set_next,
+ .set_mode = jz4740_clockevent_set_mode,
+ .rating = 200,
+ .irq = JZ4740_IRQ_TCU0,
+};
+
+static struct irqaction timer_irqaction = {
+ .handler = jz4740_clockevent_irq,
+ .flags = IRQF_PERCPU | IRQF_TIMER,
+ .name = "jz4740-timerirq",
+ .dev_id = &jz4740_clockevent,
+};
+
+void __init plat_time_init(void)
+{
+ int ret;
+ uint32_t clk_rate;
+ uint16_t ctrl;
+
+ jz4740_timer_init();
+
+ clk_rate = jz4740_clock_bdata.ext_rate >> 4;
+ jz4740_jiffies_per_tick = DIV_ROUND_CLOSEST(clk_rate, HZ);
+
+ clockevent_set_clock(&jz4740_clockevent, clk_rate);
+ jz4740_clockevent.min_delta_ns = clockevent_delta2ns(100, &jz4740_clockevent);
+ jz4740_clockevent.max_delta_ns = clockevent_delta2ns(0xffff, &jz4740_clockevent);
+ jz4740_clockevent.cpumask = cpumask_of(0);
+
+ clockevents_register_device(&jz4740_clockevent);
+
+ clocksource_set_clock(&jz4740_clocksource, clk_rate);
+ ret = clocksource_register(&jz4740_clocksource);
+
+ if (ret)
+ printk(KERN_ERR "Failed to register clocksource: %d\n", ret);
+
+ setup_irq(JZ4740_IRQ_TCU0, &timer_irqaction);
+
+ ctrl = JZ_TIMER_CTRL_PRESCALE_16 | JZ_TIMER_CTRL_SRC_EXT;
+
+ jz4740_timer_set_ctrl(TIMER_CLOCKEVENT, ctrl);
+ jz4740_timer_set_ctrl(TIMER_CLOCKSOURCE, ctrl);
+
+ jz4740_timer_set_period(TIMER_CLOCKEVENT, jz4740_jiffies_per_tick);
+ jz4740_timer_irq_full_enable(TIMER_CLOCKEVENT);
+
+ jz4740_timer_set_period(TIMER_CLOCKSOURCE, 0xffff);
+
+ jz4740_timer_enable(TIMER_CLOCKEVENT);
+ jz4740_timer_enable(TIMER_CLOCKSOURCE);
+}
--
1.5.6.5

2010-06-19 05:09:39

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 06/26] MIPS: JZ4740: Add power-management and system reset support

This patch adds support for suspend/resume and poweroff/reboot on a JZ4740 SoC.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/jz4740/pm.c | 56 ++++++++++++++++++++++++++++++++
arch/mips/jz4740/reset.c | 79 ++++++++++++++++++++++++++++++++++++++++++++++
arch/mips/jz4740/reset.h | 7 ++++
3 files changed, 142 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/pm.c
create mode 100644 arch/mips/jz4740/reset.c
create mode 100644 arch/mips/jz4740/reset.h

diff --git a/arch/mips/jz4740/pm.c b/arch/mips/jz4740/pm.c
new file mode 100644
index 0000000..a999458
--- /dev/null
+++ b/arch/mips/jz4740/pm.c
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC power management support
+ *
+ * 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/init.h>
+#include <linux/pm.h>
+#include <linux/delay.h>
+#include <linux/suspend.h>
+
+#include <asm/mach-jz4740/clock.h>
+
+#include "clock.h"
+#include "irq.h"
+
+static int jz4740_pm_enter(suspend_state_t state)
+{
+ jz4740_intc_suspend();
+ jz4740_clock_suspend();
+
+ jz4740_clock_set_wait_mode(JZ4740_WAIT_MODE_SLEEP);
+
+ __asm__(".set\tmips3\n\t"
+ "wait\n\t"
+ ".set\tmips0");
+
+ jz4740_clock_set_wait_mode(JZ4740_WAIT_MODE_IDLE);
+
+ jz4740_clock_resume();
+ jz4740_intc_resume();
+
+ return 0;
+}
+
+static struct platform_suspend_ops jz4740_pm_ops = {
+ .valid = suspend_valid_only_mem,
+ .enter = jz4740_pm_enter,
+};
+
+static int __init jz4740_pm_init(void)
+{
+ suspend_set_ops(&jz4740_pm_ops);
+ return 0;
+
+}
+late_initcall(jz4740_pm_init);
diff --git a/arch/mips/jz4740/reset.c b/arch/mips/jz4740/reset.c
new file mode 100644
index 0000000..5f1fb95
--- /dev/null
+++ b/arch/mips/jz4740/reset.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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/io.h>
+#include <linux/kernel.h>
+#include <linux/pm.h>
+
+#include <asm/reboot.h>
+
+#include <asm/mach-jz4740/base.h>
+#include <asm/mach-jz4740/timer.h>
+
+static void jz4740_halt(void)
+{
+ while (1) {
+ __asm__(".set push;\n"
+ ".set mips3;\n"
+ "wait;\n"
+ ".set pop;\n"
+ );
+ }
+}
+
+#define JZ_REG_WDT_DATA 0x00
+#define JZ_REG_WDT_COUNTER_ENABLE 0x04
+#define JZ_REG_WDT_COUNTER 0x08
+#define JZ_REG_WDT_CTRL 0x0c
+
+static void jz4740_restart(char *command)
+{
+ void __iomem *wdt_base = ioremap(JZ4740_WDT_BASE_ADDR, 0x0f);
+
+ jz4740_timer_enable_watchdog();
+
+ writeb(0, wdt_base + JZ_REG_WDT_COUNTER_ENABLE);
+
+ writew(0, wdt_base + JZ_REG_WDT_COUNTER);
+ writew(0, wdt_base + JZ_REG_WDT_DATA);
+ writew(BIT(2), wdt_base + JZ_REG_WDT_CTRL);
+
+ writeb(1, wdt_base + JZ_REG_WDT_COUNTER_ENABLE);
+ jz4740_halt();
+}
+
+#define JZ_REG_RTC_CTRL 0x00
+#define JZ_REG_RTC_HIBERNATE 0x20
+
+#define JZ_RTC_CTRL_WRDY BIT(7)
+
+static void jz4740_power_off(void)
+{
+ void __iomem *rtc_base = ioremap(JZ4740_RTC_BASE_ADDR, 0x24);
+ uint32_t ctrl;
+
+ do {
+ ctrl = readl(rtc_base + JZ_REG_RTC_CTRL);
+ } while (!(ctrl & JZ_RTC_CTRL_WRDY));
+
+ writel(1, rtc_base + JZ_REG_RTC_HIBERNATE);
+ jz4740_halt();
+}
+
+void jz4740_reset_init(void)
+{
+ _machine_restart = jz4740_restart;
+ _machine_halt = jz4740_halt;
+ pm_power_off = jz4740_power_off;
+}
diff --git a/arch/mips/jz4740/reset.h b/arch/mips/jz4740/reset.h
new file mode 100644
index 0000000..c57a829
--- /dev/null
+++ b/arch/mips/jz4740/reset.h
@@ -0,0 +1,7 @@
+#ifndef __MIPS_JZ4740_RESET_H__
+#define __MIPS_JZ4740_RESET_H__
+
+extern void jz4740_reset_init(void);
+
+#endif
+
--
1.5.6.5

2010-06-19 05:09:52

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 08/26] MIPS: JZ4740: Add gpio support

This patch adds gpiolib support for JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Fix possible race in GPIO-IRQ setup code
---
arch/mips/include/asm/mach-jz4740/gpio.h | 398 ++++++++++++++++++++
arch/mips/jz4740/gpio.c | 601 ++++++++++++++++++++++++++++++
2 files changed, 999 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h
create mode 100644 arch/mips/jz4740/gpio.c

diff --git a/arch/mips/include/asm/mach-jz4740/gpio.h b/arch/mips/include/asm/mach-jz4740/gpio.h
new file mode 100644
index 0000000..3ff3001
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/gpio.h
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ * JZ4740 GPIO pin definitions
+ *
+ * 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.
+ *
+ */
+
+#ifndef _JZ_GPIO_H
+#define _JZ_GPIO_H
+
+#include <linux/types.h>
+
+enum jz_gpio_function {
+ JZ_GPIO_FUNC_NONE,
+ JZ_GPIO_FUNC1,
+ JZ_GPIO_FUNC2,
+ JZ_GPIO_FUNC3,
+};
+
+
+/*
+ Usually a driver for a SoC component has to request several gpio pins and
+ configure them as funcion pins.
+ jz_gpio_bulk_request can be used to ease this process.
+ Usually one would do something like:
+
+ const static struct jz_gpio_bulk_request i2c_pins[] = {
+ JZ_GPIO_BULK_PIN(I2C_SDA),
+ JZ_GPIO_BULK_PIN(I2C_SCK),
+ };
+
+ inside the probe function:
+
+ ret = jz_gpio_bulk_request(i2c_pins, ARRAY_SIZE(i2c_pins));
+ if (ret) {
+ ...
+
+ inside the remove function:
+
+ jz_gpio_bulk_free(i2c_pins, ARRAY_SIZE(i2c_pins));
+
+
+*/
+struct jz_gpio_bulk_request {
+ int gpio;
+ const char *name;
+ enum jz_gpio_function function;
+};
+
+#define JZ_GPIO_BULK_PIN(pin) { \
+ .gpio = JZ_GPIO_ ## pin, \
+ .name = #pin, \
+ .function = JZ_GPIO_FUNC_ ## pin \
+}
+
+int jz_gpio_bulk_request(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_bulk_free(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_bulk_suspend(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_bulk_resume(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_enable_pullup(unsigned gpio);
+void jz_gpio_disable_pullup(unsigned gpio);
+int jz_gpio_set_function(int gpio, enum jz_gpio_function function);
+
+int jz_gpio_port_direction_input(int port, uint32_t mask);
+int jz_gpio_port_direction_output(int port, uint32_t mask);
+void jz_gpio_port_set_value(int port, uint32_t value, uint32_t mask);
+uint32_t jz_gpio_port_get_value(int port, uint32_t mask);
+
+#include <asm/mach-generic/gpio.h>
+
+#define JZ_GPIO_PORTA(x) ((x) + 32 * 0)
+#define JZ_GPIO_PORTB(x) ((x) + 32 * 1)
+#define JZ_GPIO_PORTC(x) ((x) + 32 * 2)
+#define JZ_GPIO_PORTD(x) ((x) + 32 * 3)
+
+/* Port A function pins */
+#define JZ_GPIO_MEM_DATA0 JZ_GPIO_PORTA(0)
+#define JZ_GPIO_MEM_DATA1 JZ_GPIO_PORTA(1)
+#define JZ_GPIO_MEM_DATA2 JZ_GPIO_PORTA(2)
+#define JZ_GPIO_MEM_DATA3 JZ_GPIO_PORTA(3)
+#define JZ_GPIO_MEM_DATA4 JZ_GPIO_PORTA(4)
+#define JZ_GPIO_MEM_DATA5 JZ_GPIO_PORTA(5)
+#define JZ_GPIO_MEM_DATA6 JZ_GPIO_PORTA(6)
+#define JZ_GPIO_MEM_DATA7 JZ_GPIO_PORTA(7)
+#define JZ_GPIO_MEM_DATA8 JZ_GPIO_PORTA(8)
+#define JZ_GPIO_MEM_DATA9 JZ_GPIO_PORTA(9)
+#define JZ_GPIO_MEM_DATA10 JZ_GPIO_PORTA(10)
+#define JZ_GPIO_MEM_DATA11 JZ_GPIO_PORTA(11)
+#define JZ_GPIO_MEM_DATA12 JZ_GPIO_PORTA(12)
+#define JZ_GPIO_MEM_DATA13 JZ_GPIO_PORTA(13)
+#define JZ_GPIO_MEM_DATA14 JZ_GPIO_PORTA(14)
+#define JZ_GPIO_MEM_DATA15 JZ_GPIO_PORTA(15)
+#define JZ_GPIO_MEM_DATA16 JZ_GPIO_PORTA(16)
+#define JZ_GPIO_MEM_DATA17 JZ_GPIO_PORTA(17)
+#define JZ_GPIO_MEM_DATA18 JZ_GPIO_PORTA(18)
+#define JZ_GPIO_MEM_DATA19 JZ_GPIO_PORTA(19)
+#define JZ_GPIO_MEM_DATA20 JZ_GPIO_PORTA(20)
+#define JZ_GPIO_MEM_DATA21 JZ_GPIO_PORTA(21)
+#define JZ_GPIO_MEM_DATA22 JZ_GPIO_PORTA(22)
+#define JZ_GPIO_MEM_DATA23 JZ_GPIO_PORTA(23)
+#define JZ_GPIO_MEM_DATA24 JZ_GPIO_PORTA(24)
+#define JZ_GPIO_MEM_DATA25 JZ_GPIO_PORTA(25)
+#define JZ_GPIO_MEM_DATA26 JZ_GPIO_PORTA(26)
+#define JZ_GPIO_MEM_DATA27 JZ_GPIO_PORTA(27)
+#define JZ_GPIO_MEM_DATA28 JZ_GPIO_PORTA(28)
+#define JZ_GPIO_MEM_DATA29 JZ_GPIO_PORTA(29)
+#define JZ_GPIO_MEM_DATA30 JZ_GPIO_PORTA(30)
+#define JZ_GPIO_MEM_DATA31 JZ_GPIO_PORTA(31)
+
+#define JZ_GPIO_FUNC_MEM_DATA0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA4 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA5 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA6 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA7 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA8 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA9 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA10 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA11 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA12 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA13 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA14 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA15 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA16 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA17 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA18 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA19 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA20 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA21 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA22 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA23 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA24 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA25 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA26 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA27 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA28 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA29 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA30 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA31 JZ_GPIO_FUNC1
+
+/* Port B function pins */
+#define JZ_GPIO_MEM_ADDR0 JZ_GPIO_PORTB(0)
+#define JZ_GPIO_MEM_ADDR1 JZ_GPIO_PORTB(1)
+#define JZ_GPIO_MEM_ADDR2 JZ_GPIO_PORTB(2)
+#define JZ_GPIO_MEM_ADDR3 JZ_GPIO_PORTB(3)
+#define JZ_GPIO_MEM_ADDR4 JZ_GPIO_PORTB(4)
+#define JZ_GPIO_MEM_ADDR5 JZ_GPIO_PORTB(5)
+#define JZ_GPIO_MEM_ADDR6 JZ_GPIO_PORTB(6)
+#define JZ_GPIO_MEM_ADDR7 JZ_GPIO_PORTB(7)
+#define JZ_GPIO_MEM_ADDR8 JZ_GPIO_PORTB(8)
+#define JZ_GPIO_MEM_ADDR9 JZ_GPIO_PORTB(9)
+#define JZ_GPIO_MEM_ADDR10 JZ_GPIO_PORTB(10)
+#define JZ_GPIO_MEM_ADDR11 JZ_GPIO_PORTB(11)
+#define JZ_GPIO_MEM_ADDR12 JZ_GPIO_PORTB(12)
+#define JZ_GPIO_MEM_ADDR13 JZ_GPIO_PORTB(13)
+#define JZ_GPIO_MEM_ADDR14 JZ_GPIO_PORTB(14)
+#define JZ_GPIO_MEM_ADDR15 JZ_GPIO_PORTB(15)
+#define JZ_GPIO_MEM_ADDR16 JZ_GPIO_PORTB(16)
+#define JZ_GPIO_MEM_CLS JZ_GPIO_PORTB(17)
+#define JZ_GPIO_MEM_SPL JZ_GPIO_PORTB(18)
+#define JZ_GPIO_MEM_DCS JZ_GPIO_PORTB(19)
+#define JZ_GPIO_MEM_RAS JZ_GPIO_PORTB(20)
+#define JZ_GPIO_MEM_CAS JZ_GPIO_PORTB(21)
+#define JZ_GPIO_MEM_SDWE JZ_GPIO_PORTB(22)
+#define JZ_GPIO_MEM_CKE JZ_GPIO_PORTB(23)
+#define JZ_GPIO_MEM_CKO JZ_GPIO_PORTB(24)
+#define JZ_GPIO_MEM_CS0 JZ_GPIO_PORTB(25)
+#define JZ_GPIO_MEM_CS1 JZ_GPIO_PORTB(26)
+#define JZ_GPIO_MEM_CS2 JZ_GPIO_PORTB(27)
+#define JZ_GPIO_MEM_CS3 JZ_GPIO_PORTB(28)
+#define JZ_GPIO_MEM_RD JZ_GPIO_PORTB(29)
+#define JZ_GPIO_MEM_WR JZ_GPIO_PORTB(30)
+#define JZ_GPIO_MEM_WE0 JZ_GPIO_PORTB(31)
+
+#define JZ_GPIO_FUNC_MEM_ADDR0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR4 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR5 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR6 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR7 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR8 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR9 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR10 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR11 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR12 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR13 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR14 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR15 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR16 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CLS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_SPL JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DCS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_RAS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CAS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_SDWE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CKE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CKO JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_RD JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WR JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE0 JZ_GPIO_FUNC1
+
+
+#define JZ_GPIO_MEM_ADDR21 JZ_GPIO_PORTB(17)
+#define JZ_GPIO_MEM_ADDR22 JZ_GPIO_PORTB(18)
+
+#define JZ_GPIO_FUNC_MEM_ADDR21 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR22 JZ_GPIO_FUNC2
+
+/* Port C function pins */
+#define JZ_GPIO_LCD_DATA0 JZ_GPIO_PORTC(0)
+#define JZ_GPIO_LCD_DATA1 JZ_GPIO_PORTC(1)
+#define JZ_GPIO_LCD_DATA2 JZ_GPIO_PORTC(2)
+#define JZ_GPIO_LCD_DATA3 JZ_GPIO_PORTC(3)
+#define JZ_GPIO_LCD_DATA4 JZ_GPIO_PORTC(4)
+#define JZ_GPIO_LCD_DATA5 JZ_GPIO_PORTC(5)
+#define JZ_GPIO_LCD_DATA6 JZ_GPIO_PORTC(6)
+#define JZ_GPIO_LCD_DATA7 JZ_GPIO_PORTC(7)
+#define JZ_GPIO_LCD_DATA8 JZ_GPIO_PORTC(8)
+#define JZ_GPIO_LCD_DATA9 JZ_GPIO_PORTC(9)
+#define JZ_GPIO_LCD_DATA10 JZ_GPIO_PORTC(10)
+#define JZ_GPIO_LCD_DATA11 JZ_GPIO_PORTC(11)
+#define JZ_GPIO_LCD_DATA12 JZ_GPIO_PORTC(12)
+#define JZ_GPIO_LCD_DATA13 JZ_GPIO_PORTC(13)
+#define JZ_GPIO_LCD_DATA14 JZ_GPIO_PORTC(14)
+#define JZ_GPIO_LCD_DATA15 JZ_GPIO_PORTC(15)
+#define JZ_GPIO_LCD_DATA16 JZ_GPIO_PORTC(16)
+#define JZ_GPIO_LCD_DATA17 JZ_GPIO_PORTC(17)
+#define JZ_GPIO_LCD_PCLK JZ_GPIO_PORTC(18)
+#define JZ_GPIO_LCD_HSYNC JZ_GPIO_PORTC(19)
+#define JZ_GPIO_LCD_VSYNC JZ_GPIO_PORTC(20)
+#define JZ_GPIO_LCD_DE JZ_GPIO_PORTC(21)
+#define JZ_GPIO_LCD_PS JZ_GPIO_PORTC(22)
+#define JZ_GPIO_LCD_REV JZ_GPIO_PORTC(23)
+#define JZ_GPIO_MEM_WE1 JZ_GPIO_PORTC(24)
+#define JZ_GPIO_MEM_WE2 JZ_GPIO_PORTC(25)
+#define JZ_GPIO_MEM_WE3 JZ_GPIO_PORTC(26)
+#define JZ_GPIO_MEM_WAIT JZ_GPIO_PORTC(27)
+#define JZ_GPIO_MEM_FRE JZ_GPIO_PORTC(28)
+#define JZ_GPIO_MEM_FWE JZ_GPIO_PORTC(29)
+
+#define JZ_GPIO_FUNC_LCD_DATA0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA4 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA5 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA6 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA7 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA8 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA9 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA10 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA11 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA12 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA13 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA14 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA15 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA16 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA17 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_PCLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_VSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_HSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_PS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_REV JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WAIT JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_FRE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_FWE JZ_GPIO_FUNC1
+
+
+#define JZ_GPIO_MEM_ADDR19 JZ_GPIO_PORTB(22)
+#define JZ_GPIO_MEM_ADDR20 JZ_GPIO_PORTB(23)
+
+#define JZ_GPIO_FUNC_MEM_ADDR19 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR20 JZ_GPIO_FUNC2
+
+/* Port D function pins */
+#define JZ_GPIO_CIM_DATA0 JZ_GPIO_PORTD(0)
+#define JZ_GPIO_CIM_DATA1 JZ_GPIO_PORTD(1)
+#define JZ_GPIO_CIM_DATA2 JZ_GPIO_PORTD(2)
+#define JZ_GPIO_CIM_DATA3 JZ_GPIO_PORTD(3)
+#define JZ_GPIO_CIM_DATA4 JZ_GPIO_PORTD(4)
+#define JZ_GPIO_CIM_DATA5 JZ_GPIO_PORTD(5)
+#define JZ_GPIO_CIM_DATA6 JZ_GPIO_PORTD(6)
+#define JZ_GPIO_CIM_DATA7 JZ_GPIO_PORTD(7)
+#define JZ_GPIO_MSC_CMD JZ_GPIO_PORTD(8)
+#define JZ_GPIO_MSC_CLK JZ_GPIO_PORTD(9)
+#define JZ_GPIO_MSC_DATA0 JZ_GPIO_PORTD(10)
+#define JZ_GPIO_MSC_DATA1 JZ_GPIO_PORTD(11)
+#define JZ_GPIO_MSC_DATA2 JZ_GPIO_PORTD(12)
+#define JZ_GPIO_MSC_DATA3 JZ_GPIO_PORTD(13)
+#define JZ_GPIO_CIM_MCLK JZ_GPIO_PORTD(14)
+#define JZ_GPIO_CIM_PCLK JZ_GPIO_PORTD(15)
+#define JZ_GPIO_CIM_VSYNC JZ_GPIO_PORTD(16)
+#define JZ_GPIO_CIM_HSYNC JZ_GPIO_PORTD(17)
+#define JZ_GPIO_SPI_CLK JZ_GPIO_PORTD(18)
+#define JZ_GPIO_SPI_CE0 JZ_GPIO_PORTD(19)
+#define JZ_GPIO_SPI_DT JZ_GPIO_PORTD(20)
+#define JZ_GPIO_SPI_DR JZ_GPIO_PORTD(21)
+#define JZ_GPIO_SPI_CE1 JZ_GPIO_PORTD(22)
+#define JZ_GPIO_PWM0 JZ_GPIO_PORTD(23)
+#define JZ_GPIO_PWM1 JZ_GPIO_PORTD(24)
+#define JZ_GPIO_PWM2 JZ_GPIO_PORTD(25)
+#define JZ_GPIO_PWM3 JZ_GPIO_PORTD(26)
+#define JZ_GPIO_PWM4 JZ_GPIO_PORTD(27)
+#define JZ_GPIO_PWM5 JZ_GPIO_PORTD(28)
+#define JZ_GPIO_PWM6 JZ_GPIO_PORTD(30)
+#define JZ_GPIO_PWM7 JZ_GPIO_PORTD(31)
+
+#define JZ_GPIO_FUNC_CIM_DATA JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_DATA0 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA1 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA2 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA3 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA4 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA5 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA6 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA7 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_MSC_CMD JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MSC_CLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MSC_DATA JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MSC_DATA0 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_MSC_DATA1 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_MSC_DATA2 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_MSC_DATA3 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_CIM_MCLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_PCLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_VSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_HSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_CLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_CE0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_DT JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_DR JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_CE1 JZ_GPIO_FUNC1
+
+#define JZ_GPIO_FUNC_PWM JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_PWM0 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM1 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM2 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM3 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM4 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM5 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM6 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM7 JZ_GPIO_FUNC_PWM
+
+#define JZ_GPIO_MEM_SCLK_RSTN JZ_GPIO_PORTD(18)
+#define JZ_GPIO_MEM_BCLK JZ_GPIO_PORTD(19)
+#define JZ_GPIO_MEM_SDATO JZ_GPIO_PORTD(20)
+#define JZ_GPIO_MEM_SDATI JZ_GPIO_PORTD(21)
+#define JZ_GPIO_MEM_SYNC JZ_GPIO_PORTD(22)
+#define JZ_GPIO_I2C_SDA JZ_GPIO_PORTD(23)
+#define JZ_GPIO_I2C_SCK JZ_GPIO_PORTD(24)
+#define JZ_GPIO_UART0_TXD JZ_GPIO_PORTD(25)
+#define JZ_GPIO_UART0_RXD JZ_GPIO_PORTD(26)
+#define JZ_GPIO_MEM_ADDR17 JZ_GPIO_PORTD(27)
+#define JZ_GPIO_MEM_ADDR18 JZ_GPIO_PORTD(28)
+#define JZ_GPIO_UART0_CTS JZ_GPIO_PORTD(30)
+#define JZ_GPIO_UART0_RTS JZ_GPIO_PORTD(31)
+
+#define JZ_GPIO_FUNC_MEM_SCLK_RSTN JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_BCLK JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_SDATO JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_SDATI JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_SYNC JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_I2C_SDA JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_I2C_SCK JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_TXD JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_RXD JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR17 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR18 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_CTS JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_RTS JZ_GPIO_FUNC2
+
+#define JZ_GPIO_UART1_RXD JZ_GPIO_PORTD(30)
+#define JZ_GPIO_UART1_TXD JZ_GPIO_PORTD(31)
+
+#define JZ_GPIO_FUNC_UART1_RXD JZ_GPIO_FUNC3
+#define JZ_GPIO_FUNC_UART1_TXD JZ_GPIO_FUNC3
+
+#endif
diff --git a/arch/mips/jz4740/gpio.c b/arch/mips/jz4740/gpio.c
new file mode 100644
index 0000000..3a6cce8
--- /dev/null
+++ b/arch/mips/jz4740/gpio.c
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform GPIO support
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/spinlock.h>
+#include <linux/sysdev.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <asm/mach-jz4740/base.h>
+
+#define JZ4740_GPIO_BASE_A (32*0)
+#define JZ4740_GPIO_BASE_B (32*1)
+#define JZ4740_GPIO_BASE_C (32*2)
+#define JZ4740_GPIO_BASE_D (32*3)
+
+#define JZ4740_GPIO_NUM_A 32
+#define JZ4740_GPIO_NUM_B 32
+#define JZ4740_GPIO_NUM_C 31
+#define JZ4740_GPIO_NUM_D 32
+
+#define JZ4740_IRQ_GPIO_BASE_A (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_A)
+#define JZ4740_IRQ_GPIO_BASE_B (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_B)
+#define JZ4740_IRQ_GPIO_BASE_C (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_C)
+#define JZ4740_IRQ_GPIO_BASE_D (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_D)
+
+#define JZ_REG_GPIO_PIN 0x00
+#define JZ_REG_GPIO_DATA 0x10
+#define JZ_REG_GPIO_DATA_SET 0x14
+#define JZ_REG_GPIO_DATA_CLEAR 0x18
+#define JZ_REG_GPIO_MASK 0x20
+#define JZ_REG_GPIO_MASK_SET 0x24
+#define JZ_REG_GPIO_MASK_CLEAR 0x28
+#define JZ_REG_GPIO_PULL 0x30
+#define JZ_REG_GPIO_PULL_SET 0x34
+#define JZ_REG_GPIO_PULL_CLEAR 0x38
+#define JZ_REG_GPIO_FUNC 0x40
+#define JZ_REG_GPIO_FUNC_SET 0x44
+#define JZ_REG_GPIO_FUNC_CLEAR 0x48
+#define JZ_REG_GPIO_SELECT 0x50
+#define JZ_REG_GPIO_SELECT_SET 0x54
+#define JZ_REG_GPIO_SELECT_CLEAR 0x58
+#define JZ_REG_GPIO_DIRECTION 0x60
+#define JZ_REG_GPIO_DIRECTION_SET 0x64
+#define JZ_REG_GPIO_DIRECTION_CLEAR 0x68
+#define JZ_REG_GPIO_TRIGGER 0x70
+#define JZ_REG_GPIO_TRIGGER_SET 0x74
+#define JZ_REG_GPIO_TRIGGER_CLEAR 0x78
+#define JZ_REG_GPIO_FLAG 0x80
+#define JZ_REG_GPIO_FLAG_CLEAR 0x14
+
+#define GPIO_TO_BIT(gpio) BIT(gpio & 0x1f)
+#define GPIO_TO_REG(gpio, reg) (gpio_to_jz_gpio_chip(gpio)->base + (reg))
+#define CHIP_TO_REG(chip, reg) (gpio_chip_to_jz_gpio_chip(chip)->base + (reg))
+
+struct jz_gpio_chip {
+ unsigned int irq;
+ unsigned int irq_base;
+ uint32_t wakeup;
+ uint32_t suspend_mask;
+ uint32_t edge_trigger_both;
+
+ void __iomem *base;
+
+ spinlock_t lock;
+
+ struct gpio_chip gpio_chip;
+ struct irq_chip irq_chip;
+ struct sys_device sysdev;
+};
+
+static struct jz_gpio_chip jz4740_gpio_chips[];
+
+static inline struct jz_gpio_chip *gpio_to_jz_gpio_chip(unsigned int gpio)
+{
+ return &jz4740_gpio_chips[gpio >> 5];
+}
+
+static inline struct jz_gpio_chip *gpio_chip_to_jz_gpio_chip(struct gpio_chip *gpio_chip)
+{
+ return container_of(gpio_chip, struct jz_gpio_chip, gpio_chip);
+}
+
+static inline struct jz_gpio_chip *irq_to_jz_gpio_chip(unsigned int irq)
+{
+ return get_irq_chip_data(irq);
+}
+
+static inline void jz_gpio_write_bit(unsigned int gpio, unsigned int reg)
+{
+ writel(GPIO_TO_BIT(gpio), GPIO_TO_REG(gpio, reg));
+}
+
+int jz_gpio_set_function(int gpio, enum jz_gpio_function function)
+{
+ if (function == JZ_GPIO_FUNC_NONE) {
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_FUNC_CLEAR);
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_SELECT_CLEAR);
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_TRIGGER_CLEAR);
+ } else {
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_FUNC_SET);
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_TRIGGER_CLEAR);
+ switch (function) {
+ case JZ_GPIO_FUNC1:
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_SELECT_CLEAR);
+ break;
+ case JZ_GPIO_FUNC3:
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_TRIGGER_SET);
+ case JZ_GPIO_FUNC2: /* Falltrough */
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_SELECT_SET);
+ break;
+ default:
+ BUG();
+ break;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(jz_gpio_set_function);
+
+int jz_gpio_bulk_request(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < num; ++i, ++request) {
+ ret = gpio_request(request->gpio, request->name);
+ if (ret)
+ goto err;
+ jz_gpio_set_function(request->gpio, request->function);
+ }
+
+ return 0;
+
+err:
+ for (--request; i > 0; --i, --request) {
+ gpio_free(request->gpio);
+ jz_gpio_set_function(request->gpio, JZ_GPIO_FUNC_NONE);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_request);
+
+void jz_gpio_bulk_free(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; ++i, ++request) {
+ gpio_free(request->gpio);
+ jz_gpio_set_function(request->gpio, JZ_GPIO_FUNC_NONE);
+ }
+
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_free);
+
+void jz_gpio_bulk_suspend(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; ++i, ++request) {
+ jz_gpio_set_function(request->gpio, JZ_GPIO_FUNC_NONE);
+ jz_gpio_write_bit(request->gpio, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_write_bit(request->gpio, JZ_REG_GPIO_PULL_SET);
+ }
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_suspend);
+
+void jz_gpio_bulk_resume(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; ++i, ++request)
+ jz_gpio_set_function(request->gpio, request->function);
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_resume);
+
+void jz_gpio_enable_pullup(unsigned gpio)
+{
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_PULL_CLEAR);
+}
+EXPORT_SYMBOL_GPL(jz_gpio_enable_pullup);
+
+void jz_gpio_disable_pullup(unsigned gpio)
+{
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_PULL_SET);
+}
+EXPORT_SYMBOL_GPL(jz_gpio_disable_pullup);
+
+static int jz_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+ return !!(readl(CHIP_TO_REG(chip, JZ_REG_GPIO_PIN)) & BIT(gpio));
+}
+
+static void jz_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value)
+{
+ uint32_t __iomem *reg = CHIP_TO_REG(chip, JZ_REG_GPIO_DATA_SET);
+ reg += !value;
+ writel(BIT(gpio), reg);
+}
+
+static int jz_gpio_direction_output(struct gpio_chip *chip, unsigned gpio,
+ int value)
+{
+ writel(BIT(gpio), CHIP_TO_REG(chip, JZ_REG_GPIO_DIRECTION_SET));
+ jz_gpio_set_value(chip, gpio, value);
+
+ return 0;
+}
+
+static int jz_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+ writel(BIT(gpio), CHIP_TO_REG(chip, JZ_REG_GPIO_DIRECTION_CLEAR));
+
+ return 0;
+}
+
+int jz_gpio_port_direction_input(int port, uint32_t mask)
+{
+ writel(mask, GPIO_TO_REG(port, JZ_REG_GPIO_DIRECTION_CLEAR));
+
+ return 0;
+}
+EXPORT_SYMBOL(jz_gpio_port_direction_input);
+
+int jz_gpio_port_direction_output(int port, uint32_t mask)
+{
+ writel(mask, GPIO_TO_REG(port, JZ_REG_GPIO_DIRECTION_SET));
+
+ return 0;
+}
+EXPORT_SYMBOL(jz_gpio_port_direction_output);
+
+void jz_gpio_port_set_value(int port, uint32_t value, uint32_t mask)
+{
+ writel(~value & mask, GPIO_TO_REG(port, JZ_REG_GPIO_DATA_CLEAR));
+ writel(value & mask, GPIO_TO_REG(port, JZ_REG_GPIO_DATA_SET));
+}
+EXPORT_SYMBOL(jz_gpio_port_set_value);
+
+uint32_t jz_gpio_port_get_value(int port, uint32_t mask)
+{
+ uint32_t value = readl(GPIO_TO_REG(port, JZ_REG_GPIO_PIN));
+
+ return value & mask;
+}
+EXPORT_SYMBOL(jz_gpio_port_get_value);
+
+int gpio_to_irq(unsigned gpio)
+{
+ return JZ4740_IRQ_GPIO(0) + gpio;
+}
+EXPORT_SYMBOL_GPL(gpio_to_irq);
+
+int irq_to_gpio(unsigned irq)
+{
+ return irq - JZ4740_IRQ_GPIO(0);
+}
+EXPORT_SYMBOL_GPL(irq_to_gpio);
+
+#define IRQ_TO_BIT(irq) BIT(irq_to_gpio(irq) & 0x1f)
+
+static void jz_gpio_check_trigger_both(struct jz_gpio_chip *chip, unsigned int irq)
+{
+ uint32_t value;
+ void __iomem *reg;
+ uint32_t mask = IRQ_TO_BIT(irq);
+
+ if (!(chip->edge_trigger_both & mask))
+ return;
+
+ reg = chip->base;
+
+ value = readl(chip->base + JZ_REG_GPIO_PIN);
+ if (value & mask)
+ reg += JZ_REG_GPIO_DIRECTION_CLEAR;
+ else
+ reg += JZ_REG_GPIO_DIRECTION_SET;
+
+ writel(mask, reg);
+}
+
+static void jz_gpio_irq_demux_handler(unsigned int irq, struct irq_desc *desc)
+{
+ uint32_t flag;
+ unsigned int gpio_irq;
+ unsigned int gpio_bank;
+ struct jz_gpio_chip *chip = get_irq_desc_data(desc);
+
+ gpio_bank = JZ4740_IRQ_GPIO0 - irq;
+
+ flag = readl(chip->base + JZ_REG_GPIO_FLAG);
+
+ gpio_irq = ffs(flag) - 1;
+
+ jz_gpio_check_trigger_both(chip, irq);
+
+ gpio_irq += (gpio_bank << 5) + JZ4740_IRQ_GPIO(0);
+
+ generic_handle_irq(gpio_irq);
+};
+
+static inline void jz_gpio_set_irq_bit(unsigned int irq, unsigned int reg)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+ writel(IRQ_TO_BIT(irq), chip->base + reg);
+}
+
+static void jz_gpio_irq_mask(unsigned int irq)
+{
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_MASK_SET);
+};
+
+static void jz_gpio_irq_unmask(unsigned int irq)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+
+ jz_gpio_check_trigger_both(chip, irq);
+
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_MASK_CLEAR);
+};
+
+/* TODO: Check if function is gpio */
+static unsigned int jz_gpio_irq_startup(unsigned int irq)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_SELECT_SET);
+
+ desc->status &= ~IRQ_MASKED;
+ jz_gpio_irq_unmask(irq);
+
+ return 0;
+}
+
+static void jz_gpio_irq_shutdown(unsigned int irq)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ jz_gpio_irq_mask(irq);
+ desc->status |= IRQ_MASKED;
+
+ /* Set direction to input */
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_SELECT_CLEAR);
+}
+
+static void jz_gpio_irq_ack(unsigned int irq)
+{
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_FLAG_CLEAR);
+};
+
+static int jz_gpio_irq_set_type(unsigned int irq, unsigned int flow_type)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ jz_gpio_irq_mask(irq);
+
+ if (flow_type == IRQ_TYPE_EDGE_BOTH) {
+ uint32_t value = readl(chip->base + JZ_REG_GPIO_PIN);
+ if (value & IRQ_TO_BIT(irq))
+ flow_type = IRQ_TYPE_EDGE_FALLING;
+ else
+ flow_type = IRQ_TYPE_EDGE_RISING;
+ chip->edge_trigger_both |= IRQ_TO_BIT(irq);
+ } else {
+ chip->edge_trigger_both &= ~IRQ_TO_BIT(irq);
+ }
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_SET);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_SET);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_SET);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_SET);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_CLEAR);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_CLEAR);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!(desc->status & IRQ_MASKED))
+ jz_gpio_irq_unmask(irq);
+
+ return 0;
+}
+
+static int jz_gpio_irq_set_wake(unsigned int irq, unsigned int on)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+ spin_lock(&chip->lock);
+ if (on)
+ chip->wakeup |= IRQ_TO_BIT(irq);
+ else
+ chip->wakeup &= ~IRQ_TO_BIT(irq);
+ spin_unlock(&chip->lock);
+
+ set_irq_wake(chip->irq, on);
+ return 0;
+}
+
+/*
+ * This lock class tells lockdep that GPIO irqs are in a different
+ * category than their parents, so it won't report false recursion.
+ */
+static struct lock_class_key gpio_lock_class;
+
+#define JZ4740_GPIO_CHIP(_bank) { \
+ .irq_base = JZ4740_IRQ_GPIO_BASE_ ## _bank, \
+ .gpio_chip = { \
+ .label = "Bank " # _bank, \
+ .owner = THIS_MODULE, \
+ .set = jz_gpio_set_value, \
+ .get = jz_gpio_get_value, \
+ .direction_output = jz_gpio_direction_output, \
+ .direction_input = jz_gpio_direction_input, \
+ .base = JZ4740_GPIO_BASE_ ## _bank, \
+ .ngpio = JZ4740_GPIO_NUM_ ## _bank, \
+ }, \
+ .irq_chip = { \
+ .name = "GPIO Bank " # _bank, \
+ .mask = jz_gpio_irq_mask, \
+ .unmask = jz_gpio_irq_unmask, \
+ .ack = jz_gpio_irq_ack, \
+ .startup = jz_gpio_irq_startup, \
+ .shutdown = jz_gpio_irq_shutdown, \
+ .set_type = jz_gpio_irq_set_type, \
+ .set_wake = jz_gpio_irq_set_wake, \
+ }, \
+}
+
+static struct jz_gpio_chip jz4740_gpio_chips[] = {
+ JZ4740_GPIO_CHIP(A),
+ JZ4740_GPIO_CHIP(B),
+ JZ4740_GPIO_CHIP(C),
+ JZ4740_GPIO_CHIP(D),
+};
+
+static inline struct jz_gpio_chip *sysdev_to_chip(struct sys_device *dev)
+{
+ return container_of(dev, struct jz_gpio_chip, sysdev);
+}
+
+static int jz4740_gpio_suspend(struct sys_device *dev, pm_message_t state)
+{
+ struct jz_gpio_chip *chip = sysdev_to_chip(dev);
+
+ chip->suspend_mask = readl(chip->base + JZ_REG_GPIO_MASK);
+ writel(~(chip->wakeup), chip->base + JZ_REG_GPIO_MASK_SET);
+ writel(chip->wakeup, chip->base + JZ_REG_GPIO_MASK_CLEAR);
+
+ return 0;
+}
+
+static int jz4740_gpio_resume(struct sys_device *dev)
+{
+ struct jz_gpio_chip *chip = sysdev_to_chip(dev);
+ uint32_t mask = chip->suspend_mask;
+
+ writel(~mask, chip->base + JZ_REG_GPIO_MASK_CLEAR);
+ writel(mask, chip->base + JZ_REG_GPIO_MASK_SET);
+
+ return 0;
+}
+
+static struct sysdev_class jz4740_gpio_sysdev_class = {
+ .name = "gpio",
+ .suspend = jz4740_gpio_suspend,
+ .resume = jz4740_gpio_resume,
+};
+
+static int jz4740_gpio_chip_init(struct jz_gpio_chip *chip, unsigned int id)
+{
+ int ret, irq;
+
+ chip->sysdev.id = id;
+ chip->sysdev.cls = &jz4740_gpio_sysdev_class;
+ ret = sysdev_register(&chip->sysdev);
+
+ if (ret)
+ return ret;
+
+ spin_lock_init(&chip->lock);
+
+ chip->base = ioremap(JZ4740_GPIO_BASE_ADDR + (id * 0x100), 0x100);
+
+ gpiochip_add(&chip->gpio_chip);
+
+ chip->irq = JZ4740_IRQ_INTC_GPIO(id);
+ set_irq_data(chip->irq, chip);
+ set_irq_chained_handler(chip->irq, jz_gpio_irq_demux_handler);
+
+ for (irq = chip->irq_base; irq < chip->irq_base + chip->gpio_chip.ngpio; ++irq) {
+ lockdep_set_class(&irq_desc[irq].lock, &gpio_lock_class);
+ set_irq_chip_data(irq, chip);
+ set_irq_chip_and_handler(irq, &chip->irq_chip, handle_level_irq);
+ }
+
+ return 0;
+}
+
+static int __init jz4740_gpio_init(void)
+{
+ unsigned int i;
+ int ret;
+
+ ret = sysdev_class_register(&jz4740_gpio_sysdev_class);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_gpio_chips); ++i)
+ jz4740_gpio_chip_init(&jz4740_gpio_chips[i], i);
+
+ printk(KERN_INFO "JZ4740 GPIO initalized\n");
+
+ return 0;
+}
+arch_initcall(jz4740_gpio_init);
+
+#ifdef CONFIG_DEBUG_FS
+
+static inline void gpio_seq_reg(struct seq_file *s, struct jz_gpio_chip *chip,
+ const char *name, unsigned int reg)
+{
+ seq_printf(s, "\t%s: %08x\n", name, readl(chip->base + reg));
+}
+
+static int gpio_regs_show(struct seq_file *s, void *unused)
+{
+ struct jz_gpio_chip *chip = jz4740_gpio_chips;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_gpio_chips); ++i, ++chip) {
+ seq_printf(s, "==GPIO %d==\n", i);
+ gpio_seq_reg(s, chip, "Pin", JZ_REG_GPIO_PIN);
+ gpio_seq_reg(s, chip, "Data", JZ_REG_GPIO_DATA);
+ gpio_seq_reg(s, chip, "Mask", JZ_REG_GPIO_MASK);
+ gpio_seq_reg(s, chip, "Pull", JZ_REG_GPIO_PULL);
+ gpio_seq_reg(s, chip, "Func", JZ_REG_GPIO_FUNC);
+ gpio_seq_reg(s, chip, "Select", JZ_REG_GPIO_SELECT);
+ gpio_seq_reg(s, chip, "Direction", JZ_REG_GPIO_DIRECTION);
+ gpio_seq_reg(s, chip, "Trigger", JZ_REG_GPIO_TRIGGER);
+ gpio_seq_reg(s, chip, "Flag", JZ_REG_GPIO_FLAG);
+ }
+
+ return 0;
+}
+
+static int gpio_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gpio_regs_show, NULL);
+}
+
+static const struct file_operations gpio_regs_operations = {
+ .open = gpio_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init gpio_debugfs_init(void)
+{
+ (void) debugfs_create_file("jz_regs_gpio", S_IFREG | S_IRUGO,
+ NULL, NULL, &gpio_regs_operations);
+ return 0;
+}
+subsys_initcall(gpio_debugfs_init);
+
+#endif
--
1.5.6.5

2010-06-19 05:10:21

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 13/26] MIPS: JZ4740: Add platform devices

This patch adds platform devices for all the JZ4740 platform drivers.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
* Add JZ4740 PCM device
* Add ADC MFD device and remove battery device
---
arch/mips/include/asm/mach-jz4740/platform.h | 36 ++++
arch/mips/jz4740/platform.c | 284 ++++++++++++++++++++++++++
2 files changed, 320 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h
create mode 100644 arch/mips/jz4740/platform.c

diff --git a/arch/mips/include/asm/mach-jz4740/platform.h b/arch/mips/include/asm/mach-jz4740/platform.h
new file mode 100644
index 0000000..8987a76
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/platform.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform device definitions
+ *
+ * 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.
+ *
+ */
+
+
+#ifndef __JZ4740_PLATFORM_H
+#define __JZ4740_PLATFORM_H
+
+#include <linux/platform_device.h>
+
+extern struct platform_device jz4740_usb_ohci_device;
+extern struct platform_device jz4740_udc_device;
+extern struct platform_device jz4740_mmc_device;
+extern struct platform_device jz4740_rtc_device;
+extern struct platform_device jz4740_i2c_device;
+extern struct platform_device jz4740_nand_device;
+extern struct platform_device jz4740_framebuffer_device;
+extern struct platform_device jz4740_i2s_device;
+extern struct platform_device jz4740_pcm_device;
+extern struct platform_device jz4740_codec_device;
+extern struct platform_device jz4740_adc_device;
+
+void jz4740_serial_device_register(void);
+
+#endif
diff --git a/arch/mips/jz4740/platform.c b/arch/mips/jz4740/platform.c
new file mode 100644
index 0000000..2abd086
--- /dev/null
+++ b/arch/mips/jz4740/platform.c
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform devices
+ *
+ * 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/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/resource.h>
+
+#include <linux/dma-mapping.h>
+
+#include <asm/mach-jz4740/platform.h>
+#include <asm/mach-jz4740/base.h>
+#include <asm/mach-jz4740/irq.h>
+
+#include <linux/serial_core.h>
+#include <linux/serial_8250.h>
+
+#include "serial.h"
+#include "clock.h"
+
+/* OHCI controller */
+static struct resource jz4740_usb_ohci_resources[] = {
+ {
+ .start = JZ4740_UHC_BASE_ADDR,
+ .end = JZ4740_UHC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_UHC,
+ .end = JZ4740_IRQ_UHC,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_usb_ohci_device = {
+ .name = "jz4740-ohci",
+ .id = -1,
+ .dev = {
+ .dma_mask = &jz4740_usb_ohci_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+ .num_resources = ARRAY_SIZE(jz4740_usb_ohci_resources),
+ .resource = jz4740_usb_ohci_resources,
+};
+
+/* UDC (USB gadget controller) */
+static struct resource jz4740_usb_gdt_resources[] = {
+ {
+ .start = JZ4740_UDC_BASE_ADDR,
+ .end = JZ4740_UDC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_UDC,
+ .end = JZ4740_IRQ_UDC,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_udc_device = {
+ .name = "jz-udc",
+ .id = -1,
+ .dev = {
+ .dma_mask = &jz4740_udc_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+ .num_resources = ARRAY_SIZE(jz4740_usb_gdt_resources),
+ .resource = jz4740_usb_gdt_resources,
+};
+
+/* MMC/SD controller */
+static struct resource jz4740_mmc_resources[] = {
+ {
+ .start = JZ4740_MSC_BASE_ADDR,
+ .end = JZ4740_MSC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_MSC,
+ .end = JZ4740_IRQ_MSC,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+struct platform_device jz4740_mmc_device = {
+ .name = "jz4740-mmc",
+ .id = 0,
+ .dev = {
+ .dma_mask = &jz4740_mmc_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+ .num_resources = ARRAY_SIZE(jz4740_mmc_resources),
+ .resource = jz4740_mmc_resources,
+};
+
+/* RTC controller */
+static struct resource jz4740_rtc_resources[] = {
+ {
+ .start = JZ4740_RTC_BASE_ADDR,
+ .end = JZ4740_RTC_BASE_ADDR + 0x38 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_RTC,
+ .end = JZ4740_IRQ_RTC,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_rtc_device = {
+ .name = "jz4740-rtc",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_rtc_resources),
+ .resource = jz4740_rtc_resources,
+};
+
+/* I2C controller */
+static struct resource jz4740_i2c_resources[] = {
+ {
+ .start = JZ4740_I2C_BASE_ADDR,
+ .end = JZ4740_I2C_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_I2C,
+ .end = JZ4740_IRQ_I2C,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+struct platform_device jz4740_i2c_device = {
+ .name = "jz4740-i2c",
+ .id = 0,
+ .num_resources = ARRAY_SIZE(jz4740_i2c_resources),
+ .resource = jz4740_i2c_resources,
+};
+
+/* NAND controller */
+static struct resource jz4740_nand_resources[] = {
+ {
+ .start = JZ4740_EMC_BASE_ADDR,
+ .end = JZ4740_EMC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_nand_device = {
+ .name = "jz4740-nand",
+ .num_resources = ARRAY_SIZE(jz4740_nand_resources),
+ .resource = jz4740_nand_resources,
+};
+
+/* LCD controller */
+static struct resource jz4740_framebuffer_resources[] = {
+ {
+ .start = JZ4740_LCD_BASE_ADDR,
+ .end = JZ4740_LCD_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_framebuffer_device = {
+ .name = "jz4740-fb",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_framebuffer_resources),
+ .resource = jz4740_framebuffer_resources,
+ .dev = {
+ .dma_mask = &jz4740_framebuffer_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+};
+
+/* I2S controller */
+static struct resource jz4740_i2s_resources[] = {
+ {
+ .start = JZ4740_AIC_BASE_ADDR,
+ .end = JZ4740_AIC_BASE_ADDR + 0x38 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_i2s_device = {
+ .name = "jz4740-i2s",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_i2s_resources),
+ .resource = jz4740_i2s_resources,
+};
+
+/* PCM */
+struct platform_device jz4740_pcm_device = {
+ .name = "jz4740-pcm",
+ .id = -1,
+};
+
+/* Codec */
+static struct resource jz4740_codec_resources[] = {
+ {
+ .start = JZ4740_AIC_BASE_ADDR + 0x80,
+ .end = JZ4740_AIC_BASE_ADDR + 0x88 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_codec_device = {
+ .name = "jz4740-codec",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_codec_resources),
+ .resource = jz4740_codec_resources,
+};
+
+/* ADC controller */
+static struct resource jz4740_adc_resources[] = {
+ {
+ .start = JZ4740_SADC_BASE_ADDR,
+ .end = JZ4740_SADC_BASE_ADDR + 0x30,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_SADC,
+ .end = JZ4740_IRQ_SADC,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .start = JZ4740_IRQ_ADC_BASE,
+ .end = JZ4740_IRQ_ADC_BASE,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_adc_device = {
+ .name = "jz4740-adc",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_adc_resources),
+ .resource = jz4740_adc_resources,
+};
+
+/* Serial */
+#define JZ4740_UART_DATA(_id) \
+ { \
+ .flags = UPF_SKIP_TEST | UPF_IOREMAP | UPF_FIXED_TYPE, \
+ .iotype = UPIO_MEM, \
+ .regshift = 2, \
+ .serial_out = jz4740_serial_out, \
+ .type = PORT_16550, \
+ .mapbase = JZ4740_UART ## _id ## _BASE_ADDR, \
+ .irq = JZ4740_IRQ_UART ## _id, \
+ }
+
+static struct plat_serial8250_port jz4740_uart_data[] = {
+ JZ4740_UART_DATA(0),
+ JZ4740_UART_DATA(1),
+ {},
+};
+
+static struct platform_device jz4740_uart_device = {
+ .name = "serial8250",
+ .id = 0,
+ .dev = {
+ .platform_data = jz4740_uart_data,
+ },
+};
+
+void jz4740_serial_device_register(void)
+{
+ struct plat_serial8250_port *p;
+
+ for (p = jz4740_uart_data; p->flags != 0; ++p)
+ p->uartclk = jz4740_clock_bdata.ext_rate;
+
+ platform_device_register(&jz4740_uart_device);
+}
--
1.5.6.5

2010-06-19 05:11:19

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver

This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to
demultiplex IRQs and synchronize access to shared registers between the battery,
hwmon and (future) touchscreen driver.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Samuel Ortiz <[email protected]>
---
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/jz4740-adc.c | 392 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/jz4740-adc.h | 32 ++++
4 files changed, 433 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/jz4740-adc.c
create mode 100644 include/linux/jz4740-adc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 9da0e50..9cacc39 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -482,6 +482,14 @@ config MFD_JANZ_CMODIO
host many different types of MODULbus daughterboards, including
CAN and GPIO controllers.

+config MFD_JZ4740_ADC
+ tristate "Support for the JZ4740 SoC ADC core"
+ select MFD_CORE
+ depends on MACH_JZ4740
+ help
+ Say yes here if you want support for the ADC unit in the JZ4740 SoC.
+ This driver is necessary for jz4740-battery and jz4740-hwmon driver.
+
endif # MFD_SUPPORT

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index fb503e7..a1a2765 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -71,3 +71,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
+obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c
new file mode 100644
index 0000000..cac8a52
--- /dev/null
+++ b/drivers/mfd/jz4740-adc.c
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC ADC 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.
+ *
+ * This driver synchronizes access to the JZ4740 ADC core between the
+ * JZ4740 battery and hwmon drivers.
+ */
+
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <linux/clk.h>
+#include <linux/mfd/core.h>
+
+#include <linux/jz4740-adc.h>
+
+
+#define JZ_REG_ADC_ENABLE 0x00
+#define JZ_REG_ADC_CFG 0x04
+#define JZ_REG_ADC_CTRL 0x08
+#define JZ_REG_ADC_STATUS 0x0c
+
+#define JZ_REG_ADC_TOUCHSCREEN_BASE 0x10
+#define JZ_REG_ADC_BATTERY_BASE 0x1c
+#define JZ_REG_ADC_HWMON_BASE 0x20
+
+#define JZ_ADC_ENABLE_TOUCH BIT(2)
+#define JZ_ADC_ENABLE_BATTERY BIT(1)
+#define JZ_ADC_ENABLE_ADCIN BIT(0)
+
+enum {
+ JZ_ADC_IRQ_ADCIN = 0,
+ JZ_ADC_IRQ_BATTERY,
+ JZ_ADC_IRQ_TOUCH,
+ JZ_ADC_IRQ_PENUP,
+ JZ_ADC_IRQ_PENDOWN,
+};
+
+struct jz4740_adc {
+ struct resource *mem;
+ void __iomem *base;
+
+ int irq;
+ int irq_base;
+
+ struct clk *clk;
+ unsigned int clk_ref;
+
+ spinlock_t lock;
+};
+
+static inline void jz4740_adc_irq_set_masked(struct jz4740_adc *adc, int irq,
+ bool masked)
+{
+ unsigned long flags;
+ uint8_t val;
+
+ irq -= adc->irq_base;
+
+ spin_lock_irqsave(&adc->lock, flags);
+
+ val = readb(adc->base + JZ_REG_ADC_CTRL);
+ if (masked)
+ val |= BIT(irq);
+ else
+ val &= ~BIT(irq);
+ writeb(val, adc->base + JZ_REG_ADC_CTRL);
+
+ spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+static void jz4740_adc_irq_mask(unsigned int irq)
+{
+ struct jz4740_adc *adc = get_irq_chip_data(irq);
+ jz4740_adc_irq_set_masked(adc, irq, true);
+}
+
+static void jz4740_adc_irq_unmask(unsigned int irq)
+{
+ struct jz4740_adc *adc = get_irq_chip_data(irq);
+ jz4740_adc_irq_set_masked(adc, irq, false);
+}
+
+static void jz4740_adc_irq_ack(unsigned int irq)
+{
+ struct jz4740_adc *adc = get_irq_chip_data(irq);
+
+ irq -= adc->irq_base;
+ writeb(BIT(irq), adc->base + JZ_REG_ADC_STATUS);
+}
+
+static struct irq_chip jz4740_adc_irq_chip = {
+ .name = "jz4740-adc",
+ .mask = jz4740_adc_irq_mask,
+ .unmask = jz4740_adc_irq_unmask,
+ .ack = jz4740_adc_irq_ack,
+};
+
+static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
+{
+ struct jz4740_adc *adc = get_irq_desc_data(desc);
+ uint8_t status;
+ unsigned int i;
+
+ status = readb(adc->base + JZ_REG_ADC_STATUS);
+
+ for (i = 0; i < 5; ++i) {
+ if (status & BIT(i))
+ generic_handle_irq(adc->irq_base + i);
+ }
+}
+
+static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&adc->lock, flags);
+ if (adc->clk_ref++ == 0)
+ clk_enable(adc->clk);
+ spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&adc->lock, flags);
+ if (--adc->clk_ref == 0)
+ clk_disable(adc->clk);
+ spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+
+static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine,
+ bool enabled)
+{
+ unsigned long flags;
+ uint8_t val;
+
+ spin_lock_irqsave(&adc->lock, flags);
+
+ val = readb(adc->base + JZ_REG_ADC_ENABLE);
+ if (enabled)
+ val |= BIT(engine);
+ else
+ val &= BIT(engine);
+ writeb(val, adc->base + JZ_REG_ADC_ENABLE);
+
+ spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+static int jz4740_adc_cell_enable(struct platform_device *pdev)
+{
+ struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
+
+ jz4740_adc_clk_enable(adc);
+ jz4740_adc_set_enabled(adc, pdev->id, true);
+
+ return 0;
+}
+
+static int jz4740_adc_cell_disable(struct platform_device *pdev)
+{
+ struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
+
+ jz4740_adc_set_enabled(adc, pdev->id, false);
+ jz4740_adc_clk_disable(adc);
+
+ return 0;
+}
+
+int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val)
+{
+ struct jz4740_adc *adc = dev_get_drvdata(dev);
+ unsigned long flags;
+ uint32_t cfg;
+
+ if (!adc)
+ return -ENODEV;
+
+ spin_lock_irqsave(&adc->lock, flags);
+
+ cfg = readl(adc->base + JZ_REG_ADC_CFG);
+
+ cfg &= ~mask;
+ cfg |= val;
+
+ writel(cfg, adc->base + JZ_REG_ADC_CFG);
+
+ spin_unlock_irqrestore(&adc->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(jz4740_adc_set_config);
+
+static struct resource jz4740_hwmon_resources[] = {
+ {
+ .start = JZ_ADC_IRQ_ADCIN,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .start = JZ_REG_ADC_HWMON_BASE,
+ .end = JZ_REG_ADC_HWMON_BASE + 3,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct resource jz4740_battery_resources[] = {
+ {
+ .start = JZ_ADC_IRQ_BATTERY,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .start = JZ_REG_ADC_BATTERY_BASE,
+ .end = JZ_REG_ADC_BATTERY_BASE + 3,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+const struct mfd_cell jz4740_adc_cells[] = {
+ {
+ .id = 0,
+ .name = "jz4740-hwmon",
+ .num_resources = ARRAY_SIZE(jz4740_hwmon_resources),
+ .resources = jz4740_hwmon_resources,
+ .platform_data = (void *)&jz4740_adc_cells[0],
+ .data_size = sizeof(struct mfd_cell),
+
+ .enable = jz4740_adc_cell_enable,
+ .disable = jz4740_adc_cell_disable,
+ },
+ {
+ .id = 1,
+ .name = "jz4740-battery",
+ .num_resources = ARRAY_SIZE(jz4740_battery_resources),
+ .resources = jz4740_battery_resources,
+ .platform_data = (void *)&jz4740_adc_cells[1],
+ .data_size = sizeof(struct mfd_cell),
+
+ .enable = jz4740_adc_cell_enable,
+ .disable = jz4740_adc_cell_disable,
+ },
+};
+
+static int __devinit jz4740_adc_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_adc *adc;
+ struct resource *mem_base;
+ int irq;
+
+ adc = kmalloc(sizeof(*adc), GFP_KERNEL);
+
+ adc->irq = platform_get_irq(pdev, 0);
+ if (adc->irq < 0) {
+ ret = adc->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free;
+ }
+
+ adc->irq_base = platform_get_irq(pdev, 1);
+ if (adc->irq_base < 0) {
+ ret = adc->irq_base;
+ dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);
+ goto err_free;
+ }
+
+ mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_base) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ goto err_free;
+ }
+
+ /* Only request the shared registers for the MFD driver */
+ adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
+ pdev->name);
+ if (!adc->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
+ if (!adc->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ adc->clk = clk_get(&pdev->dev, "adc");
+ if (IS_ERR(adc->clk)) {
+ ret = PTR_ERR(adc->clk);
+ dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
+ goto err_iounmap;
+ }
+
+ spin_lock_init(&adc->lock);
+
+ adc->clk_ref = 0;
+
+ platform_set_drvdata(pdev, adc);
+
+ for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) {
+ set_irq_chip_data(irq, adc);
+ set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip,
+ handle_level_irq);
+ }
+
+ set_irq_data(adc->irq, adc);
+ set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux);
+
+ writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
+ writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
+
+ mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
+ ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base);
+
+ return 0;
+
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(adc->base);
+err_release_mem_region:
+ release_mem_region(adc->mem->start, resource_size(adc->mem));
+err_free:
+ kfree(adc);
+
+ return ret;
+}
+
+static int __devexit jz4740_adc_remove(struct platform_device *pdev)
+{
+ struct jz4740_adc *adc = platform_get_drvdata(pdev);
+
+ mfd_remove_devices(&pdev->dev);
+
+ set_irq_data(adc->irq, NULL);
+ set_irq_chained_handler(adc->irq, NULL);
+
+ iounmap(adc->base);
+ release_mem_region(adc->mem->start, resource_size(adc->mem));
+
+ clk_put(adc->clk);
+
+ platform_set_drvdata(pdev, NULL);
+
+ kfree(adc);
+
+ return 0;
+}
+
+struct platform_driver jz4740_adc_driver = {
+ .probe = jz4740_adc_probe,
+ .remove = __devexit_p(jz4740_adc_remove),
+ .driver = {
+ .name = "jz4740-adc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_adc_init(void)
+{
+ return platform_driver_register(&jz4740_adc_driver);
+}
+module_init(jz4740_adc_init);
+
+static void __exit jz4740_adc_exit(void)
+{
+ platform_driver_unregister(&jz4740_adc_driver);
+}
+module_exit(jz4740_adc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-adc");
diff --git a/include/linux/jz4740-adc.h b/include/linux/jz4740-adc.h
new file mode 100644
index 0000000..9053f95
--- /dev/null
+++ b/include/linux/jz4740-adc.h
@@ -0,0 +1,32 @@
+
+#ifndef __LINUX_JZ4740_ADC
+#define __LINUX_JZ4740_ADC
+
+#include <linux/device.h>
+
+/*
+ * jz4740_adc_set_config - Configure a JZ4740 adc device
+ * @dev: Pointer to a jz4740-adc device
+ * @mask: Mask for the config value to be set
+ * @val: Value to be set
+ *
+ * This function can be used by the JZ4740 ADC mfd cells to configure their
+ * options in the shared config register.
+*/
+int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val);
+
+#define JZ_ADC_CONFIG_SPZZ BIT(31)
+#define JZ_ADC_CONFIG_EX_IN BIT(30)
+#define JZ_ADC_CONFIG_DNUM_MASK (0x7 << 16)
+#define JZ_ADC_CONFIG_DMA_ENABLE BIT(15)
+#define JZ_ADC_CONFIG_XYZ_MASK (0x2 << 13)
+#define JZ_ADC_CONFIG_SAMPLE_NUM_MASK (0x7 << 10)
+#define JZ_ADC_CONFIG_CLKDIV_MASK (0xf << 5)
+#define JZ_ADC_CONFIG_BAT_MB BIT(4)
+
+#define JZ_ADC_CONFIG_DNUM(dnum) ((dnum) << 16)
+#define JZ_ADC_CONFIG_XYZ_OFFSET(dnum) ((xyz) << 13)
+#define JZ_ADC_CONFIG_SAMPLE_NUM(x) ((x) << 10)
+#define JZ_ADC_CONFIG_CLKDIV(div) ((div) << 5)
+
+#endif
--
1.5.6.5

2010-06-19 05:10:43

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver

This patch adds support for the LCD controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: [email protected]

---
Changes since v1
- Use __packed instead of __attribute__((packed))
- Make jzfb_fix const
- Only set mode in set_par if it has changed
---
drivers/video/Kconfig | 9 +
drivers/video/Makefile | 1 +
drivers/video/jz4740_fb.c | 817 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/jz4740_fb.h | 58 ++++
4 files changed, 885 insertions(+), 0 deletions(-)
create mode 100644 drivers/video/jz4740_fb.c
create mode 100644 include/linux/jz4740_fb.h

diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index a9f9e5e..eae4c8a 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2237,6 +2237,15 @@ config FB_BROADSHEET
and could also have been called by other names when coupled with
a bridge adapter.

+config FB_JZ4740
+ tristate "JZ4740 LCD framebuffer support"
+ depends on FB
+ select FB_SYS_FILLRECT
+ select FB_SYS_COPYAREA
+ select FB_SYS_IMAGEBLIT
+ help
+ Framebuffer support for the JZ4740 SoC.
+
source "drivers/video/omap/Kconfig"
source "drivers/video/omap2/Kconfig"

diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 3c3bf86..fd2df57 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -132,6 +132,7 @@ obj-$(CONFIG_FB_CARMINE) += carminefb.o
obj-$(CONFIG_FB_MB862XX) += mb862xx/
obj-$(CONFIG_FB_MSM) += msm/
obj-$(CONFIG_FB_NUC900) += nuc900fb.o
+obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o

# Platform or fallback drivers go here
obj-$(CONFIG_FB_UVESA) += uvesafb.o
diff --git a/drivers/video/jz4740_fb.c b/drivers/video/jz4740_fb.c
new file mode 100644
index 0000000..8d03181
--- /dev/null
+++ b/drivers/video/jz4740_fb.c
@@ -0,0 +1,817 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC LCD framebuffer 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/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <linux/console.h>
+#include <linux/fb.h>
+
+#include <linux/dma-mapping.h>
+
+#include <linux/jz4740_fb.h>
+#include <asm/mach-jz4740/gpio.h>
+
+#define JZ_REG_LCD_CFG 0x00
+#define JZ_REG_LCD_VSYNC 0x04
+#define JZ_REG_LCD_HSYNC 0x08
+#define JZ_REG_LCD_VAT 0x0C
+#define JZ_REG_LCD_DAH 0x10
+#define JZ_REG_LCD_DAV 0x14
+#define JZ_REG_LCD_PS 0x18
+#define JZ_REG_LCD_CLS 0x1C
+#define JZ_REG_LCD_SPL 0x20
+#define JZ_REG_LCD_REV 0x24
+#define JZ_REG_LCD_CTRL 0x30
+#define JZ_REG_LCD_STATE 0x34
+#define JZ_REG_LCD_IID 0x38
+#define JZ_REG_LCD_DA0 0x40
+#define JZ_REG_LCD_SA0 0x44
+#define JZ_REG_LCD_FID0 0x48
+#define JZ_REG_LCD_CMD0 0x4C
+#define JZ_REG_LCD_DA1 0x50
+#define JZ_REG_LCD_SA1 0x54
+#define JZ_REG_LCD_FID1 0x58
+#define JZ_REG_LCD_CMD1 0x5C
+
+#define JZ_LCD_CFG_SLCD BIT(31)
+#define JZ_LCD_CFG_PS_DISABLE BIT(23)
+#define JZ_LCD_CFG_CLS_DISABLE BIT(22)
+#define JZ_LCD_CFG_SPL_DISABLE BIT(21)
+#define JZ_LCD_CFG_REV_DISABLE BIT(20)
+#define JZ_LCD_CFG_HSYNCM BIT(19)
+#define JZ_LCD_CFG_PCLKM BIT(18)
+#define JZ_LCD_CFG_INV BIT(17)
+#define JZ_LCD_CFG_SYNC_DIR BIT(16)
+#define JZ_LCD_CFG_PS_POLARITY BIT(15)
+#define JZ_LCD_CFG_CLS_POLARITY BIT(14)
+#define JZ_LCD_CFG_SPL_POLARITY BIT(13)
+#define JZ_LCD_CFG_REV_POLARITY BIT(12)
+#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(11)
+#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10)
+#define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9)
+#define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8)
+#define JZ_LCD_CFG_18_BIT BIT(7)
+#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4))
+#define JZ_LCD_CFG_MODE_MASK 0xf
+
+#define JZ_LCD_CTRL_BURST_4 (0x0 << 28)
+#define JZ_LCD_CTRL_BURST_8 (0x1 << 28)
+#define JZ_LCD_CTRL_BURST_16 (0x2 << 28)
+#define JZ_LCD_CTRL_RGB555 BIT(27)
+#define JZ_LCD_CTRL_OFUP BIT(26)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_4 (0x1 << 24)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_2 (0x2 << 24)
+#define JZ_LCD_CTRL_PDD_MASK (0xff << 16)
+#define JZ_LCD_CTRL_EOF_IRQ BIT(13)
+#define JZ_LCD_CTRL_SOF_IRQ BIT(12)
+#define JZ_LCD_CTRL_OFU_IRQ BIT(11)
+#define JZ_LCD_CTRL_IFU0_IRQ BIT(10)
+#define JZ_LCD_CTRL_IFU1_IRQ BIT(9)
+#define JZ_LCD_CTRL_DD_IRQ BIT(8)
+#define JZ_LCD_CTRL_QDD_IRQ BIT(7)
+#define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6)
+#define JZ_LCD_CTRL_LSB_FISRT BIT(5)
+#define JZ_LCD_CTRL_DISABLE BIT(4)
+#define JZ_LCD_CTRL_ENABLE BIT(3)
+#define JZ_LCD_CTRL_BPP_1 0x0
+#define JZ_LCD_CTRL_BPP_2 0x1
+#define JZ_LCD_CTRL_BPP_4 0x2
+#define JZ_LCD_CTRL_BPP_8 0x3
+#define JZ_LCD_CTRL_BPP_15_16 0x4
+#define JZ_LCD_CTRL_BPP_18_24 0x5
+
+#define JZ_LCD_CMD_SOF_IRQ BIT(15)
+#define JZ_LCD_CMD_EOF_IRQ BIT(16)
+#define JZ_LCD_CMD_ENABLE_PAL BIT(12)
+
+#define JZ_LCD_SYNC_MASK 0x3ff
+
+#define JZ_LCD_STATE_DISABLED BIT(0)
+
+struct jzfb_framedesc {
+ uint32_t next;
+ uint32_t addr;
+ uint32_t id;
+ uint32_t cmd;
+} __packed;
+
+struct jzfb {
+ struct fb_info *fb;
+ struct platform_device *pdev;
+ void __iomem *base;
+ struct resource *mem;
+ struct jz4740_fb_platform_data *pdata;
+
+ size_t vidmem_size;
+ void *vidmem;
+ dma_addr_t vidmem_phys;
+ struct jzfb_framedesc *framedesc;
+ dma_addr_t framedesc_phys;
+
+ struct clk *ldclk;
+ struct clk *lpclk;
+
+ unsigned is_enabled:1;
+ struct mutex lock;
+
+ uint32_t pseudo_palette[16];
+};
+
+static const struct fb_fix_screeninfo jzfb_fix __devinitdata = {
+ .id = "JZ4740 FB",
+ .type = FB_TYPE_PACKED_PIXELS,
+ .visual = FB_VISUAL_TRUECOLOR,
+ .xpanstep = 0,
+ .ypanstep = 0,
+ .ywrapstep = 0,
+ .accel = FB_ACCEL_NONE,
+};
+
+static const struct jz_gpio_bulk_request jz_lcd_ctrl_pins[] = {
+ JZ_GPIO_BULK_PIN(LCD_PCLK),
+ JZ_GPIO_BULK_PIN(LCD_HSYNC),
+ JZ_GPIO_BULK_PIN(LCD_VSYNC),
+ JZ_GPIO_BULK_PIN(LCD_DE),
+ JZ_GPIO_BULK_PIN(LCD_PS),
+ JZ_GPIO_BULK_PIN(LCD_REV),
+};
+
+static const struct jz_gpio_bulk_request jz_lcd_data_pins[] = {
+ JZ_GPIO_BULK_PIN(LCD_DATA0),
+ JZ_GPIO_BULK_PIN(LCD_DATA1),
+ JZ_GPIO_BULK_PIN(LCD_DATA2),
+ JZ_GPIO_BULK_PIN(LCD_DATA3),
+ JZ_GPIO_BULK_PIN(LCD_DATA4),
+ JZ_GPIO_BULK_PIN(LCD_DATA5),
+ JZ_GPIO_BULK_PIN(LCD_DATA6),
+ JZ_GPIO_BULK_PIN(LCD_DATA7),
+ JZ_GPIO_BULK_PIN(LCD_DATA8),
+ JZ_GPIO_BULK_PIN(LCD_DATA9),
+ JZ_GPIO_BULK_PIN(LCD_DATA10),
+ JZ_GPIO_BULK_PIN(LCD_DATA11),
+ JZ_GPIO_BULK_PIN(LCD_DATA12),
+ JZ_GPIO_BULK_PIN(LCD_DATA13),
+ JZ_GPIO_BULK_PIN(LCD_DATA14),
+ JZ_GPIO_BULK_PIN(LCD_DATA15),
+ JZ_GPIO_BULK_PIN(LCD_DATA16),
+ JZ_GPIO_BULK_PIN(LCD_DATA17),
+};
+
+static unsigned int jzfb_num_ctrl_pins(struct jzfb *jzfb)
+{
+ unsigned int num;
+
+ switch (jzfb->pdata->lcd_type) {
+ case JZ_LCD_TYPE_GENERIC_16_BIT:
+ num = 4;
+ break;
+ case JZ_LCD_TYPE_GENERIC_18_BIT:
+ num = 4;
+ break;
+ case JZ_LCD_TYPE_8BIT_SERIAL:
+ num = 3;
+ break;
+ default:
+ num = 0;
+ break;
+ }
+ return num;
+}
+
+static unsigned int jzfb_num_data_pins(struct jzfb *jzfb)
+{
+ unsigned int num;
+
+ switch (jzfb->pdata->lcd_type) {
+ case JZ_LCD_TYPE_GENERIC_16_BIT:
+ num = 16;
+ break;
+ case JZ_LCD_TYPE_GENERIC_18_BIT:
+ num = 18;
+ break;
+ case JZ_LCD_TYPE_8BIT_SERIAL:
+ num = 8;
+ break;
+ default:
+ num = 0;
+ break;
+ }
+ return num;
+}
+
+/* Based on CNVT_TOHW macro from skeletonfb.c */
+static inline uint32_t jzfb_convert_color_to_hw(unsigned val,
+ struct fb_bitfield *bf)
+{
+ return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset;
+}
+
+static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *fb)
+{
+ uint32_t color;
+
+ if (regno >= 16)
+ return -EINVAL;
+
+ color = jzfb_convert_color_to_hw(red, &fb->var.red);
+ color |= jzfb_convert_color_to_hw(green, &fb->var.green);
+ color |= jzfb_convert_color_to_hw(blue, &fb->var.blue);
+ color |= jzfb_convert_color_to_hw(transp, &fb->var.transp);
+
+ ((uint32_t *)(fb->pseudo_palette))[regno] = color;
+
+ return 0;
+}
+
+static int jzfb_get_controller_bpp(struct jzfb *jzfb)
+{
+ switch (jzfb->pdata->bpp) {
+ case 18:
+ case 24:
+ return 32;
+ case 15:
+ return 16;
+ default:
+ return jzfb->pdata->bpp;
+ }
+}
+
+static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb,
+ struct fb_var_screeninfo *var)
+{
+ size_t i;
+ struct fb_videomode *mode = jzfb->pdata->modes;
+
+ for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) {
+ if (mode->xres == var->xres && mode->yres == var->yres)
+ return mode;
+ }
+
+ return NULL;
+}
+
+static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
+{
+ struct jzfb *jzfb = fb->par;
+ struct fb_videomode *mode;
+
+ if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) &&
+ var->bits_per_pixel != jzfb->pdata->bpp)
+ return -EINVAL;
+
+ mode = jzfb_get_mode(jzfb, var);
+ if (mode == NULL)
+ return -EINVAL;
+
+ fb_videomode_to_var(var, mode);
+
+ switch (jzfb->pdata->bpp) {
+ case 8:
+ break;
+ case 15:
+ var->red.offset = 10;
+ var->red.length = 5;
+ var->green.offset = 6;
+ var->green.length = 5;
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ break;
+ case 16:
+ var->red.offset = 11;
+ var->red.length = 5;
+ var->green.offset = 5;
+ var->green.length = 6;
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ break;
+ case 18:
+ var->red.offset = 16;
+ var->red.length = 6;
+ var->green.offset = 8;
+ var->green.length = 6;
+ var->blue.offset = 0;
+ var->blue.length = 6;
+ var->bits_per_pixel = 32;
+ break;
+ case 32:
+ case 24:
+ var->transp.offset = 24;
+ var->transp.length = 8;
+ var->red.offset = 16;
+ var->red.length = 8;
+ var->green.offset = 8;
+ var->green.length = 8;
+ var->blue.offset = 0;
+ var->blue.length = 8;
+ var->bits_per_pixel = 32;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int jzfb_set_par(struct fb_info *info)
+{
+ struct jzfb *jzfb = info->par;
+ struct fb_var_screeninfo *var = &info->var;
+ struct fb_videomode *mode;
+ uint16_t hds, vds;
+ uint16_t hde, vde;
+ uint16_t ht, vt;
+ uint32_t ctrl;
+ uint32_t cfg;
+ unsigned long rate;
+
+ mode = jzfb_get_mode(jzfb, var);
+ if (mode == NULL)
+ return -EINVAL;
+
+ if (mode == info->mode)
+ return 0;
+
+ info->mode = mode;
+
+ hds = mode->hsync_len + mode->left_margin;
+ hde = hds + mode->xres;
+ ht = hde + mode->right_margin;
+
+ vds = mode->vsync_len + mode->upper_margin;
+ vde = vds + mode->yres;
+ vt = vde + mode->lower_margin;
+
+ ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16;
+
+ switch (jzfb->pdata->bpp) {
+ case 1:
+ ctrl |= JZ_LCD_CTRL_BPP_1;
+ break;
+ case 2:
+ ctrl |= JZ_LCD_CTRL_BPP_2;
+ break;
+ case 4:
+ ctrl |= JZ_LCD_CTRL_BPP_4;
+ break;
+ case 8:
+ ctrl |= JZ_LCD_CTRL_BPP_8;
+ break;
+ case 15:
+ ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */
+ case 16:
+ ctrl |= JZ_LCD_CTRL_BPP_15_16;
+ break;
+ case 18:
+ case 24:
+ case 32:
+ ctrl |= JZ_LCD_CTRL_BPP_18_24;
+ break;
+ default:
+ break;
+ }
+
+ cfg = JZ_LCD_CFG_PS_DISABLE | JZ_LCD_CFG_CLS_DISABLE |
+ JZ_LCD_CFG_SPL_DISABLE | JZ_LCD_CFG_REV_DISABLE;
+
+ if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
+ cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
+
+ if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
+ cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW;
+
+ if (jzfb->pdata->pixclk_falling_edge)
+ cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
+
+ if (jzfb->pdata->date_enable_active_low)
+ cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW;
+
+ if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT)
+ cfg |= JZ_LCD_CFG_18_BIT;
+
+ cfg |= jzfb->pdata->lcd_type & 0xf;
+
+ if (mode->pixclock) {
+ rate = PICOS2KHZ(mode->pixclock) * 1000;
+ mode->refresh = rate / vt / ht;
+ } else {
+ if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL)
+ rate = mode->refresh * (vt + 2 * mode->xres) * ht;
+ else
+ rate = mode->refresh * vt * ht;
+
+ mode->pixclock = KHZ2PICOS(rate / 1000);
+ }
+
+ mutex_lock(&jzfb->lock);
+ if (!jzfb->is_enabled)
+ clk_enable(jzfb->ldclk);
+ else
+ ctrl |= JZ_LCD_CTRL_ENABLE;
+
+ writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC);
+ writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC);
+
+ writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT);
+
+ writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH);
+ writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV);
+
+ writel(cfg, jzfb->base + JZ_REG_LCD_CFG);
+
+ writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
+
+ if (!jzfb->is_enabled)
+ clk_disable(jzfb->ldclk);
+
+ mutex_unlock(&jzfb->lock);
+
+ clk_set_rate(jzfb->lpclk, rate);
+ clk_set_rate(jzfb->ldclk, rate * 3);
+
+ return 0;
+}
+
+static void jzfb_enable(struct jzfb *jzfb)
+{
+ uint32_t ctrl;
+
+ clk_enable(jzfb->ldclk);
+
+ jz_gpio_bulk_resume(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_resume(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ writel(0, jzfb->base + JZ_REG_LCD_STATE);
+
+ writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
+
+ ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
+ ctrl |= JZ_LCD_CTRL_ENABLE;
+ ctrl &= ~JZ_LCD_CTRL_DISABLE;
+ writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
+}
+
+static void jzfb_disable(struct jzfb *jzfb)
+{
+ uint32_t ctrl;
+
+ ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
+ ctrl |= JZ_LCD_CTRL_DISABLE;
+ writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
+ do {
+ ctrl = readl(jzfb->base + JZ_REG_LCD_STATE);
+ } while (!(ctrl & JZ_LCD_STATE_DISABLED));
+
+ jz_gpio_bulk_suspend(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_suspend(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ clk_disable(jzfb->ldclk);
+}
+
+static int jzfb_blank(int blank_mode, struct fb_info *info)
+{
+ struct jzfb *jzfb = info->par;
+
+ switch (blank_mode) {
+ case FB_BLANK_UNBLANK:
+ mutex_lock(&jzfb->lock);
+ if (jzfb->is_enabled) {
+ mutex_unlock(&jzfb->lock);
+ return 0;
+ }
+
+ jzfb_enable(jzfb);
+ jzfb->is_enabled = 1;
+
+ mutex_unlock(&jzfb->lock);
+ break;
+ default:
+ mutex_lock(&jzfb->lock);
+ if (!jzfb->is_enabled) {
+ mutex_unlock(&jzfb->lock);
+ return 0;
+ }
+
+ jzfb_disable(jzfb);
+ jzfb->is_enabled = 0;
+
+ mutex_unlock(&jzfb->lock);
+ break;
+ }
+
+ return 0;
+}
+
+static int jzfb_alloc_devmem(struct jzfb *jzfb)
+{
+ int max_videosize = 0;
+ struct fb_videomode *mode = jzfb->pdata->modes;
+ void *page;
+ int i;
+
+ for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) {
+ if (max_videosize < mode->xres * mode->yres)
+ max_videosize = mode->xres * mode->yres;
+ }
+
+ max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3;
+
+ jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev,
+ sizeof(*jzfb->framedesc),
+ &jzfb->framedesc_phys, GFP_KERNEL);
+
+ if (!jzfb->framedesc)
+ return -ENOMEM;
+
+ jzfb->vidmem_size = PAGE_ALIGN(max_videosize);
+ jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev,
+ jzfb->vidmem_size,
+ &jzfb->vidmem_phys, GFP_KERNEL);
+
+ if (!jzfb->vidmem)
+ goto err_free_framedesc;
+
+ for (page = jzfb->vidmem;
+ page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size);
+ page += PAGE_SIZE) {
+ SetPageReserved(virt_to_page(page));
+ }
+
+ jzfb->framedesc->next = jzfb->framedesc_phys;
+ jzfb->framedesc->addr = jzfb->vidmem_phys;
+ jzfb->framedesc->id = 0xdeafbead;
+ jzfb->framedesc->cmd = 0;
+ jzfb->framedesc->cmd |= max_videosize / 4;
+
+ return 0;
+
+err_free_framedesc:
+ dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc),
+ jzfb->framedesc, jzfb->framedesc_phys);
+ return -ENOMEM;
+}
+
+static void jzfb_free_devmem(struct jzfb *jzfb)
+{
+ dma_free_coherent(&jzfb->pdev->dev, jzfb->vidmem_size,
+ jzfb->vidmem, jzfb->vidmem_phys);
+ dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc),
+ jzfb->framedesc, jzfb->framedesc_phys);
+}
+
+static struct fb_ops jzfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = jzfb_check_var,
+ .fb_set_par = jzfb_set_par,
+ .fb_blank = jzfb_blank,
+ .fb_fillrect = sys_fillrect,
+ .fb_copyarea = sys_copyarea,
+ .fb_imageblit = sys_imageblit,
+ .fb_setcolreg = jzfb_setcolreg,
+};
+
+static int __devinit jzfb_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jzfb *jzfb;
+ struct fb_info *fb;
+ struct jz4740_fb_platform_data *pdata = pdev->dev.platform_data;
+ struct resource *mem;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "Missing platform data\n");
+ return -ENOENT;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to get register memory resource\n");
+ return -ENOENT;
+ }
+
+ mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to request register memory region\n");
+ return -EBUSY;
+ }
+
+ fb = framebuffer_alloc(sizeof(struct jzfb), &pdev->dev);
+ if (!fb) {
+ dev_err(&pdev->dev, "Failed to allocate framebuffer device\n");
+ ret = -ENOMEM;
+ goto err_release_mem_region;
+ }
+
+ fb->fbops = &jzfb_ops;
+ fb->flags = FBINFO_DEFAULT;
+
+ jzfb = fb->par;
+ jzfb->pdev = pdev;
+ jzfb->pdata = pdata;
+ jzfb->mem = mem;
+
+ jzfb->ldclk = clk_get(&pdev->dev, "lcd");
+ if (IS_ERR(jzfb->ldclk)) {
+ ret = PTR_ERR(jzfb->ldclk);
+ dev_err(&pdev->dev, "Failed to get lcd clock: %d\n", ret);
+ goto err_framebuffer_release;
+ }
+
+ jzfb->lpclk = clk_get(&pdev->dev, "lcd_pclk");
+ if (IS_ERR(jzfb->lpclk)) {
+ ret = PTR_ERR(jzfb->lpclk);
+ dev_err(&pdev->dev, "Failed to get lcd pixel clock: %d\n", ret);
+ goto err_put_ldclk;
+ }
+
+ jzfb->base = ioremap(mem->start, resource_size(mem));
+ if (!jzfb->base) {
+ dev_err(&pdev->dev, "Failed to ioremap register memory region\n");
+ ret = -EBUSY;
+ goto err_put_lpclk;
+ }
+
+ platform_set_drvdata(pdev, jzfb);
+
+ mutex_init(&jzfb->lock);
+
+ fb_videomode_to_modelist(pdata->modes, pdata->num_modes,
+ &fb->modelist);
+ fb_videomode_to_var(&fb->var, pdata->modes);
+ fb->var.bits_per_pixel = pdata->bpp;
+ jzfb_check_var(&fb->var, fb);
+
+ ret = jzfb_alloc_devmem(jzfb);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to allocate video memory\n");
+ goto err_iounmap;
+ }
+
+ fb->fix = jzfb_fix;
+ fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8;
+ fb->fix.mmio_start = mem->start;
+ fb->fix.mmio_len = resource_size(mem);
+ fb->fix.smem_start = jzfb->vidmem_phys;
+ fb->fix.smem_len = fb->fix.line_length * fb->var.yres;
+ fb->screen_base = jzfb->vidmem;
+ fb->pseudo_palette = jzfb->pseudo_palette;
+
+ fb_alloc_cmap(&fb->cmap, 256, 0);
+
+ clk_enable(jzfb->ldclk);
+ jzfb->is_enabled = 1;
+
+ writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
+
+ fb->mode = NULL;
+ jzfb_set_par(fb);
+
+ jz_gpio_bulk_request(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_request(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ ret = register_framebuffer(fb);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret);
+ goto err_free_devmem;
+ }
+
+ jzfb->fb = fb;
+
+ return 0;
+
+err_free_devmem:
+ jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ fb_dealloc_cmap(&fb->cmap);
+ jzfb_free_devmem(jzfb);
+err_iounmap:
+ iounmap(jzfb->base);
+err_put_lpclk:
+ clk_put(jzfb->lpclk);
+err_put_ldclk:
+ clk_put(jzfb->ldclk);
+err_framebuffer_release:
+ framebuffer_release(fb);
+err_release_mem_region:
+ release_mem_region(mem->start, resource_size(mem));
+ return ret;
+}
+
+static int __devexit jzfb_remove(struct platform_device *pdev)
+{
+ struct jzfb *jzfb = platform_get_drvdata(pdev);
+
+ jzfb_blank(FB_BLANK_POWERDOWN, jzfb->fb);
+
+ jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ iounmap(jzfb->base);
+ release_mem_region(jzfb->mem->start, resource_size(jzfb->mem));
+
+ fb_dealloc_cmap(&jzfb->fb->cmap);
+ jzfb_free_devmem(jzfb);
+
+ platform_set_drvdata(pdev, NULL);
+
+ clk_put(jzfb->lpclk);
+ clk_put(jzfb->ldclk);
+
+ framebuffer_release(jzfb->fb);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int jzfb_suspend(struct device *dev)
+{
+ struct jzfb *jzfb = dev_get_drvdata(dev);
+
+ acquire_console_sem();
+ fb_set_suspend(jzfb->fb, 1);
+ release_console_sem();
+
+ mutex_lock(&jzfb->lock);
+ if (jzfb->is_enabled)
+ jzfb_disable(jzfb);
+ mutex_unlock(&jzfb->lock);
+
+ return 0;
+}
+
+static int jzfb_resume(struct device *dev)
+{
+ struct jzfb *jzfb = dev_get_drvdata(dev);
+ clk_enable(jzfb->ldclk);
+
+ mutex_lock(&jzfb->lock);
+ if (jzfb->is_enabled)
+ jzfb_enable(jzfb);
+ mutex_unlock(&jzfb->lock);
+
+ acquire_console_sem();
+ fb_set_suspend(jzfb->fb, 0);
+ release_console_sem();
+
+ return 0;
+}
+
+static const struct dev_pm_ops jzfb_pm_ops = {
+ .suspend = jzfb_suspend,
+ .resume = jzfb_resume,
+ .poweroff = jzfb_suspend,
+ .restore = jzfb_resume,
+};
+
+#define JZFB_PM_OPS (&jzfb_pm_ops)
+
+#else
+#define JZFB_PM_OPS NULL
+#endif
+
+static struct platform_driver jzfb_driver = {
+ .probe = jzfb_probe,
+ .remove = __devexit_p(jzfb_remove),
+ .driver = {
+ .name = "jz4740-fb",
+ .pm = JZFB_PM_OPS,
+ },
+};
+
+static int __init jzfb_init(void)
+{
+ return platform_driver_register(&jzfb_driver);
+}
+module_init(jzfb_init);
+
+static void __exit jzfb_exit(void)
+{
+ platform_driver_unregister(&jzfb_driver);
+}
+module_exit(jzfb_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver");
+MODULE_ALIAS("platform:jz4740-fb");
diff --git a/include/linux/jz4740_fb.h b/include/linux/jz4740_fb.h
new file mode 100644
index 0000000..ab4c963
--- /dev/null
+++ b/include/linux/jz4740_fb.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __LINUX_JZ4740_FB_H
+#define __LINUX_JZ4740_FB_H
+
+#include <linux/fb.h>
+
+enum jz4740_fb_lcd_type {
+ JZ_LCD_TYPE_GENERIC_16_BIT = 0,
+ JZ_LCD_TYPE_GENERIC_18_BIT = 0 | (1 << 4),
+ JZ_LCD_TYPE_SPECIAL_TFT_1 = 1,
+ JZ_LCD_TYPE_SPECIAL_TFT_2 = 2,
+ JZ_LCD_TYPE_SPECIAL_TFT_3 = 3,
+ JZ_LCD_TYPE_NON_INTERLACED_CCIR656 = 5,
+ JZ_LCD_TYPE_INTERLACED_CCIR656 = 7,
+ JZ_LCD_TYPE_SINGLE_COLOR_STN = 8,
+ JZ_LCD_TYPE_SINGLE_MONOCHROME_STN = 9,
+ JZ_LCD_TYPE_DUAL_COLOR_STN = 10,
+ JZ_LCD_TYPE_DUAL_MONOCHROME_STN = 11,
+ JZ_LCD_TYPE_8BIT_SERIAL = 12,
+};
+
+/*
+* width: width of the lcd display in mm
+* height: height of the lcd display in mm
+* num_modes: size of modes
+* modes: list of valid video modes
+* bpp: bits per pixel for the lcd
+* lcd_type: lcd type
+*/
+
+struct jz4740_fb_platform_data {
+ unsigned int width;
+ unsigned int height;
+
+ size_t num_modes;
+ struct fb_videomode *modes;
+
+ unsigned int bpp;
+ enum jz4740_fb_lcd_type lcd_type;
+
+ unsigned pixclk_falling_edge:1;
+ unsigned date_enable_active_low:1;
+};
+
+#endif
--
1.5.6.5

2010-06-19 05:10:35

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

This patch adds support for the RTC unit on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Alessandro Zummo <[email protected]>
Cc: Paul Gortmaker <[email protected]>
Cc: Wan ZongShun <[email protected]>
Cc: Marek Vasut <[email protected]>
Cc: [email protected]

---
Changes since v1
- Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
- Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
- Check whether rtc structure could be allocated
- Fix deadlocks which could occur if the HW was broken
---
drivers/rtc/Kconfig | 11 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-jz4740.c | 341 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 353 insertions(+), 0 deletions(-)
create mode 100644 drivers/rtc/rtc-jz4740.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 10ba12c..d0ed7e6 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
This driver can also be built as a module. If so, the module
will be called rtc-mpc5121.

+config RTC_DRV_JZ4740
+ tristate "Ingenic JZ4740 SoC"
+ depends on RTC_CLASS
+ depends on MACH_JZ4740
+ help
+ If you say yes here you get support for the Ingenic JZ4740 SoC RTC
+ controller.
+
+ This driver can also be buillt as a module. If so, the module
+ will be called rtc-jz4740.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 5adbba7..fedf9bb 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
+obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
new file mode 100644
index 0000000..720afb2
--- /dev/null
+++ b/drivers/rtc/rtc-jz4740.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC RTC 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define JZ_REG_RTC_CTRL 0x00
+#define JZ_REG_RTC_SEC 0x04
+#define JZ_REG_RTC_SEC_ALARM 0x08
+#define JZ_REG_RTC_REGULATOR 0x0C
+#define JZ_REG_RTC_HIBERNATE 0x20
+#define JZ_REG_RTC_SCRATCHPAD 0x34
+
+#define JZ_RTC_CTRL_WRDY BIT(7)
+#define JZ_RTC_CTRL_1HZ BIT(6)
+#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
+#define JZ_RTC_CTRL_AF BIT(4)
+#define JZ_RTC_CTRL_AF_IRQ BIT(3)
+#define JZ_RTC_CTRL_AE BIT(2)
+#define JZ_RTC_CTRL_ENABLE BIT(0)
+
+struct jz4740_rtc {
+ struct resource *mem;
+ void __iomem *base;
+
+ struct rtc_device *rtc;
+
+ unsigned int irq;
+
+ spinlock_t lock;
+};
+
+static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t reg)
+{
+ return readl(rtc->base + reg);
+}
+
+static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
+{
+ uint32_t ctrl;
+ int timeout = 1000;
+
+ do {
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+ } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
+}
+
+static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t reg,
+ uint32_t val)
+{
+ jz4740_rtc_wait_write_ready(rtc);
+ writel(val, rtc->base + reg);
+}
+
+static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t mask,
+ uint32_t val)
+{
+ unsigned long flags;
+ uint32_t ctrl;
+
+ spin_lock_irqsave(&rtc->lock, flags);
+
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+
+ /* Don't clear interrupt flags by accident */
+ ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
+
+ ctrl &= ~mask;
+ ctrl |= val;
+
+ jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
+
+ spin_unlock_irqrestore(&rtc->lock, flags);
+}
+
+static int jz4740_rtc_read_time(struct device *dev, struct rtc_time *time)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ uint32_t secs, secs2;
+ int timeout = 5;
+
+ /* If the seconds register is read while it is updated, it can contain a
+ * bogus value. This can be avoided by making sure that two consecutive
+ * reads have the same value.
+ */
+ secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
+ secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
+
+ while (secs != secs2 && --timeout) {
+ secs = secs2;
+ secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
+ }
+
+ if (timeout == 0)
+ return -EIO;
+
+ rtc_time_to_tm(secs, time);
+
+ return rtc_valid_tm(time);
+}
+
+static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+
+ if ((uint32_t)secs != secs)
+ return -EINVAL;
+
+ jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
+
+ return 0;
+}
+
+static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ uint32_t secs;
+ uint32_t ctrl;
+
+ secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
+
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+
+ alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
+ alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
+
+ rtc_time_to_tm(secs, &alrm->time);
+
+ return rtc_valid_tm(&alrm->time);
+}
+
+static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ unsigned long secs;
+
+ rtc_tm_to_time(&alrm->time, &secs);
+
+ if ((uint32_t)secs != secs)
+ return -EINVAL;
+
+ jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, (uint32_t)secs);
+ jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
+ alrm->enabled ? JZ_RTC_CTRL_AE : 0);
+
+ return 0;
+}
+
+static inline int jz4740_irq_enable(struct device *dev, int irq,
+ unsigned int enable)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
+
+ return 0;
+}
+
+static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned int enable)
+{
+ return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
+}
+
+static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+ return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
+}
+
+static struct rtc_class_ops jz4740_rtc_ops = {
+ .read_time = jz4740_rtc_read_time,
+ .set_mmss = jz4740_rtc_set_mmss,
+ .read_alarm = jz4740_rtc_read_alarm,
+ .set_alarm = jz4740_rtc_set_alarm,
+ .update_irq_enable = jz4740_rtc_update_irq_enable,
+ .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t jz4740_rtc_irq(int irq, void *data)
+{
+ struct jz4740_rtc *rtc = data;
+ uint32_t ctrl;
+ unsigned long events = 0;
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+
+ if (ctrl & JZ_RTC_CTRL_1HZ)
+ events |= (RTC_UF | RTC_IRQF);
+
+ if (ctrl & JZ_RTC_CTRL_AF)
+ events |= (RTC_AF | RTC_IRQF);
+
+ rtc_update_irq(rtc->rtc, 1, events);
+
+ jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, 0);
+
+ return IRQ_HANDLED;
+}
+
+void jz4740_rtc_poweroff(struct device *dev)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
+}
+EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
+
+static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_rtc *rtc;
+ uint32_t scratchpad;
+
+ rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);
+ if (!rtc)
+ return -ENOMEM;
+
+ rtc->irq = platform_get_irq(pdev, 0);
+ if (rtc->irq < 0) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform irq\n");
+ goto err_free;
+ }
+
+ rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!rtc->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
+ goto err_free;
+ }
+
+ rtc->mem = request_mem_region(rtc->mem->start, resource_size(rtc->mem),
+ pdev->name);
+ if (!rtc->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
+ if (!rtc->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ spin_lock_init(&rtc->lock);
+
+ platform_set_drvdata(pdev, rtc);
+
+ rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &jz4740_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(rtc->rtc)) {
+ ret = PTR_ERR(rtc->rtc);
+ dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret);
+ goto err_iounmap;
+ }
+
+ ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
+ pdev->name, rtc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret);
+ goto err_unregister_rtc;
+ }
+
+ scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
+ if (scratchpad != 0x12345678) {
+ jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
+ jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
+ }
+
+ return 0;
+
+err_unregister_rtc:
+ rtc_device_unregister(rtc->rtc);
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(rtc->base);
+err_release_mem_region:
+ release_mem_region(rtc->mem->start, resource_size(rtc->mem));
+err_free:
+ kfree(rtc);
+
+ return ret;
+}
+
+static int __devexit jz4740_rtc_remove(struct platform_device *pdev)
+{
+ struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
+
+ free_irq(rtc->irq, rtc);
+
+ rtc_device_unregister(rtc->rtc);
+
+ iounmap(rtc->base);
+ release_mem_region(rtc->mem->start, resource_size(rtc->mem));
+
+ kfree(rtc);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+struct platform_driver jz4740_rtc_driver = {
+ .probe = jz4740_rtc_probe,
+ .remove = __devexit_p(jz4740_rtc_remove),
+ .driver = {
+ .name = "jz4740-rtc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_rtc_init(void)
+{
+ return platform_driver_register(&jz4740_rtc_driver);
+}
+module_init(jz4740_rtc_init);
+
+static void __exit jz4740_rtc_exit(void)
+{
+ platform_driver_unregister(&jz4740_rtc_driver);
+}
+module_exit(jz4740_rtc_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
+MODULE_ALIAS("platform:jz4740-rtc");
--
1.5.6.5

2010-06-19 05:11:10

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 20/26] alsa: ASoC: Add JZ4740 codec driver

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: [email protected]

---
Changes since v1
- Put Kconfig entry in alphabetic order
- Drop codec_set_fmt since the codec supports only one format
- Rename "Capture Volume" control to "Master Capture Volume"
- Drop unnecessary format checks
- Add suspend/resume
- Cleanup jz4740_codec_set_bias_level
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/jz4740-codec.c | 514 +++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/jz4740-codec.h | 20 ++
4 files changed, 540 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/jz4740-codec.c
create mode 100644 sound/soc/codecs/jz4740-codec.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c37c844..00d347d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -24,6 +24,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS42L51 if I2C
select SND_SOC_CS4270 if I2C
+ select SND_SOC_JZ4740 if SOC_JZ4740
select SND_SOC_MAX9877 if I2C
select SND_SOC_DA7210 if I2C
select SND_SOC_PCM3008
@@ -142,6 +143,9 @@ config SND_SOC_CS4270_VD33_ERRATA
config SND_SOC_CX20442
tristate

+config SND_SOC_JZ4740_CODEC
+ tristate
+
config SND_SOC_L3
tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4a9c205..301b131 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-jz4740-codec-objs := jz4740-codec.o

# Amp
snd-soc-max9877-objs := max9877.o
@@ -80,6 +81,7 @@ obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
+obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/jz4740-codec.c b/sound/soc/codecs/jz4740-codec.c
new file mode 100644
index 0000000..35848b6
--- /dev/null
+++ b/sound/soc/codecs/jz4740-codec.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc.h>
+
+#define JZ4740_REG_CODEC_1 0x0
+#define JZ4740_REG_CODEC_2 0x1
+
+#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
+#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
+#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
+#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
+#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
+#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
+#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
+#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
+#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
+#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
+#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
+#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
+#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
+#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
+#define JZ4740_CODEC_1_SUSPEND BIT(1)
+#define JZ4740_CODEC_1_RESET BIT(0)
+
+#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
+#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
+#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
+#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
+#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
+#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000
+#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16
+#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0
+
+static const uint32_t jz4740_codec_regs[] = {
+ 0x021b2302, 0x00170803,
+};
+
+struct jz4740_codec {
+ void __iomem *base;
+ struct resource *mem;
+
+ uint32_t reg_cache[2];
+ struct snd_soc_codec codec;
+};
+
+static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec)
+{
+ return container_of(codec, struct jz4740_codec, codec);
+}
+
+static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+ return readl(jz4740_codec->base + (reg << 2));
+}
+
+static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+
+ jz4740_codec->reg_cache[reg] = val;
+ writel(val, jz4740_codec->base + (reg << 2));
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new jz4740_codec_controls[] = {
+ SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
+ SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
+ SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
+ SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
+ SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
+ SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
+ SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
+ SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
+
+ SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
+ jz4740_codec_output_controls,
+ ARRAY_SIZE(jz4740_codec_output_controls)),
+
+ SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
+ jz4740_codec_input_controls,
+ ARRAY_SIZE(jz4740_codec_input_controls)),
+ SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+
+ SND_SOC_DAPM_INPUT("MIC"),
+ SND_SOC_DAPM_INPUT("LIN"),
+ SND_SOC_DAPM_INPUT("RIN"),
+};
+
+static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
+ {"Line Input", NULL, "LIN"},
+ {"Line Input", NULL, "RIN"},
+
+ {"Input Mixer", "Line Capture Switch", "Line Input"},
+ {"Input Mixer", "Mic Capture Switch", "MIC"},
+
+ {"ADC", NULL, "Input Mixer"},
+
+ {"Output Mixer", "Bypass Switch", "Input Mixer"},
+ {"Output Mixer", "DAC Switch", "DAC"},
+
+ {"LOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+};
+
+static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ uint32_t val;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ switch (params_rate(params)) {
+ case 8000:
+ val = 0;
+ break;
+ case 11025:
+ val = 1;
+ break;
+ case 12000:
+ val = 2;
+ break;
+ case 16000:
+ val = 3;
+ break;
+ case 22050:
+ val = 4;
+ break;
+ case 24000:
+ val = 5;
+ break;
+ case 32000:
+ val = 6;
+ break;
+ case 44100:
+ val = 7;
+ break;
+ case 48000:
+ val = 8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
+ .hw_params = jz4740_codec_hw_params,
+};
+
+struct snd_soc_dai jz4740_codec_dai = {
+ .name = "jz4740",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .ops = &jz4740_codec_dai_ops,
+ .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(jz4740_codec_dai);
+
+static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
+{
+ int i;
+ uint32_t *cache = codec->reg_cache;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
+ udelay(2);
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
+ jz4740_codec_write(codec, i, cache[i]);
+}
+
+static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ unsigned int mask;
+ unsigned int value;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = 0;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* The only way to clear the suspend flag is to reset the codec */
+ if (codec->bias_level == SND_SOC_BIAS_OFF)
+ jz4740_codec_wakeup(codec);
+
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_OFF:
+ mask = JZ4740_CODEC_1_SUSPEND;
+ value = JZ4740_CODEC_1_SUSPEND;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ default:
+ break;
+ }
+
+ codec->bias_level = level;
+
+ return 0;
+}
+
+static struct snd_soc_codec *jz4740_codec_codec;
+
+static int jz4740_codec_dev_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = jz4740_codec_codec;
+
+ BUG_ON(!codec);
+
+ socdev->card->codec = codec;
+
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_add_controls(codec, jz4740_codec_controls,
+ ARRAY_SIZE(jz4740_codec_controls));
+
+ snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets,
+ ARRAY_SIZE(jz4740_codec_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes,
+ ARRAY_SIZE(jz4740_codec_dapm_routes));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+static int jz4740_codec_dev_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_jz4740_codec = {
+ .probe = jz4740_codec_dev_probe,
+ .remove = jz4740_codec_dev_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec);
+
+#ifdef CONFIG_PM_SLEEP
+
+static int jz4740_codec_suspend(struct device *dev)
+{
+ struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev);
+ return jz4740_codec_set_bias_level(&jz4740_codec->codec,
+ SND_SOC_BIAS_OFF);
+}
+
+static int jz4740_codec_resume(struct device *dev)
+{
+ struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev);
+ return jz4740_codec_set_bias_level(&jz4740_codec->codec,
+ SND_SOC_BIAS_STANDBY);
+}
+
+static const struct dev_pm_ops jz4740_pm_ops = {
+ .suspend = jz4740_codec_suspend,
+ .resume = jz4740_codec_resume,
+};
+
+#define JZ4740_CODEC_PM_OPS (&jz4740_pm_ops)
+
+#else
+#define JZ4740_CODEC_PM_OPS NULL
+#endif
+
+static int __devinit jz4740_codec_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_codec *jz4740_codec;
+ struct snd_soc_codec *codec;
+ struct resource *mem;
+
+ jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
+ if (!jz4740_codec)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
+ ret = -ENOENT;
+ goto err_free_codec;
+ }
+
+ mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ ret = -EBUSY;
+ goto err_free_codec;
+ }
+
+ jz4740_codec->base = ioremap(mem->start, resource_size(mem));
+ if (!jz4740_codec->base) {
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ ret = -EBUSY;
+ goto err_release_mem_region;
+ }
+ jz4740_codec->mem = mem;
+
+ jz4740_codec_dai.dev = &pdev->dev;
+
+ codec = &jz4740_codec->codec;
+
+ codec->dev = &pdev->dev;
+ codec->name = "jz4740";
+ codec->owner = THIS_MODULE;
+
+ codec->read = jz4740_codec_read;
+ codec->write = jz4740_codec_write;
+ codec->set_bias_level = jz4740_codec_set_bias_level;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+
+ codec->dai = &jz4740_codec_dai;
+ codec->num_dai = 1;
+
+ codec->reg_cache = jz4740_codec->reg_cache;
+ codec->reg_cache_size = 2;
+ memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs));
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ jz4740_codec_codec = codec;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
+
+ platform_set_drvdata(pdev, jz4740_codec);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec\n");
+ goto err_iounmap;
+ }
+
+ ret = snd_soc_register_dai(&jz4740_codec_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec dai\n");
+ goto err_unregister_codec;
+ }
+
+ jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+
+err_unregister_codec:
+ snd_soc_unregister_codec(codec);
+err_iounmap:
+ iounmap(jz4740_codec->base);
+err_release_mem_region:
+ release_mem_region(mem->start, resource_size(mem));
+err_free_codec:
+ kfree(jz4740_codec);
+
+ return ret;
+}
+
+static int __devexit jz4740_codec_remove(struct platform_device *pdev)
+{
+ struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
+ struct resource *mem = jz4740_codec->mem;
+
+ snd_soc_unregister_dai(&jz4740_codec_dai);
+ snd_soc_unregister_codec(&jz4740_codec->codec);
+
+ iounmap(jz4740_codec->base);
+ release_mem_region(mem->start, resource_size(mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(jz4740_codec);
+
+ return 0;
+}
+
+static struct platform_driver jz4740_codec_driver = {
+ .probe = jz4740_codec_probe,
+ .remove = __devexit_p(jz4740_codec_remove),
+ .driver = {
+ .name = "jz4740-codec",
+ .owner = THIS_MODULE,
+ .pm = JZ4740_CODEC_PM_OPS,
+ },
+};
+
+static int __init jz4740_codec_init(void)
+{
+ return platform_driver_register(&jz4740_codec_driver);
+}
+module_init(jz4740_codec_init);
+
+static void __exit jz4740_codec_exit(void)
+{
+ platform_driver_unregister(&jz4740_codec_driver);
+}
+module_exit(jz4740_codec_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:jz4740-codec");
diff --git a/sound/soc/codecs/jz4740-codec.h b/sound/soc/codecs/jz4740-codec.h
new file mode 100644
index 0000000..b5a0691
--- /dev/null
+++ b/sound/soc/codecs/jz4740-codec.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__
+#define __SND_SOC_CODECS_JZ4740_CODEC_H__
+
+extern struct snd_soc_dai jz4740_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec;
+
+#endif
--
1.5.6.5

2010-06-19 05:10:49

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver

This patch adds support for the NAND controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: [email protected]

---
Changes since v1
- JZ4740: Remove debug macro
- Fix platform driver remove callback
- Add custom nand read/write callback since we need to support more then 64 ecc
bytes
---
drivers/mtd/nand/Kconfig | 6 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/jz4740_nand.c | 474 +++++++++++++++++++++++++++++++++++++++
include/linux/mtd/jz4740_nand.h | 34 +++
4 files changed, 515 insertions(+), 0 deletions(-)
create mode 100644 drivers/mtd/nand/jz4740_nand.c
create mode 100644 include/linux/mtd/jz4740_nand.h

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index ffc3720..362d177 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -526,4 +526,10 @@ config MTD_NAND_NUC900
This enables the driver for the NAND Flash on evaluation board based
on w90p910 / NUC9xx.

+config MTD_NAND_JZ4740
+ tristate "Support for JZ4740 SoC NAND controller"
+ depends on MACH_JZ4740
+ help
+ Enables support for NAND Flash on JZ4740 SoC based boards.
+
endif # MTD_NAND
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index e8ab884..ac83dcd 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -46,5 +46,6 @@ obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o
obj-$(CONFIG_MTD_NAND_BCM_UMI) += bcm_umi_nand.o nand_bcm_umi.o
obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
+obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o

nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/jz4740_nand.c b/drivers/mtd/nand/jz4740_nand.c
new file mode 100644
index 0000000..8c55f8a
--- /dev/null
+++ b/drivers/mtd/nand/jz4740_nand.c
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC NAND 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/ioport.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+
+#include <linux/mtd/jz4740_nand.h>
+#include <linux/gpio.h>
+
+#define JZ_REG_NAND_CTRL 0x50
+#define JZ_REG_NAND_ECC_CTRL 0x100
+#define JZ_REG_NAND_DATA 0x104
+#define JZ_REG_NAND_PAR0 0x108
+#define JZ_REG_NAND_PAR1 0x10C
+#define JZ_REG_NAND_PAR2 0x110
+#define JZ_REG_NAND_IRQ_STAT 0x114
+#define JZ_REG_NAND_IRQ_CTRL 0x118
+#define JZ_REG_NAND_ERR(x) (0x11C + (x << 2))
+
+#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4)
+#define JZ_NAND_ECC_CTRL_ENCODING BIT(3)
+#define JZ_NAND_ECC_CTRL_RS BIT(2)
+#define JZ_NAND_ECC_CTRL_RESET BIT(1)
+#define JZ_NAND_ECC_CTRL_ENABLE BIT(0)
+
+#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29))
+#define JZ_NAND_STATUS_PAD_FINISH BIT(4)
+#define JZ_NAND_STATUS_DEC_FINISH BIT(3)
+#define JZ_NAND_STATUS_ENC_FINISH BIT(2)
+#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1)
+#define JZ_NAND_STATUS_ERROR BIT(0)
+
+#define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT(x << 1)
+#define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT((x << 1) + 1)
+
+#define JZ_NAND_DATA_ADDR ((void __iomem *)0xB8000000)
+#define JZ_NAND_CMD_ADDR (JZ_NAND_DATA_ADDR + 0x8000)
+#define JZ_NAND_ADDR_ADDR (JZ_NAND_DATA_ADDR + 0x10000)
+
+struct jz_nand {
+ struct mtd_info mtd;
+ struct nand_chip chip;
+ void __iomem *base;
+ struct resource *mem;
+
+ struct jz_nand_platform_data *pdata;
+ bool is_reading;
+};
+
+static inline struct jz_nand *mtd_to_jz_nand(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct jz_nand, mtd);
+}
+
+static void jz_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ struct nand_chip *chip = mtd->priv;
+ uint32_t reg;
+
+ if (ctrl & NAND_CTRL_CHANGE) {
+ BUG_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE));
+ if (ctrl & NAND_ALE)
+ chip->IO_ADDR_W = JZ_NAND_ADDR_ADDR;
+ else if (ctrl & NAND_CLE)
+ chip->IO_ADDR_W = JZ_NAND_CMD_ADDR;
+ else
+ chip->IO_ADDR_W = JZ_NAND_DATA_ADDR;
+
+ reg = readl(nand->base + JZ_REG_NAND_CTRL);
+ if (ctrl & NAND_NCE)
+ reg |= JZ_NAND_CTRL_ASSERT_CHIP(0);
+ else
+ reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(0);
+ writel(reg, nand->base + JZ_REG_NAND_CTRL);
+ }
+ if (dat != NAND_CMD_NONE)
+ writeb(dat, chip->IO_ADDR_W);
+}
+
+static int jz_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ return gpio_get_value_cansleep(nand->pdata->busy_gpio);
+}
+
+static void jz_nand_hwctl(struct mtd_info *mtd, int mode)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ uint32_t reg;
+
+ writel(0, nand->base + JZ_REG_NAND_IRQ_STAT);
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ reg |= JZ_NAND_ECC_CTRL_RESET;
+ reg |= JZ_NAND_ECC_CTRL_ENABLE;
+ reg |= JZ_NAND_ECC_CTRL_RS;
+
+ switch (mode) {
+ case NAND_ECC_READ:
+ reg &= ~JZ_NAND_ECC_CTRL_ENCODING;
+ nand->is_reading = true;
+ break;
+ case NAND_ECC_WRITE:
+ reg |= JZ_NAND_ECC_CTRL_ENCODING;
+ nand->is_reading = false;
+ break;
+ default:
+ break;
+ }
+
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+}
+
+static int jz_nand_calculate_ecc_rs(struct mtd_info *mtd, const uint8_t *dat,
+ uint8_t *ecc_code)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ uint32_t reg, status;
+ int i;
+ static uint8_t empty_block_ecc[] = {0xcd, 0x9d, 0x90, 0x58, 0xf4,
+ 0x8b, 0xff, 0xb7, 0x6f};
+
+ if (nand->is_reading)
+ return 0;
+
+ do {
+ status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
+ } while (!(status & JZ_NAND_STATUS_ENC_FINISH));
+
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+ reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ for (i = 0; i < 9; ++i)
+ ecc_code[i] = readb(nand->base + JZ_REG_NAND_PAR0 + i);
+
+ /* If the written data is completly 0xff, we also want to write 0xff as
+ * ecc, otherwise we will get in trouble when doing subpage writes. */
+ if (memcmp(ecc_code, empty_block_ecc, 9) == 0)
+ memset(ecc_code, 0xff, 9);
+
+ return 0;
+}
+
+static void correct_data(uint8_t *dat, int index, int mask)
+{
+ int offset = index & 0x7;
+ uint16_t data;
+
+ index += (index >> 3);
+
+ data = dat[index];
+ data |= dat[index+1] << 8;
+
+ mask ^= (data >> offset) & 0x1ff;
+ data &= ~(0x1ff << offset);
+ data |= (mask << offset);
+
+ dat[index] = data & 0xff;
+ dat[index+1] = (data >> 8) & 0xff;
+}
+
+static int jz_nand_correct_ecc_rs(struct mtd_info *mtd, uint8_t *dat,
+ uint8_t *read_ecc, uint8_t *calc_ecc)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ int i, error_count, index;
+ uint32_t reg, status, error;
+ uint32_t t;
+
+ t = read_ecc[0];
+
+ if (t == 0xff) {
+ for (i = 1; i < 9; ++i)
+ t &= read_ecc[i];
+
+ t &= dat[0];
+ t &= dat[nand->chip.ecc.size / 2];
+ t &= dat[nand->chip.ecc.size - 1];
+
+ if (t == 0xff) {
+ for (i = 1; i < nand->chip.ecc.size - 1; ++i)
+ t &= dat[i];
+ if (t == 0xff)
+ return 0;
+ }
+ }
+
+ for (i = 0; i < 9; ++i)
+ writeb(read_ecc[i], nand->base + JZ_REG_NAND_PAR0 + i);
+
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+ reg |= JZ_NAND_ECC_CTRL_PAR_READY;
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ do {
+ status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
+ } while (!(status & JZ_NAND_STATUS_DEC_FINISH));
+
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+ reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ if (status & JZ_NAND_STATUS_ERROR) {
+ if (status & JZ_NAND_STATUS_UNCOR_ERROR)
+ return -1;
+
+ error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29;
+
+ for (i = 0; i < error_count; ++i) {
+ error = readl(nand->base + JZ_REG_NAND_ERR(i));
+ index = ((error >> 16) & 0x1ff) - 1;
+ if (index >= 0 && index < 512)
+ correct_data(dat, index, error & 0x1ff);
+ }
+
+ return error_count;
+ }
+
+ return 0;
+}
+
+
+/* Copy paste of nand_read_page_hwecc_oob_first except for different eccpos
+ * handling. The ecc area is for 4k chips 72 bytes long and thus does not fit
+ * into the eccpos array. */
+static int jz_nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
+ struct nand_chip *chip, uint8_t *buf, int page)
+{
+ int i, eccsize = chip->ecc.size;
+ int eccbytes = chip->ecc.bytes;
+ int eccsteps = chip->ecc.steps;
+ uint8_t *p = buf;
+ unsigned int ecc_offset = chip->page_shift;
+
+ /* Read the OOB area first */
+ chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+
+ for (i = ecc_offset; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
+ int stat;
+
+ chip->ecc.hwctl(mtd, NAND_ECC_READ);
+ chip->read_buf(mtd, p, eccsize);
+
+ stat = chip->ecc.correct(mtd, p, &chip->oob_poi[i], NULL);
+ if (stat < 0)
+ mtd->ecc_stats.failed++;
+ else
+ mtd->ecc_stats.corrected += stat;
+ }
+ return 0;
+}
+
+/* Copy-and-paste of nand_write_page_hwecc with different eccpos handling. */
+static void jz_nand_write_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, const uint8_t *buf)
+{
+ int i, eccsize = chip->ecc.size;
+ int eccbytes = chip->ecc.bytes;
+ int eccsteps = chip->ecc.steps;
+ const uint8_t *p = buf;
+ unsigned int ecc_offset = chip->page_shift;
+
+ for (i = ecc_offset; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
+ chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
+ chip->write_buf(mtd, p, eccsize);
+ chip->ecc.calculate(mtd, p, &chip->oob_poi[i]);
+ }
+
+ chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+}
+
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+static const char *part_probes[] = {"cmdline", NULL};
+#endif
+
+static int __devinit jz_nand_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz_nand *nand;
+ struct nand_chip *chip;
+ struct mtd_info *mtd;
+ struct jz_nand_platform_data *pdata = pdev->dev.platform_data;
+#ifdef CONFIG_MTD_PARTITIONS
+ struct mtd_partition *partition_info;
+ int num_partitions = 0;
+#endif
+
+ nand = kzalloc(sizeof(*nand), GFP_KERNEL);
+ if (!nand) {
+ dev_err(&pdev->dev, "Failed to allocate device structure.\n");
+ return -ENOMEM;
+ }
+
+ nand->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!nand->mem) {
+ dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
+ ret = -ENOENT;
+ goto err_free;
+ }
+
+ nand->mem = request_mem_region(nand->mem->start,
+ resource_size(nand->mem), pdev->name);
+ if (!nand->mem) {
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ ret = -EBUSY;
+ goto err_free;
+ }
+
+ nand->base = ioremap(nand->mem->start, resource_size(nand->mem));
+ if (!nand->base) {
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory region\n");
+ ret = -EBUSY;
+ goto err_release_mem;
+ }
+
+ if (pdata && gpio_is_valid(pdata->busy_gpio)) {
+ ret = gpio_request(pdata->busy_gpio, "NAND busy pin");
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to request busy gpio %d: %d\n",
+ pdata->busy_gpio, ret);
+ goto err_iounmap;
+ }
+ }
+
+ mtd = &nand->mtd;
+ chip = &nand->chip;
+ mtd->priv = chip;
+ mtd->owner = THIS_MODULE;
+ mtd->name = "jz4740-nand";
+
+ chip->ecc.hwctl = jz_nand_hwctl;
+ chip->ecc.calculate = jz_nand_calculate_ecc_rs;
+ chip->ecc.correct = jz_nand_correct_ecc_rs;
+ chip->ecc.mode = NAND_ECC_HW_OOB_FIRST;
+ chip->ecc.size = 512;
+ chip->ecc.bytes = 9;
+
+ chip->ecc.read_page = jz_nand_read_page_hwecc_oob_first;
+ chip->ecc.write_page = jz_nand_write_page_hwecc;
+
+ if (pdata)
+ chip->ecc.layout = pdata->ecc_layout;
+
+ chip->chip_delay = 50;
+ chip->cmd_ctrl = jz_nand_cmd_ctrl;
+
+ if (pdata && gpio_is_valid(pdata->busy_gpio))
+ chip->dev_ready = jz_nand_dev_ready;
+
+ chip->IO_ADDR_R = JZ_NAND_DATA_ADDR;
+ chip->IO_ADDR_W = JZ_NAND_DATA_ADDR;
+
+ nand->pdata = pdata;
+ platform_set_drvdata(pdev, nand);
+
+ ret = nand_scan_ident(mtd, 1, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to scan nand\n");
+ goto err_gpio_free;
+ }
+
+ if (pdata && pdata->ident_callback) {
+ pdata->ident_callback(pdev, chip, &pdata->partitions,
+ &pdata->num_partitions);
+ }
+
+ ret = nand_scan_tail(mtd);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to scan nand\n");
+ goto err_gpio_free;
+ }
+
+#ifdef CONFIG_MTD_PARTITIONS
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+ num_partitions = parse_mtd_partitions(mtd, part_probes,
+ &partition_info, 0);
+#endif
+ if (num_partitions <= 0 && pdata) {
+ num_partitions = pdata->num_partitions;
+ partition_info = pdata->partitions;
+ }
+
+ if (num_partitions > 0)
+ ret = add_mtd_partitions(mtd, partition_info, num_partitions);
+ else
+#endif
+ ret = add_mtd_device(mtd);
+
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add mtd device\n");
+ goto err_nand_release;
+ }
+
+ dev_info(&pdev->dev, "Successfully registered JZ4740 NAND driver\n");
+
+ return 0;
+err_nand_release:
+ nand_release(&nand->mtd);
+err_gpio_free:
+ platform_set_drvdata(pdev, NULL);
+ gpio_free(pdata->busy_gpio);
+err_iounmap:
+ iounmap(nand->base);
+err_release_mem:
+ release_mem_region(nand->mem->start, resource_size(nand->mem));
+err_free:
+ kfree(nand);
+ return ret;
+}
+
+static int __devexit jz_nand_remove(struct platform_device *pdev)
+{
+ struct jz_nand *nand = platform_get_drvdata(pdev);
+
+ nand_release(&nand->mtd);
+
+ iounmap(nand->base);
+ release_mem_region(nand->mem->start, resource_size(nand->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(nand);
+
+ return 0;
+}
+
+struct platform_driver jz_nand_driver = {
+ .probe = jz_nand_probe,
+ .remove = __devexit_p(jz_nand_remove),
+ .driver = {
+ .name = "jz4740-nand",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz_nand_init(void)
+{
+ return platform_driver_register(&jz_nand_driver);
+}
+module_init(jz_nand_init);
+
+static void __exit jz_nand_exit(void)
+{
+ platform_driver_unregister(&jz_nand_driver);
+}
+module_exit(jz_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC");
+MODULE_ALIAS("platform:jz4740-nand");
diff --git a/include/linux/mtd/jz4740_nand.h b/include/linux/mtd/jz4740_nand.h
new file mode 100644
index 0000000..379f9b6
--- /dev/null
+++ b/include/linux/mtd/jz4740_nand.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC NAND 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.
+ *
+ */
+
+#ifndef __JZ_NAND_H__
+#define __JZ_NAND_H__
+
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+
+struct jz_nand_platform_data {
+ int num_partitions;
+ struct mtd_partition *partitions;
+
+ struct nand_ecclayout *ecc_layout;
+
+ unsigned int busy_gpio;
+
+ void (*ident_callback)(struct platform_device *, struct nand_chip *,
+ struct mtd_partition **, int *num_partitions);
+};
+
+#endif
--
1.5.6.5

2010-06-19 05:10:55

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver

This patch adds support for the mmc controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: [email protected]

---
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 <[email protected]>
+ * 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 <[email protected]>");
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

2010-06-19 05:11:01

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 19/26] USB: Add JZ4740 ohci support

This patch adds ohci glue code for JZ4740 SoCs ohci module.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Greg Kroah-Hartman <[email protected]>
Cc: David Brownell <[email protected]>
Cc: [email protected]

---
Changes since v1
- Do not request IRQ with IRQF_DISABLED, since it is a noop now
- Add copyright header
---
drivers/usb/Kconfig | 1 +
drivers/usb/host/ohci-hcd.c | 5 +
drivers/usb/host/ohci-jz4740.c | 276 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 282 insertions(+), 0 deletions(-)
create mode 100644 drivers/usb/host/ohci-jz4740.c

diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig
index 6a58cb1..39a6bfd 100644
--- a/drivers/usb/Kconfig
+++ b/drivers/usb/Kconfig
@@ -46,6 +46,7 @@ config USB_ARCH_HAS_OHCI
default y if PPC_MPC52xx
# MIPS:
default y if SOC_AU1X00
+ default y if MACH_JZ4740
# SH:
default y if CPU_SUBTYPE_SH7720
default y if CPU_SUBTYPE_SH7721
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index fc57655..05b071c 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -1095,6 +1095,11 @@ MODULE_LICENSE ("GPL");
#define TMIO_OHCI_DRIVER ohci_hcd_tmio_driver
#endif

+#ifdef CONFIG_MACH_JZ4740
+#include "ohci-jz4740.c"
+#define PLATFORM_DRIVER ohci_hcd_jz4740_driver
+#endif
+
#if !defined(PCI_DRIVER) && \
!defined(PLATFORM_DRIVER) && \
!defined(OMAP1_PLATFORM_DRIVER) && \
diff --git a/drivers/usb/host/ohci-jz4740.c b/drivers/usb/host/ohci-jz4740.c
new file mode 100644
index 0000000..10e1872
--- /dev/null
+++ b/drivers/usb/host/ohci-jz4740.c
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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/platform_device.h>
+#include <linux/clk.h>
+#include <linux/regulator/consumer.h>
+
+struct jz4740_ohci_hcd {
+ struct ohci_hcd ohci_hcd;
+
+ struct regulator *vbus;
+ bool vbus_enabled;
+ struct clk *clk;
+};
+
+static inline struct jz4740_ohci_hcd *hcd_to_jz4740_hcd(struct usb_hcd *hcd)
+{
+ return (struct jz4740_ohci_hcd *)(hcd->hcd_priv);
+}
+
+static inline struct usb_hcd *jz4740_hcd_to_hcd(struct jz4740_ohci_hcd *jz4740_ohci)
+{
+ return container_of((void *)jz4740_ohci, struct usb_hcd, hcd_priv);
+}
+
+static int ohci_jz4740_start(struct usb_hcd *hcd)
+{
+ struct ohci_hcd *ohci = hcd_to_ohci(hcd);
+ int ret;
+
+ ret = ohci_init(ohci);
+ if (ret < 0)
+ return ret;
+
+ ohci->num_ports = 1;
+
+ ret = ohci_run(ohci);
+ if (ret < 0) {
+ dev_err(hcd->self.controller, "Can not start %s",
+ hcd->self.bus_name);
+ ohci_stop(hcd);
+ return ret;
+ }
+ return 0;
+}
+
+static int ohci_jz4740_set_vbus_power(struct jz4740_ohci_hcd *jz4740_ohci,
+ bool enabled)
+{
+ int ret = 0;
+
+ if (!jz4740_ohci->vbus)
+ return 0;
+
+ if (enabled && !jz4740_ohci->vbus_enabled) {
+ ret = regulator_enable(jz4740_ohci->vbus);
+ if (ret)
+ dev_err(jz4740_hcd_to_hcd(jz4740_ohci)->self.controller,
+ "Could not power vbus\n");
+ } else if (!enabled && jz4740_ohci->vbus_enabled) {
+ ret = regulator_disable(jz4740_ohci->vbus);
+ }
+
+ if (ret == 0)
+ jz4740_ohci->vbus_enabled = enabled;
+
+ return ret;
+}
+
+static int ohci_jz4740_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
+ u16 wIndex, char *buf, u16 wLength)
+{
+ struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd);
+ int ret;
+
+ switch (typeReq) {
+ case SetHubFeature:
+ if (wValue == USB_PORT_FEAT_POWER)
+ ret = ohci_jz4740_set_vbus_power(jz4740_ohci, true);
+ break;
+ case ClearHubFeature:
+ if (wValue == USB_PORT_FEAT_POWER)
+ ret = ohci_jz4740_set_vbus_power(jz4740_ohci, false);
+ break;
+ }
+
+ if (ret)
+ return ret;
+
+ return ohci_hub_control(hcd, typeReq, wValue, wIndex, buf, wLength);
+}
+
+
+static const struct hc_driver ohci_jz4740_hc_driver = {
+ .description = hcd_name,
+ .product_desc = "JZ4740 OHCI",
+ .hcd_priv_size = sizeof(struct jz4740_ohci_hcd),
+
+ /*
+ * generic hardware linkage
+ */
+ .irq = ohci_irq,
+ .flags = HCD_USB11 | HCD_MEMORY,
+
+ /*
+ * basic lifecycle operations
+ */
+ .start = ohci_jz4740_start,
+ .stop = ohci_stop,
+ .shutdown = ohci_shutdown,
+
+ /*
+ * managing i/o requests and associated device resources
+ */
+ .urb_enqueue = ohci_urb_enqueue,
+ .urb_dequeue = ohci_urb_dequeue,
+ .endpoint_disable = ohci_endpoint_disable,
+
+ /*
+ * scheduling support
+ */
+ .get_frame_number = ohci_get_frame,
+
+ /*
+ * root hub support
+ */
+ .hub_status_data = ohci_hub_status_data,
+ .hub_control = ohci_jz4740_hub_control,
+#ifdef CONFIG_PM
+ .bus_suspend = ohci_bus_suspend,
+ .bus_resume = ohci_bus_resume,
+#endif
+ .start_port_reset = ohci_start_port_reset,
+};
+
+
+static __devinit int jz4740_ohci_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct usb_hcd *hcd;
+ struct jz4740_ohci_hcd *jz4740_ohci;
+ struct resource *res;
+ int irq;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to get platform resource\n");
+ return -ENOENT;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "Failed to get platform irq\n");
+ return irq;
+ }
+
+ hcd = usb_create_hcd(&ohci_jz4740_hc_driver, &pdev->dev, "jz4740");
+ if (!hcd) {
+ dev_err(&pdev->dev, "Failed to create hcd.\n");
+ return -ENOMEM;
+ }
+
+ jz4740_ohci = hcd_to_jz4740_hcd(hcd);
+
+ res = request_mem_region(res->start, resource_size(res), hcd_name);
+ if (!res) {
+ dev_err(&pdev->dev, "Failed to request mem region.\n");
+ ret = -EBUSY;
+ goto err_free;
+ }
+
+ hcd->rsrc_start = res->start;
+ hcd->rsrc_len = resource_size(res);
+ hcd->regs = ioremap(res->start, resource_size(res));
+
+ if (!hcd->regs) {
+ dev_err(&pdev->dev, "Failed to ioremap registers.\n");
+ ret = -EBUSY;
+ goto err_release_mem;
+ }
+
+ jz4740_ohci->clk = clk_get(&pdev->dev, "uhc");
+ if (IS_ERR(jz4740_ohci->clk)) {
+ ret = PTR_ERR(jz4740_ohci->clk);
+ dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
+ goto err_iounmap;
+ }
+
+ jz4740_ohci->vbus = regulator_get(&pdev->dev, "vbus");
+ if (IS_ERR(jz4740_ohci->vbus))
+ jz4740_ohci->vbus = NULL;
+
+
+ clk_set_rate(jz4740_ohci->clk, 48000000);
+ clk_enable(jz4740_ohci->clk);
+ if (jz4740_ohci->vbus)
+ ohci_jz4740_set_vbus_power(jz4740_ohci, true);
+
+ platform_set_drvdata(pdev, hcd);
+
+ ohci_hcd_init(hcd_to_ohci(hcd));
+
+ ret = usb_add_hcd(hcd, irq, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add hcd: %d\n", ret);
+ goto err_disable;
+ }
+
+ return 0;
+
+err_disable:
+ platform_set_drvdata(pdev, NULL);
+ if (jz4740_ohci->vbus) {
+ regulator_disable(jz4740_ohci->vbus);
+ regulator_put(jz4740_ohci->vbus);
+ }
+ clk_disable(jz4740_ohci->clk);
+
+ clk_put(jz4740_ohci->clk);
+err_iounmap:
+ iounmap(hcd->regs);
+err_release_mem:
+ release_mem_region(res->start, resource_size(res));
+err_free:
+ usb_put_hcd(hcd);
+
+ return ret;
+}
+
+static __devexit int jz4740_ohci_remove(struct platform_device *pdev)
+{
+ struct usb_hcd *hcd = platform_get_drvdata(pdev);
+ struct jz4740_ohci_hcd *jz4740_ohci = hcd_to_jz4740_hcd(hcd);
+
+ usb_remove_hcd(hcd);
+
+ platform_set_drvdata(pdev, NULL);
+
+ if (jz4740_ohci->vbus) {
+ regulator_disable(jz4740_ohci->vbus);
+ regulator_put(jz4740_ohci->vbus);
+ }
+
+ clk_disable(jz4740_ohci->clk);
+ clk_put(jz4740_ohci->clk);
+
+ iounmap(hcd->regs);
+ release_mem_region(hcd->rsrc_start, hcd->rsrc_len);
+
+ usb_put_hcd(hcd);
+
+ return 0;
+}
+
+static struct platform_driver ohci_hcd_jz4740_driver = {
+ .probe = jz4740_ohci_probe,
+ .remove = __devexit_p(jz4740_ohci_remove),
+ .driver = {
+ .name = "jz4740-ohci",
+ .owner = THIS_MODULE,
+ },
+};
+
+MODULE_ALIAS("platfrom:jz4740-ohci");
--
1.5.6.5

2010-06-19 05:11:22

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 21/26] alsa: ASoC: Add JZ4740 ASoC support

This patch adds ASoC support for JZ4740 SoCs I2S module.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: [email protected]

---
Changes since v1
- i2s: Properly free and disable clocks in case of an error in probe
- i2s: Drop unnecessary format checks which are already done in the core
- i2s: Drop set_clkdiv
- pcm: Refactor dma buffer position handling to be better comprehensible
- Cleanup and fix Kconfig
---
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/jz4740/Kconfig | 14 +
sound/soc/jz4740/Makefile | 9 +
sound/soc/jz4740/jz4740-i2s.c | 540 +++++++++++++++++++++++++++++++++++++++++
sound/soc/jz4740/jz4740-i2s.h | 18 ++
sound/soc/jz4740/jz4740-pcm.c | 352 +++++++++++++++++++++++++++
sound/soc/jz4740/jz4740-pcm.h | 22 ++
8 files changed, 957 insertions(+), 0 deletions(-)
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

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index d35f848..7137a9a 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -39,6 +39,7 @@ source "sound/soc/s3c24xx/Kconfig"
source "sound/soc/s6000/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/txx9/Kconfig"
+source "sound/soc/jz4740/Kconfig"

# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 97661b7..d131999 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/
obj-$(CONFIG_SND_SOC) += s6000/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += txx9/
+obj-$(CONFIG_SND_SOC) += jz4740/
diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig
new file mode 100644
index 0000000..27480f2
--- /dev/null
+++ b/sound/soc/jz4740/Kconfig
@@ -0,0 +1,14 @@
+config SND_JZ4740_SOC
+ tristate "SoC Audio for Ingenic JZ4740 SoC"
+ depends on MACH_JZ4740 && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the JZ4740 I2S interface. You will also need to select the audio
+ interfaces to support below.
+
+config SND_JZ4740_SOC_I2S
+ depends on SND_JZ4740_SOC
+ tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC"
+ help
+ Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740
+ based boards.
diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile
new file mode 100644
index 0000000..1be8d19
--- /dev/null
+++ b/sound/soc/jz4740/Makefile
@@ -0,0 +1,9 @@
+#
+# Jz4740 Platform Support
+#
+snd-soc-jz4740-objs := jz4740-pcm.o
+snd-soc-jz4740-i2s-objs := jz4740-i2s.o
+
+obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o
+obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o
+
diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c
new file mode 100644
index 0000000..eb518f0
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-i2s.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "jz4740-i2s.h"
+#include "jz4740-pcm.h"
+
+#define JZ_REG_AIC_CONF 0x00
+#define JZ_REG_AIC_CTRL 0x04
+#define JZ_REG_AIC_I2S_FMT 0x10
+#define JZ_REG_AIC_FIFO_STATUS 0x14
+#define JZ_REG_AIC_I2S_STATUS 0x1c
+#define JZ_REG_AIC_CLK_DIV 0x30
+#define JZ_REG_AIC_FIFO 0x34
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12)
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf << 8)
+#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6)
+#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5)
+#define JZ_AIC_CONF_I2S BIT(4)
+#define JZ_AIC_CONF_RESET BIT(3)
+#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2)
+#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1)
+#define JZ_AIC_CONF_ENABLE BIT(0)
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19)
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16)
+#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15)
+#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14)
+#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11)
+#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10)
+#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9)
+#define JZ_AIC_CTRL_FLUSH BIT(8)
+#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6)
+#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5)
+#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4)
+#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3)
+#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2)
+#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1)
+#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0)
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16
+
+#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12)
+#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4)
+#define JZ_AIC_I2S_FMT_MSB BIT(0)
+
+#define JZ_AIC_I2S_STATUS_BUSY BIT(2)
+
+#define JZ_AIC_CLK_DIV_MASK 0xf
+
+struct jz4740_i2s {
+ struct resource *mem;
+ void __iomem *base;
+ dma_addr_t phys_base;
+
+ struct clk *clk_aic;
+ struct clk *clk_i2s;
+
+ struct jz4740_pcm_config pcm_config_playback;
+ struct jz4740_pcm_config pcm_config_capture;
+};
+
+static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s,
+ unsigned int reg)
+{
+ return readl(i2s->base + reg);
+}
+
+static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s,
+ unsigned int reg, uint32_t value)
+{
+ writel(value, i2s->base + reg);
+}
+
+static inline struct jz4740_i2s *jz4740_dai_to_i2s(struct snd_soc_dai *dai)
+{
+ return dai->private_data;
+}
+
+static int jz4740_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf, ctrl;
+
+ if (dai->active)
+ return 0;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+ ctrl |= JZ_AIC_CTRL_FLUSH;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ clk_enable(i2s->clk_i2s);
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf |= JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ return 0;
+}
+
+static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ if (!dai->active)
+ return;
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf &= ~JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ clk_disable(i2s->clk_i2s);
+}
+
+static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+
+ uint32_t ctrl;
+ uint32_t mask;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA;
+ else
+ mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ctrl |= mask;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ctrl &= ~mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ return 0;
+}
+
+static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+
+ uint32_t format = 0;
+ uint32_t conf;
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+
+ conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER;
+ format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ conf |= JZ_AIC_CONF_SYNC_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ conf |= JZ_AIC_CONF_BIT_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_MSB:
+ format |= JZ_AIC_I2S_FMT_MSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+ jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format);
+
+ return 0;
+}
+
+static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ enum jz4740_dma_width dma_width;
+ struct jz4740_pcm_config *pcm_config;
+ unsigned int sample_size;
+ uint32_t ctrl;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ sample_size = 0;
+ dma_width = JZ4740_DMA_WIDTH_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16:
+ sample_size = 1;
+ dma_width = JZ4740_DMA_WIDTH_16BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK;
+ ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET;
+ if (params_channels(params) == 1)
+ ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO;
+ else
+ ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO;
+
+ pcm_config = &i2s->pcm_config_playback;
+ pcm_config->dma_config.dst_width = dma_width;
+
+ } else {
+ ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK;
+ ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET;
+
+ pcm_config = &i2s->pcm_config_capture;
+ pcm_config->dma_config.src_width = dma_width;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ snd_soc_dai_set_dma_data(dai, substream, pcm_config);
+
+ return 0;
+}
+
+static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ struct clk *parent;
+ int ret = 0;
+
+ switch (clk_id) {
+ case JZ4740_I2S_CLKSRC_EXT:
+ parent = clk_get(NULL, "ext");
+ clk_set_parent(i2s->clk_i2s, parent);
+ break;
+ case JZ4740_I2S_CLKSRC_PLL:
+ parent = clk_get(NULL, "pll half");
+ clk_set_parent(i2s->clk_i2s, parent);
+ ret = clk_set_rate(i2s->clk_i2s, freq);
+ break;
+ default:
+ return -EINVAL;
+ }
+ clk_put(parent);
+
+ return ret;
+}
+
+static int jz4740_i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ if (dai->active) {
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf &= ~JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ clk_disable(i2s->clk_i2s);
+ }
+
+ clk_disable(i2s->clk_aic);
+
+ return 0;
+}
+
+static int jz4740_i2s_resume(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ clk_enable(i2s->clk_aic);
+
+ if (dai->active) {
+ clk_enable(i2s->clk_i2s);
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf |= JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+ }
+
+ return 0;
+}
+
+static int jz4740_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
+ (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
+ JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
+ JZ_AIC_CONF_I2S |
+ JZ_AIC_CONF_INTERNAL_CODEC;
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET);
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_i2s_dai_ops = {
+ .startup = jz4740_i2s_startup,
+ .shutdown = jz4740_i2s_shutdown,
+ .trigger = jz4740_i2s_trigger,
+ .hw_params = jz4740_i2s_hw_params,
+ .set_fmt = jz4740_i2s_set_fmt,
+ .set_sysclk = jz4740_i2s_set_sysclk,
+};
+
+#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE)
+
+struct snd_soc_dai jz4740_i2s_dai = {
+ .name = "jz4740-i2s",
+ .probe = jz4740_i2s_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = JZ4740_I2S_FMTS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = JZ4740_I2S_FMTS,
+ },
+ .symmetric_rates = 1,
+ .ops = &jz4740_i2s_dai_ops,
+ .suspend = jz4740_i2s_suspend,
+ .resume = jz4740_i2s_resume,
+};
+EXPORT_SYMBOL_GPL(jz4740_i2s_dai);
+
+static void __devinit jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s)
+{
+ struct jz4740_dma_config *dma_config;
+
+ /* Playback */
+ dma_config = &i2s->pcm_config_playback.dma_config;
+ dma_config->src_width = JZ4740_DMA_WIDTH_32BIT,
+ dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+ dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT;
+ dma_config->flags = JZ4740_DMA_SRC_AUTOINC;
+ dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+ i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+
+ /* Capture */
+ dma_config = &i2s->pcm_config_capture.dma_config;
+ dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT,
+ dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+ dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE;
+ dma_config->flags = JZ4740_DMA_DST_AUTOINC;
+ dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+ i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+}
+
+static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev)
+{
+ struct jz4740_i2s *i2s;
+ int ret;
+
+ i2s = kzalloc(sizeof(*i2s), GFP_KERNEL);
+
+ if (!i2s)
+ return -ENOMEM;
+
+ i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!i2s->mem) {
+ ret = -ENOENT;
+ goto err_free;
+ }
+
+ i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem),
+ pdev->name);
+ if (!i2s->mem) {
+ ret = -EBUSY;
+ goto err_free;
+ }
+
+ i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem));
+ if (!i2s->base) {
+ ret = -EBUSY;
+ goto err_release_mem_region;
+ }
+
+ i2s->phys_base = i2s->mem->start;
+
+ i2s->clk_aic = clk_get(&pdev->dev, "aic");
+ if (IS_ERR(i2s->clk_aic)) {
+ ret = PTR_ERR(i2s->clk_aic);
+ goto err_iounmap;
+ }
+
+ i2s->clk_i2s = clk_get(&pdev->dev, "i2s");
+ if (IS_ERR(i2s->clk_i2s)) {
+ ret = PTR_ERR(i2s->clk_i2s);
+ goto err_clk_put_aic;
+ }
+
+ clk_enable(i2s->clk_aic);
+
+ jz4740_i2c_init_pcm_config(i2s);
+
+ jz4740_i2s_dai.private_data = i2s;
+ ret = snd_soc_register_dai(&jz4740_i2s_dai);
+
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register DAI\n");
+ goto err_clk_put_i2s;
+ }
+
+ platform_set_drvdata(pdev, i2s);
+
+ return 0;
+
+err_clk_put_i2s:
+ clk_disable(i2s->clk_aic);
+ clk_put(i2s->clk_i2s);
+err_clk_put_aic:
+ clk_put(i2s->clk_aic);
+err_iounmap:
+ iounmap(i2s->base);
+err_release_mem_region:
+ release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+err_free:
+ kfree(i2s);
+
+ return ret;
+}
+
+static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev)
+{
+ struct jz4740_i2s *i2s = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&jz4740_i2s_dai);
+
+ clk_disable(i2s->clk_aic);
+ clk_put(i2s->clk_i2s);
+ clk_put(i2s->clk_aic);
+
+ iounmap(i2s->base);
+ release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(i2s);
+
+ return 0;
+}
+
+static struct platform_driver jz4740_i2s_driver = {
+ .probe = jz4740_i2s_dev_probe,
+ .remove = __devexit_p(jz4740_i2s_dev_remove),
+ .driver = {
+ .name = "jz4740-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_i2s_init(void)
+{
+ return platform_driver_register(&jz4740_i2s_driver);
+}
+module_init(jz4740_i2s_init);
+
+static void __exit jz4740_i2s_exit(void)
+{
+ platform_driver_unregister(&jz4740_i2s_driver);
+}
+module_exit(jz4740_i2s_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen, <[email protected]>");
+MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-i2s");
diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h
new file mode 100644
index 0000000..da22ed8
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-i2s.h
@@ -0,0 +1,18 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_I2S_H
+#define _JZ4740_I2S_H
+
+/* I2S clock source */
+#define JZ4740_I2S_CLKSRC_EXT 0
+#define JZ4740_I2S_CLKSRC_PLL 1
+
+#define JZ4740_I2S_BIT_CLK 0
+
+extern struct snd_soc_dai jz4740_i2s_dai;
+
+#endif
diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c
new file mode 100644
index 0000000..67b6cf2
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-pcm.c
@@ -0,0 +1,352 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-jz4740/dma.h>
+#include "jz4740-pcm.h"
+
+struct jz4740_runtime_data {
+ unsigned long dma_period;
+ dma_addr_t dma_start;
+ dma_addr_t dma_pos;
+ dma_addr_t dma_end;
+
+ struct jz4740_dma_chan *dma;
+
+ dma_addr_t fifo_addr;
+};
+
+/* identify hardware playback capabilities */
+static const struct snd_pcm_hardware jz4740_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .period_bytes_min = 16,
+ .period_bytes_max = 2 * PAGE_SIZE,
+ .periods_min = 2,
+ .periods_max = 128,
+ .buffer_bytes_max = 128 * 2 * PAGE_SIZE,
+ .fifo_size = 32,
+};
+
+static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd,
+ struct snd_pcm_substream *substream)
+{
+ unsigned long count;
+
+ if (prtd->dma_pos == prtd->dma_end)
+ prtd->dma_pos = prtd->dma_start;
+
+ if (prtd->dma_pos + prtd->dma_period > prtd->dma_end)
+ count = prtd->dma_end - prtd->dma_pos;
+ else
+ count = prtd->dma_period;
+
+ jz4740_dma_disable(prtd->dma);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos);
+ jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr);
+ } else {
+ jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr);
+ jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos);
+ }
+
+ jz4740_dma_set_transfer_count(prtd->dma, count);
+
+ prtd->dma_pos += count;
+
+ jz4740_dma_enable(prtd->dma);
+}
+
+static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err,
+ void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ snd_pcm_period_elapsed(substream);
+
+ jz4740_pcm_start_transfer(prtd, substream);
+}
+
+static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct jz4740_pcm_config *config;
+
+ config = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream);
+
+ if (!config)
+ return 0;
+
+ if (!prtd->dma) {
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ prtd->dma = jz4740_dma_request(substream, "PCM Capture");
+ else
+ prtd->dma = jz4740_dma_request(substream, "PCM Playback");
+ }
+
+ if (!prtd->dma)
+ return -EBUSY;
+
+ jz4740_dma_configure(prtd->dma, &config->dma_config);
+ prtd->fifo_addr = config->fifo_addr;
+
+ jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ prtd->dma_period = params_period_bytes(params);
+ prtd->dma_start = runtime->dma_addr;
+ prtd->dma_pos = prtd->dma_start;
+ prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
+
+ return 0;
+}
+
+static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ if (prtd->dma) {
+ jz4740_dma_free(prtd->dma);
+ prtd->dma = NULL;
+ }
+
+ return 0;
+}
+
+static int jz4740_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+ if (!prtd->dma)
+ return -EBUSY;
+
+ prtd->dma_pos = prtd->dma_start;
+
+ return 0;
+}
+
+static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ jz4740_pcm_start_transfer(prtd, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ jz4740_dma_disable(prtd->dma);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+ unsigned long byte_offset;
+ snd_pcm_uframes_t offset;
+ struct jz4740_dma_chan *dma = prtd->dma;
+
+ /* prtd->dma_pos points to the end of the current transfer. So by
+ * subtracting prdt->dma_start we get the offset to the end of the
+ * current period in bytes. By subtracting the residue of the transfer
+ * we get the current offset in bytes. */
+ byte_offset = prtd->dma_pos - prtd->dma_start;
+ byte_offset -= jz4740_dma_get_residue(dma);
+
+ offset = bytes_to_frames(runtime, byte_offset);
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int jz4740_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd;
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware);
+
+ runtime->private_data = prtd;
+
+ return 0;
+}
+
+static int jz4740_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int jz4740_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ return remap_pfn_range(vma, vma->vm_start,
+ substream->dma_buffer.addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static struct snd_pcm_ops jz4740_pcm_ops = {
+ .open = jz4740_pcm_open,
+ .close = jz4740_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = jz4740_pcm_hw_params,
+ .hw_free = jz4740_pcm_hw_free,
+ .prepare = jz4740_pcm_prepare,
+ .trigger = jz4740_pcm_trigger,
+ .pointer = jz4740_pcm_pointer,
+ .mmap = jz4740_pcm_mmap,
+};
+
+static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = jz4740_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ buf->area = dma_alloc_noncoherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+
+ return 0;
+}
+
+static void jz4740_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area,
+ buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
+
+int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &jz4740_pcm_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->playback.channels_min) {
+ ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto err;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+struct snd_soc_platform jz4740_soc_platform = {
+ .name = "jz4740-pcm",
+ .pcm_ops = &jz4740_pcm_ops,
+ .pcm_new = jz4740_pcm_new,
+ .pcm_free = jz4740_pcm_free,
+};
+EXPORT_SYMBOL_GPL(jz4740_soc_platform);
+
+static int __init jz4740_soc_platform_init(void)
+{
+ return snd_soc_register_platform(&jz4740_soc_platform);
+}
+module_init(jz4740_soc_platform_init);
+
+static void __exit jz4740_soc_platform_exit(void)
+{
+ snd_soc_unregister_platform(&jz4740_soc_platform);
+}
+module_exit(jz4740_soc_platform_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/jz4740/jz4740-pcm.h b/sound/soc/jz4740/jz4740-pcm.h
new file mode 100644
index 0000000..e3f221e
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-pcm.h
@@ -0,0 +1,22 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_PCM_H
+#define _JZ4740_PCM_H
+
+#include <linux/dma-mapping.h>
+#include <asm/mach-jz4740/dma.h>
+
+/* platform data */
+extern struct snd_soc_platform jz4740_soc_platform;
+
+struct jz4740_pcm_config {
+ struct jz4740_dma_config dma_config;
+ phys_addr_t fifo_addr;
+};
+
+#endif
--
1.5.6.5

2010-06-19 05:11:24

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver

This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Jonathan Cameron <[email protected]>
Cc: [email protected]

---
Changes since v1
- Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
ADC driver now only reads the adcin value.
---
drivers/hwmon/Kconfig | 11 +++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/jz4740-hwmon.c | 206 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 218 insertions(+), 0 deletions(-)
create mode 100644 drivers/hwmon/jz4740-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 569082c..51fc2f6 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -446,6 +446,17 @@ config SENSORS_IT87
This driver can also be built as a module. If so, the module
will be called it87.

+config SENSORS_JZ4740
+ tristate "Ingenic JZ4740 SoC ADC driver"
+ depends on MACH_JZ4740
+ help
+ If you say yes here you get support for the Ingenic JZ4740 SoC ADC core.
+ It is required for the JZ4740 battery and touchscreen driver and is used
+ to synchronize access to the adc module between those two.
+
+ This driver can also be build as a module. If so, the module will be
+ called jz4740-adc.
+
config SENSORS_LM63
tristate "National Semiconductor LM63 and LM64"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index bca0d45..dffbdff 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
+obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o
diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c
new file mode 100644
index 0000000..f53d15e
--- /dev/null
+++ b/drivers/hwmon/jz4740-hwmon.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC HWMON 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/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/core.h>
+
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+struct jz4740_hwmon {
+ struct resource *mem;
+ void __iomem *base;
+
+ int irq;
+
+ struct mfd_cell *cell;
+ struct device *hwmon;
+
+ struct completion read_completion;
+
+ struct mutex lock;
+};
+
+static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
+{
+ struct jz4740_hwmon *hwmon = data;
+
+ complete(&hwmon->read_completion);
+ return IRQ_HANDLED;
+}
+
+static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
+ unsigned long t;
+ uint16_t val;
+ int ret;
+
+ mutex_lock(&hwmon->lock);
+
+ INIT_COMPLETION(hwmon->read_completion);
+
+ enable_irq(hwmon->irq);
+ hwmon->cell->enable(to_platform_device(dev));
+
+ t = wait_for_completion_interruptible_timeout(&hwmon->read_completion, HZ);
+
+ if (t > 0) {
+ val = readw(hwmon->base);
+ ret = sprintf(buf, "%d\n", val);
+ } else {
+ ret = t ? t : -ETIMEDOUT;
+ }
+
+ hwmon->cell->disable(to_platform_device(dev));
+ disable_irq(hwmon->irq);
+
+ mutex_unlock(&hwmon->lock);
+
+ return ret;
+}
+
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL, 0);
+
+static int __devinit jz4740_hwmon_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_hwmon *hwmon;
+
+ hwmon = kmalloc(sizeof(*hwmon), GFP_KERNEL);
+
+ hwmon->cell = pdev->dev.platform_data;
+
+ hwmon->irq = platform_get_irq(pdev, 0);
+ if (hwmon->irq < 0) {
+ ret = hwmon->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free;
+ }
+
+ hwmon->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!hwmon->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ goto err_free;
+ }
+
+ hwmon->mem = request_mem_region(hwmon->mem->start,
+ resource_size(hwmon->mem), pdev->name);
+ if (!hwmon->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ hwmon->base = ioremap_nocache(hwmon->mem->start, resource_size(hwmon->mem));
+ if (!hwmon->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ init_completion(&hwmon->read_completion);
+ mutex_init(&hwmon->lock);
+
+ platform_set_drvdata(pdev, hwmon);
+
+ ret = request_irq(hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+ goto err_iounmap;
+ }
+ disable_irq(hwmon->irq);
+
+ ret = device_create_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create sysfs file: %d\n", ret);
+ goto err_free_irq;
+ }
+
+ hwmon->hwmon = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(hwmon->hwmon)) {
+ ret = PTR_ERR(hwmon->hwmon);
+ goto err_remove_file;
+ }
+
+ return 0;
+
+err_remove_file:
+ device_remove_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
+err_free_irq:
+ free_irq(hwmon->irq, hwmon);
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(hwmon->base);
+err_release_mem_region:
+ release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
+err_free:
+ kfree(hwmon);
+
+ return ret;
+}
+
+static int __devexit jz4740_hwmon_remove(struct platform_device *pdev)
+{
+ struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(hwmon->hwmon);
+ device_remove_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
+
+ free_irq(hwmon->irq, hwmon);
+
+ iounmap(hwmon->base);
+ release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(hwmon);
+
+ return 0;
+}
+
+struct platform_driver jz4740_hwmon_driver = {
+ .probe = jz4740_hwmon_probe,
+ .remove = __devexit_p(jz4740_hwmon_remove),
+ .driver = {
+ .name = "jz4740-hwmon",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_hwmon_init(void)
+{
+ return platform_driver_register(&jz4740_hwmon_driver);
+}
+module_init(jz4740_hwmon_init);
+
+static void __exit jz4740_hwmon_exit(void)
+{
+ platform_driver_unregister(&jz4740_hwmon_driver);
+}
+module_exit(jz4740_hwmon_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC HWMON driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
--
1.5.6.5

2010-06-19 05:11:37

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 24/26] power: Add JZ4740 battery driver.

This patch adds support for the battery voltage measurement part of the JZ4740
ADC unit.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Anton Vorontsov <[email protected]>

---
Changes since v1
- Fix voltage difference check in jz_update_battery
- Move get_battery_voltage from the hwmon driver to the battery driver
- The battery driver is now a cell of the ADC MFD driver
---
drivers/power/Kconfig | 11 +
drivers/power/Makefile | 1 +
drivers/power/jz4740-battery.c | 445 ++++++++++++++++++++++++++++++++++
include/linux/power/jz4740-battery.h | 24 ++
4 files changed, 481 insertions(+), 0 deletions(-)
create mode 100644 drivers/power/jz4740-battery.c
create mode 100644 include/linux/power/jz4740-battery.h

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 8e9ba17..1e5506b 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -142,4 +142,15 @@ config CHARGER_PCF50633
help
Say Y to include support for NXP PCF50633 Main Battery Charger.

+config BATTERY_JZ4740
+ tristate "Ingenic JZ4740 battery"
+ depends on MACH_JZ4740
+ depends on MFD_JZ4740_ADC
+ help
+ Say Y to enable support for the battery on Ingenic JZ4740 based
+ boards.
+
+ This driver can be build as a module. If so, the module will be
+ called jz4740-battery.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 0005080..cf95009 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -34,3 +34,4 @@ obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
+obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c
new file mode 100644
index 0000000..20c4b95
--- /dev/null
+++ b/drivers/power/jz4740-battery.c
@@ -0,0 +1,445 @@
+/*
+ * Battery measurement code for Ingenic JZ SOC.
+ *
+ * Copyright (C) 2009 Jiejing Zhang <[email protected]>
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * based on tosa_battery.c
+ *
+ * Copyright (C) 2008 Marek Vasut <[email protected]>
+*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/power_supply.h>
+
+#include <linux/power/jz4740-battery.h>
+#include <linux/jz4740-adc.h>
+
+struct jz_battery {
+ struct jz_battery_platform_data *pdata;
+ struct platform_device *pdev;
+
+ struct resource *mem;
+ void __iomem *base;
+
+ int irq;
+ int charge_irq;
+
+ struct mfd_cell *cell;
+
+ int status;
+ long voltage;
+
+ struct completion read_completion;
+
+ struct power_supply battery;
+ struct delayed_work work;
+};
+
+static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy)
+{
+ return container_of(psy, struct jz_battery, battery);
+}
+
+static irqreturn_t jz_battery_irq_handler(int irq, void *devid)
+{
+ struct jz_battery *battery = devid;
+
+ complete(&battery->read_completion);
+ return IRQ_HANDLED;
+}
+
+static long jz_battery_read_voltage(struct jz_battery *battery)
+{
+ unsigned long t;
+ unsigned long val;
+ long voltage;
+
+ INIT_COMPLETION(battery->read_completion);
+
+ enable_irq(battery->irq);
+ battery->cell->enable(battery->pdev);
+
+ t = wait_for_completion_interruptible_timeout(&battery->read_completion,
+ HZ);
+
+ if (t > 0) {
+ val = readw(battery->base) & 0xfff;
+
+ if (battery->pdata->info.voltage_max_design <= 2500000)
+ val = (val * 78125UL) >> 7UL;
+ else
+ val = ((val * 924375UL) >> 9UL) + 33000;
+ voltage = (long)val;
+ } else {
+ voltage = t ? t : -ETIMEDOUT;
+ }
+
+ battery->cell->disable(battery->pdev);
+ disable_irq(battery->irq);
+
+ return voltage;
+}
+
+static int jz_battery_get_capacity(struct power_supply *psy)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+ struct power_supply_info *info = &jz_battery->pdata->info;
+ long voltage;
+ int ret;
+ int voltage_span;
+
+ voltage = jz_battery_read_voltage(jz_battery);
+
+ if (voltage < 0)
+ return voltage;
+
+ voltage_span = info->voltage_max_design - info->voltage_min_design;
+ ret = ((voltage - info->voltage_min_design) * 100) / voltage_span;
+
+ if (ret > 100)
+ ret = 100;
+ else if (ret < 0)
+ ret = 0;
+
+ return ret;
+}
+
+static int jz_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+ struct power_supply_info *info = &jz_battery->pdata->info;
+ long voltage;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = jz_battery->status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = jz_battery->pdata->info.technology;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ voltage = jz_battery_read_voltage(jz_battery);
+ if (voltage < info->voltage_min_design)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = jz_battery_get_capacity(psy);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = jz_battery_read_voltage(jz_battery);
+ if (val->intval < 0)
+ return val->intval;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = info->voltage_max_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = info->voltage_min_design;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void jz_battery_external_power_changed(struct power_supply *psy)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+
+ cancel_delayed_work(&jz_battery->work);
+ schedule_delayed_work(&jz_battery->work, 0);
+}
+
+static irqreturn_t jz_battery_charge_irq(int irq, void *data)
+{
+ struct jz_battery *jz_battery = data;
+
+ cancel_delayed_work(&jz_battery->work);
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static void jz_battery_update(struct jz_battery *jz_battery)
+{
+ int status;
+ long voltage;
+ bool has_changed = false;
+ int is_charging;
+
+ if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
+ is_charging = gpio_get_value(jz_battery->pdata->gpio_charge);
+ is_charging ^= jz_battery->pdata->gpio_charge_active_low;
+ if (is_charging)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ if (status != jz_battery->status) {
+ jz_battery->status = status;
+ has_changed = true;
+ }
+ }
+
+ voltage = jz_battery_read_voltage(jz_battery);
+ if (abs(voltage - jz_battery->voltage) < 50000) {
+ jz_battery->voltage = voltage;
+ has_changed = true;
+ }
+
+ if (has_changed)
+ power_supply_changed(&jz_battery->battery);
+}
+
+static enum power_supply_property jz_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static void jz_battery_work(struct work_struct *work)
+{
+ /* Too small interval will increase system workload */
+ const int interval = HZ * 30;
+ struct jz_battery *jz_battery = container_of(work, struct jz_battery,
+ work.work);
+
+ jz_battery_update(jz_battery);
+ schedule_delayed_work(&jz_battery->work, interval);
+}
+
+static int __devinit jz_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data;
+ struct jz_battery *jz_battery;
+ struct power_supply *battery;
+
+ jz_battery = kzalloc(sizeof(*jz_battery), GFP_KERNEL);
+ if (!jz_battery) {
+ dev_err(&pdev->dev, "Failed to allocate driver structure\n");
+ return -ENOMEM;
+ }
+
+ jz_battery->cell = pdev->dev.platform_data;
+
+ jz_battery->irq = platform_get_irq(pdev, 0);
+ if (jz_battery->irq < 0) {
+ ret = jz_battery->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free;
+ }
+
+ jz_battery->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!jz_battery->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ goto err_free;
+ }
+
+ jz_battery->mem = request_mem_region(jz_battery->mem->start,
+ resource_size(jz_battery->mem), pdev->name);
+ if (!jz_battery->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ jz_battery->base = ioremap_nocache(jz_battery->mem->start,
+ resource_size(jz_battery->mem));
+ if (!jz_battery->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ battery = &jz_battery->battery;
+ battery->name = pdata->info.name;
+ battery->type = POWER_SUPPLY_TYPE_BATTERY;
+ battery->properties = jz_battery_properties;
+ battery->num_properties = ARRAY_SIZE(jz_battery_properties);
+ battery->get_property = jz_battery_get_property;
+ battery->external_power_changed = jz_battery_external_power_changed;
+ battery->use_for_apm = 1;
+
+ jz_battery->pdata = pdata;
+ jz_battery->pdev = pdev;
+
+ init_completion(&jz_battery->read_completion);
+
+ INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work);
+
+ ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name,
+ jz_battery);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq %d\n", ret);
+ goto err_iounmap;
+ }
+ disable_irq(jz_battery->irq);
+
+ if (gpio_is_valid(pdata->gpio_charge)) {
+ ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev));
+ if (ret) {
+ dev_err(&pdev->dev, "charger state gpio request failed.\n");
+ goto err_free_irq;
+ }
+ ret = gpio_direction_input(pdata->gpio_charge);
+ if (ret) {
+ dev_err(&pdev->dev, "charger state gpio set direction failed.\n");
+ goto err_free_gpio;
+ }
+
+ jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge);
+
+ if (jz_battery->charge_irq >= 0) {
+ ret = request_irq(jz_battery->charge_irq,
+ jz_battery_charge_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&pdev->dev), jz_battery);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret);
+ goto err_free_gpio;
+ }
+ }
+ } else {
+ jz_battery->charge_irq = -1;
+ }
+
+ if (jz_battery->pdata->info.voltage_max_design <= 2500000)
+ jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB,
+ JZ_ADC_CONFIG_BAT_MB);
+ else
+ jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0);
+
+ ret = power_supply_register(&pdev->dev, &jz_battery->battery);
+ if (ret) {
+ dev_err(&pdev->dev, "power supply battery register failed.\n");
+ goto err_free_charge_irq;
+ }
+
+ platform_set_drvdata(pdev, jz_battery);
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return 0;
+
+err_free_charge_irq:
+ if (jz_battery->charge_irq >= 0)
+ free_irq(jz_battery->charge_irq, jz_battery);
+err_free_gpio:
+ if (gpio_is_valid(pdata->gpio_charge))
+ gpio_free(jz_battery->pdata->gpio_charge);
+err_free_irq:
+ free_irq(jz_battery->irq, jz_battery);
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(jz_battery->base);
+err_release_mem_region:
+ release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
+err_free:
+ kfree(jz_battery);
+ return ret;
+}
+
+static int __devexit jz_battery_remove(struct platform_device *pdev)
+{
+ struct jz_battery *jz_battery = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&jz_battery->work);
+
+ if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
+ if (jz_battery->charge_irq >= 0)
+ free_irq(jz_battery->charge_irq, jz_battery);
+ gpio_free(jz_battery->pdata->gpio_charge);
+ }
+
+ power_supply_unregister(&jz_battery->battery);
+
+ free_irq(jz_battery->irq, jz_battery);
+
+ iounmap(jz_battery->base);
+ release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int jz_battery_suspend(struct device *dev)
+{
+ struct jz_battery *jz_battery = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&jz_battery->work);
+ jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ return 0;
+}
+
+static int jz_battery_resume(struct device *dev)
+{
+ struct jz_battery *jz_battery = dev_get_drvdata(dev);
+
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return 0;
+}
+
+static const struct dev_pm_ops jz_battery_pm_ops = {
+ .suspend = jz_battery_suspend,
+ .resume = jz_battery_resume,
+};
+
+#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops)
+#else
+#define JZ_BATTERY_PM_OPS NULL
+#endif
+
+static struct platform_driver jz_battery_driver = {
+ .probe = jz_battery_probe,
+ .remove = __devexit_p(jz_battery_remove),
+ .driver = {
+ .name = "jz4740-battery",
+ .owner = THIS_MODULE,
+ .pm = JZ_BATTERY_PM_OPS,
+ },
+};
+
+static int __init jz_battery_init(void)
+{
+ return platform_driver_register(&jz_battery_driver);
+}
+module_init(jz_battery_init);
+
+static void __exit jz_battery_exit(void)
+{
+ platform_driver_unregister(&jz_battery_driver);
+}
+module_exit(jz_battery_exit);
+
+MODULE_ALIAS("platform:jz4740-battery");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("JZ4740 SoC battery driver");
diff --git a/include/linux/power/jz4740-battery.h b/include/linux/power/jz4740-battery.h
new file mode 100644
index 0000000..19c9610
--- /dev/null
+++ b/include/linux/power/jz4740-battery.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2009, Jiejing Zhang <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __JZ4740_BATTERY_H
+#define __JZ4740_BATTERY_H
+
+struct jz_battery_platform_data {
+ struct power_supply_info info;
+ int gpio_charge; /* GPIO port of Charger state */
+ int gpio_charge_active_low;
+};
+
+#endif
--
1.5.6.5

2010-06-19 05:11:43

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 26/26] alsa: ASoC: JZ4740: Add qi_lb60 board driver

This patch adds ASoC support for the qi_lb60 board.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: [email protected]

---
Changes since v1
- Refer to AMP gpios always by their define
- Do not try to set codecs format, since the set_fmt callback for the codec was
dropped.
---
sound/soc/jz4740/Kconfig | 9 ++
sound/soc/jz4740/Makefile | 4 +
sound/soc/jz4740/jz4740-pcm.c | 25 ++++++-
sound/soc/jz4740/qi_lb60.c | 167 +++++++++++++++++++++++++++++++++++++++++
4 files changed, 203 insertions(+), 2 deletions(-)
create mode 100644 sound/soc/jz4740/qi_lb60.c

diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig
index 27480f2..5351cba 100644
--- a/sound/soc/jz4740/Kconfig
+++ b/sound/soc/jz4740/Kconfig
@@ -12,3 +12,12 @@ config SND_JZ4740_SOC_I2S
help
Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740
based boards.
+
+config SND_JZ4740_SOC_QI_LB60
+ tristate "SoC Audio support for Qi LB60"
+ depends on SND_JZ4740_SOC && JZ4740_QI_LB60
+ select SND_JZ4740_SOC_I2S
+ select SND_SOC_JZ4740_CODEC
+ help
+ Say Y if you want to add support for ASoC audio on the Qi LB60 board
+ a.k.a Qi Ben NanoNote.
diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile
index 1be8d19..be873c1 100644
--- a/sound/soc/jz4740/Makefile
+++ b/sound/soc/jz4740/Makefile
@@ -7,3 +7,7 @@ snd-soc-jz4740-i2s-objs := jz4740-i2s.o
obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o
obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o

+# Jz4740 Machine Support
+snd-soc-qi-lb60-objs := qi_lb60.o
+
+obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o
diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c
index 67b6cf2..ee68d85 100644
--- a/sound/soc/jz4740/jz4740-pcm.c
+++ b/sound/soc/jz4740/jz4740-pcm.c
@@ -16,6 +16,7 @@
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/platform_device.h>
#include <linux/slab.h>

#include <linux/dma-mapping.h>
@@ -335,15 +336,35 @@ struct snd_soc_platform jz4740_soc_platform = {
};
EXPORT_SYMBOL_GPL(jz4740_soc_platform);

-static int __init jz4740_soc_platform_init(void)
+static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
{
return snd_soc_register_platform(&jz4740_soc_platform);
}
+
+static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&jz4740_soc_platform);
+ return 0;
+}
+
+static struct platform_driver jz4740_pcm_driver = {
+ .probe = jz4740_pcm_probe,
+ .remove = __devexit_p(jz4740_pcm_remove),
+ .driver = {
+ .name = "jz4740-pcm",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_soc_platform_init(void)
+{
+ return platform_driver_register(&jz4740_pcm_driver);
+}
module_init(jz4740_soc_platform_init);

static void __exit jz4740_soc_platform_exit(void)
{
- snd_soc_unregister_platform(&jz4740_soc_platform);
+ return platform_driver_unregister(&jz4740_pcm_driver);
}
module_exit(jz4740_soc_platform_exit);

diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c
new file mode 100644
index 0000000..829bc45
--- /dev/null
+++ b/sound/soc/jz4740/qi_lb60.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/gpio.h>
+
+#include "../codecs/jz4740-codec.h"
+#include "jz4740-pcm.h"
+#include "jz4740-i2s.h"
+
+
+#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29)
+#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4)
+
+static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *ctrl, int event)
+{
+ int on = 0;
+ if (event & SND_SOC_DAPM_POST_PMU)
+ on = 1;
+ else if (event & SND_SOC_DAPM_PRE_PMD)
+ on = 0;
+
+ gpio_set_value(QI_LB60_SND_GPIO, on);
+ gpio_set_value(QI_LB60_AMP_GPIO, on);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget qi_lb60_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route qi_lb60_routes[] = {
+ {"Mic", NULL, "MIC"},
+ {"Speaker", NULL, "LOUT"},
+ {"Speaker", NULL, "ROUT"},
+};
+
+#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBM_CFM)
+
+static int qi_lb60_codec_init(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct snd_soc_dai *cpu_dai = codec->socdev->card->dai_link->cpu_dai;
+ struct snd_soc_dai *codec_dai = codec->socdev->card->dai_link->codec_dai;
+
+ snd_soc_dapm_nc_pin(codec, "LIN");
+ snd_soc_dapm_nc_pin(codec, "RIN");
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_dapm_new_controls(codec, qi_lb60_widgets, ARRAY_SIZE(qi_lb60_widgets));
+ snd_soc_dapm_add_routes(codec, qi_lb60_routes, ARRAY_SIZE(qi_lb60_routes));
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link qi_lb60_dai = {
+ .name = "jz4740",
+ .stream_name = "jz4740",
+ .cpu_dai = &jz4740_i2s_dai,
+ .codec_dai = &jz4740_codec_dai,
+ .init = qi_lb60_codec_init,
+};
+
+static struct snd_soc_card qi_lb60 = {
+ .name = "QI LB60",
+ .dai_link = &qi_lb60_dai,
+ .num_links = 1,
+ .platform = &jz4740_soc_platform,
+};
+
+static struct snd_soc_device qi_lb60_snd_devdata = {
+ .card = &qi_lb60,
+ .codec_dev = &soc_codec_dev_jz4740_codec,
+};
+
+static struct platform_device *qi_lb60_snd_device;
+
+static int __init qi_lb60_init(void)
+{
+ int ret;
+
+ qi_lb60_snd_device = platform_device_alloc("soc-audio", -1);
+
+ if (!qi_lb60_snd_device)
+ return -ENOMEM;
+
+ ret = gpio_request(QI_LB60_SND_GPIO, "SND");
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to request SND GPIO(%d): %d\n",
+ QI_LB60_SND_GPIO, ret);
+ goto err_device_put;
+ }
+
+ ret = gpio_request(QI_LB60_AMP_GPIO, "AMP");
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to request AMP GPIO(%d): %d\n",
+ QI_LB60_AMP_GPIO, ret);
+ goto err_gpio_free_snd;
+ }
+
+ gpio_direction_output(QI_LB60_SND_GPIO, 0);
+ gpio_direction_output(QI_LB60_AMP_GPIO, 0);
+
+ platform_set_drvdata(qi_lb60_snd_device, &qi_lb60_snd_devdata);
+ qi_lb60_snd_devdata.dev = &qi_lb60_snd_device->dev;
+
+ ret = platform_device_add(qi_lb60_snd_device);
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret);
+ goto err_unset_pdata;
+ }
+
+ return 0;
+
+err_unset_pdata:
+ platform_set_drvdata(qi_lb60_snd_device, NULL);
+/*err_gpio_free_amp:*/
+ gpio_free(QI_LB60_AMP_GPIO);
+err_gpio_free_snd:
+ gpio_free(QI_LB60_SND_GPIO);
+err_device_put:
+ platform_device_put(qi_lb60_snd_device);
+
+ return ret;
+}
+module_init(qi_lb60_init);
+
+static void __exit qi_lb60_exit(void)
+{
+ gpio_free(QI_LB60_AMP_GPIO);
+ gpio_free(QI_LB60_SND_GPIO);
+ platform_device_unregister(qi_lb60_snd_device);
+}
+module_exit(qi_lb60_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
--
1.5.6.5

2010-06-19 05:12:00

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 25/26] MIPS: JZ4740: Add qi_lb60 board support

This patch adds support for the qi_lb60 (a.k.a QI Ben NanoNote) clamshell
device.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Register jz4740 pcm device
- Battery device is now registered by the ADC MFD device
---
arch/mips/jz4740/Kconfig | 4 +
arch/mips/jz4740/Makefile | 2 +
arch/mips/jz4740/board-qi_lb60.c | 483 ++++++++++++++++++++++++++++++++++++++
3 files changed, 489 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/board-qi_lb60.c

diff --git a/arch/mips/jz4740/Kconfig b/arch/mips/jz4740/Kconfig
index 8a5e850..3e7141f 100644
--- a/arch/mips/jz4740/Kconfig
+++ b/arch/mips/jz4740/Kconfig
@@ -1,6 +1,10 @@
choice
prompt "Machine type"
depends on MACH_JZ4740
+ default JZ4740_QI_LB60
+
+config JZ4740_QI_LB60
+ bool "Qi Hardware Ben NanoNote"

endchoice

diff --git a/arch/mips/jz4740/Makefile b/arch/mips/jz4740/Makefile
index a803ccb..a604eae 100644
--- a/arch/mips/jz4740/Makefile
+++ b/arch/mips/jz4740/Makefile
@@ -11,6 +11,8 @@ obj-$(CONFIG_DEBUG_FS) += clock-debugfs.o

# board specific support

+obj-$(CONFIG_JZ4740_QI_LB60) += board-qi_lb60.o
+
# PM support

obj-$(CONFIG_PM) += pm.o
diff --git a/arch/mips/jz4740/board-qi_lb60.c b/arch/mips/jz4740/board-qi_lb60.c
new file mode 100644
index 0000000..77b191a
--- /dev/null
+++ b/arch/mips/jz4740/board-qi_lb60.c
@@ -0,0 +1,483 @@
+/*
+ * linux/arch/mips/jz4740/board-qi_lb60.c
+ *
+ * QI_LB60 board support
+ *
+ * Copyright (c) 2009 Qi Hardware inc.,
+ * Author: Xiangfu Liu <[email protected]>
+ * Copyright 2010, Lars-Petrer Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or later
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+
+#include <linux/input.h>
+#include <linux/gpio_keys.h>
+#include <linux/mtd/jz4740_nand.h>
+#include <linux/jz4740_fb.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/mtd/jz4740_nand.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/power/jz4740-battery.h>
+#include <linux/mmc/jz4740_mmc.h>
+
+#include <linux/regulator/fixed.h>
+#include <linux/regulator/machine.h>
+
+#include <linux/leds_pwm.h>
+
+#include <asm/mach-jz4740/platform.h>
+
+#include "clock.h"
+
+static bool is_avt2;
+
+/* GPIOs */
+#define QI_LB60_GPIO_SD_CD JZ_GPIO_PORTD(0)
+#define QI_LB60_GPIO_SD_VCC_EN_N JZ_GPIO_PORTD(2)
+
+#define QI_LB60_GPIO_KEYOUT(x) (JZ_GPIO_PORTC(10) + (x))
+#define QI_LB60_GPIO_KEYIN(x) (JZ_GPIO_PORTD(18) + (x))
+#define QI_LB60_GPIO_KEYIN8 JZ_GPIO_PORTD(26)
+
+/* NAND */
+static struct nand_ecclayout qi_lb60_ecclayout_1gb = {
+/* .eccbytes = 36,
+ .eccpos = {
+ 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41
+ },*/
+ .oobfree = {
+ { .offset = 2, .length = 4 },
+ { .offset = 42, .length = 22 }
+ },
+};
+
+/* Early prototypes of the QI LB60 had only 1GB of NAND.
+ * In order to support these devices aswell the partition and ecc layout is
+ * initalized depending on the NAND size */
+static struct mtd_partition qi_lb60_partitions_1gb[] = {
+ {
+ .name = "NAND BOOT partition",
+ .offset = 0 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND KERNEL partition",
+ .offset = 4 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND ROOTFS partition",
+ .offset = 8 * 0x100000,
+ .size = (504 + 512) * 0x100000,
+ },
+};
+
+static struct nand_ecclayout qi_lb60_ecclayout_2gb = {
+/* .eccbytes = 72,
+ .eccpos = {
+ 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27,
+ 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67,
+ 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 83
+ },*/
+ .oobfree = {
+ { .offset = 2, .length = 10 },
+ { .offset = 84, .length = 44 },
+ },
+};
+
+static struct mtd_partition qi_lb60_partitions_2gb[] = {
+ {
+ .name = "NAND BOOT partition",
+ .offset = 0 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND KERNEL partition",
+ .offset = 4 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND ROOTFS partition",
+ .offset = 8 * 0x100000,
+ .size = (504 + 512 + 1024) * 0x100000,
+ },
+};
+
+static void qi_lb60_nand_ident(struct platform_device *pdev,
+ struct nand_chip *chip, struct mtd_partition **partitions,
+ int *num_partitions)
+{
+ if (chip->page_shift == 12) {
+ chip->ecc.layout = &qi_lb60_ecclayout_2gb;
+ *partitions = qi_lb60_partitions_2gb;
+ *num_partitions = ARRAY_SIZE(qi_lb60_partitions_2gb);
+ } else {
+ chip->ecc.layout = &qi_lb60_ecclayout_1gb;
+ *partitions = qi_lb60_partitions_1gb;
+ *num_partitions = ARRAY_SIZE(qi_lb60_partitions_1gb);
+ }
+}
+
+static struct jz_nand_platform_data qi_lb60_nand_pdata = {
+ .ident_callback = qi_lb60_nand_ident,
+ .busy_gpio = 94,
+};
+
+/* Keyboard*/
+
+#define KEY_QI_QI KEY_F13
+#define KEY_QI_UPRED KEY_RIGHTALT
+#define KEY_QI_VOLUP KEY_VOLUMEUP
+#define KEY_QI_VOLDOWN KEY_VOLUMEDOWN
+#define KEY_QI_FN KEY_LEFTCTRL
+
+static const uint32_t qi_lb60_keymap[] = {
+ KEY(0, 0, KEY_F1), /* S2 */
+ KEY(0, 1, KEY_F2), /* S3 */
+ KEY(0, 2, KEY_F3), /* S4 */
+ KEY(0, 3, KEY_F4), /* S5 */
+ KEY(0, 4, KEY_F5), /* S6 */
+ KEY(0, 5, KEY_F6), /* S7 */
+ KEY(0, 6, KEY_F7), /* S8 */
+
+ KEY(1, 0, KEY_Q), /* S10 */
+ KEY(1, 1, KEY_W), /* S11 */
+ KEY(1, 2, KEY_E), /* S12 */
+ KEY(1, 3, KEY_R), /* S13 */
+ KEY(1, 4, KEY_T), /* S14 */
+ KEY(1, 5, KEY_Y), /* S15 */
+ KEY(1, 6, KEY_U), /* S16 */
+ KEY(1, 7, KEY_I), /* S17 */
+ KEY(2, 0, KEY_A), /* S18 */
+ KEY(2, 1, KEY_S), /* S19 */
+ KEY(2, 2, KEY_D), /* S20 */
+ KEY(2, 3, KEY_F), /* S21 */
+ KEY(2, 4, KEY_G), /* S22 */
+ KEY(2, 5, KEY_H), /* S23 */
+ KEY(2, 6, KEY_J), /* S24 */
+ KEY(2, 7, KEY_K), /* S25 */
+ KEY(3, 0, KEY_ESC), /* S26 */
+ KEY(3, 1, KEY_Z), /* S27 */
+ KEY(3, 2, KEY_X), /* S28 */
+ KEY(3, 3, KEY_C), /* S29 */
+ KEY(3, 4, KEY_V), /* S30 */
+ KEY(3, 5, KEY_B), /* S31 */
+ KEY(3, 6, KEY_N), /* S32 */
+ KEY(3, 7, KEY_M), /* S33 */
+ KEY(4, 0, KEY_TAB), /* S34 */
+ KEY(4, 1, KEY_CAPSLOCK), /* S35 */
+ KEY(4, 2, KEY_BACKSLASH), /* S36 */
+ KEY(4, 3, KEY_APOSTROPHE), /* S37 */
+ KEY(4, 4, KEY_COMMA), /* S38 */
+ KEY(4, 5, KEY_DOT), /* S39 */
+ KEY(4, 6, KEY_SLASH), /* S40 */
+ KEY(4, 7, KEY_UP), /* S41 */
+ KEY(5, 0, KEY_O), /* S42 */
+ KEY(5, 1, KEY_L), /* S43 */
+ KEY(5, 2, KEY_EQUAL), /* S44 */
+ KEY(5, 3, KEY_QI_UPRED), /* S45 */
+ KEY(5, 4, KEY_SPACE), /* S46 */
+ KEY(5, 5, KEY_QI_QI), /* S47 */
+ KEY(5, 6, KEY_RIGHTCTRL), /* S48 */
+ KEY(5, 7, KEY_LEFT), /* S49 */
+ KEY(6, 0, KEY_F8), /* S50 */
+ KEY(6, 1, KEY_P), /* S51 */
+ KEY(6, 2, KEY_BACKSPACE),/* S52 */
+ KEY(6, 3, KEY_ENTER), /* S53 */
+ KEY(6, 4, KEY_QI_VOLUP), /* S54 */
+ KEY(6, 5, KEY_QI_VOLDOWN), /* S55 */
+ KEY(6, 6, KEY_DOWN), /* S56 */
+ KEY(6, 7, KEY_RIGHT), /* S57 */
+
+ KEY(7, 0, KEY_LEFTSHIFT), /* S58 */
+ KEY(7, 1, KEY_LEFTALT), /* S59 */
+ KEY(7, 2, KEY_QI_FN), /* S60 */
+};
+
+static const struct matrix_keymap_data qi_lb60_keymap_data = {
+ .keymap = qi_lb60_keymap,
+ .keymap_size = ARRAY_SIZE(qi_lb60_keymap),
+};
+
+static const unsigned int qi_lb60_keypad_cols[] = {
+ QI_LB60_GPIO_KEYOUT(0),
+ QI_LB60_GPIO_KEYOUT(1),
+ QI_LB60_GPIO_KEYOUT(2),
+ QI_LB60_GPIO_KEYOUT(3),
+ QI_LB60_GPIO_KEYOUT(4),
+ QI_LB60_GPIO_KEYOUT(5),
+ QI_LB60_GPIO_KEYOUT(6),
+ QI_LB60_GPIO_KEYOUT(7),
+};
+
+static const unsigned int qi_lb60_keypad_rows[] = {
+ QI_LB60_GPIO_KEYIN(0),
+ QI_LB60_GPIO_KEYIN(1),
+ QI_LB60_GPIO_KEYIN(2),
+ QI_LB60_GPIO_KEYIN(3),
+ QI_LB60_GPIO_KEYIN(4),
+ QI_LB60_GPIO_KEYIN(5),
+ QI_LB60_GPIO_KEYIN(7),
+ QI_LB60_GPIO_KEYIN8,
+};
+
+static struct matrix_keypad_platform_data qi_lb60_pdata = {
+ .keymap_data = &qi_lb60_keymap_data,
+ .col_gpios = qi_lb60_keypad_cols,
+ .row_gpios = qi_lb60_keypad_rows,
+ .num_col_gpios = ARRAY_SIZE(qi_lb60_keypad_cols),
+ .num_row_gpios = ARRAY_SIZE(qi_lb60_keypad_rows),
+ .col_scan_delay_us = 10,
+ .debounce_ms = 10,
+ .wakeup = 1,
+ .active_low = 1,
+};
+
+static struct platform_device qi_lb60_keypad = {
+ .name = "matrix-keypad",
+ .id = -1,
+ .dev = {
+ .platform_data = &qi_lb60_pdata,
+ },
+};
+
+/* Display */
+static struct fb_videomode qi_lb60_video_modes[] = {
+ {
+ .name = "320x240",
+ .xres = 320,
+ .yres = 240,
+ .refresh = 30,
+ .left_margin = 140,
+ .right_margin = 273,
+ .upper_margin = 20,
+ .lower_margin = 2,
+ .hsync_len = 1,
+ .vsync_len = 1,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ },
+};
+
+static struct jz4740_fb_platform_data qi_lb60_fb_pdata = {
+ .width = 60,
+ .height = 45,
+ .num_modes = ARRAY_SIZE(qi_lb60_video_modes),
+ .modes = qi_lb60_video_modes,
+ .bpp = 24,
+ .lcd_type = JZ_LCD_TYPE_8BIT_SERIAL,
+ .pixclk_falling_edge = 1,
+};
+
+struct spi_gpio_platform_data spigpio_platform_data = {
+ .sck = JZ_GPIO_PORTC(23),
+ .mosi = JZ_GPIO_PORTC(22),
+ .miso = -1,
+ .num_chipselect = 1,
+};
+
+static struct platform_device spigpio_device = {
+ .name = "spi_gpio",
+ .id = 1,
+ .dev = {
+ .platform_data = &spigpio_platform_data,
+ },
+};
+
+static struct spi_board_info qi_lb60_spi_board_info[] = {
+ {
+ .modalias = "ili8960",
+ .controller_data = (void *)JZ_GPIO_PORTC(21),
+ .chip_select = 0,
+ .bus_num = 1,
+ .max_speed_hz = 30 * 1000,
+ .mode = SPI_3WIRE,
+ },
+};
+
+/* Battery */
+static struct jz_battery_platform_data qi_lb60_battery_pdata = {
+ .gpio_charge = JZ_GPIO_PORTC(27),
+ .gpio_charge_active_low = 1,
+ .info = {
+ .name = "battery",
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+ .voltage_max_design = 4200000,
+ .voltage_min_design = 3600000,
+ },
+};
+
+/* GPIO Key: power */
+static struct gpio_keys_button qi_lb60_gpio_keys_buttons[] = {
+ [0] = {
+ .code = KEY_POWER,
+ .gpio = JZ_GPIO_PORTD(29),
+ .active_low = 1,
+ .desc = "Power",
+ .wakeup = 1,
+ },
+};
+
+static struct gpio_keys_platform_data qi_lb60_gpio_keys_data = {
+ .nbuttons = ARRAY_SIZE(qi_lb60_gpio_keys_buttons),
+ .buttons = qi_lb60_gpio_keys_buttons,
+};
+
+static struct platform_device qi_lb60_gpio_keys = {
+ .name = "gpio-keys",
+ .id = -1,
+ .dev = {
+ .platform_data = &qi_lb60_gpio_keys_data,
+ }
+};
+
+static struct jz4740_mmc_platform_data qi_lb60_mmc_pdata = {
+ .gpio_card_detect = QI_LB60_GPIO_SD_CD,
+ .gpio_read_only = -1,
+ .gpio_power = QI_LB60_GPIO_SD_VCC_EN_N,
+ .power_active_low = 1,
+};
+
+/* OHCI */
+static struct regulator_consumer_supply avt2_usb_regulator_consumer =
+ REGULATOR_SUPPLY("vbus", "jz4740-ohci");
+
+static struct regulator_init_data avt2_usb_regulator_init_data = {
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &avt2_usb_regulator_consumer,
+ .constraints = {
+ .name = "USB power",
+ .min_uV = 5000000,
+ .max_uV = 5000000,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static struct fixed_voltage_config avt2_usb_regulator_data = {
+ .supply_name = "USB power",
+ .microvolts = 5000000,
+ .gpio = JZ_GPIO_PORTB(17),
+ .init_data = &avt2_usb_regulator_init_data,
+};
+
+static struct platform_device avt2_usb_regulator_device = {
+ .name = "reg-fixed-voltage",
+ .id = -1,
+ .dev = {
+ .platform_data = &avt2_usb_regulator_data,
+ }
+};
+
+/* pizo */
+static struct led_pwm qi_lb60_pizo_led = {
+ .name = "nanonote::pizo",
+ .pwm_id = 4,
+ .max_brightness = 255,
+ .pwm_period_ns = 1000000,
+};
+
+static struct led_pwm_platform_data qi_lb60_pizo_data = {
+ .num_leds = 1,
+ .leds = &qi_lb60_pizo_led,
+};
+
+static struct platform_device qi_lb60_pizo_device = {
+ .name = "leds_pwm",
+ .id = -1,
+ .dev = {
+ .platform_data = &qi_lb60_pizo_data,
+ }
+};
+
+static struct platform_device *jz_platform_devices[] __initdata = {
+ &jz4740_udc_device,
+ &jz4740_mmc_device,
+ &jz4740_nand_device,
+ &qi_lb60_keypad,
+ &spigpio_device,
+ &jz4740_framebuffer_device,
+ &jz4740_pcm_device,
+ &jz4740_i2s_device,
+ &jz4740_codec_device,
+ &jz4740_rtc_device,
+ &jz4740_adc_device,
+ &qi_lb60_gpio_keys,
+ &qi_lb60_pizo_device,
+};
+
+static void __init board_gpio_setup(void)
+{
+ /* We only need to enable/disable pullup here for pins used in generic
+ * drivers. Everything else is done by the drivers themselfs. */
+ jz_gpio_disable_pullup(QI_LB60_GPIO_SD_VCC_EN_N);
+ jz_gpio_disable_pullup(QI_LB60_GPIO_SD_CD);
+}
+
+static int __init qi_lb60_init_platform_devices(void)
+{
+ jz4740_framebuffer_device.dev.platform_data = &qi_lb60_fb_pdata;
+ jz4740_nand_device.dev.platform_data = &qi_lb60_nand_pdata;
+ jz4740_adc_device.dev.platform_data = &qi_lb60_battery_pdata;
+ jz4740_mmc_device.dev.platform_data = &qi_lb60_mmc_pdata;
+
+ jz4740_serial_device_register();
+
+ spi_register_board_info(qi_lb60_spi_board_info,
+ ARRAY_SIZE(qi_lb60_spi_board_info));
+
+ if (is_avt2) {
+ platform_device_register(&avt2_usb_regulator_device);
+ platform_device_register(&jz4740_usb_ohci_device);
+ }
+
+ return platform_add_devices(jz_platform_devices,
+ ARRAY_SIZE(jz_platform_devices));
+
+}
+
+struct jz4740_clock_board_data jz4740_clock_bdata = {
+ .ext_rate = 12000000,
+ .rtc_rate = 32768,
+};
+
+static __init int board_avt2(char *str)
+{
+ qi_lb60_mmc_pdata.card_detect_active_low = 1;
+ is_avt2 = true;
+
+ return 1;
+}
+__setup("avt2", board_avt2);
+
+static int __init qi_lb60_board_setup(void)
+{
+ printk(KERN_INFO "Qi Hardware JZ4740 QI %s setup\n",
+ is_avt2 ? "AVT2" : "LB60");
+
+ board_gpio_setup();
+
+ if (qi_lb60_init_platform_devices())
+ panic("Failed to initalize platform devices\n");
+
+ return 0;
+}
+arch_initcall(qi_lb60_board_setup);
--
1.5.6.5

2010-06-19 05:10:31

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 14/26] MIPS: JZ4740: Add Kbuild files

This patch adds the Kbuild files for the JZ4740 architecture and adds JZ4740
support to the MIPS Kbuild files.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Adjust to MIPS Kbuild changes
---
arch/mips/Kbuild.platforms | 1 +
arch/mips/Kconfig | 13 +++++++++++++
arch/mips/jz4740/Kconfig | 8 ++++++++
arch/mips/jz4740/Makefile | 18 ++++++++++++++++++
arch/mips/jz4740/Platform | 5 +++++
5 files changed, 45 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/Kconfig
create mode 100644 arch/mips/jz4740/Makefile
create mode 100644 arch/mips/jz4740/Platform

diff --git a/arch/mips/Kbuild.platforms b/arch/mips/Kbuild.platforms
index ea3b96c..78439b8 100644
--- a/arch/mips/Kbuild.platforms
+++ b/arch/mips/Kbuild.platforms
@@ -9,6 +9,7 @@ platforms += cobalt
platforms += dec
platforms += emma
platforms += jazz
+platforms += jz4740
platforms += lasat
platforms += loongson
platforms += mipssim
diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig
index cdaae94..4d44bad 100644
--- a/arch/mips/Kconfig
+++ b/arch/mips/Kconfig
@@ -162,6 +162,18 @@ config MACH_JAZZ
Members include the Acer PICA, MIPS Magnum 4000, MIPS Millennium and
Olivetti M700-10 workstations.

+config MACH_JZ4740
+ bool "Ingenic JZ4740 based machines"
+ select SYS_HAS_CPU_MIPS32_R1
+ select SYS_SUPPORTS_32BIT_KERNEL
+ select SYS_SUPPORTS_LITTLE_ENDIAN
+ select DMA_NONCOHERENT
+ select IRQ_CPU
+ select GENERIC_GPIO
+ select ARCH_REQUIRE_GPIOLIB
+ select SYS_HAS_EARLY_PRINTK
+ select HAVE_PWM
+
config LASAT
bool "LASAT Networks platforms"
select CEVT_R4K
@@ -686,6 +698,7 @@ endchoice
source "arch/mips/alchemy/Kconfig"
source "arch/mips/bcm63xx/Kconfig"
source "arch/mips/jazz/Kconfig"
+source "arch/mips/jz4740/Kconfig"
source "arch/mips/lasat/Kconfig"
source "arch/mips/pmc-sierra/Kconfig"
source "arch/mips/powertv/Kconfig"
diff --git a/arch/mips/jz4740/Kconfig b/arch/mips/jz4740/Kconfig
new file mode 100644
index 0000000..8a5e850
--- /dev/null
+++ b/arch/mips/jz4740/Kconfig
@@ -0,0 +1,8 @@
+choice
+ prompt "Machine type"
+ depends on MACH_JZ4740
+
+endchoice
+
+config HAVE_PWM
+ bool
diff --git a/arch/mips/jz4740/Makefile b/arch/mips/jz4740/Makefile
new file mode 100644
index 0000000..a803ccb
--- /dev/null
+++ b/arch/mips/jz4740/Makefile
@@ -0,0 +1,18 @@
+#
+# Makefile for the Ingenic JZ4740.
+#
+
+# Object file lists.
+
+obj-y += prom.o irq.o time.o reset.o setup.o dma.o \
+ gpio.o clock.o platform.o timer.o pwm.o serial.o
+
+obj-$(CONFIG_DEBUG_FS) += clock-debugfs.o
+
+# board specific support
+
+# PM support
+
+obj-$(CONFIG_PM) += pm.o
+
+EXTRA_CFLAGS += -Werror -Wall
diff --git a/arch/mips/jz4740/Platform b/arch/mips/jz4740/Platform
new file mode 100644
index 0000000..540bb35
--- /dev/null
+++ b/arch/mips/jz4740/Platform
@@ -0,0 +1,3 @@
+core-$(CONFIG_MACH_JZ4740) += arch/mips/jz4740/
+cflags-$(CONFIG_MACH_JZ4740) += -I$(srctree)/arch/mips/include/asm/mach-jz4740
+load-$(CONFIG_MACH_JZ4740) += 0xffffffff80010000
--
1.5.6.5

2010-06-19 05:10:16

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 12/26] MIPS: JZ4740: Add prom support

This patch adds support for initializing arcs_cmdline on JZ4740 based machines
and provides a prom_putchar implementation.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/jz4740/prom.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 68 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/prom.c

diff --git a/arch/mips/jz4740/prom.c b/arch/mips/jz4740/prom.c
new file mode 100644
index 0000000..cfeac15
--- /dev/null
+++ b/arch/mips/jz4740/prom.c
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC prom code
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/string.h>
+
+#include <linux/serial_reg.h>
+
+#include <asm/bootinfo.h>
+#include <asm/mach-jz4740/base.h>
+
+void jz4740_init_cmdline(int argc, char *argv[])
+{
+ unsigned int count = COMMAND_LINE_SIZE - 1;
+ int i;
+ char *dst = &(arcs_cmdline[0]);
+ char *src;
+
+ for (i = 1; i < argc && count; ++i) {
+ src = argv[i];
+ while (*src && count) {
+ *dst++ = *src++;
+ --count;
+ }
+ *dst++ = ' ';
+ }
+ if (i > 1)
+ --dst;
+
+ *dst = 0;
+}
+
+void __init prom_init(void)
+{
+ jz4740_init_cmdline((int)fw_arg0, (char **)fw_arg1);
+ mips_machtype = MACH_INGENIC_JZ4740;
+}
+
+void __init prom_free_prom_memory(void)
+{
+}
+
+#define UART_REG(_reg) ((void __iomem *)CKSEG1ADDR(JZ4740_UART0_BASE_ADDR + (_reg << 2)))
+
+void prom_putchar(char c)
+{
+ uint8_t lsr;
+
+ do {
+ lsr = readb(UART_REG(UART_LSR));
+ } while ((lsr & UART_LSR_TEMT) == 0);
+
+ writeb(c, UART_REG(UART_TX));
+}
--
1.5.6.5

2010-06-19 05:10:11

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 11/26] MIPS: JZ4740: Add serial support

This patch adds support for serials on a JZ4740 SoC.

The JZ4740 UART interface is almost 16550 compatible.
The UART module needs to be enabled by setting a bit in the FCR register and it
has support for receive timeout interrupts.
Instead of adding yet another machine specific quirk to the 8250 serial driver
we provide a serial_out implementation which sets the required additional flags.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/jz4740/serial.c | 33 +++++++++++++++++++++++++++++++++
arch/mips/jz4740/serial.h | 21 +++++++++++++++++++++
2 files changed, 54 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/serial.c
create mode 100644 arch/mips/jz4740/serial.h

diff --git a/arch/mips/jz4740/serial.c b/arch/mips/jz4740/serial.c
new file mode 100644
index 0000000..d23de45
--- /dev/null
+++ b/arch/mips/jz4740/serial.c
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 serial support
+ *
+ * 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/io.h>
+#include <linux/serial_core.h>
+#include <linux/serial_reg.h>
+
+void jz4740_serial_out(struct uart_port *p, int offset, int value)
+{
+ switch (offset) {
+ case UART_FCR:
+ value |= 0x10; /* Enable uart module */
+ break;
+ case UART_IER:
+ value |= (value & 0x4) << 2;
+ break;
+ default:
+ break;
+ }
+ writeb(value, p->membase + (offset << p->regshift));
+}
diff --git a/arch/mips/jz4740/serial.h b/arch/mips/jz4740/serial.h
new file mode 100644
index 0000000..247a7d8
--- /dev/null
+++ b/arch/mips/jz4740/serial.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 serial support
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MIPS_JZ4740_SERIAL_H__
+
+void jz4740_serial_out(struct uart_port *p, int offset, int value);
+
+#endif
+
--
1.5.6.5

2010-06-19 05:10:04

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 10/26] MIPS: JZ4740: Add PWM support

This patch adds support for the PWM part of the timer unit on a JZ4740 SoC.

Signed-off-by: Lars-Peter Clausen <[email protected]>
---
arch/mips/jz4740/pwm.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 169 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/pwm.c

diff --git a/arch/mips/jz4740/pwm.c b/arch/mips/jz4740/pwm.c
new file mode 100644
index 0000000..f28369c
--- /dev/null
+++ b/arch/mips/jz4740/pwm.c
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform PWM support
+ *
+ * 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/kernel.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-jz4740/gpio.h>
+#include "timer.h"
+
+static struct clk *jz4740_pwm_clk;
+
+DEFINE_MUTEX(jz4740_pwm_mutex);
+
+struct pwm_device {
+ unsigned int id;
+ unsigned int gpio;
+ bool used;
+};
+
+static struct pwm_device jz4740_pwm_list[] = {
+ { 2, JZ_GPIO_PWM2, false },
+ { 3, JZ_GPIO_PWM3, false },
+ { 4, JZ_GPIO_PWM4, false },
+ { 5, JZ_GPIO_PWM5, false },
+ { 6, JZ_GPIO_PWM6, false },
+ { 7, JZ_GPIO_PWM7, false },
+};
+
+struct pwm_device *pwm_request(int id, const char *label)
+{
+ int ret = 0;
+ struct pwm_device *pwm;
+
+ if (!jz4740_pwm_clk) {
+ jz4740_pwm_clk = clk_get(NULL, "ext");
+
+ if (IS_ERR(jz4740_pwm_clk))
+ return ERR_CAST(jz4740_pwm_clk);
+ }
+
+ if (id < 2 || id > 7)
+ return ERR_PTR(-ENOENT);
+
+ mutex_lock(&jz4740_pwm_mutex);
+
+ pwm = &jz4740_pwm_list[id - 2];
+ if (pwm->used)
+ ret = -EBUSY;
+ else
+ pwm->used = true;
+
+ mutex_unlock(&jz4740_pwm_mutex);
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = gpio_request(pwm->gpio, label);
+
+ if (ret) {
+ printk(KERN_ERR "Failed to request pwm gpio: %d\n", ret);
+ pwm->used = false;
+ return ERR_PTR(ret);
+ }
+
+ jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_PWM);
+
+ jz4740_timer_start(id);
+
+ return pwm;
+}
+
+void pwm_free(struct pwm_device *pwm)
+{
+ pwm_disable(pwm);
+ jz4740_timer_set_ctrl(pwm->id, 0);
+
+ jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_NONE);
+ gpio_free(pwm->gpio);
+
+ jz4740_timer_stop(pwm->id);
+
+ pwm->used = false;
+}
+
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ unsigned long long tmp;
+ unsigned long period, duty;
+ unsigned int prescaler = 0;
+ unsigned int id = pwm->id;
+ uint16_t ctrl;
+ bool is_enabled;
+
+ if (duty_ns < 0 || duty_ns > period_ns)
+ return -EINVAL;
+
+ tmp = (unsigned long long)clk_get_rate(jz4740_pwm_clk) * period_ns;
+ do_div(tmp, 1000000000);
+ period = tmp;
+
+ while (period > 0xffff && prescaler < 6) {
+ period >>= 2;
+ ++prescaler;
+ }
+
+ if (prescaler == 6)
+ return -EINVAL;
+
+ tmp = (unsigned long long)period * duty_ns;
+ do_div(tmp, period_ns);
+ duty = period - tmp;
+
+ if (duty >= period)
+ duty = period - 1;
+
+ is_enabled = jz4740_timer_is_enabled(id);
+ if (is_enabled)
+ pwm_disable(pwm);
+
+ jz4740_timer_set_count(id, 0);
+ jz4740_timer_set_duty(id, duty);
+ jz4740_timer_set_period(id, period);
+
+ ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT |
+ JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN;
+
+ jz4740_timer_set_ctrl(id, ctrl);
+
+ if (is_enabled)
+ pwm_enable(pwm);
+
+ return 0;
+}
+
+int pwm_enable(struct pwm_device *pwm)
+{
+ uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+ ctrl |= JZ_TIMER_CTRL_PWM_ENABLE;
+ jz4740_timer_set_ctrl(pwm->id, ctrl);
+ jz4740_timer_enable(pwm->id);
+
+ return 0;
+}
+
+void pwm_disable(struct pwm_device *pwm)
+{
+ uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+ ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
+ jz4740_timer_disable(pwm->id);
+ jz4740_timer_set_ctrl(pwm->id, ctrl);
+}
--
1.5.6.5

2010-06-19 05:14:40

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v2 01/26] MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip

This patch adds a new cpu type for the JZ4740 to the Linux MIPS architecture code.
It also adds the iomem addresses for the different components found on a JZ4740
SoC.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Use physical addresses for the base addresses
---
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/war.h | 25 +++++++++++++++++++++++++
arch/mips/kernel/cpu-probe.c | 20 ++++++++++++++++++++
arch/mips/mm/tlbex.c | 5 +++++
6 files changed, 90 insertions(+), 1 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/base.h
create mode 100644 arch/mips/include/asm/mach-jz4740/war.h

diff --git a/arch/mips/include/asm/bootinfo.h b/arch/mips/include/asm/bootinfo.h
index 09eee09..15a8ef0 100644
--- a/arch/mips/include/asm/bootinfo.h
+++ b/arch/mips/include/asm/bootinfo.h
@@ -71,6 +71,12 @@
#define MACH_LEMOTE_LL2F 7
#define MACH_LOONGSON_END 8

+/*
+ * Valid machtype for group INGENIC
+ */
+#define MACH_INGENIC_JZ4730 0 /* JZ4730 SOC */
+#define MACH_INGENIC_JZ4740 1 /* JZ4740 SOC */
+
extern char *system_type;
const char *get_system_type(void);

diff --git a/arch/mips/include/asm/cpu.h b/arch/mips/include/asm/cpu.h
index a5acda4..b201a8f 100644
--- a/arch/mips/include/asm/cpu.h
+++ b/arch/mips/include/asm/cpu.h
@@ -34,7 +34,7 @@
#define PRID_COMP_LSI 0x080000
#define PRID_COMP_LEXRA 0x0b0000
#define PRID_COMP_CAVIUM 0x0d0000
-
+#define PRID_COMP_INGENIC 0xd00000

/*
* Assigned values for the product ID register. In order to detect a
@@ -133,6 +133,12 @@
#define PRID_IMP_CAVIUM_CN52XX 0x0700

/*
+ * These are the PRID's for when 23:16 == PRID_COMP_INGENIC
+ */
+
+#define PRID_IMP_JZRISC 0x0200
+
+/*
* Definitions for 7:0 on legacy processors
*/

@@ -219,6 +225,7 @@ enum cpu_type_enum {
CPU_4KC, CPU_4KEC, CPU_4KSC, CPU_24K, CPU_34K, CPU_1004K, CPU_74K,
CPU_ALCHEMY, CPU_PR4450, CPU_BCM3302, CPU_BCM4710,
CPU_BCM6338, CPU_BCM6345, CPU_BCM6348, CPU_BCM6358,
+ CPU_JZRISC,

/*
* MIPS64 class processors
diff --git a/arch/mips/include/asm/mach-jz4740/base.h b/arch/mips/include/asm/mach-jz4740/base.h
new file mode 100644
index 0000000..f373186
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/base.h
@@ -0,0 +1,26 @@
+#ifndef __ASM_MACH_JZ4740_BASE_H__
+#define __ASM_MACH_JZ4740_BASE_H__
+
+#define JZ4740_CPM_BASE_ADDR 0x10000000
+#define JZ4740_INTC_BASE_ADDR 0x10001000
+#define JZ4740_WDT_BASE_ADDR 0x10002000
+#define JZ4740_TCU_BASE_ADDR 0x10002010
+#define JZ4740_RTC_BASE_ADDR 0x10003000
+#define JZ4740_GPIO_BASE_ADDR 0x10010000
+#define JZ4740_AIC_BASE_ADDR 0x10020000
+#define JZ4740_MSC_BASE_ADDR 0x10021000
+#define JZ4740_UART0_BASE_ADDR 0x10030000
+#define JZ4740_UART1_BASE_ADDR 0x10031000
+#define JZ4740_I2C_BASE_ADDR 0x10042000
+#define JZ4740_SSI_BASE_ADDR 0x10043000
+#define JZ4740_SADC_BASE_ADDR 0x10070000
+#define JZ4740_EMC_BASE_ADDR 0x13010000
+#define JZ4740_DMAC_BASE_ADDR 0x13020000
+#define JZ4740_UHC_BASE_ADDR 0x13030000
+#define JZ4740_UDC_BASE_ADDR 0x13040000
+#define JZ4740_LCD_BASE_ADDR 0x13050000
+#define JZ4740_SLCD_BASE_ADDR 0x13050000
+#define JZ4740_CIM_BASE_ADDR 0x13060000
+#define JZ4740_IPU_BASE_ADDR 0x13080000
+
+#endif
diff --git a/arch/mips/include/asm/mach-jz4740/war.h b/arch/mips/include/asm/mach-jz4740/war.h
new file mode 100644
index 0000000..3a5bc17
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/war.h
@@ -0,0 +1,25 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright (C) 2002, 2004, 2007 by Ralf Baechle <[email protected]>
+ */
+#ifndef __ASM_MIPS_MACH_JZ4740_WAR_H
+#define __ASM_MIPS_MACH_JZ4740_WAR_H
+
+#define R4600_V1_INDEX_ICACHEOP_WAR 0
+#define R4600_V1_HIT_CACHEOP_WAR 0
+#define R4600_V2_HIT_CACHEOP_WAR 0
+#define R5432_CP0_INTERRUPT_WAR 0
+#define BCM1250_M3_WAR 0
+#define SIBYTE_1956_WAR 0
+#define MIPS4K_ICACHE_REFILL_WAR 0
+#define MIPS_CACHE_SYNC_WAR 0
+#define TX49XX_ICACHE_INDEX_INV_WAR 0
+#define RM9000_CDEX_SMP_WAR 0
+#define ICACHE_REFILLS_WORKAROUND_WAR 0
+#define R10000_LLSC_WAR 0
+#define MIPS34K_MISSED_ITLB_WAR 0
+
+#endif /* __ASM_MIPS_MACH_JZ4740_WAR_H */
diff --git a/arch/mips/kernel/cpu-probe.c b/arch/mips/kernel/cpu-probe.c
index 3562b85..9b66331 100644
--- a/arch/mips/kernel/cpu-probe.c
+++ b/arch/mips/kernel/cpu-probe.c
@@ -187,6 +187,7 @@ void __init check_wait(void)
case CPU_BCM6358:
case CPU_CAVIUM_OCTEON:
case CPU_CAVIUM_OCTEON_PLUS:
+ case CPU_JZRISC:
cpu_wait = r4k_wait;
break;

@@ -956,6 +957,22 @@ platform:
}
}

+static inline void cpu_probe_ingenic(struct cpuinfo_mips *c, unsigned int cpu)
+{
+ decode_configs(c);
+ /* JZRISC does not implement the CP0 counter. */
+ c->options &= ~MIPS_CPU_COUNTER;
+ switch (c->processor_id & 0xff00) {
+ case PRID_IMP_JZRISC:
+ c->cputype = CPU_JZRISC;
+ __cpu_name[cpu] = "Ingenic JZRISC";
+ break;
+ default:
+ panic("Unknown Ingenic Processor ID!");
+ break;
+ }
+}
+
const char *__cpu_name[NR_CPUS];
const char *__elf_platform;

@@ -994,6 +1011,9 @@ __cpuinit void cpu_probe(void)
case PRID_COMP_CAVIUM:
cpu_probe_cavium(c, cpu);
break;
+ case PRID_COMP_INGENIC:
+ cpu_probe_ingenic(c, cpu);
+ break;
}

BUG_ON(!__cpu_name[cpu]);
diff --git a/arch/mips/mm/tlbex.c b/arch/mips/mm/tlbex.c
index 86f004d..4510e61 100644
--- a/arch/mips/mm/tlbex.c
+++ b/arch/mips/mm/tlbex.c
@@ -409,6 +409,11 @@ static void __cpuinit build_tlb_write_entry(u32 **p, struct uasm_label **l,
tlbw(p);
break;

+ case CPU_JZRISC:
+ tlbw(p);
+ uasm_i_nop(p);
+ break;
+
default:
panic("No TLB refill handler yet (CPU type: %d)",
current_cpu_data.cputype);
--
1.5.6.5

2010-06-19 08:36:41

by Jean Delvare

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver

On Sat, 19 Jun 2010 07:08:28 +0200, Lars-Peter Clausen wrote:
> This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Jonathan Cameron <[email protected]>
> Cc: [email protected]
>
> ---
> Changes since v1
> - Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
> ADC driver now only reads the adcin value.
> ---
> drivers/hwmon/Kconfig | 11 +++
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/jz4740-hwmon.c | 206 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 218 insertions(+), 0 deletions(-)
> create mode 100644 drivers/hwmon/jz4740-hwmon.c
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 569082c..51fc2f6 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -446,6 +446,17 @@ config SENSORS_IT87
> This driver can also be built as a module. If so, the module
> will be called it87.
>
> +config SENSORS_JZ4740
> + tristate "Ingenic JZ4740 SoC ADC driver"
> + depends on MACH_JZ4740
> + help
> + If you say yes here you get support for the Ingenic JZ4740 SoC ADC core.
> + It is required for the JZ4740 battery and touchscreen driver and is used
> + to synchronize access to the adc module between those two.
> +
> + This driver can also be build as a module. If so, the module will be
> + called jz4740-adc.
> +
> config SENSORS_LM63
> tristate "National Semiconductor LM63 and LM64"
> depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index bca0d45..dffbdff 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
> obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
> obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
> obj-$(CONFIG_SENSORS_IT87) += it87.o
> +obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
> obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
> obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
> obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o
> diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c
> new file mode 100644
> index 0000000..f53d15e
> --- /dev/null
> +++ b/drivers/hwmon/jz4740-hwmon.c
> @@ -0,0 +1,206 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC HWMON 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/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include <linux/mfd/core.h>
> +
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +
> +struct jz4740_hwmon {
> + struct resource *mem;
> + void __iomem *base;
> +
> + int irq;
> +
> + struct mfd_cell *cell;
> + struct device *hwmon;
> +
> + struct completion read_completion;
> +
> + struct mutex lock;
> +};
> +
> +static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
> +{
> + struct jz4740_hwmon *hwmon = data;
> +
> + complete(&hwmon->read_completion);
> + return IRQ_HANDLED;
> +}
> +
> +static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
> + struct device_attribute *dev_attr, char *buf)
> +{
> + struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
> + unsigned long t;
> + uint16_t val;
> + int ret;
> +
> + mutex_lock(&hwmon->lock);
> +
> + INIT_COMPLETION(hwmon->read_completion);
> +
> + enable_irq(hwmon->irq);
> + hwmon->cell->enable(to_platform_device(dev));
> +
> + t = wait_for_completion_interruptible_timeout(&hwmon->read_completion, HZ);
> +
> + if (t > 0) {
> + val = readw(hwmon->base);
> + ret = sprintf(buf, "%d\n", val);

What is the unit of "val"? The value returned to userspace must be in
mV, so in most cases a simple conversion is needed in the driver.

> + } else {
> + ret = t ? t : -ETIMEDOUT;
> + }
> +
> + hwmon->cell->disable(to_platform_device(dev));
> + disable_irq(hwmon->irq);
> +
> + mutex_unlock(&hwmon->lock);
> +
> + return ret;
> +}
> +
> +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL, 0);
> +
> +static int __devinit jz4740_hwmon_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jz4740_hwmon *hwmon;
> +
> + hwmon = kmalloc(sizeof(*hwmon), GFP_KERNEL);
> +
> + hwmon->cell = pdev->dev.platform_data;
> +
> + hwmon->irq = platform_get_irq(pdev, 0);
> + if (hwmon->irq < 0) {
> + ret = hwmon->irq;
> + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
> + goto err_free;
> + }
> +
> + hwmon->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!hwmon->mem) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
> + goto err_free;
> + }
> +
> + hwmon->mem = request_mem_region(hwmon->mem->start,
> + resource_size(hwmon->mem), pdev->name);
> + if (!hwmon->mem) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + goto err_free;
> + }
> +
> + hwmon->base = ioremap_nocache(hwmon->mem->start, resource_size(hwmon->mem));
> + if (!hwmon->base) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> + goto err_release_mem_region;
> + }
> +
> + init_completion(&hwmon->read_completion);
> + mutex_init(&hwmon->lock);
> +
> + platform_set_drvdata(pdev, hwmon);
> +
> + ret = request_irq(hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
> + goto err_iounmap;
> + }
> + disable_irq(hwmon->irq);
> +
> + ret = device_create_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to create sysfs file: %d\n", ret);
> + goto err_free_irq;
> + }

You must create a name attribute as well, if you want your device to be
supported by libsensors.

> +
> + hwmon->hwmon = hwmon_device_register(&pdev->dev);
> + if (IS_ERR(hwmon->hwmon)) {
> + ret = PTR_ERR(hwmon->hwmon);
> + goto err_remove_file;
> + }
> +
> + return 0;
> +
> +err_remove_file:
> + device_remove_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
> +err_free_irq:
> + free_irq(hwmon->irq, hwmon);
> +err_iounmap:
> + platform_set_drvdata(pdev, NULL);
> + iounmap(hwmon->base);
> +err_release_mem_region:
> + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
> +err_free:
> + kfree(hwmon);
> +
> + return ret;
> +}
> +
> +static int __devexit jz4740_hwmon_remove(struct platform_device *pdev)
> +{
> + struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev);
> +
> + hwmon_device_unregister(hwmon->hwmon);
> + device_remove_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
> +
> + free_irq(hwmon->irq, hwmon);
> +
> + iounmap(hwmon->base);
> + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
> +
> + platform_set_drvdata(pdev, NULL);
> + kfree(hwmon);
> +
> + return 0;
> +}
> +
> +struct platform_driver jz4740_hwmon_driver = {
> + .probe = jz4740_hwmon_probe,
> + .remove = __devexit_p(jz4740_hwmon_remove),
> + .driver = {
> + .name = "jz4740-hwmon",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init jz4740_hwmon_init(void)
> +{
> + return platform_driver_register(&jz4740_hwmon_driver);
> +}
> +module_init(jz4740_hwmon_init);
> +
> +static void __exit jz4740_hwmon_exit(void)
> +{
> + platform_driver_unregister(&jz4740_hwmon_driver);
> +}
> +module_exit(jz4740_hwmon_exit);
> +
> +MODULE_DESCRIPTION("JZ4740 SoC HWMON driver");
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-hwmon");


--
Jean Delvare

2010-06-19 10:45:50

by Marek Vasut

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
> This patch adds support for the RTC unit on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Alessandro Zummo <[email protected]>
> Cc: Paul Gortmaker <[email protected]>
> Cc: Wan ZongShun <[email protected]>
> Cc: Marek Vasut <[email protected]>
> Cc: [email protected]
>
> ---
> Changes since v1
> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
> - Check whether rtc structure could be allocated
> - Fix deadlocks which could occur if the HW was broken
> ---
> drivers/rtc/Kconfig | 11 ++
> drivers/rtc/Makefile | 1 +
> drivers/rtc/rtc-jz4740.c | 341
> ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353
> insertions(+), 0 deletions(-)
> create mode 100644 drivers/rtc/rtc-jz4740.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 10ba12c..d0ed7e6 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
> This driver can also be built as a module. If so, the module
> will be called rtc-mpc5121.
>
> +config RTC_DRV_JZ4740
> + tristate "Ingenic JZ4740 SoC"
> + depends on RTC_CLASS
> + depends on MACH_JZ4740
> + help
> + If you say yes here you get support for the Ingenic JZ4740 SoC RTC
> + controller.
> +
> + This driver can also be buillt as a module. If so, the module
> + will be called rtc-jz4740.
> +
> endif # RTC_CLASS
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 5adbba7..fedf9bb 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
> obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
> obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
> +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
> obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
> obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
> obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
> new file mode 100644
> index 0000000..720afb2
> --- /dev/null
> +++ b/drivers/rtc/rtc-jz4740.c
> @@ -0,0 +1,341 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC RTC 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/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/rtc.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#define JZ_REG_RTC_CTRL 0x00
> +#define JZ_REG_RTC_SEC 0x04
> +#define JZ_REG_RTC_SEC_ALARM 0x08
> +#define JZ_REG_RTC_REGULATOR 0x0C
> +#define JZ_REG_RTC_HIBERNATE 0x20
> +#define JZ_REG_RTC_SCRATCHPAD 0x34
> +
> +#define JZ_RTC_CTRL_WRDY BIT(7)
> +#define JZ_RTC_CTRL_1HZ BIT(6)
> +#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
> +#define JZ_RTC_CTRL_AF BIT(4)
> +#define JZ_RTC_CTRL_AF_IRQ BIT(3)
> +#define JZ_RTC_CTRL_AE BIT(2)
> +#define JZ_RTC_CTRL_ENABLE BIT(0)
> +
> +struct jz4740_rtc {
> + struct resource *mem;
> + void __iomem *base;
> +
> + struct rtc_device *rtc;
> +
> + unsigned int irq;
> +
> + spinlock_t lock;
> +};
> +
> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t
> reg) +{
> + return readl(rtc->base + reg);
> +}
> +
> +static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
> +{
> + uint32_t ctrl;
> + int timeout = 1000;
> +
> + do {
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);

if (!timeout) {
scream_and_die_in_pain();
dev_err("I died");
... or something like that ... what if it times out, in this implementation,
noone will know this failed.

I haven't looked through the whole source code, but can't this be wrapped into
the reg_write() ?
}
> +}
> +
> +static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t
> reg, + uint32_t val)
> +{
> + jz4740_rtc_wait_write_ready(rtc);
> + writel(val, rtc->base + reg);
> +}
> +
> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t
> mask, + uint32_t val)
> +{
> + unsigned long flags;
> + uint32_t ctrl;
> +
> + spin_lock_irqsave(&rtc->lock, flags);

Can't we use local_irq_save()/local_irq_restore() ?

> +
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> +
> + /* Don't clear interrupt flags by accident */
> + ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
> +
> + ctrl &= ~mask;
> + ctrl |= val;
> +
> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
> +
> + spin_unlock_irqrestore(&rtc->lock, flags);
> +}
> +
> +static int jz4740_rtc_read_time(struct device *dev, struct rtc_time *time)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + uint32_t secs, secs2;
> + int timeout = 5;
> +
> + /* If the seconds register is read while it is updated, it can contain a
> + * bogus value. This can be avoided by making sure that two consecutive
> + * reads have the same value.
> + */
> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> +
> + while (secs != secs2 && --timeout) {
> + secs = secs2;
> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> + }
> +
> + if (timeout == 0)
> + return -EIO;
> +
> + rtc_time_to_tm(secs, time);
> +
> + return rtc_valid_tm(time);
> +}
> +
> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> +
> + if ((uint32_t)secs != secs)
> + return -EINVAL;

Is the typecast here necessary ?

> +
> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
> +
> + return 0;
> +}
> +
> +static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm
> *alrm) +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + uint32_t secs;
> + uint32_t ctrl;
> +
> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
> +
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> +
> + alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
> + alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
> +

Is the double negation (!!) here necessary ?

> + rtc_time_to_tm(secs, &alrm->time);
> +
> + return rtc_valid_tm(&alrm->time);
> +}
> +
> +static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm
> *alrm) +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + unsigned long secs;
> +
> + rtc_tm_to_time(&alrm->time, &secs);
> +
> + if ((uint32_t)secs != secs)
> + return -EINVAL;

DTTO above

> +
> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, (uint32_t)secs);

DTTO

> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
> + alrm->enabled ? JZ_RTC_CTRL_AE : 0);

Possibly the double negation above wasn't necessary

> +
> + return 0;
> +}
> +
> +static inline int jz4740_irq_enable(struct device *dev, int irq,
> + unsigned int enable)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
> +
> + return 0;
> +}
> +
> +static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned int
> enable) +{
> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
> +}
> +
> +static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int
> enable) +{
> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
> +}
> +
> +static struct rtc_class_ops jz4740_rtc_ops = {
> + .read_time = jz4740_rtc_read_time,
> + .set_mmss = jz4740_rtc_set_mmss,
> + .read_alarm = jz4740_rtc_read_alarm,
> + .set_alarm = jz4740_rtc_set_alarm,
> + .update_irq_enable = jz4740_rtc_update_irq_enable,
> + .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
> +};
> +
> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
> +{
> + struct jz4740_rtc *rtc = data;
> + uint32_t ctrl;
> + unsigned long events = 0;
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> +
> + if (ctrl & JZ_RTC_CTRL_1HZ)
> + events |= (RTC_UF | RTC_IRQF);
> +
> + if (ctrl & JZ_RTC_CTRL_AF)
> + events |= (RTC_AF | RTC_IRQF);
> +
> + rtc_update_irq(rtc->rtc, 1, events);
> +
> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, 0);
> +
> + return IRQ_HANDLED;
> +}
> +
> +void jz4740_rtc_poweroff(struct device *dev)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
> +}
> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
> +
> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jz4740_rtc *rtc;
> + uint32_t scratchpad;
> +
> + rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);
> + if (!rtc)
> + return -ENOMEM;
> +
> + rtc->irq = platform_get_irq(pdev, 0);
> + if (rtc->irq < 0) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform irq\n");
> + goto err_free;
> + }
> +
> + rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!rtc->mem) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
> + goto err_free;
> + }
> +
> + rtc->mem = request_mem_region(rtc->mem->start, resource_size(rtc->mem),
> + pdev->name);
> + if (!rtc->mem) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + goto err_free;
> + }
> +
> + rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
> + if (!rtc->base) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> + goto err_release_mem_region;
> + }
> +
> + spin_lock_init(&rtc->lock);
> +
> + platform_set_drvdata(pdev, rtc);

dev_set_drvdata()?

> +
> + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &jz4740_rtc_ops,
> + THIS_MODULE);
> + if (IS_ERR(rtc->rtc)) {
> + ret = PTR_ERR(rtc->rtc);
> + dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret);
> + goto err_iounmap;
> + }
> +
> + ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
> + pdev->name, rtc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret);
> + goto err_unregister_rtc;
> + }
> +
> + scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
> + if (scratchpad != 0x12345678) {
> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
> + }
> +
> + return 0;
> +
> +err_unregister_rtc:
> + rtc_device_unregister(rtc->rtc);
> +err_iounmap:
> + platform_set_drvdata(pdev, NULL);
> + iounmap(rtc->base);
> +err_release_mem_region:
> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
> +err_free:
> + kfree(rtc);
> +
> + return ret;
> +}
> +
> +static int __devexit jz4740_rtc_remove(struct platform_device *pdev)
> +{
> + struct jz4740_rtc *rtc = platform_get_drvdata(pdev);

dev_get_drvdata();

> +
> + free_irq(rtc->irq, rtc);
> +
> + rtc_device_unregister(rtc->rtc);
> +
> + iounmap(rtc->base);
> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
> +
> + kfree(rtc);
> +
> + platform_set_drvdata(pdev, NULL);

DTTO

> +
> + return 0;
> +}
> +
> +struct platform_driver jz4740_rtc_driver = {
> + .probe = jz4740_rtc_probe,
> + .remove = __devexit_p(jz4740_rtc_remove),
> + .driver = {
> + .name = "jz4740-rtc",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init jz4740_rtc_init(void)
> +{
> + return platform_driver_register(&jz4740_rtc_driver);
> +}
> +module_init(jz4740_rtc_init);
> +
> +static void __exit jz4740_rtc_exit(void)
> +{
> + platform_driver_unregister(&jz4740_rtc_driver);
> +}
> +module_exit(jz4740_rtc_exit);
> +
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
> +MODULE_ALIAS("platform:jz4740-rtc");

Cheers

2010-06-19 12:59:17

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver

Jean Delvare wrote:
> On Sat, 19 Jun 2010 07:08:28 +0200, Lars-Peter Clausen wrote:
>
>> This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>> Cc: Jonathan Cameron <[email protected]>
>> Cc: [email protected]
>>
>> ---
>> Changes since v1
>> - Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
>> ADC driver now only reads the adcin value.
>> ---
>> drivers/hwmon/Kconfig | 11 +++
>> drivers/hwmon/Makefile | 1 +
>> drivers/hwmon/jz4740-hwmon.c | 206 ++++++++++++++++++++++++++++++++++++++++++
>> 3 files changed, 218 insertions(+), 0 deletions(-)
>> create mode 100644 drivers/hwmon/jz4740-hwmon.c
>>
>> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
>> index 569082c..51fc2f6 100644
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -446,6 +446,17 @@ config SENSORS_IT87
>> This driver can also be built as a module. If so, the module
>> will be called it87.
>>
>> +config SENSORS_JZ4740
>> + tristate "Ingenic JZ4740 SoC ADC driver"
>> + depends on MACH_JZ4740
>> + help
>> + If you say yes here you get support for the Ingenic JZ4740 SoC ADC core.
>> + It is required for the JZ4740 battery and touchscreen driver and is used
>> + to synchronize access to the adc module between those two.
>> +
>> + This driver can also be build as a module. If so, the module will be
>> + called jz4740-adc.
>> +
>> config SENSORS_LM63
>> tristate "National Semiconductor LM63 and LM64"
>> depends on I2C
>> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
>> index bca0d45..dffbdff 100644
>> --- a/drivers/hwmon/Makefile
>> +++ b/drivers/hwmon/Makefile
>> @@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
>> obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
>> obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
>> obj-$(CONFIG_SENSORS_IT87) += it87.o
>> +obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
>> obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
>> obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
>> obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o
>> diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c
>> new file mode 100644
>> index 0000000..f53d15e
>> --- /dev/null
>> +++ b/drivers/hwmon/jz4740-hwmon.c
>> @@ -0,0 +1,206 @@
>> +/*
>> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>> + * JZ4740 SoC HWMON 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/err.h>
>> +#include <linux/interrupt.h>
>> +#include <linux/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/slab.h>
>> +
>> +#include <linux/mfd/core.h>
>> +
>> +#include <linux/hwmon.h>
>> +#include <linux/hwmon-sysfs.h>
>> +
>> +struct jz4740_hwmon {
>> + struct resource *mem;
>> + void __iomem *base;
>> +
>> + int irq;
>> +
>> + struct mfd_cell *cell;
>> + struct device *hwmon;
>> +
>> + struct completion read_completion;
>> +
>> + struct mutex lock;
>> +};
>> +
>> +static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
>> +{
>> + struct jz4740_hwmon *hwmon = data;
>> +
>> + complete(&hwmon->read_completion);
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
>> + struct device_attribute *dev_attr, char *buf)
>> +{
>> + struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
>> + unsigned long t;
>> + uint16_t val;
>> + int ret;
>> +
>> + mutex_lock(&hwmon->lock);
>> +
>> + INIT_COMPLETION(hwmon->read_completion);
>> +
>> + enable_irq(hwmon->irq);
>> + hwmon->cell->enable(to_platform_device(dev));
>> +
>> + t = wait_for_completion_interruptible_timeout(&hwmon->read_completion, HZ);
>> +
>> + if (t > 0) {
>> + val = readw(hwmon->base);
>> + ret = sprintf(buf, "%d\n", val);
>>
>
> What is the unit of "val"? The value returned to userspace must be in
> mV, so in most cases a simple conversion is needed in the driver.
>
>
Right, forgot about to change it, sorry.
>> + } else {
>> + ret = t ? t : -ETIMEDOUT;
>> + }
>> +
>> + hwmon->cell->disable(to_platform_device(dev));
>> + disable_irq(hwmon->irq);
>> +
>> + mutex_unlock(&hwmon->lock);
>> +
>> + return ret;
>> +}
>> +
>> +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL, 0);
>> +
>> +static int __devinit jz4740_hwmon_probe(struct platform_device *pdev)
>> +{
>> + int ret;
>> + struct jz4740_hwmon *hwmon;
>> +
>> + hwmon = kmalloc(sizeof(*hwmon), GFP_KERNEL);
>> +
>> + hwmon->cell = pdev->dev.platform_data;
>> +
>> + hwmon->irq = platform_get_irq(pdev, 0);
>> + if (hwmon->irq < 0) {
>> + ret = hwmon->irq;
>> + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
>> + goto err_free;
>> + }
>> +
>> + hwmon->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + if (!hwmon->mem) {
>> + ret = -ENOENT;
>> + dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
>> + goto err_free;
>> + }
>> +
>> + hwmon->mem = request_mem_region(hwmon->mem->start,
>> + resource_size(hwmon->mem), pdev->name);
>> + if (!hwmon->mem) {
>> + ret = -EBUSY;
>> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
>> + goto err_free;
>> + }
>> +
>> + hwmon->base = ioremap_nocache(hwmon->mem->start, resource_size(hwmon->mem));
>> + if (!hwmon->base) {
>> + ret = -EBUSY;
>> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
>> + goto err_release_mem_region;
>> + }
>> +
>> + init_completion(&hwmon->read_completion);
>> + mutex_init(&hwmon->lock);
>> +
>> + platform_set_drvdata(pdev, hwmon);
>> +
>> + ret = request_irq(hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
>> + goto err_iounmap;
>> + }
>> + disable_irq(hwmon->irq);
>> +
>> + ret = device_create_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to create sysfs file: %d\n", ret);
>> + goto err_free_irq;
>> + }
>>
>
> You must create a name attribute as well, if you want your device to be
> supported by libsensors.
>
Ok.
>
>> +
>> + hwmon->hwmon = hwmon_device_register(&pdev->dev);
>> + if (IS_ERR(hwmon->hwmon)) {
>> + ret = PTR_ERR(hwmon->hwmon);
>> + goto err_remove_file;
>> + }
>> +
>> + return 0;
>> +
>> +err_remove_file:
>> + device_remove_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
>> +err_free_irq:
>> + free_irq(hwmon->irq, hwmon);
>> +err_iounmap:
>> + platform_set_drvdata(pdev, NULL);
>> + iounmap(hwmon->base);
>> +err_release_mem_region:
>> + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
>> +err_free:
>> + kfree(hwmon);
>> +
>> + return ret;
>> +}
>> +
>> +static int __devexit jz4740_hwmon_remove(struct platform_device *pdev)
>> +{
>> + struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev);
>> +
>> + hwmon_device_unregister(hwmon->hwmon);
>> + device_remove_file(&pdev->dev, &sensor_dev_attr_in0_input.dev_attr);
>> +
>> + free_irq(hwmon->irq, hwmon);
>> +
>> + iounmap(hwmon->base);
>> + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
>> +
>> + platform_set_drvdata(pdev, NULL);
>> + kfree(hwmon);
>> +
>> + return 0;
>> +}
>> +
>> +struct platform_driver jz4740_hwmon_driver = {
>> + .probe = jz4740_hwmon_probe,
>> + .remove = __devexit_p(jz4740_hwmon_remove),
>> + .driver = {
>> + .name = "jz4740-hwmon",
>> + .owner = THIS_MODULE,
>> + },
>> +};
>> +
>> +static int __init jz4740_hwmon_init(void)
>> +{
>> + return platform_driver_register(&jz4740_hwmon_driver);
>> +}
>> +module_init(jz4740_hwmon_init);
>> +
>> +static void __exit jz4740_hwmon_exit(void)
>> +{
>> + platform_driver_unregister(&jz4740_hwmon_driver);
>> +}
>> +module_exit(jz4740_hwmon_exit);
>> +
>> +MODULE_DESCRIPTION("JZ4740 SoC HWMON driver");
>> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:jz4740-hwmon");
>>
>
>
Thanks for reviewing
-Lars

2010-06-19 13:05:52

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

Hi

Marek Vasut wrote:
> Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
>
>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>> Cc: Alessandro Zummo <[email protected]>
>> Cc: Paul Gortmaker <[email protected]>
>> Cc: Wan ZongShun <[email protected]>
>> Cc: Marek Vasut <[email protected]>
>> Cc: [email protected]
>>
>> ---
>> Changes since v1
>> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
>> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
>> - Check whether rtc structure could be allocated
>> - Fix deadlocks which could occur if the HW was broken
>> ---
>> drivers/rtc/Kconfig | 11 ++
>> drivers/rtc/Makefile | 1 +
>> drivers/rtc/rtc-jz4740.c | 341
>> ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353
>> insertions(+), 0 deletions(-)
>> create mode 100644 drivers/rtc/rtc-jz4740.c
>>
>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>> index 10ba12c..d0ed7e6 100644
>> --- a/drivers/rtc/Kconfig
>> +++ b/drivers/rtc/Kconfig
>> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
>> This driver can also be built as a module. If so, the module
>> will be called rtc-mpc5121.
>>
>> +config RTC_DRV_JZ4740
>> + tristate "Ingenic JZ4740 SoC"
>> + depends on RTC_CLASS
>> + depends on MACH_JZ4740
>> + help
>> + If you say yes here you get support for the Ingenic JZ4740 SoC RTC
>> + controller.
>> +
>> + This driver can also be buillt as a module. If so, the module
>> + will be called rtc-jz4740.
>> +
>> endif # RTC_CLASS
>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>> index 5adbba7..fedf9bb 100644
>> --- a/drivers/rtc/Makefile
>> +++ b/drivers/rtc/Makefile
>> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
>> obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
>> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
>> obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
>> +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
>> obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
>> obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
>> obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
>> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
>> new file mode 100644
>> index 0000000..720afb2
>> --- /dev/null
>> +++ b/drivers/rtc/rtc-jz4740.c
>> @@ -0,0 +1,341 @@
>> +/*
>> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>> + * JZ4740 SoC RTC 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/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/rtc.h>
>> +#include <linux/slab.h>
>> +#include <linux/spinlock.h>
>> +
>> +#define JZ_REG_RTC_CTRL 0x00
>> +#define JZ_REG_RTC_SEC 0x04
>> +#define JZ_REG_RTC_SEC_ALARM 0x08
>> +#define JZ_REG_RTC_REGULATOR 0x0C
>> +#define JZ_REG_RTC_HIBERNATE 0x20
>> +#define JZ_REG_RTC_SCRATCHPAD 0x34
>> +
>> +#define JZ_RTC_CTRL_WRDY BIT(7)
>> +#define JZ_RTC_CTRL_1HZ BIT(6)
>> +#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
>> +#define JZ_RTC_CTRL_AF BIT(4)
>> +#define JZ_RTC_CTRL_AF_IRQ BIT(3)
>> +#define JZ_RTC_CTRL_AE BIT(2)
>> +#define JZ_RTC_CTRL_ENABLE BIT(0)
>> +
>> +struct jz4740_rtc {
>> + struct resource *mem;
>> + void __iomem *base;
>> +
>> + struct rtc_device *rtc;
>> +
>> + unsigned int irq;
>> +
>> + spinlock_t lock;
>> +};
>> +
>> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t
>> reg) +{
>> + return readl(rtc->base + reg);
>> +}
>> +
>> +static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
>> +{
>> + uint32_t ctrl;
>> + int timeout = 1000;
>> +
>> + do {
>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>> + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
>>
>
> if (!timeout) {
> scream_and_die_in_pain();
> dev_err("I died");
> ... or something like that ... what if it times out, in this implementation,
> noone will know this failed.
>
> I haven't looked through the whole source code, but can't this be wrapped into
> the reg_write() ?
> }
>
Well IF it will ever die, you'll notice cause your rtc clock won't work
anymore.

It could be wrapped into reg_write, but there is a different version of
the SoC with the only difference of the RTC unit being that a different
mechanism is used determine whether it is ok to write or not. So it
makes sense to keep it seperate.
>> +}
>> +
>> +static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t
>> reg, + uint32_t val)
>> +{
>> + jz4740_rtc_wait_write_ready(rtc);
>> + writel(val, rtc->base + reg);
>> +}
>> +
>> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t
>> mask, + uint32_t val)
>> +{
>> + unsigned long flags;
>> + uint32_t ctrl;
>> +
>> + spin_lock_irqsave(&rtc->lock, flags);
>>
>
> Can't we use local_irq_save()/local_irq_restore() ?
>
Why would that be preferable? In the non-debug, non-rt case this will
expand to local_irq_{save,restore} anyway, but you'll lose the semantics
of an lock.
>
>> +
>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>> +
>> + /* Don't clear interrupt flags by accident */
>> + ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
>> +
>> + ctrl &= ~mask;
>> + ctrl |= val;
>> +
>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
>> +
>> + spin_unlock_irqrestore(&rtc->lock, flags);
>> +}
>> +
>> +static int jz4740_rtc_read_time(struct device *dev, struct rtc_time *time)
>> +{
>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>> + uint32_t secs, secs2;
>> + int timeout = 5;
>> +
>> + /* If the seconds register is read while it is updated, it can contain a
>> + * bogus value. This can be avoided by making sure that two consecutive
>> + * reads have the same value.
>> + */
>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>> +
>> + while (secs != secs2 && --timeout) {
>> + secs = secs2;
>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>> + }
>> +
>> + if (timeout == 0)
>> + return -EIO;
>> +
>> + rtc_time_to_tm(secs, time);
>> +
>> + return rtc_valid_tm(time);
>> +}
>> +
>> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
>> +{
>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>> +
>> + if ((uint32_t)secs != secs)
>> + return -EINVAL;
>>
>
> Is the typecast here necessary ?
>
Strictly speaking not.
>
>> +
>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
>> +
>> + return 0;
>> +}
>> +
>> +static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm
>> *alrm) +{
>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>> + uint32_t secs;
>> + uint32_t ctrl;
>> +
>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
>> +
>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>> +
>> + alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
>> + alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
>> +
>>
>
> Is the double negation (!!) here necessary ?
>
>
To quote rtc.h "/* 0 = alarm disabled, 1 = alarm enabled */", so yes.
>> + rtc_time_to_tm(secs, &alrm->time);
>> +
>> + return rtc_valid_tm(&alrm->time);
>> +}
>> +
>> +static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm
>> *alrm) +{
>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>> + unsigned long secs;
>> +
>> + rtc_tm_to_time(&alrm->time, &secs);
>> +
>> + if ((uint32_t)secs != secs)
>> + return -EINVAL;
>>
>
> DTTO above
>
>
>> +
>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, (uint32_t)secs);
>>
>
> DTTO
>
>
>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
>> + alrm->enabled ? JZ_RTC_CTRL_AE : 0);
>>
>
> Possibly the double negation above wasn't necessary
>
>
>> +
>> + return 0;
>> +}
>> +
>> +static inline int jz4740_irq_enable(struct device *dev, int irq,
>> + unsigned int enable)
>> +{
>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>> + jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
>> +
>> + return 0;
>> +}
>> +
>> +static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned int
>> enable) +{
>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
>> +}
>> +
>> +static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int
>> enable) +{
>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
>> +}
>> +
>> +static struct rtc_class_ops jz4740_rtc_ops = {
>> + .read_time = jz4740_rtc_read_time,
>> + .set_mmss = jz4740_rtc_set_mmss,
>> + .read_alarm = jz4740_rtc_read_alarm,
>> + .set_alarm = jz4740_rtc_set_alarm,
>> + .update_irq_enable = jz4740_rtc_update_irq_enable,
>> + .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
>> +};
>> +
>> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
>> +{
>> + struct jz4740_rtc *rtc = data;
>> + uint32_t ctrl;
>> + unsigned long events = 0;
>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>> +
>> + if (ctrl & JZ_RTC_CTRL_1HZ)
>> + events |= (RTC_UF | RTC_IRQF);
>> +
>> + if (ctrl & JZ_RTC_CTRL_AF)
>> + events |= (RTC_AF | RTC_IRQF);
>> +
>> + rtc_update_irq(rtc->rtc, 1, events);
>> +
>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, 0);
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +void jz4740_rtc_poweroff(struct device *dev)
>> +{
>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
>> +}
>> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
>> +
>> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
>> +{
>> + int ret;
>> + struct jz4740_rtc *rtc;
>> + uint32_t scratchpad;
>> +
>> + rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);
>> + if (!rtc)
>> + return -ENOMEM;
>> +
>> + rtc->irq = platform_get_irq(pdev, 0);
>> + if (rtc->irq < 0) {
>> + ret = -ENOENT;
>> + dev_err(&pdev->dev, "Failed to get platform irq\n");
>> + goto err_free;
>> + }
>> +
>> + rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + if (!rtc->mem) {
>> + ret = -ENOENT;
>> + dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
>> + goto err_free;
>> + }
>> +
>> + rtc->mem = request_mem_region(rtc->mem->start, resource_size(rtc->mem),
>> + pdev->name);
>> + if (!rtc->mem) {
>> + ret = -EBUSY;
>> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
>> + goto err_free;
>> + }
>> +
>> + rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
>> + if (!rtc->base) {
>> + ret = -EBUSY;
>> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
>> + goto err_release_mem_region;
>> + }
>> +
>> + spin_lock_init(&rtc->lock);
>> +
>> + platform_set_drvdata(pdev, rtc);
>>
>
> dev_set_drvdata()?
>
>
No.
>> +
>> + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &jz4740_rtc_ops,
>> + THIS_MODULE);
>> + if (IS_ERR(rtc->rtc)) {
>> + ret = PTR_ERR(rtc->rtc);
>> + dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret);
>> + goto err_iounmap;
>> + }
>> +
>> + ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
>> + pdev->name, rtc);
>> + if (ret) {
>> + dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret);
>> + goto err_unregister_rtc;
>> + }
>> +
>> + scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
>> + if (scratchpad != 0x12345678) {
>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
>> + }
>> +
>> + return 0;
>> +
>> +err_unregister_rtc:
>> + rtc_device_unregister(rtc->rtc);
>> +err_iounmap:
>> + platform_set_drvdata(pdev, NULL);
>> + iounmap(rtc->base);
>> +err_release_mem_region:
>> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>> +err_free:
>> + kfree(rtc);
>> +
>> + return ret;
>> +}
>> +
>> +static int __devexit jz4740_rtc_remove(struct platform_device *pdev)
>> +{
>> + struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
>>
>
> dev_get_drvdata();
>
>
>> +
>> + free_irq(rtc->irq, rtc);
>> +
>> + rtc_device_unregister(rtc->rtc);
>> +
>> + iounmap(rtc->base);
>> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>> +
>> + kfree(rtc);
>> +
>> + platform_set_drvdata(pdev, NULL);
>>
>
> DTTO
>
>
>> +
>> + return 0;
>> +}
>> +
>> +struct platform_driver jz4740_rtc_driver = {
>> + .probe = jz4740_rtc_probe,
>> + .remove = __devexit_p(jz4740_rtc_remove),
>> + .driver = {
>> + .name = "jz4740-rtc",
>> + .owner = THIS_MODULE,
>> + },
>> +};
>> +
>> +static int __init jz4740_rtc_init(void)
>> +{
>> + return platform_driver_register(&jz4740_rtc_driver);
>> +}
>> +module_init(jz4740_rtc_init);
>> +
>> +static void __exit jz4740_rtc_exit(void)
>> +{
>> + platform_driver_unregister(&jz4740_rtc_driver);
>> +}
>> +module_exit(jz4740_rtc_exit);
>> +
>> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
>> +MODULE_ALIAS("platform:jz4740-rtc");
>>
>
> Cheers
>
Thanks for reviewing

- Lars

2010-06-19 13:37:49

by Wan ZongShun

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

Hi Lars-Peter,


> Hi
>
> Marek Vasut wrote:
>> Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
>>
>>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>>
>>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>>> Cc: Alessandro Zummo <[email protected]>
>>> Cc: Paul Gortmaker <[email protected]>
>>> Cc: Wan ZongShun <[email protected]>
>>> Cc: Marek Vasut <[email protected]>
>>> Cc: [email protected]
>>>
>>> ---
>>> Changes since v1
>>> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
>>> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
>>> - Check whether rtc structure could be allocated
>>> - Fix deadlocks which could occur if the HW was broken
>>> ---
>>> drivers/rtc/Kconfig | 11 ++
>>> drivers/rtc/Makefile | 1 +
>>> drivers/rtc/rtc-jz4740.c | 341
>>> ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353
>>> insertions(+), 0 deletions(-)
>>> create mode 100644 drivers/rtc/rtc-jz4740.c
>>>
>>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>>> index 10ba12c..d0ed7e6 100644
>>> --- a/drivers/rtc/Kconfig
>>> +++ b/drivers/rtc/Kconfig
>>> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
>>> This driver can also be built as a module. If so, the module
>>> will be called rtc-mpc5121.
>>>
>>> +config RTC_DRV_JZ4740
>>> + tristate "Ingenic JZ4740 SoC"
>>> + depends on RTC_CLASS
>>> + depends on MACH_JZ4740
>>> + help
>>> + If you say yes here you get support for the Ingenic JZ4740 SoC RTC
>>> + controller.
>>> +
>>> + This driver can also be buillt as a module. If so, the module
>>> + will be called rtc-jz4740.
>>> +
>>> endif # RTC_CLASS
>>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>>> index 5adbba7..fedf9bb 100644
>>> --- a/drivers/rtc/Makefile
>>> +++ b/drivers/rtc/Makefile
>>> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
>>> obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
>>> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
>>> obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
>>> +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
>>> obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
>>> obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
>>> obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
>>> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
>>> new file mode 100644
>>> index 0000000..720afb2
>>> --- /dev/null
>>> +++ b/drivers/rtc/rtc-jz4740.c
>>> @@ -0,0 +1,341 @@
>>> +/*
>>> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>>> + * JZ4740 SoC RTC 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/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/rtc.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/spinlock.h>
>>> +
>>> +#define JZ_REG_RTC_CTRL 0x00
>>> +#define JZ_REG_RTC_SEC 0x04
>>> +#define JZ_REG_RTC_SEC_ALARM 0x08
>>> +#define JZ_REG_RTC_REGULATOR 0x0C
>>> +#define JZ_REG_RTC_HIBERNATE 0x20
>>> +#define JZ_REG_RTC_SCRATCHPAD 0x34
>>> +
>>> +#define JZ_RTC_CTRL_WRDY BIT(7)
>>> +#define JZ_RTC_CTRL_1HZ BIT(6)
>>> +#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
>>> +#define JZ_RTC_CTRL_AF BIT(4)
>>> +#define JZ_RTC_CTRL_AF_IRQ BIT(3)
>>> +#define JZ_RTC_CTRL_AE BIT(2)
>>> +#define JZ_RTC_CTRL_ENABLE BIT(0)
>>> +
>>> +struct jz4740_rtc {
>>> + struct resource *mem;
>>> + void __iomem *base;
>>> +
>>> + struct rtc_device *rtc;
>>> +
>>> + unsigned int irq;
>>> +
>>> + spinlock_t lock;
>>> +};
>>> +
>>> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t
>>> reg) +{
>>> + return readl(rtc->base + reg);
>>> +}
>>> +
>>> +static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
>>> +{
>>> + uint32_t ctrl;
>>> + int timeout = 1000;
>>> +
>>> + do {
>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>> + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
>>>
>> if (!timeout) {
>> scream_and_die_in_pain();
>> dev_err("I died");
>> ... or something like that ... what if it times out, in this implementation,
>> noone will know this failed.
>>
>> I haven't looked through the whole source code, but can't this be wrapped into
>> the reg_write() ?
>> }
>>
> Well IF it will ever die, you'll notice cause your rtc clock won't work
> anymore.
>
> It could be wrapped into reg_write, but there is a different version of
> the SoC with the only difference of the RTC unit being that a different
> mechanism is used determine whether it is ok to write or not. So it
> makes sense to keep it seperate.
>>> +}
>>> +
>>> +static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t
>>> reg, + uint32_t val)
>>> +{
>>> + jz4740_rtc_wait_write_ready(rtc);
>>> + writel(val, rtc->base + reg);
>>> +}
>>> +
>>> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t
>>> mask, + uint32_t val)
>>> +{
>>> + unsigned long flags;
>>> + uint32_t ctrl;
>>> +
>>> + spin_lock_irqsave(&rtc->lock, flags);
>>>
>> Can't we use local_irq_save()/local_irq_restore() ?
>>
> Why would that be preferable? In the non-debug, non-rt case this will
> expand to local_irq_{save,restore} anyway, but you'll lose the semantics
> of an lock.

Anyway,spin_lock_irqsave is most universal and secure lock function, it can
apply to smp or single cpu, it can be ok here.


>>
>>> +
>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>> +
>>> + /* Don't clear interrupt flags by accident */
>>> + ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
>>> +
>>> + ctrl &= ~mask;
>>> + ctrl |= val;
>>> +
>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
>>> +
>>> + spin_unlock_irqrestore(&rtc->lock, flags);
>>> +}
>>> +
>>> +static int jz4740_rtc_read_time(struct device *dev, struct rtc_time *time)
>>> +{
>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>> + uint32_t secs, secs2;
>>> + int timeout = 5;
>>> +
>>> + /* If the seconds register is read while it is updated, it can contain a
>>> + * bogus value. This can be avoided by making sure that two consecutive
>>> + * reads have the same value.
>>> + */
>>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>> +
>>> + while (secs != secs2 && --timeout) {
>>> + secs = secs2;
>>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>> + }
>>> +
>>> + if (timeout == 0)
>>> + return -EIO;
>>> +
>>> + rtc_time_to_tm(secs, time);
>>> +
>>> + return rtc_valid_tm(time);
>>> +}
>>> +
>>> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
>>> +{
>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>> +
>>> + if ((uint32_t)secs != secs)
>>> + return -EINVAL;
>>>
>> Is the typecast here necessary ?
>>
> Strictly speaking not.
>>
>>> +
>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm
>>> *alrm) +{
>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>> + uint32_t secs;
>>> + uint32_t ctrl;
>>> +
>>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
>>> +
>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>> +
>>> + alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
>>> + alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
>>> +
>>>
>> Is the double negation (!!) here necessary ?
>>
>>
> To quote rtc.h "/* 0 = alarm disabled, 1 = alarm enabled */", so yes.

You are right, but it is not true reason.

Please keep (!!) here, to make sure 'enabled' and 'pending'
to be '0' or '1' is good habit, since they are 'unsigned char' variables.

Thanks!

>>> + rtc_time_to_tm(secs, &alrm->time);
>>> +
>>> + return rtc_valid_tm(&alrm->time);
>>> +}
>>> +
>>> +static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm
>>> *alrm) +{
>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>> + unsigned long secs;
>>> +
>>> + rtc_tm_to_time(&alrm->time, &secs);
>>> +
>>> + if ((uint32_t)secs != secs)
>>> + return -EINVAL;
>>>
>> DTTO above
>>
>>
>>> +
>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, (uint32_t)secs);
>>>
>> DTTO
>>
>>
>>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
>>> + alrm->enabled ? JZ_RTC_CTRL_AE : 0);
>>>
>> Possibly the double negation above wasn't necessary
>>
>>
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static inline int jz4740_irq_enable(struct device *dev, int irq,
>>> + unsigned int enable)
>>> +{
>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>> + jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned int
>>> enable) +{
>>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
>>> +}
>>> +
>>> +static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int
>>> enable) +{
>>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
>>> +}
>>> +
>>> +static struct rtc_class_ops jz4740_rtc_ops = {
>>> + .read_time = jz4740_rtc_read_time,
>>> + .set_mmss = jz4740_rtc_set_mmss,
>>> + .read_alarm = jz4740_rtc_read_alarm,
>>> + .set_alarm = jz4740_rtc_set_alarm,
>>> + .update_irq_enable = jz4740_rtc_update_irq_enable,
>>> + .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
>>> +};
>>> +
>>> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
>>> +{
>>> + struct jz4740_rtc *rtc = data;
>>> + uint32_t ctrl;
>>> + unsigned long events = 0;
>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>> +
>>> + if (ctrl & JZ_RTC_CTRL_1HZ)
>>> + events |= (RTC_UF | RTC_IRQF);
>>> +
>>> + if (ctrl & JZ_RTC_CTRL_AF)
>>> + events |= (RTC_AF | RTC_IRQF);
>>> +
>>> + rtc_update_irq(rtc->rtc, 1, events);
>>> +
>>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, 0);
>>> +
>>> + return IRQ_HANDLED;
>>> +}
>>> +
>>> +void jz4740_rtc_poweroff(struct device *dev)
>>> +{
>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
>>> +}
>>> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
>>> +
>>> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
>>> +{
>>> + int ret;
>>> + struct jz4740_rtc *rtc;
>>> + uint32_t scratchpad;
>>> +
>>> + rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);

Please use kzalloc or kmalloc plus memset, but you forget to add memset,
I prefer kzalloc, of course, you can use latter.

>>> + if (!rtc)
>>> + return -ENOMEM;
>>> +
>>> + rtc->irq = platform_get_irq(pdev, 0);
>>> + if (rtc->irq < 0) {
>>> + ret = -ENOENT;
>>> + dev_err(&pdev->dev, "Failed to get platform irq\n");
>>> + goto err_free;
>>> + }
>>> +
>>> + rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>> + if (!rtc->mem) {
>>> + ret = -ENOENT;
>>> + dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
>>> + goto err_free;
>>> + }
>>> +
>>> + rtc->mem = request_mem_region(rtc->mem->start, resource_size(rtc->mem),
>>> + pdev->name);
>>> + if (!rtc->mem) {
>>> + ret = -EBUSY;
>>> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
>>> + goto err_free;
>>> + }
>>> +
>>> + rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
>>> + if (!rtc->base) {
>>> + ret = -EBUSY;
>>> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
>>> + goto err_release_mem_region;
>>> + }
>>> +
>>> + spin_lock_init(&rtc->lock);
>>> +
>>> + platform_set_drvdata(pdev, rtc);
>>>
>> dev_set_drvdata()?
>>
>>
> No.
>>> +
>>> + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &jz4740_rtc_ops,
>>> + THIS_MODULE);
>>> + if (IS_ERR(rtc->rtc)) {
>>> + ret = PTR_ERR(rtc->rtc);
>>> + dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret);
>>> + goto err_iounmap;
>>> + }
>>> +
>>> + ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
>>> + pdev->name, rtc);
In fact, I prefer you can use this IRQ flags, such as IRQF_DISABLED, IRQF_SHARED.

>>> + if (ret) {
>>> + dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret);
>>> + goto err_unregister_rtc;
>>> + }
>>> +
>>> + scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
>>> + if (scratchpad != 0x12345678) {
>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
>>> + }
>>> +
>>> + return 0;
>>> +
>>> +err_unregister_rtc:
>>> + rtc_device_unregister(rtc->rtc);
>>> +err_iounmap:
>>> + platform_set_drvdata(pdev, NULL);
>>> + iounmap(rtc->base);
>>> +err_release_mem_region:
>>> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>>> +err_free:
>>> + kfree(rtc);
>>> +
>>> + return ret;
>>> +}
>>> +
>>> +static int __devexit jz4740_rtc_remove(struct platform_device *pdev)
>>> +{
>>> + struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
>>>
>> dev_get_drvdata();
>>
>>
>>> +
>>> + free_irq(rtc->irq, rtc);
>>> +
>>> + rtc_device_unregister(rtc->rtc);
>>> +
>>> + iounmap(rtc->base);
>>> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>>> +
>>> + kfree(rtc);
>>> +
>>> + platform_set_drvdata(pdev, NULL);
>>>
>> DTTO
>>
>>
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +struct platform_driver jz4740_rtc_driver = {
>>> + .probe = jz4740_rtc_probe,
>>> + .remove = __devexit_p(jz4740_rtc_remove),
>>> + .driver = {
>>> + .name = "jz4740-rtc",
>>> + .owner = THIS_MODULE,
>>> + },
>>> +};
>>> +
>>> +static int __init jz4740_rtc_init(void)
>>> +{
>>> + return platform_driver_register(&jz4740_rtc_driver);
>>> +}
>>> +module_init(jz4740_rtc_init);
>>> +
>>> +static void __exit jz4740_rtc_exit(void)
>>> +{
>>> + platform_driver_unregister(&jz4740_rtc_driver);
>>> +}
>>> +module_exit(jz4740_rtc_exit);
>>> +
>>> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
>>> +MODULE_ALIAS("platform:jz4740-rtc");
>>>
>> Cheers
>>
> Thanks for reviewing
>
> - Lars
>

2010-06-19 13:54:00

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Wan ZongShun wrote:
> Hi Lars-Peter,
>
>
>> Hi
>>
>> Marek Vasut wrote:
>>> Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
>>>
>>>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>>>
>>>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>>>> Cc: Alessandro Zummo <[email protected]>
>>>> Cc: Paul Gortmaker <[email protected]>
>>>> Cc: Wan ZongShun <[email protected]>
>>>> Cc: Marek Vasut <[email protected]>
>>>> Cc: [email protected]
>>>>
>>>> ---
>>>> Changes since v1
>>>> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
>>>> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
>>>> - Check whether rtc structure could be allocated
>>>> - Fix deadlocks which could occur if the HW was broken
>>>> ---
>>>> drivers/rtc/Kconfig | 11 ++
>>>> drivers/rtc/Makefile | 1 +
>>>> drivers/rtc/rtc-jz4740.c | 341
>>>> ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353
>>>> insertions(+), 0 deletions(-)
>>>> create mode 100644 drivers/rtc/rtc-jz4740.c
>>>>
>>>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>>>> index 10ba12c..d0ed7e6 100644
>>>> --- a/drivers/rtc/Kconfig
>>>> +++ b/drivers/rtc/Kconfig
>>>> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
>>>> This driver can also be built as a module. If so, the module
>>>> will be called rtc-mpc5121.
>>>>
>>>> +config RTC_DRV_JZ4740
>>>> + tristate "Ingenic JZ4740 SoC"
>>>> + depends on RTC_CLASS
>>>> + depends on MACH_JZ4740
>>>> + help
>>>> + If you say yes here you get support for the Ingenic JZ4740
>>>> SoC RTC
>>>> + controller.
>>>> +
>>>> + This driver can also be buillt as a module. If so, the module
>>>> + will be called rtc-jz4740.
>>>> +
>>>> endif # RTC_CLASS
>>>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>>>> index 5adbba7..fedf9bb 100644
>>>> --- a/drivers/rtc/Makefile
>>>> +++ b/drivers/rtc/Makefile
>>>> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
>>>> obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
>>>> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
>>>> obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
>>>> +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
>>>> obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
>>>> obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
>>>> obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
>>>> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
>>>> new file mode 100644
>>>> index 0000000..720afb2
>>>> --- /dev/null
>>>> +++ b/drivers/rtc/rtc-jz4740.c
>>>> @@ -0,0 +1,341 @@
>>>> +/*
>>>> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>>>> + * JZ4740 SoC RTC 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/kernel.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/rtc.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/spinlock.h>
>>>> +
>>>> +#define JZ_REG_RTC_CTRL 0x00
>>>> +#define JZ_REG_RTC_SEC 0x04
>>>> +#define JZ_REG_RTC_SEC_ALARM 0x08
>>>> +#define JZ_REG_RTC_REGULATOR 0x0C
>>>> +#define JZ_REG_RTC_HIBERNATE 0x20
>>>> +#define JZ_REG_RTC_SCRATCHPAD 0x34
>>>> +
>>>> +#define JZ_RTC_CTRL_WRDY BIT(7)
>>>> +#define JZ_RTC_CTRL_1HZ BIT(6)
>>>> +#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
>>>> +#define JZ_RTC_CTRL_AF BIT(4)
>>>> +#define JZ_RTC_CTRL_AF_IRQ BIT(3)
>>>> +#define JZ_RTC_CTRL_AE BIT(2)
>>>> +#define JZ_RTC_CTRL_ENABLE BIT(0)
>>>> +
>>>> +struct jz4740_rtc {
>>>> + struct resource *mem;
>>>> + void __iomem *base;
>>>> +
>>>> + struct rtc_device *rtc;
>>>> +
>>>> + unsigned int irq;
>>>> +
>>>> + spinlock_t lock;
>>>> +};
>>>> +
>>>> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc
>>>> *rtc, size_t
>>>> reg) +{
>>>> + return readl(rtc->base + reg);
>>>> +}
>>>> +
>>>> +static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc
>>>> *rtc)
>>>> +{
>>>> + uint32_t ctrl;
>>>> + int timeout = 1000;
>>>> +
>>>> + do {
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
>>>>
>>> if (!timeout) {
>>> scream_and_die_in_pain();
>>> dev_err("I died");
>>> ... or something like that ... what if it times out, in this
>>> implementation, noone will know this failed.
>>>
>>> I haven't looked through the whole source code, but can't this be
>>> wrapped into the reg_write() ?
>>> }
>>>
>> Well IF it will ever die, you'll notice cause your rtc clock won't
>> work
>> anymore.
>>
>> It could be wrapped into reg_write, but there is a different
>> version of
>> the SoC with the only difference of the RTC unit being that a
>> different
>> mechanism is used determine whether it is ok to write or not. So it
>> makes sense to keep it seperate.
>>>> +}
>>>> +
>>>> +static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc,
>>>> size_t
>>>> reg, + uint32_t val)
>>>> +{
>>>> + jz4740_rtc_wait_write_ready(rtc);
>>>> + writel(val, rtc->base + reg);
>>>> +}
>>>> +
>>>> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc,
>>>> uint32_t
>>>> mask, + uint32_t val)
>>>> +{
>>>> + unsigned long flags;
>>>> + uint32_t ctrl;
>>>> +
>>>> + spin_lock_irqsave(&rtc->lock, flags);
>>>>
>>> Can't we use local_irq_save()/local_irq_restore() ?
>>>
>> Why would that be preferable? In the non-debug, non-rt case this will
>> expand to local_irq_{save,restore} anyway, but you'll lose the
>> semantics
>> of an lock.
>
> Anyway,spin_lock_irqsave is most universal and secure lock function,
> it can
> apply to smp or single cpu, it can be ok here.
>
>
>>>
>>>> +
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> +
>>>> + /* Don't clear interrupt flags by accident */
>>>> + ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
>>>> +
>>>> + ctrl &= ~mask;
>>>> + ctrl |= val;
>>>> +
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
>>>> +
>>>> + spin_unlock_irqrestore(&rtc->lock, flags);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_read_time(struct device *dev, struct
>>>> rtc_time *time)
>>>> +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + uint32_t secs, secs2;
>>>> + int timeout = 5;
>>>> +
>>>> + /* If the seconds register is read while it is updated, it
>>>> can contain a
>>>> + * bogus value. This can be avoided by making sure that two
>>>> consecutive
>>>> + * reads have the same value.
>>>> + */
>>>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>> +
>>>> + while (secs != secs2 && --timeout) {
>>>> + secs = secs2;
>>>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>> + }
>>>> +
>>>> + if (timeout == 0)
>>>> + return -EIO;
>>>> +
>>>> + rtc_time_to_tm(secs, time);
>>>> +
>>>> + return rtc_valid_tm(time);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long
>>>> secs)
>>>> +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> +
>>>> + if ((uint32_t)secs != secs)
>>>> + return -EINVAL;
>>>>
>>> Is the typecast here necessary ?
>>>
>> Strictly speaking not.
>>>
>>>> +
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_read_alarm(struct device *dev, struct
>>>> rtc_wkalrm
>>>> *alrm) +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + uint32_t secs;
>>>> + uint32_t ctrl;
>>>> +
>>>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
>>>> +
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> +
>>>> + alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
>>>> + alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
>>>> +
>>>>
>>> Is the double negation (!!) here necessary ?
>>>
>>>
>> To quote rtc.h "/* 0 = alarm disabled, 1 = alarm enabled */", so yes.
>
> You are right, but it is not true reason.
>
> Please keep (!!) here, to make sure 'enabled' and 'pending'
> to be '0' or '1' is good habit, since they are 'unsigned char'
> variables.
>
> Thanks!
>
>>>> + rtc_time_to_tm(secs, &alrm->time);
>>>> +
>>>> + return rtc_valid_tm(&alrm->time);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_set_alarm(struct device *dev, struct
>>>> rtc_wkalrm
>>>> *alrm) +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + unsigned long secs;
>>>> +
>>>> + rtc_tm_to_time(&alrm->time, &secs);
>>>> +
>>>> + if ((uint32_t)secs != secs)
>>>> + return -EINVAL;
>>>>
>>> DTTO above
>>>
>>>
>>>> +
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM,
>>>> (uint32_t)secs);
>>>>
>>> DTTO
>>>
>>>
>>>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
>>>> + alrm->enabled ? JZ_RTC_CTRL_AE : 0);
>>>>
>>> Possibly the double negation above wasn't necessary
>>>
>>>
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static inline int jz4740_irq_enable(struct device *dev, int irq,
>>>> + unsigned int enable)
>>>> +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_update_irq_enable(struct device *dev,
>>>> unsigned int
>>>> enable) +{
>>>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_alarm_irq_enable(struct device *dev,
>>>> unsigned int
>>>> enable) +{
>>>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
>>>> +}
>>>> +
>>>> +static struct rtc_class_ops jz4740_rtc_ops = {
>>>> + .read_time = jz4740_rtc_read_time,
>>>> + .set_mmss = jz4740_rtc_set_mmss,
>>>> + .read_alarm = jz4740_rtc_read_alarm,
>>>> + .set_alarm = jz4740_rtc_set_alarm,
>>>> + .update_irq_enable = jz4740_rtc_update_irq_enable,
>>>> + .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
>>>> +};
>>>> +
>>>> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
>>>> +{
>>>> + struct jz4740_rtc *rtc = data;
>>>> + uint32_t ctrl;
>>>> + unsigned long events = 0;
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> +
>>>> + if (ctrl & JZ_RTC_CTRL_1HZ)
>>>> + events |= (RTC_UF | RTC_IRQF);
>>>> +
>>>> + if (ctrl & JZ_RTC_CTRL_AF)
>>>> + events |= (RTC_AF | RTC_IRQF);
>>>> +
>>>> + rtc_update_irq(rtc->rtc, 1, events);
>>>> +
>>>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ |
>>>> JZ_RTC_CTRL_AF, 0);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +void jz4740_rtc_poweroff(struct device *dev)
>>>> +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
>>>> +
>>>> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
>>>> +{
>>>> + int ret;
>>>> + struct jz4740_rtc *rtc;
>>>> + uint32_t scratchpad;
>>>> +
>>>> + rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);
>
> Please use kzalloc or kmalloc plus memset, but you forget to add
> memset,
> I prefer kzalloc, of course, you can use latter.
>
All fields of the struct are initialized in probe function, so kzalloc
is unnecessary overhead(small though).
>>>> + if (!rtc)
>>>> + return -ENOMEM;
>>>> +
>>>> + rtc->irq = platform_get_irq(pdev, 0);
>>>> + if (rtc->irq < 0) {
>>>> + ret = -ENOENT;
>>>> + dev_err(&pdev->dev, "Failed to get platform irq\n");
>>>> + goto err_free;
>>>> + }
>>>> +
>>>> + rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>> + if (!rtc->mem) {
>>>> + ret = -ENOENT;
>>>> + dev_err(&pdev->dev, "Failed to get platform mmio
>>>> memory\n");
>>>> + goto err_free;
>>>> + }
>>>> +
>>>> + rtc->mem = request_mem_region(rtc->mem->start,
>>>> resource_size(rtc->mem),
>>>> + pdev->name);
>>>> + if (!rtc->mem) {
>>>> + ret = -EBUSY;
>>>> + dev_err(&pdev->dev, "Failed to request mmio memory
>>>> region\n");
>>>> + goto err_free;
>>>> + }
>>>> +
>>>> + rtc->base = ioremap_nocache(rtc->mem->start,
>>>> resource_size(rtc->mem));
>>>> + if (!rtc->base) {
>>>> + ret = -EBUSY;
>>>> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
>>>> + goto err_release_mem_region;
>>>> + }
>>>> +
>>>> + spin_lock_init(&rtc->lock);
>>>> +
>>>> + platform_set_drvdata(pdev, rtc);
>>>>
>>> dev_set_drvdata()?
>>>
>>>
>> No.
>>>> +
>>>> + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev,
>>>> &jz4740_rtc_ops,
>>>> + THIS_MODULE);
>>>> + if (IS_ERR(rtc->rtc)) {
>>>> + ret = PTR_ERR(rtc->rtc);
>>>> + dev_err(&pdev->dev, "Failed to register rtc device:
>>>> %d\n", ret);
>>>> + goto err_iounmap;
>>>> + }
>>>> +
>>>> + ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
>>>> + pdev->name, rtc);
> In fact, I prefer you can use this IRQ flags, such as IRQF_DISABLED,
> IRQF_SHARED.
IRQF_DISABLED is deprecated and IRQF_SHARED doesn't make any sense
here. In fact not requesting with IRQF_SHARED might help to spot
errors if another driver requested the IRQ by accident (Or this driver
requested the wrong irq).
>
>>>> + if (ret) {
>>>> + dev_err(&pdev->dev, "Failed to request rtc irq: %d\n",
>>>> ret);
>>>> + goto err_unregister_rtc;
>>>> + }
>>>> +
>>>> + scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
>>>> + if (scratchpad != 0x12345678) {
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD,
>>>> 0x12345678);
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
>>>> + }
>>>> +
>>>> + return 0;
>>>> +
>>>> +err_unregister_rtc:
>>>> + rtc_device_unregister(rtc->rtc);
>>>> +err_iounmap:
>>>> + platform_set_drvdata(pdev, NULL);
>>>> + iounmap(rtc->base);
>>>> +err_release_mem_region:
>>>> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>>>> +err_free:
>>>> + kfree(rtc);
>>>> +
>>>> + return ret;
>>>> +}
>>>> +
>>>> +static int __devexit jz4740_rtc_remove(struct platform_device
>>>> *pdev)
>>>> +{
>>>> + struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
>>>>
>>> dev_get_drvdata();
>>>
>>>
>>>> +
>>>> + free_irq(rtc->irq, rtc);
>>>> +
>>>> + rtc_device_unregister(rtc->rtc);
>>>> +
>>>> + iounmap(rtc->base);
>>>> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>>>> +
>>>> + kfree(rtc);
>>>> +
>>>> + platform_set_drvdata(pdev, NULL);
>>>>
>>> DTTO
>>>
>>>
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +struct platform_driver jz4740_rtc_driver = {
>>>> + .probe = jz4740_rtc_probe,
>>>> + .remove = __devexit_p(jz4740_rtc_remove),
>>>> + .driver = {
>>>> + .name = "jz4740-rtc",
>>>> + .owner = THIS_MODULE,
>>>> + },
>>>> +};
>>>> +
>>>> +static int __init jz4740_rtc_init(void)
>>>> +{
>>>> + return platform_driver_register(&jz4740_rtc_driver);
>>>> +}
>>>> +module_init(jz4740_rtc_init);
>>>> +
>>>> +static void __exit jz4740_rtc_exit(void)
>>>> +{
>>>> + platform_driver_unregister(&jz4740_rtc_driver);
>>>> +}
>>>> +module_exit(jz4740_rtc_exit);
>>>> +
>>>> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
>>>> +MODULE_LICENSE("GPL");
>>>> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
>>>> +MODULE_ALIAS("platform:jz4740-rtc");
>>>>
>>> Cheers
>>>
Thanks for reviewing
- - Lars


-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkwcy80ACgkQBX4mSR26RiM+lgCfZPjAyf1kJstVzqIVlv2uCGMc
ruMAoIRF2fDWMG8mQJFb9V7iTmVTSgqs
=2MQV
-----END PGP SIGNATURE-----

2010-06-19 14:06:35

by Marek Vasut

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

Dne So 19. června 2010 15:05:18 Lars-Peter Clausen napsal(a):
> Hi
>
> Marek Vasut wrote:
> > Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
> >> This patch adds support for the RTC unit on JZ4740 SoCs.
> >>
> >> Signed-off-by: Lars-Peter Clausen <[email protected]>
> >> Cc: Alessandro Zummo <[email protected]>
> >> Cc: Paul Gortmaker <[email protected]>
> >> Cc: Wan ZongShun <[email protected]>
> >> Cc: Marek Vasut <[email protected]>
> >> Cc: [email protected]
> >>
> >> ---
> >> Changes since v1
> >> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
> >> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
> >> - Check whether rtc structure could be allocated
> >> - Fix deadlocks which could occur if the HW was broken
> >> ---
> >>
> >> drivers/rtc/Kconfig | 11 ++
> >> drivers/rtc/Makefile | 1 +
> >> drivers/rtc/rtc-jz4740.c | 341
> >>
> >> ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353
> >> insertions(+), 0 deletions(-)
> >>
> >> create mode 100644 drivers/rtc/rtc-jz4740.c
> >>
> >> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> >> index 10ba12c..d0ed7e6 100644
> >> --- a/drivers/rtc/Kconfig
> >> +++ b/drivers/rtc/Kconfig
> >> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
> >>
> >> This driver can also be built as a module. If so, the module
> >> will be called rtc-mpc5121.
> >>
> >> +config RTC_DRV_JZ4740
> >> + tristate "Ingenic JZ4740 SoC"
> >> + depends on RTC_CLASS
> >> + depends on MACH_JZ4740
> >> + help
> >> + If you say yes here you get support for the Ingenic JZ4740 SoC RTC
> >> + controller.
> >> +
> >> + This driver can also be buillt as a module. If so, the module
> >> + will be called rtc-jz4740.
> >> +
> >>
> >> endif # RTC_CLASS
> >>
> >> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> >> index 5adbba7..fedf9bb 100644
> >> --- a/drivers/rtc/Makefile
> >> +++ b/drivers/rtc/Makefile
> >> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
> >>
> >> obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
> >> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
> >> obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
> >>
> >> +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
> >>
> >> obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
> >> obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
> >> obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
> >>
> >> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
> >> new file mode 100644
> >> index 0000000..720afb2
> >> --- /dev/null
> >> +++ b/drivers/rtc/rtc-jz4740.c
> >> @@ -0,0 +1,341 @@
> >> +/*
> >> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> >> + * JZ4740 SoC RTC 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/kernel.h>
> >> +#include <linux/module.h>
> >> +#include <linux/platform_device.h>
> >> +#include <linux/rtc.h>
> >> +#include <linux/slab.h>
> >> +#include <linux/spinlock.h>
> >> +
> >> +#define JZ_REG_RTC_CTRL 0x00
> >> +#define JZ_REG_RTC_SEC 0x04
> >> +#define JZ_REG_RTC_SEC_ALARM 0x08
> >> +#define JZ_REG_RTC_REGULATOR 0x0C
> >> +#define JZ_REG_RTC_HIBERNATE 0x20
> >> +#define JZ_REG_RTC_SCRATCHPAD 0x34
> >> +
> >> +#define JZ_RTC_CTRL_WRDY BIT(7)
> >> +#define JZ_RTC_CTRL_1HZ BIT(6)
> >> +#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
> >> +#define JZ_RTC_CTRL_AF BIT(4)
> >> +#define JZ_RTC_CTRL_AF_IRQ BIT(3)
> >> +#define JZ_RTC_CTRL_AE BIT(2)
> >> +#define JZ_RTC_CTRL_ENABLE BIT(0)
> >> +
> >> +struct jz4740_rtc {
> >> + struct resource *mem;
> >> + void __iomem *base;
> >> +
> >> + struct rtc_device *rtc;
> >> +
> >> + unsigned int irq;
> >> +
> >> + spinlock_t lock;
> >> +};
> >> +
> >> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc,
> >> size_t reg) +{
> >> + return readl(rtc->base + reg);
> >> +}
> >> +
> >> +static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
> >> +{
> >> + uint32_t ctrl;
> >> + int timeout = 1000;
> >> +
> >> + do {
> >> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> >> + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
> >
> > if (!timeout) {
> >
> > scream_and_die_in_pain();
> > dev_err("I died");
> >
> > ... or something like that ... what if it times out, in this
> > implementation, noone will know this failed.
> >
> > I haven't looked through the whole source code, but can't this be wrapped
> > into the reg_write() ?
> > }
>
> Well IF it will ever die, you'll notice cause your rtc clock won't work
> anymore.

Then maybe some dev_err() would be good to have there.
>
> It could be wrapped into reg_write, but there is a different version of
> the SoC with the only difference of the RTC unit being that a different
> mechanism is used determine whether it is ok to write or not. So it
> makes sense to keep it seperate.

OK
>
> >> +}
> >> +
> >> +static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t
> >> reg, + uint32_t val)
> >> +{
> >> + jz4740_rtc_wait_write_ready(rtc);
> >> + writel(val, rtc->base + reg);
> >> +}
> >> +
> >> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t
> >> mask, + uint32_t val)
> >> +{
> >> + unsigned long flags;
> >> + uint32_t ctrl;
> >> +
> >> + spin_lock_irqsave(&rtc->lock, flags);
> >
> > Can't we use local_irq_save()/local_irq_restore() ?
>
> Why would that be preferable? In the non-debug, non-rt case this will
> expand to local_irq_{save,restore} anyway, but you'll lose the semantics
> of an lock.

I believe on SMP systems, local_irq_save will give you finer locking
granularity.
>
> >> +
> >> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> >> +
> >> + /* Don't clear interrupt flags by accident */
> >> + ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
> >> +
> >> + ctrl &= ~mask;
> >> + ctrl |= val;
> >> +
> >> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
> >> +
> >> + spin_unlock_irqrestore(&rtc->lock, flags);
> >> +}
> >> +
> >> +static int jz4740_rtc_read_time(struct device *dev, struct rtc_time
> >> *time) +{
> >> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> >> + uint32_t secs, secs2;
> >> + int timeout = 5;
> >> +
> >> + /* If the seconds register is read while it is updated, it can contain
> >> a + * bogus value. This can be avoided by making sure that two
> >> consecutive + * reads have the same value.
> >> + */
> >> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> >> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> >> +
> >> + while (secs != secs2 && --timeout) {
> >> + secs = secs2;
> >> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> >> + }
> >> +
> >> + if (timeout == 0)
> >> + return -EIO;
> >> +
> >> + rtc_time_to_tm(secs, time);
> >> +
> >> + return rtc_valid_tm(time);
> >> +}
> >> +
> >> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
> >> +{
> >> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> >> +
> >> + if ((uint32_t)secs != secs)
> >> + return -EINVAL;
> >
> > Is the typecast here necessary ?
>
> Strictly speaking not.

OK
>
> >> +
> >> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm
> >> *alrm) +{
> >> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> >> + uint32_t secs;
> >> + uint32_t ctrl;
> >> +
> >> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
> >> +
> >> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> >> +
> >> + alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
> >> + alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
> >> +
> >
> > Is the double negation (!!) here necessary ?
>
> To quote rtc.h "/* 0 = alarm disabled, 1 = alarm enabled */", so yes.

Oh my ... well, maybe someone should fix that to take 0 for NO, !0 for YES.
>
> >> + rtc_time_to_tm(secs, &alrm->time);
> >> +
> >> + return rtc_valid_tm(&alrm->time);
> >> +}
> >> +
> >> +static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm
> >> *alrm) +{
> >> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> >> + unsigned long secs;
> >> +
> >> + rtc_tm_to_time(&alrm->time, &secs);
> >> +
> >> + if ((uint32_t)secs != secs)
> >> + return -EINVAL;
> >
> > DTTO above
> >
> >> +
> >> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, (uint32_t)secs);
> >
> > DTTO
> >
> >> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
> >> + alrm->enabled ? JZ_RTC_CTRL_AE : 0);
> >
> > Possibly the double negation above wasn't necessary
> >
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static inline int jz4740_irq_enable(struct device *dev, int irq,
> >> + unsigned int enable)
> >> +{
> >> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> >> + jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned
> >> int enable) +{
> >> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
> >> +}
> >> +
> >> +static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int
> >> enable) +{
> >> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
> >> +}
> >> +
> >> +static struct rtc_class_ops jz4740_rtc_ops = {
> >> + .read_time = jz4740_rtc_read_time,
> >> + .set_mmss = jz4740_rtc_set_mmss,
> >> + .read_alarm = jz4740_rtc_read_alarm,
> >> + .set_alarm = jz4740_rtc_set_alarm,
> >> + .update_irq_enable = jz4740_rtc_update_irq_enable,
> >> + .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
> >> +};
> >> +
> >> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
> >> +{
> >> + struct jz4740_rtc *rtc = data;
> >> + uint32_t ctrl;
> >> + unsigned long events = 0;
> >> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> >> +
> >> + if (ctrl & JZ_RTC_CTRL_1HZ)
> >> + events |= (RTC_UF | RTC_IRQF);
> >> +
> >> + if (ctrl & JZ_RTC_CTRL_AF)
> >> + events |= (RTC_AF | RTC_IRQF);
> >> +
> >> + rtc_update_irq(rtc->rtc, 1, events);
> >> +
> >> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, 0);
> >> +
> >> + return IRQ_HANDLED;
> >> +}
> >> +
> >> +void jz4740_rtc_poweroff(struct device *dev)
> >> +{
> >> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> >> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
> >> +}
> >> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
> >> +
> >> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
> >> +{
> >> + int ret;
> >> + struct jz4740_rtc *rtc;
> >> + uint32_t scratchpad;
> >> +
> >> + rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);
> >> + if (!rtc)
> >> + return -ENOMEM;
> >> +
> >> + rtc->irq = platform_get_irq(pdev, 0);
> >> + if (rtc->irq < 0) {
> >> + ret = -ENOENT;
> >> + dev_err(&pdev->dev, "Failed to get platform irq\n");
> >> + goto err_free;
> >> + }
> >> +
> >> + rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> >> + if (!rtc->mem) {
> >> + ret = -ENOENT;
> >> + dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
> >> + goto err_free;
> >> + }
> >> +
> >> + rtc->mem = request_mem_region(rtc->mem->start,
> >> resource_size(rtc->mem), + pdev->name);
> >> + if (!rtc->mem) {
> >> + ret = -EBUSY;
> >> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> >> + goto err_free;
> >> + }
> >> +
> >> + rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
> >> + if (!rtc->base) {
> >> + ret = -EBUSY;
> >> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> >> + goto err_release_mem_region;
> >> + }
> >> +
> >> + spin_lock_init(&rtc->lock);
> >> +
> >> + platform_set_drvdata(pdev, rtc);
> >
> > dev_set_drvdata()?
>
> No.

Why not ?
>
> >> +
> >> + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev,
> >> &jz4740_rtc_ops, + THIS_MODULE);
> >> + if (IS_ERR(rtc->rtc)) {
> >> + ret = PTR_ERR(rtc->rtc);
> >> + dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret);
> >> + goto err_iounmap;
> >> + }
> >> +
> >> + ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
> >> + pdev->name, rtc);
> >> + if (ret) {
> >> + dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret);
> >> + goto err_unregister_rtc;
> >> + }
> >> +
> >> + scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
> >> + if (scratchpad != 0x12345678) {
> >> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
> >> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
> >> + }
> >> +
> >> + return 0;
> >> +
> >> +err_unregister_rtc:
> >> + rtc_device_unregister(rtc->rtc);
> >> +err_iounmap:
> >> + platform_set_drvdata(pdev, NULL);
> >> + iounmap(rtc->base);
> >> +err_release_mem_region:
> >> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
> >> +err_free:
> >> + kfree(rtc);
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static int __devexit jz4740_rtc_remove(struct platform_device *pdev)
> >> +{
> >> + struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
> >
> > dev_get_drvdata();
> >
> >> +
> >> + free_irq(rtc->irq, rtc);
> >> +
> >> + rtc_device_unregister(rtc->rtc);
> >> +
> >> + iounmap(rtc->base);
> >> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
> >> +
> >> + kfree(rtc);
> >> +
> >> + platform_set_drvdata(pdev, NULL);
> >
> > DTTO
> >
> >> +
> >> + return 0;
> >> +}
> >> +
> >> +struct platform_driver jz4740_rtc_driver = {
> >> + .probe = jz4740_rtc_probe,
> >> + .remove = __devexit_p(jz4740_rtc_remove),
> >> + .driver = {
> >> + .name = "jz4740-rtc",
> >> + .owner = THIS_MODULE,
> >> + },
> >> +};
> >> +
> >> +static int __init jz4740_rtc_init(void)
> >> +{
> >> + return platform_driver_register(&jz4740_rtc_driver);
> >> +}
> >> +module_init(jz4740_rtc_init);
> >> +
> >> +static void __exit jz4740_rtc_exit(void)
> >> +{
> >> + platform_driver_unregister(&jz4740_rtc_driver);
> >> +}
> >> +module_exit(jz4740_rtc_exit);
> >> +
> >> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> >> +MODULE_LICENSE("GPL");
> >> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
> >> +MODULE_ALIAS("platform:jz4740-rtc");
> >
> > Cheers
>
> Thanks for reviewing
>
> - Lars

2010-06-19 14:36:10

by Wan ZongShun

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

2010/6/19 Lars-Peter Clausen <[email protected]>:
> -----BEGIN PGP SIGNED MESSAGE-----
> Hash: SHA1
>
> Wan ZongShun wrote:
>> Hi Lars-Peter,
>>
>>
>>> Hi
>>>
>>> Marek Vasut wrote:
>>>> Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
>>>>
>>>>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>>>>
>>>>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>>>>> Cc: Alessandro Zummo <[email protected]>
>>>>> Cc: Paul Gortmaker <[email protected]>
>>>>> Cc: Wan ZongShun <[email protected]>
>>>>> Cc: Marek Vasut <[email protected]>
>>>>> Cc: [email protected]
>>>>>
>>>>> ---
>>>>> Changes since v1
>>>>> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
>>>>> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
>>>>> - Check whether rtc structure could be allocated
>>>>> - Fix deadlocks which could occur if the HW was broken
>>>>> ---
>>>>>  drivers/rtc/Kconfig      |   11 ++
>>>>>  drivers/rtc/Makefile     |    1 +
>>>>>  drivers/rtc/rtc-jz4740.c |  341
>>>>> ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353
>>>>> insertions(+), 0 deletions(-)
>>>>>  create mode 100644 drivers/rtc/rtc-jz4740.c
>>>>>
>>>>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>>>>> index 10ba12c..d0ed7e6 100644
>>>>> --- a/drivers/rtc/Kconfig
>>>>> +++ b/drivers/rtc/Kconfig
>>>>> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
>>>>>        This driver can also be built as a module. If so, the module
>>>>>        will be called rtc-mpc5121.
>>>>>
>>>>> +config RTC_DRV_JZ4740
>>>>> +    tristate "Ingenic JZ4740 SoC"
>>>>> +    depends on RTC_CLASS
>>>>> +    depends on MACH_JZ4740
>>>>> +    help
>>>>> +      If you say yes here you get support for the Ingenic JZ4740
>>>>> SoC RTC
>>>>> +      controller.
>>>>> +
>>>>> +      This driver can also be buillt as a module. If so, the module
>>>>> +      will be called rtc-jz4740.
>>>>> +
>>>>>  endif # RTC_CLASS
>>>>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>>>>> index 5adbba7..fedf9bb 100644
>>>>> --- a/drivers/rtc/Makefile
>>>>> +++ b/drivers/rtc/Makefile
>>>>> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX)    += rtc-ep93xx.o
>>>>>  obj-$(CONFIG_RTC_DRV_FM3130)    += rtc-fm3130.o
>>>>>  obj-$(CONFIG_RTC_DRV_GENERIC)    += rtc-generic.o
>>>>>  obj-$(CONFIG_RTC_DRV_ISL1208)    += rtc-isl1208.o
>>>>> +obj-$(CONFIG_RTC_DRV_JZ4740)    += rtc-jz4740.o
>>>>>  obj-$(CONFIG_RTC_DRV_M41T80)    += rtc-m41t80.o
>>>>>  obj-$(CONFIG_RTC_DRV_M41T94)    += rtc-m41t94.o
>>>>>  obj-$(CONFIG_RTC_DRV_M48T35)    += rtc-m48t35.o
>>>>> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
>>>>> new file mode 100644
>>>>> index 0000000..720afb2
>>>>> --- /dev/null
>>>>> +++ b/drivers/rtc/rtc-jz4740.c
>>>>> @@ -0,0 +1,341 @@
>>>>> +/*
>>>>> + *  Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>>>>> + *    JZ4740 SoC RTC 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/kernel.h>
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/rtc.h>
>>>>> +#include <linux/slab.h>
>>>>> +#include <linux/spinlock.h>
>>>>> +
>>>>> +#define JZ_REG_RTC_CTRL        0x00
>>>>> +#define JZ_REG_RTC_SEC        0x04
>>>>> +#define JZ_REG_RTC_SEC_ALARM    0x08
>>>>> +#define JZ_REG_RTC_REGULATOR    0x0C
>>>>> +#define JZ_REG_RTC_HIBERNATE    0x20
>>>>> +#define JZ_REG_RTC_SCRATCHPAD    0x34
>>>>> +
>>>>> +#define JZ_RTC_CTRL_WRDY    BIT(7)
>>>>> +#define JZ_RTC_CTRL_1HZ        BIT(6)
>>>>> +#define JZ_RTC_CTRL_1HZ_IRQ    BIT(5)
>>>>> +#define JZ_RTC_CTRL_AF        BIT(4)
>>>>> +#define JZ_RTC_CTRL_AF_IRQ    BIT(3)
>>>>> +#define JZ_RTC_CTRL_AE        BIT(2)
>>>>> +#define JZ_RTC_CTRL_ENABLE    BIT(0)
>>>>> +
>>>>> +struct jz4740_rtc {
>>>>> +    struct resource *mem;
>>>>> +    void __iomem *base;
>>>>> +
>>>>> +    struct rtc_device *rtc;
>>>>> +
>>>>> +    unsigned int irq;
>>>>> +
>>>>> +    spinlock_t lock;
>>>>> +};
>>>>> +
>>>>> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc
>>>>> *rtc, size_t
>>>>> reg) +{
>>>>> +    return readl(rtc->base + reg);
>>>>> +}
>>>>> +
>>>>> +static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc
>>>>> *rtc)
>>>>> +{
>>>>> +    uint32_t ctrl;
>>>>> +    int timeout = 1000;
>>>>> +
>>>>> +    do {
>>>>> +        ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>>> +    } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
>>>>>
>>>> if (!timeout) {
>>>>     scream_and_die_in_pain();
>>>>     dev_err("I died");
>>>> ... or something like that ... what if it times out, in this
>>>> implementation, noone will know this failed.
>>>>
>>>> I haven't looked through the whole source code, but can't this be
>>>> wrapped into the reg_write() ?
>>>> }
>>>>
>>> Well IF it will ever die, you'll notice cause your rtc clock won't
>>> work
>>> anymore.
>>>
>>> It could be wrapped into reg_write, but there is a different
>>> version of
>>> the SoC with the only difference of the RTC unit being that a
>>> different
>>> mechanism is used determine whether it is ok to write or not. So it
>>> makes sense to keep it seperate.
>>>>> +}
>>>>> +
>>>>> +static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc,
>>>>> size_t
>>>>> reg, +    uint32_t val)
>>>>> +{
>>>>> +    jz4740_rtc_wait_write_ready(rtc);
>>>>> +    writel(val, rtc->base + reg);
>>>>> +}
>>>>> +
>>>>> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc,
>>>>> uint32_t
>>>>> mask, +    uint32_t val)
>>>>> +{
>>>>> +    unsigned long flags;
>>>>> +    uint32_t ctrl;
>>>>> +
>>>>> +    spin_lock_irqsave(&rtc->lock, flags);
>>>>>
>>>> Can't we use local_irq_save()/local_irq_restore() ?
>>>>
>>> Why would that be preferable? In the non-debug, non-rt case this will
>>> expand to local_irq_{save,restore} anyway, but you'll lose the
>>> semantics
>>> of an lock.
>>
>> Anyway,spin_lock_irqsave is most universal and secure lock function,
>> it can
>> apply to smp or single cpu, it can be ok here.
>>
>>
>>>>
>>>>> +
>>>>> +    ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>>> +
>>>>> +    /* Don't clear interrupt flags by accident */
>>>>> +    ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
>>>>> +
>>>>> +    ctrl &= ~mask;
>>>>> +    ctrl |= val;
>>>>> +
>>>>> +    jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
>>>>> +
>>>>> +    spin_unlock_irqrestore(&rtc->lock, flags);
>>>>> +}
>>>>> +
>>>>> +static int jz4740_rtc_read_time(struct device *dev, struct
>>>>> rtc_time *time)
>>>>> +{
>>>>> +    struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>>> +    uint32_t secs, secs2;
>>>>> +    int timeout = 5;
>>>>> +
>>>>> +    /* If the seconds register is read while it is updated, it
>>>>> can contain a
>>>>> +     * bogus value. This can be avoided by making sure that two
>>>>> consecutive
>>>>> +     * reads have the same value.
>>>>> +     */
>>>>> +    secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>>> +    secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>>> +
>>>>> +    while (secs != secs2 && --timeout) {
>>>>> +        secs = secs2;
>>>>> +        secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>>> +    }
>>>>> +
>>>>> +    if (timeout == 0)
>>>>> +        return -EIO;
>>>>> +
>>>>> +    rtc_time_to_tm(secs, time);
>>>>> +
>>>>> +    return rtc_valid_tm(time);
>>>>> +}
>>>>> +
>>>>> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long
>>>>> secs)
>>>>> +{
>>>>> +    struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>>> +
>>>>> +    if ((uint32_t)secs != secs)
>>>>> +        return -EINVAL;
>>>>>
>>>> Is the typecast here necessary ?
>>>>
>>> Strictly speaking not.
>>>>
>>>>> +
>>>>> +    jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int jz4740_rtc_read_alarm(struct device *dev, struct
>>>>> rtc_wkalrm
>>>>> *alrm) +{
>>>>> +    struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>>> +    uint32_t secs;
>>>>> +    uint32_t ctrl;
>>>>> +
>>>>> +    secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
>>>>> +
>>>>> +    ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>>> +
>>>>> +    alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
>>>>> +    alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
>>>>> +
>>>>>
>>>> Is the double negation (!!) here necessary ?
>>>>
>>>>
>>> To quote rtc.h "/* 0 = alarm disabled, 1 = alarm enabled */", so yes.
>>
>> You are right, but it is not true reason.
>>
>> Please keep (!!) here, to make sure 'enabled' and 'pending'
>> to be '0' or '1' is good habit, since they are 'unsigned char'
>> variables.
>>
>> Thanks!
>>
>>>>> +    rtc_time_to_tm(secs, &alrm->time);
>>>>> +
>>>>> +    return rtc_valid_tm(&alrm->time);
>>>>> +}
>>>>> +
>>>>> +static int jz4740_rtc_set_alarm(struct device *dev, struct
>>>>> rtc_wkalrm
>>>>> *alrm) +{
>>>>> +    struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>>> +    unsigned long secs;
>>>>> +
>>>>> +    rtc_tm_to_time(&alrm->time, &secs);
>>>>> +
>>>>> +    if ((uint32_t)secs != secs)
>>>>> +        return -EINVAL;
>>>>>
>>>> DTTO above
>>>>
>>>>
>>>>> +
>>>>> +    jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM,
>>>>> (uint32_t)secs);
>>>>>
>>>> DTTO
>>>>
>>>>
>>>>> +    jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
>>>>> +                    alrm->enabled ? JZ_RTC_CTRL_AE : 0);
>>>>>
>>>> Possibly the double negation above wasn't necessary
>>>>
>>>>
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static inline int jz4740_irq_enable(struct device *dev, int irq,
>>>>> +    unsigned int enable)
>>>>> +{
>>>>> +    struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>>> +    jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +static int jz4740_rtc_update_irq_enable(struct device *dev,
>>>>> unsigned int
>>>>> enable) +{
>>>>> +    return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
>>>>> +}
>>>>> +
>>>>> +static int jz4740_rtc_alarm_irq_enable(struct device *dev,
>>>>> unsigned int
>>>>> enable) +{
>>>>> +    return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
>>>>> +}
>>>>> +
>>>>> +static struct rtc_class_ops jz4740_rtc_ops = {
>>>>> +    .read_time    = jz4740_rtc_read_time,
>>>>> +    .set_mmss    = jz4740_rtc_set_mmss,
>>>>> +    .read_alarm    = jz4740_rtc_read_alarm,
>>>>> +    .set_alarm    = jz4740_rtc_set_alarm,
>>>>> +    .update_irq_enable = jz4740_rtc_update_irq_enable,
>>>>> +    .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
>>>>> +};
>>>>> +
>>>>> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
>>>>> +{
>>>>> +    struct jz4740_rtc *rtc = data;
>>>>> +    uint32_t ctrl;
>>>>> +    unsigned long events = 0;
>>>>> +    ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>>> +
>>>>> +    if (ctrl & JZ_RTC_CTRL_1HZ)
>>>>> +        events |= (RTC_UF | RTC_IRQF);
>>>>> +
>>>>> +    if (ctrl & JZ_RTC_CTRL_AF)
>>>>> +        events |= (RTC_AF | RTC_IRQF);
>>>>> +
>>>>> +    rtc_update_irq(rtc->rtc, 1, events);
>>>>> +
>>>>> +    jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ |
>>>>> JZ_RTC_CTRL_AF, 0);
>>>>> +
>>>>> +    return IRQ_HANDLED;
>>>>> +}
>>>>> +
>>>>> +void jz4740_rtc_poweroff(struct device *dev)
>>>>> +{
>>>>> +    struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>>> +    jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
>>>>> +}
>>>>> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
>>>>> +
>>>>> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +    int ret;
>>>>> +    struct jz4740_rtc *rtc;
>>>>> +    uint32_t scratchpad;
>>>>> +
>>>>> +    rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);
>>
>> Please use kzalloc or kmalloc plus memset, but you forget to add
>> memset,
>> I prefer kzalloc, of course, you can use latter.
>>
> All fields of the struct are initialized in probe function, so kzalloc
> is unnecessary overhead(small though).

Of course, you have the correct reason, but using kzalloc is good habit,
now, many people submit their patches to convert kmalloc + memset.

In addition, if some guys write their drivers refer to your this
driver, but they
did not know initializing all the fields of struct is indispensible,
which will be
a potential dangerous.

Other parts of The driver look good to me, except this issue, so I
cannot recommend
it to Andrew for merging your patch, you have to wait for Andrew's ACK.

>>>>> +    if (!rtc)
>>>>> +        return -ENOMEM;
>>>>> +
>>>>> +    rtc->irq = platform_get_irq(pdev, 0);
>>>>> +    if (rtc->irq < 0) {
>>>>> +        ret = -ENOENT;
>>>>> +        dev_err(&pdev->dev, "Failed to get platform irq\n");
>>>>> +        goto err_free;
>>>>> +    }
>>>>> +
>>>>> +    rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>>> +    if (!rtc->mem) {
>>>>> +        ret = -ENOENT;
>>>>> +        dev_err(&pdev->dev, "Failed to get platform mmio
>>>>> memory\n");
>>>>> +        goto err_free;
>>>>> +    }
>>>>> +
>>>>> +    rtc->mem = request_mem_region(rtc->mem->start,
>>>>> resource_size(rtc->mem),
>>>>> +                    pdev->name);
>>>>> +    if (!rtc->mem) {
>>>>> +        ret = -EBUSY;
>>>>> +        dev_err(&pdev->dev, "Failed to request mmio memory
>>>>> region\n");
>>>>> +        goto err_free;
>>>>> +    }
>>>>> +
>>>>> +    rtc->base = ioremap_nocache(rtc->mem->start,
>>>>> resource_size(rtc->mem));
>>>>> +    if (!rtc->base) {
>>>>> +        ret = -EBUSY;
>>>>> +        dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
>>>>> +        goto err_release_mem_region;
>>>>> +    }
>>>>> +
>>>>> +    spin_lock_init(&rtc->lock);
>>>>> +
>>>>> +    platform_set_drvdata(pdev, rtc);
>>>>>
>>>> dev_set_drvdata()?
>>>>
>>>>
>>> No.
>>>>> +
>>>>> +    rtc->rtc = rtc_device_register(pdev->name, &pdev->dev,
>>>>> &jz4740_rtc_ops,
>>>>> +                    THIS_MODULE);
>>>>> +    if (IS_ERR(rtc->rtc)) {
>>>>> +        ret = PTR_ERR(rtc->rtc);
>>>>> +        dev_err(&pdev->dev, "Failed to register rtc device:
>>>>> %d\n", ret);
>>>>> +        goto err_iounmap;
>>>>> +    }
>>>>> +
>>>>> +    ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
>>>>> +                pdev->name, rtc);
>> In fact, I prefer you can use this IRQ flags, such as IRQF_DISABLED,
>> IRQF_SHARED.
> IRQF_DISABLED is deprecated and IRQF_SHARED doesn't make any sense
> here. In fact not requesting with IRQF_SHARED might help to spot
> errors if another driver requested the IRQ by accident (Or this driver
> requested the wrong irq).
>>
>>>>> +    if (ret) {
>>>>> +        dev_err(&pdev->dev, "Failed to request rtc irq: %d\n",
>>>>> ret);
>>>>> +        goto err_unregister_rtc;
>>>>> +    }
>>>>> +
>>>>> +    scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
>>>>> +    if (scratchpad != 0x12345678) {
>>>>> +        jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD,
>>>>> 0x12345678);
>>>>> +        jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
>>>>> +    }
>>>>> +
>>>>> +    return 0;
>>>>> +
>>>>> +err_unregister_rtc:
>>>>> +    rtc_device_unregister(rtc->rtc);
>>>>> +err_iounmap:
>>>>> +    platform_set_drvdata(pdev, NULL);
>>>>> +    iounmap(rtc->base);
>>>>> +err_release_mem_region:
>>>>> +    release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>>>>> +err_free:
>>>>> +    kfree(rtc);
>>>>> +
>>>>> +    return ret;
>>>>> +}
>>>>> +
>>>>> +static int __devexit jz4740_rtc_remove(struct platform_device
>>>>> *pdev)
>>>>> +{
>>>>> +    struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
>>>>>
>>>> dev_get_drvdata();
>>>>
>>>>
>>>>> +
>>>>> +    free_irq(rtc->irq, rtc);
>>>>> +
>>>>> +    rtc_device_unregister(rtc->rtc);
>>>>> +
>>>>> +    iounmap(rtc->base);
>>>>> +    release_mem_region(rtc->mem->start, resource_size(rtc->mem));
>>>>> +
>>>>> +    kfree(rtc);
>>>>> +
>>>>> +    platform_set_drvdata(pdev, NULL);
>>>>>
>>>> DTTO
>>>>
>>>>
>>>>> +
>>>>> +    return 0;
>>>>> +}
>>>>> +
>>>>> +struct platform_driver jz4740_rtc_driver = {
>>>>> +    .probe = jz4740_rtc_probe,
>>>>> +    .remove = __devexit_p(jz4740_rtc_remove),
>>>>> +    .driver = {
>>>>> +        .name = "jz4740-rtc",
>>>>> +        .owner = THIS_MODULE,
>>>>> +    },
>>>>> +};
>>>>> +
>>>>> +static int __init jz4740_rtc_init(void)
>>>>> +{
>>>>> +    return platform_driver_register(&jz4740_rtc_driver);
>>>>> +}
>>>>> +module_init(jz4740_rtc_init);
>>>>> +
>>>>> +static void __exit jz4740_rtc_exit(void)
>>>>> +{
>>>>> +    platform_driver_unregister(&jz4740_rtc_driver);
>>>>> +}
>>>>> +module_exit(jz4740_rtc_exit);
>>>>> +
>>>>> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
>>>>> +MODULE_LICENSE("GPL");
>>>>> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
>>>>> +MODULE_ALIAS("platform:jz4740-rtc");
>>>>>
>>>> Cheers
>>>>
> Thanks for reviewing
> - - Lars
>
>
> -----BEGIN PGP SIGNATURE-----
> Version: GnuPG v1.4.9 (GNU/Linux)
> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org
>
> iEYEARECAAYFAkwcy80ACgkQBX4mSR26RiM+lgCfZPjAyf1kJstVzqIVlv2uCGMc
> ruMAoIRF2fDWMG8mQJFb9V7iTmVTSgqs
> =2MQV
> -----END PGP SIGNATURE-----
>
>



--
*linux-arm-kernel mailing list
mail addr:[email protected]
you can subscribe by:
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

* linux-arm-NUC900 mailing list
mail addr:[email protected]
main web: https://groups.google.com/group/NUC900
you can subscribe it by sending me mail:
[email protected]

2010-06-19 14:46:28

by Matt Fleming

[permalink] [raw]
Subject: Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver

On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen <[email protected]> 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?

2010-06-19 14:47:35

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] hwmon: Add JZ4740 ADC driver

This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: [email protected]

--
Changes since v1
- Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
ADC driver now only reads the adcin value.
Changes since v2
- Add name sysfs attribute
- Report adcin in value in millivolts
---
drivers/hwmon/Kconfig | 11 ++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/jz4740-hwmon.c | 224 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 236 insertions(+), 0 deletions(-)
create mode 100644 drivers/hwmon/jz4740-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 569082c..51fc2f6 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -446,6 +446,17 @@ config SENSORS_IT87
This driver can also be built as a module. If so, the module
will be called it87.

+config SENSORS_JZ4740
+ tristate "Ingenic JZ4740 SoC ADC driver"
+ depends on MACH_JZ4740
+ help
+ If you say yes here you get support for the Ingenic JZ4740 SoC ADC core.
+ It is required for the JZ4740 battery and touchscreen driver and is used
+ to synchronize access to the adc module between those two.
+
+ This driver can also be build as a module. If so, the module will be
+ called jz4740-adc.
+
config SENSORS_LM63
tristate "National Semiconductor LM63 and LM64"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index bca0d45..dffbdff 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
+obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o
diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c
new file mode 100644
index 0000000..a448d78
--- /dev/null
+++ b/drivers/hwmon/jz4740-hwmon.c
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC HWMON 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/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/core.h>
+
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+
+struct jz4740_hwmon {
+ struct resource *mem;
+ void __iomem *base;
+
+ int irq;
+
+ struct mfd_cell *cell;
+ struct device *hwmon;
+
+ struct completion read_completion;
+
+ struct mutex lock;
+};
+
+static ssize_t jz4740_hwmon_show_name(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ return sprintf(buf, "jz4740\n");
+}
+
+static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
+{
+ struct jz4740_hwmon *hwmon = data;
+
+ complete(&hwmon->read_completion);
+ return IRQ_HANDLED;
+}
+
+static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
+ unsigned long t;
+ unsigned long val;
+ int ret;
+
+ mutex_lock(&hwmon->lock);
+
+ INIT_COMPLETION(hwmon->read_completion);
+
+ enable_irq(hwmon->irq);
+ hwmon->cell->enable(to_platform_device(dev));
+
+ t = wait_for_completion_interruptible_timeout(&hwmon->read_completion, HZ);
+
+ if (t > 0) {
+ val = readw(hwmon->base) & 0xfff;
+ val = (val * 3300) >> 12;
+ ret = sprintf(buf, "%lu\n", val);
+ } else {
+ ret = t ? t : -ETIMEDOUT;
+ }
+
+ hwmon->cell->disable(to_platform_device(dev));
+ disable_irq(hwmon->irq);
+
+ mutex_unlock(&hwmon->lock);
+
+ return ret;
+}
+
+static DEVICE_ATTR(name, S_IRUGO, jz4740_hwmon_show_name, NULL);
+static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL, 0);
+
+static struct attribute jz4740_hwmon_attributes[] = {
+ &dev_attr_name.attr,
+ &sensor_dev_attr_in0_input.dev_attr.attr,
+ NULL
+};
+
+static const struct attribute_group jz4740_hwmon_attr_group = {
+ .attrs = jz4740_hwmon_attributes,
+};
+
+static int __devinit jz4740_hwmon_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_hwmon *hwmon;
+
+ hwmon = kmalloc(sizeof(*hwmon), GFP_KERNEL);
+
+ hwmon->cell = pdev->dev.platform_data;
+
+ hwmon->irq = platform_get_irq(pdev, 0);
+ if (hwmon->irq < 0) {
+ ret = hwmon->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free;
+ }
+
+ hwmon->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!hwmon->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ goto err_free;
+ }
+
+ hwmon->mem = request_mem_region(hwmon->mem->start,
+ resource_size(hwmon->mem), pdev->name);
+ if (!hwmon->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ hwmon->base = ioremap_nocache(hwmon->mem->start, resource_size(hwmon->mem));
+ if (!hwmon->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ init_completion(&hwmon->read_completion);
+ mutex_init(&hwmon->lock);
+
+ platform_set_drvdata(pdev, hwmon);
+
+ ret = request_irq(hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+ goto err_iounmap;
+ }
+ disable_irq(hwmon->irq);
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create sysfs group: %d\n", ret);
+ goto err_free_irq;
+ }
+
+ hwmon->hwmon = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(hwmon->hwmon)) {
+ ret = PTR_ERR(hwmon->hwmon);
+ goto err_remove_file;
+ }
+
+ return 0;
+
+err_remove_file:
+ sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
+err_free_irq:
+ free_irq(hwmon->irq, hwmon);
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(hwmon->base);
+err_release_mem_region:
+ release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
+err_free:
+ kfree(hwmon);
+
+ return ret;
+}
+
+static int __devexit jz4740_hwmon_remove(struct platform_device *pdev)
+{
+ struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(hwmon->hwmon);
+ sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
+
+ free_irq(hwmon->irq, hwmon);
+
+ iounmap(hwmon->base);
+ release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(hwmon);
+
+ return 0;
+}
+
+struct platform_driver jz4740_hwmon_driver = {
+ .probe = jz4740_hwmon_probe,
+ .remove = __devexit_p(jz4740_hwmon_remove),
+ .driver = {
+ .name = "jz4740-hwmon",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_hwmon_init(void)
+{
+ return platform_driver_register(&jz4740_hwmon_driver);
+}
+module_init(jz4740_hwmon_init);
+
+static void __exit jz4740_hwmon_exit(void)
+{
+ platform_driver_unregister(&jz4740_hwmon_driver);
+}
+module_exit(jz4740_hwmon_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC HWMON driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
--
1.5.6.5

2010-06-19 14:50:27

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] alsa: ASoC: Add JZ4740 codec driver

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: [email protected]

--
Changes since v1
- Put Kconfig entry in alphabetic order
- Drop codec_set_fmt since the codec supports only one format
- Rename "Capture Volume" control to "Master Capture Volume"
- Drop unnecessary format checks
- Add suspend/resume
- Cleanup jz4740_codec_set_bias_level

Changes since v2
- Drop codec prefix from the filename. Note that I keeped it for the object
name, because otherwise when build as a module it will clash with the ASoC
JZ4740 platform support.
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/jz4740.c | 514 +++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/jz4740.h | 20 ++
4 files changed, 540 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/jz4740.c
create mode 100644 sound/soc/codecs/jz4740.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c37c844..00d347d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -24,6 +24,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS42L51 if I2C
select SND_SOC_CS4270 if I2C
+ select SND_SOC_JZ4740 if SOC_JZ4740
select SND_SOC_MAX9877 if I2C
select SND_SOC_DA7210 if I2C
select SND_SOC_PCM3008
@@ -142,6 +143,9 @@ config SND_SOC_CS4270_VD33_ERRATA
config SND_SOC_CX20442
tristate

+config SND_SOC_JZ4740_CODEC
+ tristate
+
config SND_SOC_L3
tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4a9c205..d8d9eeb 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-jz4740-codec-objs := jz4740.o

# Amp
snd-soc-max9877-objs := max9877.o
@@ -80,6 +81,7 @@ obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
+obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c
new file mode 100644
index 0000000..35848b6
--- /dev/null
+++ b/sound/soc/codecs/jz4740.c
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc.h>
+
+#define JZ4740_REG_CODEC_1 0x0
+#define JZ4740_REG_CODEC_2 0x1
+
+#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
+#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
+#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
+#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
+#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
+#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
+#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
+#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
+#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
+#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
+#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
+#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
+#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
+#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
+#define JZ4740_CODEC_1_SUSPEND BIT(1)
+#define JZ4740_CODEC_1_RESET BIT(0)
+
+#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
+#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
+#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
+#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
+#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
+#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000
+#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16
+#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0
+
+static const uint32_t jz4740_codec_regs[] = {
+ 0x021b2302, 0x00170803,
+};
+
+struct jz4740_codec {
+ void __iomem *base;
+ struct resource *mem;
+
+ uint32_t reg_cache[2];
+ struct snd_soc_codec codec;
+};
+
+static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec)
+{
+ return container_of(codec, struct jz4740_codec, codec);
+}
+
+static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+ return readl(jz4740_codec->base + (reg << 2));
+}
+
+static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+
+ jz4740_codec->reg_cache[reg] = val;
+ writel(val, jz4740_codec->base + (reg << 2));
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new jz4740_codec_controls[] = {
+ SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
+ SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
+ SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
+ SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
+ SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
+ SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
+ SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
+ SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
+
+ SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
+ jz4740_codec_output_controls,
+ ARRAY_SIZE(jz4740_codec_output_controls)),
+
+ SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
+ jz4740_codec_input_controls,
+ ARRAY_SIZE(jz4740_codec_input_controls)),
+ SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+
+ SND_SOC_DAPM_INPUT("MIC"),
+ SND_SOC_DAPM_INPUT("LIN"),
+ SND_SOC_DAPM_INPUT("RIN"),
+};
+
+static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
+ {"Line Input", NULL, "LIN"},
+ {"Line Input", NULL, "RIN"},
+
+ {"Input Mixer", "Line Capture Switch", "Line Input"},
+ {"Input Mixer", "Mic Capture Switch", "MIC"},
+
+ {"ADC", NULL, "Input Mixer"},
+
+ {"Output Mixer", "Bypass Switch", "Input Mixer"},
+ {"Output Mixer", "DAC Switch", "DAC"},
+
+ {"LOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+};
+
+static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ uint32_t val;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ switch (params_rate(params)) {
+ case 8000:
+ val = 0;
+ break;
+ case 11025:
+ val = 1;
+ break;
+ case 12000:
+ val = 2;
+ break;
+ case 16000:
+ val = 3;
+ break;
+ case 22050:
+ val = 4;
+ break;
+ case 24000:
+ val = 5;
+ break;
+ case 32000:
+ val = 6;
+ break;
+ case 44100:
+ val = 7;
+ break;
+ case 48000:
+ val = 8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
+ .hw_params = jz4740_codec_hw_params,
+};
+
+struct snd_soc_dai jz4740_codec_dai = {
+ .name = "jz4740",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .ops = &jz4740_codec_dai_ops,
+ .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(jz4740_codec_dai);
+
+static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
+{
+ int i;
+ uint32_t *cache = codec->reg_cache;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
+ udelay(2);
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
+ jz4740_codec_write(codec, i, cache[i]);
+}
+
+static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ unsigned int mask;
+ unsigned int value;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = 0;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* The only way to clear the suspend flag is to reset the codec */
+ if (codec->bias_level == SND_SOC_BIAS_OFF)
+ jz4740_codec_wakeup(codec);
+
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_OFF:
+ mask = JZ4740_CODEC_1_SUSPEND;
+ value = JZ4740_CODEC_1_SUSPEND;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ default:
+ break;
+ }
+
+ codec->bias_level = level;
+
+ return 0;
+}
+
+static struct snd_soc_codec *jz4740_codec_codec;
+
+static int jz4740_codec_dev_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = jz4740_codec_codec;
+
+ BUG_ON(!codec);
+
+ socdev->card->codec = codec;
+
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_add_controls(codec, jz4740_codec_controls,
+ ARRAY_SIZE(jz4740_codec_controls));
+
+ snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets,
+ ARRAY_SIZE(jz4740_codec_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes,
+ ARRAY_SIZE(jz4740_codec_dapm_routes));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+static int jz4740_codec_dev_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+struct snd_soc_codec_device soc_codec_dev_jz4740_codec = {
+ .probe = jz4740_codec_dev_probe,
+ .remove = jz4740_codec_dev_remove,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec);
+
+#ifdef CONFIG_PM_SLEEP
+
+static int jz4740_codec_suspend(struct device *dev)
+{
+ struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev);
+ return jz4740_codec_set_bias_level(&jz4740_codec->codec,
+ SND_SOC_BIAS_OFF);
+}
+
+static int jz4740_codec_resume(struct device *dev)
+{
+ struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev);
+ return jz4740_codec_set_bias_level(&jz4740_codec->codec,
+ SND_SOC_BIAS_STANDBY);
+}
+
+static const struct dev_pm_ops jz4740_pm_ops = {
+ .suspend = jz4740_codec_suspend,
+ .resume = jz4740_codec_resume,
+};
+
+#define JZ4740_CODEC_PM_OPS (&jz4740_pm_ops)
+
+#else
+#define JZ4740_CODEC_PM_OPS NULL
+#endif
+
+static int __devinit jz4740_codec_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_codec *jz4740_codec;
+ struct snd_soc_codec *codec;
+ struct resource *mem;
+
+ jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
+ if (!jz4740_codec)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
+ ret = -ENOENT;
+ goto err_free_codec;
+ }
+
+ mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ ret = -EBUSY;
+ goto err_free_codec;
+ }
+
+ jz4740_codec->base = ioremap(mem->start, resource_size(mem));
+ if (!jz4740_codec->base) {
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ ret = -EBUSY;
+ goto err_release_mem_region;
+ }
+ jz4740_codec->mem = mem;
+
+ jz4740_codec_dai.dev = &pdev->dev;
+
+ codec = &jz4740_codec->codec;
+
+ codec->dev = &pdev->dev;
+ codec->name = "jz4740";
+ codec->owner = THIS_MODULE;
+
+ codec->read = jz4740_codec_read;
+ codec->write = jz4740_codec_write;
+ codec->set_bias_level = jz4740_codec_set_bias_level;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+
+ codec->dai = &jz4740_codec_dai;
+ codec->num_dai = 1;
+
+ codec->reg_cache = jz4740_codec->reg_cache;
+ codec->reg_cache_size = 2;
+ memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs));
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ jz4740_codec_codec = codec;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
+
+ platform_set_drvdata(pdev, jz4740_codec);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec\n");
+ goto err_iounmap;
+ }
+
+ ret = snd_soc_register_dai(&jz4740_codec_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec dai\n");
+ goto err_unregister_codec;
+ }
+
+ jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+
+err_unregister_codec:
+ snd_soc_unregister_codec(codec);
+err_iounmap:
+ iounmap(jz4740_codec->base);
+err_release_mem_region:
+ release_mem_region(mem->start, resource_size(mem));
+err_free_codec:
+ kfree(jz4740_codec);
+
+ return ret;
+}
+
+static int __devexit jz4740_codec_remove(struct platform_device *pdev)
+{
+ struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
+ struct resource *mem = jz4740_codec->mem;
+
+ snd_soc_unregister_dai(&jz4740_codec_dai);
+ snd_soc_unregister_codec(&jz4740_codec->codec);
+
+ iounmap(jz4740_codec->base);
+ release_mem_region(mem->start, resource_size(mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(jz4740_codec);
+
+ return 0;
+}
+
+static struct platform_driver jz4740_codec_driver = {
+ .probe = jz4740_codec_probe,
+ .remove = __devexit_p(jz4740_codec_remove),
+ .driver = {
+ .name = "jz4740-codec",
+ .owner = THIS_MODULE,
+ .pm = JZ4740_CODEC_PM_OPS,
+ },
+};
+
+static int __init jz4740_codec_init(void)
+{
+ return platform_driver_register(&jz4740_codec_driver);
+}
+module_init(jz4740_codec_init);
+
+static void __exit jz4740_codec_exit(void)
+{
+ platform_driver_unregister(&jz4740_codec_driver);
+}
+module_exit(jz4740_codec_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:jz4740-codec");
diff --git a/sound/soc/codecs/jz4740.h b/sound/soc/codecs/jz4740.h
new file mode 100644
index 0000000..b5a0691
--- /dev/null
+++ b/sound/soc/codecs/jz4740.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__
+#define __SND_SOC_CODECS_JZ4740_CODEC_H__
+
+extern struct snd_soc_dai jz4740_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec;
+
+#endif
--
1.5.6.5

2010-06-19 14:51:12

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] alsa: ASoC: Add JZ4740 ASoC support

This patch adds ASoC support for JZ4740 SoCs I2S module.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: [email protected]

alsa: ASoC: jz4740-i2s: Properly free and disable clocks in case of an error

--
Changes since v1
- i2s: Properly free and disable clocks in case of an error in probe
- i2s: Drop unnecessary format checks which are already done in the core
- i2s: Drop set_clkdiv
- pcm: Refactor dma buffer position handling to be better comprehensible
- Cleanup and fix Kconfig

Changes since v2
- Use a platform device to register the snd_soc_platform
---
sound/soc/Kconfig | 1 +
sound/soc/Makefile | 1 +
sound/soc/jz4740/Kconfig | 14 +
sound/soc/jz4740/Makefile | 9 +
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 ++
8 files changed, 978 insertions(+), 0 deletions(-)
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

diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index d35f848..7137a9a 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -39,6 +39,7 @@ source "sound/soc/s3c24xx/Kconfig"
source "sound/soc/s6000/Kconfig"
source "sound/soc/sh/Kconfig"
source "sound/soc/txx9/Kconfig"
+source "sound/soc/jz4740/Kconfig"

# Supported codecs
source "sound/soc/codecs/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 97661b7..d131999 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_SND_SOC) += s3c24xx/
obj-$(CONFIG_SND_SOC) += s6000/
obj-$(CONFIG_SND_SOC) += sh/
obj-$(CONFIG_SND_SOC) += txx9/
+obj-$(CONFIG_SND_SOC) += jz4740/
diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig
new file mode 100644
index 0000000..27480f2
--- /dev/null
+++ b/sound/soc/jz4740/Kconfig
@@ -0,0 +1,14 @@
+config SND_JZ4740_SOC
+ tristate "SoC Audio for Ingenic JZ4740 SoC"
+ depends on MACH_JZ4740 && SND_SOC
+ help
+ Say Y or M if you want to add support for codecs attached to
+ the JZ4740 I2S interface. You will also need to select the audio
+ interfaces to support below.
+
+config SND_JZ4740_SOC_I2S
+ depends on SND_JZ4740_SOC
+ tristate "SoC Audio (I2S protocol) for Ingenic JZ4740 SoC"
+ help
+ Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740
+ based boards.
diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile
new file mode 100644
index 0000000..1be8d19
--- /dev/null
+++ b/sound/soc/jz4740/Makefile
@@ -0,0 +1,9 @@
+#
+# Jz4740 Platform Support
+#
+snd-soc-jz4740-objs := jz4740-pcm.o
+snd-soc-jz4740-i2s-objs := jz4740-i2s.o
+
+obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o
+obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o
+
diff --git a/sound/soc/jz4740/jz4740-i2s.c b/sound/soc/jz4740/jz4740-i2s.c
new file mode 100644
index 0000000..eb518f0
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-i2s.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+
+#include "jz4740-i2s.h"
+#include "jz4740-pcm.h"
+
+#define JZ_REG_AIC_CONF 0x00
+#define JZ_REG_AIC_CTRL 0x04
+#define JZ_REG_AIC_I2S_FMT 0x10
+#define JZ_REG_AIC_FIFO_STATUS 0x14
+#define JZ_REG_AIC_I2S_STATUS 0x1c
+#define JZ_REG_AIC_CLK_DIV 0x30
+#define JZ_REG_AIC_FIFO 0x34
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_MASK (0xf << 12)
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_MASK (0xf << 8)
+#define JZ_AIC_CONF_OVERFLOW_PLAY_LAST BIT(6)
+#define JZ_AIC_CONF_INTERNAL_CODEC BIT(5)
+#define JZ_AIC_CONF_I2S BIT(4)
+#define JZ_AIC_CONF_RESET BIT(3)
+#define JZ_AIC_CONF_BIT_CLK_MASTER BIT(2)
+#define JZ_AIC_CONF_SYNC_CLK_MASTER BIT(1)
+#define JZ_AIC_CONF_ENABLE BIT(0)
+
+#define JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET 12
+#define JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET 8
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK (0x7 << 19)
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK (0x7 << 16)
+#define JZ_AIC_CTRL_ENABLE_RX_DMA BIT(15)
+#define JZ_AIC_CTRL_ENABLE_TX_DMA BIT(14)
+#define JZ_AIC_CTRL_MONO_TO_STEREO BIT(11)
+#define JZ_AIC_CTRL_SWITCH_ENDIANNESS BIT(10)
+#define JZ_AIC_CTRL_SIGNED_TO_UNSIGNED BIT(9)
+#define JZ_AIC_CTRL_FLUSH BIT(8)
+#define JZ_AIC_CTRL_ENABLE_ROR_INT BIT(6)
+#define JZ_AIC_CTRL_ENABLE_TUR_INT BIT(5)
+#define JZ_AIC_CTRL_ENABLE_RFS_INT BIT(4)
+#define JZ_AIC_CTRL_ENABLE_TFS_INT BIT(3)
+#define JZ_AIC_CTRL_ENABLE_LOOPBACK BIT(2)
+#define JZ_AIC_CTRL_ENABLE_PLAYBACK BIT(1)
+#define JZ_AIC_CTRL_ENABLE_CAPTURE BIT(0)
+
+#define JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET 19
+#define JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET 16
+
+#define JZ_AIC_I2S_FMT_DISABLE_BIT_CLK BIT(12)
+#define JZ_AIC_I2S_FMT_ENABLE_SYS_CLK BIT(4)
+#define JZ_AIC_I2S_FMT_MSB BIT(0)
+
+#define JZ_AIC_I2S_STATUS_BUSY BIT(2)
+
+#define JZ_AIC_CLK_DIV_MASK 0xf
+
+struct jz4740_i2s {
+ struct resource *mem;
+ void __iomem *base;
+ dma_addr_t phys_base;
+
+ struct clk *clk_aic;
+ struct clk *clk_i2s;
+
+ struct jz4740_pcm_config pcm_config_playback;
+ struct jz4740_pcm_config pcm_config_capture;
+};
+
+static inline uint32_t jz4740_i2s_read(const struct jz4740_i2s *i2s,
+ unsigned int reg)
+{
+ return readl(i2s->base + reg);
+}
+
+static inline void jz4740_i2s_write(const struct jz4740_i2s *i2s,
+ unsigned int reg, uint32_t value)
+{
+ writel(value, i2s->base + reg);
+}
+
+static inline struct jz4740_i2s *jz4740_dai_to_i2s(struct snd_soc_dai *dai)
+{
+ return dai->private_data;
+}
+
+static int jz4740_i2s_startup(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf, ctrl;
+
+ if (dai->active)
+ return 0;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+ ctrl |= JZ_AIC_CTRL_FLUSH;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ clk_enable(i2s->clk_i2s);
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf |= JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ return 0;
+}
+
+static void jz4740_i2s_shutdown(struct snd_pcm_substream *substream,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ if (!dai->active)
+ return;
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf &= ~JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ clk_disable(i2s->clk_i2s);
+}
+
+static int jz4740_i2s_trigger(struct snd_pcm_substream *substream, int cmd,
+ struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+
+ uint32_t ctrl;
+ uint32_t mask;
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+ mask = JZ_AIC_CTRL_ENABLE_PLAYBACK | JZ_AIC_CTRL_ENABLE_TX_DMA;
+ else
+ mask = JZ_AIC_CTRL_ENABLE_CAPTURE | JZ_AIC_CTRL_ENABLE_RX_DMA;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ ctrl |= mask;
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ ctrl &= ~mask;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ return 0;
+}
+
+static int jz4740_i2s_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+
+ uint32_t format = 0;
+ uint32_t conf;
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+
+ conf &= ~(JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER);
+
+ switch (fmt & SND_SOC_DAIFMT_MASTER_MASK) {
+ case SND_SOC_DAIFMT_CBS_CFS:
+ conf |= JZ_AIC_CONF_BIT_CLK_MASTER | JZ_AIC_CONF_SYNC_CLK_MASTER;
+ format |= JZ_AIC_I2S_FMT_ENABLE_SYS_CLK;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFS:
+ conf |= JZ_AIC_CONF_SYNC_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBS_CFM:
+ conf |= JZ_AIC_CONF_BIT_CLK_MASTER;
+ break;
+ case SND_SOC_DAIFMT_CBM_CFM:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+ case SND_SOC_DAIFMT_MSB:
+ format |= JZ_AIC_I2S_FMT_MSB;
+ break;
+ case SND_SOC_DAIFMT_I2S:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+ case SND_SOC_DAIFMT_NB_NF:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+ jz4740_i2s_write(i2s, JZ_REG_AIC_I2S_FMT, format);
+
+ return 0;
+}
+
+static int jz4740_i2s_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ enum jz4740_dma_width dma_width;
+ struct jz4740_pcm_config *pcm_config;
+ unsigned int sample_size;
+ uint32_t ctrl;
+
+ ctrl = jz4740_i2s_read(i2s, JZ_REG_AIC_CTRL);
+
+ switch (params_format(params)) {
+ case SNDRV_PCM_FORMAT_S8:
+ sample_size = 0;
+ dma_width = JZ4740_DMA_WIDTH_8BIT;
+ break;
+ case SNDRV_PCM_FORMAT_S16:
+ sample_size = 1;
+ dma_width = JZ4740_DMA_WIDTH_16BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ ctrl &= ~JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_MASK;
+ ctrl |= sample_size << JZ_AIC_CTRL_OUTPUT_SAMPLE_SIZE_OFFSET;
+ if (params_channels(params) == 1)
+ ctrl |= JZ_AIC_CTRL_MONO_TO_STEREO;
+ else
+ ctrl &= ~JZ_AIC_CTRL_MONO_TO_STEREO;
+
+ pcm_config = &i2s->pcm_config_playback;
+ pcm_config->dma_config.dst_width = dma_width;
+
+ } else {
+ ctrl &= ~JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_MASK;
+ ctrl |= sample_size << JZ_AIC_CTRL_INPUT_SAMPLE_SIZE_OFFSET;
+
+ pcm_config = &i2s->pcm_config_capture;
+ pcm_config->dma_config.src_width = dma_width;
+ }
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CTRL, ctrl);
+
+ snd_soc_dai_set_dma_data(dai, substream, pcm_config);
+
+ return 0;
+}
+
+static int jz4740_i2s_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+ unsigned int freq, int dir)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ struct clk *parent;
+ int ret = 0;
+
+ switch (clk_id) {
+ case JZ4740_I2S_CLKSRC_EXT:
+ parent = clk_get(NULL, "ext");
+ clk_set_parent(i2s->clk_i2s, parent);
+ break;
+ case JZ4740_I2S_CLKSRC_PLL:
+ parent = clk_get(NULL, "pll half");
+ clk_set_parent(i2s->clk_i2s, parent);
+ ret = clk_set_rate(i2s->clk_i2s, freq);
+ break;
+ default:
+ return -EINVAL;
+ }
+ clk_put(parent);
+
+ return ret;
+}
+
+static int jz4740_i2s_suspend(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ if (dai->active) {
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf &= ~JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ clk_disable(i2s->clk_i2s);
+ }
+
+ clk_disable(i2s->clk_aic);
+
+ return 0;
+}
+
+static int jz4740_i2s_resume(struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ clk_enable(i2s->clk_aic);
+
+ if (dai->active) {
+ clk_enable(i2s->clk_i2s);
+
+ conf = jz4740_i2s_read(i2s, JZ_REG_AIC_CONF);
+ conf |= JZ_AIC_CONF_ENABLE;
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+ }
+
+ return 0;
+}
+
+static int jz4740_i2s_probe(struct platform_device *pdev, struct snd_soc_dai *dai)
+{
+ struct jz4740_i2s *i2s = jz4740_dai_to_i2s(dai);
+ uint32_t conf;
+
+ conf = (7 << JZ_AIC_CONF_FIFO_RX_THRESHOLD_OFFSET) |
+ (8 << JZ_AIC_CONF_FIFO_TX_THRESHOLD_OFFSET) |
+ JZ_AIC_CONF_OVERFLOW_PLAY_LAST |
+ JZ_AIC_CONF_I2S |
+ JZ_AIC_CONF_INTERNAL_CODEC;
+
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, JZ_AIC_CONF_RESET);
+ jz4740_i2s_write(i2s, JZ_REG_AIC_CONF, conf);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_i2s_dai_ops = {
+ .startup = jz4740_i2s_startup,
+ .shutdown = jz4740_i2s_shutdown,
+ .trigger = jz4740_i2s_trigger,
+ .hw_params = jz4740_i2s_hw_params,
+ .set_fmt = jz4740_i2s_set_fmt,
+ .set_sysclk = jz4740_i2s_set_sysclk,
+};
+
+#define JZ4740_I2S_FMTS (SNDRV_PCM_FMTBIT_S8 | \
+ SNDRV_PCM_FMTBIT_S16_LE)
+
+struct snd_soc_dai jz4740_i2s_dai = {
+ .name = "jz4740-i2s",
+ .probe = jz4740_i2s_probe,
+ .playback = {
+ .channels_min = 1,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = JZ4740_I2S_FMTS,
+ },
+ .capture = {
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = JZ4740_I2S_FMTS,
+ },
+ .symmetric_rates = 1,
+ .ops = &jz4740_i2s_dai_ops,
+ .suspend = jz4740_i2s_suspend,
+ .resume = jz4740_i2s_resume,
+};
+EXPORT_SYMBOL_GPL(jz4740_i2s_dai);
+
+static void __devinit jz4740_i2c_init_pcm_config(struct jz4740_i2s *i2s)
+{
+ struct jz4740_dma_config *dma_config;
+
+ /* Playback */
+ dma_config = &i2s->pcm_config_playback.dma_config;
+ dma_config->src_width = JZ4740_DMA_WIDTH_32BIT,
+ dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+ dma_config->request_type = JZ4740_DMA_TYPE_AIC_TRANSMIT;
+ dma_config->flags = JZ4740_DMA_SRC_AUTOINC;
+ dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+ i2s->pcm_config_playback.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+
+ /* Capture */
+ dma_config = &i2s->pcm_config_capture.dma_config;
+ dma_config->dst_width = JZ4740_DMA_WIDTH_32BIT,
+ dma_config->transfer_size = JZ4740_DMA_TRANSFER_SIZE_16BYTE;
+ dma_config->request_type = JZ4740_DMA_TYPE_AIC_RECEIVE;
+ dma_config->flags = JZ4740_DMA_DST_AUTOINC;
+ dma_config->mode = JZ4740_DMA_MODE_SINGLE;
+ i2s->pcm_config_capture.fifo_addr = i2s->phys_base + JZ_REG_AIC_FIFO;
+}
+
+static int __devinit jz4740_i2s_dev_probe(struct platform_device *pdev)
+{
+ struct jz4740_i2s *i2s;
+ int ret;
+
+ i2s = kzalloc(sizeof(*i2s), GFP_KERNEL);
+
+ if (!i2s)
+ return -ENOMEM;
+
+ i2s->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!i2s->mem) {
+ ret = -ENOENT;
+ goto err_free;
+ }
+
+ i2s->mem = request_mem_region(i2s->mem->start, resource_size(i2s->mem),
+ pdev->name);
+ if (!i2s->mem) {
+ ret = -EBUSY;
+ goto err_free;
+ }
+
+ i2s->base = ioremap_nocache(i2s->mem->start, resource_size(i2s->mem));
+ if (!i2s->base) {
+ ret = -EBUSY;
+ goto err_release_mem_region;
+ }
+
+ i2s->phys_base = i2s->mem->start;
+
+ i2s->clk_aic = clk_get(&pdev->dev, "aic");
+ if (IS_ERR(i2s->clk_aic)) {
+ ret = PTR_ERR(i2s->clk_aic);
+ goto err_iounmap;
+ }
+
+ i2s->clk_i2s = clk_get(&pdev->dev, "i2s");
+ if (IS_ERR(i2s->clk_i2s)) {
+ ret = PTR_ERR(i2s->clk_i2s);
+ goto err_clk_put_aic;
+ }
+
+ clk_enable(i2s->clk_aic);
+
+ jz4740_i2c_init_pcm_config(i2s);
+
+ jz4740_i2s_dai.private_data = i2s;
+ ret = snd_soc_register_dai(&jz4740_i2s_dai);
+
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register DAI\n");
+ goto err_clk_put_i2s;
+ }
+
+ platform_set_drvdata(pdev, i2s);
+
+ return 0;
+
+err_clk_put_i2s:
+ clk_disable(i2s->clk_aic);
+ clk_put(i2s->clk_i2s);
+err_clk_put_aic:
+ clk_put(i2s->clk_aic);
+err_iounmap:
+ iounmap(i2s->base);
+err_release_mem_region:
+ release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+err_free:
+ kfree(i2s);
+
+ return ret;
+}
+
+static int __devexit jz4740_i2s_dev_remove(struct platform_device *pdev)
+{
+ struct jz4740_i2s *i2s = platform_get_drvdata(pdev);
+
+ snd_soc_unregister_dai(&jz4740_i2s_dai);
+
+ clk_disable(i2s->clk_aic);
+ clk_put(i2s->clk_i2s);
+ clk_put(i2s->clk_aic);
+
+ iounmap(i2s->base);
+ release_mem_region(i2s->mem->start, resource_size(i2s->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(i2s);
+
+ return 0;
+}
+
+static struct platform_driver jz4740_i2s_driver = {
+ .probe = jz4740_i2s_dev_probe,
+ .remove = __devexit_p(jz4740_i2s_dev_remove),
+ .driver = {
+ .name = "jz4740-i2s",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_i2s_init(void)
+{
+ return platform_driver_register(&jz4740_i2s_driver);
+}
+module_init(jz4740_i2s_init);
+
+static void __exit jz4740_i2s_exit(void)
+{
+ platform_driver_unregister(&jz4740_i2s_driver);
+}
+module_exit(jz4740_i2s_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen, <[email protected]>");
+MODULE_DESCRIPTION("Ingenic JZ4740 SoC I2S driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-i2s");
diff --git a/sound/soc/jz4740/jz4740-i2s.h b/sound/soc/jz4740/jz4740-i2s.h
new file mode 100644
index 0000000..da22ed8
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-i2s.h
@@ -0,0 +1,18 @@
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_I2S_H
+#define _JZ4740_I2S_H
+
+/* I2S clock source */
+#define JZ4740_I2S_CLKSRC_EXT 0
+#define JZ4740_I2S_CLKSRC_PLL 1
+
+#define JZ4740_I2S_BIT_CLK 0
+
+extern struct snd_soc_dai jz4740_i2s_dai;
+
+#endif
diff --git a/sound/soc/jz4740/jz4740-pcm.c b/sound/soc/jz4740/jz4740-pcm.c
new file mode 100644
index 0000000..ee68d85
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-pcm.c
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/dma-mapping.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include <asm/mach-jz4740/dma.h>
+#include "jz4740-pcm.h"
+
+struct jz4740_runtime_data {
+ unsigned long dma_period;
+ dma_addr_t dma_start;
+ dma_addr_t dma_pos;
+ dma_addr_t dma_end;
+
+ struct jz4740_dma_chan *dma;
+
+ dma_addr_t fifo_addr;
+};
+
+/* identify hardware playback capabilities */
+static const struct snd_pcm_hardware jz4740_pcm_hardware = {
+ .info = SNDRV_PCM_INFO_MMAP |
+ SNDRV_PCM_INFO_MMAP_VALID |
+ SNDRV_PCM_INFO_INTERLEAVED |
+ SNDRV_PCM_INFO_BLOCK_TRANSFER,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .channels_min = 1,
+ .channels_max = 2,
+ .period_bytes_min = 16,
+ .period_bytes_max = 2 * PAGE_SIZE,
+ .periods_min = 2,
+ .periods_max = 128,
+ .buffer_bytes_max = 128 * 2 * PAGE_SIZE,
+ .fifo_size = 32,
+};
+
+static void jz4740_pcm_start_transfer(struct jz4740_runtime_data *prtd,
+ struct snd_pcm_substream *substream)
+{
+ unsigned long count;
+
+ if (prtd->dma_pos == prtd->dma_end)
+ prtd->dma_pos = prtd->dma_start;
+
+ if (prtd->dma_pos + prtd->dma_period > prtd->dma_end)
+ count = prtd->dma_end - prtd->dma_pos;
+ else
+ count = prtd->dma_period;
+
+ jz4740_dma_disable(prtd->dma);
+
+ if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+ jz4740_dma_set_src_addr(prtd->dma, prtd->dma_pos);
+ jz4740_dma_set_dst_addr(prtd->dma, prtd->fifo_addr);
+ } else {
+ jz4740_dma_set_src_addr(prtd->dma, prtd->fifo_addr);
+ jz4740_dma_set_dst_addr(prtd->dma, prtd->dma_pos);
+ }
+
+ jz4740_dma_set_transfer_count(prtd->dma, count);
+
+ prtd->dma_pos += count;
+
+ jz4740_dma_enable(prtd->dma);
+}
+
+static void jz4740_pcm_dma_transfer_done(struct jz4740_dma_chan *dma, int err,
+ void *dev_id)
+{
+ struct snd_pcm_substream *substream = dev_id;
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ snd_pcm_period_elapsed(substream);
+
+ jz4740_pcm_start_transfer(prtd, substream);
+}
+
+static int jz4740_pcm_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct jz4740_pcm_config *config;
+
+ config = snd_soc_dai_get_dma_data(rtd->dai->cpu_dai, substream);
+
+ if (!config)
+ return 0;
+
+ if (!prtd->dma) {
+ if (substream->stream == SNDRV_PCM_STREAM_CAPTURE)
+ prtd->dma = jz4740_dma_request(substream, "PCM Capture");
+ else
+ prtd->dma = jz4740_dma_request(substream, "PCM Playback");
+ }
+
+ if (!prtd->dma)
+ return -EBUSY;
+
+ jz4740_dma_configure(prtd->dma, &config->dma_config);
+ prtd->fifo_addr = config->fifo_addr;
+
+ jz4740_dma_set_complete_cb(prtd->dma, jz4740_pcm_dma_transfer_done);
+
+ snd_pcm_set_runtime_buffer(substream, &substream->dma_buffer);
+ runtime->dma_bytes = params_buffer_bytes(params);
+
+ prtd->dma_period = params_period_bytes(params);
+ prtd->dma_start = runtime->dma_addr;
+ prtd->dma_pos = prtd->dma_start;
+ prtd->dma_end = prtd->dma_start + runtime->dma_bytes;
+
+ return 0;
+}
+
+static int jz4740_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+ struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+ snd_pcm_set_runtime_buffer(substream, NULL);
+ if (prtd->dma) {
+ jz4740_dma_free(prtd->dma);
+ prtd->dma = NULL;
+ }
+
+ return 0;
+}
+
+static int jz4740_pcm_prepare(struct snd_pcm_substream *substream)
+{
+ struct jz4740_runtime_data *prtd = substream->runtime->private_data;
+
+ if (!prtd->dma)
+ return -EBUSY;
+
+ prtd->dma_pos = prtd->dma_start;
+
+ return 0;
+}
+
+static int jz4740_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ switch (cmd) {
+ case SNDRV_PCM_TRIGGER_START:
+ case SNDRV_PCM_TRIGGER_RESUME:
+ case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+ jz4740_pcm_start_transfer(prtd, substream);
+ break;
+ case SNDRV_PCM_TRIGGER_STOP:
+ case SNDRV_PCM_TRIGGER_SUSPEND:
+ case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+ jz4740_dma_disable(prtd->dma);
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static snd_pcm_uframes_t jz4740_pcm_pointer(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+ unsigned long byte_offset;
+ snd_pcm_uframes_t offset;
+ struct jz4740_dma_chan *dma = prtd->dma;
+
+ /* prtd->dma_pos points to the end of the current transfer. So by
+ * subtracting prdt->dma_start we get the offset to the end of the
+ * current period in bytes. By subtracting the residue of the transfer
+ * we get the current offset in bytes. */
+ byte_offset = prtd->dma_pos - prtd->dma_start;
+ byte_offset -= jz4740_dma_get_residue(dma);
+
+ offset = bytes_to_frames(runtime, byte_offset);
+ if (offset >= runtime->buffer_size)
+ offset = 0;
+
+ return offset;
+}
+
+static int jz4740_pcm_open(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd;
+
+ prtd = kzalloc(sizeof(*prtd), GFP_KERNEL);
+ if (prtd == NULL)
+ return -ENOMEM;
+
+ snd_soc_set_runtime_hwparams(substream, &jz4740_pcm_hardware);
+
+ runtime->private_data = prtd;
+
+ return 0;
+}
+
+static int jz4740_pcm_close(struct snd_pcm_substream *substream)
+{
+ struct snd_pcm_runtime *runtime = substream->runtime;
+ struct jz4740_runtime_data *prtd = runtime->private_data;
+
+ kfree(prtd);
+
+ return 0;
+}
+
+static int jz4740_pcm_mmap(struct snd_pcm_substream *substream,
+ struct vm_area_struct *vma)
+{
+ return remap_pfn_range(vma, vma->vm_start,
+ substream->dma_buffer.addr >> PAGE_SHIFT,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot);
+}
+
+static struct snd_pcm_ops jz4740_pcm_ops = {
+ .open = jz4740_pcm_open,
+ .close = jz4740_pcm_close,
+ .ioctl = snd_pcm_lib_ioctl,
+ .hw_params = jz4740_pcm_hw_params,
+ .hw_free = jz4740_pcm_hw_free,
+ .prepare = jz4740_pcm_prepare,
+ .trigger = jz4740_pcm_trigger,
+ .pointer = jz4740_pcm_pointer,
+ .mmap = jz4740_pcm_mmap,
+};
+
+static int jz4740_pcm_preallocate_dma_buffer(struct snd_pcm *pcm, int stream)
+{
+ struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+ struct snd_dma_buffer *buf = &substream->dma_buffer;
+ size_t size = jz4740_pcm_hardware.buffer_bytes_max;
+
+ buf->dev.type = SNDRV_DMA_TYPE_DEV;
+ buf->dev.dev = pcm->card->dev;
+ buf->private_data = NULL;
+
+ buf->area = dma_alloc_noncoherent(pcm->card->dev, size,
+ &buf->addr, GFP_KERNEL);
+ if (!buf->area)
+ return -ENOMEM;
+
+ buf->bytes = size;
+
+ return 0;
+}
+
+static void jz4740_pcm_free(struct snd_pcm *pcm)
+{
+ struct snd_pcm_substream *substream;
+ struct snd_dma_buffer *buf;
+ int stream;
+
+ for (stream = 0; stream < SNDRV_PCM_STREAM_LAST; ++stream) {
+ substream = pcm->streams[stream].substream;
+ if (!substream)
+ continue;
+
+ buf = &substream->dma_buffer;
+ if (!buf->area)
+ continue;
+
+ dma_free_noncoherent(pcm->card->dev, buf->bytes, buf->area,
+ buf->addr);
+ buf->area = NULL;
+ }
+}
+
+static u64 jz4740_pcm_dmamask = DMA_BIT_MASK(32);
+
+int jz4740_pcm_new(struct snd_card *card, struct snd_soc_dai *dai,
+ struct snd_pcm *pcm)
+{
+ int ret = 0;
+
+ if (!card->dev->dma_mask)
+ card->dev->dma_mask = &jz4740_pcm_dmamask;
+
+ if (!card->dev->coherent_dma_mask)
+ card->dev->coherent_dma_mask = DMA_BIT_MASK(32);
+
+ if (dai->playback.channels_min) {
+ ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_PLAYBACK);
+ if (ret)
+ goto err;
+ }
+
+ if (dai->capture.channels_min) {
+ ret = jz4740_pcm_preallocate_dma_buffer(pcm,
+ SNDRV_PCM_STREAM_CAPTURE);
+ if (ret)
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+struct snd_soc_platform jz4740_soc_platform = {
+ .name = "jz4740-pcm",
+ .pcm_ops = &jz4740_pcm_ops,
+ .pcm_new = jz4740_pcm_new,
+ .pcm_free = jz4740_pcm_free,
+};
+EXPORT_SYMBOL_GPL(jz4740_soc_platform);
+
+static int __devinit jz4740_pcm_probe(struct platform_device *pdev)
+{
+ return snd_soc_register_platform(&jz4740_soc_platform);
+}
+
+static int __devexit jz4740_pcm_remove(struct platform_device *pdev)
+{
+ snd_soc_unregister_platform(&jz4740_soc_platform);
+ return 0;
+}
+
+static struct platform_driver jz4740_pcm_driver = {
+ .probe = jz4740_pcm_probe,
+ .remove = __devexit_p(jz4740_pcm_remove),
+ .driver = {
+ .name = "jz4740-pcm",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_soc_platform_init(void)
+{
+ return platform_driver_register(&jz4740_pcm_driver);
+}
+module_init(jz4740_soc_platform_init);
+
+static void __exit jz4740_soc_platform_exit(void)
+{
+ return platform_driver_unregister(&jz4740_pcm_driver);
+}
+module_exit(jz4740_soc_platform_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("Ingenic SoC JZ4740 PCM driver");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/jz4740/jz4740-pcm.h b/sound/soc/jz4740/jz4740-pcm.h
new file mode 100644
index 0000000..e3f221e
--- /dev/null
+++ b/sound/soc/jz4740/jz4740-pcm.h
@@ -0,0 +1,22 @@
+/*
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _JZ4740_PCM_H
+#define _JZ4740_PCM_H
+
+#include <linux/dma-mapping.h>
+#include <asm/mach-jz4740/dma.h>
+
+/* platform data */
+extern struct snd_soc_platform jz4740_soc_platform;
+
+struct jz4740_pcm_config {
+ struct jz4740_dma_config dma_config;
+ phys_addr_t fifo_addr;
+};
+
+#endif
--
1.5.6.5

2010-06-19 14:53:27

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] alsa: ASoC: JZ4740: Add qi_lb60 board driver

This patch adds ASoC support for the qi_lb60 board.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: [email protected]

--
Changes since v1
- Refer to AMP gpios always by their define
- Do not try to set codecs format, since the set_fmt callback for the codec was
dropped.

Changes since v2
- The codec prefix was removed from jz4740 codec include file
- This patch accidentally contained a few bits meant for ASoC JZ4740 platform
support patch. Removed them.
---
sound/soc/jz4740/Kconfig | 9 +++
sound/soc/jz4740/Makefile | 4 +
sound/soc/jz4740/qi_lb60.c | 166 ++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 179 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/jz4740/qi_lb60.c

diff --git a/sound/soc/jz4740/Kconfig b/sound/soc/jz4740/Kconfig
index 27480f2..5351cba 100644
--- a/sound/soc/jz4740/Kconfig
+++ b/sound/soc/jz4740/Kconfig
@@ -12,3 +12,12 @@ config SND_JZ4740_SOC_I2S
help
Say Y if you want to use I2S protocol and I2S codec on Ingenic JZ4740
based boards.
+
+config SND_JZ4740_SOC_QI_LB60
+ tristate "SoC Audio support for Qi LB60"
+ depends on SND_JZ4740_SOC && JZ4740_QI_LB60
+ select SND_JZ4740_SOC_I2S
+ select SND_SOC_JZ4740_CODEC
+ help
+ Say Y if you want to add support for ASoC audio on the Qi LB60 board
+ a.k.a Qi Ben NanoNote.
diff --git a/sound/soc/jz4740/Makefile b/sound/soc/jz4740/Makefile
index 1be8d19..be873c1 100644
--- a/sound/soc/jz4740/Makefile
+++ b/sound/soc/jz4740/Makefile
@@ -7,3 +7,7 @@ snd-soc-jz4740-i2s-objs := jz4740-i2s.o
obj-$(CONFIG_SND_JZ4740_SOC) += snd-soc-jz4740.o
obj-$(CONFIG_SND_JZ4740_SOC_I2S) += snd-soc-jz4740-i2s.o

+# Jz4740 Machine Support
+snd-soc-qi-lb60-objs := qi_lb60.o
+
+obj-$(CONFIG_SND_JZ4740_SOC_QI_LB60) += snd-soc-qi-lb60.o
diff --git a/sound/soc/jz4740/qi_lb60.c b/sound/soc/jz4740/qi_lb60.c
new file mode 100644
index 0000000..f15f491
--- /dev/null
+++ b/sound/soc/jz4740/qi_lb60.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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/module.h>
+#include <linux/moduleparam.h>
+#include <linux/timer.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <linux/gpio.h>
+
+#include "../codecs/jz4740.h"
+#include "jz4740-pcm.h"
+#include "jz4740-i2s.h"
+
+
+#define QI_LB60_SND_GPIO JZ_GPIO_PORTB(29)
+#define QI_LB60_AMP_GPIO JZ_GPIO_PORTD(4)
+
+static int qi_lb60_spk_event(struct snd_soc_dapm_widget *widget,
+ struct snd_kcontrol *ctrl, int event)
+{
+ int on = 0;
+ if (event & SND_SOC_DAPM_POST_PMU)
+ on = 1;
+ else if (event & SND_SOC_DAPM_PRE_PMD)
+ on = 0;
+
+ gpio_set_value(QI_LB60_SND_GPIO, on);
+ gpio_set_value(QI_LB60_AMP_GPIO, on);
+
+ return 0;
+}
+
+static const struct snd_soc_dapm_widget qi_lb60_widgets[] = {
+ SND_SOC_DAPM_SPK("Speaker", qi_lb60_spk_event),
+ SND_SOC_DAPM_MIC("Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route qi_lb60_routes[] = {
+ {"Mic", NULL, "MIC"},
+ {"Speaker", NULL, "LOUT"},
+ {"Speaker", NULL, "ROUT"},
+};
+
+#define QI_LB60_DAIFMT (SND_SOC_DAIFMT_I2S | \
+ SND_SOC_DAIFMT_NB_NF | \
+ SND_SOC_DAIFMT_CBM_CFM)
+
+static int qi_lb60_codec_init(struct snd_soc_codec *codec)
+{
+ int ret;
+ struct snd_soc_dai *cpu_dai = codec->socdev->card->dai_link->cpu_dai;
+
+ snd_soc_dapm_nc_pin(codec, "LIN");
+ snd_soc_dapm_nc_pin(codec, "RIN");
+
+ ret = snd_soc_dai_set_fmt(cpu_dai, QI_LB60_DAIFMT);
+ if (ret < 0) {
+ dev_err(codec->dev, "Failed to set cpu dai format: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_dapm_new_controls(codec, qi_lb60_widgets, ARRAY_SIZE(qi_lb60_widgets));
+ snd_soc_dapm_add_routes(codec, qi_lb60_routes, ARRAY_SIZE(qi_lb60_routes));
+ snd_soc_dapm_sync(codec);
+
+ return 0;
+}
+
+static struct snd_soc_dai_link qi_lb60_dai = {
+ .name = "jz4740",
+ .stream_name = "jz4740",
+ .cpu_dai = &jz4740_i2s_dai,
+ .codec_dai = &jz4740_codec_dai,
+ .init = qi_lb60_codec_init,
+};
+
+static struct snd_soc_card qi_lb60 = {
+ .name = "QI LB60",
+ .dai_link = &qi_lb60_dai,
+ .num_links = 1,
+ .platform = &jz4740_soc_platform,
+};
+
+static struct snd_soc_device qi_lb60_snd_devdata = {
+ .card = &qi_lb60,
+ .codec_dev = &soc_codec_dev_jz4740_codec,
+};
+
+static struct platform_device *qi_lb60_snd_device;
+
+static int __init qi_lb60_init(void)
+{
+ int ret;
+
+ qi_lb60_snd_device = platform_device_alloc("soc-audio", -1);
+
+ if (!qi_lb60_snd_device)
+ return -ENOMEM;
+
+ ret = gpio_request(QI_LB60_SND_GPIO, "SND");
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to request SND GPIO(%d): %d\n",
+ QI_LB60_SND_GPIO, ret);
+ goto err_device_put;
+ }
+
+ ret = gpio_request(QI_LB60_AMP_GPIO, "AMP");
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to request AMP GPIO(%d): %d\n",
+ QI_LB60_AMP_GPIO, ret);
+ goto err_gpio_free_snd;
+ }
+
+ gpio_direction_output(QI_LB60_SND_GPIO, 0);
+ gpio_direction_output(QI_LB60_AMP_GPIO, 0);
+
+ platform_set_drvdata(qi_lb60_snd_device, &qi_lb60_snd_devdata);
+ qi_lb60_snd_devdata.dev = &qi_lb60_snd_device->dev;
+
+ ret = platform_device_add(qi_lb60_snd_device);
+ if (ret) {
+ pr_err("qi_lb60 snd: Failed to add snd soc device: %d\n", ret);
+ goto err_unset_pdata;
+ }
+
+ return 0;
+
+err_unset_pdata:
+ platform_set_drvdata(qi_lb60_snd_device, NULL);
+/*err_gpio_free_amp:*/
+ gpio_free(QI_LB60_AMP_GPIO);
+err_gpio_free_snd:
+ gpio_free(QI_LB60_SND_GPIO);
+err_device_put:
+ platform_device_put(qi_lb60_snd_device);
+
+ return ret;
+}
+module_init(qi_lb60_init);
+
+static void __exit qi_lb60_exit(void)
+{
+ gpio_free(QI_LB60_AMP_GPIO);
+ gpio_free(QI_LB60_SND_GPIO);
+ platform_device_unregister(qi_lb60_snd_device);
+}
+module_exit(qi_lb60_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
--
1.5.6.5

2010-06-19 15:30:23

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver

Hi

Matt Fleming wrote:
> On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen
<[email protected]> 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

2010-06-19 16:24:17

by Jean Delvare

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v3] hwmon: Add JZ4740 ADC driver

Hi Lars-Peter,

On Sat, 19 Jun 2010 16:47:00 +0200, Lars-Peter Clausen wrote:
> This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: [email protected]
>
> --
> Changes since v1
> - Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
> ADC driver now only reads the adcin value.
> Changes since v2
> - Add name sysfs attribute
> - Report adcin in value in millivolts

Changes look good. A few more comments below; other than these, your
driver look good, and I can add it to my hwmon tree and schedule it for
2.6.36 if you want.

> ---
> drivers/hwmon/Kconfig | 11 ++
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/jz4740-hwmon.c | 224 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 236 insertions(+), 0 deletions(-)
> create mode 100644 drivers/hwmon/jz4740-hwmon.c
>
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 569082c..51fc2f6 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -446,6 +446,17 @@ config SENSORS_IT87
> This driver can also be built as a module. If so, the module
> will be called it87.
>
> +config SENSORS_JZ4740
> + tristate "Ingenic JZ4740 SoC ADC driver"
> + depends on MACH_JZ4740
> + help
> + If you say yes here you get support for the Ingenic JZ4740 SoC ADC core.
> + It is required for the JZ4740 battery and touchscreen driver and is used
> + to synchronize access to the adc module between those two.
> +
> + This driver can also be build as a module. If so, the module will be
> + called jz4740-adc.

Actually not. And please use tabs for the first indentation level.

> +
> config SENSORS_LM63
> tristate "National Semiconductor LM63 and LM64"
> depends on I2C
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index bca0d45..dffbdff 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
> obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
> obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
> obj-$(CONFIG_SENSORS_IT87) += it87.o
> +obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
> obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
> obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
> obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o
> diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c
> new file mode 100644
> index 0000000..a448d78
> --- /dev/null
> +++ b/drivers/hwmon/jz4740-hwmon.c
> @@ -0,0 +1,224 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC HWMON 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/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include <linux/mfd/core.h>
> +
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +
> +struct jz4740_hwmon {
> + struct resource *mem;
> + void __iomem *base;
> +
> + int irq;
> +
> + struct mfd_cell *cell;
> + struct device *hwmon;
> +
> + struct completion read_completion;

You must include <linux/completion.h> for this.

> +
> + struct mutex lock;
> +};
> +
> +static ssize_t jz4740_hwmon_show_name(struct device *dev,
> + struct device_attribute *dev_attr, char *buf)
> +{
> + return sprintf(buf, "jz4740\n");
> +}
> +
> +static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
> +{
> + struct jz4740_hwmon *hwmon = data;
> +
> + complete(&hwmon->read_completion);
> + return IRQ_HANDLED;
> +}
> +
> +static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
> + struct device_attribute *dev_attr, char *buf)
> +{
> + struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
> + unsigned long t;
> + unsigned long val;
> + int ret;
> +
> + mutex_lock(&hwmon->lock);
> +
> + INIT_COMPLETION(hwmon->read_completion);
> +
> + enable_irq(hwmon->irq);
> + hwmon->cell->enable(to_platform_device(dev));
> +
> + t = wait_for_completion_interruptible_timeout(&hwmon->read_completion, HZ);

Line over 80 characters.

> +
> + if (t > 0) {
> + val = readw(hwmon->base) & 0xfff;
> + val = (val * 3300) >> 12;
> + ret = sprintf(buf, "%lu\n", val);
> + } else {
> + ret = t ? t : -ETIMEDOUT;
> + }
> +
> + hwmon->cell->disable(to_platform_device(dev));
> + disable_irq(hwmon->irq);
> +
> + mutex_unlock(&hwmon->lock);
> +
> + return ret;
> +}
> +
> +static DEVICE_ATTR(name, S_IRUGO, jz4740_hwmon_show_name, NULL);
> +static SENSOR_DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL, 0);

Unless you plan to add support for devices with more than 1 voltage
input, you can use DEVICE_ATTR() instead of SENSOR_DEVICE_ATTR(), it's
cheaper. If you do, you no longer need to include <linux/hwmon-sysfs.h>.

> +
> +static struct attribute jz4740_hwmon_attributes[] = {
> + &dev_attr_name.attr,
> + &sensor_dev_attr_in0_input.dev_attr.attr,
> + NULL
> +};
> +
> +static const struct attribute_group jz4740_hwmon_attr_group = {
> + .attrs = jz4740_hwmon_attributes,
> +};
> +
> +static int __devinit jz4740_hwmon_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jz4740_hwmon *hwmon;
> +
> + hwmon = kmalloc(sizeof(*hwmon), GFP_KERNEL);
> +
> + hwmon->cell = pdev->dev.platform_data;
> +
> + hwmon->irq = platform_get_irq(pdev, 0);
> + if (hwmon->irq < 0) {
> + ret = hwmon->irq;
> + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
> + goto err_free;
> + }
> +
> + hwmon->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!hwmon->mem) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
> + goto err_free;
> + }
> +
> + hwmon->mem = request_mem_region(hwmon->mem->start,
> + resource_size(hwmon->mem), pdev->name);
> + if (!hwmon->mem) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + goto err_free;
> + }
> +
> + hwmon->base = ioremap_nocache(hwmon->mem->start, resource_size(hwmon->mem));

Line over 80 characters.

> + if (!hwmon->base) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> + goto err_release_mem_region;
> + }
> +
> + init_completion(&hwmon->read_completion);
> + mutex_init(&hwmon->lock);
> +
> + platform_set_drvdata(pdev, hwmon);
> +
> + ret = request_irq(hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
> + goto err_iounmap;
> + }
> + disable_irq(hwmon->irq);
> +
> + ret = sysfs_create_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to create sysfs group: %d\n", ret);
> + goto err_free_irq;
> + }
> +
> + hwmon->hwmon = hwmon_device_register(&pdev->dev);
> + if (IS_ERR(hwmon->hwmon)) {
> + ret = PTR_ERR(hwmon->hwmon);
> + goto err_remove_file;
> + }
> +
> + return 0;
> +
> +err_remove_file:
> + sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
> +err_free_irq:
> + free_irq(hwmon->irq, hwmon);
> +err_iounmap:
> + platform_set_drvdata(pdev, NULL);
> + iounmap(hwmon->base);
> +err_release_mem_region:
> + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
> +err_free:
> + kfree(hwmon);
> +
> + return ret;
> +}
> +
> +static int __devexit jz4740_hwmon_remove(struct platform_device *pdev)
> +{
> + struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev);
> +
> + hwmon_device_unregister(hwmon->hwmon);
> + sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
> +
> + free_irq(hwmon->irq, hwmon);
> +
> + iounmap(hwmon->base);
> + release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
> +
> + platform_set_drvdata(pdev, NULL);
> + kfree(hwmon);
> +
> + return 0;
> +}
> +
> +struct platform_driver jz4740_hwmon_driver = {
> + .probe = jz4740_hwmon_probe,
> + .remove = __devexit_p(jz4740_hwmon_remove),
> + .driver = {
> + .name = "jz4740-hwmon",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init jz4740_hwmon_init(void)
> +{
> + return platform_driver_register(&jz4740_hwmon_driver);
> +}
> +module_init(jz4740_hwmon_init);
> +
> +static void __exit jz4740_hwmon_exit(void)
> +{
> + platform_driver_unregister(&jz4740_hwmon_driver);
> +}
> +module_exit(jz4740_hwmon_exit);
> +
> +MODULE_DESCRIPTION("JZ4740 SoC HWMON driver");
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-hwmon");


--
Jean Delvare

2010-06-19 17:20:42

by Greg KH

[permalink] [raw]
Subject: Re: [PATCH v2 19/26] USB: Add JZ4740 ohci support

On Sat, Jun 19, 2010 at 07:08:24AM +0200, Lars-Peter Clausen wrote:
> This patch adds ohci glue code for JZ4740 SoCs ohci module.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Greg Kroah-Hartman <[email protected]>
> Cc: David Brownell <[email protected]>
> Cc: [email protected]

Acked-by: Greg Kroah-Hartman <[email protected]>

2010-06-19 17:43:16

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

Marek Vasut wrote:
> Dne So 19. června 2010 15:05:18 Lars-Peter Clausen napsal(a):
>
>> Hi
>>
>> Marek Vasut wrote:
>>
>>> Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
>>>
>>>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>>>
>>>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>>>> Cc: Alessandro Zummo <[email protected]>
>>>> Cc: Paul Gortmaker <[email protected]>
>>>> Cc: Wan ZongShun <[email protected]>
>>>> Cc: Marek Vasut <[email protected]>
>>>> Cc: [email protected]
>>>>
>>>> ---
>>>> Changes since v1
>>>> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
>>>> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
>>>> - Check whether rtc structure could be allocated
>>>> - Fix deadlocks which could occur if the HW was broken
>>>> ---
>>>>
>>>> drivers/rtc/Kconfig | 11 ++
>>>> drivers/rtc/Makefile | 1 +
>>>> drivers/rtc/rtc-jz4740.c | 341
>>>>
>>>> ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 353
>>>> insertions(+), 0 deletions(-)
>>>>
>>>> create mode 100644 drivers/rtc/rtc-jz4740.c
>>>>
>>>> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
>>>> index 10ba12c..d0ed7e6 100644
>>>> --- a/drivers/rtc/Kconfig
>>>> +++ b/drivers/rtc/Kconfig
>>>> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
>>>>
>>>> This driver can also be built as a module. If so, the module
>>>> will be called rtc-mpc5121.
>>>>
>>>> +config RTC_DRV_JZ4740
>>>> + tristate "Ingenic JZ4740 SoC"
>>>> + depends on RTC_CLASS
>>>> + depends on MACH_JZ4740
>>>> + help
>>>> + If you say yes here you get support for the Ingenic JZ4740 SoC RTC
>>>> + controller.
>>>> +
>>>> + This driver can also be buillt as a module. If so, the module
>>>> + will be called rtc-jz4740.
>>>> +
>>>>
>>>> endif # RTC_CLASS
>>>>
>>>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>>>> index 5adbba7..fedf9bb 100644
>>>> --- a/drivers/rtc/Makefile
>>>> +++ b/drivers/rtc/Makefile
>>>> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
>>>>
>>>> obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
>>>> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
>>>> obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
>>>>
>>>> +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
>>>>
>>>> obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
>>>> obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
>>>> obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
>>>>
>>>> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
>>>> new file mode 100644
>>>> index 0000000..720afb2
>>>> --- /dev/null
>>>> +++ b/drivers/rtc/rtc-jz4740.c
>>>> @@ -0,0 +1,341 @@
>>>> +/*
>>>> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>>>> + * JZ4740 SoC RTC 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/kernel.h>
>>>> +#include <linux/module.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/rtc.h>
>>>> +#include <linux/slab.h>
>>>> +#include <linux/spinlock.h>
>>>> +
>>>> +#define JZ_REG_RTC_CTRL 0x00
>>>> +#define JZ_REG_RTC_SEC 0x04
>>>> +#define JZ_REG_RTC_SEC_ALARM 0x08
>>>> +#define JZ_REG_RTC_REGULATOR 0x0C
>>>> +#define JZ_REG_RTC_HIBERNATE 0x20
>>>> +#define JZ_REG_RTC_SCRATCHPAD 0x34
>>>> +
>>>> +#define JZ_RTC_CTRL_WRDY BIT(7)
>>>> +#define JZ_RTC_CTRL_1HZ BIT(6)
>>>> +#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
>>>> +#define JZ_RTC_CTRL_AF BIT(4)
>>>> +#define JZ_RTC_CTRL_AF_IRQ BIT(3)
>>>> +#define JZ_RTC_CTRL_AE BIT(2)
>>>> +#define JZ_RTC_CTRL_ENABLE BIT(0)
>>>> +
>>>> +struct jz4740_rtc {
>>>> + struct resource *mem;
>>>> + void __iomem *base;
>>>> +
>>>> + struct rtc_device *rtc;
>>>> +
>>>> + unsigned int irq;
>>>> +
>>>> + spinlock_t lock;
>>>> +};
>>>> +
>>>> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc,
>>>> size_t reg) +{
>>>> + return readl(rtc->base + reg);
>>>> +}
>>>> +
>>>> +static inline void jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
>>>> +{
>>>> + uint32_t ctrl;
>>>> + int timeout = 1000;
>>>> +
>>>> + do {
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
>>>>
>>> if (!timeout) {
>>>
>>> scream_and_die_in_pain();
>>> dev_err("I died");
>>>
>>> ... or something like that ... what if it times out, in this
>>> implementation, noone will know this failed.
>>>
>>> I haven't looked through the whole source code, but can't this be wrapped
>>> into the reg_write() ?
>>> }
>>>
>> Well IF it will ever die, you'll notice cause your rtc clock won't work
>> anymore.
>>
>
> Then maybe some dev_err() would be good to have there.
>
That would spam the kernel log. The best solution would be to propagate
a -EIO up to rtc_ops callers. But I'm not sure if the overhead is worth
it given that the case will virtually never happen. But I'll think about it.
>> It could be wrapped into reg_write, but there is a different version of
>> the SoC with the only difference of the RTC unit being that a different
>> mechanism is used determine whether it is ok to write or not. So it
>> makes sense to keep it seperate.
>>
>
> OK
>
>>>> +}
>>>> +
>>>> +static inline void jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t
>>>> reg, + uint32_t val)
>>>> +{
>>>> + jz4740_rtc_wait_write_ready(rtc);
>>>> + writel(val, rtc->base + reg);
>>>> +}
>>>> +
>>>> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t
>>>> mask, + uint32_t val)
>>>> +{
>>>> + unsigned long flags;
>>>> + uint32_t ctrl;
>>>> +
>>>> + spin_lock_irqsave(&rtc->lock, flags);
>>>>
>>> Can't we use local_irq_save()/local_irq_restore() ?
>>>
>> Why would that be preferable? In the non-debug, non-rt case this will
>> expand to local_irq_{save,restore} anyway, but you'll lose the semantics
>> of an lock.
>>
>
> I believe on SMP systems, local_irq_save will give you finer locking
> granularity.
>
Hm, not sure about that. But this is on a non SMP system anyway.
>>>> +
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> +
>>>> + /* Don't clear interrupt flags by accident */
>>>> + ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
>>>> +
>>>> + ctrl &= ~mask;
>>>> + ctrl |= val;
>>>> +
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
>>>> +
>>>> + spin_unlock_irqrestore(&rtc->lock, flags);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_read_time(struct device *dev, struct rtc_time
>>>> *time) +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + uint32_t secs, secs2;
>>>> + int timeout = 5;
>>>> +
>>>> + /* If the seconds register is read while it is updated, it can contain
>>>> a + * bogus value. This can be avoided by making sure that two
>>>> consecutive + * reads have the same value.
>>>> + */
>>>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>> +
>>>> + while (secs != secs2 && --timeout) {
>>>> + secs = secs2;
>>>> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
>>>> + }
>>>> +
>>>> + if (timeout == 0)
>>>> + return -EIO;
>>>> +
>>>> + rtc_time_to_tm(secs, time);
>>>> +
>>>> + return rtc_valid_tm(time);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
>>>> +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> +
>>>> + if ((uint32_t)secs != secs)
>>>> + return -EINVAL;
>>>>
>>> Is the typecast here necessary ?
>>>
>> Strictly speaking not.
>>
>
> OK
>
>>>> +
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm
>>>> *alrm) +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + uint32_t secs;
>>>> + uint32_t ctrl;
>>>> +
>>>> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
>>>> +
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> +
>>>> + alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
>>>> + alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
>>>> +
>>>>
>>> Is the double negation (!!) here necessary ?
>>>
>> To quote rtc.h "/* 0 = alarm disabled, 1 = alarm enabled */", so yes.
>>
>
> Oh my ... well, maybe someone should fix that to take 0 for NO, !0 for YES.
>
Well, it's part of the userspace api.
>>>> + rtc_time_to_tm(secs, &alrm->time);
>>>> +
>>>> + return rtc_valid_tm(&alrm->time);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm
>>>> *alrm) +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + unsigned long secs;
>>>> +
>>>> + rtc_tm_to_time(&alrm->time, &secs);
>>>> +
>>>> + if ((uint32_t)secs != secs)
>>>> + return -EINVAL;
>>>>
>>> DTTO above
>>>
>>>
>>>> +
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, (uint32_t)secs);
>>>>
>>> DTTO
>>>
>>>
>>>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE,
>>>> + alrm->enabled ? JZ_RTC_CTRL_AE : 0);
>>>>
>>> Possibly the double negation above wasn't necessary
>>>
>>>
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static inline int jz4740_irq_enable(struct device *dev, int irq,
>>>> + unsigned int enable)
>>>> +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + jz4740_rtc_ctrl_set_bits(rtc, irq, enable ? irq : 0);
>>>> +
>>>> + return 0;
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned
>>>> int enable) +{
>>>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_1HZ_IRQ, enable);
>>>> +}
>>>> +
>>>> +static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int
>>>> enable) +{
>>>> + return jz4740_irq_enable(dev, JZ_RTC_CTRL_AF_IRQ, enable);
>>>> +}
>>>> +
>>>> +static struct rtc_class_ops jz4740_rtc_ops = {
>>>> + .read_time = jz4740_rtc_read_time,
>>>> + .set_mmss = jz4740_rtc_set_mmss,
>>>> + .read_alarm = jz4740_rtc_read_alarm,
>>>> + .set_alarm = jz4740_rtc_set_alarm,
>>>> + .update_irq_enable = jz4740_rtc_update_irq_enable,
>>>> + .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
>>>> +};
>>>> +
>>>> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
>>>> +{
>>>> + struct jz4740_rtc *rtc = data;
>>>> + uint32_t ctrl;
>>>> + unsigned long events = 0;
>>>> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
>>>> +
>>>> + if (ctrl & JZ_RTC_CTRL_1HZ)
>>>> + events |= (RTC_UF | RTC_IRQF);
>>>> +
>>>> + if (ctrl & JZ_RTC_CTRL_AF)
>>>> + events |= (RTC_AF | RTC_IRQF);
>>>> +
>>>> + rtc_update_irq(rtc->rtc, 1, events);
>>>> +
>>>> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, 0);
>>>> +
>>>> + return IRQ_HANDLED;
>>>> +}
>>>> +
>>>> +void jz4740_rtc_poweroff(struct device *dev)
>>>> +{
>>>> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
>>>> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
>>>> +}
>>>> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
>>>> +
>>>> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
>>>> +{
>>>> + int ret;
>>>> + struct jz4740_rtc *rtc;
>>>> + uint32_t scratchpad;
>>>> +
>>>> + rtc = kmalloc(sizeof(*rtc), GFP_KERNEL);
>>>> + if (!rtc)
>>>> + return -ENOMEM;
>>>> +
>>>> + rtc->irq = platform_get_irq(pdev, 0);
>>>> + if (rtc->irq < 0) {
>>>> + ret = -ENOENT;
>>>> + dev_err(&pdev->dev, "Failed to get platform irq\n");
>>>> + goto err_free;
>>>> + }
>>>> +
>>>> + rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>>>> + if (!rtc->mem) {
>>>> + ret = -ENOENT;
>>>> + dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
>>>> + goto err_free;
>>>> + }
>>>> +
>>>> + rtc->mem = request_mem_region(rtc->mem->start,
>>>> resource_size(rtc->mem), + pdev->name);
>>>> + if (!rtc->mem) {
>>>> + ret = -EBUSY;
>>>> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
>>>> + goto err_free;
>>>> + }
>>>> +
>>>> + rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
>>>> + if (!rtc->base) {
>>>> + ret = -EBUSY;
>>>> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
>>>> + goto err_release_mem_region;
>>>> + }
>>>> +
>>>> + spin_lock_init(&rtc->lock);
>>>> +
>>>> + platform_set_drvdata(pdev, rtc);
>>>>
>>> dev_set_drvdata()?
>>>
>> No.
>>
>
> Why not ?
>
platform_set_drvdata(pdev, data) expands to dev_set_drvdata(&(pdev)->dev, data)

- Lars

2010-06-19 17:53:42

by Geert Uytterhoeven

[permalink] [raw]
Subject: Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver

On Sat, Jun 19, 2010 at 19:42, Lars-Peter Clausen <[email protected]> wrote:
> Marek Vasut wrote:
>> Dne So 19. června 2010 15:05:18 Lars-Peter Clausen napsal(a):
>>> Marek Vasut wrote:
>>>> Dne So 19. června 2010 07:08:20 Lars-Peter Clausen napsal(a):
>>>>> This patch adds support for the RTC unit on JZ4740 SoCs.

>>>>> +static void jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t
>>>>> mask, +    uint32_t val)
>>>>> +{
>>>>> +  unsigned long flags;
>>>>> +  uint32_t ctrl;
>>>>> +
>>>>> +  spin_lock_irqsave(&rtc->lock, flags);
>>>>>
>>>> Can't we use local_irq_save()/local_irq_restore() ?
>>>>
>>> Why would that be preferable? In the non-debug, non-rt case this will
>>> expand to local_irq_{save,restore} anyway, but you'll lose the semantics
>>> of an lock.
>>>
>>
>> I believe on SMP systems, local_irq_save will give you finer locking
>> granularity.
>>
> Hm, not sure about that. But this is on a non SMP system anyway.

If the driver will ever be used on a SMP system, local_irq_save() will
not protect
against concurrent accesses on different CPUs. So it's better to use
spin_lock_irqsave().

Gr{oetje,eeting}s,

Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds

2010-06-19 18:00:27

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v3] hwmon: Add JZ4740 ADC driver

Hi

Jean Delvare wrote:
> Hi Lars-Peter,
>
> On Sat, 19 Jun 2010 16:47:00 +0200, Lars-Peter Clausen wrote:
>
>> This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>> Cc: [email protected]
>>
>> --
>> Changes since v1
>> - Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
>> ADC driver now only reads the adcin value.
>> Changes since v2
>> - Add name sysfs attribute
>> - Report adcin in value in millivolts
>>
>
> Changes look good. A few more comments below; other than these, your
> driver look good, and I can add it to my hwmon tree and schedule it for
> 2.6.36 if you want.
>
Great thanks :)
As written in the introduction mail to this thread it would be good if
the majority of the patches could go through Ralfs tree. So if you don't
see any problems doing this, it would be nice if you could give your
Acked-By once I sent a updated patch.

- Lars

2010-06-19 19:30:43

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] RTC: Add JZ4740 RTC driver

This patch adds support for the RTC unit on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Alessandro Zummo <[email protected]>
Cc: Paul Gortmaker <[email protected]>
Cc: [email protected]

--
Changes since v1
- Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
- Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
- Check whether rtc structure could be allocated
- Remove deadlocks which could occur if the HW was broken

Changes since v2
- Use kzalloc instead of kmalloc
- Propagate errors in jz4740_rtc_reg_write up to its callers
---
drivers/rtc/Kconfig | 11 ++
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-jz4740.c | 345 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 357 insertions(+), 0 deletions(-)
create mode 100644 drivers/rtc/rtc-jz4740.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 10ba12c..d0ed7e6 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
This driver can also be built as a module. If so, the module
will be called rtc-mpc5121.

+config RTC_DRV_JZ4740
+ tristate "Ingenic JZ4740 SoC"
+ depends on RTC_CLASS
+ depends on MACH_JZ4740
+ help
+ If you say yes here you get support for the Ingenic JZ4740 SoC RTC
+ controller.
+
+ This driver can also be buillt as a module. If so, the module
+ will be called rtc-jz4740.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 5adbba7..fedf9bb 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
+obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
new file mode 100644
index 0000000..10b59fc
--- /dev/null
+++ b/drivers/rtc/rtc-jz4740.c
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC RTC 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define JZ_REG_RTC_CTRL 0x00
+#define JZ_REG_RTC_SEC 0x04
+#define JZ_REG_RTC_SEC_ALARM 0x08
+#define JZ_REG_RTC_REGULATOR 0x0C
+#define JZ_REG_RTC_HIBERNATE 0x20
+#define JZ_REG_RTC_SCRATCHPAD 0x34
+
+#define JZ_RTC_CTRL_WRDY BIT(7)
+#define JZ_RTC_CTRL_1HZ BIT(6)
+#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
+#define JZ_RTC_CTRL_AF BIT(4)
+#define JZ_RTC_CTRL_AF_IRQ BIT(3)
+#define JZ_RTC_CTRL_AE BIT(2)
+#define JZ_RTC_CTRL_ENABLE BIT(0)
+
+struct jz4740_rtc {
+ struct resource *mem;
+ void __iomem *base;
+
+ struct rtc_device *rtc;
+
+ unsigned int irq;
+
+ spinlock_t lock;
+};
+
+static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t reg)
+{
+ return readl(rtc->base + reg);
+}
+
+static int jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
+{
+ uint32_t ctrl;
+ int timeout = 1000;
+
+ do {
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+ } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
+
+ return timeout ? 0 : -EIO;
+}
+
+static inline int jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t reg,
+ uint32_t val)
+{
+ int ret;
+ ret = jz4740_rtc_wait_write_ready(rtc);
+ if (ret == 0)
+ writel(val, rtc->base + reg);
+
+ return ret;
+}
+
+static int jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t mask,
+ bool set)
+{
+ int ret;
+ unsigned long flags;
+ uint32_t ctrl;
+
+ spin_lock_irqsave(&rtc->lock, flags);
+
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+
+ /* Don't clear interrupt flags by accident */
+ ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
+
+ if (set)
+ ctrl |= mask;
+ else
+ ctrl &= ~mask;
+
+ ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
+
+ spin_unlock_irqrestore(&rtc->lock, flags);
+
+ return ret;
+}
+
+static int jz4740_rtc_read_time(struct device *dev, struct rtc_time *time)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ uint32_t secs, secs2;
+ int timeout = 5;
+
+ /* If the seconds register is read while it is updated, it can contain a
+ * bogus value. This can be avoided by making sure that two consecutive
+ * reads have the same value.
+ */
+ secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
+ secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
+
+ while (secs != secs2 && --timeout) {
+ secs = secs2;
+ secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
+ }
+
+ if (timeout == 0)
+ return -EIO;
+
+ rtc_time_to_tm(secs, time);
+
+ return rtc_valid_tm(time);
+}
+
+static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+
+ return jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
+}
+
+static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ uint32_t secs;
+ uint32_t ctrl;
+
+ secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
+
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+
+ alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
+ alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
+
+ rtc_time_to_tm(secs, &alrm->time);
+
+ return rtc_valid_tm(&alrm->time);
+}
+
+static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
+{
+ int ret;
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ unsigned long secs;
+
+ rtc_tm_to_time(&alrm->time, &secs);
+
+ ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, secs);
+ if (!ret)
+ ret = jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE, alrm->enabled);
+
+ return ret;
+}
+
+static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned int enable)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ return jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ_IRQ, enable);
+}
+
+static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ return jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AF_IRQ, enable);
+}
+
+static struct rtc_class_ops jz4740_rtc_ops = {
+ .read_time = jz4740_rtc_read_time,
+ .set_mmss = jz4740_rtc_set_mmss,
+ .read_alarm = jz4740_rtc_read_alarm,
+ .set_alarm = jz4740_rtc_set_alarm,
+ .update_irq_enable = jz4740_rtc_update_irq_enable,
+ .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t jz4740_rtc_irq(int irq, void *data)
+{
+ struct jz4740_rtc *rtc = data;
+ uint32_t ctrl;
+ unsigned long events = 0;
+
+ ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
+
+ if (ctrl & JZ_RTC_CTRL_1HZ)
+ events |= (RTC_UF | RTC_IRQF);
+
+ if (ctrl & JZ_RTC_CTRL_AF)
+ events |= (RTC_AF | RTC_IRQF);
+
+ rtc_update_irq(rtc->rtc, 1, events);
+
+ jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, false);
+
+ return IRQ_HANDLED;
+}
+
+void jz4740_rtc_poweroff(struct device *dev)
+{
+ struct jz4740_rtc *rtc = dev_get_drvdata(dev);
+ jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
+}
+EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
+
+static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_rtc *rtc;
+ uint32_t scratchpad;
+
+ rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
+ if (!rtc)
+ return -ENOMEM;
+
+ rtc->irq = platform_get_irq(pdev, 0);
+ if (rtc->irq < 0) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform irq\n");
+ goto err_free;
+ }
+
+ rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!rtc->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
+ goto err_free;
+ }
+
+ rtc->mem = request_mem_region(rtc->mem->start, resource_size(rtc->mem),
+ pdev->name);
+ if (!rtc->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
+ if (!rtc->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ spin_lock_init(&rtc->lock);
+
+ platform_set_drvdata(pdev, rtc);
+
+ rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &jz4740_rtc_ops,
+ THIS_MODULE);
+ if (IS_ERR(rtc->rtc)) {
+ ret = PTR_ERR(rtc->rtc);
+ dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret);
+ goto err_iounmap;
+ }
+
+ ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
+ pdev->name, rtc);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret);
+ goto err_unregister_rtc;
+ }
+
+ scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
+ if (scratchpad != 0x12345678) {
+ ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
+ ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
+ if (ret) {
+ dev_err(&pdev->dev, "Could not write write to RTC registers\n");
+ goto err_free_irq;
+ }
+ }
+
+ return 0;
+
+err_free_irq:
+ free_irq(rtc->irq, rtc);
+err_unregister_rtc:
+ rtc_device_unregister(rtc->rtc);
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(rtc->base);
+err_release_mem_region:
+ release_mem_region(rtc->mem->start, resource_size(rtc->mem));
+err_free:
+ kfree(rtc);
+
+ return ret;
+}
+
+static int __devexit jz4740_rtc_remove(struct platform_device *pdev)
+{
+ struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
+
+ free_irq(rtc->irq, rtc);
+
+ rtc_device_unregister(rtc->rtc);
+
+ iounmap(rtc->base);
+ release_mem_region(rtc->mem->start, resource_size(rtc->mem));
+
+ kfree(rtc);
+
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+struct platform_driver jz4740_rtc_driver = {
+ .probe = jz4740_rtc_probe,
+ .remove = __devexit_p(jz4740_rtc_remove),
+ .driver = {
+ .name = "jz4740-rtc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_rtc_init(void)
+{
+ return platform_driver_register(&jz4740_rtc_driver);
+}
+module_init(jz4740_rtc_init);
+
+static void __exit jz4740_rtc_exit(void)
+{
+ platform_driver_unregister(&jz4740_rtc_driver);
+}
+module_exit(jz4740_rtc_exit);
+
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
+MODULE_ALIAS("platform:jz4740-rtc");
--
1.5.6.5

2010-06-19 19:33:40

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v4] hwmon: Add JZ4740 ADC driver

This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: [email protected]

--
Changes since v1
- Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
ADC driver now only reads the adcin value.

Changes since v2
- Add name sysfs attribute
- Report adcin in value in millivolts

Changes since v3
- Fix Kconfig entry
- Add include to completion.h
- Break overlong lines
- Use DEVICE_ATTR instead of SENSOR_DEVICE_ATTR for the adcin sys file.
---
drivers/hwmon/Kconfig | 10 ++
drivers/hwmon/Makefile | 1 +
drivers/hwmon/jz4740-hwmon.c | 226 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 237 insertions(+), 0 deletions(-)
create mode 100644 drivers/hwmon/jz4740-hwmon.c

diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 569082c..a01f32f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -446,6 +446,16 @@ config SENSORS_IT87
This driver can also be built as a module. If so, the module
will be called it87.

+config SENSORS_JZ4740
+ tristate "Ingenic JZ4740 SoC ADC driver"
+ depends on MACH_JZ4740 && MFD_JZ4740_ADC
+ help
+ If you say yes here you get support for reading adc values from the ADCIN
+ pin on Ingenic JZ4740 SoC based boards.
+
+ This driver can also be build as a module. If so, the module will be
+ called jz4740-hwmon.
+
config SENSORS_LM63
tristate "National Semiconductor LM63 and LM64"
depends on I2C
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index bca0d45..dffbdff 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -55,6 +55,7 @@ obj-$(CONFIG_SENSORS_I5K_AMB) += i5k_amb.o
obj-$(CONFIG_SENSORS_IBMAEM) += ibmaem.o
obj-$(CONFIG_SENSORS_IBMPEX) += ibmpex.o
obj-$(CONFIG_SENSORS_IT87) += it87.o
+obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o
obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o
obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o
obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o
diff --git a/drivers/hwmon/jz4740-hwmon.c b/drivers/hwmon/jz4740-hwmon.c
new file mode 100644
index 0000000..72a4335
--- /dev/null
+++ b/drivers/hwmon/jz4740-hwmon.c
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC HWMON 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/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/completion.h>
+#include <linux/mfd/core.h>
+
+#include <linux/hwmon.h>
+
+struct jz4740_hwmon {
+ struct resource *mem;
+ void __iomem *base;
+
+ int irq;
+
+ struct mfd_cell *cell;
+ struct device *hwmon;
+
+ struct completion read_completion;
+
+ struct mutex lock;
+};
+
+static ssize_t jz4740_hwmon_show_name(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ return sprintf(buf, "jz4740\n");
+}
+
+static irqreturn_t jz4740_hwmon_irq(int irq, void *data)
+{
+ struct jz4740_hwmon *hwmon = data;
+
+ complete(&hwmon->read_completion);
+ return IRQ_HANDLED;
+}
+
+static ssize_t jz4740_hwmon_read_adcin(struct device *dev,
+ struct device_attribute *dev_attr, char *buf)
+{
+ struct jz4740_hwmon *hwmon = dev_get_drvdata(dev);
+ struct completion *completion = &hwmon->read_completion;
+ unsigned long t;
+ unsigned long val;
+ int ret;
+
+ mutex_lock(&hwmon->lock);
+
+ INIT_COMPLETION(*completion);
+
+ enable_irq(hwmon->irq);
+ hwmon->cell->enable(to_platform_device(dev));
+
+ t = wait_for_completion_interruptible_timeout(completion, HZ);
+
+ if (t > 0) {
+ val = readw(hwmon->base) & 0xfff;
+ val = (val * 3300) >> 12;
+ ret = sprintf(buf, "%lu\n", val);
+ } else {
+ ret = t ? t : -ETIMEDOUT;
+ }
+
+ hwmon->cell->disable(to_platform_device(dev));
+ disable_irq(hwmon->irq);
+
+ mutex_unlock(&hwmon->lock);
+
+ return ret;
+}
+
+static DEVICE_ATTR(name, S_IRUGO, jz4740_hwmon_show_name, NULL);
+static DEVICE_ATTR(in0_input, S_IRUGO, jz4740_hwmon_read_adcin, NULL);
+
+static struct attribute *jz4740_hwmon_attributes[] = {
+ &dev_attr_name.attr,
+ &dev_attr_in0_input.attr,
+ NULL
+};
+
+static const struct attribute_group jz4740_hwmon_attr_group = {
+ .attrs = jz4740_hwmon_attributes,
+};
+
+static int __devinit jz4740_hwmon_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_hwmon *hwmon;
+
+ hwmon = kmalloc(sizeof(*hwmon), GFP_KERNEL);
+
+ hwmon->cell = pdev->dev.platform_data;
+
+ hwmon->irq = platform_get_irq(pdev, 0);
+ if (hwmon->irq < 0) {
+ ret = hwmon->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free;
+ }
+
+ hwmon->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!hwmon->mem) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ goto err_free;
+ }
+
+ hwmon->mem = request_mem_region(hwmon->mem->start,
+ resource_size(hwmon->mem), pdev->name);
+ if (!hwmon->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ hwmon->base = ioremap_nocache(hwmon->mem->start,
+ resource_size(hwmon->mem));
+ if (!hwmon->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ init_completion(&hwmon->read_completion);
+ mutex_init(&hwmon->lock);
+
+ platform_set_drvdata(pdev, hwmon);
+
+ ret = request_irq(hwmon->irq, jz4740_hwmon_irq, 0, pdev->name, hwmon);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+ goto err_iounmap;
+ }
+ disable_irq(hwmon->irq);
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create sysfs group: %d\n", ret);
+ goto err_free_irq;
+ }
+
+ hwmon->hwmon = hwmon_device_register(&pdev->dev);
+ if (IS_ERR(hwmon->hwmon)) {
+ ret = PTR_ERR(hwmon->hwmon);
+ goto err_remove_file;
+ }
+
+ return 0;
+
+err_remove_file:
+ sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
+err_free_irq:
+ free_irq(hwmon->irq, hwmon);
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(hwmon->base);
+err_release_mem_region:
+ release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
+err_free:
+ kfree(hwmon);
+
+ return ret;
+}
+
+static int __devexit jz4740_hwmon_remove(struct platform_device *pdev)
+{
+ struct jz4740_hwmon *hwmon = platform_get_drvdata(pdev);
+
+ hwmon_device_unregister(hwmon->hwmon);
+ sysfs_remove_group(&pdev->dev.kobj, &jz4740_hwmon_attr_group);
+
+ free_irq(hwmon->irq, hwmon);
+
+ iounmap(hwmon->base);
+ release_mem_region(hwmon->mem->start, resource_size(hwmon->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(hwmon);
+
+ return 0;
+}
+
+struct platform_driver jz4740_hwmon_driver = {
+ .probe = jz4740_hwmon_probe,
+ .remove = __devexit_p(jz4740_hwmon_remove),
+ .driver = {
+ .name = "jz4740-hwmon",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_hwmon_init(void)
+{
+ return platform_driver_register(&jz4740_hwmon_driver);
+}
+module_init(jz4740_hwmon_init);
+
+static void __exit jz4740_hwmon_exit(void)
+{
+ platform_driver_unregister(&jz4740_hwmon_driver);
+}
+module_exit(jz4740_hwmon_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC HWMON driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
--
1.5.6.5

2010-06-20 01:13:28

by Wan ZongShun

[permalink] [raw]
Subject: Re: [rtc-linux] [PATCH v3] RTC: Add JZ4740 RTC driver


> This patch adds support for the RTC unit on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Alessandro Zummo <[email protected]>
> Cc: Paul Gortmaker <[email protected]>
> Cc: [email protected]
>
> --
> Changes since v1
> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
> - Check whether rtc structure could be allocated
> - Remove deadlocks which could occur if the HW was broken
>
> Changes since v2
> - Use kzalloc instead of kmalloc
> - Propagate errors in jz4740_rtc_reg_write up to its callers

Acked-by: Wan ZongShun <[email protected]>

Andrew, the v3 patch has fixed some above issues, it looks good to me now.
Could you please consider merging it to your git tree?

Thanks!

> ---
> drivers/rtc/Kconfig | 11 ++
> drivers/rtc/Makefile | 1 +
> drivers/rtc/rtc-jz4740.c | 345 ++++++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 357 insertions(+), 0 deletions(-)
> create mode 100644 drivers/rtc/rtc-jz4740.c
>
> diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
> index 10ba12c..d0ed7e6 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -905,4 +905,15 @@ config RTC_DRV_MPC5121
> This driver can also be built as a module. If so, the module
> will be called rtc-mpc5121.
>
> +config RTC_DRV_JZ4740
> + tristate "Ingenic JZ4740 SoC"
> + depends on RTC_CLASS
> + depends on MACH_JZ4740
> + help
> + If you say yes here you get support for the Ingenic JZ4740 SoC RTC
> + controller.
> +
> + This driver can also be buillt as a module. If so, the module
> + will be called rtc-jz4740.
> +
> endif # RTC_CLASS
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 5adbba7..fedf9bb 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -47,6 +47,7 @@ obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
> obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
> obj-$(CONFIG_RTC_DRV_GENERIC) += rtc-generic.o
> obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
> +obj-$(CONFIG_RTC_DRV_JZ4740) += rtc-jz4740.o
> obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
> obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
> obj-$(CONFIG_RTC_DRV_M48T35) += rtc-m48t35.o
> diff --git a/drivers/rtc/rtc-jz4740.c b/drivers/rtc/rtc-jz4740.c
> new file mode 100644
> index 0000000..10b59fc
> --- /dev/null
> +++ b/drivers/rtc/rtc-jz4740.c
> @@ -0,0 +1,345 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC RTC 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/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/rtc.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#define JZ_REG_RTC_CTRL 0x00
> +#define JZ_REG_RTC_SEC 0x04
> +#define JZ_REG_RTC_SEC_ALARM 0x08
> +#define JZ_REG_RTC_REGULATOR 0x0C
> +#define JZ_REG_RTC_HIBERNATE 0x20
> +#define JZ_REG_RTC_SCRATCHPAD 0x34
> +
> +#define JZ_RTC_CTRL_WRDY BIT(7)
> +#define JZ_RTC_CTRL_1HZ BIT(6)
> +#define JZ_RTC_CTRL_1HZ_IRQ BIT(5)
> +#define JZ_RTC_CTRL_AF BIT(4)
> +#define JZ_RTC_CTRL_AF_IRQ BIT(3)
> +#define JZ_RTC_CTRL_AE BIT(2)
> +#define JZ_RTC_CTRL_ENABLE BIT(0)
> +
> +struct jz4740_rtc {
> + struct resource *mem;
> + void __iomem *base;
> +
> + struct rtc_device *rtc;
> +
> + unsigned int irq;
> +
> + spinlock_t lock;
> +};
> +
> +static inline uint32_t jz4740_rtc_reg_read(struct jz4740_rtc *rtc, size_t reg)
> +{
> + return readl(rtc->base + reg);
> +}
> +
> +static int jz4740_rtc_wait_write_ready(struct jz4740_rtc *rtc)
> +{
> + uint32_t ctrl;
> + int timeout = 1000;
> +
> + do {
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> + } while (!(ctrl & JZ_RTC_CTRL_WRDY) && --timeout);
> +
> + return timeout ? 0 : -EIO;
> +}
> +
> +static inline int jz4740_rtc_reg_write(struct jz4740_rtc *rtc, size_t reg,
> + uint32_t val)
> +{
> + int ret;
> + ret = jz4740_rtc_wait_write_ready(rtc);
> + if (ret == 0)
> + writel(val, rtc->base + reg);
> +
> + return ret;
> +}
> +
> +static int jz4740_rtc_ctrl_set_bits(struct jz4740_rtc *rtc, uint32_t mask,
> + bool set)
> +{
> + int ret;
> + unsigned long flags;
> + uint32_t ctrl;
> +
> + spin_lock_irqsave(&rtc->lock, flags);
> +
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> +
> + /* Don't clear interrupt flags by accident */
> + ctrl |= JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF;
> +
> + if (set)
> + ctrl |= mask;
> + else
> + ctrl &= ~mask;
> +
> + ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_CTRL, ctrl);
> +
> + spin_unlock_irqrestore(&rtc->lock, flags);
> +
> + return ret;
> +}
> +
> +static int jz4740_rtc_read_time(struct device *dev, struct rtc_time *time)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + uint32_t secs, secs2;
> + int timeout = 5;
> +
> + /* If the seconds register is read while it is updated, it can contain a
> + * bogus value. This can be avoided by making sure that two consecutive
> + * reads have the same value.
> + */
> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> +
> + while (secs != secs2 && --timeout) {
> + secs = secs2;
> + secs2 = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC);
> + }
> +
> + if (timeout == 0)
> + return -EIO;
> +
> + rtc_time_to_tm(secs, time);
> +
> + return rtc_valid_tm(time);
> +}
> +
> +static int jz4740_rtc_set_mmss(struct device *dev, unsigned long secs)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> +
> + return jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, secs);
> +}
> +
> +static int jz4740_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + uint32_t secs;
> + uint32_t ctrl;
> +
> + secs = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SEC_ALARM);
> +
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> +
> + alrm->enabled = !!(ctrl & JZ_RTC_CTRL_AE);
> + alrm->pending = !!(ctrl & JZ_RTC_CTRL_AF);
> +
> + rtc_time_to_tm(secs, &alrm->time);
> +
> + return rtc_valid_tm(&alrm->time);
> +}
> +
> +static int jz4740_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
> +{
> + int ret;
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + unsigned long secs;
> +
> + rtc_tm_to_time(&alrm->time, &secs);
> +
> + ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC_ALARM, secs);
> + if (!ret)
> + ret = jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AE, alrm->enabled);
> +
> + return ret;
> +}
> +
> +static int jz4740_rtc_update_irq_enable(struct device *dev, unsigned int enable)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + return jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ_IRQ, enable);
> +}
> +
> +static int jz4740_rtc_alarm_irq_enable(struct device *dev, unsigned int enable)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + return jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_AF_IRQ, enable);
> +}
> +
> +static struct rtc_class_ops jz4740_rtc_ops = {
> + .read_time = jz4740_rtc_read_time,
> + .set_mmss = jz4740_rtc_set_mmss,
> + .read_alarm = jz4740_rtc_read_alarm,
> + .set_alarm = jz4740_rtc_set_alarm,
> + .update_irq_enable = jz4740_rtc_update_irq_enable,
> + .alarm_irq_enable = jz4740_rtc_alarm_irq_enable,
> +};
> +
> +static irqreturn_t jz4740_rtc_irq(int irq, void *data)
> +{
> + struct jz4740_rtc *rtc = data;
> + uint32_t ctrl;
> + unsigned long events = 0;
> +
> + ctrl = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_CTRL);
> +
> + if (ctrl & JZ_RTC_CTRL_1HZ)
> + events |= (RTC_UF | RTC_IRQF);
> +
> + if (ctrl & JZ_RTC_CTRL_AF)
> + events |= (RTC_AF | RTC_IRQF);
> +
> + rtc_update_irq(rtc->rtc, 1, events);
> +
> + jz4740_rtc_ctrl_set_bits(rtc, JZ_RTC_CTRL_1HZ | JZ_RTC_CTRL_AF, false);
> +
> + return IRQ_HANDLED;
> +}
> +
> +void jz4740_rtc_poweroff(struct device *dev)
> +{
> + struct jz4740_rtc *rtc = dev_get_drvdata(dev);
> + jz4740_rtc_reg_write(rtc, JZ_REG_RTC_HIBERNATE, 1);
> +}
> +EXPORT_SYMBOL_GPL(jz4740_rtc_poweroff);
> +
> +static int __devinit jz4740_rtc_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jz4740_rtc *rtc;
> + uint32_t scratchpad;
> +
> + rtc = kzalloc(sizeof(*rtc), GFP_KERNEL);
> + if (!rtc)
> + return -ENOMEM;
> +
> + rtc->irq = platform_get_irq(pdev, 0);
> + if (rtc->irq < 0) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform irq\n");
> + goto err_free;
> + }
> +
> + rtc->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!rtc->mem) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
> + goto err_free;
> + }
> +
> + rtc->mem = request_mem_region(rtc->mem->start, resource_size(rtc->mem),
> + pdev->name);
> + if (!rtc->mem) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + goto err_free;
> + }
> +
> + rtc->base = ioremap_nocache(rtc->mem->start, resource_size(rtc->mem));
> + if (!rtc->base) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> + goto err_release_mem_region;
> + }
> +
> + spin_lock_init(&rtc->lock);
> +
> + platform_set_drvdata(pdev, rtc);
> +
> + rtc->rtc = rtc_device_register(pdev->name, &pdev->dev, &jz4740_rtc_ops,
> + THIS_MODULE);
> + if (IS_ERR(rtc->rtc)) {
> + ret = PTR_ERR(rtc->rtc);
> + dev_err(&pdev->dev, "Failed to register rtc device: %d\n", ret);
> + goto err_iounmap;
> + }
> +
> + ret = request_irq(rtc->irq, jz4740_rtc_irq, 0,
> + pdev->name, rtc);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request rtc irq: %d\n", ret);
> + goto err_unregister_rtc;
> + }
> +
> + scratchpad = jz4740_rtc_reg_read(rtc, JZ_REG_RTC_SCRATCHPAD);
> + if (scratchpad != 0x12345678) {
> + ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SCRATCHPAD, 0x12345678);
> + ret = jz4740_rtc_reg_write(rtc, JZ_REG_RTC_SEC, 0);
> + if (ret) {
> + dev_err(&pdev->dev, "Could not write write to RTC registers\n");
> + goto err_free_irq;
> + }
> + }
> +
> + return 0;
> +
> +err_free_irq:
> + free_irq(rtc->irq, rtc);
> +err_unregister_rtc:
> + rtc_device_unregister(rtc->rtc);
> +err_iounmap:
> + platform_set_drvdata(pdev, NULL);
> + iounmap(rtc->base);
> +err_release_mem_region:
> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
> +err_free:
> + kfree(rtc);
> +
> + return ret;
> +}
> +
> +static int __devexit jz4740_rtc_remove(struct platform_device *pdev)
> +{
> + struct jz4740_rtc *rtc = platform_get_drvdata(pdev);
> +
> + free_irq(rtc->irq, rtc);
> +
> + rtc_device_unregister(rtc->rtc);
> +
> + iounmap(rtc->base);
> + release_mem_region(rtc->mem->start, resource_size(rtc->mem));
> +
> + kfree(rtc);
> +
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +struct platform_driver jz4740_rtc_driver = {
> + .probe = jz4740_rtc_probe,
> + .remove = __devexit_p(jz4740_rtc_remove),
> + .driver = {
> + .name = "jz4740-rtc",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init jz4740_rtc_init(void)
> +{
> + return platform_driver_register(&jz4740_rtc_driver);
> +}
> +module_init(jz4740_rtc_init);
> +
> +static void __exit jz4740_rtc_exit(void)
> +{
> + platform_driver_unregister(&jz4740_rtc_driver);
> +}
> +module_exit(jz4740_rtc_exit);
> +
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
> +MODULE_ALIAS("platform:jz4740-rtc");

2010-06-20 01:23:51

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [rtc-linux] [PATCH v3] RTC: Add JZ4740 RTC driver

Wan ZongShun wrote:
>
>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>> Cc: Alessandro Zummo <[email protected]>
>> Cc: Paul Gortmaker <[email protected]>
>> Cc: [email protected]
>>
>> --
>> Changes since v1
>> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
>> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
>> - Check whether rtc structure could be allocated
>> - Remove deadlocks which could occur if the HW was broken
>>
>> Changes since v2
>> - Use kzalloc instead of kmalloc
>> - Propagate errors in jz4740_rtc_reg_write up to its callers
>
> Acked-by: Wan ZongShun <[email protected]>
>
> Andrew, the v3 patch has fixed some above issues, it looks good to me
> now.
> Could you please consider merging it to your git tree?
>
Hi

As written in the introduction mail to this thread it would be good if
the majority of the patches could go through Ralfs tree.
So if the patch is good an "Acked-by:" would be preferable.

Thanks,
- Lars

2010-06-20 01:30:40

by Wan ZongShun

[permalink] [raw]
Subject: Re: [rtc-linux] [PATCH v3] RTC: Add JZ4740 RTC driver

Lars-Peter Clausen :
> Wan ZongShun wrote:
>>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>>
>>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>>> Cc: Alessandro Zummo <[email protected]>
>>> Cc: Paul Gortmaker <[email protected]>
>>> Cc: [email protected]
>>>
>>> --
>>> Changes since v1
>>> - Use dev_get_drvdata directly instead of wrapping it in dev_to_rtc
>>> - Add common implementation for jz4740_rtc_{alarm,update}_irq_enable
>>> - Check whether rtc structure could be allocated
>>> - Remove deadlocks which could occur if the HW was broken
>>>
>>> Changes since v2
>>> - Use kzalloc instead of kmalloc
>>> - Propagate errors in jz4740_rtc_reg_write up to its callers
>> Acked-by: Wan ZongShun <[email protected]>
>>
>> Andrew, the v3 patch has fixed some above issues, it looks good to me
>> now.
>> Could you please consider merging it to your git tree?
>>
> Hi
>
> As written in the introduction mail to this thread it would be good if
> the majority of the patches could go through Ralfs tree.
> So if the patch is good an "Acked-by:" would be preferable.
>
Okay, Sound like good to me, please do you want to do.
Acked-by: Wan ZongShun <[email protected]>

> Thanks,
> - Lars
>
>
>

2010-06-20 06:32:26

by Jean Delvare

[permalink] [raw]
Subject: Re: [lm-sensors] [PATCH v4] hwmon: Add JZ4740 ADC driver

On Sat, 19 Jun 2010 21:32:58 +0200, Lars-Peter Clausen wrote:
> This patch adds support for reading the ADCIN pin of ADC unit on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: [email protected]
>
> --
> Changes since v1
> - Move ADC core access synchronizing from the HWMON driver to a MFD driver. The
> ADC driver now only reads the adcin value.
>
> Changes since v2
> - Add name sysfs attribute
> - Report adcin in value in millivolts
>
> Changes since v3
> - Fix Kconfig entry
> - Add include to completion.h
> - Break overlong lines
> - Use DEVICE_ATTR instead of SENSOR_DEVICE_ATTR for the adcin sys file.
> ---
> drivers/hwmon/Kconfig | 10 ++
> drivers/hwmon/Makefile | 1 +
> drivers/hwmon/jz4740-hwmon.c | 226 ++++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 237 insertions(+), 0 deletions(-)
> create mode 100644 drivers/hwmon/jz4740-hwmon.c
> (...)

Acked-by: Jean Delvare <[email protected]>

--
Jean Delvare

2010-06-20 10:10:26

by Thomas Bogendoerfer

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

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 ]

2010-06-20 13:11:43

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v3] alsa: ASoC: Add JZ4740 codec driver

On Sat, Jun 19, 2010 at 04:49:53PM +0200, Lars-Peter Clausen wrote:

This looks good, just one thing:

> +#ifdef CONFIG_PM_SLEEP
> +
> +static int jz4740_codec_suspend(struct device *dev)
> +{
> + struct jz4740_codec *jz4740_codec = dev_get_drvdata(dev);
> + return jz4740_codec_set_bias_level(&jz4740_codec->codec,
> + SND_SOC_BIAS_OFF);
> +}

You've got these set up on the CODEC platform device rather than the
ASoC CODEC. This means that the suspend and resume will happen out of
sequence with the rest of the ASoC suspend and resume which could result
in poor performance or bugs if the device is suspended while the core
still thinks it's active. For example, ASoC will use DAPM to shut down
the CODEC and it's possible that the CODEC could be suspended (and
generate an audible noise) while an external amplifier is still powered,
worsening the problem.

2010-06-20 14:32:12

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

Thomas Bogendoerfer wrote:
> 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.
>
Hi

Graham Gower started working on JZ4730 support based on this series some
time ago, but had to put a hold on it because he was busy with other
things. You both should probably get in contact.

There are some parts on the JZ4730 which are similar those on the
JZ4740, others are different. So to put support for common parts into a
shared source directory is the right approach. But I think this is
something that can be done when somebody actually steps forward to
implement support for a different JZ47xx SoC. Luckily the code is not
set into stone and can be re-factored or renamed once it is required or
makes sense to do.
Another issue with naming is that while a component might be similar in
JZ4730 and JZ4740 it might be completely different in a different JZ47xx
SoC. So naming a driver 'jz47xx_driver' instead of 'jz4740_driver' wont
work either.

- Lars

2010-06-20 16:34:49

by Thomas Bogendoerfer

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

On Sun, Jun 20, 2010 at 04:31:26PM +0200, Lars-Peter Clausen wrote:
> Another issue with naming is that while a component might be similar in
> JZ4730 and JZ4740 it might be completely different in a different JZ47xx
> SoC. So naming a driver 'jz47xx_driver' instead of 'jz4740_driver' wont
> work either.

so ? call it xx for the common part an exact number for special part.
It might be just jugglin with pieces, but getting it better in the first
run is always a plus.

Thomas.

--
Crap can work. Given enough thrust pigs will fly, but it's not necessary a
good idea. [ RFC1925, 2.3 ]

2010-06-20 16:49:40

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

Thomas Bogendoerfer wrote:
> On Sun, Jun 20, 2010 at 04:31:26PM +0200, Lars-Peter Clausen wrote:
>
>> Another issue with naming is that while a component might be similar in
>> JZ4730 and JZ4740 it might be completely different in a different JZ47xx
>> SoC. So naming a driver 'jz47xx_driver' instead of 'jz4740_driver' wont
>> work either.
>>
>
> so ? call it xx for the common part an exact number for special part.
> It might be just jugglin with pieces, but getting it better in the first
> run is always a plus.
>
> Thomas.
>
As I said, parts might be common between JZ4730 and JZ4740 but be
different to JZ4750 and JZ4760. So JZ47xx wont fit either.
Right now there is no practical use to moving things around, and there
wont be until somebody who can actually test it starts adding support
for a different JZ47XX SoC.

- Lars

2010-06-20 17:01:29

by Thomas Bogendoerfer

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

On Sun, Jun 20, 2010 at 06:49:01PM +0200, Lars-Peter Clausen wrote:
> different to JZ4750 and JZ4760. So JZ47xx wont fit either.
> Right now there is no practical use to moving things around, and there
> wont be until somebody who can actually test it starts adding support
> for a different JZ47XX SoC.

great, I like such attitude:-(

Thomas.

--
Crap can work. Given enough thrust pigs will fly, but it's not necessary a
good idea. [ RFC1925, 2.3 ]

2010-06-20 17:57:53

by Florian Fainelli

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

Hi,

Le Sunday 20 June 2010 19:01:11, Thomas Bogendoerfer a ?crit :
> On Sun, Jun 20, 2010 at 06:49:01PM +0200, Lars-Peter Clausen wrote:
> > different to JZ4750 and JZ4760. So JZ47xx wont fit either.
> > Right now there is no practical use to moving things around, and there
> > wont be until somebody who can actually test it starts adding support
> > for a different JZ47XX SoC.
>
> great, I like such attitude:-(

I have to agree with Thomas here, if your concern is about the naming, then
just have a look at the vendor sources and find similarities for what is worth
being named JZ47XX and what deserves a name which is more specific. Also, it is
much easier to do that factoring job now instead of when there will be 3 or
more flavors of that SoC to be supported.

Take a look at BCM63xx for instance, it is named like that because it supports
4 different versions of the family SoC, even though the internals of the SoC
have been varying a lot, still we support it with a single kernel and what is
really family specific is named accordingly from what is chip-specific.
--
Florian

2010-06-20 18:31:35

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Florian Fainelli wrote:
> Hi,
>
> Le Sunday 20 June 2010 19:01:11, Thomas Bogendoerfer a ?crit :
>> On Sun, Jun 20, 2010 at 06:49:01PM +0200, Lars-Peter Clausen wrote:
>>> different to JZ4750 and JZ4760. So JZ47xx wont fit either.
>>> Right now there is no practical use to moving things around, and there
>>> wont be until somebody who can actually test it starts adding support
>>> for a different JZ47XX SoC.
>> great, I like such attitude:-(
>
> I have to agree with Thomas here, if your concern is about the naming,
then
> just have a look at the vendor sources and find similarities for what
is worth
> being named JZ47XX and what deserves a name which is more specific.
Also, it is
> much easier to do that factoring job now instead of when there will be
3 or
> more flavors of that SoC to be supported.
Well, it's not like somebody who wants to add support for e.g. JZ4730
would start from scratch and add a complete implementation which then
has to be merged with JZ4740. You would start adding it on-top of the
existing JZ4740 platform support and generalize it where necessary.
Renaming is cheap! This is not part of an API thats set into stone...
Seriously, it doesn't make any sense to waste time and try to
generalize now while it is uncertain if there will be support of a
different JZ47xx SoC anytime soon. Furthermore the likelihood of over-
or under-generalizing is pretty high if you do not know exactly what
you want or what you need.
I strongly disagree that it is easier to do the factoring job now. It
will be easier when you actually know what requirements you'll have
based on hard facts instead of having some loose ideas of what might
work and what not.

That said, the platform support has been designed with having the idea
of support for multiple JZ47XX SoCs at some point. So it will mostly
be picking up the components shared between different SoCs and put
them in a shared folder (and maybe do a 's/jz4740/jz47xx/g'). But
right now there is only JZ4740 support...

- - Lars



-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkweXl0ACgkQBX4mSR26RiM7hACfRMhD54TJEdI11AgsaaWRiDaK
xrYAnRR+0VT3CurJm2Xc9DgC9+bcrFfv
=SFR2
-----END PGP SIGNATURE-----

2010-06-21 02:56:39

by Xiangfu Liu

[permalink] [raw]
Subject: Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip

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

2010-06-22 00:21:10

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v4] alsa: ASoC: Add JZ4740 codec driver

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Mark Brown <[email protected]>
Cc: Liam Girdwood <[email protected]>
Cc: [email protected]

---
Changes since v1
- Put Kconfig entry in alphabetic order
- Drop codec_set_fmt since the codec supports only one format
- Rename "Capture Volume" control to "Master Capture Volume"
- Drop unnecessary format checks
- Add suspend/resume
- Cleanup jz4740_codec_set_bias_level

Changes since v2
- Drop codec prefix from the filename. Note that I keeped it for the object
name, because otherwise when build as a module it will clash with the ASoC
JZ4740 platform support.

Changes since v3
- Hook up suspend/resume callbacks to the codec instead of the platform device
---
sound/soc/codecs/Kconfig | 4 +
sound/soc/codecs/Makefile | 2 +
sound/soc/codecs/jz4740.c | 511 +++++++++++++++++++++++++++++++++++++++++++++
sound/soc/codecs/jz4740.h | 20 ++
4 files changed, 537 insertions(+), 0 deletions(-)
create mode 100644 sound/soc/codecs/jz4740.c
create mode 100644 sound/soc/codecs/jz4740.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index c37c844..00d347d 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -24,6 +24,7 @@ config SND_SOC_ALL_CODECS
select SND_SOC_CQ0093VC if MFD_DAVINCI_VOICECODEC
select SND_SOC_CS42L51 if I2C
select SND_SOC_CS4270 if I2C
+ select SND_SOC_JZ4740 if SOC_JZ4740
select SND_SOC_MAX9877 if I2C
select SND_SOC_DA7210 if I2C
select SND_SOC_PCM3008
@@ -142,6 +143,9 @@ config SND_SOC_CS4270_VD33_ERRATA
config SND_SOC_CX20442
tristate

+config SND_SOC_JZ4740_CODEC
+ tristate
+
config SND_SOC_L3
tristate

diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 4a9c205..d8d9eeb 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -57,6 +57,7 @@ snd-soc-wm9705-objs := wm9705.o
snd-soc-wm9712-objs := wm9712.o
snd-soc-wm9713-objs := wm9713.o
snd-soc-wm-hubs-objs := wm_hubs.o
+snd-soc-jz4740-codec-objs := jz4740.o

# Amp
snd-soc-max9877-objs := max9877.o
@@ -80,6 +81,7 @@ obj-$(CONFIG_SND_SOC_CS4270) += snd-soc-cs4270.o
obj-$(CONFIG_SND_SOC_CX20442) += snd-soc-cx20442.o
obj-$(CONFIG_SND_SOC_DA7210) += snd-soc-da7210.o
obj-$(CONFIG_SND_SOC_L3) += snd-soc-l3.o
+obj-$(CONFIG_SND_SOC_JZ4740_CODEC) += snd-soc-jz4740-codec.o
obj-$(CONFIG_SND_SOC_PCM3008) += snd-soc-pcm3008.o
obj-$(CONFIG_SND_SOC_SPDIF) += snd-soc-spdif.o
obj-$(CONFIG_SND_SOC_SSM2602) += snd-soc-ssm2602.o
diff --git a/sound/soc/codecs/jz4740.c b/sound/soc/codecs/jz4740.c
new file mode 100644
index 0000000..66557de
--- /dev/null
+++ b/sound/soc/codecs/jz4740.c
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/delay.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/soc-dapm.h>
+#include <sound/soc.h>
+
+#define JZ4740_REG_CODEC_1 0x0
+#define JZ4740_REG_CODEC_2 0x1
+
+#define JZ4740_CODEC_1_LINE_ENABLE BIT(29)
+#define JZ4740_CODEC_1_MIC_ENABLE BIT(28)
+#define JZ4740_CODEC_1_SW1_ENABLE BIT(27)
+#define JZ4740_CODEC_1_ADC_ENABLE BIT(26)
+#define JZ4740_CODEC_1_SW2_ENABLE BIT(25)
+#define JZ4740_CODEC_1_DAC_ENABLE BIT(24)
+#define JZ4740_CODEC_1_VREF_DISABLE BIT(20)
+#define JZ4740_CODEC_1_VREF_AMP_DISABLE BIT(19)
+#define JZ4740_CODEC_1_VREF_PULLDOWN BIT(18)
+#define JZ4740_CODEC_1_VREF_LOW_CURRENT BIT(17)
+#define JZ4740_CODEC_1_VREF_HIGH_CURRENT BIT(16)
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE BIT(14)
+#define JZ4740_CODEC_1_HEADPHONE_AMP_CHANGE_ANY BIT(13)
+#define JZ4740_CODEC_1_HEADPHONE_CHARGE BIT(12)
+#define JZ4740_CODEC_1_HEADPHONE_PULLDOWN (BIT(11) | BIT(10))
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M BIT(9)
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN BIT(8)
+#define JZ4740_CODEC_1_SUSPEND BIT(1)
+#define JZ4740_CODEC_1_RESET BIT(0)
+
+#define JZ4740_CODEC_1_LINE_ENABLE_OFFSET 29
+#define JZ4740_CODEC_1_MIC_ENABLE_OFFSET 28
+#define JZ4740_CODEC_1_SW1_ENABLE_OFFSET 27
+#define JZ4740_CODEC_1_ADC_ENABLE_OFFSET 26
+#define JZ4740_CODEC_1_SW2_ENABLE_OFFSET 25
+#define JZ4740_CODEC_1_DAC_ENABLE_OFFSET 24
+#define JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET 14
+#define JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET 8
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_MASK 0x1f0000
+#define JZ4740_CODEC_2_SAMPLE_RATE_MASK 0x000f00
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_MASK 0x000030
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_MASK 0x000003
+
+#define JZ4740_CODEC_2_INPUT_VOLUME_OFFSET 16
+#define JZ4740_CODEC_2_SAMPLE_RATE_OFFSET 8
+#define JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET 4
+#define JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET 0
+
+static const uint32_t jz4740_codec_regs[] = {
+ 0x021b2302, 0x00170803,
+};
+
+struct jz4740_codec {
+ void __iomem *base;
+ struct resource *mem;
+
+ uint32_t reg_cache[2];
+ struct snd_soc_codec codec;
+};
+
+static inline struct jz4740_codec *codec_to_jz4740(struct snd_soc_codec *codec)
+{
+ return container_of(codec, struct jz4740_codec, codec);
+}
+
+static unsigned int jz4740_codec_read(struct snd_soc_codec *codec,
+ unsigned int reg)
+{
+ struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+ return readl(jz4740_codec->base + (reg << 2));
+}
+
+static int jz4740_codec_write(struct snd_soc_codec *codec, unsigned int reg,
+ unsigned int val)
+{
+ struct jz4740_codec *jz4740_codec = codec_to_jz4740(codec);
+
+ jz4740_codec->reg_cache[reg] = val;
+ writel(val, jz4740_codec->base + (reg << 2));
+
+ return 0;
+}
+
+static const struct snd_kcontrol_new jz4740_codec_controls[] = {
+ SOC_SINGLE("Master Playback Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_HEADPHONE_VOLUME_OFFSET, 3, 0),
+ SOC_SINGLE("Master Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_INPUT_VOLUME_OFFSET, 31, 0),
+ SOC_SINGLE("Master Playback Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_DISABLE_OFFSET, 1, 1),
+ SOC_SINGLE("Mic Capture Volume", JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_MIC_BOOST_GAIN_OFFSET, 3, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_output_controls[] = {
+ SOC_DAPM_SINGLE("Bypass Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW1_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("DAC Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_kcontrol_new jz4740_codec_input_controls[] = {
+ SOC_DAPM_SINGLE("Line Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_LINE_ENABLE_OFFSET, 1, 0),
+ SOC_DAPM_SINGLE("Mic Capture Switch", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_MIC_ENABLE_OFFSET, 1, 0),
+};
+
+static const struct snd_soc_dapm_widget jz4740_codec_dapm_widgets[] = {
+ SND_SOC_DAPM_ADC("ADC", "Capture", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_ADC_ENABLE_OFFSET, 0),
+ SND_SOC_DAPM_DAC("DAC", "Playback", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_DAC_ENABLE_OFFSET, 0),
+
+ SND_SOC_DAPM_MIXER("Output Mixer", JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_OFFSET, 1,
+ jz4740_codec_output_controls,
+ ARRAY_SIZE(jz4740_codec_output_controls)),
+
+ SND_SOC_DAPM_MIXER_NAMED_CTL("Input Mixer", SND_SOC_NOPM, 0, 0,
+ jz4740_codec_input_controls,
+ ARRAY_SIZE(jz4740_codec_input_controls)),
+ SND_SOC_DAPM_MIXER("Line Input", SND_SOC_NOPM, 0, 0, NULL, 0),
+
+ SND_SOC_DAPM_OUTPUT("LOUT"),
+ SND_SOC_DAPM_OUTPUT("ROUT"),
+
+ SND_SOC_DAPM_INPUT("MIC"),
+ SND_SOC_DAPM_INPUT("LIN"),
+ SND_SOC_DAPM_INPUT("RIN"),
+};
+
+static const struct snd_soc_dapm_route jz4740_codec_dapm_routes[] = {
+ {"Line Input", NULL, "LIN"},
+ {"Line Input", NULL, "RIN"},
+
+ {"Input Mixer", "Line Capture Switch", "Line Input"},
+ {"Input Mixer", "Mic Capture Switch", "MIC"},
+
+ {"ADC", NULL, "Input Mixer"},
+
+ {"Output Mixer", "Bypass Switch", "Input Mixer"},
+ {"Output Mixer", "DAC Switch", "DAC"},
+
+ {"LOUT", NULL, "Output Mixer"},
+ {"ROUT", NULL, "Output Mixer"},
+};
+
+static int jz4740_codec_hw_params(struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+ uint32_t val;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ struct snd_soc_device *socdev = rtd->socdev;
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ switch (params_rate(params)) {
+ case 8000:
+ val = 0;
+ break;
+ case 11025:
+ val = 1;
+ break;
+ case 12000:
+ val = 2;
+ break;
+ case 16000:
+ val = 3;
+ break;
+ case 22050:
+ val = 4;
+ break;
+ case 24000:
+ val = 5;
+ break;
+ case 32000:
+ val = 6;
+ break;
+ case 44100:
+ val = 7;
+ break;
+ case 48000:
+ val = 8;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ val <<= JZ4740_CODEC_2_SAMPLE_RATE_OFFSET;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_2,
+ JZ4740_CODEC_2_SAMPLE_RATE_MASK, val);
+
+ return 0;
+}
+
+static struct snd_soc_dai_ops jz4740_codec_dai_ops = {
+ .hw_params = jz4740_codec_hw_params,
+};
+
+struct snd_soc_dai jz4740_codec_dai = {
+ .name = "jz4740",
+ .playback = {
+ .stream_name = "Playback",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .capture = {
+ .stream_name = "Capture",
+ .channels_min = 2,
+ .channels_max = 2,
+ .rates = SNDRV_PCM_RATE_8000_48000,
+ .formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S8,
+ },
+ .ops = &jz4740_codec_dai_ops,
+ .symmetric_rates = 1,
+};
+EXPORT_SYMBOL_GPL(jz4740_codec_dai);
+
+static void jz4740_codec_wakeup(struct snd_soc_codec *codec)
+{
+ int i;
+ uint32_t *cache = codec->reg_cache;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_RESET, JZ4740_CODEC_1_RESET);
+ udelay(2);
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SUSPEND | JZ4740_CODEC_1_RESET, 0);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_codec_regs); ++i)
+ jz4740_codec_write(codec, i, cache[i]);
+}
+
+static int jz4740_codec_set_bias_level(struct snd_soc_codec *codec,
+ enum snd_soc_bias_level level)
+{
+ unsigned int mask;
+ unsigned int value;
+
+ switch (level) {
+ case SND_SOC_BIAS_ON:
+ break;
+ case SND_SOC_BIAS_PREPARE:
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = 0;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_STANDBY:
+ /* The only way to clear the suspend flag is to reset the codec */
+ if (codec->bias_level == SND_SOC_BIAS_OFF)
+ jz4740_codec_wakeup(codec);
+
+ mask = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+ value = JZ4740_CODEC_1_VREF_DISABLE |
+ JZ4740_CODEC_1_VREF_AMP_DISABLE |
+ JZ4740_CODEC_1_HEADPHONE_POWERDOWN_M;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ case SND_SOC_BIAS_OFF:
+ mask = JZ4740_CODEC_1_SUSPEND;
+ value = JZ4740_CODEC_1_SUSPEND;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1, mask, value);
+ break;
+ default:
+ break;
+ }
+
+ codec->bias_level = level;
+
+ return 0;
+}
+
+static struct snd_soc_codec *jz4740_codec_codec;
+
+static int jz4740_codec_dev_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = jz4740_codec_codec;
+
+ BUG_ON(!codec);
+
+ socdev->card->codec = codec;
+
+ ret = snd_soc_new_pcms(socdev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to create pcms: %d\n", ret);
+ return ret;
+ }
+
+ snd_soc_add_controls(codec, jz4740_codec_controls,
+ ARRAY_SIZE(jz4740_codec_controls));
+
+ snd_soc_dapm_new_controls(codec, jz4740_codec_dapm_widgets,
+ ARRAY_SIZE(jz4740_codec_dapm_widgets));
+
+ snd_soc_dapm_add_routes(codec, jz4740_codec_dapm_routes,
+ ARRAY_SIZE(jz4740_codec_dapm_routes));
+
+ snd_soc_dapm_new_widgets(codec);
+
+ return 0;
+}
+
+static int jz4740_codec_dev_remove(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+
+ snd_soc_free_pcms(socdev);
+ snd_soc_dapm_free(socdev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int jz4740_codec_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_OFF);
+}
+
+static int jz4740_codec_resume(struct platform_device *pdev)
+{
+ struct snd_soc_device *socdev = platform_get_drvdata(pdev);
+ struct snd_soc_codec *codec = socdev->card->codec;
+
+ return jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+}
+
+#else
+#define jz4740_codec_suspend NULL
+#define jz4740_codec_resume NULL
+#endif
+
+struct snd_soc_codec_device soc_codec_dev_jz4740_codec = {
+ .probe = jz4740_codec_dev_probe,
+ .remove = jz4740_codec_dev_remove,
+ .suspend = jz4740_codec_suspend,
+ .resume = jz4740_codec_resume,
+};
+EXPORT_SYMBOL_GPL(soc_codec_dev_jz4740_codec);
+
+static int __devinit jz4740_codec_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_codec *jz4740_codec;
+ struct snd_soc_codec *codec;
+ struct resource *mem;
+
+ jz4740_codec = kzalloc(sizeof(*jz4740_codec), GFP_KERNEL);
+ if (!jz4740_codec)
+ return -ENOMEM;
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to get mmio memory resource\n");
+ ret = -ENOENT;
+ goto err_free_codec;
+ }
+
+ mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ ret = -EBUSY;
+ goto err_free_codec;
+ }
+
+ jz4740_codec->base = ioremap(mem->start, resource_size(mem));
+ if (!jz4740_codec->base) {
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ ret = -EBUSY;
+ goto err_release_mem_region;
+ }
+ jz4740_codec->mem = mem;
+
+ jz4740_codec_dai.dev = &pdev->dev;
+
+ codec = &jz4740_codec->codec;
+
+ codec->dev = &pdev->dev;
+ codec->name = "jz4740";
+ codec->owner = THIS_MODULE;
+
+ codec->read = jz4740_codec_read;
+ codec->write = jz4740_codec_write;
+ codec->set_bias_level = jz4740_codec_set_bias_level;
+ codec->bias_level = SND_SOC_BIAS_OFF;
+
+ codec->dai = &jz4740_codec_dai;
+ codec->num_dai = 1;
+
+ codec->reg_cache = jz4740_codec->reg_cache;
+ codec->reg_cache_size = 2;
+ memcpy(codec->reg_cache, jz4740_codec_regs, sizeof(jz4740_codec_regs));
+
+ mutex_init(&codec->mutex);
+ INIT_LIST_HEAD(&codec->dapm_widgets);
+ INIT_LIST_HEAD(&codec->dapm_paths);
+
+ jz4740_codec_codec = codec;
+
+ snd_soc_update_bits(codec, JZ4740_REG_CODEC_1,
+ JZ4740_CODEC_1_SW2_ENABLE, JZ4740_CODEC_1_SW2_ENABLE);
+
+ platform_set_drvdata(pdev, jz4740_codec);
+
+ ret = snd_soc_register_codec(codec);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec\n");
+ goto err_iounmap;
+ }
+
+ ret = snd_soc_register_dai(&jz4740_codec_dai);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register codec dai\n");
+ goto err_unregister_codec;
+ }
+
+ jz4740_codec_set_bias_level(codec, SND_SOC_BIAS_STANDBY);
+
+ return 0;
+
+err_unregister_codec:
+ snd_soc_unregister_codec(codec);
+err_iounmap:
+ iounmap(jz4740_codec->base);
+err_release_mem_region:
+ release_mem_region(mem->start, resource_size(mem));
+err_free_codec:
+ kfree(jz4740_codec);
+
+ return ret;
+}
+
+static int __devexit jz4740_codec_remove(struct platform_device *pdev)
+{
+ struct jz4740_codec *jz4740_codec = platform_get_drvdata(pdev);
+ struct resource *mem = jz4740_codec->mem;
+
+ snd_soc_unregister_dai(&jz4740_codec_dai);
+ snd_soc_unregister_codec(&jz4740_codec->codec);
+
+ iounmap(jz4740_codec->base);
+ release_mem_region(mem->start, resource_size(mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(jz4740_codec);
+
+ return 0;
+}
+
+static struct platform_driver jz4740_codec_driver = {
+ .probe = jz4740_codec_probe,
+ .remove = __devexit_p(jz4740_codec_remove),
+ .driver = {
+ .name = "jz4740-codec",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_codec_init(void)
+{
+ return platform_driver_register(&jz4740_codec_driver);
+}
+module_init(jz4740_codec_init);
+
+static void __exit jz4740_codec_exit(void)
+{
+ platform_driver_unregister(&jz4740_codec_driver);
+}
+module_exit(jz4740_codec_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC internal codec driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:jz4740-codec");
diff --git a/sound/soc/codecs/jz4740.h b/sound/soc/codecs/jz4740.h
new file mode 100644
index 0000000..b5a0691
--- /dev/null
+++ b/sound/soc/codecs/jz4740.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ */
+
+#ifndef __SND_SOC_CODECS_JZ4740_CODEC_H__
+#define __SND_SOC_CODECS_JZ4740_CODEC_H__
+
+extern struct snd_soc_dai jz4740_codec_dai;
+extern struct snd_soc_codec_device soc_codec_dev_jz4740_codec;
+
+#endif
--
1.5.6.5

2010-06-22 06:05:03

by Alessandro Zummo

[permalink] [raw]
Subject: Re: [PATCH v3] RTC: Add JZ4740 RTC driver

On Sat, 19 Jun 2010 21:29:50 +0200
Lars-Peter Clausen <[email protected]> wrote:

> This patch adds support for the RTC unit on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Alessandro Zummo <[email protected]>
> Cc: Paul Gortmaker <[email protected]>
> Cc: [email protected]

Acked-by: Alessandro Zummo <[email protected]>


--

Best regards,

Alessandro Zummo,
Tower Technologies - Torino, Italy

http://www.towertech.it

2010-06-22 10:12:25

by Liam Girdwood

[permalink] [raw]
Subject: Re: [PATCH v4] alsa: ASoC: Add JZ4740 codec driver

On Tue, 2010-06-22 at 00:46 +0200, Lars-Peter Clausen wrote:
> This patch adds support for the JZ4740 internal codec.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Mark Brown <[email protected]>
> Cc: Liam Girdwood <[email protected]>
> Cc: [email protected]
>
> ---
> Changes since v1
> - Put Kconfig entry in alphabetic order
> - Drop codec_set_fmt since the codec supports only one format
> - Rename "Capture Volume" control to "Master Capture Volume"
> - Drop unnecessary format checks
> - Add suspend/resume
> - Cleanup jz4740_codec_set_bias_level
>
> Changes since v2
> - Drop codec prefix from the filename. Note that I keeped it for the object
> name, because otherwise when build as a module it will clash with the ASoC
> JZ4740 platform support.
>
> Changes since v3
> - Hook up suspend/resume callbacks to the codec instead of the platform device

Acked-by: Liam Girdwood <[email protected]>
--
Freelance Developer, SlimLogic Ltd
ASoC and Voltage Regulator Maintainer.
http://www.slimlogic.co.uk

2010-06-22 23:12:28

by Mark Brown

[permalink] [raw]
Subject: Re: [PATCH v4] alsa: ASoC: Add JZ4740 codec driver

On Tue, Jun 22, 2010 at 12:46:31AM +0200, Lars-Peter Clausen wrote:
> This patch adds support for the JZ4740 internal codec.

Applied all three, thanks.

2010-06-27 01:59:31

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 24/26] power: Add JZ4740 battery driver.

Hi Anton

You already said that v1 of the patch looked good to you. There are some minor
modifications in v2 due to code re-factoring of the ADC driver. If you think this
version is good as well an Acked-By would be nice :)

Thanks
- Lars


Lars-Peter Clausen wrote:
> This patch adds support for the battery voltage measurement part of the JZ4740
> ADC unit.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Anton Vorontsov <[email protected]>
>
> ---
> Changes since v1
> - Fix voltage difference check in jz_update_battery
> - Move get_battery_voltage from the hwmon driver to the battery driver
> - The battery driver is now a cell of the ADC MFD driver
> ---
> drivers/power/Kconfig | 11 +
> drivers/power/Makefile | 1 +
> drivers/power/jz4740-battery.c | 445 ++++++++++++++++++++++++++++++++++
> include/linux/power/jz4740-battery.h | 24 ++
> 4 files changed, 481 insertions(+), 0 deletions(-)
> create mode 100644 drivers/power/jz4740-battery.c
> create mode 100644 include/linux/power/jz4740-battery.h
>
> diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
> index 8e9ba17..1e5506b 100644
> --- a/drivers/power/Kconfig
> +++ b/drivers/power/Kconfig
> @@ -142,4 +142,15 @@ config CHARGER_PCF50633
> help
> Say Y to include support for NXP PCF50633 Main Battery Charger.
>
> +config BATTERY_JZ4740
> + tristate "Ingenic JZ4740 battery"
> + depends on MACH_JZ4740
> + depends on MFD_JZ4740_ADC
> + help
> + Say Y to enable support for the battery on Ingenic JZ4740 based
> + boards.
> +
> + This driver can be build as a module. If so, the module will be
> + called jz4740-battery.
> +
> endif # POWER_SUPPLY
> diff --git a/drivers/power/Makefile b/drivers/power/Makefile
> index 0005080..cf95009 100644
> --- a/drivers/power/Makefile
> +++ b/drivers/power/Makefile
> @@ -34,3 +34,4 @@ obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
> obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
> obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
> obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
> +obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o
> diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c
> new file mode 100644
> index 0000000..20c4b95
> --- /dev/null
> +++ b/drivers/power/jz4740-battery.c
> @@ -0,0 +1,445 @@
> +/*
> + * Battery measurement code for Ingenic JZ SOC.
> + *
> + * Copyright (C) 2009 Jiejing Zhang <[email protected]>
> + * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
> + *
> + * based on tosa_battery.c
> + *
> + * Copyright (C) 2008 Marek Vasut <[email protected]>
> +*
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include <linux/delay.h>
> +#include <linux/gpio.h>
> +#include <linux/mfd/core.h>
> +#include <linux/power_supply.h>
> +
> +#include <linux/power/jz4740-battery.h>
> +#include <linux/jz4740-adc.h>
> +
> +struct jz_battery {
> + struct jz_battery_platform_data *pdata;
> + struct platform_device *pdev;
> +
> + struct resource *mem;
> + void __iomem *base;
> +
> + int irq;
> + int charge_irq;
> +
> + struct mfd_cell *cell;
> +
> + int status;
> + long voltage;
> +
> + struct completion read_completion;
> +
> + struct power_supply battery;
> + struct delayed_work work;
> +};
> +
> +static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy)
> +{
> + return container_of(psy, struct jz_battery, battery);
> +}
> +
> +static irqreturn_t jz_battery_irq_handler(int irq, void *devid)
> +{
> + struct jz_battery *battery = devid;
> +
> + complete(&battery->read_completion);
> + return IRQ_HANDLED;
> +}
> +
> +static long jz_battery_read_voltage(struct jz_battery *battery)
> +{
> + unsigned long t;
> + unsigned long val;
> + long voltage;
> +
> + INIT_COMPLETION(battery->read_completion);
> +
> + enable_irq(battery->irq);
> + battery->cell->enable(battery->pdev);
> +
> + t = wait_for_completion_interruptible_timeout(&battery->read_completion,
> + HZ);
> +
> + if (t > 0) {
> + val = readw(battery->base) & 0xfff;
> +
> + if (battery->pdata->info.voltage_max_design <= 2500000)
> + val = (val * 78125UL) >> 7UL;
> + else
> + val = ((val * 924375UL) >> 9UL) + 33000;
> + voltage = (long)val;
> + } else {
> + voltage = t ? t : -ETIMEDOUT;
> + }
> +
> + battery->cell->disable(battery->pdev);
> + disable_irq(battery->irq);
> +
> + return voltage;
> +}
> +
> +static int jz_battery_get_capacity(struct power_supply *psy)
> +{
> + struct jz_battery *jz_battery = psy_to_jz_battery(psy);
> + struct power_supply_info *info = &jz_battery->pdata->info;
> + long voltage;
> + int ret;
> + int voltage_span;
> +
> + voltage = jz_battery_read_voltage(jz_battery);
> +
> + if (voltage < 0)
> + return voltage;
> +
> + voltage_span = info->voltage_max_design - info->voltage_min_design;
> + ret = ((voltage - info->voltage_min_design) * 100) / voltage_span;
> +
> + if (ret > 100)
> + ret = 100;
> + else if (ret < 0)
> + ret = 0;
> +
> + return ret;
> +}
> +
> +static int jz_battery_get_property(struct power_supply *psy,
> + enum power_supply_property psp, union power_supply_propval *val)
> +{
> + struct jz_battery *jz_battery = psy_to_jz_battery(psy);
> + struct power_supply_info *info = &jz_battery->pdata->info;
> + long voltage;
> +
> + switch (psp) {
> + case POWER_SUPPLY_PROP_STATUS:
> + val->intval = jz_battery->status;
> + break;
> + case POWER_SUPPLY_PROP_TECHNOLOGY:
> + val->intval = jz_battery->pdata->info.technology;
> + break;
> + case POWER_SUPPLY_PROP_HEALTH:
> + voltage = jz_battery_read_voltage(jz_battery);
> + if (voltage < info->voltage_min_design)
> + val->intval = POWER_SUPPLY_HEALTH_DEAD;
> + else
> + val->intval = POWER_SUPPLY_HEALTH_GOOD;
> + break;
> + case POWER_SUPPLY_PROP_CAPACITY:
> + val->intval = jz_battery_get_capacity(psy);
> + break;
> + case POWER_SUPPLY_PROP_VOLTAGE_NOW:
> + val->intval = jz_battery_read_voltage(jz_battery);
> + if (val->intval < 0)
> + return val->intval;
> + break;
> + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
> + val->intval = info->voltage_max_design;
> + break;
> + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
> + val->intval = info->voltage_min_design;
> + break;
> + case POWER_SUPPLY_PROP_PRESENT:
> + val->intval = 1;
> + break;
> + default:
> + return -EINVAL;
> + }
> + return 0;
> +}
> +
> +static void jz_battery_external_power_changed(struct power_supply *psy)
> +{
> + struct jz_battery *jz_battery = psy_to_jz_battery(psy);
> +
> + cancel_delayed_work(&jz_battery->work);
> + schedule_delayed_work(&jz_battery->work, 0);
> +}
> +
> +static irqreturn_t jz_battery_charge_irq(int irq, void *data)
> +{
> + struct jz_battery *jz_battery = data;
> +
> + cancel_delayed_work(&jz_battery->work);
> + schedule_delayed_work(&jz_battery->work, 0);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void jz_battery_update(struct jz_battery *jz_battery)
> +{
> + int status;
> + long voltage;
> + bool has_changed = false;
> + int is_charging;
> +
> + if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
> + is_charging = gpio_get_value(jz_battery->pdata->gpio_charge);
> + is_charging ^= jz_battery->pdata->gpio_charge_active_low;
> + if (is_charging)
> + status = POWER_SUPPLY_STATUS_CHARGING;
> + else
> + status = POWER_SUPPLY_STATUS_NOT_CHARGING;
> +
> + if (status != jz_battery->status) {
> + jz_battery->status = status;
> + has_changed = true;
> + }
> + }
> +
> + voltage = jz_battery_read_voltage(jz_battery);
> + if (abs(voltage - jz_battery->voltage) < 50000) {
> + jz_battery->voltage = voltage;
> + has_changed = true;
> + }
> +
> + if (has_changed)
> + power_supply_changed(&jz_battery->battery);
> +}
> +
> +static enum power_supply_property jz_battery_properties[] = {
> + POWER_SUPPLY_PROP_STATUS,
> + POWER_SUPPLY_PROP_TECHNOLOGY,
> + POWER_SUPPLY_PROP_HEALTH,
> + POWER_SUPPLY_PROP_CAPACITY,
> + POWER_SUPPLY_PROP_VOLTAGE_NOW,
> + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
> + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
> + POWER_SUPPLY_PROP_PRESENT,
> +};
> +
> +static void jz_battery_work(struct work_struct *work)
> +{
> + /* Too small interval will increase system workload */
> + const int interval = HZ * 30;
> + struct jz_battery *jz_battery = container_of(work, struct jz_battery,
> + work.work);
> +
> + jz_battery_update(jz_battery);
> + schedule_delayed_work(&jz_battery->work, interval);
> +}
> +
> +static int __devinit jz_battery_probe(struct platform_device *pdev)
> +{
> + int ret = 0;
> + struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data;
> + struct jz_battery *jz_battery;
> + struct power_supply *battery;
> +
> + jz_battery = kzalloc(sizeof(*jz_battery), GFP_KERNEL);
> + if (!jz_battery) {
> + dev_err(&pdev->dev, "Failed to allocate driver structure\n");
> + return -ENOMEM;
> + }
> +
> + jz_battery->cell = pdev->dev.platform_data;
> +
> + jz_battery->irq = platform_get_irq(pdev, 0);
> + if (jz_battery->irq < 0) {
> + ret = jz_battery->irq;
> + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
> + goto err_free;
> + }
> +
> + jz_battery->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!jz_battery->mem) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
> + goto err_free;
> + }
> +
> + jz_battery->mem = request_mem_region(jz_battery->mem->start,
> + resource_size(jz_battery->mem), pdev->name);
> + if (!jz_battery->mem) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + goto err_free;
> + }
> +
> + jz_battery->base = ioremap_nocache(jz_battery->mem->start,
> + resource_size(jz_battery->mem));
> + if (!jz_battery->base) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> + goto err_release_mem_region;
> + }
> +
> + battery = &jz_battery->battery;
> + battery->name = pdata->info.name;
> + battery->type = POWER_SUPPLY_TYPE_BATTERY;
> + battery->properties = jz_battery_properties;
> + battery->num_properties = ARRAY_SIZE(jz_battery_properties);
> + battery->get_property = jz_battery_get_property;
> + battery->external_power_changed = jz_battery_external_power_changed;
> + battery->use_for_apm = 1;
> +
> + jz_battery->pdata = pdata;
> + jz_battery->pdev = pdev;
> +
> + init_completion(&jz_battery->read_completion);
> +
> + INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work);
> +
> + ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name,
> + jz_battery);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request irq %d\n", ret);
> + goto err_iounmap;
> + }
> + disable_irq(jz_battery->irq);
> +
> + if (gpio_is_valid(pdata->gpio_charge)) {
> + ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev));
> + if (ret) {
> + dev_err(&pdev->dev, "charger state gpio request failed.\n");
> + goto err_free_irq;
> + }
> + ret = gpio_direction_input(pdata->gpio_charge);
> + if (ret) {
> + dev_err(&pdev->dev, "charger state gpio set direction failed.\n");
> + goto err_free_gpio;
> + }
> +
> + jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge);
> +
> + if (jz_battery->charge_irq >= 0) {
> + ret = request_irq(jz_battery->charge_irq,
> + jz_battery_charge_irq,
> + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
> + dev_name(&pdev->dev), jz_battery);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret);
> + goto err_free_gpio;
> + }
> + }
> + } else {
> + jz_battery->charge_irq = -1;
> + }
> +
> + if (jz_battery->pdata->info.voltage_max_design <= 2500000)
> + jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB,
> + JZ_ADC_CONFIG_BAT_MB);
> + else
> + jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0);
> +
> + ret = power_supply_register(&pdev->dev, &jz_battery->battery);
> + if (ret) {
> + dev_err(&pdev->dev, "power supply battery register failed.\n");
> + goto err_free_charge_irq;
> + }
> +
> + platform_set_drvdata(pdev, jz_battery);
> + schedule_delayed_work(&jz_battery->work, 0);
> +
> + return 0;
> +
> +err_free_charge_irq:
> + if (jz_battery->charge_irq >= 0)
> + free_irq(jz_battery->charge_irq, jz_battery);
> +err_free_gpio:
> + if (gpio_is_valid(pdata->gpio_charge))
> + gpio_free(jz_battery->pdata->gpio_charge);
> +err_free_irq:
> + free_irq(jz_battery->irq, jz_battery);
> +err_iounmap:
> + platform_set_drvdata(pdev, NULL);
> + iounmap(jz_battery->base);
> +err_release_mem_region:
> + release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
> +err_free:
> + kfree(jz_battery);
> + return ret;
> +}
> +
> +static int __devexit jz_battery_remove(struct platform_device *pdev)
> +{
> + struct jz_battery *jz_battery = platform_get_drvdata(pdev);
> +
> + cancel_delayed_work_sync(&jz_battery->work);
> +
> + if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
> + if (jz_battery->charge_irq >= 0)
> + free_irq(jz_battery->charge_irq, jz_battery);
> + gpio_free(jz_battery->pdata->gpio_charge);
> + }
> +
> + power_supply_unregister(&jz_battery->battery);
> +
> + free_irq(jz_battery->irq, jz_battery);
> +
> + iounmap(jz_battery->base);
> + release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem));
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +static int jz_battery_suspend(struct device *dev)
> +{
> + struct jz_battery *jz_battery = dev_get_drvdata(dev);
> +
> + cancel_delayed_work_sync(&jz_battery->work);
> + jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN;
> +
> + return 0;
> +}
> +
> +static int jz_battery_resume(struct device *dev)
> +{
> + struct jz_battery *jz_battery = dev_get_drvdata(dev);
> +
> + schedule_delayed_work(&jz_battery->work, 0);
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops jz_battery_pm_ops = {
> + .suspend = jz_battery_suspend,
> + .resume = jz_battery_resume,
> +};
> +
> +#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops)
> +#else
> +#define JZ_BATTERY_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver jz_battery_driver = {
> + .probe = jz_battery_probe,
> + .remove = __devexit_p(jz_battery_remove),
> + .driver = {
> + .name = "jz4740-battery",
> + .owner = THIS_MODULE,
> + .pm = JZ_BATTERY_PM_OPS,
> + },
> +};
> +
> +static int __init jz_battery_init(void)
> +{
> + return platform_driver_register(&jz_battery_driver);
> +}
> +module_init(jz_battery_init);
> +
> +static void __exit jz_battery_exit(void)
> +{
> + platform_driver_unregister(&jz_battery_driver);
> +}
> +module_exit(jz_battery_exit);
> +
> +MODULE_ALIAS("platform:jz4740-battery");
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_DESCRIPTION("JZ4740 SoC battery driver");
> diff --git a/include/linux/power/jz4740-battery.h b/include/linux/power/jz4740-battery.h
> new file mode 100644
> index 0000000..19c9610
> --- /dev/null
> +++ b/include/linux/power/jz4740-battery.h
> @@ -0,0 +1,24 @@
> +/*
> + * Copyright (C) 2009, Jiejing Zhang <[email protected]>
> + *
> + * 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.
> + *
> + */
> +
> +#ifndef __JZ4740_BATTERY_H
> +#define __JZ4740_BATTERY_H
> +
> +struct jz_battery_platform_data {
> + struct power_supply_info info;
> + int gpio_charge; /* GPIO port of Charger state */
> + int gpio_charge_active_low;
> +};
> +
> +#endif

2010-06-28 01:21:19

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] MMC: Add JZ4740 mmc driver

This patch adds support for the mmc controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Matt Fleming <[email protected]>
Cc: [email protected]

---
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 <[email protected]>
+ * 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 <[email protected]>");
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

2010-06-28 01:24:13

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3 10/26] MIPS: JZ4740: Add PWM support

This patch adds support for the PWM part of the timer unit on a JZ4740 SoC.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v2
- Fix handling if the pwm clock is not available
---
arch/mips/jz4740/pwm.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 177 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/pwm.c

diff --git a/arch/mips/jz4740/pwm.c b/arch/mips/jz4740/pwm.c
new file mode 100644
index 0000000..0bb5b66
--- /dev/null
+++ b/arch/mips/jz4740/pwm.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform PWM support
+ *
+ * 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/kernel.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-jz4740/gpio.h>
+#include "timer.h"
+
+static struct clk *jz4740_pwm_clk;
+
+DEFINE_MUTEX(jz4740_pwm_mutex);
+
+struct pwm_device {
+ unsigned int id;
+ unsigned int gpio;
+ bool used;
+};
+
+static struct pwm_device jz4740_pwm_list[] = {
+ { 2, JZ_GPIO_PWM2, false },
+ { 3, JZ_GPIO_PWM3, false },
+ { 4, JZ_GPIO_PWM4, false },
+ { 5, JZ_GPIO_PWM5, false },
+ { 6, JZ_GPIO_PWM6, false },
+ { 7, JZ_GPIO_PWM7, false },
+};
+
+struct pwm_device *pwm_request(int id, const char *label)
+{
+ int ret = 0;
+ struct pwm_device *pwm;
+
+ if (id < 2 || id > 7 || !jz4740_pwm_clk)
+ return ERR_PTR(-ENOENT);
+
+ mutex_lock(&jz4740_pwm_mutex);
+
+ pwm = &jz4740_pwm_list[id - 2];
+ if (pwm->used)
+ ret = -EBUSY;
+ else
+ pwm->used = true;
+
+ mutex_unlock(&jz4740_pwm_mutex);
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = gpio_request(pwm->gpio, label);
+
+ if (ret) {
+ printk(KERN_ERR "Failed to request pwm gpio: %d\n", ret);
+ pwm->used = false;
+ return ERR_PTR(ret);
+ }
+
+ jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_PWM);
+
+ jz4740_timer_start(id);
+
+ return pwm;
+}
+
+void pwm_free(struct pwm_device *pwm)
+{
+ pwm_disable(pwm);
+ jz4740_timer_set_ctrl(pwm->id, 0);
+
+ jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_NONE);
+ gpio_free(pwm->gpio);
+
+ jz4740_timer_stop(pwm->id);
+
+ pwm->used = false;
+}
+
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ unsigned long long tmp;
+ unsigned long period, duty;
+ unsigned int prescaler = 0;
+ unsigned int id = pwm->id;
+ uint16_t ctrl;
+ bool is_enabled;
+
+ if (duty_ns < 0 || duty_ns > period_ns)
+ return -EINVAL;
+
+ tmp = (unsigned long long)clk_get_rate(jz4740_pwm_clk) * period_ns;
+ do_div(tmp, 1000000000);
+ period = tmp;
+
+ while (period > 0xffff && prescaler < 6) {
+ period >>= 2;
+ ++prescaler;
+ }
+
+ if (prescaler == 6)
+ return -EINVAL;
+
+ tmp = (unsigned long long)period * duty_ns;
+ do_div(tmp, period_ns);
+ duty = period - tmp;
+
+ if (duty >= period)
+ duty = period - 1;
+
+ is_enabled = jz4740_timer_is_enabled(id);
+ if (is_enabled)
+ pwm_disable(pwm);
+
+ jz4740_timer_set_count(id, 0);
+ jz4740_timer_set_duty(id, duty);
+ jz4740_timer_set_period(id, period);
+
+ ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT |
+ JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN;
+
+ jz4740_timer_set_ctrl(id, ctrl);
+
+ if (is_enabled)
+ pwm_enable(pwm);
+
+ return 0;
+}
+
+int pwm_enable(struct pwm_device *pwm)
+{
+ uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+ ctrl |= JZ_TIMER_CTRL_PWM_ENABLE;
+ jz4740_timer_set_ctrl(pwm->id, ctrl);
+ jz4740_timer_enable(pwm->id);
+
+ return 0;
+}
+
+void pwm_disable(struct pwm_device *pwm)
+{
+ uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+ ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
+ jz4740_timer_disable(pwm->id);
+ jz4740_timer_set_ctrl(pwm->id, ctrl);
+}
+
+static int __init jz4740_pwm_init(void)
+{
+ int ret = 0;
+
+ jz4740_pwm_clk = clk_get(NULL, "ext");
+
+ if (IS_ERR(jz4740_pwm_clk)) {
+ ret = PTR_ERR(jz4740_pwm_clk);
+ jz4740_pwm_clk = NULL;
+ }
+
+ return ret;
+}
+arch_initcall(jz4740_pwm_init);
--
1.5.6.5

2010-06-28 01:24:52

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3 03/26] MIPS: JZ4740: Add clock API support.

This patch adds support for managing the clocks found on JZ4740 SoC through
the Linux clock API.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---

Changes since v2
- Fix setting of inital parents for spi and i2s clocks
- In clk_set_parent(), preserve clock enabled/disabled state.
- Set correct get_rate callback for the pll clock
- Fix pll frequency rate formula
---
arch/mips/include/asm/mach-jz4740/clock.h | 28 +
arch/mips/jz4740/clock-debugfs.c | 109 ++++
arch/mips/jz4740/clock.c | 924 +++++++++++++++++++++++++++++
arch/mips/jz4740/clock.h | 76 +++
4 files changed, 1137 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h
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

diff --git a/arch/mips/include/asm/mach-jz4740/clock.h b/arch/mips/include/asm/mach-jz4740/clock.h
new file mode 100644
index 0000000..1b7408d
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/clock.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_JZ4740_CLOCK_H__
+#define __ASM_JZ4740_CLOCK_H__
+
+enum jz4740_wait_mode {
+ JZ4740_WAIT_MODE_IDLE,
+ JZ4740_WAIT_MODE_SLEEP,
+};
+
+void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode);
+
+void jz4740_clock_udc_enable_auto_suspend(void);
+void jz4740_clock_udc_disable_auto_suspend(void);
+
+#endif
diff --git a/arch/mips/jz4740/clock-debugfs.c b/arch/mips/jz4740/clock-debugfs.c
new file mode 100644
index 0000000..330a0f2
--- /dev/null
+++ b/arch/mips/jz4740/clock-debugfs.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support debugfs entries
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+
+#include <asm/mach-jz4740/clock.h>
+#include "clock.h"
+
+static struct dentry *jz4740_clock_debugfs;
+
+static int jz4740_clock_debugfs_show_enabled(void *data, uint64_t *value)
+{
+ struct clk *clk = data;
+ *value = clk_is_enabled(clk);
+
+ return 0;
+}
+
+static int jz4740_clock_debugfs_set_enabled(void *data, uint64_t value)
+{
+ struct clk *clk = data;
+
+ if (value)
+ return clk_enable(clk);
+ else
+ clk_disable(clk);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(jz4740_clock_debugfs_ops_enabled,
+ jz4740_clock_debugfs_show_enabled,
+ jz4740_clock_debugfs_set_enabled,
+ "%llu\n");
+
+static int jz4740_clock_debugfs_show_rate(void *data, uint64_t *value)
+{
+ struct clk *clk = data;
+ *value = clk_get_rate(clk);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(jz4740_clock_debugfs_ops_rate,
+ jz4740_clock_debugfs_show_rate,
+ NULL,
+ "%llu\n");
+
+void jz4740_clock_debugfs_add_clk(struct clk *clk)
+{
+ if (!jz4740_clock_debugfs)
+ return;
+
+ clk->debugfs_entry = debugfs_create_dir(clk->name, jz4740_clock_debugfs);
+ debugfs_create_file("rate", S_IWUGO | S_IRUGO, clk->debugfs_entry, clk,
+ &jz4740_clock_debugfs_ops_rate);
+ debugfs_create_file("enabled", S_IRUGO, clk->debugfs_entry, clk,
+ &jz4740_clock_debugfs_ops_enabled);
+
+ if (clk->parent) {
+ char parent_path[100];
+ snprintf(parent_path, 100, "../%s", clk->parent->name);
+ clk->debugfs_parent_entry = debugfs_create_symlink("parent",
+ clk->debugfs_entry,
+ parent_path);
+ }
+}
+
+/* TODO: Locking */
+void jz4740_clock_debugfs_update_parent(struct clk *clk)
+{
+ if (clk->debugfs_parent_entry)
+ debugfs_remove(clk->debugfs_parent_entry);
+
+ if (clk->parent) {
+ char parent_path[100];
+ snprintf(parent_path, 100, "../%s", clk->parent->name);
+ clk->debugfs_parent_entry = debugfs_create_symlink("parent",
+ clk->debugfs_entry,
+ parent_path);
+ } else {
+ clk->debugfs_parent_entry = NULL;
+ }
+}
+
+void jz4740_clock_debugfs_init(void)
+{
+ jz4740_clock_debugfs = debugfs_create_dir("jz4740-clock", NULL);
+ if (IS_ERR(jz4740_clock_debugfs))
+ jz4740_clock_debugfs = NULL;
+}
diff --git a/arch/mips/jz4740/clock.c b/arch/mips/jz4740/clock.c
new file mode 100644
index 0000000..bdfacc3
--- /dev/null
+++ b/arch/mips/jz4740/clock.c
@@ -0,0 +1,924 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support
+ *
+ * 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/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/err.h>
+
+#include <asm/mach-jz4740/clock.h>
+#include <asm/mach-jz4740/base.h>
+
+#include "clock.h"
+
+#define JZ_REG_CLOCK_CTRL 0x00
+#define JZ_REG_CLOCK_LOW_POWER 0x04
+#define JZ_REG_CLOCK_PLL 0x10
+#define JZ_REG_CLOCK_GATE 0x20
+#define JZ_REG_CLOCK_SLEEP_CTRL 0x24
+#define JZ_REG_CLOCK_I2S 0x60
+#define JZ_REG_CLOCK_LCD 0x64
+#define JZ_REG_CLOCK_MMC 0x68
+#define JZ_REG_CLOCK_UHC 0x6C
+#define JZ_REG_CLOCK_SPI 0x74
+
+#define JZ_CLOCK_CTRL_I2S_SRC_PLL BIT(31)
+#define JZ_CLOCK_CTRL_KO_ENABLE BIT(30)
+#define JZ_CLOCK_CTRL_UDC_SRC_PLL BIT(29)
+#define JZ_CLOCK_CTRL_UDIV_MASK 0x1f800000
+#define JZ_CLOCK_CTRL_CHANGE_ENABLE BIT(22)
+#define JZ_CLOCK_CTRL_PLL_HALF BIT(21)
+#define JZ_CLOCK_CTRL_LDIV_MASK 0x001f0000
+#define JZ_CLOCK_CTRL_UDIV_OFFSET 23
+#define JZ_CLOCK_CTRL_LDIV_OFFSET 16
+#define JZ_CLOCK_CTRL_MDIV_OFFSET 12
+#define JZ_CLOCK_CTRL_PDIV_OFFSET 8
+#define JZ_CLOCK_CTRL_HDIV_OFFSET 4
+#define JZ_CLOCK_CTRL_CDIV_OFFSET 0
+
+#define JZ_CLOCK_GATE_UART0 BIT(0)
+#define JZ_CLOCK_GATE_TCU BIT(1)
+#define JZ_CLOCK_GATE_RTC BIT(2)
+#define JZ_CLOCK_GATE_I2C BIT(3)
+#define JZ_CLOCK_GATE_SPI BIT(4)
+#define JZ_CLOCK_GATE_AIC BIT(5)
+#define JZ_CLOCK_GATE_I2S BIT(6)
+#define JZ_CLOCK_GATE_MMC BIT(7)
+#define JZ_CLOCK_GATE_ADC BIT(8)
+#define JZ_CLOCK_GATE_CIM BIT(9)
+#define JZ_CLOCK_GATE_LCD BIT(10)
+#define JZ_CLOCK_GATE_UDC BIT(11)
+#define JZ_CLOCK_GATE_DMAC BIT(12)
+#define JZ_CLOCK_GATE_IPU BIT(13)
+#define JZ_CLOCK_GATE_UHC BIT(14)
+#define JZ_CLOCK_GATE_UART1 BIT(15)
+
+#define JZ_CLOCK_I2S_DIV_MASK 0x01ff
+
+#define JZ_CLOCK_LCD_DIV_MASK 0x01ff
+
+#define JZ_CLOCK_MMC_DIV_MASK 0x001f
+
+#define JZ_CLOCK_UHC_DIV_MASK 0x000f
+
+#define JZ_CLOCK_SPI_SRC_PLL BIT(31)
+#define JZ_CLOCK_SPI_DIV_MASK 0x000f
+
+#define JZ_CLOCK_PLL_M_MASK 0x01ff
+#define JZ_CLOCK_PLL_N_MASK 0x001f
+#define JZ_CLOCK_PLL_OD_MASK 0x0003
+#define JZ_CLOCK_PLL_STABLE BIT(10)
+#define JZ_CLOCK_PLL_BYPASS BIT(9)
+#define JZ_CLOCK_PLL_ENABLED BIT(8)
+#define JZ_CLOCK_PLL_STABLIZE_MASK 0x000f
+#define JZ_CLOCK_PLL_M_OFFSET 23
+#define JZ_CLOCK_PLL_N_OFFSET 18
+#define JZ_CLOCK_PLL_OD_OFFSET 16
+
+#define JZ_CLOCK_LOW_POWER_MODE_DOZE BIT(2)
+#define JZ_CLOCK_LOW_POWER_MODE_SLEEP BIT(0)
+
+#define JZ_CLOCK_SLEEP_CTRL_SUSPEND_UHC BIT(7)
+#define JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC BIT(6)
+
+static void __iomem *jz_clock_base;
+static spinlock_t jz_clock_lock;
+static LIST_HEAD(jz_clocks);
+
+struct main_clk {
+ struct clk clk;
+ uint32_t div_offset;
+};
+
+struct divided_clk {
+ struct clk clk;
+ uint32_t reg;
+ uint32_t mask;
+};
+
+struct static_clk {
+ struct clk clk;
+ unsigned long rate;
+};
+
+static uint32_t jz_clk_reg_read(int reg)
+{
+ return readl(jz_clock_base + reg);
+}
+
+static void jz_clk_reg_write_mask(int reg, uint32_t val, uint32_t mask)
+{
+ uint32_t val2;
+
+ spin_lock(&jz_clock_lock);
+ val2 = readl(jz_clock_base + reg);
+ val2 &= ~mask;
+ val2 |= val;
+ writel(val2, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static void jz_clk_reg_set_bits(int reg, uint32_t mask)
+{
+ uint32_t val;
+
+ spin_lock(&jz_clock_lock);
+ val = readl(jz_clock_base + reg);
+ val |= mask;
+ writel(val, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static void jz_clk_reg_clear_bits(int reg, uint32_t mask)
+{
+ uint32_t val;
+
+ spin_lock(&jz_clock_lock);
+ val = readl(jz_clock_base + reg);
+ val &= ~mask;
+ writel(val, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static int jz_clk_enable_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return -EINVAL;
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE, clk->gate_bit);
+ return 0;
+}
+
+static int jz_clk_disable_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return -EINVAL;
+
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE, clk->gate_bit);
+ return 0;
+}
+
+static int jz_clk_is_enabled_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return 1;
+
+ return !(jz_clk_reg_read(JZ_REG_CLOCK_GATE) & clk->gate_bit);
+}
+
+static unsigned long jz_clk_static_get_rate(struct clk *clk)
+{
+ return ((struct static_clk *)clk)->rate;
+}
+
+static int jz_clk_ko_enable(struct clk *clk)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_KO_ENABLE);
+ return 0;
+}
+
+static int jz_clk_ko_disable(struct clk *clk)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_KO_ENABLE);
+ return 0;
+}
+
+static int jz_clk_ko_is_enabled(struct clk *clk)
+{
+ return !!(jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_KO_ENABLE);
+}
+
+static const int pllno[] = {1, 2, 2, 4};
+
+static unsigned long jz_clk_pll_get_rate(struct clk *clk)
+{
+ uint32_t val;
+ int m;
+ int n;
+ int od;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_PLL);
+
+ if (val & JZ_CLOCK_PLL_BYPASS)
+ return clk_get_rate(clk->parent);
+
+ m = ((val >> 23) & 0x1ff) + 2;
+ n = ((val >> 18) & 0x1f) + 2;
+ od = (val >> 16) & 0x3;
+
+ return ((clk_get_rate(clk->parent) / n) * m) / pllno[od];
+}
+
+static unsigned long jz_clk_pll_half_get_rate(struct clk *clk)
+{
+ uint32_t reg;
+
+ reg = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+ if (reg & JZ_CLOCK_CTRL_PLL_HALF)
+ return jz_clk_pll_get_rate(clk->parent);
+ return jz_clk_pll_get_rate(clk->parent) >> 1;
+}
+
+static const int jz_clk_main_divs[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32};
+
+static unsigned long jz_clk_main_round_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long parent_rate = jz_clk_pll_get_rate(clk->parent);
+ int div;
+
+ div = parent_rate / rate;
+ if (div > 32)
+ return parent_rate / 32;
+ else if (div < 1)
+ return parent_rate;
+
+ div &= (0x3 << (ffs(div) - 1));
+
+ return parent_rate / div;
+}
+
+static unsigned long jz_clk_main_get_rate(struct clk *clk)
+{
+ struct main_clk *mclk = (struct main_clk *)clk;
+ uint32_t div;
+
+ div = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+
+ div >>= mclk->div_offset;
+ div &= 0xf;
+
+ if (div >= ARRAY_SIZE(jz_clk_main_divs))
+ div = ARRAY_SIZE(jz_clk_main_divs) - 1;
+
+ return jz_clk_pll_get_rate(clk->parent) / jz_clk_main_divs[div];
+}
+
+static int jz_clk_main_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct main_clk *mclk = (struct main_clk *)clk;
+ int i;
+ int div;
+ unsigned long parent_rate = jz_clk_pll_get_rate(clk->parent);
+
+ rate = jz_clk_main_round_rate(clk, rate);
+
+ div = parent_rate / rate;
+
+ i = (ffs(div) - 1) << 1;
+ if (i > 0 && !(div & BIT(i-1)))
+ i -= 1;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, i << mclk->div_offset,
+ 0xf << mclk->div_offset);
+
+ return 0;
+}
+
+static struct clk_ops jz_clk_static_ops = {
+ .get_rate = jz_clk_static_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct static_clk jz_clk_ext = {
+ .clk = {
+ .name = "ext",
+ .gate_bit = JZ4740_CLK_NOT_GATED,
+ .ops = &jz_clk_static_ops,
+ },
+};
+
+static struct clk_ops jz_clk_pll_ops = {
+ .get_rate = jz_clk_pll_get_rate,
+};
+
+static struct clk jz_clk_pll = {
+ .name = "pll",
+ .parent = &jz_clk_ext.clk,
+ .ops = &jz_clk_pll_ops,
+};
+
+static struct clk_ops jz_clk_pll_half_ops = {
+ .get_rate = jz_clk_pll_half_get_rate,
+};
+
+static struct clk jz_clk_pll_half = {
+ .name = "pll half",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_pll_half_ops,
+};
+
+static const struct clk_ops jz_clk_main_ops = {
+ .get_rate = jz_clk_main_get_rate,
+ .set_rate = jz_clk_main_set_rate,
+ .round_rate = jz_clk_main_round_rate,
+};
+
+static struct main_clk jz_clk_cpu = {
+ .clk = {
+ .name = "cclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_CDIV_OFFSET,
+};
+
+static struct main_clk jz_clk_memory = {
+ .clk = {
+ .name = "mclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_MDIV_OFFSET,
+};
+
+static struct main_clk jz_clk_high_speed_peripheral = {
+ .clk = {
+ .name = "hclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_HDIV_OFFSET,
+};
+
+
+static struct main_clk jz_clk_low_speed_peripheral = {
+ .clk = {
+ .name = "pclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_PDIV_OFFSET,
+};
+
+static const struct clk_ops jz_clk_ko_ops = {
+ .enable = jz_clk_ko_enable,
+ .disable = jz_clk_ko_disable,
+ .is_enabled = jz_clk_ko_is_enabled,
+};
+
+static struct clk jz_clk_ko = {
+ .name = "cko",
+ .parent = &jz_clk_memory.clk,
+ .ops = &jz_clk_ko_ops,
+};
+
+static int jz_clk_spi_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll)
+ jz_clk_reg_set_bits(JZ_CLOCK_SPI_SRC_PLL, JZ_REG_CLOCK_SPI);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_CLOCK_SPI_SRC_PLL, JZ_REG_CLOCK_SPI);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_i2s_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll_half)
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_I2S_SRC_PLL);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_I2S_SRC_PLL);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_udc_enable(struct clk *clk)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_SLEEP_CTRL,
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+
+ return 0;
+}
+
+static int jz_clk_udc_disable(struct clk *clk)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_SLEEP_CTRL,
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+
+ return 0;
+}
+
+static int jz_clk_udc_is_enabled(struct clk *clk)
+{
+ return !!(jz_clk_reg_read(JZ_REG_CLOCK_SLEEP_CTRL) &
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+}
+
+static int jz_clk_udc_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll_half)
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_UDC_SRC_PLL);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_UDC_SRC_PLL);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_udc_set_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return -EINVAL;
+
+ div = clk_get_rate(clk->parent) / rate - 1;
+
+ if (div < 0)
+ div = 0;
+ else if (div > 63)
+ div = 63;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, div << JZ_CLOCK_CTRL_UDIV_OFFSET,
+ JZ_CLOCK_CTRL_UDIV_MASK);
+ return 0;
+}
+
+static unsigned long jz_clk_udc_get_rate(struct clk *clk)
+{
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return clk_get_rate(clk->parent);
+
+ div = (jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_UDIV_MASK);
+ div >>= JZ_CLOCK_CTRL_UDIV_OFFSET;
+ div += 1;
+
+ return clk_get_rate(clk->parent) / div;
+}
+
+static unsigned long jz_clk_divided_get_rate(struct clk *clk)
+{
+ struct divided_clk *dclk = (struct divided_clk *)clk;
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return clk_get_rate(clk->parent);
+
+ div = (jz_clk_reg_read(dclk->reg) & dclk->mask) + 1;
+
+ return clk_get_rate(clk->parent) / div;
+}
+
+static int jz_clk_divided_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct divided_clk *dclk = (struct divided_clk *)clk;
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return -EINVAL;
+
+ div = clk_get_rate(clk->parent) / rate - 1;
+
+ if (div < 0)
+ div = 0;
+ else if (div > dclk->mask)
+ div = dclk->mask;
+
+ jz_clk_reg_write_mask(dclk->reg, div, dclk->mask);
+
+ return 0;
+}
+
+static unsigned long jz_clk_ldclk_round_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+ unsigned long parent_rate = jz_clk_pll_half_get_rate(clk->parent);
+
+ if (rate > 150000000)
+ return 150000000;
+
+ div = parent_rate / rate;
+ if (div < 1)
+ div = 1;
+ else if (div > 32)
+ div = 32;
+
+ return parent_rate / div;
+}
+
+static int jz_clk_ldclk_set_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+
+ if (rate > 150000000)
+ return -EINVAL;
+
+ div = jz_clk_pll_half_get_rate(clk->parent) / rate - 1;
+ if (div < 0)
+ div = 0;
+ else if (div > 31)
+ div = 31;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, div << JZ_CLOCK_CTRL_LDIV_OFFSET,
+ JZ_CLOCK_CTRL_LDIV_MASK);
+
+ return 0;
+}
+
+static unsigned long jz_clk_ldclk_get_rate(struct clk *clk)
+{
+ int div;
+
+ div = jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_LDIV_MASK;
+ div >>= JZ_CLOCK_CTRL_LDIV_OFFSET;
+
+ return jz_clk_pll_half_get_rate(clk->parent) / (div + 1);
+}
+
+static const struct clk_ops jz_clk_ops_ld = {
+ .set_rate = jz_clk_ldclk_set_rate,
+ .get_rate = jz_clk_ldclk_get_rate,
+ .round_rate = jz_clk_ldclk_round_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct clk jz_clk_ld = {
+ .name = "lcd",
+ .gate_bit = JZ_CLOCK_GATE_LCD,
+ .parent = &jz_clk_pll_half,
+ .ops = &jz_clk_ops_ld,
+};
+
+static const struct clk_ops jz_clk_i2s_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+ .set_parent = jz_clk_i2s_set_parent,
+};
+
+static const struct clk_ops jz_clk_spi_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+ .set_parent = jz_clk_spi_set_parent,
+};
+
+static const struct clk_ops jz_clk_divided_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct divided_clk jz4740_clock_divided_clks[] = {
+ [0] = {
+ .clk = {
+ .name = "i2s",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_I2S,
+ .ops = &jz_clk_i2s_ops,
+ },
+ .reg = JZ_REG_CLOCK_I2S,
+ .mask = JZ_CLOCK_I2S_DIV_MASK,
+ },
+ [1] = {
+ .clk = {
+ .name = "spi",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_SPI,
+ .ops = &jz_clk_spi_ops,
+ },
+ .reg = JZ_REG_CLOCK_SPI,
+ .mask = JZ_CLOCK_SPI_DIV_MASK,
+ },
+ [2] = {
+ .clk = {
+ .name = "lcd_pclk",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ4740_CLK_NOT_GATED,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_LCD,
+ .mask = JZ_CLOCK_LCD_DIV_MASK,
+ },
+ [3] = {
+ .clk = {
+ .name = "mmc",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ_CLOCK_GATE_MMC,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_MMC,
+ .mask = JZ_CLOCK_MMC_DIV_MASK,
+ },
+ [4] = {
+ .clk = {
+ .name = "uhc",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ_CLOCK_GATE_UHC,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_UHC,
+ .mask = JZ_CLOCK_UHC_DIV_MASK,
+ },
+};
+
+static const struct clk_ops jz_clk_udc_ops = {
+ .set_parent = jz_clk_udc_set_parent,
+ .set_rate = jz_clk_udc_set_rate,
+ .get_rate = jz_clk_udc_get_rate,
+ .enable = jz_clk_udc_enable,
+ .disable = jz_clk_udc_disable,
+ .is_enabled = jz_clk_udc_is_enabled,
+};
+
+static const struct clk_ops jz_clk_simple_ops = {
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct clk jz4740_clock_simple_clks[] = {
+ [0] = {
+ .name = "udc",
+ .parent = &jz_clk_ext.clk,
+ .ops = &jz_clk_udc_ops,
+ },
+ [1] = {
+ .name = "uart0",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART0,
+ .ops = &jz_clk_simple_ops,
+ },
+ [2] = {
+ .name = "uart1",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART1,
+ .ops = &jz_clk_simple_ops,
+ },
+ [3] = {
+ .name = "dma",
+ .parent = &jz_clk_high_speed_peripheral.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART0,
+ .ops = &jz_clk_simple_ops,
+ },
+ [4] = {
+ .name = "ipu",
+ .parent = &jz_clk_high_speed_peripheral.clk,
+ .gate_bit = JZ_CLOCK_GATE_IPU,
+ .ops = &jz_clk_simple_ops,
+ },
+ [5] = {
+ .name = "adc",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_ADC,
+ .ops = &jz_clk_simple_ops,
+ },
+ [6] = {
+ .name = "i2c",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_I2C,
+ .ops = &jz_clk_simple_ops,
+ },
+ [7] = {
+ .name = "aic",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_AIC,
+ .ops = &jz_clk_simple_ops,
+ },
+};
+
+static struct static_clk jz_clk_rtc = {
+ .clk = {
+ .name = "rtc",
+ .gate_bit = JZ_CLOCK_GATE_RTC,
+ .ops = &jz_clk_static_ops,
+ },
+ .rate = 32768,
+};
+
+int clk_enable(struct clk *clk)
+{
+ if (!clk->ops->enable)
+ return -EINVAL;
+
+ return clk->ops->enable(clk);
+}
+EXPORT_SYMBOL_GPL(clk_enable);
+
+void clk_disable(struct clk *clk)
+{
+ if (clk->ops->disable)
+ clk->ops->disable(clk);
+}
+EXPORT_SYMBOL_GPL(clk_disable);
+
+int clk_is_enabled(struct clk *clk)
+{
+ if (clk->ops->is_enabled)
+ return clk->ops->is_enabled(clk);
+
+ return 1;
+}
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ if (clk->ops->get_rate)
+ return clk->ops->get_rate(clk);
+ if (clk->parent)
+ return clk_get_rate(clk->parent);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(clk_get_rate);
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ if (!clk->ops->set_rate)
+ return -EINVAL;
+ return clk->ops->set_rate(clk, rate);
+}
+EXPORT_SYMBOL_GPL(clk_set_rate);
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ if (clk->ops->round_rate)
+ return clk->ops->round_rate(clk, rate);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(clk_round_rate);
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ int ret;
+ int enabled;
+
+ if (!clk->ops->set_parent)
+ return -EINVAL;
+
+ enabled = clk_is_enabled(clk);
+ if (enabled)
+ clk_disable(clk);
+ ret = clk->ops->set_parent(clk, parent);
+ if (enabled)
+ clk_enable(clk);
+
+ jz4740_clock_debugfs_update_parent(clk);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(clk_set_parent);
+
+struct clk *clk_get(struct device *dev, const char *name)
+{
+ struct clk *clk;
+
+ list_for_each_entry(clk, &jz_clocks, list) {
+ if (strcmp(clk->name, name) == 0)
+ return clk;
+ }
+ return ERR_PTR(-ENOENT);
+}
+EXPORT_SYMBOL_GPL(clk_get);
+
+void clk_put(struct clk *clk)
+{
+}
+EXPORT_SYMBOL_GPL(clk_put);
+
+static inline void clk_add(struct clk *clk)
+{
+ list_add_tail(&clk->list, &jz_clocks);
+
+ jz4740_clock_debugfs_add_clk(clk);
+}
+
+static void clk_register_clks(void)
+{
+ size_t i;
+
+ clk_add(&jz_clk_ext.clk);
+ clk_add(&jz_clk_pll);
+ clk_add(&jz_clk_pll_half);
+ clk_add(&jz_clk_cpu.clk);
+ clk_add(&jz_clk_high_speed_peripheral.clk);
+ clk_add(&jz_clk_low_speed_peripheral.clk);
+ clk_add(&jz_clk_ko);
+ clk_add(&jz_clk_ld);
+ clk_add(&jz_clk_rtc.clk);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_clock_divided_clks); ++i)
+ clk_add(&jz4740_clock_divided_clks[i].clk);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_clock_simple_clks); ++i)
+ clk_add(&jz4740_clock_simple_clks[i]);
+}
+
+void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode)
+{
+ switch (mode) {
+ case JZ4740_WAIT_MODE_IDLE:
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_LOW_POWER, JZ_CLOCK_LOW_POWER_MODE_SLEEP);
+ break;
+ case JZ4740_WAIT_MODE_SLEEP:
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_LOW_POWER, JZ_CLOCK_LOW_POWER_MODE_SLEEP);
+ break;
+ }
+}
+
+void jz4740_clock_udc_disable_auto_suspend(void)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE, JZ_CLOCK_GATE_UDC);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_disable_auto_suspend);
+
+void jz4740_clock_udc_enable_auto_suspend(void)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE, JZ_CLOCK_GATE_UDC);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_enable_auto_suspend);
+
+void jz4740_clock_suspend(void)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE,
+ JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0);
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_PLL, JZ_CLOCK_PLL_ENABLED);
+}
+
+void jz4740_clock_resume(void)
+{
+ uint32_t pll;
+
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_PLL, JZ_CLOCK_PLL_ENABLED);
+
+ do {
+ pll = jz_clk_reg_read(JZ_REG_CLOCK_PLL);
+ } while (!(pll & JZ_CLOCK_PLL_STABLE));
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE,
+ JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0);
+}
+
+static int jz4740_clock_init(void)
+{
+ uint32_t val;
+
+ jz_clock_base = ioremap(JZ4740_CPM_BASE_ADDR, 0x100);
+ if (!jz_clock_base)
+ return -EBUSY;
+
+ spin_lock_init(&jz_clock_lock);
+
+ jz_clk_ext.rate = jz4740_clock_bdata.ext_rate;
+ jz_clk_rtc.rate = jz4740_clock_bdata.rtc_rate;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_SPI);
+
+ if (val & JZ_CLOCK_SPI_SRC_PLL)
+ jz4740_clock_divided_clks[1].clk.parent = &jz_clk_pll_half;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+
+ if (val & JZ_CLOCK_CTRL_I2S_SRC_PLL)
+ jz4740_clock_divided_clks[0].clk.parent = &jz_clk_pll_half;
+
+ if (val & JZ_CLOCK_CTRL_UDC_SRC_PLL)
+ jz4740_clock_simple_clks[0].parent = &jz_clk_pll_half;
+
+ jz4740_clock_debugfs_init();
+
+ clk_register_clks();
+
+ return 0;
+}
+subsys_initcall(jz4740_clock_init);
diff --git a/arch/mips/jz4740/clock.h b/arch/mips/jz4740/clock.h
new file mode 100644
index 0000000..5d07499
--- /dev/null
+++ b/arch/mips/jz4740/clock.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MIPS_JZ4740_CLOCK_H__
+#define __MIPS_JZ4740_CLOCK_H__
+
+#include <linux/list.h>
+
+struct jz4740_clock_board_data {
+ unsigned long ext_rate;
+ unsigned long rtc_rate;
+};
+
+extern struct jz4740_clock_board_data jz4740_clock_bdata;
+
+void jz4740_clock_suspend(void);
+void jz4740_clock_resume(void);
+
+struct clk;
+
+struct clk_ops {
+ unsigned long (*get_rate)(struct clk *clk);
+ unsigned long (*round_rate)(struct clk *clk, unsigned long rate);
+ int (*set_rate)(struct clk *clk, unsigned long rate);
+ int (*enable)(struct clk *clk);
+ int (*disable)(struct clk *clk);
+ int (*is_enabled)(struct clk *clk);
+
+ int (*set_parent)(struct clk *clk, struct clk *parent);
+
+};
+
+struct clk {
+ const char *name;
+ struct clk *parent;
+
+ uint32_t gate_bit;
+
+ const struct clk_ops *ops;
+
+ struct list_head list;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs_entry;
+ struct dentry *debugfs_parent_entry;
+#endif
+
+};
+
+#define JZ4740_CLK_NOT_GATED ((uint32_t)-1)
+
+int clk_is_enabled(struct clk *clk);
+
+#ifdef CONFIG_DEBUG_FS
+void jz4740_clock_debugfs_init(void);
+void jz4740_clock_debugfs_add_clk(struct clk *clk);
+void jz4740_clock_debugfs_update_parent(struct clk *clk);
+#else
+static inline void jz4740_clock_debugfs_init(void) {};
+static inline void jz4740_clock_debugfs_add_clk(struct clk *clk) {};
+static inline void jz4740_clock_debugfs_update_parent(struct clk *clk) {};
+#endif
+
+#endif
--
1.5.6.5

2010-06-28 11:44:01

by Anton Vorontsov

[permalink] [raw]
Subject: Re: [PATCH v2 24/26] power: Add JZ4740 battery driver.

On Sun, Jun 27, 2010 at 03:58:52AM +0200, Lars-Peter Clausen wrote:
> Hi Anton
>
> You already said that v1 of the patch looked good to you. There are some minor
> modifications in v2 due to code re-factoring of the ADC driver. If you think this
> version is good as well an Acked-By would be nice :)

Acked-by: Anton Vorontsov <[email protected]>

Thanks,

> Lars-Peter Clausen wrote:
> > This patch adds support for the battery voltage measurement part of the JZ4740
> > ADC unit.
> >
> > Signed-off-by: Lars-Peter Clausen <[email protected]>
> > Cc: Anton Vorontsov <[email protected]>
> >
> > ---
> > Changes since v1
> > - Fix voltage difference check in jz_update_battery
> > - Move get_battery_voltage from the hwmon driver to the battery driver
> > - The battery driver is now a cell of the ADC MFD driver
> > ---

--
Anton Vorontsov
email: [email protected]
irc://irc.freenode.net/bd2

2010-06-29 20:17:05

by Matt Fleming

[permalink] [raw]
Subject: Re: [PATCH v3] MMC: Add JZ4740 mmc driver

On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <[email protected]> wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Matt Fleming <[email protected]>
> Cc: [email protected]
>
> ---
> 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 <[email protected]>

Are you planning on maintaining this driver? If so, it'd be a good idea
to update MAINTAINERS.

2010-06-30 20:55:48

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH v3] MMC: Add JZ4740 mmc driver

On Mon, 28 Jun 2010 03:20:41 +0200
Lars-Peter Clausen <[email protected]> wrote:

> This patch adds support for the mmc controller on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Matt Fleming <[email protected]>
> Cc: [email protected]
>
> ...
>
> +#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?

2010-07-01 15:46:34

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v3] MMC: Add JZ4740 mmc driver

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi

Andrew Morton wrote:
> On Mon, 28 Jun 2010 03:20:41 +0200
> Lars-Peter Clausen <[email protected]> wrote:
>
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>> Cc: Andrew Morton <[email protected]>
>> Cc: Matt Fleming <[email protected]>
>> Cc: [email protected]
>>
>> ...
>>
>> +#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-----

2010-07-01 15:47:58

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v3] MMC: Add JZ4740 mmc driver

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Matt Fleming wrote:
> On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <[email protected]> wrote:
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>> Cc: Andrew Morton <[email protected]>
>> Cc: Matt Fleming <[email protected]>
>> Cc: [email protected]
>>
>> ---
>> 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 <[email protected]>
>
> 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-----

2010-07-04 22:28:08

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver

Hi Andrew

v2 of this patch has been around for two weeks without any comments. I've fixed all
the issues you had with v1. If you this version is ok, an Acked-By would be nice :)

Thanks
- Lars

Lars-Peter Clausen wrote:
> This patch adds support for the LCD controller on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: [email protected]
>
> ---
> Changes since v1
> - Use __packed instead of __attribute__((packed))
> - Make jzfb_fix const
> - Only set mode in set_par if it has changed
> ---
> drivers/video/Kconfig | 9 +
> drivers/video/Makefile | 1 +
> drivers/video/jz4740_fb.c | 817 +++++++++++++++++++++++++++++++++++++++++++++
> include/linux/jz4740_fb.h | 58 ++++
> 4 files changed, 885 insertions(+), 0 deletions(-)
> create mode 100644 drivers/video/jz4740_fb.c
> create mode 100644 include/linux/jz4740_fb.h
>
> diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
> index a9f9e5e..eae4c8a 100644
> --- a/drivers/video/Kconfig
> +++ b/drivers/video/Kconfig
> @@ -2237,6 +2237,15 @@ config FB_BROADSHEET
> and could also have been called by other names when coupled with
> a bridge adapter.
>
> +config FB_JZ4740
> + tristate "JZ4740 LCD framebuffer support"
> + depends on FB
> + select FB_SYS_FILLRECT
> + select FB_SYS_COPYAREA
> + select FB_SYS_IMAGEBLIT
> + help
> + Framebuffer support for the JZ4740 SoC.
> +
> source "drivers/video/omap/Kconfig"
> source "drivers/video/omap2/Kconfig"
>
> diff --git a/drivers/video/Makefile b/drivers/video/Makefile
> index 3c3bf86..fd2df57 100644
> --- a/drivers/video/Makefile
> +++ b/drivers/video/Makefile
> @@ -132,6 +132,7 @@ obj-$(CONFIG_FB_CARMINE) += carminefb.o
> obj-$(CONFIG_FB_MB862XX) += mb862xx/
> obj-$(CONFIG_FB_MSM) += msm/
> obj-$(CONFIG_FB_NUC900) += nuc900fb.o
> +obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o
>
> # Platform or fallback drivers go here
> obj-$(CONFIG_FB_UVESA) += uvesafb.o
> diff --git a/drivers/video/jz4740_fb.c b/drivers/video/jz4740_fb.c
> new file mode 100644
> index 0000000..8d03181
> --- /dev/null
> +++ b/drivers/video/jz4740_fb.c
> @@ -0,0 +1,817 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC LCD framebuffer 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/kernel.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/platform_device.h>
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +
> +#include <linux/console.h>
> +#include <linux/fb.h>
> +
> +#include <linux/dma-mapping.h>
> +
> +#include <linux/jz4740_fb.h>
> +#include <asm/mach-jz4740/gpio.h>
> +
> +#define JZ_REG_LCD_CFG 0x00
> +#define JZ_REG_LCD_VSYNC 0x04
> +#define JZ_REG_LCD_HSYNC 0x08
> +#define JZ_REG_LCD_VAT 0x0C
> +#define JZ_REG_LCD_DAH 0x10
> +#define JZ_REG_LCD_DAV 0x14
> +#define JZ_REG_LCD_PS 0x18
> +#define JZ_REG_LCD_CLS 0x1C
> +#define JZ_REG_LCD_SPL 0x20
> +#define JZ_REG_LCD_REV 0x24
> +#define JZ_REG_LCD_CTRL 0x30
> +#define JZ_REG_LCD_STATE 0x34
> +#define JZ_REG_LCD_IID 0x38
> +#define JZ_REG_LCD_DA0 0x40
> +#define JZ_REG_LCD_SA0 0x44
> +#define JZ_REG_LCD_FID0 0x48
> +#define JZ_REG_LCD_CMD0 0x4C
> +#define JZ_REG_LCD_DA1 0x50
> +#define JZ_REG_LCD_SA1 0x54
> +#define JZ_REG_LCD_FID1 0x58
> +#define JZ_REG_LCD_CMD1 0x5C
> +
> +#define JZ_LCD_CFG_SLCD BIT(31)
> +#define JZ_LCD_CFG_PS_DISABLE BIT(23)
> +#define JZ_LCD_CFG_CLS_DISABLE BIT(22)
> +#define JZ_LCD_CFG_SPL_DISABLE BIT(21)
> +#define JZ_LCD_CFG_REV_DISABLE BIT(20)
> +#define JZ_LCD_CFG_HSYNCM BIT(19)
> +#define JZ_LCD_CFG_PCLKM BIT(18)
> +#define JZ_LCD_CFG_INV BIT(17)
> +#define JZ_LCD_CFG_SYNC_DIR BIT(16)
> +#define JZ_LCD_CFG_PS_POLARITY BIT(15)
> +#define JZ_LCD_CFG_CLS_POLARITY BIT(14)
> +#define JZ_LCD_CFG_SPL_POLARITY BIT(13)
> +#define JZ_LCD_CFG_REV_POLARITY BIT(12)
> +#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(11)
> +#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10)
> +#define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9)
> +#define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8)
> +#define JZ_LCD_CFG_18_BIT BIT(7)
> +#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4))
> +#define JZ_LCD_CFG_MODE_MASK 0xf
> +
> +#define JZ_LCD_CTRL_BURST_4 (0x0 << 28)
> +#define JZ_LCD_CTRL_BURST_8 (0x1 << 28)
> +#define JZ_LCD_CTRL_BURST_16 (0x2 << 28)
> +#define JZ_LCD_CTRL_RGB555 BIT(27)
> +#define JZ_LCD_CTRL_OFUP BIT(26)
> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24)
> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_4 (0x1 << 24)
> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_2 (0x2 << 24)
> +#define JZ_LCD_CTRL_PDD_MASK (0xff << 16)
> +#define JZ_LCD_CTRL_EOF_IRQ BIT(13)
> +#define JZ_LCD_CTRL_SOF_IRQ BIT(12)
> +#define JZ_LCD_CTRL_OFU_IRQ BIT(11)
> +#define JZ_LCD_CTRL_IFU0_IRQ BIT(10)
> +#define JZ_LCD_CTRL_IFU1_IRQ BIT(9)
> +#define JZ_LCD_CTRL_DD_IRQ BIT(8)
> +#define JZ_LCD_CTRL_QDD_IRQ BIT(7)
> +#define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6)
> +#define JZ_LCD_CTRL_LSB_FISRT BIT(5)
> +#define JZ_LCD_CTRL_DISABLE BIT(4)
> +#define JZ_LCD_CTRL_ENABLE BIT(3)
> +#define JZ_LCD_CTRL_BPP_1 0x0
> +#define JZ_LCD_CTRL_BPP_2 0x1
> +#define JZ_LCD_CTRL_BPP_4 0x2
> +#define JZ_LCD_CTRL_BPP_8 0x3
> +#define JZ_LCD_CTRL_BPP_15_16 0x4
> +#define JZ_LCD_CTRL_BPP_18_24 0x5
> +
> +#define JZ_LCD_CMD_SOF_IRQ BIT(15)
> +#define JZ_LCD_CMD_EOF_IRQ BIT(16)
> +#define JZ_LCD_CMD_ENABLE_PAL BIT(12)
> +
> +#define JZ_LCD_SYNC_MASK 0x3ff
> +
> +#define JZ_LCD_STATE_DISABLED BIT(0)
> +
> +struct jzfb_framedesc {
> + uint32_t next;
> + uint32_t addr;
> + uint32_t id;
> + uint32_t cmd;
> +} __packed;
> +
> +struct jzfb {
> + struct fb_info *fb;
> + struct platform_device *pdev;
> + void __iomem *base;
> + struct resource *mem;
> + struct jz4740_fb_platform_data *pdata;
> +
> + size_t vidmem_size;
> + void *vidmem;
> + dma_addr_t vidmem_phys;
> + struct jzfb_framedesc *framedesc;
> + dma_addr_t framedesc_phys;
> +
> + struct clk *ldclk;
> + struct clk *lpclk;
> +
> + unsigned is_enabled:1;
> + struct mutex lock;
> +
> + uint32_t pseudo_palette[16];
> +};
> +
> +static const struct fb_fix_screeninfo jzfb_fix __devinitdata = {
> + .id = "JZ4740 FB",
> + .type = FB_TYPE_PACKED_PIXELS,
> + .visual = FB_VISUAL_TRUECOLOR,
> + .xpanstep = 0,
> + .ypanstep = 0,
> + .ywrapstep = 0,
> + .accel = FB_ACCEL_NONE,
> +};
> +
> +static const struct jz_gpio_bulk_request jz_lcd_ctrl_pins[] = {
> + JZ_GPIO_BULK_PIN(LCD_PCLK),
> + JZ_GPIO_BULK_PIN(LCD_HSYNC),
> + JZ_GPIO_BULK_PIN(LCD_VSYNC),
> + JZ_GPIO_BULK_PIN(LCD_DE),
> + JZ_GPIO_BULK_PIN(LCD_PS),
> + JZ_GPIO_BULK_PIN(LCD_REV),
> +};
> +
> +static const struct jz_gpio_bulk_request jz_lcd_data_pins[] = {
> + JZ_GPIO_BULK_PIN(LCD_DATA0),
> + JZ_GPIO_BULK_PIN(LCD_DATA1),
> + JZ_GPIO_BULK_PIN(LCD_DATA2),
> + JZ_GPIO_BULK_PIN(LCD_DATA3),
> + JZ_GPIO_BULK_PIN(LCD_DATA4),
> + JZ_GPIO_BULK_PIN(LCD_DATA5),
> + JZ_GPIO_BULK_PIN(LCD_DATA6),
> + JZ_GPIO_BULK_PIN(LCD_DATA7),
> + JZ_GPIO_BULK_PIN(LCD_DATA8),
> + JZ_GPIO_BULK_PIN(LCD_DATA9),
> + JZ_GPIO_BULK_PIN(LCD_DATA10),
> + JZ_GPIO_BULK_PIN(LCD_DATA11),
> + JZ_GPIO_BULK_PIN(LCD_DATA12),
> + JZ_GPIO_BULK_PIN(LCD_DATA13),
> + JZ_GPIO_BULK_PIN(LCD_DATA14),
> + JZ_GPIO_BULK_PIN(LCD_DATA15),
> + JZ_GPIO_BULK_PIN(LCD_DATA16),
> + JZ_GPIO_BULK_PIN(LCD_DATA17),
> +};
> +
> +static unsigned int jzfb_num_ctrl_pins(struct jzfb *jzfb)
> +{
> + unsigned int num;
> +
> + switch (jzfb->pdata->lcd_type) {
> + case JZ_LCD_TYPE_GENERIC_16_BIT:
> + num = 4;
> + break;
> + case JZ_LCD_TYPE_GENERIC_18_BIT:
> + num = 4;
> + break;
> + case JZ_LCD_TYPE_8BIT_SERIAL:
> + num = 3;
> + break;
> + default:
> + num = 0;
> + break;
> + }
> + return num;
> +}
> +
> +static unsigned int jzfb_num_data_pins(struct jzfb *jzfb)
> +{
> + unsigned int num;
> +
> + switch (jzfb->pdata->lcd_type) {
> + case JZ_LCD_TYPE_GENERIC_16_BIT:
> + num = 16;
> + break;
> + case JZ_LCD_TYPE_GENERIC_18_BIT:
> + num = 18;
> + break;
> + case JZ_LCD_TYPE_8BIT_SERIAL:
> + num = 8;
> + break;
> + default:
> + num = 0;
> + break;
> + }
> + return num;
> +}
> +
> +/* Based on CNVT_TOHW macro from skeletonfb.c */
> +static inline uint32_t jzfb_convert_color_to_hw(unsigned val,
> + struct fb_bitfield *bf)
> +{
> + return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset;
> +}
> +
> +static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green,
> + unsigned blue, unsigned transp, struct fb_info *fb)
> +{
> + uint32_t color;
> +
> + if (regno >= 16)
> + return -EINVAL;
> +
> + color = jzfb_convert_color_to_hw(red, &fb->var.red);
> + color |= jzfb_convert_color_to_hw(green, &fb->var.green);
> + color |= jzfb_convert_color_to_hw(blue, &fb->var.blue);
> + color |= jzfb_convert_color_to_hw(transp, &fb->var.transp);
> +
> + ((uint32_t *)(fb->pseudo_palette))[regno] = color;
> +
> + return 0;
> +}
> +
> +static int jzfb_get_controller_bpp(struct jzfb *jzfb)
> +{
> + switch (jzfb->pdata->bpp) {
> + case 18:
> + case 24:
> + return 32;
> + case 15:
> + return 16;
> + default:
> + return jzfb->pdata->bpp;
> + }
> +}
> +
> +static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb,
> + struct fb_var_screeninfo *var)
> +{
> + size_t i;
> + struct fb_videomode *mode = jzfb->pdata->modes;
> +
> + for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) {
> + if (mode->xres == var->xres && mode->yres == var->yres)
> + return mode;
> + }
> +
> + return NULL;
> +}
> +
> +static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
> +{
> + struct jzfb *jzfb = fb->par;
> + struct fb_videomode *mode;
> +
> + if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) &&
> + var->bits_per_pixel != jzfb->pdata->bpp)
> + return -EINVAL;
> +
> + mode = jzfb_get_mode(jzfb, var);
> + if (mode == NULL)
> + return -EINVAL;
> +
> + fb_videomode_to_var(var, mode);
> +
> + switch (jzfb->pdata->bpp) {
> + case 8:
> + break;
> + case 15:
> + var->red.offset = 10;
> + var->red.length = 5;
> + var->green.offset = 6;
> + var->green.length = 5;
> + var->blue.offset = 0;
> + var->blue.length = 5;
> + break;
> + case 16:
> + var->red.offset = 11;
> + var->red.length = 5;
> + var->green.offset = 5;
> + var->green.length = 6;
> + var->blue.offset = 0;
> + var->blue.length = 5;
> + break;
> + case 18:
> + var->red.offset = 16;
> + var->red.length = 6;
> + var->green.offset = 8;
> + var->green.length = 6;
> + var->blue.offset = 0;
> + var->blue.length = 6;
> + var->bits_per_pixel = 32;
> + break;
> + case 32:
> + case 24:
> + var->transp.offset = 24;
> + var->transp.length = 8;
> + var->red.offset = 16;
> + var->red.length = 8;
> + var->green.offset = 8;
> + var->green.length = 8;
> + var->blue.offset = 0;
> + var->blue.length = 8;
> + var->bits_per_pixel = 32;
> + break;
> + default:
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static int jzfb_set_par(struct fb_info *info)
> +{
> + struct jzfb *jzfb = info->par;
> + struct fb_var_screeninfo *var = &info->var;
> + struct fb_videomode *mode;
> + uint16_t hds, vds;
> + uint16_t hde, vde;
> + uint16_t ht, vt;
> + uint32_t ctrl;
> + uint32_t cfg;
> + unsigned long rate;
> +
> + mode = jzfb_get_mode(jzfb, var);
> + if (mode == NULL)
> + return -EINVAL;
> +
> + if (mode == info->mode)
> + return 0;
> +
> + info->mode = mode;
> +
> + hds = mode->hsync_len + mode->left_margin;
> + hde = hds + mode->xres;
> + ht = hde + mode->right_margin;
> +
> + vds = mode->vsync_len + mode->upper_margin;
> + vde = vds + mode->yres;
> + vt = vde + mode->lower_margin;
> +
> + ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16;
> +
> + switch (jzfb->pdata->bpp) {
> + case 1:
> + ctrl |= JZ_LCD_CTRL_BPP_1;
> + break;
> + case 2:
> + ctrl |= JZ_LCD_CTRL_BPP_2;
> + break;
> + case 4:
> + ctrl |= JZ_LCD_CTRL_BPP_4;
> + break;
> + case 8:
> + ctrl |= JZ_LCD_CTRL_BPP_8;
> + break;
> + case 15:
> + ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */
> + case 16:
> + ctrl |= JZ_LCD_CTRL_BPP_15_16;
> + break;
> + case 18:
> + case 24:
> + case 32:
> + ctrl |= JZ_LCD_CTRL_BPP_18_24;
> + break;
> + default:
> + break;
> + }
> +
> + cfg = JZ_LCD_CFG_PS_DISABLE | JZ_LCD_CFG_CLS_DISABLE |
> + JZ_LCD_CFG_SPL_DISABLE | JZ_LCD_CFG_REV_DISABLE;
> +
> + if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
> + cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
> +
> + if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
> + cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW;
> +
> + if (jzfb->pdata->pixclk_falling_edge)
> + cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
> +
> + if (jzfb->pdata->date_enable_active_low)
> + cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW;
> +
> + if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT)
> + cfg |= JZ_LCD_CFG_18_BIT;
> +
> + cfg |= jzfb->pdata->lcd_type & 0xf;
> +
> + if (mode->pixclock) {
> + rate = PICOS2KHZ(mode->pixclock) * 1000;
> + mode->refresh = rate / vt / ht;
> + } else {
> + if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL)
> + rate = mode->refresh * (vt + 2 * mode->xres) * ht;
> + else
> + rate = mode->refresh * vt * ht;
> +
> + mode->pixclock = KHZ2PICOS(rate / 1000);
> + }
> +
> + mutex_lock(&jzfb->lock);
> + if (!jzfb->is_enabled)
> + clk_enable(jzfb->ldclk);
> + else
> + ctrl |= JZ_LCD_CTRL_ENABLE;
> +
> + writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC);
> + writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC);
> +
> + writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT);
> +
> + writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH);
> + writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV);
> +
> + writel(cfg, jzfb->base + JZ_REG_LCD_CFG);
> +
> + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
> +
> + if (!jzfb->is_enabled)
> + clk_disable(jzfb->ldclk);
> +
> + mutex_unlock(&jzfb->lock);
> +
> + clk_set_rate(jzfb->lpclk, rate);
> + clk_set_rate(jzfb->ldclk, rate * 3);
> +
> + return 0;
> +}
> +
> +static void jzfb_enable(struct jzfb *jzfb)
> +{
> + uint32_t ctrl;
> +
> + clk_enable(jzfb->ldclk);
> +
> + jz_gpio_bulk_resume(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
> + jz_gpio_bulk_resume(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
> +
> + writel(0, jzfb->base + JZ_REG_LCD_STATE);
> +
> + writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
> +
> + ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
> + ctrl |= JZ_LCD_CTRL_ENABLE;
> + ctrl &= ~JZ_LCD_CTRL_DISABLE;
> + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
> +}
> +
> +static void jzfb_disable(struct jzfb *jzfb)
> +{
> + uint32_t ctrl;
> +
> + ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
> + ctrl |= JZ_LCD_CTRL_DISABLE;
> + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
> + do {
> + ctrl = readl(jzfb->base + JZ_REG_LCD_STATE);
> + } while (!(ctrl & JZ_LCD_STATE_DISABLED));
> +
> + jz_gpio_bulk_suspend(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
> + jz_gpio_bulk_suspend(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
> +
> + clk_disable(jzfb->ldclk);
> +}
> +
> +static int jzfb_blank(int blank_mode, struct fb_info *info)
> +{
> + struct jzfb *jzfb = info->par;
> +
> + switch (blank_mode) {
> + case FB_BLANK_UNBLANK:
> + mutex_lock(&jzfb->lock);
> + if (jzfb->is_enabled) {
> + mutex_unlock(&jzfb->lock);
> + return 0;
> + }
> +
> + jzfb_enable(jzfb);
> + jzfb->is_enabled = 1;
> +
> + mutex_unlock(&jzfb->lock);
> + break;
> + default:
> + mutex_lock(&jzfb->lock);
> + if (!jzfb->is_enabled) {
> + mutex_unlock(&jzfb->lock);
> + return 0;
> + }
> +
> + jzfb_disable(jzfb);
> + jzfb->is_enabled = 0;
> +
> + mutex_unlock(&jzfb->lock);
> + break;
> + }
> +
> + return 0;
> +}
> +
> +static int jzfb_alloc_devmem(struct jzfb *jzfb)
> +{
> + int max_videosize = 0;
> + struct fb_videomode *mode = jzfb->pdata->modes;
> + void *page;
> + int i;
> +
> + for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) {
> + if (max_videosize < mode->xres * mode->yres)
> + max_videosize = mode->xres * mode->yres;
> + }
> +
> + max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3;
> +
> + jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev,
> + sizeof(*jzfb->framedesc),
> + &jzfb->framedesc_phys, GFP_KERNEL);
> +
> + if (!jzfb->framedesc)
> + return -ENOMEM;
> +
> + jzfb->vidmem_size = PAGE_ALIGN(max_videosize);
> + jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev,
> + jzfb->vidmem_size,
> + &jzfb->vidmem_phys, GFP_KERNEL);
> +
> + if (!jzfb->vidmem)
> + goto err_free_framedesc;
> +
> + for (page = jzfb->vidmem;
> + page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size);
> + page += PAGE_SIZE) {
> + SetPageReserved(virt_to_page(page));
> + }
> +
> + jzfb->framedesc->next = jzfb->framedesc_phys;
> + jzfb->framedesc->addr = jzfb->vidmem_phys;
> + jzfb->framedesc->id = 0xdeafbead;
> + jzfb->framedesc->cmd = 0;
> + jzfb->framedesc->cmd |= max_videosize / 4;
> +
> + return 0;
> +
> +err_free_framedesc:
> + dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc),
> + jzfb->framedesc, jzfb->framedesc_phys);
> + return -ENOMEM;
> +}
> +
> +static void jzfb_free_devmem(struct jzfb *jzfb)
> +{
> + dma_free_coherent(&jzfb->pdev->dev, jzfb->vidmem_size,
> + jzfb->vidmem, jzfb->vidmem_phys);
> + dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc),
> + jzfb->framedesc, jzfb->framedesc_phys);
> +}
> +
> +static struct fb_ops jzfb_ops = {
> + .owner = THIS_MODULE,
> + .fb_check_var = jzfb_check_var,
> + .fb_set_par = jzfb_set_par,
> + .fb_blank = jzfb_blank,
> + .fb_fillrect = sys_fillrect,
> + .fb_copyarea = sys_copyarea,
> + .fb_imageblit = sys_imageblit,
> + .fb_setcolreg = jzfb_setcolreg,
> +};
> +
> +static int __devinit jzfb_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jzfb *jzfb;
> + struct fb_info *fb;
> + struct jz4740_fb_platform_data *pdata = pdev->dev.platform_data;
> + struct resource *mem;
> +
> + if (!pdata) {
> + dev_err(&pdev->dev, "Missing platform data\n");
> + return -ENOENT;
> + }
> +
> + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!mem) {
> + dev_err(&pdev->dev, "Failed to get register memory resource\n");
> + return -ENOENT;
> + }
> +
> + mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
> + if (!mem) {
> + dev_err(&pdev->dev, "Failed to request register memory region\n");
> + return -EBUSY;
> + }
> +
> + fb = framebuffer_alloc(sizeof(struct jzfb), &pdev->dev);
> + if (!fb) {
> + dev_err(&pdev->dev, "Failed to allocate framebuffer device\n");
> + ret = -ENOMEM;
> + goto err_release_mem_region;
> + }
> +
> + fb->fbops = &jzfb_ops;
> + fb->flags = FBINFO_DEFAULT;
> +
> + jzfb = fb->par;
> + jzfb->pdev = pdev;
> + jzfb->pdata = pdata;
> + jzfb->mem = mem;
> +
> + jzfb->ldclk = clk_get(&pdev->dev, "lcd");
> + if (IS_ERR(jzfb->ldclk)) {
> + ret = PTR_ERR(jzfb->ldclk);
> + dev_err(&pdev->dev, "Failed to get lcd clock: %d\n", ret);
> + goto err_framebuffer_release;
> + }
> +
> + jzfb->lpclk = clk_get(&pdev->dev, "lcd_pclk");
> + if (IS_ERR(jzfb->lpclk)) {
> + ret = PTR_ERR(jzfb->lpclk);
> + dev_err(&pdev->dev, "Failed to get lcd pixel clock: %d\n", ret);
> + goto err_put_ldclk;
> + }
> +
> + jzfb->base = ioremap(mem->start, resource_size(mem));
> + if (!jzfb->base) {
> + dev_err(&pdev->dev, "Failed to ioremap register memory region\n");
> + ret = -EBUSY;
> + goto err_put_lpclk;
> + }
> +
> + platform_set_drvdata(pdev, jzfb);
> +
> + mutex_init(&jzfb->lock);
> +
> + fb_videomode_to_modelist(pdata->modes, pdata->num_modes,
> + &fb->modelist);
> + fb_videomode_to_var(&fb->var, pdata->modes);
> + fb->var.bits_per_pixel = pdata->bpp;
> + jzfb_check_var(&fb->var, fb);
> +
> + ret = jzfb_alloc_devmem(jzfb);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to allocate video memory\n");
> + goto err_iounmap;
> + }
> +
> + fb->fix = jzfb_fix;
> + fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8;
> + fb->fix.mmio_start = mem->start;
> + fb->fix.mmio_len = resource_size(mem);
> + fb->fix.smem_start = jzfb->vidmem_phys;
> + fb->fix.smem_len = fb->fix.line_length * fb->var.yres;
> + fb->screen_base = jzfb->vidmem;
> + fb->pseudo_palette = jzfb->pseudo_palette;
> +
> + fb_alloc_cmap(&fb->cmap, 256, 0);
> +
> + clk_enable(jzfb->ldclk);
> + jzfb->is_enabled = 1;
> +
> + writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
> +
> + fb->mode = NULL;
> + jzfb_set_par(fb);
> +
> + jz_gpio_bulk_request(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
> + jz_gpio_bulk_request(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
> +
> + ret = register_framebuffer(fb);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret);
> + goto err_free_devmem;
> + }
> +
> + jzfb->fb = fb;
> +
> + return 0;
> +
> +err_free_devmem:
> + jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
> + jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
> +
> + fb_dealloc_cmap(&fb->cmap);
> + jzfb_free_devmem(jzfb);
> +err_iounmap:
> + iounmap(jzfb->base);
> +err_put_lpclk:
> + clk_put(jzfb->lpclk);
> +err_put_ldclk:
> + clk_put(jzfb->ldclk);
> +err_framebuffer_release:
> + framebuffer_release(fb);
> +err_release_mem_region:
> + release_mem_region(mem->start, resource_size(mem));
> + return ret;
> +}
> +
> +static int __devexit jzfb_remove(struct platform_device *pdev)
> +{
> + struct jzfb *jzfb = platform_get_drvdata(pdev);
> +
> + jzfb_blank(FB_BLANK_POWERDOWN, jzfb->fb);
> +
> + jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
> + jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
> +
> + iounmap(jzfb->base);
> + release_mem_region(jzfb->mem->start, resource_size(jzfb->mem));
> +
> + fb_dealloc_cmap(&jzfb->fb->cmap);
> + jzfb_free_devmem(jzfb);
> +
> + platform_set_drvdata(pdev, NULL);
> +
> + clk_put(jzfb->lpclk);
> + clk_put(jzfb->ldclk);
> +
> + framebuffer_release(jzfb->fb);
> +
> + return 0;
> +}
> +
> +#ifdef CONFIG_PM
> +
> +static int jzfb_suspend(struct device *dev)
> +{
> + struct jzfb *jzfb = dev_get_drvdata(dev);
> +
> + acquire_console_sem();
> + fb_set_suspend(jzfb->fb, 1);
> + release_console_sem();
> +
> + mutex_lock(&jzfb->lock);
> + if (jzfb->is_enabled)
> + jzfb_disable(jzfb);
> + mutex_unlock(&jzfb->lock);
> +
> + return 0;
> +}
> +
> +static int jzfb_resume(struct device *dev)
> +{
> + struct jzfb *jzfb = dev_get_drvdata(dev);
> + clk_enable(jzfb->ldclk);
> +
> + mutex_lock(&jzfb->lock);
> + if (jzfb->is_enabled)
> + jzfb_enable(jzfb);
> + mutex_unlock(&jzfb->lock);
> +
> + acquire_console_sem();
> + fb_set_suspend(jzfb->fb, 0);
> + release_console_sem();
> +
> + return 0;
> +}
> +
> +static const struct dev_pm_ops jzfb_pm_ops = {
> + .suspend = jzfb_suspend,
> + .resume = jzfb_resume,
> + .poweroff = jzfb_suspend,
> + .restore = jzfb_resume,
> +};
> +
> +#define JZFB_PM_OPS (&jzfb_pm_ops)
> +
> +#else
> +#define JZFB_PM_OPS NULL
> +#endif
> +
> +static struct platform_driver jzfb_driver = {
> + .probe = jzfb_probe,
> + .remove = __devexit_p(jzfb_remove),
> + .driver = {
> + .name = "jz4740-fb",
> + .pm = JZFB_PM_OPS,
> + },
> +};
> +
> +static int __init jzfb_init(void)
> +{
> + return platform_driver_register(&jzfb_driver);
> +}
> +module_init(jzfb_init);
> +
> +static void __exit jzfb_exit(void)
> +{
> + platform_driver_unregister(&jzfb_driver);
> +}
> +module_exit(jzfb_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver");
> +MODULE_ALIAS("platform:jz4740-fb");
> diff --git a/include/linux/jz4740_fb.h b/include/linux/jz4740_fb.h
> new file mode 100644
> index 0000000..ab4c963
> --- /dev/null
> +++ b/include/linux/jz4740_fb.h
> @@ -0,0 +1,58 @@
> +/*
> + * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
> + *
> + * 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.
> + *
> + */
> +
> +#ifndef __LINUX_JZ4740_FB_H
> +#define __LINUX_JZ4740_FB_H
> +
> +#include <linux/fb.h>
> +
> +enum jz4740_fb_lcd_type {
> + JZ_LCD_TYPE_GENERIC_16_BIT = 0,
> + JZ_LCD_TYPE_GENERIC_18_BIT = 0 | (1 << 4),
> + JZ_LCD_TYPE_SPECIAL_TFT_1 = 1,
> + JZ_LCD_TYPE_SPECIAL_TFT_2 = 2,
> + JZ_LCD_TYPE_SPECIAL_TFT_3 = 3,
> + JZ_LCD_TYPE_NON_INTERLACED_CCIR656 = 5,
> + JZ_LCD_TYPE_INTERLACED_CCIR656 = 7,
> + JZ_LCD_TYPE_SINGLE_COLOR_STN = 8,
> + JZ_LCD_TYPE_SINGLE_MONOCHROME_STN = 9,
> + JZ_LCD_TYPE_DUAL_COLOR_STN = 10,
> + JZ_LCD_TYPE_DUAL_MONOCHROME_STN = 11,
> + JZ_LCD_TYPE_8BIT_SERIAL = 12,
> +};
> +
> +/*
> +* width: width of the lcd display in mm
> +* height: height of the lcd display in mm
> +* num_modes: size of modes
> +* modes: list of valid video modes
> +* bpp: bits per pixel for the lcd
> +* lcd_type: lcd type
> +*/
> +
> +struct jz4740_fb_platform_data {
> + unsigned int width;
> + unsigned int height;
> +
> + size_t num_modes;
> + struct fb_videomode *modes;
> +
> + unsigned int bpp;
> + enum jz4740_fb_lcd_type lcd_type;
> +
> + unsigned pixclk_falling_edge:1;
> + unsigned date_enable_active_low:1;
> +};
> +
> +#endif

2010-07-04 22:35:31

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND

Hi

Any comments?

Thanks
- Lars

Lars-Peter Clausen wrote:
> This patch adds support for the NAND controller on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: David Woodhouse <[email protected]>
> Cc: [email protected]
>
> ---
> Changes since v1
> - JZ4740: Remove debug macro
> - Fix platform driver remove callback
> - Add custom nand read/write callback since we need to support more then 64 ecc
> bytes
> ---
> drivers/mtd/nand/Kconfig | 6 +
> drivers/mtd/nand/Makefile | 1 +
> drivers/mtd/nand/jz4740_nand.c | 474 +++++++++++++++++++++++++++++++++++++++
> include/linux/mtd/jz4740_nand.h | 34 +++
> 4 files changed, 515 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mtd/nand/jz4740_nand.c
> create mode 100644 include/linux/mtd/jz4740_nand.h
>
> diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
> index ffc3720..362d177 100644
> --- a/drivers/mtd/nand/Kconfig
> +++ b/drivers/mtd/nand/Kconfig
> @@ -526,4 +526,10 @@ config MTD_NAND_NUC900
> This enables the driver for the NAND Flash on evaluation board based
> on w90p910 / NUC9xx.
>
> +config MTD_NAND_JZ4740
> + tristate "Support for JZ4740 SoC NAND controller"
> + depends on MACH_JZ4740
> + help
> + Enables support for NAND Flash on JZ4740 SoC based boards.
> +
> endif # MTD_NAND
> diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
> index e8ab884..ac83dcd 100644
> --- a/drivers/mtd/nand/Makefile
> +++ b/drivers/mtd/nand/Makefile
> @@ -46,5 +46,6 @@ obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o
> obj-$(CONFIG_MTD_NAND_BCM_UMI) += bcm_umi_nand.o nand_bcm_umi.o
> obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
> obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
> +obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o
>
> nand-objs := nand_base.o nand_bbt.o
> diff --git a/drivers/mtd/nand/jz4740_nand.c b/drivers/mtd/nand/jz4740_nand.c
> new file mode 100644
> index 0000000..8c55f8a
> --- /dev/null
> +++ b/drivers/mtd/nand/jz4740_nand.c
> @@ -0,0 +1,474 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC NAND 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/ioport.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +
> +#include <linux/mtd/mtd.h>
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +
> +#include <linux/mtd/jz4740_nand.h>
> +#include <linux/gpio.h>
> +
> +#define JZ_REG_NAND_CTRL 0x50
> +#define JZ_REG_NAND_ECC_CTRL 0x100
> +#define JZ_REG_NAND_DATA 0x104
> +#define JZ_REG_NAND_PAR0 0x108
> +#define JZ_REG_NAND_PAR1 0x10C
> +#define JZ_REG_NAND_PAR2 0x110
> +#define JZ_REG_NAND_IRQ_STAT 0x114
> +#define JZ_REG_NAND_IRQ_CTRL 0x118
> +#define JZ_REG_NAND_ERR(x) (0x11C + (x << 2))
> +
> +#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4)
> +#define JZ_NAND_ECC_CTRL_ENCODING BIT(3)
> +#define JZ_NAND_ECC_CTRL_RS BIT(2)
> +#define JZ_NAND_ECC_CTRL_RESET BIT(1)
> +#define JZ_NAND_ECC_CTRL_ENABLE BIT(0)
> +
> +#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29))
> +#define JZ_NAND_STATUS_PAD_FINISH BIT(4)
> +#define JZ_NAND_STATUS_DEC_FINISH BIT(3)
> +#define JZ_NAND_STATUS_ENC_FINISH BIT(2)
> +#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1)
> +#define JZ_NAND_STATUS_ERROR BIT(0)
> +
> +#define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT(x << 1)
> +#define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT((x << 1) + 1)
> +
> +#define JZ_NAND_DATA_ADDR ((void __iomem *)0xB8000000)
> +#define JZ_NAND_CMD_ADDR (JZ_NAND_DATA_ADDR + 0x8000)
> +#define JZ_NAND_ADDR_ADDR (JZ_NAND_DATA_ADDR + 0x10000)
> +
> +struct jz_nand {
> + struct mtd_info mtd;
> + struct nand_chip chip;
> + void __iomem *base;
> + struct resource *mem;
> +
> + struct jz_nand_platform_data *pdata;
> + bool is_reading;
> +};
> +
> +static inline struct jz_nand *mtd_to_jz_nand(struct mtd_info *mtd)
> +{
> + return container_of(mtd, struct jz_nand, mtd);
> +}
> +
> +static void jz_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
> +{
> + struct jz_nand *nand = mtd_to_jz_nand(mtd);
> + struct nand_chip *chip = mtd->priv;
> + uint32_t reg;
> +
> + if (ctrl & NAND_CTRL_CHANGE) {
> + BUG_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE));
> + if (ctrl & NAND_ALE)
> + chip->IO_ADDR_W = JZ_NAND_ADDR_ADDR;
> + else if (ctrl & NAND_CLE)
> + chip->IO_ADDR_W = JZ_NAND_CMD_ADDR;
> + else
> + chip->IO_ADDR_W = JZ_NAND_DATA_ADDR;
> +
> + reg = readl(nand->base + JZ_REG_NAND_CTRL);
> + if (ctrl & NAND_NCE)
> + reg |= JZ_NAND_CTRL_ASSERT_CHIP(0);
> + else
> + reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(0);
> + writel(reg, nand->base + JZ_REG_NAND_CTRL);
> + }
> + if (dat != NAND_CMD_NONE)
> + writeb(dat, chip->IO_ADDR_W);
> +}
> +
> +static int jz_nand_dev_ready(struct mtd_info *mtd)
> +{
> + struct jz_nand *nand = mtd_to_jz_nand(mtd);
> + return gpio_get_value_cansleep(nand->pdata->busy_gpio);
> +}
> +
> +static void jz_nand_hwctl(struct mtd_info *mtd, int mode)
> +{
> + struct jz_nand *nand = mtd_to_jz_nand(mtd);
> + uint32_t reg;
> +
> + writel(0, nand->base + JZ_REG_NAND_IRQ_STAT);
> + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
> +
> + reg |= JZ_NAND_ECC_CTRL_RESET;
> + reg |= JZ_NAND_ECC_CTRL_ENABLE;
> + reg |= JZ_NAND_ECC_CTRL_RS;
> +
> + switch (mode) {
> + case NAND_ECC_READ:
> + reg &= ~JZ_NAND_ECC_CTRL_ENCODING;
> + nand->is_reading = true;
> + break;
> + case NAND_ECC_WRITE:
> + reg |= JZ_NAND_ECC_CTRL_ENCODING;
> + nand->is_reading = false;
> + break;
> + default:
> + break;
> + }
> +
> + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
> +}
> +
> +static int jz_nand_calculate_ecc_rs(struct mtd_info *mtd, const uint8_t *dat,
> + uint8_t *ecc_code)
> +{
> + struct jz_nand *nand = mtd_to_jz_nand(mtd);
> + uint32_t reg, status;
> + int i;
> + static uint8_t empty_block_ecc[] = {0xcd, 0x9d, 0x90, 0x58, 0xf4,
> + 0x8b, 0xff, 0xb7, 0x6f};
> +
> + if (nand->is_reading)
> + return 0;
> +
> + do {
> + status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
> + } while (!(status & JZ_NAND_STATUS_ENC_FINISH));
> +
> + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
> + reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
> + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
> +
> + for (i = 0; i < 9; ++i)
> + ecc_code[i] = readb(nand->base + JZ_REG_NAND_PAR0 + i);
> +
> + /* If the written data is completly 0xff, we also want to write 0xff as
> + * ecc, otherwise we will get in trouble when doing subpage writes. */
> + if (memcmp(ecc_code, empty_block_ecc, 9) == 0)
> + memset(ecc_code, 0xff, 9);
> +
> + return 0;
> +}
> +
> +static void correct_data(uint8_t *dat, int index, int mask)
> +{
> + int offset = index & 0x7;
> + uint16_t data;
> +
> + index += (index >> 3);
> +
> + data = dat[index];
> + data |= dat[index+1] << 8;
> +
> + mask ^= (data >> offset) & 0x1ff;
> + data &= ~(0x1ff << offset);
> + data |= (mask << offset);
> +
> + dat[index] = data & 0xff;
> + dat[index+1] = (data >> 8) & 0xff;
> +}
> +
> +static int jz_nand_correct_ecc_rs(struct mtd_info *mtd, uint8_t *dat,
> + uint8_t *read_ecc, uint8_t *calc_ecc)
> +{
> + struct jz_nand *nand = mtd_to_jz_nand(mtd);
> + int i, error_count, index;
> + uint32_t reg, status, error;
> + uint32_t t;
> +
> + t = read_ecc[0];
> +
> + if (t == 0xff) {
> + for (i = 1; i < 9; ++i)
> + t &= read_ecc[i];
> +
> + t &= dat[0];
> + t &= dat[nand->chip.ecc.size / 2];
> + t &= dat[nand->chip.ecc.size - 1];
> +
> + if (t == 0xff) {
> + for (i = 1; i < nand->chip.ecc.size - 1; ++i)
> + t &= dat[i];
> + if (t == 0xff)
> + return 0;
> + }
> + }
> +
> + for (i = 0; i < 9; ++i)
> + writeb(read_ecc[i], nand->base + JZ_REG_NAND_PAR0 + i);
> +
> + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
> + reg |= JZ_NAND_ECC_CTRL_PAR_READY;
> + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
> +
> + do {
> + status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
> + } while (!(status & JZ_NAND_STATUS_DEC_FINISH));
> +
> + reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
> + reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
> + writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
> +
> + if (status & JZ_NAND_STATUS_ERROR) {
> + if (status & JZ_NAND_STATUS_UNCOR_ERROR)
> + return -1;
> +
> + error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29;
> +
> + for (i = 0; i < error_count; ++i) {
> + error = readl(nand->base + JZ_REG_NAND_ERR(i));
> + index = ((error >> 16) & 0x1ff) - 1;
> + if (index >= 0 && index < 512)
> + correct_data(dat, index, error & 0x1ff);
> + }
> +
> + return error_count;
> + }
> +
> + return 0;
> +}
> +
> +
> +/* Copy paste of nand_read_page_hwecc_oob_first except for different eccpos
> + * handling. The ecc area is for 4k chips 72 bytes long and thus does not fit
> + * into the eccpos array. */
> +static int jz_nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
> + struct nand_chip *chip, uint8_t *buf, int page)
> +{
> + int i, eccsize = chip->ecc.size;
> + int eccbytes = chip->ecc.bytes;
> + int eccsteps = chip->ecc.steps;
> + uint8_t *p = buf;
> + unsigned int ecc_offset = chip->page_shift;
> +
> + /* Read the OOB area first */
> + chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
> + chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
> + chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
> +
> + for (i = ecc_offset; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
> + int stat;
> +
> + chip->ecc.hwctl(mtd, NAND_ECC_READ);
> + chip->read_buf(mtd, p, eccsize);
> +
> + stat = chip->ecc.correct(mtd, p, &chip->oob_poi[i], NULL);
> + if (stat < 0)
> + mtd->ecc_stats.failed++;
> + else
> + mtd->ecc_stats.corrected += stat;
> + }
> + return 0;
> +}
> +
> +/* Copy-and-paste of nand_write_page_hwecc with different eccpos handling. */
> +static void jz_nand_write_page_hwecc(struct mtd_info *mtd,
> + struct nand_chip *chip, const uint8_t *buf)
> +{
> + int i, eccsize = chip->ecc.size;
> + int eccbytes = chip->ecc.bytes;
> + int eccsteps = chip->ecc.steps;
> + const uint8_t *p = buf;
> + unsigned int ecc_offset = chip->page_shift;
> +
> + for (i = ecc_offset; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
> + chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
> + chip->write_buf(mtd, p, eccsize);
> + chip->ecc.calculate(mtd, p, &chip->oob_poi[i]);
> + }
> +
> + chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
> +}
> +
> +#ifdef CONFIG_MTD_CMDLINE_PARTS
> +static const char *part_probes[] = {"cmdline", NULL};
> +#endif
> +
> +static int __devinit jz_nand_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jz_nand *nand;
> + struct nand_chip *chip;
> + struct mtd_info *mtd;
> + struct jz_nand_platform_data *pdata = pdev->dev.platform_data;
> +#ifdef CONFIG_MTD_PARTITIONS
> + struct mtd_partition *partition_info;
> + int num_partitions = 0;
> +#endif
> +
> + nand = kzalloc(sizeof(*nand), GFP_KERNEL);
> + if (!nand) {
> + dev_err(&pdev->dev, "Failed to allocate device structure.\n");
> + return -ENOMEM;
> + }
> +
> + nand->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!nand->mem) {
> + dev_err(&pdev->dev, "Failed to get platform mmio memory\n");
> + ret = -ENOENT;
> + goto err_free;
> + }
> +
> + nand->mem = request_mem_region(nand->mem->start,
> + resource_size(nand->mem), pdev->name);
> + if (!nand->mem) {
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + ret = -EBUSY;
> + goto err_free;
> + }
> +
> + nand->base = ioremap(nand->mem->start, resource_size(nand->mem));
> + if (!nand->base) {
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory region\n");
> + ret = -EBUSY;
> + goto err_release_mem;
> + }
> +
> + if (pdata && gpio_is_valid(pdata->busy_gpio)) {
> + ret = gpio_request(pdata->busy_gpio, "NAND busy pin");
> + if (ret) {
> + dev_err(&pdev->dev,
> + "Failed to request busy gpio %d: %d\n",
> + pdata->busy_gpio, ret);
> + goto err_iounmap;
> + }
> + }
> +
> + mtd = &nand->mtd;
> + chip = &nand->chip;
> + mtd->priv = chip;
> + mtd->owner = THIS_MODULE;
> + mtd->name = "jz4740-nand";
> +
> + chip->ecc.hwctl = jz_nand_hwctl;
> + chip->ecc.calculate = jz_nand_calculate_ecc_rs;
> + chip->ecc.correct = jz_nand_correct_ecc_rs;
> + chip->ecc.mode = NAND_ECC_HW_OOB_FIRST;
> + chip->ecc.size = 512;
> + chip->ecc.bytes = 9;
> +
> + chip->ecc.read_page = jz_nand_read_page_hwecc_oob_first;
> + chip->ecc.write_page = jz_nand_write_page_hwecc;
> +
> + if (pdata)
> + chip->ecc.layout = pdata->ecc_layout;
> +
> + chip->chip_delay = 50;
> + chip->cmd_ctrl = jz_nand_cmd_ctrl;
> +
> + if (pdata && gpio_is_valid(pdata->busy_gpio))
> + chip->dev_ready = jz_nand_dev_ready;
> +
> + chip->IO_ADDR_R = JZ_NAND_DATA_ADDR;
> + chip->IO_ADDR_W = JZ_NAND_DATA_ADDR;
> +
> + nand->pdata = pdata;
> + platform_set_drvdata(pdev, nand);
> +
> + ret = nand_scan_ident(mtd, 1, NULL);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to scan nand\n");
> + goto err_gpio_free;
> + }
> +
> + if (pdata && pdata->ident_callback) {
> + pdata->ident_callback(pdev, chip, &pdata->partitions,
> + &pdata->num_partitions);
> + }
> +
> + ret = nand_scan_tail(mtd);
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to scan nand\n");
> + goto err_gpio_free;
> + }
> +
> +#ifdef CONFIG_MTD_PARTITIONS
> +#ifdef CONFIG_MTD_CMDLINE_PARTS
> + num_partitions = parse_mtd_partitions(mtd, part_probes,
> + &partition_info, 0);
> +#endif
> + if (num_partitions <= 0 && pdata) {
> + num_partitions = pdata->num_partitions;
> + partition_info = pdata->partitions;
> + }
> +
> + if (num_partitions > 0)
> + ret = add_mtd_partitions(mtd, partition_info, num_partitions);
> + else
> +#endif
> + ret = add_mtd_device(mtd);
> +
> + if (ret) {
> + dev_err(&pdev->dev, "Failed to add mtd device\n");
> + goto err_nand_release;
> + }
> +
> + dev_info(&pdev->dev, "Successfully registered JZ4740 NAND driver\n");
> +
> + return 0;
> +err_nand_release:
> + nand_release(&nand->mtd);
> +err_gpio_free:
> + platform_set_drvdata(pdev, NULL);
> + gpio_free(pdata->busy_gpio);
> +err_iounmap:
> + iounmap(nand->base);
> +err_release_mem:
> + release_mem_region(nand->mem->start, resource_size(nand->mem));
> +err_free:
> + kfree(nand);
> + return ret;
> +}
> +
> +static int __devexit jz_nand_remove(struct platform_device *pdev)
> +{
> + struct jz_nand *nand = platform_get_drvdata(pdev);
> +
> + nand_release(&nand->mtd);
> +
> + iounmap(nand->base);
> + release_mem_region(nand->mem->start, resource_size(nand->mem));
> +
> + platform_set_drvdata(pdev, NULL);
> + kfree(nand);
> +
> + return 0;
> +}
> +
> +struct platform_driver jz_nand_driver = {
> + .probe = jz_nand_probe,
> + .remove = __devexit_p(jz_nand_remove),
> + .driver = {
> + .name = "jz4740-nand",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init jz_nand_init(void)
> +{
> + return platform_driver_register(&jz_nand_driver);
> +}
> +module_init(jz_nand_init);
> +
> +static void __exit jz_nand_exit(void)
> +{
> + platform_driver_unregister(&jz_nand_driver);
> +}
> +module_exit(jz_nand_exit);
> +
> +MODULE_LICENSE("GPL");
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC");
> +MODULE_ALIAS("platform:jz4740-nand");
> diff --git a/include/linux/mtd/jz4740_nand.h b/include/linux/mtd/jz4740_nand.h
> new file mode 100644
> index 0000000..379f9b6
> --- /dev/null
> +++ b/include/linux/mtd/jz4740_nand.h
> @@ -0,0 +1,34 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC NAND 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.
> + *
> + */
> +
> +#ifndef __JZ_NAND_H__
> +#define __JZ_NAND_H__
> +
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +
> +struct jz_nand_platform_data {
> + int num_partitions;
> + struct mtd_partition *partitions;
> +
> + struct nand_ecclayout *ecc_layout;
> +
> + unsigned int busy_gpio;
> +
> + void (*ident_callback)(struct platform_device *, struct nand_chip *,
> + struct mtd_partition **, int *num_partitions);
> +};
> +
> +#endif

2010-07-04 22:48:09

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Samuel

Could you take a look at this driver and say whether this driver looks ok or not?

Thanks
- - Lars

Lars-Peter Clausen wrote:
> This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to
> demultiplex IRQs and synchronize access to shared registers between the battery,
> hwmon and (future) touchscreen driver.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Samuel Ortiz <[email protected]>
> ---
> drivers/mfd/Kconfig | 8 +
> drivers/mfd/Makefile | 1 +
> drivers/mfd/jz4740-adc.c | 392 ++++++++++++++++++++++++++++++++++++++++++++
> include/linux/jz4740-adc.h | 32 ++++
> 4 files changed, 433 insertions(+), 0 deletions(-)
> create mode 100644 drivers/mfd/jz4740-adc.c
> create mode 100644 include/linux/jz4740-adc.h
>
> diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
> index 9da0e50..9cacc39 100644
> --- a/drivers/mfd/Kconfig
> +++ b/drivers/mfd/Kconfig
> @@ -482,6 +482,14 @@ config MFD_JANZ_CMODIO
> host many different types of MODULbus daughterboards, including
> CAN and GPIO controllers.
>
> +config MFD_JZ4740_ADC
> + tristate "Support for the JZ4740 SoC ADC core"
> + select MFD_CORE
> + depends on MACH_JZ4740
> + help
> + Say yes here if you want support for the ADC unit in the JZ4740 SoC.
> + This driver is necessary for jz4740-battery and jz4740-hwmon driver.
> +
> endif # MFD_SUPPORT
>
> menu "Multimedia Capabilities Port drivers"
> diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
> index fb503e7..a1a2765 100644
> --- a/drivers/mfd/Makefile
> +++ b/drivers/mfd/Makefile
> @@ -71,3 +71,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
> obj-$(CONFIG_LPC_SCH) += lpc_sch.o
> obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
> obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
> +obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
> diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c
> new file mode 100644
> index 0000000..cac8a52
> --- /dev/null
> +++ b/drivers/mfd/jz4740-adc.c
> @@ -0,0 +1,392 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC ADC 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.
> + *
> + * This driver synchronizes access to the JZ4740 ADC core between the
> + * JZ4740 battery and hwmon drivers.
> + */
> +
> +#include <linux/err.h>
> +#include <linux/irq.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#include <linux/clk.h>
> +#include <linux/mfd/core.h>
> +
> +#include <linux/jz4740-adc.h>
> +
> +
> +#define JZ_REG_ADC_ENABLE 0x00
> +#define JZ_REG_ADC_CFG 0x04
> +#define JZ_REG_ADC_CTRL 0x08
> +#define JZ_REG_ADC_STATUS 0x0c
> +
> +#define JZ_REG_ADC_TOUCHSCREEN_BASE 0x10
> +#define JZ_REG_ADC_BATTERY_BASE 0x1c
> +#define JZ_REG_ADC_HWMON_BASE 0x20
> +
> +#define JZ_ADC_ENABLE_TOUCH BIT(2)
> +#define JZ_ADC_ENABLE_BATTERY BIT(1)
> +#define JZ_ADC_ENABLE_ADCIN BIT(0)
> +
> +enum {
> + JZ_ADC_IRQ_ADCIN = 0,
> + JZ_ADC_IRQ_BATTERY,
> + JZ_ADC_IRQ_TOUCH,
> + JZ_ADC_IRQ_PENUP,
> + JZ_ADC_IRQ_PENDOWN,
> +};
> +
> +struct jz4740_adc {
> + struct resource *mem;
> + void __iomem *base;
> +
> + int irq;
> + int irq_base;
> +
> + struct clk *clk;
> + unsigned int clk_ref;
> +
> + spinlock_t lock;
> +};
> +
> +static inline void jz4740_adc_irq_set_masked(struct jz4740_adc *adc, int irq,
> + bool masked)
> +{
> + unsigned long flags;
> + uint8_t val;
> +
> + irq -= adc->irq_base;
> +
> + spin_lock_irqsave(&adc->lock, flags);
> +
> + val = readb(adc->base + JZ_REG_ADC_CTRL);
> + if (masked)
> + val |= BIT(irq);
> + else
> + val &= ~BIT(irq);
> + writeb(val, adc->base + JZ_REG_ADC_CTRL);
> +
> + spin_unlock_irqrestore(&adc->lock, flags);
> +}
> +
> +static void jz4740_adc_irq_mask(unsigned int irq)
> +{
> + struct jz4740_adc *adc = get_irq_chip_data(irq);
> + jz4740_adc_irq_set_masked(adc, irq, true);
> +}
> +
> +static void jz4740_adc_irq_unmask(unsigned int irq)
> +{
> + struct jz4740_adc *adc = get_irq_chip_data(irq);
> + jz4740_adc_irq_set_masked(adc, irq, false);
> +}
> +
> +static void jz4740_adc_irq_ack(unsigned int irq)
> +{
> + struct jz4740_adc *adc = get_irq_chip_data(irq);
> +
> + irq -= adc->irq_base;
> + writeb(BIT(irq), adc->base + JZ_REG_ADC_STATUS);
> +}
> +
> +static struct irq_chip jz4740_adc_irq_chip = {
> + .name = "jz4740-adc",
> + .mask = jz4740_adc_irq_mask,
> + .unmask = jz4740_adc_irq_unmask,
> + .ack = jz4740_adc_irq_ack,
> +};
> +
> +static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
> +{
> + struct jz4740_adc *adc = get_irq_desc_data(desc);
> + uint8_t status;
> + unsigned int i;
> +
> + status = readb(adc->base + JZ_REG_ADC_STATUS);
> +
> + for (i = 0; i < 5; ++i) {
> + if (status & BIT(i))
> + generic_handle_irq(adc->irq_base + i);
> + }
> +}
> +
> +static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&adc->lock, flags);
> + if (adc->clk_ref++ == 0)
> + clk_enable(adc->clk);
> + spin_unlock_irqrestore(&adc->lock, flags);
> +}
> +
> +static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&adc->lock, flags);
> + if (--adc->clk_ref == 0)
> + clk_disable(adc->clk);
> + spin_unlock_irqrestore(&adc->lock, flags);
> +}
> +
> +
> +static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine,
> + bool enabled)
> +{
> + unsigned long flags;
> + uint8_t val;
> +
> + spin_lock_irqsave(&adc->lock, flags);
> +
> + val = readb(adc->base + JZ_REG_ADC_ENABLE);
> + if (enabled)
> + val |= BIT(engine);
> + else
> + val &= BIT(engine);
> + writeb(val, adc->base + JZ_REG_ADC_ENABLE);
> +
> + spin_unlock_irqrestore(&adc->lock, flags);
> +}
> +
> +static int jz4740_adc_cell_enable(struct platform_device *pdev)
> +{
> + struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
> +
> + jz4740_adc_clk_enable(adc);
> + jz4740_adc_set_enabled(adc, pdev->id, true);
> +
> + return 0;
> +}
> +
> +static int jz4740_adc_cell_disable(struct platform_device *pdev)
> +{
> + struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
> +
> + jz4740_adc_set_enabled(adc, pdev->id, false);
> + jz4740_adc_clk_disable(adc);
> +
> + return 0;
> +}
> +
> +int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val)
> +{
> + struct jz4740_adc *adc = dev_get_drvdata(dev);
> + unsigned long flags;
> + uint32_t cfg;
> +
> + if (!adc)
> + return -ENODEV;
> +
> + spin_lock_irqsave(&adc->lock, flags);
> +
> + cfg = readl(adc->base + JZ_REG_ADC_CFG);
> +
> + cfg &= ~mask;
> + cfg |= val;
> +
> + writel(cfg, adc->base + JZ_REG_ADC_CFG);
> +
> + spin_unlock_irqrestore(&adc->lock, flags);
> +
> + return 0;
> +}
> +EXPORT_SYMBOL_GPL(jz4740_adc_set_config);
> +
> +static struct resource jz4740_hwmon_resources[] = {
> + {
> + .start = JZ_ADC_IRQ_ADCIN,
> + .flags = IORESOURCE_IRQ,
> + },
> + {
> + .start = JZ_REG_ADC_HWMON_BASE,
> + .end = JZ_REG_ADC_HWMON_BASE + 3,
> + .flags = IORESOURCE_MEM,
> + },
> +};
> +
> +static struct resource jz4740_battery_resources[] = {
> + {
> + .start = JZ_ADC_IRQ_BATTERY,
> + .flags = IORESOURCE_IRQ,
> + },
> + {
> + .start = JZ_REG_ADC_BATTERY_BASE,
> + .end = JZ_REG_ADC_BATTERY_BASE + 3,
> + .flags = IORESOURCE_MEM,
> + },
> +};
> +
> +const struct mfd_cell jz4740_adc_cells[] = {
> + {
> + .id = 0,
> + .name = "jz4740-hwmon",
> + .num_resources = ARRAY_SIZE(jz4740_hwmon_resources),
> + .resources = jz4740_hwmon_resources,
> + .platform_data = (void *)&jz4740_adc_cells[0],
> + .data_size = sizeof(struct mfd_cell),
> +
> + .enable = jz4740_adc_cell_enable,
> + .disable = jz4740_adc_cell_disable,
> + },
> + {
> + .id = 1,
> + .name = "jz4740-battery",
> + .num_resources = ARRAY_SIZE(jz4740_battery_resources),
> + .resources = jz4740_battery_resources,
> + .platform_data = (void *)&jz4740_adc_cells[1],
> + .data_size = sizeof(struct mfd_cell),
> +
> + .enable = jz4740_adc_cell_enable,
> + .disable = jz4740_adc_cell_disable,
> + },
> +};
> +
> +static int __devinit jz4740_adc_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jz4740_adc *adc;
> + struct resource *mem_base;
> + int irq;
> +
> + adc = kmalloc(sizeof(*adc), GFP_KERNEL);
> +
> + adc->irq = platform_get_irq(pdev, 0);
> + if (adc->irq < 0) {
> + ret = adc->irq;
> + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
> + goto err_free;
> + }
> +
> + adc->irq_base = platform_get_irq(pdev, 1);
> + if (adc->irq_base < 0) {
> + ret = adc->irq_base;
> + dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);
> + goto err_free;
> + }
> +
> + mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!mem_base) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
> + goto err_free;
> + }
> +
> + /* Only request the shared registers for the MFD driver */
> + adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
> + pdev->name);
> + if (!adc->mem) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + goto err_free;
> + }
> +
> + adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
> + if (!adc->base) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> + goto err_release_mem_region;
> + }
> +
> + adc->clk = clk_get(&pdev->dev, "adc");
> + if (IS_ERR(adc->clk)) {
> + ret = PTR_ERR(adc->clk);
> + dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
> + goto err_iounmap;
> + }
> +
> + spin_lock_init(&adc->lock);
> +
> + adc->clk_ref = 0;
> +
> + platform_set_drvdata(pdev, adc);
> +
> + for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) {
> + set_irq_chip_data(irq, adc);
> + set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip,
> + handle_level_irq);
> + }
> +
> + set_irq_data(adc->irq, adc);
> + set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux);
> +
> + writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
> + writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
> +
> + mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
> + ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base);
> +
> + return 0;
> +
> +err_iounmap:
> + platform_set_drvdata(pdev, NULL);
> + iounmap(adc->base);
> +err_release_mem_region:
> + release_mem_region(adc->mem->start, resource_size(adc->mem));
> +err_free:
> + kfree(adc);
> +
> + return ret;
> +}
> +
> +static int __devexit jz4740_adc_remove(struct platform_device *pdev)
> +{
> + struct jz4740_adc *adc = platform_get_drvdata(pdev);
> +
> + mfd_remove_devices(&pdev->dev);
> +
> + set_irq_data(adc->irq, NULL);
> + set_irq_chained_handler(adc->irq, NULL);
> +
> + iounmap(adc->base);
> + release_mem_region(adc->mem->start, resource_size(adc->mem));
> +
> + clk_put(adc->clk);
> +
> + platform_set_drvdata(pdev, NULL);
> +
> + kfree(adc);
> +
> + return 0;
> +}
> +
> +struct platform_driver jz4740_adc_driver = {
> + .probe = jz4740_adc_probe,
> + .remove = __devexit_p(jz4740_adc_remove),
> + .driver = {
> + .name = "jz4740-adc",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init jz4740_adc_init(void)
> +{
> + return platform_driver_register(&jz4740_adc_driver);
> +}
> +module_init(jz4740_adc_init);
> +
> +static void __exit jz4740_adc_exit(void)
> +{
> + platform_driver_unregister(&jz4740_adc_driver);
> +}
> +module_exit(jz4740_adc_exit);
> +
> +MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
> +MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-adc");
> diff --git a/include/linux/jz4740-adc.h b/include/linux/jz4740-adc.h
> new file mode 100644
> index 0000000..9053f95
> --- /dev/null
> +++ b/include/linux/jz4740-adc.h
> @@ -0,0 +1,32 @@
> +
> +#ifndef __LINUX_JZ4740_ADC
> +#define __LINUX_JZ4740_ADC
> +
> +#include <linux/device.h>
> +
> +/*
> + * jz4740_adc_set_config - Configure a JZ4740 adc device
> + * @dev: Pointer to a jz4740-adc device
> + * @mask: Mask for the config value to be set
> + * @val: Value to be set
> + *
> + * This function can be used by the JZ4740 ADC mfd cells to configure their
> + * options in the shared config register.
> +*/
> +int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val);
> +
> +#define JZ_ADC_CONFIG_SPZZ BIT(31)
> +#define JZ_ADC_CONFIG_EX_IN BIT(30)
> +#define JZ_ADC_CONFIG_DNUM_MASK (0x7 << 16)
> +#define JZ_ADC_CONFIG_DMA_ENABLE BIT(15)
> +#define JZ_ADC_CONFIG_XYZ_MASK (0x2 << 13)
> +#define JZ_ADC_CONFIG_SAMPLE_NUM_MASK (0x7 << 10)
> +#define JZ_ADC_CONFIG_CLKDIV_MASK (0xf << 5)
> +#define JZ_ADC_CONFIG_BAT_MB BIT(4)
> +
> +#define JZ_ADC_CONFIG_DNUM(dnum) ((dnum) << 16)
> +#define JZ_ADC_CONFIG_XYZ_OFFSET(dnum) ((xyz) << 13)
> +#define JZ_ADC_CONFIG_SAMPLE_NUM(x) ((x) << 10)
> +#define JZ_ADC_CONFIG_CLKDIV(div) ((div) << 5)
> +
> +#endif

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkwxD5AACgkQBX4mSR26RiN/pgCfbdAlLjaN4Ot91yNDkNs+27TX
f90AoIFB5pXsuLK+6lQbqq5WUt8cOhlM
=wFGF
-----END PGP SIGNATURE-----

2010-07-05 14:53:12

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver

Hi Lars,

Sorry for the review delay. This one got lost in my inbox :-/

On Sat, Jun 19, 2010 at 07:08:27AM +0200, Lars-Peter Clausen wrote:
> This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to
> demultiplex IRQs and synchronize access to shared registers between the battery,
> hwmon and (future) touchscreen driver.
The driver looks pretty clean. I only have a couple nitpicks:

> +static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
> +{
> + struct jz4740_adc *adc = get_irq_desc_data(desc);
> + uint8_t status;
> + unsigned int i;
> +
> + status = readb(adc->base + JZ_REG_ADC_STATUS);
> +
> + for (i = 0; i < 5; ++i) {
> + if (status & BIT(i))
> + generic_handle_irq(adc->irq_base + i);
for_each_set_bit() could nicely replace your loop here.

> +static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
> +{
> + unsigned long flags;
> +
> + spin_lock_irqsave(&adc->lock, flags);
> + if (adc->clk_ref++ == 0)
> + clk_enable(adc->clk);
> + spin_unlock_irqrestore(&adc->lock, flags);
> +}
I'm not familiar with your platform clock framework, but shouldn't the
refcounting be handled there instead of spread over all your drivers ?

> +static int __devinit jz4740_adc_probe(struct platform_device *pdev)
> +{
> + int ret;
> + struct jz4740_adc *adc;
> + struct resource *mem_base;
> + int irq;
> +
> + adc = kmalloc(sizeof(*adc), GFP_KERNEL);
> +
> + adc->irq = platform_get_irq(pdev, 0);
> + if (adc->irq < 0) {
> + ret = adc->irq;
> + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
> + goto err_free;
> + }
> +
> + adc->irq_base = platform_get_irq(pdev, 1);
> + if (adc->irq_base < 0) {
> + ret = adc->irq_base;
> + dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);
> + goto err_free;
> + }
> +
> + mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (!mem_base) {
> + ret = -ENOENT;
> + dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
> + goto err_free;
> + }
> +
> + /* Only request the shared registers for the MFD driver */
> + adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
> + pdev->name);
> + if (!adc->mem) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
> + goto err_free;
> + }
> +
> + adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
> + if (!adc->base) {
> + ret = -EBUSY;
> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
> + goto err_release_mem_region;
> + }
> +
> + adc->clk = clk_get(&pdev->dev, "adc");
> + if (IS_ERR(adc->clk)) {
> + ret = PTR_ERR(adc->clk);
> + dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
> + goto err_iounmap;
> + }
> +
> + spin_lock_init(&adc->lock);
> +
> + adc->clk_ref = 0;
> +
> + platform_set_drvdata(pdev, adc);
> +
> + for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) {
> + set_irq_chip_data(irq, adc);
> + set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip,
> + handle_level_irq);
> + }
> +
> + set_irq_data(adc->irq, adc);
> + set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux);
> +
> + writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
> + writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
> +
> + mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
> + ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base);
> +
> + return 0;
Please return the mfd_add_devices return value.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2010-07-05 15:44:01

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver

Hi

Samuel Ortiz wrote:
> Hi Lars,
>
> Sorry for the review delay. This one got lost in my inbox :-/
>
> On Sat, Jun 19, 2010 at 07:08:27AM +0200, Lars-Peter Clausen wrote:
>> This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to
>> demultiplex IRQs and synchronize access to shared registers between the battery,
>> hwmon and (future) touchscreen driver.
> The driver looks pretty clean. I only have a couple nitpicks:
>
>> +static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
>> +{
>> + struct jz4740_adc *adc = get_irq_desc_data(desc);
>> + uint8_t status;
>> + unsigned int i;
>> +
>> + status = readb(adc->base + JZ_REG_ADC_STATUS);
>> +
>> + for (i = 0; i < 5; ++i) {
>> + if (status & BIT(i))
>> + generic_handle_irq(adc->irq_base + i);
> for_each_set_bit() could nicely replace your loop here.

I wonder if it would make sense to optimize for_each_set_bit for small builtin
constants. Using it in it's current form for this loop would likely add some overhead.

>
>> +static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
>> +{
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&adc->lock, flags);
>> + if (adc->clk_ref++ == 0)
>> + clk_enable(adc->clk);
>> + spin_unlock_irqrestore(&adc->lock, flags);
>> +}
> I'm not familiar with your platform clock framework, but shouldn't the
> refcounting be handled there instead of spread over all your drivers ?

The ADC clock is the only clock on this platform which is shared between multiple
devices so I refrained from adding the refcounting to the core for now. But to be
strictly complaint with the clock API as defined in linux/clk.h the implementation
should do refcounting. I'm still a bit uncertain what would be done best here.

>
>> +static int __devinit jz4740_adc_probe(struct platform_device *pdev)
>> +{
>> + int ret;
>> + struct jz4740_adc *adc;
>> + struct resource *mem_base;
>> + int irq;
>> +
>> + adc = kmalloc(sizeof(*adc), GFP_KERNEL);
>> +
>> + adc->irq = platform_get_irq(pdev, 0);
>> + if (adc->irq < 0) {
>> + ret = adc->irq;
>> + dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
>> + goto err_free;
>> + }
>> +
>> + adc->irq_base = platform_get_irq(pdev, 1);
>> + if (adc->irq_base < 0) {
>> + ret = adc->irq_base;
>> + dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);
>> + goto err_free;
>> + }
>> +
>> + mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> + if (!mem_base) {
>> + ret = -ENOENT;
>> + dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
>> + goto err_free;
>> + }
>> +
>> + /* Only request the shared registers for the MFD driver */
>> + adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
>> + pdev->name);
>> + if (!adc->mem) {
>> + ret = -EBUSY;
>> + dev_err(&pdev->dev, "Failed to request mmio memory region\n");
>> + goto err_free;
>> + }
>> +
>> + adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
>> + if (!adc->base) {
>> + ret = -EBUSY;
>> + dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
>> + goto err_release_mem_region;
>> + }
>> +
>> + adc->clk = clk_get(&pdev->dev, "adc");
>> + if (IS_ERR(adc->clk)) {
>> + ret = PTR_ERR(adc->clk);
>> + dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
>> + goto err_iounmap;
>> + }
>> +
>> + spin_lock_init(&adc->lock);
>> +
>> + adc->clk_ref = 0;
>> +
>> + platform_set_drvdata(pdev, adc);
>> +
>> + for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) {
>> + set_irq_chip_data(irq, adc);
>> + set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip,
>> + handle_level_irq);
>> + }
>> +
>> + set_irq_data(adc->irq, adc);
>> + set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux);
>> +
>> + writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
>> + writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
>> +
>> + mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
>> + ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base);
>> +
>> + return 0;
> Please return the mfd_add_devices return value.
>
> Cheers,
> Samuel.
>

Thanks for reviewing the patch

- Lars

2010-07-05 15:53:30

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver

On Mon, Jul 05, 2010 at 05:43:31PM +0200, Lars-Peter Clausen wrote:
> >> +static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
> >> +{
> >> + unsigned long flags;
> >> +
> >> + spin_lock_irqsave(&adc->lock, flags);
> >> + if (adc->clk_ref++ == 0)
> >> + clk_enable(adc->clk);
> >> + spin_unlock_irqrestore(&adc->lock, flags);
> >> +}
> > I'm not familiar with your platform clock framework, but shouldn't the
> > refcounting be handled there instead of spread over all your drivers ?
>
> The ADC clock is the only clock on this platform which is shared between multiple
> devices so I refrained from adding the refcounting to the core for now. But to be
> strictly complaint with the clock API as defined in linux/clk.h the implementation
> should do refcounting. I'm still a bit uncertain what would be done best here.
>
I can't see what leaving the refcount handling to drivers could bring compared
to a centralized implementation. But that's your platform, either way is fine
with me as far as this MFD driver is concerned.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2010-07-07 23:42:32

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver

On Mon, 05 Jul 2010 00:27:25 +0200
Lars-Peter Clausen <[email protected]> wrote:

> Hi Andrew
>
> v2 of this patch has been around for two weeks without any comments. I've fixed all
> the issues you had with v1. If you this version is ok, an Acked-By would be nice :)

I don't do acks - I already have enough to be blamed for. I'd merge it
if asked to though. Although I might whine about inappropriate use of
ENOENT, which means "No such file or directory".

2010-07-08 06:11:01

by Artem Bityutskiy

[permalink] [raw]
Subject: Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver

On Sat, 2010-06-19 at 07:08 +0200, Lars-Peter Clausen wrote:
> diff --git a/include/linux/mtd/jz4740_nand.h b/include/linux/mtd/jz4740_nand.h
> new file mode 100644
> index 0000000..379f9b6
> --- /dev/null
> +++ b/include/linux/mtd/jz4740_nand.h
> @@ -0,0 +1,34 @@
> +/*
> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
> + * JZ4740 SoC NAND 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.
> + *
> + */
> +
> +#ifndef __JZ_NAND_H__
> +#define __JZ_NAND_H__
> +
> +#include <linux/mtd/nand.h>
> +#include <linux/mtd/partitions.h>
> +
> +struct jz_nand_platform_data {
> + int num_partitions;
> + struct mtd_partition *partitions;
> +
> + struct nand_ecclayout *ecc_layout;
> +
> + unsigned int busy_gpio;
> +
> + void (*ident_callback)(struct platform_device *, struct nand_chip *,
> + struct mtd_partition **, int *num_partitions);
> +};
> +
> +#endif

Do you really have to add your platform data strucutre to
"inlculde/mtd" ? That is quite global namespace, and ideally only things
like user-space interface and "public" interface of the MTD subsystem
should live there.

Can you keep this somewhere in mips architecture directory?
--
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

2010-07-08 13:20:35

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver

Artem Bityutskiy wrote:
> On Sat, 2010-06-19 at 07:08 +0200, Lars-Peter Clausen wrote:
>> diff --git a/include/linux/mtd/jz4740_nand.h b/include/linux/mtd/jz4740_nand.h
>> new file mode 100644
>> index 0000000..379f9b6
>> --- /dev/null
>> +++ b/include/linux/mtd/jz4740_nand.h
>> @@ -0,0 +1,34 @@
>> +/*
>> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>> + * JZ4740 SoC NAND 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.
>> + *
>> + */
>> +
>> +#ifndef __JZ_NAND_H__
>> +#define __JZ_NAND_H__
>> +
>> +#include <linux/mtd/nand.h>
>> +#include <linux/mtd/partitions.h>
>> +
>> +struct jz_nand_platform_data {
>> + int num_partitions;
>> + struct mtd_partition *partitions;
>> +
>> + struct nand_ecclayout *ecc_layout;
>> +
>> + unsigned int busy_gpio;
>> +
>> + void (*ident_callback)(struct platform_device *, struct nand_chip *,
>> + struct mtd_partition **, int *num_partitions);
>> +};
>> +
>> +#endif
>
> Do you really have to add your platform data strucutre to
> "inlculde/mtd" ? That is quite global namespace, and ideally only things
> like user-space interface and "public" interface of the MTD subsystem
> should live there.
>
> Can you keep this somewhere in mips architecture directory?

Hi

Hm, ok, I see. I'll move it to arch/mips/include/asm/mach-jz4740/ then.
But I guess I should move the headers for all the other jz4740 driver to the same
directory as well.

On the other hand I'm wondering where on would put headers for non platform specific
drivers?

- Lars

2010-07-08 13:23:57

by Artem Bityutskiy

[permalink] [raw]
Subject: Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver

On Thu, 2010-07-08 at 15:20 +0200, Lars-Peter Clausen wrote:
> On the other hand I'm wondering where on would put headers for non platform specific
> drivers?

If we are talking about MTD, then drivers/mtd ?

--
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

2010-07-08 13:28:31

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver

Andrew Morton wrote:
> On Mon, 05 Jul 2010 00:27:25 +0200
> Lars-Peter Clausen <[email protected]> wrote:
>
>> Hi Andrew
>>
>> v2 of this patch has been around for two weeks without any comments. I've fixed all
>> the issues you had with v1. If you this version is ok, an Acked-By would be nice :)
>
> I don't do acks - I already have enough to be blamed for.
Hm, ok.

> Although I might whine about inappropriate use of
> ENOENT, which means "No such file or directory".
>

That is due to to many bad examples, I guess. Would using ENXIO instead keep you from
whining?

- Lars

2010-07-08 14:02:42

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver

Artem Bityutskiy wrote:
> On Thu, 2010-07-08 at 15:20 +0200, Lars-Peter Clausen wrote:
>> On the other hand I'm wondering where on would put headers for non platform specific
>> drivers?
>
> If we are talking about MTD, then drivers/mtd ?
>
No, what I meant was header defining platform data structs and such.
And what I wanted to get at is an answer to why driver header files are put in
different directories while the driver files themselves are all keep in the same
directory. (drivers of the same subsystem that is)

- Lars

2010-07-08 14:19:15

by Artem Bityutskiy

[permalink] [raw]
Subject: Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver

On Thu, 2010-07-08 at 16:02 +0200, Lars-Peter Clausen wrote:
> Artem Bityutskiy wrote:
> > On Thu, 2010-07-08 at 15:20 +0200, Lars-Peter Clausen wrote:
> >> On the other hand I'm wondering where on would put headers for non platform specific
> >> drivers?
> >
> > If we are talking about MTD, then drivers/mtd ?
> >
> No, what I meant was header defining platform data structs and such.
> And what I wanted to get at is an answer to why driver header files are put in
> different directories while the driver files themselves are all keep in the same
> directory. (drivers of the same subsystem that is)

To be frank I do not know, I did not look at the whole picture, just at
the MTD part :-)

--
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

2010-07-08 16:47:26

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver

On Thu, 08 Jul 2010 15:28:04 +0200 Lars-Peter Clausen <[email protected]> wrote:

> > Although I might whine about inappropriate use of
> > ENOENT, which means "No such file or directory".
> >
>
> That is due to to many bad examples, I guess. Would using ENXIO instead keep
> you from whining?

Sounds a bit better. It's hardly a huge issue though.

2010-07-09 01:26:43

by Jaya Kumar

[permalink] [raw]
Subject: Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver

On Mon, Jul 5, 2010 at 6:27 AM, Lars-Peter Clausen <[email protected]> wrote:
> Hi Andrew
>
> v2 of this patch has been around for two weeks without any comments. I've fixed all
> the issues you had with v1. If you this version is ok, an Acked-By would be nice :)
>
> Thanks
> - Lars
>
> Lars-Peter Clausen wrote:
>> This patch adds support for the LCD controller on JZ4740 SoCs.

Hi Lars, Andrew,

I think this driver looks okay but I had some quick questions:

>> +config FB_JZ4740
>> + ? ? tristate "JZ4740 LCD framebuffer support"
>> + ? ? depends on FB
>> + ? ? select FB_SYS_FILLRECT
>> + ? ? select FB_SYS_COPYAREA
>> + ? ? select FB_SYS_IMAGEBLIT

Out of curiosity, why FB_SYS and not FB_CFB? The users of FB_SYS are
drivers that use virtual memory for their framebuffer. I didn't see
any vmalloc in this driver.

>> + ? ? help
>> + ? ? ? Framebuffer support for the JZ4740 SoC.
>> +
>> ?source "drivers/video/omap/Kconfig"
>> ?source "drivers/video/omap2/Kconfig"
>>
>> diff --git a/drivers/video/Makefile b/drivers/video/Makefile
>> index 3c3bf86..fd2df57 100644
>> --- a/drivers/video/Makefile
>> +++ b/drivers/video/Makefile
>> @@ -132,6 +132,7 @@ obj-$(CONFIG_FB_CARMINE) ? ? ? ? ?+= carminefb.o
>> ?obj-$(CONFIG_FB_MB862XX) ? ? ? += mb862xx/
>> ?obj-$(CONFIG_FB_MSM) ? ? ? ? ? ? ?+= msm/
>> ?obj-$(CONFIG_FB_NUC900) ? ? ? ? ? += nuc900fb.o
>> +obj-$(CONFIG_FB_JZ4740) ? ? ? ? ? ? ? ?+= jz4740_fb.o

Just a minor nit, the typical naming in fbdev appears to be
controllerfb.c as opposed to controller_fb.c .

>>
>> ?# Platform or fallback drivers go here
>> ?obj-$(CONFIG_FB_UVESA) ? ? ? ? ? ?+= uvesafb.o
>> diff --git a/drivers/video/jz4740_fb.c b/drivers/video/jz4740_fb.c
>> new file mode 100644
>> index 0000000..8d03181
>> --- /dev/null
>> +++ b/drivers/video/jz4740_fb.c
>> @@ -0,0 +1,817 @@
>> +/*
>> + * ?Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>> + * ? JZ4740 SoC LCD framebuffer 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/kernel.h>
>> +#include <linux/module.h>
>> +#include <linux/mutex.h>
>> +#include <linux/platform_device.h>
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +
>> +#include <linux/console.h>
>> +#include <linux/fb.h>
>> +
>> +#include <linux/dma-mapping.h>
>> +
>> +#include <linux/jz4740_fb.h>

I see a lot of register defines below. Is there any reason why they
shouldn't go in above j*fb.h? Also, I'm not sure if linux/j*.h is the
best place. I think most fbdev drivers place their headers in
drivers/video itself unless they need to share something driver
specific with userspace which is rare. Is there a specific reason why
you put it in include/linux?


>> +#include <asm/mach-jz4740/gpio.h>
>> +
>> +#define JZ_REG_LCD_CFG ? ? ? ? ? ? ? 0x00
>> +#define JZ_REG_LCD_VSYNC ? ? 0x04
>> +#define JZ_REG_LCD_HSYNC ? ? 0x08
>> +#define JZ_REG_LCD_VAT ? ? ? ? ? ? ? 0x0C
>> +#define JZ_REG_LCD_DAH ? ? ? ? ? ? ? 0x10
>> +#define JZ_REG_LCD_DAV ? ? ? ? ? ? ? 0x14
>> +#define JZ_REG_LCD_PS ? ? ? ? ? ? ? ?0x18
>> +#define JZ_REG_LCD_CLS ? ? ? ? ? ? ? 0x1C
>> +#define JZ_REG_LCD_SPL ? ? ? ? ? ? ? 0x20
>> +#define JZ_REG_LCD_REV ? ? ? ? ? ? ? 0x24
>> +#define JZ_REG_LCD_CTRL ? ? ? ? ? ? ?0x30
>> +#define JZ_REG_LCD_STATE ? ? 0x34
>> +#define JZ_REG_LCD_IID ? ? ? ? ? ? ? 0x38
>> +#define JZ_REG_LCD_DA0 ? ? ? ? ? ? ? 0x40
>> +#define JZ_REG_LCD_SA0 ? ? ? ? ? ? ? 0x44
>> +#define JZ_REG_LCD_FID0 ? ? ? ? ? ? ?0x48
>> +#define JZ_REG_LCD_CMD0 ? ? ? ? ? ? ?0x4C
>> +#define JZ_REG_LCD_DA1 ? ? ? ? ? ? ? 0x50
>> +#define JZ_REG_LCD_SA1 ? ? ? ? ? ? ? 0x54
>> +#define JZ_REG_LCD_FID1 ? ? ? ? ? ? ?0x58
>> +#define JZ_REG_LCD_CMD1 ? ? ? ? ? ? ?0x5C
>> +
>> +#define JZ_LCD_CFG_SLCD ? ? ? ? ? ? ? ? ? ? ?BIT(31)
>> +#define JZ_LCD_CFG_PS_DISABLE ? ? ? ? ? ? ? ?BIT(23)
>> +#define JZ_LCD_CFG_CLS_DISABLE ? ? ? ? ? ? ? BIT(22)
>> +#define JZ_LCD_CFG_SPL_DISABLE ? ? ? ? ? ? ? BIT(21)
>> +#define JZ_LCD_CFG_REV_DISABLE ? ? ? ? ? ? ? BIT(20)
>> +#define JZ_LCD_CFG_HSYNCM ? ? ? ? ? ?BIT(19)
>> +#define JZ_LCD_CFG_PCLKM ? ? ? ? ? ? BIT(18)
>> +#define JZ_LCD_CFG_INV ? ? ? ? ? ? ? ? ? ? ? BIT(17)
>> +#define JZ_LCD_CFG_SYNC_DIR ? ? ? ? ?BIT(16)
>> +#define JZ_LCD_CFG_PS_POLARITY ? ? ? ? ? ? ? BIT(15)
>> +#define JZ_LCD_CFG_CLS_POLARITY ? ? ? ? ? ? ?BIT(14)
>> +#define JZ_LCD_CFG_SPL_POLARITY ? ? ? ? ? ? ?BIT(13)
>> +#define JZ_LCD_CFG_REV_POLARITY ? ? ? ? ? ? ?BIT(12)
>> +#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW ?BIT(11)
>> +#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10)
>> +#define JZ_LCD_CFG_DE_ACTIVE_LOW ? ? BIT(9)
>> +#define JZ_LCD_CFG_VSYNC_ACTIVE_LOW ?BIT(8)
>> +#define JZ_LCD_CFG_18_BIT ? ? ? ? ? ?BIT(7)
>> +#define JZ_LCD_CFG_PDW ? ? ? ? ? ? ? ? ? ? ? (BIT(5) | BIT(4))
>> +#define JZ_LCD_CFG_MODE_MASK 0xf
>> +
>> +#define JZ_LCD_CTRL_BURST_4 ? ? ? ? ?(0x0 << 28)
>> +#define JZ_LCD_CTRL_BURST_8 ? ? ? ? ?(0x1 << 28)
>> +#define JZ_LCD_CTRL_BURST_16 ? ? ? ? (0x2 << 28)
>> +#define JZ_LCD_CTRL_RGB555 ? ? ? ? ? BIT(27)
>> +#define JZ_LCD_CTRL_OFUP ? ? ? ? ? ? BIT(26)
>> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24)
>> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_4 ?(0x1 << 24)
>> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_2 ?(0x2 << 24)
>> +#define JZ_LCD_CTRL_PDD_MASK ? ? ? ? (0xff << 16)
>> +#define JZ_LCD_CTRL_EOF_IRQ ? ? ? ? ?BIT(13)
>> +#define JZ_LCD_CTRL_SOF_IRQ ? ? ? ? ?BIT(12)
>> +#define JZ_LCD_CTRL_OFU_IRQ ? ? ? ? ?BIT(11)
>> +#define JZ_LCD_CTRL_IFU0_IRQ ? ? ? ? BIT(10)
>> +#define JZ_LCD_CTRL_IFU1_IRQ ? ? ? ? BIT(9)
>> +#define JZ_LCD_CTRL_DD_IRQ ? ? ? ? ? BIT(8)
>> +#define JZ_LCD_CTRL_QDD_IRQ ? ? ? ? ?BIT(7)
>> +#define JZ_LCD_CTRL_REVERSE_ENDIAN ? BIT(6)
>> +#define JZ_LCD_CTRL_LSB_FISRT ? ? ? ? ? ? ? ?BIT(5)
>> +#define JZ_LCD_CTRL_DISABLE ? ? ? ? ?BIT(4)
>> +#define JZ_LCD_CTRL_ENABLE ? ? ? ? ? BIT(3)
>> +#define JZ_LCD_CTRL_BPP_1 ? ? ? ? ? ?0x0
>> +#define JZ_LCD_CTRL_BPP_2 ? ? ? ? ? ?0x1
>> +#define JZ_LCD_CTRL_BPP_4 ? ? ? ? ? ?0x2
>> +#define JZ_LCD_CTRL_BPP_8 ? ? ? ? ? ?0x3
>> +#define JZ_LCD_CTRL_BPP_15_16 ? ? ? ? ? ? ? ?0x4
>> +#define JZ_LCD_CTRL_BPP_18_24 ? ? ? ? ? ? ? ?0x5
>> +
>> +#define JZ_LCD_CMD_SOF_IRQ BIT(15)
>> +#define JZ_LCD_CMD_EOF_IRQ BIT(16)
>> +#define JZ_LCD_CMD_ENABLE_PAL BIT(12)
>> +
>> +#define JZ_LCD_SYNC_MASK 0x3ff
>> +
>> +#define JZ_LCD_STATE_DISABLED BIT(0)
>> +
>> +struct jzfb_framedesc {
>> + ? ? uint32_t next;
>> + ? ? uint32_t addr;
>> + ? ? uint32_t id;
>> + ? ? uint32_t cmd;
>> +} __packed;
>> +
>> +struct jzfb {
>> + ? ? struct fb_info *fb;
>> + ? ? struct platform_device *pdev;
>> + ? ? void __iomem *base;
>> + ? ? struct resource *mem;
>> + ? ? struct jz4740_fb_platform_data *pdata;
>> +
>> + ? ? size_t vidmem_size;
>> + ? ? void *vidmem;
>> + ? ? dma_addr_t vidmem_phys;
>> + ? ? struct jzfb_framedesc *framedesc;
>> + ? ? dma_addr_t framedesc_phys;
>> +
>> + ? ? struct clk *ldclk;
>> + ? ? struct clk *lpclk;
>> +
>> + ? ? unsigned is_enabled:1;

Out of curiosity, why the bitfield?

>> + ? ? struct mutex lock;
>> +
>> + ? ? uint32_t pseudo_palette[16];
>> +};
>> +
>> +static const struct fb_fix_screeninfo jzfb_fix __devinitdata = {
>> + ? ? .id ? ? ? ? ? ? = "JZ4740 FB",
>> + ? ? .type ? ? ? ? ? = FB_TYPE_PACKED_PIXELS,
>> + ? ? .visual ? ? ? ? = FB_VISUAL_TRUECOLOR,
>> + ? ? .xpanstep ? ? ? = 0,
>> + ? ? .ypanstep ? ? ? = 0,
>> + ? ? .ywrapstep ? ? ?= 0,
>> + ? ? .accel ? ? ? ? ?= FB_ACCEL_NONE,
>> +};
>> +
>> +static const struct jz_gpio_bulk_request jz_lcd_ctrl_pins[] = {
>> + ? ? JZ_GPIO_BULK_PIN(LCD_PCLK),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_HSYNC),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_VSYNC),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DE),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_PS),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_REV),
>> +};
>> +
>> +static const struct jz_gpio_bulk_request jz_lcd_data_pins[] = {
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA0),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA1),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA2),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA3),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA4),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA5),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA6),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA7),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA8),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA9),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA10),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA11),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA12),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA13),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA14),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA15),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA16),
>> + ? ? JZ_GPIO_BULK_PIN(LCD_DATA17),
>> +};
>> +
>> +static unsigned int jzfb_num_ctrl_pins(struct jzfb *jzfb)
>> +{
>> + ? ? unsigned int num;
>> +
>> + ? ? switch (jzfb->pdata->lcd_type) {
>> + ? ? case JZ_LCD_TYPE_GENERIC_16_BIT:
>> + ? ? ? ? ? ? num = 4;
>> + ? ? ? ? ? ? break;
>> + ? ? case JZ_LCD_TYPE_GENERIC_18_BIT:
>> + ? ? ? ? ? ? num = 4;
>> + ? ? ? ? ? ? break;
>> + ? ? case JZ_LCD_TYPE_8BIT_SERIAL:
>> + ? ? ? ? ? ? num = 3;
>> + ? ? ? ? ? ? break;

The lcd type serial looks interesting to me. It'd be nice to have some
descriptive comments so that we know what its used with.

>> + ? ? default:
>> + ? ? ? ? ? ? num = 0;
>> + ? ? ? ? ? ? break;
>> + ? ? }
>> + ? ? return num;
>> +}
>> +
>> +static unsigned int jzfb_num_data_pins(struct jzfb *jzfb)
>> +{
>> + ? ? unsigned int num;
>> +
>> + ? ? switch (jzfb->pdata->lcd_type) {
>> + ? ? case JZ_LCD_TYPE_GENERIC_16_BIT:
>> + ? ? ? ? ? ? num = 16;
>> + ? ? ? ? ? ? break;
>> + ? ? case JZ_LCD_TYPE_GENERIC_18_BIT:
>> + ? ? ? ? ? ? num = 18;
>> + ? ? ? ? ? ? break;
>> + ? ? case JZ_LCD_TYPE_8BIT_SERIAL:
>> + ? ? ? ? ? ? num = 8;
>> + ? ? ? ? ? ? break;
>> + ? ? default:
>> + ? ? ? ? ? ? num = 0;
>> + ? ? ? ? ? ? break;
>> + ? ? }
>> + ? ? return num;
>> +}
>> +
>> +/* Based on CNVT_TOHW macro from skeletonfb.c */
>> +static inline uint32_t jzfb_convert_color_to_hw(unsigned val,
>> + ? ? struct fb_bitfield *bf)
>> +{
>> + ? ? return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset;
>> +}
>> +
>> +static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green,
>> + ? ? ? ? ? ? ? ? ? ? unsigned blue, unsigned transp, struct fb_info *fb)
>> +{
>> + ? ? uint32_t color;
>> +
>> + ? ? if (regno >= 16)
>> + ? ? ? ? ? ? return -EINVAL;
>> +
>> + ? ? color = jzfb_convert_color_to_hw(red, &fb->var.red);
>> + ? ? color |= jzfb_convert_color_to_hw(green, &fb->var.green);
>> + ? ? color |= jzfb_convert_color_to_hw(blue, &fb->var.blue);
>> + ? ? color |= jzfb_convert_color_to_hw(transp, &fb->var.transp);
>> +
>> + ? ? ((uint32_t *)(fb->pseudo_palette))[regno] = color;
>> +
>> + ? ? return 0;
>> +}
>> +
>> +static int jzfb_get_controller_bpp(struct jzfb *jzfb)
>> +{
>> + ? ? switch (jzfb->pdata->bpp) {
>> + ? ? case 18:
>> + ? ? case 24:
>> + ? ? ? ? ? ? return 32;
>> + ? ? case 15:
>> + ? ? ? ? ? ? return 16;
>> + ? ? default:
>> + ? ? ? ? ? ? return jzfb->pdata->bpp;
>> + ? ? }
>> +}
>> +
>> +static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb,
>> + ? ? struct fb_var_screeninfo *var)
>> +{
>> + ? ? size_t i;
>> + ? ? struct fb_videomode *mode = jzfb->pdata->modes;
>> +
>> + ? ? for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) {
>> + ? ? ? ? ? ? if (mode->xres == var->xres && mode->yres == var->yres)
>> + ? ? ? ? ? ? ? ? ? ? return mode;
>> + ? ? }
>> +
>> + ? ? return NULL;
>> +}
>> +
>> +static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
>> +{
>> + ? ? struct jzfb *jzfb = fb->par;
>> + ? ? struct fb_videomode *mode;
>> +
>> + ? ? if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) &&
>> + ? ? ? ? ? ? var->bits_per_pixel != jzfb->pdata->bpp)
>> + ? ? ? ? ? ? return -EINVAL;
>> +
>> + ? ? mode = jzfb_get_mode(jzfb, var);
>> + ? ? if (mode == NULL)
>> + ? ? ? ? ? ? return -EINVAL;
>> +
>> + ? ? fb_videomode_to_var(var, mode);
>> +
>> + ? ? switch (jzfb->pdata->bpp) {
>> + ? ? case 8:
>> + ? ? ? ? ? ? break;
>> + ? ? case 15:
>> + ? ? ? ? ? ? var->red.offset = 10;
>> + ? ? ? ? ? ? var->red.length = 5;
>> + ? ? ? ? ? ? var->green.offset = 6;
>> + ? ? ? ? ? ? var->green.length = 5;
>> + ? ? ? ? ? ? var->blue.offset = 0;
>> + ? ? ? ? ? ? var->blue.length = 5;
>> + ? ? ? ? ? ? break;
>> + ? ? case 16:
>> + ? ? ? ? ? ? var->red.offset = 11;
>> + ? ? ? ? ? ? var->red.length = 5;
>> + ? ? ? ? ? ? var->green.offset = 5;
>> + ? ? ? ? ? ? var->green.length = 6;
>> + ? ? ? ? ? ? var->blue.offset = 0;
>> + ? ? ? ? ? ? var->blue.length = 5;
>> + ? ? ? ? ? ? break;
>> + ? ? case 18:
>> + ? ? ? ? ? ? var->red.offset = 16;
>> + ? ? ? ? ? ? var->red.length = 6;
>> + ? ? ? ? ? ? var->green.offset = 8;
>> + ? ? ? ? ? ? var->green.length = 6;
>> + ? ? ? ? ? ? var->blue.offset = 0;
>> + ? ? ? ? ? ? var->blue.length = 6;
>> + ? ? ? ? ? ? var->bits_per_pixel = 32;
>> + ? ? ? ? ? ? break;
>> + ? ? case 32:
>> + ? ? case 24:
>> + ? ? ? ? ? ? var->transp.offset = 24;
>> + ? ? ? ? ? ? var->transp.length = 8;
>> + ? ? ? ? ? ? var->red.offset = 16;
>> + ? ? ? ? ? ? var->red.length = 8;
>> + ? ? ? ? ? ? var->green.offset = 8;
>> + ? ? ? ? ? ? var->green.length = 8;
>> + ? ? ? ? ? ? var->blue.offset = 0;
>> + ? ? ? ? ? ? var->blue.length = 8;
>> + ? ? ? ? ? ? var->bits_per_pixel = 32;
>> + ? ? ? ? ? ? break;
>> + ? ? default:
>> + ? ? ? ? ? ? break;
>> + ? ? }
>> +
>> + ? ? return 0;
>> +}
>> +
>> +static int jzfb_set_par(struct fb_info *info)
>> +{
>> + ? ? struct jzfb *jzfb = info->par;
>> + ? ? struct fb_var_screeninfo *var = &info->var;
>> + ? ? struct fb_videomode *mode;
>> + ? ? uint16_t hds, vds;
>> + ? ? uint16_t hde, vde;
>> + ? ? uint16_t ht, vt;
>> + ? ? uint32_t ctrl;
>> + ? ? uint32_t cfg;
>> + ? ? unsigned long rate;
>> +
>> + ? ? mode = jzfb_get_mode(jzfb, var);
>> + ? ? if (mode == NULL)
>> + ? ? ? ? ? ? return -EINVAL;
>> +
>> + ? ? if (mode == info->mode)
>> + ? ? ? ? ? ? return 0;
>> +
>> + ? ? info->mode = mode;
>> +
>> + ? ? hds = mode->hsync_len + mode->left_margin;
>> + ? ? hde = hds + mode->xres;
>> + ? ? ht = hde + mode->right_margin;
>> +
>> + ? ? vds = mode->vsync_len + mode->upper_margin;
>> + ? ? vde = vds + mode->yres;
>> + ? ? vt = vde + mode->lower_margin;
>> +
>> + ? ? ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16;
>> +
>> + ? ? switch (jzfb->pdata->bpp) {
>> + ? ? case 1:
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_BPP_1;
>> + ? ? ? ? ? ? break;
>> + ? ? case 2:
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_BPP_2;
>> + ? ? ? ? ? ? break;
>> + ? ? case 4:
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_BPP_4;
>> + ? ? ? ? ? ? break;
>> + ? ? case 8:
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_BPP_8;
>> + ? ? break;
>> + ? ? case 15:
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */
>> + ? ? case 16:
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_BPP_15_16;
>> + ? ? ? ? ? ? break;
>> + ? ? case 18:
>> + ? ? case 24:
>> + ? ? case 32:
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_BPP_18_24;
>> + ? ? ? ? ? ? break;
>> + ? ? default:
>> + ? ? ? ? ? ? break;
>> + ? ? }
>> +
>> + ? ? cfg = JZ_LCD_CFG_PS_DISABLE | JZ_LCD_CFG_CLS_DISABLE |
>> + ? ? ? ? ? ? JZ_LCD_CFG_SPL_DISABLE | JZ_LCD_CFG_REV_DISABLE;
>> +
>> + ? ? if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
>> + ? ? ? ? ? ? cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
>> +
>> + ? ? if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
>> + ? ? ? ? ? ? cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW;
>> +
>> + ? ? if (jzfb->pdata->pixclk_falling_edge)
>> + ? ? ? ? ? ? cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
>> +
>> + ? ? if (jzfb->pdata->date_enable_active_low)
>> + ? ? ? ? ? ? cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW;
>> +
>> + ? ? if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT)
>> + ? ? ? ? ? ? cfg |= JZ_LCD_CFG_18_BIT;
>> +
>> + ? ? cfg |= jzfb->pdata->lcd_type & 0xf;
>> +
>> + ? ? if (mode->pixclock) {
>> + ? ? ? ? ? ? rate = PICOS2KHZ(mode->pixclock) * 1000;
>> + ? ? ? ? ? ? mode->refresh = rate / vt / ht;
>> + ? ? } else {
>> + ? ? ? ? ? ? if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL)
>> + ? ? ? ? ? ? ? ? ? ? rate = mode->refresh * (vt + 2 * mode->xres) * ht;
>> + ? ? ? ? ? ? else
>> + ? ? ? ? ? ? ? ? ? ? rate = mode->refresh * vt * ht;
>> +
>> + ? ? ? ? ? ? mode->pixclock = KHZ2PICOS(rate / 1000);
>> + ? ? }
>> +
>> + ? ? mutex_lock(&jzfb->lock);
>> + ? ? if (!jzfb->is_enabled)
>> + ? ? ? ? ? ? clk_enable(jzfb->ldclk);
>> + ? ? else
>> + ? ? ? ? ? ? ctrl |= JZ_LCD_CTRL_ENABLE;
>> +
>> + ? ? writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC);
>> + ? ? writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC);
>> +
>> + ? ? writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT);
>> +
>> + ? ? writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH);
>> + ? ? writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV);
>> +
>> + ? ? writel(cfg, jzfb->base + JZ_REG_LCD_CFG);
>> +
>> + ? ? writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
>> +
>> + ? ? if (!jzfb->is_enabled)
>> + ? ? ? ? ? ? clk_disable(jzfb->ldclk);
>> +
>> + ? ? mutex_unlock(&jzfb->lock);
>> +
>> + ? ? clk_set_rate(jzfb->lpclk, rate);
>> + ? ? clk_set_rate(jzfb->ldclk, rate * 3);
>> +
>> + ? ? return 0;
>> +}
>> +
>> +static void jzfb_enable(struct jzfb *jzfb)
>> +{
>> + ? ? uint32_t ctrl;
>> +
>> + ? ? clk_enable(jzfb->ldclk);
>> +
>> + ? ? jz_gpio_bulk_resume(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
>> + ? ? jz_gpio_bulk_resume(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
>> +
>> + ? ? writel(0, jzfb->base + JZ_REG_LCD_STATE);
>> +
>> + ? ? writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
>> +
>> + ? ? ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
>> + ? ? ctrl |= JZ_LCD_CTRL_ENABLE;
>> + ? ? ctrl &= ~JZ_LCD_CTRL_DISABLE;
>> + ? ? writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
>> +}
>> +
>> +static void jzfb_disable(struct jzfb *jzfb)
>> +{
>> + ? ? uint32_t ctrl;
>> +
>> + ? ? ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
>> + ? ? ctrl |= JZ_LCD_CTRL_DISABLE;
>> + ? ? writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
>> + ? ? do {
>> + ? ? ? ? ? ? ctrl = readl(jzfb->base + JZ_REG_LCD_STATE);
>> + ? ? } while (!(ctrl & JZ_LCD_STATE_DISABLED));
>> +
>> + ? ? jz_gpio_bulk_suspend(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
>> + ? ? jz_gpio_bulk_suspend(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
>> +
>> + ? ? clk_disable(jzfb->ldclk);
>> +}
>> +
>> +static int jzfb_blank(int blank_mode, struct fb_info *info)
>> +{
>> + ? ? struct jzfb *jzfb = info->par;
>> +
>> + ? ? switch (blank_mode) {
>> + ? ? case FB_BLANK_UNBLANK:
>> + ? ? ? ? ? ? mutex_lock(&jzfb->lock);
>> + ? ? ? ? ? ? if (jzfb->is_enabled) {
>> + ? ? ? ? ? ? ? ? ? ? mutex_unlock(&jzfb->lock);
>> + ? ? ? ? ? ? ? ? ? ? return 0;
>> + ? ? ? ? ? ? }
>> +
>> + ? ? ? ? ? ? jzfb_enable(jzfb);
>> + ? ? ? ? ? ? jzfb->is_enabled = 1;
>> +
>> + ? ? ? ? ? ? mutex_unlock(&jzfb->lock);
>> + ? ? ? ? ? ? break;
>> + ? ? default:
>> + ? ? ? ? ? ? mutex_lock(&jzfb->lock);
>> + ? ? ? ? ? ? if (!jzfb->is_enabled) {
>> + ? ? ? ? ? ? ? ? ? ? mutex_unlock(&jzfb->lock);
>> + ? ? ? ? ? ? ? ? ? ? return 0;
>> + ? ? ? ? ? ? }
>> +
>> + ? ? ? ? ? ? jzfb_disable(jzfb);
>> + ? ? ? ? ? ? jzfb->is_enabled = 0;
>> +
>> + ? ? ? ? ? ? mutex_unlock(&jzfb->lock);
>> + ? ? ? ? ? ? break;
>> + ? ? }
>> +
>> + ? ? return 0;
>> +}
>> +
>> +static int jzfb_alloc_devmem(struct jzfb *jzfb)
>> +{
>> + ? ? int max_videosize = 0;
>> + ? ? struct fb_videomode *mode = jzfb->pdata->modes;
>> + ? ? void *page;
>> + ? ? int i;
>> +
>> + ? ? for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) {
>> + ? ? ? ? ? ? if (max_videosize < mode->xres * mode->yres)
>> + ? ? ? ? ? ? ? ? ? ? max_videosize = mode->xres * mode->yres;
>> + ? ? }
>> +
>> + ? ? max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3;
>> +
>> + ? ? jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? sizeof(*jzfb->framedesc),
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &jzfb->framedesc_phys, GFP_KERNEL);
>> +
>> + ? ? if (!jzfb->framedesc)
>> + ? ? ? ? ? ? return -ENOMEM;
>> +
>> + ? ? jzfb->vidmem_size = PAGE_ALIGN(max_videosize);
>> + ? ? jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? jzfb->vidmem_size,
>> + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? &jzfb->vidmem_phys, GFP_KERNEL);
>> +
>> + ? ? if (!jzfb->vidmem)
>> + ? ? ? ? ? ? goto err_free_framedesc;
>> +
>> + ? ? for (page = jzfb->vidmem;
>> + ? ? ? ? ? ? ?page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size);
>> + ? ? ? ? ? ? ?page += PAGE_SIZE) {
>> + ? ? ? ? ? ? SetPageReserved(virt_to_page(page));
>> + ? ? }

It'd be nice to know the reasoning for the SetPageReserved.

Thanks,
jaya

2010-07-09 15:32:26

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Jaya Kumar wrote:
> On Mon, Jul 5, 2010 at 6:27 AM, Lars-Peter Clausen <[email protected]> wrote:
>> Hi Andrew
>>
>> v2 of this patch has been around for two weeks without any comments. I've fixed all
>> the issues you had with v1. If you this version is ok, an Acked-By would be nice :)
>>
>> Thanks
>> - Lars
>>
>> Lars-Peter Clausen wrote:
>>> This patch adds support for the LCD controller on JZ4740 SoCs.
>
> Hi Lars, Andrew,
>
> I think this driver looks okay but I had some quick questions:
>
>>> +config FB_JZ4740
>>> + tristate "JZ4740 LCD framebuffer support"
>>> + depends on FB
>>> + select FB_SYS_FILLRECT
>>> + select FB_SYS_COPYAREA
>>> + select FB_SYS_IMAGEBLIT
>
> Out of curiosity, why FB_SYS and not FB_CFB? The users of FB_SYS are
> drivers that use virtual memory for their framebuffer. I didn't see
> any vmalloc in this driver.

The framebuffer is allocated with dma_alloc_coherent and thus lives in system memory.

>
>>> + help
>>> + Framebuffer support for the JZ4740 SoC.
>>> +
>>> source "drivers/video/omap/Kconfig"
>>> source "drivers/video/omap2/Kconfig"
>>>
>>> diff --git a/drivers/video/Makefile b/drivers/video/Makefile
>>> index 3c3bf86..fd2df57 100644
>>> --- a/drivers/video/Makefile
>>> +++ b/drivers/video/Makefile
>>> @@ -132,6 +132,7 @@ obj-$(CONFIG_FB_CARMINE) += carminefb.o
>>> obj-$(CONFIG_FB_MB862XX) += mb862xx/
>>> obj-$(CONFIG_FB_MSM) += msm/
>>> obj-$(CONFIG_FB_NUC900) += nuc900fb.o
>>> +obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o
>
> Just a minor nit, the typical naming in fbdev appears to be
> controllerfb.c as opposed to controller_fb.c .
>
>>> # Platform or fallback drivers go here
>>> obj-$(CONFIG_FB_UVESA) += uvesafb.o
>>> diff --git a/drivers/video/jz4740_fb.c b/drivers/video/jz4740_fb.c
>>> new file mode 100644
>>> index 0000000..8d03181
>>> --- /dev/null
>>> +++ b/drivers/video/jz4740_fb.c
>>> @@ -0,0 +1,817 @@
>>> +/*
>>> + * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
>>> + * JZ4740 SoC LCD framebuffer 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/kernel.h>
>>> +#include <linux/module.h>
>>> +#include <linux/mutex.h>
>>> +#include <linux/platform_device.h>
>>> +
>>> +#include <linux/clk.h>
>>> +#include <linux/delay.h>
>>> +
>>> +#include <linux/console.h>
>>> +#include <linux/fb.h>
>>> +
>>> +#include <linux/dma-mapping.h>
>>> +
>>> +#include <linux/jz4740_fb.h>
>
> I see a lot of register defines below. Is there any reason why they
> shouldn't go in above j*fb.h? Also, I'm not sure if linux/j*.h is the
> best place. I think most fbdev drivers place their headers in
> drivers/video itself unless they need to share something driver
> specific with userspace which is rare. Is there a specific reason why
> you put it in include/linux?
>
>

The register definitions are only used by the driver, so there is no reason why they
should be in a different file.
The header file defines the platform data struct for this driver. But I guess the
better location for it is include/video.

>>> +#include <asm/mach-jz4740/gpio.h>
>>> +
>>> +#define JZ_REG_LCD_CFG 0x00
>>> +#define JZ_REG_LCD_VSYNC 0x04
>>> +#define JZ_REG_LCD_HSYNC 0x08
>>> +#define JZ_REG_LCD_VAT 0x0C
>>> +#define JZ_REG_LCD_DAH 0x10
>>> +#define JZ_REG_LCD_DAV 0x14
>>> +#define JZ_REG_LCD_PS 0x18
>>> +#define JZ_REG_LCD_CLS 0x1C
>>> +#define JZ_REG_LCD_SPL 0x20
>>> +#define JZ_REG_LCD_REV 0x24
>>> +#define JZ_REG_LCD_CTRL 0x30
>>> +#define JZ_REG_LCD_STATE 0x34
>>> +#define JZ_REG_LCD_IID 0x38
>>> +#define JZ_REG_LCD_DA0 0x40
>>> +#define JZ_REG_LCD_SA0 0x44
>>> +#define JZ_REG_LCD_FID0 0x48
>>> +#define JZ_REG_LCD_CMD0 0x4C
>>> +#define JZ_REG_LCD_DA1 0x50
>>> +#define JZ_REG_LCD_SA1 0x54
>>> +#define JZ_REG_LCD_FID1 0x58
>>> +#define JZ_REG_LCD_CMD1 0x5C
>>> +
>>> +#define JZ_LCD_CFG_SLCD BIT(31)
>>> +#define JZ_LCD_CFG_PS_DISABLE BIT(23)
>>> +#define JZ_LCD_CFG_CLS_DISABLE BIT(22)
>>> +#define JZ_LCD_CFG_SPL_DISABLE BIT(21)
>>> +#define JZ_LCD_CFG_REV_DISABLE BIT(20)
>>> +#define JZ_LCD_CFG_HSYNCM BIT(19)
>>> +#define JZ_LCD_CFG_PCLKM BIT(18)
>>> +#define JZ_LCD_CFG_INV BIT(17)
>>> +#define JZ_LCD_CFG_SYNC_DIR BIT(16)
>>> +#define JZ_LCD_CFG_PS_POLARITY BIT(15)
>>> +#define JZ_LCD_CFG_CLS_POLARITY BIT(14)
>>> +#define JZ_LCD_CFG_SPL_POLARITY BIT(13)
>>> +#define JZ_LCD_CFG_REV_POLARITY BIT(12)
>>> +#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(11)
>>> +#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10)
>>> +#define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9)
>>> +#define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8)
>>> +#define JZ_LCD_CFG_18_BIT BIT(7)
>>> +#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4))
>>> +#define JZ_LCD_CFG_MODE_MASK 0xf
>>> +
>>> +#define JZ_LCD_CTRL_BURST_4 (0x0 << 28)
>>> +#define JZ_LCD_CTRL_BURST_8 (0x1 << 28)
>>> +#define JZ_LCD_CTRL_BURST_16 (0x2 << 28)
>>> +#define JZ_LCD_CTRL_RGB555 BIT(27)
>>> +#define JZ_LCD_CTRL_OFUP BIT(26)
>>> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24)
>>> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_4 (0x1 << 24)
>>> +#define JZ_LCD_CTRL_FRC_GRAYSCALE_2 (0x2 << 24)
>>> +#define JZ_LCD_CTRL_PDD_MASK (0xff << 16)
>>> +#define JZ_LCD_CTRL_EOF_IRQ BIT(13)
>>> +#define JZ_LCD_CTRL_SOF_IRQ BIT(12)
>>> +#define JZ_LCD_CTRL_OFU_IRQ BIT(11)
>>> +#define JZ_LCD_CTRL_IFU0_IRQ BIT(10)
>>> +#define JZ_LCD_CTRL_IFU1_IRQ BIT(9)
>>> +#define JZ_LCD_CTRL_DD_IRQ BIT(8)
>>> +#define JZ_LCD_CTRL_QDD_IRQ BIT(7)
>>> +#define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6)
>>> +#define JZ_LCD_CTRL_LSB_FISRT BIT(5)
>>> +#define JZ_LCD_CTRL_DISABLE BIT(4)
>>> +#define JZ_LCD_CTRL_ENABLE BIT(3)
>>> +#define JZ_LCD_CTRL_BPP_1 0x0
>>> +#define JZ_LCD_CTRL_BPP_2 0x1
>>> +#define JZ_LCD_CTRL_BPP_4 0x2
>>> +#define JZ_LCD_CTRL_BPP_8 0x3
>>> +#define JZ_LCD_CTRL_BPP_15_16 0x4
>>> +#define JZ_LCD_CTRL_BPP_18_24 0x5
>>> +
>>> +#define JZ_LCD_CMD_SOF_IRQ BIT(15)
>>> +#define JZ_LCD_CMD_EOF_IRQ BIT(16)
>>> +#define JZ_LCD_CMD_ENABLE_PAL BIT(12)
>>> +
>>> +#define JZ_LCD_SYNC_MASK 0x3ff
>>> +
>>> +#define JZ_LCD_STATE_DISABLED BIT(0)
>>> +
>>> +struct jzfb_framedesc {
>>> + uint32_t next;
>>> + uint32_t addr;
>>> + uint32_t id;
>>> + uint32_t cmd;
>>> +} __packed;
>>> +
>>> +struct jzfb {
>>> + struct fb_info *fb;
>>> + struct platform_device *pdev;
>>> + void __iomem *base;
>>> + struct resource *mem;
>>> + struct jz4740_fb_platform_data *pdata;
>>> +
>>> + size_t vidmem_size;
>>> + void *vidmem;
>>> + dma_addr_t vidmem_phys;
>>> + struct jzfb_framedesc *framedesc;
>>> + dma_addr_t framedesc_phys;
>>> +
>>> + struct clk *ldclk;
>>> + struct clk *lpclk;
>>> +
>>> + unsigned is_enabled:1;
>
> Out of curiosity, why the bitfield?
>
>>> + struct mutex lock;
>>> +
>>> + uint32_t pseudo_palette[16];
>>> +};
>>> +
>>> +static const struct fb_fix_screeninfo jzfb_fix __devinitdata = {
>>> + .id = "JZ4740 FB",
>>> + .type = FB_TYPE_PACKED_PIXELS,
>>> + .visual = FB_VISUAL_TRUECOLOR,
>>> + .xpanstep = 0,
>>> + .ypanstep = 0,
>>> + .ywrapstep = 0,
>>> + .accel = FB_ACCEL_NONE,
>>> +};
>>> +
>>> +static const struct jz_gpio_bulk_request jz_lcd_ctrl_pins[] = {
>>> + JZ_GPIO_BULK_PIN(LCD_PCLK),
>>> + JZ_GPIO_BULK_PIN(LCD_HSYNC),
>>> + JZ_GPIO_BULK_PIN(LCD_VSYNC),
>>> + JZ_GPIO_BULK_PIN(LCD_DE),
>>> + JZ_GPIO_BULK_PIN(LCD_PS),
>>> + JZ_GPIO_BULK_PIN(LCD_REV),
>>> +};
>>> +
>>> +static const struct jz_gpio_bulk_request jz_lcd_data_pins[] = {
>>> + JZ_GPIO_BULK_PIN(LCD_DATA0),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA1),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA2),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA3),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA4),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA5),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA6),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA7),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA8),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA9),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA10),
f>>> + JZ_GPIO_BULK_PIN(LCD_DATA11),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA12),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA13),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA14),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA15),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA16),
>>> + JZ_GPIO_BULK_PIN(LCD_DATA17),
>>> +};
>>> +
>>> +static unsigned int jzfb_num_ctrl_pins(struct jzfb *jzfb)
>>> +{
>>> + unsigned int num;
>>> +
>>> + switch (jzfb->pdata->lcd_type) {
>>> + case JZ_LCD_TYPE_GENERIC_16_BIT:
>>> + num = 4;
>>> + break;
>>> + case JZ_LCD_TYPE_GENERIC_18_BIT:
>>> + num = 4;
>>> + break;
>>> + case JZ_LCD_TYPE_8BIT_SERIAL:
>>> + num = 3;
>>> + break;
>
> The lcd type serial looks interesting to me. It'd be nice to have some
> descriptive comments so that we know what its used with.
>
Uhm, its a standard 8bit serial connection. 3 channels (R,G,B) and 8 bits per
channel. And the channels are send one after another over the wire.

>>> + default:
>>> + num = 0;
>>> + break;
>>> + }
>>> + return num;
>>> +}
>>> +
>>> +static unsigned int jzfb_num_data_pins(struct jzfb *jzfb)
>>> +{
>>> + unsigned int num;
>>> +
>>> + switch (jzfb->pdata->lcd_type) {
>>> + case JZ_LCD_TYPE_GENERIC_16_BIT:
>>> + num = 16;
>>> + break;
>>> + case JZ_LCD_TYPE_GENERIC_18_BIT:
>>> + num = 18;
>>> + break;
>>> + case JZ_LCD_TYPE_8BIT_SERIAL:
>>> + num = 8;
>>> + break;
>>> + default:
>>> + num = 0;
>>> + break;
>>> + }
>>> + return num;
>>> +}
>>> +
>>> +/* Based on CNVT_TOHW macro from skeletonfb.c */
>>> +static inline uint32_t jzfb_convert_color_to_hw(unsigned val,
>>> + struct fb_bitfield *bf)
>>> +{
>>> + return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset;
>>> +}
>>> +
>>> +static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green,
>>> + unsigned blue, unsigned transp, struct fb_info *fb)
>>> +{
>>> + uint32_t color;
>>> +
>>> + if (regno >= 16)
>>> + return -EINVAL;
>>> +
>>> + color = jzfb_convert_color_to_hw(red, &fb->var.red);
>>> + color |= jzfb_convert_color_to_hw(green, &fb->var.green);
>>> + color |= jzfb_convert_color_to_hw(blue, &fb->var.blue);
>>> + color |= jzfb_convert_color_to_hw(transp, &fb->var.transp);
>>> +
>>> + ((uint32_t *)(fb->pseudo_palette))[regno] = color;
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int jzfb_get_controller_bpp(struct jzfb *jzfb)
>>> +{
>>> + switch (jzfb->pdata->bpp) {
>>> + case 18:
>>> + case 24:
>>> + return 32;
>>> + case 15:
>>> + return 16;
>>> + default:
>>> + return jzfb->pdata->bpp;
>>> + }
>>> +}
>>> +
>>> +static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb,
>>> + struct fb_var_screeninfo *var)
>>> +{
>>> + size_t i;
>>> + struct fb_videomode *mode = jzfb->pdata->modes;
>>> +
>>> + for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) {
>>> + if (mode->xres == var->xres && mode->yres == var->yres)
>>> + return mode;
>>> + }
>>> +
>>> + return NULL;
>>> +}
>>> +
>>> +static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
>>> +{
>>> + struct jzfb *jzfb = fb->par;
>>> + struct fb_videomode *mode;
>>> +
>>> + if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) &&
>>> + var->bits_per_pixel != jzfb->pdata->bpp)
>>> + return -EINVAL;
>>> +
>>> + mode = jzfb_get_mode(jzfb, var);
>>> + if (mode == NULL)
>>> + return -EINVAL;
>>> +
>>> + fb_videomode_to_var(var, mode);
>>> +
>>> + switch (jzfb->pdata->bpp) {
>>> + case 8:
>>> + break;
>>> + case 15:
>>> + var->red.offset = 10;
>>> + var->red.length = 5;
>>> + var->green.offset = 6;
>>> + var->green.length = 5;
>>> + var->blue.offset = 0;
>>> + var->blue.length = 5;
>>> + break;
>>> + case 16:
>>> + var->red.offset = 11;
>>> + var->red.length = 5;
>>> + var->green.offset = 5;
>>> + var->green.length = 6;
>>> + var->blue.offset = 0;
>>> + var->blue.length = 5;
>>> + break;
>>> + case 18:
>>> + var->red.offset = 16;
>>> + var->red.length = 6;
>>> + var->green.offset = 8;
>>> + var->green.length = 6;
>>> + var->blue.offset = 0;
>>> + var->blue.length = 6;
>>> + var->bits_per_pixel = 32;
>>> + break;
>>> + case 32:
>>> + case 24:
>>> + var->transp.offset = 24;
>>> + var->transp.length = 8;
>>> + var->red.offset = 16;
>>> + var->red.length = 8;
>>> + var->green.offset = 8;
>>> + var->green.length = 8;
>>> + var->blue.offset = 0;
>>> + var->blue.length = 8;
>>> + var->bits_per_pixel = 32;
>>> + break;
>>> + default:
>>> + break;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int jzfb_set_par(struct fb_info *info)
>>> +{
>>> + struct jzfb *jzfb = info->par;
>>> + struct fb_var_screeninfo *var = &info->var;
>>> + struct fb_videomode *mode;
>>> + uint16_t hds, vds;
>>> + uint16_t hde, vde;
>>> + uint16_t ht, vt;
>>> + uint32_t ctrl;
>>> + uint32_t cfg;
>>> + unsigned long rate;
>>> +
>>> + mode = jzfb_get_mode(jzfb, var);
>>> + if (mode == NULL)
>>> + return -EINVAL;
>>> +
>>> + if (mode == info->mode)
>>> + return 0;
>>> +
>>> + info->mode = mode;
>>> +
>>> + hds = mode->hsync_len + mode->left_margin;
>>> + hde = hds + mode->xres;
>>> + ht = hde + mode->right_margin;
>>> +
>>> + vds = mode->vsync_len + mode->upper_margin;
>>> + vde = vds + mode->yres;
>>> + vt = vde + mode->lower_margin;
>>> +
>>> + ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16;
>>> +
>>> + switch (jzfb->pdata->bpp) {
>>> + case 1:
>>> + ctrl |= JZ_LCD_CTRL_BPP_1;
>>> + break;
>>> + case 2:
>>> + ctrl |= JZ_LCD_CTRL_BPP_2;
>>> + break;
>>> + case 4:
>>> + ctrl |= JZ_LCD_CTRL_BPP_4;
>>> + break;
>>> + case 8:
>>> + ctrl |= JZ_LCD_CTRL_BPP_8;
>>> + break;
>>> + case 15:
>>> + ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */
>>> + case 16:
>>> + ctrl |= JZ_LCD_CTRL_BPP_15_16;
>>> + break;
>>> + case 18:
>>> + case 24:
>>> + case 32:
>>> + ctrl |= JZ_LCD_CTRL_BPP_18_24;
>>> + break;
>>> + default:
>>> + break;
>>> + }
>>> +
>>> + cfg = JZ_LCD_CFG_PS_DISABLE | JZ_LCD_CFG_CLS_DISABLE |
>>> + JZ_LCD_CFG_SPL_DISABLE | JZ_LCD_CFG_REV_DISABLE;
>>> +
>>> + if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
>>> + cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
>>> +
>>> + if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
>>> + cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW;
>>> +
>>> + if (jzfb->pdata->pixclk_falling_edge)
>>> + cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
>>> +
>>> + if (jzfb->pdata->date_enable_active_low)
>>> + cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW;
>>> +
>>> + if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT)
>>> + cfg |= JZ_LCD_CFG_18_BIT;
>>> +
>>> + cfg |= jzfb->pdata->lcd_type & 0xf;
>>> +
>>> + if (mode->pixclock) {
>>> + rate = PICOS2KHZ(mode->pixclock) * 1000;
>>> + mode->refresh = rate / vt / ht;
>>> + } else {
>>> + if (jzfb->pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL)
>>> + rate = mode->refresh * (vt + 2 * mode->xres) * ht;
>>> + else
>>> + rate = mode->refresh * vt * ht;
>>> +
>>> + mode->pixclock = KHZ2PICOS(rate / 1000);
>>> + }
>>> +
>>> + mutex_lock(&jzfb->lock);
>>> + if (!jzfb->is_enabled)
>>> + clk_enable(jzfb->ldclk);
>>> + else
>>> + ctrl |= JZ_LCD_CTRL_ENABLE;
>>> +
>>> + writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC);
>>> + writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC);
>>> +
>>> + writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT);
>>> +
>>> + writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH);
>>> + writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV);
>>> +
>>> + writel(cfg, jzfb->base + JZ_REG_LCD_CFG);
>>> +
>>> + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
>>> +
>>> + if (!jzfb->is_enabled)
>>> + clk_disable(jzfb->ldclk);
>>> +
>>> + mutex_unlock(&jzfb->lock);
>>> +
>>> + clk_set_rate(jzfb->lpclk, rate);
>>> + clk_set_rate(jzfb->ldclk, rate * 3);
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static void jzfb_enable(struct jzfb *jzfb)
>>> +{
>>> + uint32_t ctrl;
>>> +
>>> + clk_enable(jzfb->ldclk);
>>> +
>>> + jz_gpio_bulk_resume(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
>>> + jz_gpio_bulk_resume(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
>>> +
>>> + writel(0, jzfb->base + JZ_REG_LCD_STATE);
>>> +
>>> + writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
>>> +
>>> + ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
>>> + ctrl |= JZ_LCD_CTRL_ENABLE;
>>> + ctrl &= ~JZ_LCD_CTRL_DISABLE;
>>> + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
>>> +}
>>> +
>>> +static void jzfb_disable(struct jzfb *jzfb)
>>> +{
>>> + uint32_t ctrl;
>>> +
>>> + ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
>>> + ctrl |= JZ_LCD_CTRL_DISABLE;
>>> + writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
>>> + do {
>>> + ctrl = readl(jzfb->base + JZ_REG_LCD_STATE);
>>> + } while (!(ctrl & JZ_LCD_STATE_DISABLED));
>>> +
>>> + jz_gpio_bulk_suspend(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
>>> + jz_gpio_bulk_suspend(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
>>> +
>>> + clk_disable(jzfb->ldclk);
>>> +}
>>> +
>>> +static int jzfb_blank(int blank_mode, struct fb_info *info)
>>> +{
>>> + struct jzfb *jzfb = info->par;
>>> +
>>> + switch (blank_mode) {
>>> + case FB_BLANK_UNBLANK:
>>> + mutex_lock(&jzfb->lock);
>>> + if (jzfb->is_enabled) {
>>> + mutex_unlock(&jzfb->lock);
>>> + return 0;
>>> + }
>>> +
>>> + jzfb_enable(jzfb);
>>> + jzfb->is_enabled = 1;
>>> +
>>> + mutex_unlock(&jzfb->lock);
>>> + break;
>>> + default:
>>> + mutex_lock(&jzfb->lock);
>>> + if (!jzfb->is_enabled) {
>>> + mutex_unlock(&jzfb->lock);
>>> + return 0;
>>> + }
>>> +
>>> + jzfb_disable(jzfb);
>>> + jzfb->is_enabled = 0;
>>> +
>>> + mutex_unlock(&jzfb->lock);
>>> + break;
>>> + }
>>> +
>>> + return 0;
>>> +}
>>> +
>>> +static int jzfb_alloc_devmem(struct jzfb *jzfb)
>>> +{
>>> + int max_videosize = 0;
>>> + struct fb_videomode *mode = jzfb->pdata->modes;
>>> + void *page;
>>> + int i;
>>> +
>>> + for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) {
>>> + if (max_videosize < mode->xres * mode->yres)
>>> + max_videosize = mode->xres * mode->yres;
>>> + }
>>> +
>>> + max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3;
>>> +
>>> + jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev,
>>> + sizeof(*jzfb->framedesc),
>>> + &jzfb->framedesc_phys, GFP_KERNEL);
>>> +
>>> + if (!jzfb->framedesc)
>>> + return -ENOMEM;
>>> +
>>> + jzfb->vidmem_size = PAGE_ALIGN(max_videosize);
>>> + jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev,
>>> + jzfb->vidmem_size,
>>> + &jzfb->vidmem_phys, GFP_KERNEL);
>>> +
>>> + if (!jzfb->vidmem)
>>> + goto err_free_framedesc;
>>> +
>>> + for (page = jzfb->vidmem;
>>> + page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size);
>>> + page += PAGE_SIZE) {
>>> + SetPageReserved(virt_to_page(page));
>>> + }
>
> It'd be nice to know the reasoning for the SetPageReserved.
>
It's to make sure that the framebuffer memory is not swapped out. But actually I'm
not sure if it's really needed, since I'm not that familar with how the swap
management code choose pages to swap out. But there are similar drivers seem to do
the same thing.

> Thanks,
> jaya
>

Thanks for reviewing,
- - Lars

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.9 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iEYEARECAAYFAkw3QN8ACgkQBX4mSR26RiNg3wCfeg7uauWGHNx5z9StBcbloOrj
luYAnjjGFuYcD8O7zBKjlzOHOMD8PPuJ
=qrO3
-----END PGP SIGNATURE-----

2010-07-12 01:48:32

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] MFD: Add JZ4740 ADC driver

This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to
demultiplex IRQs and synchronize access to shared registers between the battery,
hwmon and (future) touchscreen driver.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Samuel Ortiz <[email protected]>

---
Changes since v2
- Use atmic_t for clock ref-counting.
- Add a comment why ref-counting is done in the driver and not in the clock
framework.
- Return more appropriate error codes when resources are missing.
- Return result of mfd_add_devices at the end of the drivers probe function.
---
drivers/mfd/Kconfig | 8 +
drivers/mfd/Makefile | 1 +
drivers/mfd/jz4740-adc.c | 384 ++++++++++++++++++++++++++++++++++++++++++++
include/linux/jz4740-adc.h | 32 ++++
4 files changed, 425 insertions(+), 0 deletions(-)
create mode 100644 drivers/mfd/jz4740-adc.c
create mode 100644 include/linux/jz4740-adc.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 3f65dd0..2cde665 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -524,6 +524,14 @@ config MFD_JANZ_CMODIO
host many different types of MODULbus daughterboards, including
CAN and GPIO controllers.

+config MFD_JZ4740_ADC
+ tristate "Support for the JZ4740 SoC ADC core"
+ select MFD_CORE
+ depends on MACH_JZ4740
+ help
+ Say yes here if you want support for the ADC unit in the JZ4740 SoC.
+ This driver is necessary for jz4740-battery and jz4740-hwmon driver.
+
endif # MFD_SUPPORT

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 130c5f0..8a751dd 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -73,3 +73,4 @@ obj-$(CONFIG_PMIC_ADP5520) += adp5520.o
obj-$(CONFIG_LPC_SCH) += lpc_sch.o
obj-$(CONFIG_MFD_RDC321X) += rdc321x-southbridge.o
obj-$(CONFIG_MFD_JANZ_CMODIO) += janz-cmodio.o
+obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
diff --git a/drivers/mfd/jz4740-adc.c b/drivers/mfd/jz4740-adc.c
new file mode 100644
index 0000000..7a844ae
--- /dev/null
+++ b/drivers/mfd/jz4740-adc.c
@@ -0,0 +1,384 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC ADC 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.
+ *
+ * This driver synchronizes access to the JZ4740 ADC core between the
+ * JZ4740 battery and hwmon drivers.
+ */
+
+#include <linux/err.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <linux/clk.h>
+#include <linux/mfd/core.h>
+
+#include <linux/jz4740-adc.h>
+
+
+#define JZ_REG_ADC_ENABLE 0x00
+#define JZ_REG_ADC_CFG 0x04
+#define JZ_REG_ADC_CTRL 0x08
+#define JZ_REG_ADC_STATUS 0x0c
+
+#define JZ_REG_ADC_TOUCHSCREEN_BASE 0x10
+#define JZ_REG_ADC_BATTERY_BASE 0x1c
+#define JZ_REG_ADC_HWMON_BASE 0x20
+
+#define JZ_ADC_ENABLE_TOUCH BIT(2)
+#define JZ_ADC_ENABLE_BATTERY BIT(1)
+#define JZ_ADC_ENABLE_ADCIN BIT(0)
+
+enum {
+ JZ_ADC_IRQ_ADCIN = 0,
+ JZ_ADC_IRQ_BATTERY,
+ JZ_ADC_IRQ_TOUCH,
+ JZ_ADC_IRQ_PENUP,
+ JZ_ADC_IRQ_PENDOWN,
+};
+
+struct jz4740_adc {
+ struct resource *mem;
+ void __iomem *base;
+
+ int irq;
+ int irq_base;
+
+ struct clk *clk;
+ atomic_t clk_ref;
+
+ spinlock_t lock;
+};
+
+static inline void jz4740_adc_irq_set_masked(struct jz4740_adc *adc, int irq,
+ bool masked)
+{
+ unsigned long flags;
+ uint8_t val;
+
+ irq -= adc->irq_base;
+
+ spin_lock_irqsave(&adc->lock, flags);
+
+ val = readb(adc->base + JZ_REG_ADC_CTRL);
+ if (masked)
+ val |= BIT(irq);
+ else
+ val &= ~BIT(irq);
+ writeb(val, adc->base + JZ_REG_ADC_CTRL);
+
+ spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+static void jz4740_adc_irq_mask(unsigned int irq)
+{
+ struct jz4740_adc *adc = get_irq_chip_data(irq);
+ jz4740_adc_irq_set_masked(adc, irq, true);
+}
+
+static void jz4740_adc_irq_unmask(unsigned int irq)
+{
+ struct jz4740_adc *adc = get_irq_chip_data(irq);
+ jz4740_adc_irq_set_masked(adc, irq, false);
+}
+
+static void jz4740_adc_irq_ack(unsigned int irq)
+{
+ struct jz4740_adc *adc = get_irq_chip_data(irq);
+
+ irq -= adc->irq_base;
+ writeb(BIT(irq), adc->base + JZ_REG_ADC_STATUS);
+}
+
+static struct irq_chip jz4740_adc_irq_chip = {
+ .name = "jz4740-adc",
+ .mask = jz4740_adc_irq_mask,
+ .unmask = jz4740_adc_irq_unmask,
+ .ack = jz4740_adc_irq_ack,
+};
+
+static void jz4740_adc_irq_demux(unsigned int irq, struct irq_desc *desc)
+{
+ struct jz4740_adc *adc = get_irq_desc_data(desc);
+ uint8_t status;
+ unsigned int i;
+
+ status = readb(adc->base + JZ_REG_ADC_STATUS);
+
+ for (i = 0; i < 5; ++i) {
+ if (status & BIT(i))
+ generic_handle_irq(adc->irq_base + i);
+ }
+}
+
+
+/* Refcounting for the ADC clock is done in here instead of in the clock
+ * framework, because it is the only clock which is shared between multiple
+ * devices and thus is the only clock which needs refcounting */
+static inline void jz4740_adc_clk_enable(struct jz4740_adc *adc)
+{
+ if (atomic_inc_return(&adc->clk_ref) == 1)
+ clk_enable(adc->clk);
+}
+
+static inline void jz4740_adc_clk_disable(struct jz4740_adc *adc)
+{
+ if (atomic_dec_return(&adc->clk_ref) == 0)
+ clk_disable(adc->clk);
+}
+
+static inline void jz4740_adc_set_enabled(struct jz4740_adc *adc, int engine,
+ bool enabled)
+{
+ unsigned long flags;
+ uint8_t val;
+
+ spin_lock_irqsave(&adc->lock, flags);
+
+ val = readb(adc->base + JZ_REG_ADC_ENABLE);
+ if (enabled)
+ val |= BIT(engine);
+ else
+ val &= BIT(engine);
+ writeb(val, adc->base + JZ_REG_ADC_ENABLE);
+
+ spin_unlock_irqrestore(&adc->lock, flags);
+}
+
+static int jz4740_adc_cell_enable(struct platform_device *pdev)
+{
+ struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
+
+ jz4740_adc_clk_enable(adc);
+ jz4740_adc_set_enabled(adc, pdev->id, true);
+
+ return 0;
+}
+
+static int jz4740_adc_cell_disable(struct platform_device *pdev)
+{
+ struct jz4740_adc *adc = dev_get_drvdata(pdev->dev.parent);
+
+ jz4740_adc_set_enabled(adc, pdev->id, false);
+ jz4740_adc_clk_disable(adc);
+
+ return 0;
+}
+
+int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val)
+{
+ struct jz4740_adc *adc = dev_get_drvdata(dev);
+ unsigned long flags;
+ uint32_t cfg;
+
+ if (!adc)
+ return -ENODEV;
+
+ spin_lock_irqsave(&adc->lock, flags);
+
+ cfg = readl(adc->base + JZ_REG_ADC_CFG);
+
+ cfg &= ~mask;
+ cfg |= val;
+
+ writel(cfg, adc->base + JZ_REG_ADC_CFG);
+
+ spin_unlock_irqrestore(&adc->lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(jz4740_adc_set_config);
+
+static struct resource jz4740_hwmon_resources[] = {
+ {
+ .start = JZ_ADC_IRQ_ADCIN,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .start = JZ_REG_ADC_HWMON_BASE,
+ .end = JZ_REG_ADC_HWMON_BASE + 3,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+static struct resource jz4740_battery_resources[] = {
+ {
+ .start = JZ_ADC_IRQ_BATTERY,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .start = JZ_REG_ADC_BATTERY_BASE,
+ .end = JZ_REG_ADC_BATTERY_BASE + 3,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+const struct mfd_cell jz4740_adc_cells[] = {
+ {
+ .id = 0,
+ .name = "jz4740-hwmon",
+ .num_resources = ARRAY_SIZE(jz4740_hwmon_resources),
+ .resources = jz4740_hwmon_resources,
+ .platform_data = (void *)&jz4740_adc_cells[0],
+ .data_size = sizeof(struct mfd_cell),
+
+ .enable = jz4740_adc_cell_enable,
+ .disable = jz4740_adc_cell_disable,
+ },
+ {
+ .id = 1,
+ .name = "jz4740-battery",
+ .num_resources = ARRAY_SIZE(jz4740_battery_resources),
+ .resources = jz4740_battery_resources,
+ .platform_data = (void *)&jz4740_adc_cells[1],
+ .data_size = sizeof(struct mfd_cell),
+
+ .enable = jz4740_adc_cell_enable,
+ .disable = jz4740_adc_cell_disable,
+ },
+};
+
+static int __devinit jz4740_adc_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz4740_adc *adc;
+ struct resource *mem_base;
+ int irq;
+
+ adc = kmalloc(sizeof(*adc), GFP_KERNEL);
+
+ adc->irq = platform_get_irq(pdev, 0);
+ if (adc->irq < 0) {
+ ret = adc->irq;
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ goto err_free;
+ }
+
+ adc->irq_base = platform_get_irq(pdev, 1);
+ if (adc->irq_base < 0) {
+ ret = adc->irq_base;
+ dev_err(&pdev->dev, "Failed to get irq base: %d\n", ret);
+ goto err_free;
+ }
+
+ mem_base = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem_base) {
+ ret = -ENOENT;
+ dev_err(&pdev->dev, "Failed to get platform mmio resource\n");
+ goto err_free;
+ }
+
+ /* Only request the shared registers for the MFD driver */
+ adc->mem = request_mem_region(mem_base->start, JZ_REG_ADC_STATUS,
+ pdev->name);
+ if (!adc->mem) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to request mmio memory region\n");
+ goto err_free;
+ }
+
+ adc->base = ioremap_nocache(adc->mem->start, resource_size(adc->mem));
+ if (!adc->base) {
+ ret = -EBUSY;
+ dev_err(&pdev->dev, "Failed to ioremap mmio memory\n");
+ goto err_release_mem_region;
+ }
+
+ adc->clk = clk_get(&pdev->dev, "adc");
+ if (IS_ERR(adc->clk)) {
+ ret = PTR_ERR(adc->clk);
+ dev_err(&pdev->dev, "Failed to get clock: %d\n", ret);
+ goto err_iounmap;
+ }
+
+ spin_lock_init(&adc->lock);
+ atomic_set(&adc->clk_ref, 0);
+
+ platform_set_drvdata(pdev, adc);
+
+ for (irq = adc->irq_base; irq < adc->irq_base + 5; ++irq) {
+ set_irq_chip_data(irq, adc);
+ set_irq_chip_and_handler(irq, &jz4740_adc_irq_chip,
+ handle_level_irq);
+ }
+
+ set_irq_data(adc->irq, adc);
+ set_irq_chained_handler(adc->irq, jz4740_adc_irq_demux);
+
+ writeb(0x00, adc->base + JZ_REG_ADC_ENABLE);
+ writeb(0xff, adc->base + JZ_REG_ADC_CTRL);
+
+ return mfd_add_devices(&pdev->dev, 0, jz4740_adc_cells,
+ ARRAY_SIZE(jz4740_adc_cells), mem_base, adc->irq_base);
+
+err_iounmap:
+ platform_set_drvdata(pdev, NULL);
+ iounmap(adc->base);
+err_release_mem_region:
+ release_mem_region(adc->mem->start, resource_size(adc->mem));
+err_free:
+ kfree(adc);
+
+ return ret;
+}
+
+static int __devexit jz4740_adc_remove(struct platform_device *pdev)
+{
+ struct jz4740_adc *adc = platform_get_drvdata(pdev);
+
+ mfd_remove_devices(&pdev->dev);
+
+ set_irq_data(adc->irq, NULL);
+ set_irq_chained_handler(adc->irq, NULL);
+
+ iounmap(adc->base);
+ release_mem_region(adc->mem->start, resource_size(adc->mem));
+
+ clk_put(adc->clk);
+
+ platform_set_drvdata(pdev, NULL);
+
+ kfree(adc);
+
+ return 0;
+}
+
+struct platform_driver jz4740_adc_driver = {
+ .probe = jz4740_adc_probe,
+ .remove = __devexit_p(jz4740_adc_remove),
+ .driver = {
+ .name = "jz4740-adc",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz4740_adc_init(void)
+{
+ return platform_driver_register(&jz4740_adc_driver);
+}
+module_init(jz4740_adc_init);
+
+static void __exit jz4740_adc_exit(void)
+{
+ platform_driver_unregister(&jz4740_adc_driver);
+}
+module_exit(jz4740_adc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SoC ADC driver");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-adc");
diff --git a/include/linux/jz4740-adc.h b/include/linux/jz4740-adc.h
new file mode 100644
index 0000000..9053f95
--- /dev/null
+++ b/include/linux/jz4740-adc.h
@@ -0,0 +1,32 @@
+
+#ifndef __LINUX_JZ4740_ADC
+#define __LINUX_JZ4740_ADC
+
+#include <linux/device.h>
+
+/*
+ * jz4740_adc_set_config - Configure a JZ4740 adc device
+ * @dev: Pointer to a jz4740-adc device
+ * @mask: Mask for the config value to be set
+ * @val: Value to be set
+ *
+ * This function can be used by the JZ4740 ADC mfd cells to configure their
+ * options in the shared config register.
+*/
+int jz4740_adc_set_config(struct device *dev, uint32_t mask, uint32_t val);
+
+#define JZ_ADC_CONFIG_SPZZ BIT(31)
+#define JZ_ADC_CONFIG_EX_IN BIT(30)
+#define JZ_ADC_CONFIG_DNUM_MASK (0x7 << 16)
+#define JZ_ADC_CONFIG_DMA_ENABLE BIT(15)
+#define JZ_ADC_CONFIG_XYZ_MASK (0x2 << 13)
+#define JZ_ADC_CONFIG_SAMPLE_NUM_MASK (0x7 << 10)
+#define JZ_ADC_CONFIG_CLKDIV_MASK (0xf << 5)
+#define JZ_ADC_CONFIG_BAT_MB BIT(4)
+
+#define JZ_ADC_CONFIG_DNUM(dnum) ((dnum) << 16)
+#define JZ_ADC_CONFIG_XYZ_OFFSET(dnum) ((xyz) << 13)
+#define JZ_ADC_CONFIG_SAMPLE_NUM(x) ((x) << 10)
+#define JZ_ADC_CONFIG_CLKDIV(div) ((div) << 5)
+
+#endif
--
1.5.6.5

2010-07-12 21:33:59

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v4] MMC: Add JZ4740 mmc driver

This patch adds support for the mmc controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Acked-by: Matt Fleming <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Matt Fleming <[email protected]>
Cc: [email protected]

---
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 <[email protected]>
+ * 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 <[email protected]>");
--
1.5.6.5

2010-07-12 21:43:23

by Randy Dunlap

[permalink] [raw]
Subject: Re: [PATCH v4] MMC: Add JZ4740 mmc driver

Lars-Peter Clausen wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Acked-by: Matt Fleming <[email protected]>
> Cc: Andrew Morton <[email protected]>
> Cc: Matt Fleming <[email protected]>
> Cc: [email protected]
>
> ---
> 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

2010-07-12 22:08:30

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v4] MMC: Add JZ4740 mmc driver

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 <[email protected]>
>> Acked-by: Matt Fleming <[email protected]>
>> Cc: Andrew Morton <[email protected]>
>> Cc: Matt Fleming <[email protected]>
>> Cc: [email protected]
>>
>> ---
>> 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

2010-07-12 22:21:10

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v5] MMC: Add JZ4740 mmc driver

This patch adds support for the mmc controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Acked-by: Matt Fleming <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Matt Fleming <[email protected]>
Cc: [email protected]

---
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 <[email protected]>
+ * 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 <[email protected]>");
--
1.5.6.5

2010-07-12 22:45:15

by Joe Perches

[permalink] [raw]
Subject: Re: [PATCH v5] MMC: Add JZ4740 mmc driver

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

2010-07-12 23:46:06

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v5] MMC: Add JZ4740 mmc driver

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

2010-07-14 09:19:41

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v3] MFD: Add JZ4740 ADC driver

Hi Lars,

On Mon, Jul 12, 2010 at 03:48:08AM +0200, Lars-Peter Clausen wrote:
> This patch adds a MFD driver for the JZ4740 ADC unit. The driver is used to
> demultiplex IRQs and synchronize access to shared registers between the battery,
> hwmon and (future) touchscreen driver.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: Samuel Ortiz <[email protected]>
Patch applied, many thanks.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2010-07-15 21:06:38

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v6] MMC: Add JZ4740 mmc driver

This patch adds support for the mmc controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Acked-by: Matt Fleming <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: Matt Fleming <[email protected]>
Cc: [email protected]

---
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 <[email protected]>
+ * 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 <[email protected]>");
--
1.5.6.5

2010-07-15 21:17:14

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH v6] MMC: Add JZ4740 mmc driver

On Thu, 15 Jul 2010 23:06:04 +0200
Lars-Peter Clausen <[email protected]> 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?

2010-07-15 21:38:23

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v6] MMC: Add JZ4740 mmc driver

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Andrew Morton wrote:
> On Thu, 15 Jul 2010 23:06:04 +0200
> Lars-Peter Clausen <[email protected]> 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-----

2010-07-17 12:09:05

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] MIPS: jz4740: Add IRQ handler code

This patch adds support for IRQ handling on a JZ4740 SoC.

Signed-off-by: Lars-Peter Clausen <[email protected]>

--
Changes since v1
- Reserve IRQ numbers for ADC IRQ demultiplexing

Changes since v2
- Use __fls instead of ffs in the interrupt demuxer
---
arch/mips/include/asm/mach-jz4740/irq.h | 57 +++++++++++
arch/mips/jz4740/irq.c | 167 +++++++++++++++++++++++++++++++
arch/mips/jz4740/irq.h | 21 ++++
3 files changed, 245 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h
create mode 100644 arch/mips/jz4740/irq.c
create mode 100644 arch/mips/jz4740/irq.h

diff --git a/arch/mips/include/asm/mach-jz4740/irq.h b/arch/mips/include/asm/mach-jz4740/irq.h
new file mode 100644
index 0000000..a865c98
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/irq.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 IRQ definitions
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_MACH_JZ4740_IRQ_H__
+#define __ASM_MACH_JZ4740_IRQ_H__
+
+#define MIPS_CPU_IRQ_BASE 0
+#define JZ4740_IRQ_BASE 8
+
+/* 1st-level interrupts */
+#define JZ4740_IRQ(x) (JZ4740_IRQ_BASE + (x))
+#define JZ4740_IRQ_I2C JZ4740_IRQ(1)
+#define JZ4740_IRQ_UHC JZ4740_IRQ(3)
+#define JZ4740_IRQ_UART1 JZ4740_IRQ(8)
+#define JZ4740_IRQ_UART0 JZ4740_IRQ(9)
+#define JZ4740_IRQ_SADC JZ4740_IRQ(12)
+#define JZ4740_IRQ_MSC JZ4740_IRQ(14)
+#define JZ4740_IRQ_RTC JZ4740_IRQ(15)
+#define JZ4740_IRQ_SSI JZ4740_IRQ(16)
+#define JZ4740_IRQ_CIM JZ4740_IRQ(17)
+#define JZ4740_IRQ_AIC JZ4740_IRQ(18)
+#define JZ4740_IRQ_ETH JZ4740_IRQ(19)
+#define JZ4740_IRQ_DMAC JZ4740_IRQ(20)
+#define JZ4740_IRQ_TCU2 JZ4740_IRQ(21)
+#define JZ4740_IRQ_TCU1 JZ4740_IRQ(22)
+#define JZ4740_IRQ_TCU0 JZ4740_IRQ(23)
+#define JZ4740_IRQ_UDC JZ4740_IRQ(24)
+#define JZ4740_IRQ_GPIO3 JZ4740_IRQ(25)
+#define JZ4740_IRQ_GPIO2 JZ4740_IRQ(26)
+#define JZ4740_IRQ_GPIO1 JZ4740_IRQ(27)
+#define JZ4740_IRQ_GPIO0 JZ4740_IRQ(28)
+#define JZ4740_IRQ_IPU JZ4740_IRQ(29)
+#define JZ4740_IRQ_LCD JZ4740_IRQ(30)
+
+/* 2nd-level interrupts */
+#define JZ4740_IRQ_DMA(x) (JZ4740_IRQ(32) + (X))
+
+#define JZ4740_IRQ_INTC_GPIO(x) (JZ4740_IRQ_GPIO0 - (x))
+#define JZ4740_IRQ_GPIO(x) (JZ4740_IRQ(48) + (x))
+
+#define JZ4740_IRQ_ADC_BASE JZ4740_IRQ(176)
+
+#define NR_IRQS (JZ4740_IRQ_ADC_BASE + 6)
+
+#endif
diff --git a/arch/mips/jz4740/irq.c b/arch/mips/jz4740/irq.c
new file mode 100644
index 0000000..7d33ff8
--- /dev/null
+++ b/arch/mips/jz4740/irq.c
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform IRQ support
+ *
+ * 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/errno.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/ioport.h>
+#include <linux/timex.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <asm/io.h>
+#include <asm/mipsregs.h>
+#include <asm/irq_cpu.h>
+
+#include <asm/mach-jz4740/base.h>
+
+static void __iomem *jz_intc_base;
+static uint32_t jz_intc_wakeup;
+static uint32_t jz_intc_saved;
+
+#define JZ_REG_INTC_STATUS 0x00
+#define JZ_REG_INTC_MASK 0x04
+#define JZ_REG_INTC_SET_MASK 0x08
+#define JZ_REG_INTC_CLEAR_MASK 0x0c
+#define JZ_REG_INTC_PENDING 0x10
+
+#define IRQ_BIT(x) BIT((x) - JZ4740_IRQ_BASE)
+
+static void intc_irq_unmask(unsigned int irq)
+{
+ writel(IRQ_BIT(irq), jz_intc_base + JZ_REG_INTC_CLEAR_MASK);
+}
+
+static void intc_irq_mask(unsigned int irq)
+{
+ writel(IRQ_BIT(irq), jz_intc_base + JZ_REG_INTC_SET_MASK);
+}
+
+static int intc_irq_set_wake(unsigned int irq, unsigned int on)
+{
+ if (on)
+ jz_intc_wakeup |= IRQ_BIT(irq);
+ else
+ jz_intc_wakeup &= ~IRQ_BIT(irq);
+
+ return 0;
+}
+
+static struct irq_chip intc_irq_type = {
+ .name = "INTC",
+ .mask = intc_irq_mask,
+ .mask_ack = intc_irq_mask,
+ .unmask = intc_irq_unmask,
+ .set_wake = intc_irq_set_wake,
+};
+
+static irqreturn_t jz4740_cascade(int irq, void *data)
+{
+ uint32_t irq_reg;
+
+ irq_reg = readl(jz_intc_base + JZ_REG_INTC_PENDING);
+
+ if (irq_reg)
+ generic_handle_irq(__fls(irq_reg) + JZ4740_IRQ_BASE);
+
+ return IRQ_HANDLED;
+}
+
+static struct irqaction jz4740_cascade_action = {
+ .handler = jz4740_cascade,
+ .name = "JZ4740 cascade interrupt",
+};
+
+void __init arch_init_irq(void)
+{
+ int i;
+ mips_cpu_irq_init();
+
+ jz_intc_base = ioremap(JZ4740_INTC_BASE_ADDR, 0x14);
+
+ for (i = JZ4740_IRQ_BASE; i < JZ4740_IRQ_BASE + 32; i++) {
+ intc_irq_mask(i);
+ set_irq_chip_and_handler(i, &intc_irq_type, handle_level_irq);
+ }
+
+ setup_irq(2, &jz4740_cascade_action);
+}
+
+asmlinkage void plat_irq_dispatch(void)
+{
+ unsigned int pending = read_c0_status() & read_c0_cause() & ST0_IM;
+ if (pending & STATUSF_IP2)
+ do_IRQ(2);
+ else if (pending & STATUSF_IP3)
+ do_IRQ(3);
+ else
+ spurious_interrupt();
+}
+
+void jz4740_intc_suspend(void)
+{
+ jz_intc_saved = readl(jz_intc_base + JZ_REG_INTC_MASK);
+ writel(~jz_intc_wakeup, jz_intc_base + JZ_REG_INTC_SET_MASK);
+ writel(jz_intc_wakeup, jz_intc_base + JZ_REG_INTC_CLEAR_MASK);
+}
+
+void jz4740_intc_resume(void)
+{
+ writel(~jz_intc_saved, jz_intc_base + JZ_REG_INTC_CLEAR_MASK);
+ writel(jz_intc_saved, jz_intc_base + JZ_REG_INTC_SET_MASK);
+}
+
+#ifdef CONFIG_DEBUG_FS
+
+static inline void intc_seq_reg(struct seq_file *s, const char *name,
+ unsigned int reg)
+{
+ seq_printf(s, "%s:\t\t%08x\n", name, readl(jz_intc_base + reg));
+}
+
+static int intc_regs_show(struct seq_file *s, void *unused)
+{
+ intc_seq_reg(s, "Status", JZ_REG_INTC_STATUS);
+ intc_seq_reg(s, "Mask", JZ_REG_INTC_MASK);
+ intc_seq_reg(s, "Pending", JZ_REG_INTC_PENDING);
+
+ return 0;
+}
+
+static int intc_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, intc_regs_show, NULL);
+}
+
+static const struct file_operations intc_regs_operations = {
+ .open = intc_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init intc_debugfs_init(void)
+{
+ (void) debugfs_create_file("jz_regs_intc", S_IFREG | S_IRUGO,
+ NULL, NULL, &intc_regs_operations);
+ return 0;
+}
+subsys_initcall(intc_debugfs_init);
+
+#endif
diff --git a/arch/mips/jz4740/irq.h b/arch/mips/jz4740/irq.h
new file mode 100644
index 0000000..56b5ead
--- /dev/null
+++ b/arch/mips/jz4740/irq.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MIPS_JZ4740_IRQ_H__
+#define __MIPS_JZ4740_IRQ_H__
+
+extern void jz4740_intc_suspend(void);
+extern void jz4740_intc_resume(void);
+
+#endif
--
1.5.6.5

2010-07-17 12:10:21

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v4] MIPS: JZ4740: Add clock API support.

This patch adds support for managing the clocks found on JZ4740 SoC through
the Linux clock API.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v2
- Fix setting of inital parents for spi and i2s clocks
- In clk_set_parent(), preserve clock enabled/disabled state.
- Set correct get_rate callback for the pll clock
- Fix pll frequency rate formula

Changes since v3
- Move clock initialization to arch_initcall
---
arch/mips/include/asm/mach-jz4740/clock.h | 28 +
arch/mips/jz4740/clock-debugfs.c | 109 ++++
arch/mips/jz4740/clock.c | 924 +++++++++++++++++++++++++++++
arch/mips/jz4740/clock.h | 76 +++
4 files changed, 1137 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h
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

diff --git a/arch/mips/include/asm/mach-jz4740/clock.h b/arch/mips/include/asm/mach-jz4740/clock.h
new file mode 100644
index 0000000..1b7408d
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/clock.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_JZ4740_CLOCK_H__
+#define __ASM_JZ4740_CLOCK_H__
+
+enum jz4740_wait_mode {
+ JZ4740_WAIT_MODE_IDLE,
+ JZ4740_WAIT_MODE_SLEEP,
+};
+
+void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode);
+
+void jz4740_clock_udc_enable_auto_suspend(void);
+void jz4740_clock_udc_disable_auto_suspend(void);
+
+#endif
diff --git a/arch/mips/jz4740/clock-debugfs.c b/arch/mips/jz4740/clock-debugfs.c
new file mode 100644
index 0000000..330a0f2
--- /dev/null
+++ b/arch/mips/jz4740/clock-debugfs.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support debugfs entries
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+
+#include <asm/mach-jz4740/clock.h>
+#include "clock.h"
+
+static struct dentry *jz4740_clock_debugfs;
+
+static int jz4740_clock_debugfs_show_enabled(void *data, uint64_t *value)
+{
+ struct clk *clk = data;
+ *value = clk_is_enabled(clk);
+
+ return 0;
+}
+
+static int jz4740_clock_debugfs_set_enabled(void *data, uint64_t value)
+{
+ struct clk *clk = data;
+
+ if (value)
+ return clk_enable(clk);
+ else
+ clk_disable(clk);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(jz4740_clock_debugfs_ops_enabled,
+ jz4740_clock_debugfs_show_enabled,
+ jz4740_clock_debugfs_set_enabled,
+ "%llu\n");
+
+static int jz4740_clock_debugfs_show_rate(void *data, uint64_t *value)
+{
+ struct clk *clk = data;
+ *value = clk_get_rate(clk);
+
+ return 0;
+}
+
+DEFINE_SIMPLE_ATTRIBUTE(jz4740_clock_debugfs_ops_rate,
+ jz4740_clock_debugfs_show_rate,
+ NULL,
+ "%llu\n");
+
+void jz4740_clock_debugfs_add_clk(struct clk *clk)
+{
+ if (!jz4740_clock_debugfs)
+ return;
+
+ clk->debugfs_entry = debugfs_create_dir(clk->name, jz4740_clock_debugfs);
+ debugfs_create_file("rate", S_IWUGO | S_IRUGO, clk->debugfs_entry, clk,
+ &jz4740_clock_debugfs_ops_rate);
+ debugfs_create_file("enabled", S_IRUGO, clk->debugfs_entry, clk,
+ &jz4740_clock_debugfs_ops_enabled);
+
+ if (clk->parent) {
+ char parent_path[100];
+ snprintf(parent_path, 100, "../%s", clk->parent->name);
+ clk->debugfs_parent_entry = debugfs_create_symlink("parent",
+ clk->debugfs_entry,
+ parent_path);
+ }
+}
+
+/* TODO: Locking */
+void jz4740_clock_debugfs_update_parent(struct clk *clk)
+{
+ if (clk->debugfs_parent_entry)
+ debugfs_remove(clk->debugfs_parent_entry);
+
+ if (clk->parent) {
+ char parent_path[100];
+ snprintf(parent_path, 100, "../%s", clk->parent->name);
+ clk->debugfs_parent_entry = debugfs_create_symlink("parent",
+ clk->debugfs_entry,
+ parent_path);
+ } else {
+ clk->debugfs_parent_entry = NULL;
+ }
+}
+
+void jz4740_clock_debugfs_init(void)
+{
+ jz4740_clock_debugfs = debugfs_create_dir("jz4740-clock", NULL);
+ if (IS_ERR(jz4740_clock_debugfs))
+ jz4740_clock_debugfs = NULL;
+}
diff --git a/arch/mips/jz4740/clock.c b/arch/mips/jz4740/clock.c
new file mode 100644
index 0000000..118a8a5
--- /dev/null
+++ b/arch/mips/jz4740/clock.c
@@ -0,0 +1,924 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support
+ *
+ * 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/kernel.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/err.h>
+
+#include <asm/mach-jz4740/clock.h>
+#include <asm/mach-jz4740/base.h>
+
+#include "clock.h"
+
+#define JZ_REG_CLOCK_CTRL 0x00
+#define JZ_REG_CLOCK_LOW_POWER 0x04
+#define JZ_REG_CLOCK_PLL 0x10
+#define JZ_REG_CLOCK_GATE 0x20
+#define JZ_REG_CLOCK_SLEEP_CTRL 0x24
+#define JZ_REG_CLOCK_I2S 0x60
+#define JZ_REG_CLOCK_LCD 0x64
+#define JZ_REG_CLOCK_MMC 0x68
+#define JZ_REG_CLOCK_UHC 0x6C
+#define JZ_REG_CLOCK_SPI 0x74
+
+#define JZ_CLOCK_CTRL_I2S_SRC_PLL BIT(31)
+#define JZ_CLOCK_CTRL_KO_ENABLE BIT(30)
+#define JZ_CLOCK_CTRL_UDC_SRC_PLL BIT(29)
+#define JZ_CLOCK_CTRL_UDIV_MASK 0x1f800000
+#define JZ_CLOCK_CTRL_CHANGE_ENABLE BIT(22)
+#define JZ_CLOCK_CTRL_PLL_HALF BIT(21)
+#define JZ_CLOCK_CTRL_LDIV_MASK 0x001f0000
+#define JZ_CLOCK_CTRL_UDIV_OFFSET 23
+#define JZ_CLOCK_CTRL_LDIV_OFFSET 16
+#define JZ_CLOCK_CTRL_MDIV_OFFSET 12
+#define JZ_CLOCK_CTRL_PDIV_OFFSET 8
+#define JZ_CLOCK_CTRL_HDIV_OFFSET 4
+#define JZ_CLOCK_CTRL_CDIV_OFFSET 0
+
+#define JZ_CLOCK_GATE_UART0 BIT(0)
+#define JZ_CLOCK_GATE_TCU BIT(1)
+#define JZ_CLOCK_GATE_RTC BIT(2)
+#define JZ_CLOCK_GATE_I2C BIT(3)
+#define JZ_CLOCK_GATE_SPI BIT(4)
+#define JZ_CLOCK_GATE_AIC BIT(5)
+#define JZ_CLOCK_GATE_I2S BIT(6)
+#define JZ_CLOCK_GATE_MMC BIT(7)
+#define JZ_CLOCK_GATE_ADC BIT(8)
+#define JZ_CLOCK_GATE_CIM BIT(9)
+#define JZ_CLOCK_GATE_LCD BIT(10)
+#define JZ_CLOCK_GATE_UDC BIT(11)
+#define JZ_CLOCK_GATE_DMAC BIT(12)
+#define JZ_CLOCK_GATE_IPU BIT(13)
+#define JZ_CLOCK_GATE_UHC BIT(14)
+#define JZ_CLOCK_GATE_UART1 BIT(15)
+
+#define JZ_CLOCK_I2S_DIV_MASK 0x01ff
+
+#define JZ_CLOCK_LCD_DIV_MASK 0x01ff
+
+#define JZ_CLOCK_MMC_DIV_MASK 0x001f
+
+#define JZ_CLOCK_UHC_DIV_MASK 0x000f
+
+#define JZ_CLOCK_SPI_SRC_PLL BIT(31)
+#define JZ_CLOCK_SPI_DIV_MASK 0x000f
+
+#define JZ_CLOCK_PLL_M_MASK 0x01ff
+#define JZ_CLOCK_PLL_N_MASK 0x001f
+#define JZ_CLOCK_PLL_OD_MASK 0x0003
+#define JZ_CLOCK_PLL_STABLE BIT(10)
+#define JZ_CLOCK_PLL_BYPASS BIT(9)
+#define JZ_CLOCK_PLL_ENABLED BIT(8)
+#define JZ_CLOCK_PLL_STABLIZE_MASK 0x000f
+#define JZ_CLOCK_PLL_M_OFFSET 23
+#define JZ_CLOCK_PLL_N_OFFSET 18
+#define JZ_CLOCK_PLL_OD_OFFSET 16
+
+#define JZ_CLOCK_LOW_POWER_MODE_DOZE BIT(2)
+#define JZ_CLOCK_LOW_POWER_MODE_SLEEP BIT(0)
+
+#define JZ_CLOCK_SLEEP_CTRL_SUSPEND_UHC BIT(7)
+#define JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC BIT(6)
+
+static void __iomem *jz_clock_base;
+static spinlock_t jz_clock_lock;
+static LIST_HEAD(jz_clocks);
+
+struct main_clk {
+ struct clk clk;
+ uint32_t div_offset;
+};
+
+struct divided_clk {
+ struct clk clk;
+ uint32_t reg;
+ uint32_t mask;
+};
+
+struct static_clk {
+ struct clk clk;
+ unsigned long rate;
+};
+
+static uint32_t jz_clk_reg_read(int reg)
+{
+ return readl(jz_clock_base + reg);
+}
+
+static void jz_clk_reg_write_mask(int reg, uint32_t val, uint32_t mask)
+{
+ uint32_t val2;
+
+ spin_lock(&jz_clock_lock);
+ val2 = readl(jz_clock_base + reg);
+ val2 &= ~mask;
+ val2 |= val;
+ writel(val2, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static void jz_clk_reg_set_bits(int reg, uint32_t mask)
+{
+ uint32_t val;
+
+ spin_lock(&jz_clock_lock);
+ val = readl(jz_clock_base + reg);
+ val |= mask;
+ writel(val, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static void jz_clk_reg_clear_bits(int reg, uint32_t mask)
+{
+ uint32_t val;
+
+ spin_lock(&jz_clock_lock);
+ val = readl(jz_clock_base + reg);
+ val &= ~mask;
+ writel(val, jz_clock_base + reg);
+ spin_unlock(&jz_clock_lock);
+}
+
+static int jz_clk_enable_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return -EINVAL;
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE, clk->gate_bit);
+ return 0;
+}
+
+static int jz_clk_disable_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return -EINVAL;
+
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE, clk->gate_bit);
+ return 0;
+}
+
+static int jz_clk_is_enabled_gating(struct clk *clk)
+{
+ if (clk->gate_bit == JZ4740_CLK_NOT_GATED)
+ return 1;
+
+ return !(jz_clk_reg_read(JZ_REG_CLOCK_GATE) & clk->gate_bit);
+}
+
+static unsigned long jz_clk_static_get_rate(struct clk *clk)
+{
+ return ((struct static_clk *)clk)->rate;
+}
+
+static int jz_clk_ko_enable(struct clk *clk)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_KO_ENABLE);
+ return 0;
+}
+
+static int jz_clk_ko_disable(struct clk *clk)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_KO_ENABLE);
+ return 0;
+}
+
+static int jz_clk_ko_is_enabled(struct clk *clk)
+{
+ return !!(jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_KO_ENABLE);
+}
+
+static const int pllno[] = {1, 2, 2, 4};
+
+static unsigned long jz_clk_pll_get_rate(struct clk *clk)
+{
+ uint32_t val;
+ int m;
+ int n;
+ int od;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_PLL);
+
+ if (val & JZ_CLOCK_PLL_BYPASS)
+ return clk_get_rate(clk->parent);
+
+ m = ((val >> 23) & 0x1ff) + 2;
+ n = ((val >> 18) & 0x1f) + 2;
+ od = (val >> 16) & 0x3;
+
+ return ((clk_get_rate(clk->parent) / n) * m) / pllno[od];
+}
+
+static unsigned long jz_clk_pll_half_get_rate(struct clk *clk)
+{
+ uint32_t reg;
+
+ reg = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+ if (reg & JZ_CLOCK_CTRL_PLL_HALF)
+ return jz_clk_pll_get_rate(clk->parent);
+ return jz_clk_pll_get_rate(clk->parent) >> 1;
+}
+
+static const int jz_clk_main_divs[] = {1, 2, 3, 4, 6, 8, 12, 16, 24, 32};
+
+static unsigned long jz_clk_main_round_rate(struct clk *clk, unsigned long rate)
+{
+ unsigned long parent_rate = jz_clk_pll_get_rate(clk->parent);
+ int div;
+
+ div = parent_rate / rate;
+ if (div > 32)
+ return parent_rate / 32;
+ else if (div < 1)
+ return parent_rate;
+
+ div &= (0x3 << (ffs(div) - 1));
+
+ return parent_rate / div;
+}
+
+static unsigned long jz_clk_main_get_rate(struct clk *clk)
+{
+ struct main_clk *mclk = (struct main_clk *)clk;
+ uint32_t div;
+
+ div = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+
+ div >>= mclk->div_offset;
+ div &= 0xf;
+
+ if (div >= ARRAY_SIZE(jz_clk_main_divs))
+ div = ARRAY_SIZE(jz_clk_main_divs) - 1;
+
+ return jz_clk_pll_get_rate(clk->parent) / jz_clk_main_divs[div];
+}
+
+static int jz_clk_main_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct main_clk *mclk = (struct main_clk *)clk;
+ int i;
+ int div;
+ unsigned long parent_rate = jz_clk_pll_get_rate(clk->parent);
+
+ rate = jz_clk_main_round_rate(clk, rate);
+
+ div = parent_rate / rate;
+
+ i = (ffs(div) - 1) << 1;
+ if (i > 0 && !(div & BIT(i-1)))
+ i -= 1;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, i << mclk->div_offset,
+ 0xf << mclk->div_offset);
+
+ return 0;
+}
+
+static struct clk_ops jz_clk_static_ops = {
+ .get_rate = jz_clk_static_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct static_clk jz_clk_ext = {
+ .clk = {
+ .name = "ext",
+ .gate_bit = JZ4740_CLK_NOT_GATED,
+ .ops = &jz_clk_static_ops,
+ },
+};
+
+static struct clk_ops jz_clk_pll_ops = {
+ .get_rate = jz_clk_pll_get_rate,
+};
+
+static struct clk jz_clk_pll = {
+ .name = "pll",
+ .parent = &jz_clk_ext.clk,
+ .ops = &jz_clk_pll_ops,
+};
+
+static struct clk_ops jz_clk_pll_half_ops = {
+ .get_rate = jz_clk_pll_half_get_rate,
+};
+
+static struct clk jz_clk_pll_half = {
+ .name = "pll half",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_pll_half_ops,
+};
+
+static const struct clk_ops jz_clk_main_ops = {
+ .get_rate = jz_clk_main_get_rate,
+ .set_rate = jz_clk_main_set_rate,
+ .round_rate = jz_clk_main_round_rate,
+};
+
+static struct main_clk jz_clk_cpu = {
+ .clk = {
+ .name = "cclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_CDIV_OFFSET,
+};
+
+static struct main_clk jz_clk_memory = {
+ .clk = {
+ .name = "mclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_MDIV_OFFSET,
+};
+
+static struct main_clk jz_clk_high_speed_peripheral = {
+ .clk = {
+ .name = "hclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_HDIV_OFFSET,
+};
+
+
+static struct main_clk jz_clk_low_speed_peripheral = {
+ .clk = {
+ .name = "pclk",
+ .parent = &jz_clk_pll,
+ .ops = &jz_clk_main_ops,
+ },
+ .div_offset = JZ_CLOCK_CTRL_PDIV_OFFSET,
+};
+
+static const struct clk_ops jz_clk_ko_ops = {
+ .enable = jz_clk_ko_enable,
+ .disable = jz_clk_ko_disable,
+ .is_enabled = jz_clk_ko_is_enabled,
+};
+
+static struct clk jz_clk_ko = {
+ .name = "cko",
+ .parent = &jz_clk_memory.clk,
+ .ops = &jz_clk_ko_ops,
+};
+
+static int jz_clk_spi_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll)
+ jz_clk_reg_set_bits(JZ_CLOCK_SPI_SRC_PLL, JZ_REG_CLOCK_SPI);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_CLOCK_SPI_SRC_PLL, JZ_REG_CLOCK_SPI);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_i2s_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll_half)
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_I2S_SRC_PLL);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_I2S_SRC_PLL);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_udc_enable(struct clk *clk)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_SLEEP_CTRL,
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+
+ return 0;
+}
+
+static int jz_clk_udc_disable(struct clk *clk)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_SLEEP_CTRL,
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+
+ return 0;
+}
+
+static int jz_clk_udc_is_enabled(struct clk *clk)
+{
+ return !!(jz_clk_reg_read(JZ_REG_CLOCK_SLEEP_CTRL) &
+ JZ_CLOCK_SLEEP_CTRL_ENABLE_UDC);
+}
+
+static int jz_clk_udc_set_parent(struct clk *clk, struct clk *parent)
+{
+ if (parent == &jz_clk_pll_half)
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_UDC_SRC_PLL);
+ else if (parent == &jz_clk_ext.clk)
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_CTRL, JZ_CLOCK_CTRL_UDC_SRC_PLL);
+ else
+ return -EINVAL;
+
+ clk->parent = parent;
+
+ return 0;
+}
+
+static int jz_clk_udc_set_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return -EINVAL;
+
+ div = clk_get_rate(clk->parent) / rate - 1;
+
+ if (div < 0)
+ div = 0;
+ else if (div > 63)
+ div = 63;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, div << JZ_CLOCK_CTRL_UDIV_OFFSET,
+ JZ_CLOCK_CTRL_UDIV_MASK);
+ return 0;
+}
+
+static unsigned long jz_clk_udc_get_rate(struct clk *clk)
+{
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return clk_get_rate(clk->parent);
+
+ div = (jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_UDIV_MASK);
+ div >>= JZ_CLOCK_CTRL_UDIV_OFFSET;
+ div += 1;
+
+ return clk_get_rate(clk->parent) / div;
+}
+
+static unsigned long jz_clk_divided_get_rate(struct clk *clk)
+{
+ struct divided_clk *dclk = (struct divided_clk *)clk;
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return clk_get_rate(clk->parent);
+
+ div = (jz_clk_reg_read(dclk->reg) & dclk->mask) + 1;
+
+ return clk_get_rate(clk->parent) / div;
+}
+
+static int jz_clk_divided_set_rate(struct clk *clk, unsigned long rate)
+{
+ struct divided_clk *dclk = (struct divided_clk *)clk;
+ int div;
+
+ if (clk->parent == &jz_clk_ext.clk)
+ return -EINVAL;
+
+ div = clk_get_rate(clk->parent) / rate - 1;
+
+ if (div < 0)
+ div = 0;
+ else if (div > dclk->mask)
+ div = dclk->mask;
+
+ jz_clk_reg_write_mask(dclk->reg, div, dclk->mask);
+
+ return 0;
+}
+
+static unsigned long jz_clk_ldclk_round_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+ unsigned long parent_rate = jz_clk_pll_half_get_rate(clk->parent);
+
+ if (rate > 150000000)
+ return 150000000;
+
+ div = parent_rate / rate;
+ if (div < 1)
+ div = 1;
+ else if (div > 32)
+ div = 32;
+
+ return parent_rate / div;
+}
+
+static int jz_clk_ldclk_set_rate(struct clk *clk, unsigned long rate)
+{
+ int div;
+
+ if (rate > 150000000)
+ return -EINVAL;
+
+ div = jz_clk_pll_half_get_rate(clk->parent) / rate - 1;
+ if (div < 0)
+ div = 0;
+ else if (div > 31)
+ div = 31;
+
+ jz_clk_reg_write_mask(JZ_REG_CLOCK_CTRL, div << JZ_CLOCK_CTRL_LDIV_OFFSET,
+ JZ_CLOCK_CTRL_LDIV_MASK);
+
+ return 0;
+}
+
+static unsigned long jz_clk_ldclk_get_rate(struct clk *clk)
+{
+ int div;
+
+ div = jz_clk_reg_read(JZ_REG_CLOCK_CTRL) & JZ_CLOCK_CTRL_LDIV_MASK;
+ div >>= JZ_CLOCK_CTRL_LDIV_OFFSET;
+
+ return jz_clk_pll_half_get_rate(clk->parent) / (div + 1);
+}
+
+static const struct clk_ops jz_clk_ops_ld = {
+ .set_rate = jz_clk_ldclk_set_rate,
+ .get_rate = jz_clk_ldclk_get_rate,
+ .round_rate = jz_clk_ldclk_round_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct clk jz_clk_ld = {
+ .name = "lcd",
+ .gate_bit = JZ_CLOCK_GATE_LCD,
+ .parent = &jz_clk_pll_half,
+ .ops = &jz_clk_ops_ld,
+};
+
+static const struct clk_ops jz_clk_i2s_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+ .set_parent = jz_clk_i2s_set_parent,
+};
+
+static const struct clk_ops jz_clk_spi_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+ .set_parent = jz_clk_spi_set_parent,
+};
+
+static const struct clk_ops jz_clk_divided_ops = {
+ .set_rate = jz_clk_divided_set_rate,
+ .get_rate = jz_clk_divided_get_rate,
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct divided_clk jz4740_clock_divided_clks[] = {
+ [0] = {
+ .clk = {
+ .name = "i2s",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_I2S,
+ .ops = &jz_clk_i2s_ops,
+ },
+ .reg = JZ_REG_CLOCK_I2S,
+ .mask = JZ_CLOCK_I2S_DIV_MASK,
+ },
+ [1] = {
+ .clk = {
+ .name = "spi",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_SPI,
+ .ops = &jz_clk_spi_ops,
+ },
+ .reg = JZ_REG_CLOCK_SPI,
+ .mask = JZ_CLOCK_SPI_DIV_MASK,
+ },
+ [2] = {
+ .clk = {
+ .name = "lcd_pclk",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ4740_CLK_NOT_GATED,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_LCD,
+ .mask = JZ_CLOCK_LCD_DIV_MASK,
+ },
+ [3] = {
+ .clk = {
+ .name = "mmc",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ_CLOCK_GATE_MMC,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_MMC,
+ .mask = JZ_CLOCK_MMC_DIV_MASK,
+ },
+ [4] = {
+ .clk = {
+ .name = "uhc",
+ .parent = &jz_clk_pll_half,
+ .gate_bit = JZ_CLOCK_GATE_UHC,
+ .ops = &jz_clk_divided_ops,
+ },
+ .reg = JZ_REG_CLOCK_UHC,
+ .mask = JZ_CLOCK_UHC_DIV_MASK,
+ },
+};
+
+static const struct clk_ops jz_clk_udc_ops = {
+ .set_parent = jz_clk_udc_set_parent,
+ .set_rate = jz_clk_udc_set_rate,
+ .get_rate = jz_clk_udc_get_rate,
+ .enable = jz_clk_udc_enable,
+ .disable = jz_clk_udc_disable,
+ .is_enabled = jz_clk_udc_is_enabled,
+};
+
+static const struct clk_ops jz_clk_simple_ops = {
+ .enable = jz_clk_enable_gating,
+ .disable = jz_clk_disable_gating,
+ .is_enabled = jz_clk_is_enabled_gating,
+};
+
+static struct clk jz4740_clock_simple_clks[] = {
+ [0] = {
+ .name = "udc",
+ .parent = &jz_clk_ext.clk,
+ .ops = &jz_clk_udc_ops,
+ },
+ [1] = {
+ .name = "uart0",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART0,
+ .ops = &jz_clk_simple_ops,
+ },
+ [2] = {
+ .name = "uart1",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART1,
+ .ops = &jz_clk_simple_ops,
+ },
+ [3] = {
+ .name = "dma",
+ .parent = &jz_clk_high_speed_peripheral.clk,
+ .gate_bit = JZ_CLOCK_GATE_UART0,
+ .ops = &jz_clk_simple_ops,
+ },
+ [4] = {
+ .name = "ipu",
+ .parent = &jz_clk_high_speed_peripheral.clk,
+ .gate_bit = JZ_CLOCK_GATE_IPU,
+ .ops = &jz_clk_simple_ops,
+ },
+ [5] = {
+ .name = "adc",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_ADC,
+ .ops = &jz_clk_simple_ops,
+ },
+ [6] = {
+ .name = "i2c",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_I2C,
+ .ops = &jz_clk_simple_ops,
+ },
+ [7] = {
+ .name = "aic",
+ .parent = &jz_clk_ext.clk,
+ .gate_bit = JZ_CLOCK_GATE_AIC,
+ .ops = &jz_clk_simple_ops,
+ },
+};
+
+static struct static_clk jz_clk_rtc = {
+ .clk = {
+ .name = "rtc",
+ .gate_bit = JZ_CLOCK_GATE_RTC,
+ .ops = &jz_clk_static_ops,
+ },
+ .rate = 32768,
+};
+
+int clk_enable(struct clk *clk)
+{
+ if (!clk->ops->enable)
+ return -EINVAL;
+
+ return clk->ops->enable(clk);
+}
+EXPORT_SYMBOL_GPL(clk_enable);
+
+void clk_disable(struct clk *clk)
+{
+ if (clk->ops->disable)
+ clk->ops->disable(clk);
+}
+EXPORT_SYMBOL_GPL(clk_disable);
+
+int clk_is_enabled(struct clk *clk)
+{
+ if (clk->ops->is_enabled)
+ return clk->ops->is_enabled(clk);
+
+ return 1;
+}
+
+unsigned long clk_get_rate(struct clk *clk)
+{
+ if (clk->ops->get_rate)
+ return clk->ops->get_rate(clk);
+ if (clk->parent)
+ return clk_get_rate(clk->parent);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(clk_get_rate);
+
+int clk_set_rate(struct clk *clk, unsigned long rate)
+{
+ if (!clk->ops->set_rate)
+ return -EINVAL;
+ return clk->ops->set_rate(clk, rate);
+}
+EXPORT_SYMBOL_GPL(clk_set_rate);
+
+long clk_round_rate(struct clk *clk, unsigned long rate)
+{
+ if (clk->ops->round_rate)
+ return clk->ops->round_rate(clk, rate);
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(clk_round_rate);
+
+int clk_set_parent(struct clk *clk, struct clk *parent)
+{
+ int ret;
+ int enabled;
+
+ if (!clk->ops->set_parent)
+ return -EINVAL;
+
+ enabled = clk_is_enabled(clk);
+ if (enabled)
+ clk_disable(clk);
+ ret = clk->ops->set_parent(clk, parent);
+ if (enabled)
+ clk_enable(clk);
+
+ jz4740_clock_debugfs_update_parent(clk);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(clk_set_parent);
+
+struct clk *clk_get(struct device *dev, const char *name)
+{
+ struct clk *clk;
+
+ list_for_each_entry(clk, &jz_clocks, list) {
+ if (strcmp(clk->name, name) == 0)
+ return clk;
+ }
+ return ERR_PTR(-ENXIO);
+}
+EXPORT_SYMBOL_GPL(clk_get);
+
+void clk_put(struct clk *clk)
+{
+}
+EXPORT_SYMBOL_GPL(clk_put);
+
+static inline void clk_add(struct clk *clk)
+{
+ list_add_tail(&clk->list, &jz_clocks);
+
+ jz4740_clock_debugfs_add_clk(clk);
+}
+
+static void clk_register_clks(void)
+{
+ size_t i;
+
+ clk_add(&jz_clk_ext.clk);
+ clk_add(&jz_clk_pll);
+ clk_add(&jz_clk_pll_half);
+ clk_add(&jz_clk_cpu.clk);
+ clk_add(&jz_clk_high_speed_peripheral.clk);
+ clk_add(&jz_clk_low_speed_peripheral.clk);
+ clk_add(&jz_clk_ko);
+ clk_add(&jz_clk_ld);
+ clk_add(&jz_clk_rtc.clk);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_clock_divided_clks); ++i)
+ clk_add(&jz4740_clock_divided_clks[i].clk);
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_clock_simple_clks); ++i)
+ clk_add(&jz4740_clock_simple_clks[i]);
+}
+
+void jz4740_clock_set_wait_mode(enum jz4740_wait_mode mode)
+{
+ switch (mode) {
+ case JZ4740_WAIT_MODE_IDLE:
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_LOW_POWER, JZ_CLOCK_LOW_POWER_MODE_SLEEP);
+ break;
+ case JZ4740_WAIT_MODE_SLEEP:
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_LOW_POWER, JZ_CLOCK_LOW_POWER_MODE_SLEEP);
+ break;
+ }
+}
+
+void jz4740_clock_udc_disable_auto_suspend(void)
+{
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE, JZ_CLOCK_GATE_UDC);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_disable_auto_suspend);
+
+void jz4740_clock_udc_enable_auto_suspend(void)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE, JZ_CLOCK_GATE_UDC);
+}
+EXPORT_SYMBOL_GPL(jz4740_clock_udc_enable_auto_suspend);
+
+void jz4740_clock_suspend(void)
+{
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_GATE,
+ JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0);
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_PLL, JZ_CLOCK_PLL_ENABLED);
+}
+
+void jz4740_clock_resume(void)
+{
+ uint32_t pll;
+
+ jz_clk_reg_set_bits(JZ_REG_CLOCK_PLL, JZ_CLOCK_PLL_ENABLED);
+
+ do {
+ pll = jz_clk_reg_read(JZ_REG_CLOCK_PLL);
+ } while (!(pll & JZ_CLOCK_PLL_STABLE));
+
+ jz_clk_reg_clear_bits(JZ_REG_CLOCK_GATE,
+ JZ_CLOCK_GATE_TCU | JZ_CLOCK_GATE_DMAC | JZ_CLOCK_GATE_UART0);
+}
+
+static int jz4740_clock_init(void)
+{
+ uint32_t val;
+
+ jz_clock_base = ioremap(JZ4740_CPM_BASE_ADDR, 0x100);
+ if (!jz_clock_base)
+ return -EBUSY;
+
+ spin_lock_init(&jz_clock_lock);
+
+ jz_clk_ext.rate = jz4740_clock_bdata.ext_rate;
+ jz_clk_rtc.rate = jz4740_clock_bdata.rtc_rate;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_SPI);
+
+ if (val & JZ_CLOCK_SPI_SRC_PLL)
+ jz4740_clock_divided_clks[1].clk.parent = &jz_clk_pll_half;
+
+ val = jz_clk_reg_read(JZ_REG_CLOCK_CTRL);
+
+ if (val & JZ_CLOCK_CTRL_I2S_SRC_PLL)
+ jz4740_clock_divided_clks[0].clk.parent = &jz_clk_pll_half;
+
+ if (val & JZ_CLOCK_CTRL_UDC_SRC_PLL)
+ jz4740_clock_simple_clks[0].parent = &jz_clk_pll_half;
+
+ jz4740_clock_debugfs_init();
+
+ clk_register_clks();
+
+ return 0;
+}
+arch_initcall(jz4740_clock_init);
diff --git a/arch/mips/jz4740/clock.h b/arch/mips/jz4740/clock.h
new file mode 100644
index 0000000..5d07499
--- /dev/null
+++ b/arch/mips/jz4740/clock.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC clock support
+ *
+ * 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.
+ *
+ */
+
+#ifndef __MIPS_JZ4740_CLOCK_H__
+#define __MIPS_JZ4740_CLOCK_H__
+
+#include <linux/list.h>
+
+struct jz4740_clock_board_data {
+ unsigned long ext_rate;
+ unsigned long rtc_rate;
+};
+
+extern struct jz4740_clock_board_data jz4740_clock_bdata;
+
+void jz4740_clock_suspend(void);
+void jz4740_clock_resume(void);
+
+struct clk;
+
+struct clk_ops {
+ unsigned long (*get_rate)(struct clk *clk);
+ unsigned long (*round_rate)(struct clk *clk, unsigned long rate);
+ int (*set_rate)(struct clk *clk, unsigned long rate);
+ int (*enable)(struct clk *clk);
+ int (*disable)(struct clk *clk);
+ int (*is_enabled)(struct clk *clk);
+
+ int (*set_parent)(struct clk *clk, struct clk *parent);
+
+};
+
+struct clk {
+ const char *name;
+ struct clk *parent;
+
+ uint32_t gate_bit;
+
+ const struct clk_ops *ops;
+
+ struct list_head list;
+
+#ifdef CONFIG_DEBUG_FS
+ struct dentry *debugfs_entry;
+ struct dentry *debugfs_parent_entry;
+#endif
+
+};
+
+#define JZ4740_CLK_NOT_GATED ((uint32_t)-1)
+
+int clk_is_enabled(struct clk *clk);
+
+#ifdef CONFIG_DEBUG_FS
+void jz4740_clock_debugfs_init(void);
+void jz4740_clock_debugfs_add_clk(struct clk *clk);
+void jz4740_clock_debugfs_update_parent(struct clk *clk);
+#else
+static inline void jz4740_clock_debugfs_init(void) {};
+static inline void jz4740_clock_debugfs_add_clk(struct clk *clk) {};
+static inline void jz4740_clock_debugfs_update_parent(struct clk *clk) {};
+#endif
+
+#endif
--
1.5.6.5

2010-07-17 12:11:40

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] MIPS: JZ4740: Add gpio support

This patch adds gpiolib support for JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Fix possible irq setup race

Changes since v2
- Use __fls instead of ffs in the interupt demuxer
- Fix LCD pins wrongly labeld as MEM pins
---
arch/mips/include/asm/mach-jz4740/gpio.h | 398 ++++++++++++++++++++
arch/mips/jz4740/gpio.c | 604 ++++++++++++++++++++++++++++++
2 files changed, 1002 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h
create mode 100644 arch/mips/jz4740/gpio.c

diff --git a/arch/mips/include/asm/mach-jz4740/gpio.h b/arch/mips/include/asm/mach-jz4740/gpio.h
new file mode 100644
index 0000000..7b74703
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/gpio.h
@@ -0,0 +1,398 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ * JZ4740 GPIO pin definitions
+ *
+ * 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.
+ *
+ */
+
+#ifndef _JZ_GPIO_H
+#define _JZ_GPIO_H
+
+#include <linux/types.h>
+
+enum jz_gpio_function {
+ JZ_GPIO_FUNC_NONE,
+ JZ_GPIO_FUNC1,
+ JZ_GPIO_FUNC2,
+ JZ_GPIO_FUNC3,
+};
+
+
+/*
+ Usually a driver for a SoC component has to request several gpio pins and
+ configure them as funcion pins.
+ jz_gpio_bulk_request can be used to ease this process.
+ Usually one would do something like:
+
+ const static struct jz_gpio_bulk_request i2c_pins[] = {
+ JZ_GPIO_BULK_PIN(I2C_SDA),
+ JZ_GPIO_BULK_PIN(I2C_SCK),
+ };
+
+ inside the probe function:
+
+ ret = jz_gpio_bulk_request(i2c_pins, ARRAY_SIZE(i2c_pins));
+ if (ret) {
+ ...
+
+ inside the remove function:
+
+ jz_gpio_bulk_free(i2c_pins, ARRAY_SIZE(i2c_pins));
+
+
+*/
+struct jz_gpio_bulk_request {
+ int gpio;
+ const char *name;
+ enum jz_gpio_function function;
+};
+
+#define JZ_GPIO_BULK_PIN(pin) { \
+ .gpio = JZ_GPIO_ ## pin, \
+ .name = #pin, \
+ .function = JZ_GPIO_FUNC_ ## pin \
+}
+
+int jz_gpio_bulk_request(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_bulk_free(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_bulk_suspend(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_bulk_resume(const struct jz_gpio_bulk_request *request, size_t num);
+void jz_gpio_enable_pullup(unsigned gpio);
+void jz_gpio_disable_pullup(unsigned gpio);
+int jz_gpio_set_function(int gpio, enum jz_gpio_function function);
+
+int jz_gpio_port_direction_input(int port, uint32_t mask);
+int jz_gpio_port_direction_output(int port, uint32_t mask);
+void jz_gpio_port_set_value(int port, uint32_t value, uint32_t mask);
+uint32_t jz_gpio_port_get_value(int port, uint32_t mask);
+
+#include <asm/mach-generic/gpio.h>
+
+#define JZ_GPIO_PORTA(x) ((x) + 32 * 0)
+#define JZ_GPIO_PORTB(x) ((x) + 32 * 1)
+#define JZ_GPIO_PORTC(x) ((x) + 32 * 2)
+#define JZ_GPIO_PORTD(x) ((x) + 32 * 3)
+
+/* Port A function pins */
+#define JZ_GPIO_MEM_DATA0 JZ_GPIO_PORTA(0)
+#define JZ_GPIO_MEM_DATA1 JZ_GPIO_PORTA(1)
+#define JZ_GPIO_MEM_DATA2 JZ_GPIO_PORTA(2)
+#define JZ_GPIO_MEM_DATA3 JZ_GPIO_PORTA(3)
+#define JZ_GPIO_MEM_DATA4 JZ_GPIO_PORTA(4)
+#define JZ_GPIO_MEM_DATA5 JZ_GPIO_PORTA(5)
+#define JZ_GPIO_MEM_DATA6 JZ_GPIO_PORTA(6)
+#define JZ_GPIO_MEM_DATA7 JZ_GPIO_PORTA(7)
+#define JZ_GPIO_MEM_DATA8 JZ_GPIO_PORTA(8)
+#define JZ_GPIO_MEM_DATA9 JZ_GPIO_PORTA(9)
+#define JZ_GPIO_MEM_DATA10 JZ_GPIO_PORTA(10)
+#define JZ_GPIO_MEM_DATA11 JZ_GPIO_PORTA(11)
+#define JZ_GPIO_MEM_DATA12 JZ_GPIO_PORTA(12)
+#define JZ_GPIO_MEM_DATA13 JZ_GPIO_PORTA(13)
+#define JZ_GPIO_MEM_DATA14 JZ_GPIO_PORTA(14)
+#define JZ_GPIO_MEM_DATA15 JZ_GPIO_PORTA(15)
+#define JZ_GPIO_MEM_DATA16 JZ_GPIO_PORTA(16)
+#define JZ_GPIO_MEM_DATA17 JZ_GPIO_PORTA(17)
+#define JZ_GPIO_MEM_DATA18 JZ_GPIO_PORTA(18)
+#define JZ_GPIO_MEM_DATA19 JZ_GPIO_PORTA(19)
+#define JZ_GPIO_MEM_DATA20 JZ_GPIO_PORTA(20)
+#define JZ_GPIO_MEM_DATA21 JZ_GPIO_PORTA(21)
+#define JZ_GPIO_MEM_DATA22 JZ_GPIO_PORTA(22)
+#define JZ_GPIO_MEM_DATA23 JZ_GPIO_PORTA(23)
+#define JZ_GPIO_MEM_DATA24 JZ_GPIO_PORTA(24)
+#define JZ_GPIO_MEM_DATA25 JZ_GPIO_PORTA(25)
+#define JZ_GPIO_MEM_DATA26 JZ_GPIO_PORTA(26)
+#define JZ_GPIO_MEM_DATA27 JZ_GPIO_PORTA(27)
+#define JZ_GPIO_MEM_DATA28 JZ_GPIO_PORTA(28)
+#define JZ_GPIO_MEM_DATA29 JZ_GPIO_PORTA(29)
+#define JZ_GPIO_MEM_DATA30 JZ_GPIO_PORTA(30)
+#define JZ_GPIO_MEM_DATA31 JZ_GPIO_PORTA(31)
+
+#define JZ_GPIO_FUNC_MEM_DATA0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA4 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA5 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA6 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA7 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA8 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA9 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA10 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA11 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA12 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA13 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA14 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA15 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA16 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA17 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA18 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA19 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA20 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA21 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA22 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA23 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA24 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA25 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA26 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA27 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA28 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA29 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA30 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DATA31 JZ_GPIO_FUNC1
+
+/* Port B function pins */
+#define JZ_GPIO_MEM_ADDR0 JZ_GPIO_PORTB(0)
+#define JZ_GPIO_MEM_ADDR1 JZ_GPIO_PORTB(1)
+#define JZ_GPIO_MEM_ADDR2 JZ_GPIO_PORTB(2)
+#define JZ_GPIO_MEM_ADDR3 JZ_GPIO_PORTB(3)
+#define JZ_GPIO_MEM_ADDR4 JZ_GPIO_PORTB(4)
+#define JZ_GPIO_MEM_ADDR5 JZ_GPIO_PORTB(5)
+#define JZ_GPIO_MEM_ADDR6 JZ_GPIO_PORTB(6)
+#define JZ_GPIO_MEM_ADDR7 JZ_GPIO_PORTB(7)
+#define JZ_GPIO_MEM_ADDR8 JZ_GPIO_PORTB(8)
+#define JZ_GPIO_MEM_ADDR9 JZ_GPIO_PORTB(9)
+#define JZ_GPIO_MEM_ADDR10 JZ_GPIO_PORTB(10)
+#define JZ_GPIO_MEM_ADDR11 JZ_GPIO_PORTB(11)
+#define JZ_GPIO_MEM_ADDR12 JZ_GPIO_PORTB(12)
+#define JZ_GPIO_MEM_ADDR13 JZ_GPIO_PORTB(13)
+#define JZ_GPIO_MEM_ADDR14 JZ_GPIO_PORTB(14)
+#define JZ_GPIO_MEM_ADDR15 JZ_GPIO_PORTB(15)
+#define JZ_GPIO_MEM_ADDR16 JZ_GPIO_PORTB(16)
+#define JZ_GPIO_LCD_CLS JZ_GPIO_PORTB(17)
+#define JZ_GPIO_LCD_SPL JZ_GPIO_PORTB(18)
+#define JZ_GPIO_MEM_DCS JZ_GPIO_PORTB(19)
+#define JZ_GPIO_MEM_RAS JZ_GPIO_PORTB(20)
+#define JZ_GPIO_MEM_CAS JZ_GPIO_PORTB(21)
+#define JZ_GPIO_MEM_SDWE JZ_GPIO_PORTB(22)
+#define JZ_GPIO_MEM_CKE JZ_GPIO_PORTB(23)
+#define JZ_GPIO_MEM_CKO JZ_GPIO_PORTB(24)
+#define JZ_GPIO_MEM_CS0 JZ_GPIO_PORTB(25)
+#define JZ_GPIO_MEM_CS1 JZ_GPIO_PORTB(26)
+#define JZ_GPIO_MEM_CS2 JZ_GPIO_PORTB(27)
+#define JZ_GPIO_MEM_CS3 JZ_GPIO_PORTB(28)
+#define JZ_GPIO_MEM_RD JZ_GPIO_PORTB(29)
+#define JZ_GPIO_MEM_WR JZ_GPIO_PORTB(30)
+#define JZ_GPIO_MEM_WE0 JZ_GPIO_PORTB(31)
+
+#define JZ_GPIO_FUNC_MEM_ADDR0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR4 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR5 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR6 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR7 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR8 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR9 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR10 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR11 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR12 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR13 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR14 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR15 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_ADDR16 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_CLS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_SPL JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_DCS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_RAS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CAS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_SDWE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CKE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CKO JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_CS3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_RD JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WR JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE0 JZ_GPIO_FUNC1
+
+
+#define JZ_GPIO_MEM_ADDR21 JZ_GPIO_PORTB(17)
+#define JZ_GPIO_MEM_ADDR22 JZ_GPIO_PORTB(18)
+
+#define JZ_GPIO_FUNC_MEM_ADDR21 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR22 JZ_GPIO_FUNC2
+
+/* Port C function pins */
+#define JZ_GPIO_LCD_DATA0 JZ_GPIO_PORTC(0)
+#define JZ_GPIO_LCD_DATA1 JZ_GPIO_PORTC(1)
+#define JZ_GPIO_LCD_DATA2 JZ_GPIO_PORTC(2)
+#define JZ_GPIO_LCD_DATA3 JZ_GPIO_PORTC(3)
+#define JZ_GPIO_LCD_DATA4 JZ_GPIO_PORTC(4)
+#define JZ_GPIO_LCD_DATA5 JZ_GPIO_PORTC(5)
+#define JZ_GPIO_LCD_DATA6 JZ_GPIO_PORTC(6)
+#define JZ_GPIO_LCD_DATA7 JZ_GPIO_PORTC(7)
+#define JZ_GPIO_LCD_DATA8 JZ_GPIO_PORTC(8)
+#define JZ_GPIO_LCD_DATA9 JZ_GPIO_PORTC(9)
+#define JZ_GPIO_LCD_DATA10 JZ_GPIO_PORTC(10)
+#define JZ_GPIO_LCD_DATA11 JZ_GPIO_PORTC(11)
+#define JZ_GPIO_LCD_DATA12 JZ_GPIO_PORTC(12)
+#define JZ_GPIO_LCD_DATA13 JZ_GPIO_PORTC(13)
+#define JZ_GPIO_LCD_DATA14 JZ_GPIO_PORTC(14)
+#define JZ_GPIO_LCD_DATA15 JZ_GPIO_PORTC(15)
+#define JZ_GPIO_LCD_DATA16 JZ_GPIO_PORTC(16)
+#define JZ_GPIO_LCD_DATA17 JZ_GPIO_PORTC(17)
+#define JZ_GPIO_LCD_PCLK JZ_GPIO_PORTC(18)
+#define JZ_GPIO_LCD_HSYNC JZ_GPIO_PORTC(19)
+#define JZ_GPIO_LCD_VSYNC JZ_GPIO_PORTC(20)
+#define JZ_GPIO_LCD_DE JZ_GPIO_PORTC(21)
+#define JZ_GPIO_LCD_PS JZ_GPIO_PORTC(22)
+#define JZ_GPIO_LCD_REV JZ_GPIO_PORTC(23)
+#define JZ_GPIO_MEM_WE1 JZ_GPIO_PORTC(24)
+#define JZ_GPIO_MEM_WE2 JZ_GPIO_PORTC(25)
+#define JZ_GPIO_MEM_WE3 JZ_GPIO_PORTC(26)
+#define JZ_GPIO_MEM_WAIT JZ_GPIO_PORTC(27)
+#define JZ_GPIO_MEM_FRE JZ_GPIO_PORTC(28)
+#define JZ_GPIO_MEM_FWE JZ_GPIO_PORTC(29)
+
+#define JZ_GPIO_FUNC_LCD_DATA0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA4 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA5 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA6 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA7 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA8 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA9 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA10 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA11 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA12 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA13 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA14 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA15 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA16 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DATA17 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_PCLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_VSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_HSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_DE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_PS JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_LCD_REV JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE1 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE2 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WE3 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_WAIT JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_FRE JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MEM_FWE JZ_GPIO_FUNC1
+
+
+#define JZ_GPIO_MEM_ADDR19 JZ_GPIO_PORTB(22)
+#define JZ_GPIO_MEM_ADDR20 JZ_GPIO_PORTB(23)
+
+#define JZ_GPIO_FUNC_MEM_ADDR19 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR20 JZ_GPIO_FUNC2
+
+/* Port D function pins */
+#define JZ_GPIO_CIM_DATA0 JZ_GPIO_PORTD(0)
+#define JZ_GPIO_CIM_DATA1 JZ_GPIO_PORTD(1)
+#define JZ_GPIO_CIM_DATA2 JZ_GPIO_PORTD(2)
+#define JZ_GPIO_CIM_DATA3 JZ_GPIO_PORTD(3)
+#define JZ_GPIO_CIM_DATA4 JZ_GPIO_PORTD(4)
+#define JZ_GPIO_CIM_DATA5 JZ_GPIO_PORTD(5)
+#define JZ_GPIO_CIM_DATA6 JZ_GPIO_PORTD(6)
+#define JZ_GPIO_CIM_DATA7 JZ_GPIO_PORTD(7)
+#define JZ_GPIO_MSC_CMD JZ_GPIO_PORTD(8)
+#define JZ_GPIO_MSC_CLK JZ_GPIO_PORTD(9)
+#define JZ_GPIO_MSC_DATA0 JZ_GPIO_PORTD(10)
+#define JZ_GPIO_MSC_DATA1 JZ_GPIO_PORTD(11)
+#define JZ_GPIO_MSC_DATA2 JZ_GPIO_PORTD(12)
+#define JZ_GPIO_MSC_DATA3 JZ_GPIO_PORTD(13)
+#define JZ_GPIO_CIM_MCLK JZ_GPIO_PORTD(14)
+#define JZ_GPIO_CIM_PCLK JZ_GPIO_PORTD(15)
+#define JZ_GPIO_CIM_VSYNC JZ_GPIO_PORTD(16)
+#define JZ_GPIO_CIM_HSYNC JZ_GPIO_PORTD(17)
+#define JZ_GPIO_SPI_CLK JZ_GPIO_PORTD(18)
+#define JZ_GPIO_SPI_CE0 JZ_GPIO_PORTD(19)
+#define JZ_GPIO_SPI_DT JZ_GPIO_PORTD(20)
+#define JZ_GPIO_SPI_DR JZ_GPIO_PORTD(21)
+#define JZ_GPIO_SPI_CE1 JZ_GPIO_PORTD(22)
+#define JZ_GPIO_PWM0 JZ_GPIO_PORTD(23)
+#define JZ_GPIO_PWM1 JZ_GPIO_PORTD(24)
+#define JZ_GPIO_PWM2 JZ_GPIO_PORTD(25)
+#define JZ_GPIO_PWM3 JZ_GPIO_PORTD(26)
+#define JZ_GPIO_PWM4 JZ_GPIO_PORTD(27)
+#define JZ_GPIO_PWM5 JZ_GPIO_PORTD(28)
+#define JZ_GPIO_PWM6 JZ_GPIO_PORTD(30)
+#define JZ_GPIO_PWM7 JZ_GPIO_PORTD(31)
+
+#define JZ_GPIO_FUNC_CIM_DATA JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_DATA0 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA1 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA2 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA3 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA4 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA5 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA6 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_CIM_DATA7 JZ_GPIO_FUNC_CIM_DATA
+#define JZ_GPIO_FUNC_MSC_CMD JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MSC_CLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MSC_DATA JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_MSC_DATA0 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_MSC_DATA1 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_MSC_DATA2 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_MSC_DATA3 JZ_GPIO_FUNC_MSC_DATA
+#define JZ_GPIO_FUNC_CIM_MCLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_PCLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_VSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_CIM_HSYNC JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_CLK JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_CE0 JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_DT JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_DR JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_SPI_CE1 JZ_GPIO_FUNC1
+
+#define JZ_GPIO_FUNC_PWM JZ_GPIO_FUNC1
+#define JZ_GPIO_FUNC_PWM0 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM1 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM2 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM3 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM4 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM5 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM6 JZ_GPIO_FUNC_PWM
+#define JZ_GPIO_FUNC_PWM7 JZ_GPIO_FUNC_PWM
+
+#define JZ_GPIO_MEM_SCLK_RSTN JZ_GPIO_PORTD(18)
+#define JZ_GPIO_MEM_BCLK JZ_GPIO_PORTD(19)
+#define JZ_GPIO_MEM_SDATO JZ_GPIO_PORTD(20)
+#define JZ_GPIO_MEM_SDATI JZ_GPIO_PORTD(21)
+#define JZ_GPIO_MEM_SYNC JZ_GPIO_PORTD(22)
+#define JZ_GPIO_I2C_SDA JZ_GPIO_PORTD(23)
+#define JZ_GPIO_I2C_SCK JZ_GPIO_PORTD(24)
+#define JZ_GPIO_UART0_TXD JZ_GPIO_PORTD(25)
+#define JZ_GPIO_UART0_RXD JZ_GPIO_PORTD(26)
+#define JZ_GPIO_MEM_ADDR17 JZ_GPIO_PORTD(27)
+#define JZ_GPIO_MEM_ADDR18 JZ_GPIO_PORTD(28)
+#define JZ_GPIO_UART0_CTS JZ_GPIO_PORTD(30)
+#define JZ_GPIO_UART0_RTS JZ_GPIO_PORTD(31)
+
+#define JZ_GPIO_FUNC_MEM_SCLK_RSTN JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_BCLK JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_SDATO JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_SDATI JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_SYNC JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_I2C_SDA JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_I2C_SCK JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_TXD JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_RXD JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR17 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_MEM_ADDR18 JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_CTS JZ_GPIO_FUNC2
+#define JZ_GPIO_FUNC_UART0_RTS JZ_GPIO_FUNC2
+
+#define JZ_GPIO_UART1_RXD JZ_GPIO_PORTD(30)
+#define JZ_GPIO_UART1_TXD JZ_GPIO_PORTD(31)
+
+#define JZ_GPIO_FUNC_UART1_RXD JZ_GPIO_FUNC3
+#define JZ_GPIO_FUNC_UART1_TXD JZ_GPIO_FUNC3
+
+#endif
diff --git a/arch/mips/jz4740/gpio.c b/arch/mips/jz4740/gpio.c
new file mode 100644
index 0000000..38f60f3
--- /dev/null
+++ b/arch/mips/jz4740/gpio.c
@@ -0,0 +1,604 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform GPIO support
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+
+#include <linux/spinlock.h>
+#include <linux/sysdev.h>
+#include <linux/io.h>
+#include <linux/gpio.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#include <asm/mach-jz4740/base.h>
+
+#define JZ4740_GPIO_BASE_A (32*0)
+#define JZ4740_GPIO_BASE_B (32*1)
+#define JZ4740_GPIO_BASE_C (32*2)
+#define JZ4740_GPIO_BASE_D (32*3)
+
+#define JZ4740_GPIO_NUM_A 32
+#define JZ4740_GPIO_NUM_B 32
+#define JZ4740_GPIO_NUM_C 31
+#define JZ4740_GPIO_NUM_D 32
+
+#define JZ4740_IRQ_GPIO_BASE_A (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_A)
+#define JZ4740_IRQ_GPIO_BASE_B (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_B)
+#define JZ4740_IRQ_GPIO_BASE_C (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_C)
+#define JZ4740_IRQ_GPIO_BASE_D (JZ4740_IRQ_GPIO(0) + JZ4740_GPIO_BASE_D)
+
+#define JZ_REG_GPIO_PIN 0x00
+#define JZ_REG_GPIO_DATA 0x10
+#define JZ_REG_GPIO_DATA_SET 0x14
+#define JZ_REG_GPIO_DATA_CLEAR 0x18
+#define JZ_REG_GPIO_MASK 0x20
+#define JZ_REG_GPIO_MASK_SET 0x24
+#define JZ_REG_GPIO_MASK_CLEAR 0x28
+#define JZ_REG_GPIO_PULL 0x30
+#define JZ_REG_GPIO_PULL_SET 0x34
+#define JZ_REG_GPIO_PULL_CLEAR 0x38
+#define JZ_REG_GPIO_FUNC 0x40
+#define JZ_REG_GPIO_FUNC_SET 0x44
+#define JZ_REG_GPIO_FUNC_CLEAR 0x48
+#define JZ_REG_GPIO_SELECT 0x50
+#define JZ_REG_GPIO_SELECT_SET 0x54
+#define JZ_REG_GPIO_SELECT_CLEAR 0x58
+#define JZ_REG_GPIO_DIRECTION 0x60
+#define JZ_REG_GPIO_DIRECTION_SET 0x64
+#define JZ_REG_GPIO_DIRECTION_CLEAR 0x68
+#define JZ_REG_GPIO_TRIGGER 0x70
+#define JZ_REG_GPIO_TRIGGER_SET 0x74
+#define JZ_REG_GPIO_TRIGGER_CLEAR 0x78
+#define JZ_REG_GPIO_FLAG 0x80
+#define JZ_REG_GPIO_FLAG_CLEAR 0x14
+
+#define GPIO_TO_BIT(gpio) BIT(gpio & 0x1f)
+#define GPIO_TO_REG(gpio, reg) (gpio_to_jz_gpio_chip(gpio)->base + (reg))
+#define CHIP_TO_REG(chip, reg) (gpio_chip_to_jz_gpio_chip(chip)->base + (reg))
+
+struct jz_gpio_chip {
+ unsigned int irq;
+ unsigned int irq_base;
+ uint32_t wakeup;
+ uint32_t suspend_mask;
+ uint32_t edge_trigger_both;
+
+ void __iomem *base;
+
+ spinlock_t lock;
+
+ struct gpio_chip gpio_chip;
+ struct irq_chip irq_chip;
+ struct sys_device sysdev;
+};
+
+static struct jz_gpio_chip jz4740_gpio_chips[];
+
+static inline struct jz_gpio_chip *gpio_to_jz_gpio_chip(unsigned int gpio)
+{
+ return &jz4740_gpio_chips[gpio >> 5];
+}
+
+static inline struct jz_gpio_chip *gpio_chip_to_jz_gpio_chip(struct gpio_chip *gpio_chip)
+{
+ return container_of(gpio_chip, struct jz_gpio_chip, gpio_chip);
+}
+
+static inline struct jz_gpio_chip *irq_to_jz_gpio_chip(unsigned int irq)
+{
+ return get_irq_chip_data(irq);
+}
+
+static inline void jz_gpio_write_bit(unsigned int gpio, unsigned int reg)
+{
+ writel(GPIO_TO_BIT(gpio), GPIO_TO_REG(gpio, reg));
+}
+
+int jz_gpio_set_function(int gpio, enum jz_gpio_function function)
+{
+ if (function == JZ_GPIO_FUNC_NONE) {
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_FUNC_CLEAR);
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_SELECT_CLEAR);
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_TRIGGER_CLEAR);
+ } else {
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_FUNC_SET);
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_TRIGGER_CLEAR);
+ switch (function) {
+ case JZ_GPIO_FUNC1:
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_SELECT_CLEAR);
+ break;
+ case JZ_GPIO_FUNC3:
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_TRIGGER_SET);
+ case JZ_GPIO_FUNC2: /* Falltrough */
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_SELECT_SET);
+ break;
+ default:
+ BUG();
+ break;
+ }
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(jz_gpio_set_function);
+
+int jz_gpio_bulk_request(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+ int ret;
+
+ for (i = 0; i < num; ++i, ++request) {
+ ret = gpio_request(request->gpio, request->name);
+ if (ret)
+ goto err;
+ jz_gpio_set_function(request->gpio, request->function);
+ }
+
+ return 0;
+
+err:
+ for (--request; i > 0; --i, --request) {
+ gpio_free(request->gpio);
+ jz_gpio_set_function(request->gpio, JZ_GPIO_FUNC_NONE);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_request);
+
+void jz_gpio_bulk_free(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; ++i, ++request) {
+ gpio_free(request->gpio);
+ jz_gpio_set_function(request->gpio, JZ_GPIO_FUNC_NONE);
+ }
+
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_free);
+
+void jz_gpio_bulk_suspend(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; ++i, ++request) {
+ jz_gpio_set_function(request->gpio, JZ_GPIO_FUNC_NONE);
+ jz_gpio_write_bit(request->gpio, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_write_bit(request->gpio, JZ_REG_GPIO_PULL_SET);
+ }
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_suspend);
+
+void jz_gpio_bulk_resume(const struct jz_gpio_bulk_request *request, size_t num)
+{
+ size_t i;
+
+ for (i = 0; i < num; ++i, ++request)
+ jz_gpio_set_function(request->gpio, request->function);
+}
+EXPORT_SYMBOL_GPL(jz_gpio_bulk_resume);
+
+void jz_gpio_enable_pullup(unsigned gpio)
+{
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_PULL_CLEAR);
+}
+EXPORT_SYMBOL_GPL(jz_gpio_enable_pullup);
+
+void jz_gpio_disable_pullup(unsigned gpio)
+{
+ jz_gpio_write_bit(gpio, JZ_REG_GPIO_PULL_SET);
+}
+EXPORT_SYMBOL_GPL(jz_gpio_disable_pullup);
+
+static int jz_gpio_get_value(struct gpio_chip *chip, unsigned gpio)
+{
+ return !!(readl(CHIP_TO_REG(chip, JZ_REG_GPIO_PIN)) & BIT(gpio));
+}
+
+static void jz_gpio_set_value(struct gpio_chip *chip, unsigned gpio, int value)
+{
+ uint32_t __iomem *reg = CHIP_TO_REG(chip, JZ_REG_GPIO_DATA_SET);
+ reg += !value;
+ writel(BIT(gpio), reg);
+}
+
+static int jz_gpio_direction_output(struct gpio_chip *chip, unsigned gpio,
+ int value)
+{
+ writel(BIT(gpio), CHIP_TO_REG(chip, JZ_REG_GPIO_DIRECTION_SET));
+ jz_gpio_set_value(chip, gpio, value);
+
+ return 0;
+}
+
+static int jz_gpio_direction_input(struct gpio_chip *chip, unsigned gpio)
+{
+ writel(BIT(gpio), CHIP_TO_REG(chip, JZ_REG_GPIO_DIRECTION_CLEAR));
+
+ return 0;
+}
+
+int jz_gpio_port_direction_input(int port, uint32_t mask)
+{
+ writel(mask, GPIO_TO_REG(port, JZ_REG_GPIO_DIRECTION_CLEAR));
+
+ return 0;
+}
+EXPORT_SYMBOL(jz_gpio_port_direction_input);
+
+int jz_gpio_port_direction_output(int port, uint32_t mask)
+{
+ writel(mask, GPIO_TO_REG(port, JZ_REG_GPIO_DIRECTION_SET));
+
+ return 0;
+}
+EXPORT_SYMBOL(jz_gpio_port_direction_output);
+
+void jz_gpio_port_set_value(int port, uint32_t value, uint32_t mask)
+{
+ writel(~value & mask, GPIO_TO_REG(port, JZ_REG_GPIO_DATA_CLEAR));
+ writel(value & mask, GPIO_TO_REG(port, JZ_REG_GPIO_DATA_SET));
+}
+EXPORT_SYMBOL(jz_gpio_port_set_value);
+
+uint32_t jz_gpio_port_get_value(int port, uint32_t mask)
+{
+ uint32_t value = readl(GPIO_TO_REG(port, JZ_REG_GPIO_PIN));
+
+ return value & mask;
+}
+EXPORT_SYMBOL(jz_gpio_port_get_value);
+
+int gpio_to_irq(unsigned gpio)
+{
+ return JZ4740_IRQ_GPIO(0) + gpio;
+}
+EXPORT_SYMBOL_GPL(gpio_to_irq);
+
+int irq_to_gpio(unsigned irq)
+{
+ return irq - JZ4740_IRQ_GPIO(0);
+}
+EXPORT_SYMBOL_GPL(irq_to_gpio);
+
+#define IRQ_TO_BIT(irq) BIT(irq_to_gpio(irq) & 0x1f)
+
+static void jz_gpio_check_trigger_both(struct jz_gpio_chip *chip, unsigned int irq)
+{
+ uint32_t value;
+ void __iomem *reg;
+ uint32_t mask = IRQ_TO_BIT(irq);
+
+ if (!(chip->edge_trigger_both & mask))
+ return;
+
+ reg = chip->base;
+
+ value = readl(chip->base + JZ_REG_GPIO_PIN);
+ if (value & mask)
+ reg += JZ_REG_GPIO_DIRECTION_CLEAR;
+ else
+ reg += JZ_REG_GPIO_DIRECTION_SET;
+
+ writel(mask, reg);
+}
+
+static void jz_gpio_irq_demux_handler(unsigned int irq, struct irq_desc *desc)
+{
+ uint32_t flag;
+ unsigned int gpio_irq;
+ unsigned int gpio_bank;
+ struct jz_gpio_chip *chip = get_irq_desc_data(desc);
+
+ gpio_bank = JZ4740_IRQ_GPIO0 - irq;
+
+ flag = readl(chip->base + JZ_REG_GPIO_FLAG);
+
+ if (!flag)
+ return;
+
+ gpio_irq = __fls(flag);
+
+ jz_gpio_check_trigger_both(chip, irq);
+
+ gpio_irq += (gpio_bank << 5) + JZ4740_IRQ_GPIO(0);
+
+ generic_handle_irq(gpio_irq);
+};
+
+static inline void jz_gpio_set_irq_bit(unsigned int irq, unsigned int reg)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+ writel(IRQ_TO_BIT(irq), chip->base + reg);
+}
+
+static void jz_gpio_irq_mask(unsigned int irq)
+{
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_MASK_SET);
+};
+
+static void jz_gpio_irq_unmask(unsigned int irq)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+
+ jz_gpio_check_trigger_both(chip, irq);
+
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_MASK_CLEAR);
+};
+
+/* TODO: Check if function is gpio */
+static unsigned int jz_gpio_irq_startup(unsigned int irq)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_SELECT_SET);
+
+ desc->status &= ~IRQ_MASKED;
+ jz_gpio_irq_unmask(irq);
+
+ return 0;
+}
+
+static void jz_gpio_irq_shutdown(unsigned int irq)
+{
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ jz_gpio_irq_mask(irq);
+ desc->status |= IRQ_MASKED;
+
+ /* Set direction to input */
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_SELECT_CLEAR);
+}
+
+static void jz_gpio_irq_ack(unsigned int irq)
+{
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_FLAG_CLEAR);
+};
+
+static int jz_gpio_irq_set_type(unsigned int irq, unsigned int flow_type)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+ struct irq_desc *desc = irq_to_desc(irq);
+
+ jz_gpio_irq_mask(irq);
+
+ if (flow_type == IRQ_TYPE_EDGE_BOTH) {
+ uint32_t value = readl(chip->base + JZ_REG_GPIO_PIN);
+ if (value & IRQ_TO_BIT(irq))
+ flow_type = IRQ_TYPE_EDGE_FALLING;
+ else
+ flow_type = IRQ_TYPE_EDGE_RISING;
+ chip->edge_trigger_both |= IRQ_TO_BIT(irq);
+ } else {
+ chip->edge_trigger_both &= ~IRQ_TO_BIT(irq);
+ }
+
+ switch (flow_type) {
+ case IRQ_TYPE_EDGE_RISING:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_SET);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_SET);
+ break;
+ case IRQ_TYPE_EDGE_FALLING:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_SET);
+ break;
+ case IRQ_TYPE_LEVEL_HIGH:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_SET);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_CLEAR);
+ break;
+ case IRQ_TYPE_LEVEL_LOW:
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_DIRECTION_CLEAR);
+ jz_gpio_set_irq_bit(irq, JZ_REG_GPIO_TRIGGER_CLEAR);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (!(desc->status & IRQ_MASKED))
+ jz_gpio_irq_unmask(irq);
+
+ return 0;
+}
+
+static int jz_gpio_irq_set_wake(unsigned int irq, unsigned int on)
+{
+ struct jz_gpio_chip *chip = irq_to_jz_gpio_chip(irq);
+ spin_lock(&chip->lock);
+ if (on)
+ chip->wakeup |= IRQ_TO_BIT(irq);
+ else
+ chip->wakeup &= ~IRQ_TO_BIT(irq);
+ spin_unlock(&chip->lock);
+
+ set_irq_wake(chip->irq, on);
+ return 0;
+}
+
+/*
+ * This lock class tells lockdep that GPIO irqs are in a different
+ * category than their parents, so it won't report false recursion.
+ */
+static struct lock_class_key gpio_lock_class;
+
+#define JZ4740_GPIO_CHIP(_bank) { \
+ .irq_base = JZ4740_IRQ_GPIO_BASE_ ## _bank, \
+ .gpio_chip = { \
+ .label = "Bank " # _bank, \
+ .owner = THIS_MODULE, \
+ .set = jz_gpio_set_value, \
+ .get = jz_gpio_get_value, \
+ .direction_output = jz_gpio_direction_output, \
+ .direction_input = jz_gpio_direction_input, \
+ .base = JZ4740_GPIO_BASE_ ## _bank, \
+ .ngpio = JZ4740_GPIO_NUM_ ## _bank, \
+ }, \
+ .irq_chip = { \
+ .name = "GPIO Bank " # _bank, \
+ .mask = jz_gpio_irq_mask, \
+ .unmask = jz_gpio_irq_unmask, \
+ .ack = jz_gpio_irq_ack, \
+ .startup = jz_gpio_irq_startup, \
+ .shutdown = jz_gpio_irq_shutdown, \
+ .set_type = jz_gpio_irq_set_type, \
+ .set_wake = jz_gpio_irq_set_wake, \
+ }, \
+}
+
+static struct jz_gpio_chip jz4740_gpio_chips[] = {
+ JZ4740_GPIO_CHIP(A),
+ JZ4740_GPIO_CHIP(B),
+ JZ4740_GPIO_CHIP(C),
+ JZ4740_GPIO_CHIP(D),
+};
+
+static inline struct jz_gpio_chip *sysdev_to_chip(struct sys_device *dev)
+{
+ return container_of(dev, struct jz_gpio_chip, sysdev);
+}
+
+static int jz4740_gpio_suspend(struct sys_device *dev, pm_message_t state)
+{
+ struct jz_gpio_chip *chip = sysdev_to_chip(dev);
+
+ chip->suspend_mask = readl(chip->base + JZ_REG_GPIO_MASK);
+ writel(~(chip->wakeup), chip->base + JZ_REG_GPIO_MASK_SET);
+ writel(chip->wakeup, chip->base + JZ_REG_GPIO_MASK_CLEAR);
+
+ return 0;
+}
+
+static int jz4740_gpio_resume(struct sys_device *dev)
+{
+ struct jz_gpio_chip *chip = sysdev_to_chip(dev);
+ uint32_t mask = chip->suspend_mask;
+
+ writel(~mask, chip->base + JZ_REG_GPIO_MASK_CLEAR);
+ writel(mask, chip->base + JZ_REG_GPIO_MASK_SET);
+
+ return 0;
+}
+
+static struct sysdev_class jz4740_gpio_sysdev_class = {
+ .name = "gpio",
+ .suspend = jz4740_gpio_suspend,
+ .resume = jz4740_gpio_resume,
+};
+
+static int jz4740_gpio_chip_init(struct jz_gpio_chip *chip, unsigned int id)
+{
+ int ret, irq;
+
+ chip->sysdev.id = id;
+ chip->sysdev.cls = &jz4740_gpio_sysdev_class;
+ ret = sysdev_register(&chip->sysdev);
+
+ if (ret)
+ return ret;
+
+ spin_lock_init(&chip->lock);
+
+ chip->base = ioremap(JZ4740_GPIO_BASE_ADDR + (id * 0x100), 0x100);
+
+ gpiochip_add(&chip->gpio_chip);
+
+ chip->irq = JZ4740_IRQ_INTC_GPIO(id);
+ set_irq_data(chip->irq, chip);
+ set_irq_chained_handler(chip->irq, jz_gpio_irq_demux_handler);
+
+ for (irq = chip->irq_base; irq < chip->irq_base + chip->gpio_chip.ngpio; ++irq) {
+ lockdep_set_class(&irq_desc[irq].lock, &gpio_lock_class);
+ set_irq_chip_data(irq, chip);
+ set_irq_chip_and_handler(irq, &chip->irq_chip, handle_level_irq);
+ }
+
+ return 0;
+}
+
+static int __init jz4740_gpio_init(void)
+{
+ unsigned int i;
+ int ret;
+
+ ret = sysdev_class_register(&jz4740_gpio_sysdev_class);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_gpio_chips); ++i)
+ jz4740_gpio_chip_init(&jz4740_gpio_chips[i], i);
+
+ printk(KERN_INFO "JZ4740 GPIO initalized\n");
+
+ return 0;
+}
+arch_initcall(jz4740_gpio_init);
+
+#ifdef CONFIG_DEBUG_FS
+
+static inline void gpio_seq_reg(struct seq_file *s, struct jz_gpio_chip *chip,
+ const char *name, unsigned int reg)
+{
+ seq_printf(s, "\t%s: %08x\n", name, readl(chip->base + reg));
+}
+
+static int gpio_regs_show(struct seq_file *s, void *unused)
+{
+ struct jz_gpio_chip *chip = jz4740_gpio_chips;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(jz4740_gpio_chips); ++i, ++chip) {
+ seq_printf(s, "==GPIO %d==\n", i);
+ gpio_seq_reg(s, chip, "Pin", JZ_REG_GPIO_PIN);
+ gpio_seq_reg(s, chip, "Data", JZ_REG_GPIO_DATA);
+ gpio_seq_reg(s, chip, "Mask", JZ_REG_GPIO_MASK);
+ gpio_seq_reg(s, chip, "Pull", JZ_REG_GPIO_PULL);
+ gpio_seq_reg(s, chip, "Func", JZ_REG_GPIO_FUNC);
+ gpio_seq_reg(s, chip, "Select", JZ_REG_GPIO_SELECT);
+ gpio_seq_reg(s, chip, "Direction", JZ_REG_GPIO_DIRECTION);
+ gpio_seq_reg(s, chip, "Trigger", JZ_REG_GPIO_TRIGGER);
+ gpio_seq_reg(s, chip, "Flag", JZ_REG_GPIO_FLAG);
+ }
+
+ return 0;
+}
+
+static int gpio_regs_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, gpio_regs_show, NULL);
+}
+
+static const struct file_operations gpio_regs_operations = {
+ .open = gpio_regs_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init gpio_debugfs_init(void)
+{
+ (void) debugfs_create_file("jz_regs_gpio", S_IFREG | S_IRUGO,
+ NULL, NULL, &gpio_regs_operations);
+ return 0;
+}
+subsys_initcall(gpio_debugfs_init);
+
+#endif
--
1.5.6.5

2010-07-17 12:12:39

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v4] MIPS: JZ4740: Add PWM support

This patch adds support for the PWM part of the timer unit on a JZ4740 SoC.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v2
- Fix handling if the pwm clock is not available

Changes since v3
- Move pwm subsys_initcall to subsys_initcall
---
arch/mips/jz4740/pwm.c | 177 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 177 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/pwm.c

diff --git a/arch/mips/jz4740/pwm.c b/arch/mips/jz4740/pwm.c
new file mode 100644
index 0000000..a26a6fa
--- /dev/null
+++ b/arch/mips/jz4740/pwm.c
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform PWM support
+ *
+ * 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/kernel.h>
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/pwm.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-jz4740/gpio.h>
+#include "timer.h"
+
+static struct clk *jz4740_pwm_clk;
+
+DEFINE_MUTEX(jz4740_pwm_mutex);
+
+struct pwm_device {
+ unsigned int id;
+ unsigned int gpio;
+ bool used;
+};
+
+static struct pwm_device jz4740_pwm_list[] = {
+ { 2, JZ_GPIO_PWM2, false },
+ { 3, JZ_GPIO_PWM3, false },
+ { 4, JZ_GPIO_PWM4, false },
+ { 5, JZ_GPIO_PWM5, false },
+ { 6, JZ_GPIO_PWM6, false },
+ { 7, JZ_GPIO_PWM7, false },
+};
+
+struct pwm_device *pwm_request(int id, const char *label)
+{
+ int ret = 0;
+ struct pwm_device *pwm;
+
+ if (id < 2 || id > 7 || !jz4740_pwm_clk)
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(&jz4740_pwm_mutex);
+
+ pwm = &jz4740_pwm_list[id - 2];
+ if (pwm->used)
+ ret = -EBUSY;
+ else
+ pwm->used = true;
+
+ mutex_unlock(&jz4740_pwm_mutex);
+
+ if (ret)
+ return ERR_PTR(ret);
+
+ ret = gpio_request(pwm->gpio, label);
+
+ if (ret) {
+ printk(KERN_ERR "Failed to request pwm gpio: %d\n", ret);
+ pwm->used = false;
+ return ERR_PTR(ret);
+ }
+
+ jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_PWM);
+
+ jz4740_timer_start(id);
+
+ return pwm;
+}
+
+void pwm_free(struct pwm_device *pwm)
+{
+ pwm_disable(pwm);
+ jz4740_timer_set_ctrl(pwm->id, 0);
+
+ jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_NONE);
+ gpio_free(pwm->gpio);
+
+ jz4740_timer_stop(pwm->id);
+
+ pwm->used = false;
+}
+
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ unsigned long long tmp;
+ unsigned long period, duty;
+ unsigned int prescaler = 0;
+ unsigned int id = pwm->id;
+ uint16_t ctrl;
+ bool is_enabled;
+
+ if (duty_ns < 0 || duty_ns > period_ns)
+ return -EINVAL;
+
+ tmp = (unsigned long long)clk_get_rate(jz4740_pwm_clk) * period_ns;
+ do_div(tmp, 1000000000);
+ period = tmp;
+
+ while (period > 0xffff && prescaler < 6) {
+ period >>= 2;
+ ++prescaler;
+ }
+
+ if (prescaler == 6)
+ return -EINVAL;
+
+ tmp = (unsigned long long)period * duty_ns;
+ do_div(tmp, period_ns);
+ duty = period - tmp;
+
+ if (duty >= period)
+ duty = period - 1;
+
+ is_enabled = jz4740_timer_is_enabled(id);
+ if (is_enabled)
+ pwm_disable(pwm);
+
+ jz4740_timer_set_count(id, 0);
+ jz4740_timer_set_duty(id, duty);
+ jz4740_timer_set_period(id, period);
+
+ ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT |
+ JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN;
+
+ jz4740_timer_set_ctrl(id, ctrl);
+
+ if (is_enabled)
+ pwm_enable(pwm);
+
+ return 0;
+}
+
+int pwm_enable(struct pwm_device *pwm)
+{
+ uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+ ctrl |= JZ_TIMER_CTRL_PWM_ENABLE;
+ jz4740_timer_set_ctrl(pwm->id, ctrl);
+ jz4740_timer_enable(pwm->id);
+
+ return 0;
+}
+
+void pwm_disable(struct pwm_device *pwm)
+{
+ uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id);
+
+ ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE;
+ jz4740_timer_disable(pwm->id);
+ jz4740_timer_set_ctrl(pwm->id, ctrl);
+}
+
+static int __init jz4740_pwm_init(void)
+{
+ int ret = 0;
+
+ jz4740_pwm_clk = clk_get(NULL, "ext");
+
+ if (IS_ERR(jz4740_pwm_clk)) {
+ ret = PTR_ERR(jz4740_pwm_clk);
+ jz4740_pwm_clk = NULL;
+ }
+
+ return ret;
+}
+subsys_initcall(jz4740_pwm_init);
--
1.5.6.5

2010-07-17 12:13:48

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] MIPS: JZ4740: Add platform devices

This patch adds platform devices for all the JZ4740 platform drivers.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Add JZ4740 PCM device
- Add ADC MFD device and remove battery device

Changes since v2
- Add memory region for NAND bank
---
arch/mips/include/asm/mach-jz4740/platform.h | 36 ++++
arch/mips/jz4740/platform.c | 291 ++++++++++++++++++++++++++
2 files changed, 327 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h
create mode 100644 arch/mips/jz4740/platform.c

diff --git a/arch/mips/include/asm/mach-jz4740/platform.h b/arch/mips/include/asm/mach-jz4740/platform.h
new file mode 100644
index 0000000..8987a76
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/platform.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform device definitions
+ *
+ * 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.
+ *
+ */
+
+
+#ifndef __JZ4740_PLATFORM_H
+#define __JZ4740_PLATFORM_H
+
+#include <linux/platform_device.h>
+
+extern struct platform_device jz4740_usb_ohci_device;
+extern struct platform_device jz4740_udc_device;
+extern struct platform_device jz4740_mmc_device;
+extern struct platform_device jz4740_rtc_device;
+extern struct platform_device jz4740_i2c_device;
+extern struct platform_device jz4740_nand_device;
+extern struct platform_device jz4740_framebuffer_device;
+extern struct platform_device jz4740_i2s_device;
+extern struct platform_device jz4740_pcm_device;
+extern struct platform_device jz4740_codec_device;
+extern struct platform_device jz4740_adc_device;
+
+void jz4740_serial_device_register(void);
+
+#endif
diff --git a/arch/mips/jz4740/platform.c b/arch/mips/jz4740/platform.c
new file mode 100644
index 0000000..95bc2b5
--- /dev/null
+++ b/arch/mips/jz4740/platform.c
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 platform devices
+ *
+ * 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/device.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/resource.h>
+
+#include <linux/dma-mapping.h>
+
+#include <asm/mach-jz4740/platform.h>
+#include <asm/mach-jz4740/base.h>
+#include <asm/mach-jz4740/irq.h>
+
+#include <linux/serial_core.h>
+#include <linux/serial_8250.h>
+
+#include "serial.h"
+#include "clock.h"
+
+/* OHCI controller */
+static struct resource jz4740_usb_ohci_resources[] = {
+ {
+ .start = JZ4740_UHC_BASE_ADDR,
+ .end = JZ4740_UHC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_UHC,
+ .end = JZ4740_IRQ_UHC,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_usb_ohci_device = {
+ .name = "jz4740-ohci",
+ .id = -1,
+ .dev = {
+ .dma_mask = &jz4740_usb_ohci_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+ .num_resources = ARRAY_SIZE(jz4740_usb_ohci_resources),
+ .resource = jz4740_usb_ohci_resources,
+};
+
+/* UDC (USB gadget controller) */
+static struct resource jz4740_usb_gdt_resources[] = {
+ {
+ .start = JZ4740_UDC_BASE_ADDR,
+ .end = JZ4740_UDC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_UDC,
+ .end = JZ4740_IRQ_UDC,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_udc_device = {
+ .name = "jz-udc",
+ .id = -1,
+ .dev = {
+ .dma_mask = &jz4740_udc_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+ .num_resources = ARRAY_SIZE(jz4740_usb_gdt_resources),
+ .resource = jz4740_usb_gdt_resources,
+};
+
+/* MMC/SD controller */
+static struct resource jz4740_mmc_resources[] = {
+ {
+ .start = JZ4740_MSC_BASE_ADDR,
+ .end = JZ4740_MSC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_MSC,
+ .end = JZ4740_IRQ_MSC,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+struct platform_device jz4740_mmc_device = {
+ .name = "jz4740-mmc",
+ .id = 0,
+ .dev = {
+ .dma_mask = &jz4740_mmc_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+ .num_resources = ARRAY_SIZE(jz4740_mmc_resources),
+ .resource = jz4740_mmc_resources,
+};
+
+/* RTC controller */
+static struct resource jz4740_rtc_resources[] = {
+ {
+ .start = JZ4740_RTC_BASE_ADDR,
+ .end = JZ4740_RTC_BASE_ADDR + 0x38 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_RTC,
+ .end = JZ4740_IRQ_RTC,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_rtc_device = {
+ .name = "jz4740-rtc",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_rtc_resources),
+ .resource = jz4740_rtc_resources,
+};
+
+/* I2C controller */
+static struct resource jz4740_i2c_resources[] = {
+ {
+ .start = JZ4740_I2C_BASE_ADDR,
+ .end = JZ4740_I2C_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_I2C,
+ .end = JZ4740_IRQ_I2C,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+struct platform_device jz4740_i2c_device = {
+ .name = "jz4740-i2c",
+ .id = 0,
+ .num_resources = ARRAY_SIZE(jz4740_i2c_resources),
+ .resource = jz4740_i2c_resources,
+};
+
+/* NAND controller */
+static struct resource jz4740_nand_resources[] = {
+ {
+ .name = "mmio",
+ .start = JZ4740_EMC_BASE_ADDR,
+ .end = JZ4740_EMC_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .name = "bank",
+ .start = 0x18000000,
+ .end = 0x180C0000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_nand_device = {
+ .name = "jz4740-nand",
+ .num_resources = ARRAY_SIZE(jz4740_nand_resources),
+ .resource = jz4740_nand_resources,
+};
+
+/* LCD controller */
+static struct resource jz4740_framebuffer_resources[] = {
+ {
+ .start = JZ4740_LCD_BASE_ADDR,
+ .end = JZ4740_LCD_BASE_ADDR + 0x1000 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_framebuffer_device = {
+ .name = "jz4740-fb",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_framebuffer_resources),
+ .resource = jz4740_framebuffer_resources,
+ .dev = {
+ .dma_mask = &jz4740_framebuffer_device.dev.coherent_dma_mask,
+ .coherent_dma_mask = DMA_BIT_MASK(32),
+ },
+};
+
+/* I2S controller */
+static struct resource jz4740_i2s_resources[] = {
+ {
+ .start = JZ4740_AIC_BASE_ADDR,
+ .end = JZ4740_AIC_BASE_ADDR + 0x38 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_i2s_device = {
+ .name = "jz4740-i2s",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_i2s_resources),
+ .resource = jz4740_i2s_resources,
+};
+
+/* PCM */
+struct platform_device jz4740_pcm_device = {
+ .name = "jz4740-pcm",
+ .id = -1,
+};
+
+/* Codec */
+static struct resource jz4740_codec_resources[] = {
+ {
+ .start = JZ4740_AIC_BASE_ADDR + 0x80,
+ .end = JZ4740_AIC_BASE_ADDR + 0x88 - 1,
+ .flags = IORESOURCE_MEM,
+ },
+};
+
+struct platform_device jz4740_codec_device = {
+ .name = "jz4740-codec",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_codec_resources),
+ .resource = jz4740_codec_resources,
+};
+
+/* ADC controller */
+static struct resource jz4740_adc_resources[] = {
+ {
+ .start = JZ4740_SADC_BASE_ADDR,
+ .end = JZ4740_SADC_BASE_ADDR + 0x30,
+ .flags = IORESOURCE_MEM,
+ },
+ {
+ .start = JZ4740_IRQ_SADC,
+ .end = JZ4740_IRQ_SADC,
+ .flags = IORESOURCE_IRQ,
+ },
+ {
+ .start = JZ4740_IRQ_ADC_BASE,
+ .end = JZ4740_IRQ_ADC_BASE,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+struct platform_device jz4740_adc_device = {
+ .name = "jz4740-adc",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(jz4740_adc_resources),
+ .resource = jz4740_adc_resources,
+};
+
+/* Serial */
+#define JZ4740_UART_DATA(_id) \
+ { \
+ .flags = UPF_SKIP_TEST | UPF_IOREMAP | UPF_FIXED_TYPE, \
+ .iotype = UPIO_MEM, \
+ .regshift = 2, \
+ .serial_out = jz4740_serial_out, \
+ .type = PORT_16550, \
+ .mapbase = JZ4740_UART ## _id ## _BASE_ADDR, \
+ .irq = JZ4740_IRQ_UART ## _id, \
+ }
+
+static struct plat_serial8250_port jz4740_uart_data[] = {
+ JZ4740_UART_DATA(0),
+ JZ4740_UART_DATA(1),
+ {},
+};
+
+static struct platform_device jz4740_uart_device = {
+ .name = "serial8250",
+ .id = 0,
+ .dev = {
+ .platform_data = jz4740_uart_data,
+ },
+};
+
+void jz4740_serial_device_register(void)
+{
+ struct plat_serial8250_port *p;
+
+ for (p = jz4740_uart_data; p->flags != 0; ++p)
+ p->uartclk = jz4740_clock_bdata.ext_rate;
+
+ platform_device_register(&jz4740_uart_device);
+}
--
1.5.6.5

2010-07-17 12:14:55

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] fbdev: Add JZ4740 framebuffer driver

This patch adds support for the LCD controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: Andrew Morton <[email protected]>
Cc: [email protected]

--
Changes since v1
- Use __packed instead of __attribute__((packed))
- Make jzfb_fix const
- Only set mode in set_par if it has changed

Changes since v2
- Add support for special tft type lcds
- Move include file from include/linux to arch/mips/include/asm/mach-jz4740
---
arch/mips/include/asm/mach-jz4740/jz4740_fb.h | 67 ++
drivers/video/Kconfig | 9 +
drivers/video/Makefile | 1 +
drivers/video/jz4740_fb.c | 847 +++++++++++++++++++++++++
4 files changed, 924 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_fb.h
create mode 100644 drivers/video/jz4740_fb.c

diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_fb.h b/arch/mips/include/asm/mach-jz4740/jz4740_fb.h
new file mode 100644
index 0000000..6a50e6f
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/jz4740_fb.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009, Lars-Peter Clausen <[email protected]>
+ *
+ * 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.
+ *
+ */
+
+#ifndef __ASM_MACH_JZ4740_JZ4740_FB_H__
+#define __ASM_MACH_JZ4740_JZ4740_FB_H__
+
+#include <linux/fb.h>
+
+enum jz4740_fb_lcd_type {
+ JZ_LCD_TYPE_GENERIC_16_BIT = 0,
+ JZ_LCD_TYPE_GENERIC_18_BIT = 0 | (1 << 4),
+ JZ_LCD_TYPE_SPECIAL_TFT_1 = 1,
+ JZ_LCD_TYPE_SPECIAL_TFT_2 = 2,
+ JZ_LCD_TYPE_SPECIAL_TFT_3 = 3,
+ JZ_LCD_TYPE_NON_INTERLACED_CCIR656 = 5,
+ JZ_LCD_TYPE_INTERLACED_CCIR656 = 7,
+ JZ_LCD_TYPE_SINGLE_COLOR_STN = 8,
+ JZ_LCD_TYPE_SINGLE_MONOCHROME_STN = 9,
+ JZ_LCD_TYPE_DUAL_COLOR_STN = 10,
+ JZ_LCD_TYPE_DUAL_MONOCHROME_STN = 11,
+ JZ_LCD_TYPE_8BIT_SERIAL = 12,
+};
+
+#define JZ4740_FB_SPECIAL_TFT_CONFIG(start, stop) (((start) << 16) | (stop))
+
+/*
+* width: width of the lcd display in mm
+* height: height of the lcd display in mm
+* num_modes: size of modes
+* modes: list of valid video modes
+* bpp: bits per pixel for the lcd
+* lcd_type: lcd type
+*/
+
+struct jz4740_fb_platform_data {
+ unsigned int width;
+ unsigned int height;
+
+ size_t num_modes;
+ struct fb_videomode *modes;
+
+ unsigned int bpp;
+ enum jz4740_fb_lcd_type lcd_type;
+
+ struct {
+ uint32_t spl;
+ uint32_t cls;
+ uint32_t ps;
+ uint32_t rev;
+ } special_tft_config;
+
+ unsigned pixclk_falling_edge:1;
+ unsigned date_enable_active_low:1;
+};
+
+#endif
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index a9f9e5e..eae4c8a 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2237,6 +2237,15 @@ config FB_BROADSHEET
and could also have been called by other names when coupled with
a bridge adapter.

+config FB_JZ4740
+ tristate "JZ4740 LCD framebuffer support"
+ depends on FB
+ select FB_SYS_FILLRECT
+ select FB_SYS_COPYAREA
+ select FB_SYS_IMAGEBLIT
+ help
+ Framebuffer support for the JZ4740 SoC.
+
source "drivers/video/omap/Kconfig"
source "drivers/video/omap2/Kconfig"

diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index 3c3bf86..fd2df57 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -132,6 +132,7 @@ obj-$(CONFIG_FB_CARMINE) += carminefb.o
obj-$(CONFIG_FB_MB862XX) += mb862xx/
obj-$(CONFIG_FB_MSM) += msm/
obj-$(CONFIG_FB_NUC900) += nuc900fb.o
+obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o

# Platform or fallback drivers go here
obj-$(CONFIG_FB_UVESA) += uvesafb.o
diff --git a/drivers/video/jz4740_fb.c b/drivers/video/jz4740_fb.c
new file mode 100644
index 0000000..670ecaa
--- /dev/null
+++ b/drivers/video/jz4740_fb.c
@@ -0,0 +1,847 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC LCD framebuffer 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/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+
+#include <linux/console.h>
+#include <linux/fb.h>
+
+#include <linux/dma-mapping.h>
+
+#include <asm/mach-jz4740/jz4740_fb.h>
+#include <asm/mach-jz4740/gpio.h>
+
+#define JZ_REG_LCD_CFG 0x00
+#define JZ_REG_LCD_VSYNC 0x04
+#define JZ_REG_LCD_HSYNC 0x08
+#define JZ_REG_LCD_VAT 0x0C
+#define JZ_REG_LCD_DAH 0x10
+#define JZ_REG_LCD_DAV 0x14
+#define JZ_REG_LCD_PS 0x18
+#define JZ_REG_LCD_CLS 0x1C
+#define JZ_REG_LCD_SPL 0x20
+#define JZ_REG_LCD_REV 0x24
+#define JZ_REG_LCD_CTRL 0x30
+#define JZ_REG_LCD_STATE 0x34
+#define JZ_REG_LCD_IID 0x38
+#define JZ_REG_LCD_DA0 0x40
+#define JZ_REG_LCD_SA0 0x44
+#define JZ_REG_LCD_FID0 0x48
+#define JZ_REG_LCD_CMD0 0x4C
+#define JZ_REG_LCD_DA1 0x50
+#define JZ_REG_LCD_SA1 0x54
+#define JZ_REG_LCD_FID1 0x58
+#define JZ_REG_LCD_CMD1 0x5C
+
+#define JZ_LCD_CFG_SLCD BIT(31)
+#define JZ_LCD_CFG_PS_DISABLE BIT(23)
+#define JZ_LCD_CFG_CLS_DISABLE BIT(22)
+#define JZ_LCD_CFG_SPL_DISABLE BIT(21)
+#define JZ_LCD_CFG_REV_DISABLE BIT(20)
+#define JZ_LCD_CFG_HSYNCM BIT(19)
+#define JZ_LCD_CFG_PCLKM BIT(18)
+#define JZ_LCD_CFG_INV BIT(17)
+#define JZ_LCD_CFG_SYNC_DIR BIT(16)
+#define JZ_LCD_CFG_PS_POLARITY BIT(15)
+#define JZ_LCD_CFG_CLS_POLARITY BIT(14)
+#define JZ_LCD_CFG_SPL_POLARITY BIT(13)
+#define JZ_LCD_CFG_REV_POLARITY BIT(12)
+#define JZ_LCD_CFG_HSYNC_ACTIVE_LOW BIT(11)
+#define JZ_LCD_CFG_PCLK_FALLING_EDGE BIT(10)
+#define JZ_LCD_CFG_DE_ACTIVE_LOW BIT(9)
+#define JZ_LCD_CFG_VSYNC_ACTIVE_LOW BIT(8)
+#define JZ_LCD_CFG_18_BIT BIT(7)
+#define JZ_LCD_CFG_PDW (BIT(5) | BIT(4))
+#define JZ_LCD_CFG_MODE_MASK 0xf
+
+#define JZ_LCD_CTRL_BURST_4 (0x0 << 28)
+#define JZ_LCD_CTRL_BURST_8 (0x1 << 28)
+#define JZ_LCD_CTRL_BURST_16 (0x2 << 28)
+#define JZ_LCD_CTRL_RGB555 BIT(27)
+#define JZ_LCD_CTRL_OFUP BIT(26)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_16 (0x0 << 24)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_4 (0x1 << 24)
+#define JZ_LCD_CTRL_FRC_GRAYSCALE_2 (0x2 << 24)
+#define JZ_LCD_CTRL_PDD_MASK (0xff << 16)
+#define JZ_LCD_CTRL_EOF_IRQ BIT(13)
+#define JZ_LCD_CTRL_SOF_IRQ BIT(12)
+#define JZ_LCD_CTRL_OFU_IRQ BIT(11)
+#define JZ_LCD_CTRL_IFU0_IRQ BIT(10)
+#define JZ_LCD_CTRL_IFU1_IRQ BIT(9)
+#define JZ_LCD_CTRL_DD_IRQ BIT(8)
+#define JZ_LCD_CTRL_QDD_IRQ BIT(7)
+#define JZ_LCD_CTRL_REVERSE_ENDIAN BIT(6)
+#define JZ_LCD_CTRL_LSB_FISRT BIT(5)
+#define JZ_LCD_CTRL_DISABLE BIT(4)
+#define JZ_LCD_CTRL_ENABLE BIT(3)
+#define JZ_LCD_CTRL_BPP_1 0x0
+#define JZ_LCD_CTRL_BPP_2 0x1
+#define JZ_LCD_CTRL_BPP_4 0x2
+#define JZ_LCD_CTRL_BPP_8 0x3
+#define JZ_LCD_CTRL_BPP_15_16 0x4
+#define JZ_LCD_CTRL_BPP_18_24 0x5
+
+#define JZ_LCD_CMD_SOF_IRQ BIT(15)
+#define JZ_LCD_CMD_EOF_IRQ BIT(16)
+#define JZ_LCD_CMD_ENABLE_PAL BIT(12)
+
+#define JZ_LCD_SYNC_MASK 0x3ff
+
+#define JZ_LCD_STATE_DISABLED BIT(0)
+
+struct jzfb_framedesc {
+ uint32_t next;
+ uint32_t addr;
+ uint32_t id;
+ uint32_t cmd;
+} __packed;
+
+struct jzfb {
+ struct fb_info *fb;
+ struct platform_device *pdev;
+ void __iomem *base;
+ struct resource *mem;
+ struct jz4740_fb_platform_data *pdata;
+
+ size_t vidmem_size;
+ void *vidmem;
+ dma_addr_t vidmem_phys;
+ struct jzfb_framedesc *framedesc;
+ dma_addr_t framedesc_phys;
+
+ struct clk *ldclk;
+ struct clk *lpclk;
+
+ unsigned is_enabled:1;
+ struct mutex lock;
+
+ uint32_t pseudo_palette[16];
+};
+
+static const struct fb_fix_screeninfo jzfb_fix __devinitdata = {
+ .id = "JZ4740 FB",
+ .type = FB_TYPE_PACKED_PIXELS,
+ .visual = FB_VISUAL_TRUECOLOR,
+ .xpanstep = 0,
+ .ypanstep = 0,
+ .ywrapstep = 0,
+ .accel = FB_ACCEL_NONE,
+};
+
+static const struct jz_gpio_bulk_request jz_lcd_ctrl_pins[] = {
+ JZ_GPIO_BULK_PIN(LCD_PCLK),
+ JZ_GPIO_BULK_PIN(LCD_HSYNC),
+ JZ_GPIO_BULK_PIN(LCD_VSYNC),
+ JZ_GPIO_BULK_PIN(LCD_DE),
+ JZ_GPIO_BULK_PIN(LCD_PS),
+ JZ_GPIO_BULK_PIN(LCD_REV),
+ JZ_GPIO_BULK_PIN(LCD_CLS),
+ JZ_GPIO_BULK_PIN(LCD_SPL),
+};
+
+static const struct jz_gpio_bulk_request jz_lcd_data_pins[] = {
+ JZ_GPIO_BULK_PIN(LCD_DATA0),
+ JZ_GPIO_BULK_PIN(LCD_DATA1),
+ JZ_GPIO_BULK_PIN(LCD_DATA2),
+ JZ_GPIO_BULK_PIN(LCD_DATA3),
+ JZ_GPIO_BULK_PIN(LCD_DATA4),
+ JZ_GPIO_BULK_PIN(LCD_DATA5),
+ JZ_GPIO_BULK_PIN(LCD_DATA6),
+ JZ_GPIO_BULK_PIN(LCD_DATA7),
+ JZ_GPIO_BULK_PIN(LCD_DATA8),
+ JZ_GPIO_BULK_PIN(LCD_DATA9),
+ JZ_GPIO_BULK_PIN(LCD_DATA10),
+ JZ_GPIO_BULK_PIN(LCD_DATA11),
+ JZ_GPIO_BULK_PIN(LCD_DATA12),
+ JZ_GPIO_BULK_PIN(LCD_DATA13),
+ JZ_GPIO_BULK_PIN(LCD_DATA14),
+ JZ_GPIO_BULK_PIN(LCD_DATA15),
+ JZ_GPIO_BULK_PIN(LCD_DATA16),
+ JZ_GPIO_BULK_PIN(LCD_DATA17),
+};
+
+static unsigned int jzfb_num_ctrl_pins(struct jzfb *jzfb)
+{
+ unsigned int num;
+
+ switch (jzfb->pdata->lcd_type) {
+ case JZ_LCD_TYPE_GENERIC_16_BIT:
+ num = 4;
+ break;
+ case JZ_LCD_TYPE_GENERIC_18_BIT:
+ num = 4;
+ break;
+ case JZ_LCD_TYPE_8BIT_SERIAL:
+ num = 3;
+ break;
+ case JZ_LCD_TYPE_SPECIAL_TFT_1:
+ case JZ_LCD_TYPE_SPECIAL_TFT_2:
+ case JZ_LCD_TYPE_SPECIAL_TFT_3:
+ num = 8;
+ break;
+ default:
+ num = 0;
+ break;
+ }
+ return num;
+}
+
+static unsigned int jzfb_num_data_pins(struct jzfb *jzfb)
+{
+ unsigned int num;
+
+ switch (jzfb->pdata->lcd_type) {
+ case JZ_LCD_TYPE_GENERIC_16_BIT:
+ num = 16;
+ break;
+ case JZ_LCD_TYPE_GENERIC_18_BIT:
+ num = 18;
+ break;
+ case JZ_LCD_TYPE_8BIT_SERIAL:
+ num = 8;
+ break;
+ case JZ_LCD_TYPE_SPECIAL_TFT_1:
+ case JZ_LCD_TYPE_SPECIAL_TFT_2:
+ case JZ_LCD_TYPE_SPECIAL_TFT_3:
+ if (jzfb->pdata->bpp == 18)
+ num = 18;
+ else
+ num = 16;
+ break;
+ default:
+ num = 0;
+ break;
+ }
+ return num;
+}
+
+/* Based on CNVT_TOHW macro from skeletonfb.c */
+static inline uint32_t jzfb_convert_color_to_hw(unsigned val,
+ struct fb_bitfield *bf)
+{
+ return (((val << bf->length) + 0x7FFF - val) >> 16) << bf->offset;
+}
+
+static int jzfb_setcolreg(unsigned regno, unsigned red, unsigned green,
+ unsigned blue, unsigned transp, struct fb_info *fb)
+{
+ uint32_t color;
+
+ if (regno >= 16)
+ return -EINVAL;
+
+ color = jzfb_convert_color_to_hw(red, &fb->var.red);
+ color |= jzfb_convert_color_to_hw(green, &fb->var.green);
+ color |= jzfb_convert_color_to_hw(blue, &fb->var.blue);
+ color |= jzfb_convert_color_to_hw(transp, &fb->var.transp);
+
+ ((uint32_t *)(fb->pseudo_palette))[regno] = color;
+
+ return 0;
+}
+
+static int jzfb_get_controller_bpp(struct jzfb *jzfb)
+{
+ switch (jzfb->pdata->bpp) {
+ case 18:
+ case 24:
+ return 32;
+ case 15:
+ return 16;
+ default:
+ return jzfb->pdata->bpp;
+ }
+}
+
+static struct fb_videomode *jzfb_get_mode(struct jzfb *jzfb,
+ struct fb_var_screeninfo *var)
+{
+ size_t i;
+ struct fb_videomode *mode = jzfb->pdata->modes;
+
+ for (i = 0; i < jzfb->pdata->num_modes; ++i, ++mode) {
+ if (mode->xres == var->xres && mode->yres == var->yres)
+ return mode;
+ }
+
+ return NULL;
+}
+
+static int jzfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fb)
+{
+ struct jzfb *jzfb = fb->par;
+ struct fb_videomode *mode;
+
+ if (var->bits_per_pixel != jzfb_get_controller_bpp(jzfb) &&
+ var->bits_per_pixel != jzfb->pdata->bpp)
+ return -EINVAL;
+
+ mode = jzfb_get_mode(jzfb, var);
+ if (mode == NULL)
+ return -EINVAL;
+
+ fb_videomode_to_var(var, mode);
+
+ switch (jzfb->pdata->bpp) {
+ case 8:
+ break;
+ case 15:
+ var->red.offset = 10;
+ var->red.length = 5;
+ var->green.offset = 6;
+ var->green.length = 5;
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ break;
+ case 16:
+ var->red.offset = 11;
+ var->red.length = 5;
+ var->green.offset = 5;
+ var->green.length = 6;
+ var->blue.offset = 0;
+ var->blue.length = 5;
+ break;
+ case 18:
+ var->red.offset = 16;
+ var->red.length = 6;
+ var->green.offset = 8;
+ var->green.length = 6;
+ var->blue.offset = 0;
+ var->blue.length = 6;
+ var->bits_per_pixel = 32;
+ break;
+ case 32:
+ case 24:
+ var->transp.offset = 24;
+ var->transp.length = 8;
+ var->red.offset = 16;
+ var->red.length = 8;
+ var->green.offset = 8;
+ var->green.length = 8;
+ var->blue.offset = 0;
+ var->blue.length = 8;
+ var->bits_per_pixel = 32;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int jzfb_set_par(struct fb_info *info)
+{
+ struct jzfb *jzfb = info->par;
+ struct jz4740_fb_platform_data *pdata = jzfb->pdata;
+ struct fb_var_screeninfo *var = &info->var;
+ struct fb_videomode *mode;
+ uint16_t hds, vds;
+ uint16_t hde, vde;
+ uint16_t ht, vt;
+ uint32_t ctrl;
+ uint32_t cfg;
+ unsigned long rate;
+
+ mode = jzfb_get_mode(jzfb, var);
+ if (mode == NULL)
+ return -EINVAL;
+
+ if (mode == info->mode)
+ return 0;
+
+ info->mode = mode;
+
+ hds = mode->hsync_len + mode->left_margin;
+ hde = hds + mode->xres;
+ ht = hde + mode->right_margin;
+
+ vds = mode->vsync_len + mode->upper_margin;
+ vde = vds + mode->yres;
+ vt = vde + mode->lower_margin;
+
+ ctrl = JZ_LCD_CTRL_OFUP | JZ_LCD_CTRL_BURST_16;
+
+ switch (pdata->bpp) {
+ case 1:
+ ctrl |= JZ_LCD_CTRL_BPP_1;
+ break;
+ case 2:
+ ctrl |= JZ_LCD_CTRL_BPP_2;
+ break;
+ case 4:
+ ctrl |= JZ_LCD_CTRL_BPP_4;
+ break;
+ case 8:
+ ctrl |= JZ_LCD_CTRL_BPP_8;
+ break;
+ case 15:
+ ctrl |= JZ_LCD_CTRL_RGB555; /* Falltrough */
+ case 16:
+ ctrl |= JZ_LCD_CTRL_BPP_15_16;
+ break;
+ case 18:
+ case 24:
+ case 32:
+ ctrl |= JZ_LCD_CTRL_BPP_18_24;
+ break;
+ default:
+ break;
+ }
+
+ cfg = pdata->lcd_type & 0xf;
+
+ if (!(mode->sync & FB_SYNC_HOR_HIGH_ACT))
+ cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
+
+ if (!(mode->sync & FB_SYNC_VERT_HIGH_ACT))
+ cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW;
+
+ if (pdata->pixclk_falling_edge)
+ cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
+
+ if (pdata->date_enable_active_low)
+ cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW;
+
+ if (pdata->lcd_type == JZ_LCD_TYPE_GENERIC_18_BIT)
+ cfg |= JZ_LCD_CFG_18_BIT;
+
+ if (mode->pixclock) {
+ rate = PICOS2KHZ(mode->pixclock) * 1000;
+ mode->refresh = rate / vt / ht;
+ } else {
+ if (pdata->lcd_type == JZ_LCD_TYPE_8BIT_SERIAL)
+ rate = mode->refresh * (vt + 2 * mode->xres) * ht;
+ else
+ rate = mode->refresh * vt * ht;
+
+ mode->pixclock = KHZ2PICOS(rate / 1000);
+ }
+
+ mutex_lock(&jzfb->lock);
+ if (!jzfb->is_enabled)
+ clk_enable(jzfb->ldclk);
+ else
+ ctrl |= JZ_LCD_CTRL_ENABLE;
+
+ switch (pdata->lcd_type) {
+ case JZ_LCD_TYPE_SPECIAL_TFT_1:
+ case JZ_LCD_TYPE_SPECIAL_TFT_2:
+ case JZ_LCD_TYPE_SPECIAL_TFT_3:
+ writel(pdata->special_tft_config.spl, jzfb->base + JZ_REG_LCD_SPL);
+ writel(pdata->special_tft_config.cls, jzfb->base + JZ_REG_LCD_CLS);
+ writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_PS);
+ writel(pdata->special_tft_config.ps, jzfb->base + JZ_REG_LCD_REV);
+ break;
+ default:
+ cfg |= JZ_LCD_CFG_PS_DISABLE;
+ cfg |= JZ_LCD_CFG_CLS_DISABLE;
+ cfg |= JZ_LCD_CFG_SPL_DISABLE;
+ cfg |= JZ_LCD_CFG_REV_DISABLE;
+ break;
+ }
+
+ writel(mode->hsync_len, jzfb->base + JZ_REG_LCD_HSYNC);
+ writel(mode->vsync_len, jzfb->base + JZ_REG_LCD_VSYNC);
+
+ writel((ht << 16) | vt, jzfb->base + JZ_REG_LCD_VAT);
+
+ writel((hds << 16) | hde, jzfb->base + JZ_REG_LCD_DAH);
+ writel((vds << 16) | vde, jzfb->base + JZ_REG_LCD_DAV);
+
+ writel(cfg, jzfb->base + JZ_REG_LCD_CFG);
+
+ writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
+
+ if (!jzfb->is_enabled)
+ clk_disable(jzfb->ldclk);
+
+ mutex_unlock(&jzfb->lock);
+
+ clk_set_rate(jzfb->lpclk, rate);
+ clk_set_rate(jzfb->ldclk, rate * 3);
+
+ return 0;
+}
+
+static void jzfb_enable(struct jzfb *jzfb)
+{
+ uint32_t ctrl;
+
+ clk_enable(jzfb->ldclk);
+
+ jz_gpio_bulk_resume(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_resume(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ writel(0, jzfb->base + JZ_REG_LCD_STATE);
+
+ writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
+
+ ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
+ ctrl |= JZ_LCD_CTRL_ENABLE;
+ ctrl &= ~JZ_LCD_CTRL_DISABLE;
+ writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
+}
+
+static void jzfb_disable(struct jzfb *jzfb)
+{
+ uint32_t ctrl;
+
+ ctrl = readl(jzfb->base + JZ_REG_LCD_CTRL);
+ ctrl |= JZ_LCD_CTRL_DISABLE;
+ writel(ctrl, jzfb->base + JZ_REG_LCD_CTRL);
+ do {
+ ctrl = readl(jzfb->base + JZ_REG_LCD_STATE);
+ } while (!(ctrl & JZ_LCD_STATE_DISABLED));
+
+ jz_gpio_bulk_suspend(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_suspend(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ clk_disable(jzfb->ldclk);
+}
+
+static int jzfb_blank(int blank_mode, struct fb_info *info)
+{
+ struct jzfb *jzfb = info->par;
+
+ switch (blank_mode) {
+ case FB_BLANK_UNBLANK:
+ mutex_lock(&jzfb->lock);
+ if (jzfb->is_enabled) {
+ mutex_unlock(&jzfb->lock);
+ return 0;
+ }
+
+ jzfb_enable(jzfb);
+ jzfb->is_enabled = 1;
+
+ mutex_unlock(&jzfb->lock);
+ break;
+ default:
+ mutex_lock(&jzfb->lock);
+ if (!jzfb->is_enabled) {
+ mutex_unlock(&jzfb->lock);
+ return 0;
+ }
+
+ jzfb_disable(jzfb);
+ jzfb->is_enabled = 0;
+
+ mutex_unlock(&jzfb->lock);
+ break;
+ }
+
+ return 0;
+}
+
+static int jzfb_alloc_devmem(struct jzfb *jzfb)
+{
+ int max_videosize = 0;
+ struct fb_videomode *mode = jzfb->pdata->modes;
+ void *page;
+ int i;
+
+ for (i = 0; i < jzfb->pdata->num_modes; ++mode, ++i) {
+ if (max_videosize < mode->xres * mode->yres)
+ max_videosize = mode->xres * mode->yres;
+ }
+
+ max_videosize *= jzfb_get_controller_bpp(jzfb) >> 3;
+
+ jzfb->framedesc = dma_alloc_coherent(&jzfb->pdev->dev,
+ sizeof(*jzfb->framedesc),
+ &jzfb->framedesc_phys, GFP_KERNEL);
+
+ if (!jzfb->framedesc)
+ return -ENOMEM;
+
+ jzfb->vidmem_size = PAGE_ALIGN(max_videosize);
+ jzfb->vidmem = dma_alloc_coherent(&jzfb->pdev->dev,
+ jzfb->vidmem_size,
+ &jzfb->vidmem_phys, GFP_KERNEL);
+
+ if (!jzfb->vidmem)
+ goto err_free_framedesc;
+
+ for (page = jzfb->vidmem;
+ page < jzfb->vidmem + PAGE_ALIGN(jzfb->vidmem_size);
+ page += PAGE_SIZE) {
+ SetPageReserved(virt_to_page(page));
+ }
+
+ jzfb->framedesc->next = jzfb->framedesc_phys;
+ jzfb->framedesc->addr = jzfb->vidmem_phys;
+ jzfb->framedesc->id = 0xdeafbead;
+ jzfb->framedesc->cmd = 0;
+ jzfb->framedesc->cmd |= max_videosize / 4;
+
+ return 0;
+
+err_free_framedesc:
+ dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc),
+ jzfb->framedesc, jzfb->framedesc_phys);
+ return -ENOMEM;
+}
+
+static void jzfb_free_devmem(struct jzfb *jzfb)
+{
+ dma_free_coherent(&jzfb->pdev->dev, jzfb->vidmem_size,
+ jzfb->vidmem, jzfb->vidmem_phys);
+ dma_free_coherent(&jzfb->pdev->dev, sizeof(*jzfb->framedesc),
+ jzfb->framedesc, jzfb->framedesc_phys);
+}
+
+static struct fb_ops jzfb_ops = {
+ .owner = THIS_MODULE,
+ .fb_check_var = jzfb_check_var,
+ .fb_set_par = jzfb_set_par,
+ .fb_blank = jzfb_blank,
+ .fb_fillrect = sys_fillrect,
+ .fb_copyarea = sys_copyarea,
+ .fb_imageblit = sys_imageblit,
+ .fb_setcolreg = jzfb_setcolreg,
+};
+
+static int __devinit jzfb_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jzfb *jzfb;
+ struct fb_info *fb;
+ struct jz4740_fb_platform_data *pdata = pdev->dev.platform_data;
+ struct resource *mem;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "Missing platform data\n");
+ return -ENXIO;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to get register memory resource\n");
+ return -ENXIO;
+ }
+
+ mem = request_mem_region(mem->start, resource_size(mem), pdev->name);
+ if (!mem) {
+ dev_err(&pdev->dev, "Failed to request register memory region\n");
+ return -EBUSY;
+ }
+
+ fb = framebuffer_alloc(sizeof(struct jzfb), &pdev->dev);
+ if (!fb) {
+ dev_err(&pdev->dev, "Failed to allocate framebuffer device\n");
+ ret = -ENOMEM;
+ goto err_release_mem_region;
+ }
+
+ fb->fbops = &jzfb_ops;
+ fb->flags = FBINFO_DEFAULT;
+
+ jzfb = fb->par;
+ jzfb->pdev = pdev;
+ jzfb->pdata = pdata;
+ jzfb->mem = mem;
+
+ jzfb->ldclk = clk_get(&pdev->dev, "lcd");
+ if (IS_ERR(jzfb->ldclk)) {
+ ret = PTR_ERR(jzfb->ldclk);
+ dev_err(&pdev->dev, "Failed to get lcd clock: %d\n", ret);
+ goto err_framebuffer_release;
+ }
+
+ jzfb->lpclk = clk_get(&pdev->dev, "lcd_pclk");
+ if (IS_ERR(jzfb->lpclk)) {
+ ret = PTR_ERR(jzfb->lpclk);
+ dev_err(&pdev->dev, "Failed to get lcd pixel clock: %d\n", ret);
+ goto err_put_ldclk;
+ }
+
+ jzfb->base = ioremap(mem->start, resource_size(mem));
+ if (!jzfb->base) {
+ dev_err(&pdev->dev, "Failed to ioremap register memory region\n");
+ ret = -EBUSY;
+ goto err_put_lpclk;
+ }
+
+ platform_set_drvdata(pdev, jzfb);
+
+ mutex_init(&jzfb->lock);
+
+ fb_videomode_to_modelist(pdata->modes, pdata->num_modes,
+ &fb->modelist);
+ fb_videomode_to_var(&fb->var, pdata->modes);
+ fb->var.bits_per_pixel = pdata->bpp;
+ jzfb_check_var(&fb->var, fb);
+
+ ret = jzfb_alloc_devmem(jzfb);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to allocate video memory\n");
+ goto err_iounmap;
+ }
+
+ fb->fix = jzfb_fix;
+ fb->fix.line_length = fb->var.bits_per_pixel * fb->var.xres / 8;
+ fb->fix.mmio_start = mem->start;
+ fb->fix.mmio_len = resource_size(mem);
+ fb->fix.smem_start = jzfb->vidmem_phys;
+ fb->fix.smem_len = fb->fix.line_length * fb->var.yres;
+ fb->screen_base = jzfb->vidmem;
+ fb->pseudo_palette = jzfb->pseudo_palette;
+
+ fb_alloc_cmap(&fb->cmap, 256, 0);
+
+ clk_enable(jzfb->ldclk);
+ jzfb->is_enabled = 1;
+
+ writel(jzfb->framedesc->next, jzfb->base + JZ_REG_LCD_DA0);
+
+ fb->mode = NULL;
+ jzfb_set_par(fb);
+
+ jz_gpio_bulk_request(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_request(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ ret = register_framebuffer(fb);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register framebuffer: %d\n", ret);
+ goto err_free_devmem;
+ }
+
+ jzfb->fb = fb;
+
+ return 0;
+
+err_free_devmem:
+ jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ fb_dealloc_cmap(&fb->cmap);
+ jzfb_free_devmem(jzfb);
+err_iounmap:
+ iounmap(jzfb->base);
+err_put_lpclk:
+ clk_put(jzfb->lpclk);
+err_put_ldclk:
+ clk_put(jzfb->ldclk);
+err_framebuffer_release:
+ framebuffer_release(fb);
+err_release_mem_region:
+ release_mem_region(mem->start, resource_size(mem));
+ return ret;
+}
+
+static int __devexit jzfb_remove(struct platform_device *pdev)
+{
+ struct jzfb *jzfb = platform_get_drvdata(pdev);
+
+ jzfb_blank(FB_BLANK_POWERDOWN, jzfb->fb);
+
+ jz_gpio_bulk_free(jz_lcd_ctrl_pins, jzfb_num_ctrl_pins(jzfb));
+ jz_gpio_bulk_free(jz_lcd_data_pins, jzfb_num_data_pins(jzfb));
+
+ iounmap(jzfb->base);
+ release_mem_region(jzfb->mem->start, resource_size(jzfb->mem));
+
+ fb_dealloc_cmap(&jzfb->fb->cmap);
+ jzfb_free_devmem(jzfb);
+
+ platform_set_drvdata(pdev, NULL);
+
+ clk_put(jzfb->lpclk);
+ clk_put(jzfb->ldclk);
+
+ framebuffer_release(jzfb->fb);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int jzfb_suspend(struct device *dev)
+{
+ struct jzfb *jzfb = dev_get_drvdata(dev);
+
+ acquire_console_sem();
+ fb_set_suspend(jzfb->fb, 1);
+ release_console_sem();
+
+ mutex_lock(&jzfb->lock);
+ if (jzfb->is_enabled)
+ jzfb_disable(jzfb);
+ mutex_unlock(&jzfb->lock);
+
+ return 0;
+}
+
+static int jzfb_resume(struct device *dev)
+{
+ struct jzfb *jzfb = dev_get_drvdata(dev);
+ clk_enable(jzfb->ldclk);
+
+ mutex_lock(&jzfb->lock);
+ if (jzfb->is_enabled)
+ jzfb_enable(jzfb);
+ mutex_unlock(&jzfb->lock);
+
+ acquire_console_sem();
+ fb_set_suspend(jzfb->fb, 0);
+ release_console_sem();
+
+ return 0;
+}
+
+static const struct dev_pm_ops jzfb_pm_ops = {
+ .suspend = jzfb_suspend,
+ .resume = jzfb_resume,
+ .poweroff = jzfb_suspend,
+ .restore = jzfb_resume,
+};
+
+#define JZFB_PM_OPS (&jzfb_pm_ops)
+
+#else
+#define JZFB_PM_OPS NULL
+#endif
+
+static struct platform_driver jzfb_driver = {
+ .probe = jzfb_probe,
+ .remove = __devexit_p(jzfb_remove),
+ .driver = {
+ .name = "jz4740-fb",
+ .pm = JZFB_PM_OPS,
+ },
+};
+
+static int __init jzfb_init(void)
+{
+ return platform_driver_register(&jzfb_driver);
+}
+module_init(jzfb_init);
+
+static void __exit jzfb_exit(void)
+{
+ platform_driver_unregister(&jzfb_driver);
+}
+module_exit(jzfb_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver");
+MODULE_ALIAS("platform:jz4740-fb");
--
1.5.6.5

2010-07-17 12:15:50

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] MTD: Nand: Add JZ4740 NAND driver

This patch adds support for the NAND controller on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <[email protected]>
Cc: David Woodhouse <[email protected]>
Cc: [email protected]

--
Changes since v1
- JZ4740: Remove debug macro
- Fix platform driver remove callback
- Add custom nand read/write callback since we need to support more then 64 ecc
bytes

Changes since v2
- Fix potential deadlock that can happen when the hardware is broken
- Move include file from include/linux/mtd/ to arch/mips/include/asm/mach-jz4740/
- {Enable,Disable} NAND-chip in {probe,remove}
- Supply memory bank address through platform resource
---
arch/mips/include/asm/mach-jz4740/jz4740_nand.h | 34 ++
drivers/mtd/nand/Kconfig | 6 +
drivers/mtd/nand/Makefile | 1 +
drivers/mtd/nand/jz4740_nand.c | 516 +++++++++++++++++++++++
4 files changed, 557 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_nand.h
create mode 100644 drivers/mtd/nand/jz4740_nand.c

diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_nand.h b/arch/mips/include/asm/mach-jz4740/jz4740_nand.h
new file mode 100644
index 0000000..bb5b9a4
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/jz4740_nand.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC NAND 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.
+ *
+ */
+
+#ifndef __ASM_MACH_JZ4740_JZ4740_NAND_H__
+#define __ASM_MACH_JZ4740_JZ4740_NAND_H__
+
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+
+struct jz_nand_platform_data {
+ int num_partitions;
+ struct mtd_partition *partitions;
+
+ struct nand_ecclayout *ecc_layout;
+
+ unsigned int busy_gpio;
+
+ void (*ident_callback)(struct platform_device *, struct nand_chip *,
+ struct mtd_partition **, int *num_partitions);
+};
+
+#endif
diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index ffc3720..362d177 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -526,4 +526,10 @@ config MTD_NAND_NUC900
This enables the driver for the NAND Flash on evaluation board based
on w90p910 / NUC9xx.

+config MTD_NAND_JZ4740
+ tristate "Support for JZ4740 SoC NAND controller"
+ depends on MACH_JZ4740
+ help
+ Enables support for NAND Flash on JZ4740 SoC based boards.
+
endif # MTD_NAND
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index e8ab884..ac83dcd 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -46,5 +46,6 @@ obj-$(CONFIG_MTD_NAND_NOMADIK) += nomadik_nand.o
obj-$(CONFIG_MTD_NAND_BCM_UMI) += bcm_umi_nand.o nand_bcm_umi.o
obj-$(CONFIG_MTD_NAND_MPC5121_NFC) += mpc5121_nfc.o
obj-$(CONFIG_MTD_NAND_RICOH) += r852.o
+obj-$(CONFIG_MTD_NAND_JZ4740) += jz4740_nand.o

nand-objs := nand_base.o nand_bbt.o
diff --git a/drivers/mtd/nand/jz4740_nand.c b/drivers/mtd/nand/jz4740_nand.c
new file mode 100644
index 0000000..67343fc
--- /dev/null
+++ b/drivers/mtd/nand/jz4740_nand.c
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2009-2010, Lars-Peter Clausen <[email protected]>
+ * JZ4740 SoC NAND 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/ioport.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+
+#include <linux/gpio.h>
+
+#include <asm/mach-jz4740/jz4740_nand.h>
+
+#define JZ_REG_NAND_CTRL 0x50
+#define JZ_REG_NAND_ECC_CTRL 0x100
+#define JZ_REG_NAND_DATA 0x104
+#define JZ_REG_NAND_PAR0 0x108
+#define JZ_REG_NAND_PAR1 0x10C
+#define JZ_REG_NAND_PAR2 0x110
+#define JZ_REG_NAND_IRQ_STAT 0x114
+#define JZ_REG_NAND_IRQ_CTRL 0x118
+#define JZ_REG_NAND_ERR(x) (0x11C + ((x) << 2))
+
+#define JZ_NAND_ECC_CTRL_PAR_READY BIT(4)
+#define JZ_NAND_ECC_CTRL_ENCODING BIT(3)
+#define JZ_NAND_ECC_CTRL_RS BIT(2)
+#define JZ_NAND_ECC_CTRL_RESET BIT(1)
+#define JZ_NAND_ECC_CTRL_ENABLE BIT(0)
+
+#define JZ_NAND_STATUS_ERR_COUNT (BIT(31) | BIT(30) | BIT(29))
+#define JZ_NAND_STATUS_PAD_FINISH BIT(4)
+#define JZ_NAND_STATUS_DEC_FINISH BIT(3)
+#define JZ_NAND_STATUS_ENC_FINISH BIT(2)
+#define JZ_NAND_STATUS_UNCOR_ERROR BIT(1)
+#define JZ_NAND_STATUS_ERROR BIT(0)
+
+#define JZ_NAND_CTRL_ENABLE_CHIP(x) BIT((x) << 1)
+#define JZ_NAND_CTRL_ASSERT_CHIP(x) BIT(((x) << 1) + 1)
+
+#define JZ_NAND_MEM_ADDR_OFFSET 0x10000
+#define JZ_NAND_MEM_CMD_OFFSET 0x08000
+
+struct jz_nand {
+ struct mtd_info mtd;
+ struct nand_chip chip;
+ void __iomem *base;
+ struct resource *mem;
+
+ void __iomem *bank_base;
+ struct resource *bank_mem;
+
+ struct jz_nand_platform_data *pdata;
+ bool is_reading;
+};
+
+static inline struct jz_nand *mtd_to_jz_nand(struct mtd_info *mtd)
+{
+ return container_of(mtd, struct jz_nand, mtd);
+}
+
+static void jz_nand_cmd_ctrl(struct mtd_info *mtd, int dat, unsigned int ctrl)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ struct nand_chip *chip = mtd->priv;
+ uint32_t reg;
+
+ if (ctrl & NAND_CTRL_CHANGE) {
+ BUG_ON((ctrl & NAND_ALE) && (ctrl & NAND_CLE));
+ if (ctrl & NAND_ALE)
+ chip->IO_ADDR_W = nand->bank_base + JZ_NAND_MEM_ADDR_OFFSET;
+ else if (ctrl & NAND_CLE)
+ chip->IO_ADDR_W = nand->bank_base + JZ_NAND_MEM_CMD_OFFSET;
+ else
+ chip->IO_ADDR_W = nand->bank_base;
+
+ reg = readl(nand->base + JZ_REG_NAND_CTRL);
+ if (ctrl & NAND_NCE)
+ reg |= JZ_NAND_CTRL_ASSERT_CHIP(0);
+ else
+ reg &= ~JZ_NAND_CTRL_ASSERT_CHIP(0);
+ writel(reg, nand->base + JZ_REG_NAND_CTRL);
+ }
+ if (dat != NAND_CMD_NONE)
+ writeb(dat, chip->IO_ADDR_W);
+}
+
+static int jz_nand_dev_ready(struct mtd_info *mtd)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ return gpio_get_value_cansleep(nand->pdata->busy_gpio);
+}
+
+static void jz_nand_hwctl(struct mtd_info *mtd, int mode)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ uint32_t reg;
+
+ writel(0, nand->base + JZ_REG_NAND_IRQ_STAT);
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ reg |= JZ_NAND_ECC_CTRL_RESET;
+ reg |= JZ_NAND_ECC_CTRL_ENABLE;
+ reg |= JZ_NAND_ECC_CTRL_RS;
+
+ switch (mode) {
+ case NAND_ECC_READ:
+ reg &= ~JZ_NAND_ECC_CTRL_ENCODING;
+ nand->is_reading = true;
+ break;
+ case NAND_ECC_WRITE:
+ reg |= JZ_NAND_ECC_CTRL_ENCODING;
+ nand->is_reading = false;
+ break;
+ default:
+ break;
+ }
+
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+}
+
+static int jz_nand_calculate_ecc_rs(struct mtd_info *mtd, const uint8_t *dat,
+ uint8_t *ecc_code)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ uint32_t reg, status;
+ int i;
+ unsigned int timeout = 1000;
+ static uint8_t empty_block_ecc[] = {0xcd, 0x9d, 0x90, 0x58, 0xf4,
+ 0x8b, 0xff, 0xb7, 0x6f};
+
+ if (nand->is_reading)
+ return 0;
+
+ do {
+ status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
+ } while (!(status & JZ_NAND_STATUS_ENC_FINISH) && --timeout);
+
+ if (timeout == 0)
+ return -1;
+
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+ reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ for (i = 0; i < 9; ++i)
+ ecc_code[i] = readb(nand->base + JZ_REG_NAND_PAR0 + i);
+
+ /* If the written data is completly 0xff, we also want to write 0xff as
+ * ecc, otherwise we will get in trouble when doing subpage writes. */
+ if (memcmp(ecc_code, empty_block_ecc, 9) == 0)
+ memset(ecc_code, 0xff, 9);
+
+ return 0;
+}
+
+static void jz_nand_correct_data(uint8_t *dat, int index, int mask)
+{
+ int offset = index & 0x7;
+ uint16_t data;
+
+ index += (index >> 3);
+
+ data = dat[index];
+ data |= dat[index+1] << 8;
+
+ mask ^= (data >> offset) & 0x1ff;
+ data &= ~(0x1ff << offset);
+ data |= (mask << offset);
+
+ dat[index] = data & 0xff;
+ dat[index+1] = (data >> 8) & 0xff;
+}
+
+static int jz_nand_correct_ecc_rs(struct mtd_info *mtd, uint8_t *dat,
+ uint8_t *read_ecc, uint8_t *calc_ecc)
+{
+ struct jz_nand *nand = mtd_to_jz_nand(mtd);
+ int i, error_count, index;
+ uint32_t reg, status, error;
+ uint32_t t;
+ unsigned int timeout = 1000;
+
+ t = read_ecc[0];
+
+ if (t == 0xff) {
+ for (i = 1; i < 9; ++i)
+ t &= read_ecc[i];
+
+ t &= dat[0];
+ t &= dat[nand->chip.ecc.size / 2];
+ t &= dat[nand->chip.ecc.size - 1];
+
+ if (t == 0xff) {
+ for (i = 1; i < nand->chip.ecc.size - 1; ++i)
+ t &= dat[i];
+ if (t == 0xff)
+ return 0;
+ }
+ }
+
+ for (i = 0; i < 9; ++i)
+ writeb(read_ecc[i], nand->base + JZ_REG_NAND_PAR0 + i);
+
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+ reg |= JZ_NAND_ECC_CTRL_PAR_READY;
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ do {
+ status = readl(nand->base + JZ_REG_NAND_IRQ_STAT);
+ } while (!(status & JZ_NAND_STATUS_DEC_FINISH) && --timeout);
+
+ if (timeout == 0)
+ return -1;
+
+ reg = readl(nand->base + JZ_REG_NAND_ECC_CTRL);
+ reg &= ~JZ_NAND_ECC_CTRL_ENABLE;
+ writel(reg, nand->base + JZ_REG_NAND_ECC_CTRL);
+
+ if (status & JZ_NAND_STATUS_ERROR) {
+ if (status & JZ_NAND_STATUS_UNCOR_ERROR)
+ return -1;
+
+ error_count = (status & JZ_NAND_STATUS_ERR_COUNT) >> 29;
+
+ for (i = 0; i < error_count; ++i) {
+ error = readl(nand->base + JZ_REG_NAND_ERR(i));
+ index = ((error >> 16) & 0x1ff) - 1;
+ if (index >= 0 && index < 512)
+ jz_nand_correct_data(dat, index, error & 0x1ff);
+ }
+
+ return error_count;
+ }
+
+ return 0;
+}
+
+
+/* Copy paste of nand_read_page_hwecc_oob_first except for different eccpos
+ * handling. The ecc area is for 4k chips 72 bytes long and thus does not fit
+ * into the eccpos array. */
+static int jz_nand_read_page_hwecc_oob_first(struct mtd_info *mtd,
+ struct nand_chip *chip, uint8_t *buf, int page)
+{
+ int i, eccsize = chip->ecc.size;
+ int eccbytes = chip->ecc.bytes;
+ int eccsteps = chip->ecc.steps;
+ uint8_t *p = buf;
+ unsigned int ecc_offset = chip->page_shift;
+
+ /* Read the OOB area first */
+ chip->cmdfunc(mtd, NAND_CMD_READOOB, 0, page);
+ chip->read_buf(mtd, chip->oob_poi, mtd->oobsize);
+ chip->cmdfunc(mtd, NAND_CMD_READ0, 0, page);
+
+ for (i = ecc_offset; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
+ int stat;
+
+ chip->ecc.hwctl(mtd, NAND_ECC_READ);
+ chip->read_buf(mtd, p, eccsize);
+
+ stat = chip->ecc.correct(mtd, p, &chip->oob_poi[i], NULL);
+ if (stat < 0)
+ mtd->ecc_stats.failed++;
+ else
+ mtd->ecc_stats.corrected += stat;
+ }
+ return 0;
+}
+
+/* Copy-and-paste of nand_write_page_hwecc with different eccpos handling. */
+static void jz_nand_write_page_hwecc(struct mtd_info *mtd,
+ struct nand_chip *chip, const uint8_t *buf)
+{
+ int i, eccsize = chip->ecc.size;
+ int eccbytes = chip->ecc.bytes;
+ int eccsteps = chip->ecc.steps;
+ const uint8_t *p = buf;
+ unsigned int ecc_offset = chip->page_shift;
+
+ for (i = ecc_offset; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
+ chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
+ chip->write_buf(mtd, p, eccsize);
+ chip->ecc.calculate(mtd, p, &chip->oob_poi[i]);
+ }
+
+ chip->write_buf(mtd, chip->oob_poi, mtd->oobsize);
+}
+
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+static const char *part_probes[] = {"cmdline", NULL};
+#endif
+
+static int jz_nand_ioremap_resource(struct platform_device *pdev,
+ const char *name, struct resource **res, void __iomem **base)
+{
+ int ret;
+
+ *res = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
+ if (!*res) {
+ dev_err(&pdev->dev, "Failed to get platform %s memory\n", name);
+ ret = -ENXIO;
+ goto err;
+ }
+
+ *res = request_mem_region((*res)->start, resource_size(*res),
+ pdev->name);
+ if (!*res) {
+ dev_err(&pdev->dev, "Failed to request %s memory region\n", name);
+ ret = -EBUSY;
+ goto err;
+ }
+
+ *base = ioremap((*res)->start, resource_size(*res));
+ if (!*base) {
+ dev_err(&pdev->dev, "Failed to ioremap %s memory region\n", name);
+ ret = -EBUSY;
+ goto err_release_mem;
+ }
+
+ return 0;
+
+err_release_mem:
+ release_mem_region((*res)->start, resource_size(*res));
+err:
+ *res = NULL;
+ *base = NULL;
+ return ret;
+}
+
+static int __devinit jz_nand_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct jz_nand *nand;
+ struct nand_chip *chip;
+ struct mtd_info *mtd;
+ struct jz_nand_platform_data *pdata = pdev->dev.platform_data;
+#ifdef CONFIG_MTD_PARTITIONS
+ struct mtd_partition *partition_info;
+ int num_partitions = 0;
+#endif
+
+ nand = kzalloc(sizeof(*nand), GFP_KERNEL);
+ if (!nand) {
+ dev_err(&pdev->dev, "Failed to allocate device structure.\n");
+ return -ENOMEM;
+ }
+
+ ret = jz_nand_ioremap_resource(pdev, "mmio", &nand->mem, &nand->base);
+ if (ret)
+ goto err_free;
+ ret = jz_nand_ioremap_resource(pdev, "bank", &nand->bank_mem,
+ &nand->bank_base);
+ if (ret)
+ goto err_iounmap_mmio;
+
+ if (pdata && gpio_is_valid(pdata->busy_gpio)) {
+ ret = gpio_request(pdata->busy_gpio, "NAND busy pin");
+ if (ret) {
+ dev_err(&pdev->dev,
+ "Failed to request busy gpio %d: %d\n",
+ pdata->busy_gpio, ret);
+ goto err_iounmap_mem;
+ }
+ }
+
+ mtd = &nand->mtd;
+ chip = &nand->chip;
+ mtd->priv = chip;
+ mtd->owner = THIS_MODULE;
+ mtd->name = "jz4740-nand";
+
+ chip->ecc.hwctl = jz_nand_hwctl;
+ chip->ecc.calculate = jz_nand_calculate_ecc_rs;
+ chip->ecc.correct = jz_nand_correct_ecc_rs;
+ chip->ecc.mode = NAND_ECC_HW_OOB_FIRST;
+ chip->ecc.size = 512;
+ chip->ecc.bytes = 9;
+
+ chip->ecc.read_page = jz_nand_read_page_hwecc_oob_first;
+ chip->ecc.write_page = jz_nand_write_page_hwecc;
+
+ if (pdata)
+ chip->ecc.layout = pdata->ecc_layout;
+
+ chip->chip_delay = 50;
+ chip->cmd_ctrl = jz_nand_cmd_ctrl;
+
+ if (pdata && gpio_is_valid(pdata->busy_gpio))
+ chip->dev_ready = jz_nand_dev_ready;
+
+ chip->IO_ADDR_R = nand->bank_base;
+ chip->IO_ADDR_W = nand->bank_base;
+
+ nand->pdata = pdata;
+ platform_set_drvdata(pdev, nand);
+
+ writel(JZ_NAND_CTRL_ENABLE_CHIP(0), nand->base + JZ_REG_NAND_CTRL);
+
+ ret = nand_scan_ident(mtd, 1, NULL);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to scan nand\n");
+ goto err_gpio_free;
+ }
+
+ if (pdata && pdata->ident_callback) {
+ pdata->ident_callback(pdev, chip, &pdata->partitions,
+ &pdata->num_partitions);
+ }
+
+ ret = nand_scan_tail(mtd);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to scan nand\n");
+ goto err_gpio_free;
+ }
+
+#ifdef CONFIG_MTD_PARTITIONS
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+ num_partitions = parse_mtd_partitions(mtd, part_probes,
+ &partition_info, 0);
+#endif
+ if (num_partitions <= 0 && pdata) {
+ num_partitions = pdata->num_partitions;
+ partition_info = pdata->partitions;
+ }
+
+ if (num_partitions > 0)
+ ret = add_mtd_partitions(mtd, partition_info, num_partitions);
+ else
+#endif
+ ret = add_mtd_device(mtd);
+
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to add mtd device\n");
+ goto err_nand_release;
+ }
+
+ dev_info(&pdev->dev, "Successfully registered JZ4740 NAND driver\n");
+
+ return 0;
+
+err_nand_release:
+ nand_release(&nand->mtd);
+err_gpio_free:
+ platform_set_drvdata(pdev, NULL);
+ gpio_free(pdata->busy_gpio);
+err_iounmap_mem:
+ iounmap(nand->bank_base);
+err_iounmap_mmio:
+ iounmap(nand->base);
+err_free:
+ kfree(nand);
+ return ret;
+}
+
+static int __devexit jz_nand_remove(struct platform_device *pdev)
+{
+ struct jz_nand *nand = platform_get_drvdata(pdev);
+
+ nand_release(&nand->mtd);
+
+ /* Deassert and disable all chips */
+ writel(0, nand->base + JZ_REG_NAND_CTRL);
+
+ iounmap(nand->bank_base);
+ release_mem_region(nand->bank_mem->start, resource_size(nand->bank_mem));
+ iounmap(nand->base);
+ release_mem_region(nand->mem->start, resource_size(nand->mem));
+
+ platform_set_drvdata(pdev, NULL);
+ kfree(nand);
+
+ return 0;
+}
+
+struct platform_driver jz_nand_driver = {
+ .probe = jz_nand_probe,
+ .remove = __devexit_p(jz_nand_remove),
+ .driver = {
+ .name = "jz4740-nand",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init jz_nand_init(void)
+{
+ return platform_driver_register(&jz_nand_driver);
+}
+module_init(jz_nand_init);
+
+static void __exit jz_nand_exit(void)
+{
+ platform_driver_unregister(&jz_nand_driver);
+}
+module_exit(jz_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <[email protected]>");
+MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC");
+MODULE_ALIAS("platform:jz4740-nand");
--
1.5.6.5

2010-07-17 12:17:00

by Lars-Peter Clausen

[permalink] [raw]
Subject: [PATCH v3] MIPS: JZ4740: Add qi_lb60 board support

This patch adds support for the qi_lb60 (a.k.a QI Ben NanoNote) clamshell
device.

Signed-off-by: Lars-Peter Clausen <[email protected]>

---
Changes since v1
- Register jz4740 pcm device
- Battery device is now registered by the ADC MFD device

Changes since v2
- Use the pwm-beeper instead of the pwm-leds driver for the piezo
- Update include file locations
---
arch/mips/jz4740/Kconfig | 4 +
arch/mips/jz4740/Makefile | 2 +
arch/mips/jz4740/board-qi_lb60.c | 471 ++++++++++++++++++++++++++++++++++++++
3 files changed, 477 insertions(+), 0 deletions(-)
create mode 100644 arch/mips/jz4740/board-qi_lb60.c

diff --git a/arch/mips/jz4740/Kconfig b/arch/mips/jz4740/Kconfig
index 8a5e850..3e7141f 100644
--- a/arch/mips/jz4740/Kconfig
+++ b/arch/mips/jz4740/Kconfig
@@ -1,6 +1,10 @@
choice
prompt "Machine type"
depends on MACH_JZ4740
+ default JZ4740_QI_LB60
+
+config JZ4740_QI_LB60
+ bool "Qi Hardware Ben NanoNote"

endchoice

diff --git a/arch/mips/jz4740/Makefile b/arch/mips/jz4740/Makefile
index a803ccb..a604eae 100644
--- a/arch/mips/jz4740/Makefile
+++ b/arch/mips/jz4740/Makefile
@@ -11,6 +11,8 @@ obj-$(CONFIG_DEBUG_FS) += clock-debugfs.o

# board specific support

+obj-$(CONFIG_JZ4740_QI_LB60) += board-qi_lb60.o
+
# PM support

obj-$(CONFIG_PM) += pm.o
diff --git a/arch/mips/jz4740/board-qi_lb60.c b/arch/mips/jz4740/board-qi_lb60.c
new file mode 100644
index 0000000..5742bb4
--- /dev/null
+++ b/arch/mips/jz4740/board-qi_lb60.c
@@ -0,0 +1,471 @@
+/*
+ * linux/arch/mips/jz4740/board-qi_lb60.c
+ *
+ * QI_LB60 board support
+ *
+ * Copyright (c) 2009 Qi Hardware inc.,
+ * Author: Xiangfu Liu <[email protected]>
+ * Copyright 2010, Lars-Petrer Clausen <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 or later
+ * as published by the Free Software Foundation.
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/gpio.h>
+
+#include <linux/input.h>
+#include <linux/gpio_keys.h>
+#include <linux/input/matrix_keypad.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/spi_gpio.h>
+#include <linux/power_supply.h>
+#include <linux/power/jz4740-battery.h>
+
+#include <asm/mach-jz4740/jz4740_fb.h>
+#include <asm/mach-jz4740/jz4740_mmc.h>
+#include <asm/mach-jz4740/jz4740_nand.h>
+
+#include <linux/regulator/fixed.h>
+#include <linux/regulator/machine.h>
+
+#include <linux/leds_pwm.h>
+
+#include <asm/mach-jz4740/platform.h>
+
+#include "clock.h"
+
+static bool is_avt2;
+
+/* GPIOs */
+#define QI_LB60_GPIO_SD_CD JZ_GPIO_PORTD(0)
+#define QI_LB60_GPIO_SD_VCC_EN_N JZ_GPIO_PORTD(2)
+
+#define QI_LB60_GPIO_KEYOUT(x) (JZ_GPIO_PORTC(10) + (x))
+#define QI_LB60_GPIO_KEYIN(x) (JZ_GPIO_PORTD(18) + (x))
+#define QI_LB60_GPIO_KEYIN8 JZ_GPIO_PORTD(26)
+
+/* NAND */
+static struct nand_ecclayout qi_lb60_ecclayout_1gb = {
+/* .eccbytes = 36,
+ .eccpos = {
+ 6, 7, 8, 9, 10, 11, 12, 13,
+ 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 33, 34, 35, 36, 37,
+ 38, 39, 40, 41
+ },*/
+ .oobfree = {
+ { .offset = 2, .length = 4 },
+ { .offset = 42, .length = 22 }
+ },
+};
+
+/* Early prototypes of the QI LB60 had only 1GB of NAND.
+ * In order to support these devices aswell the partition and ecc layout is
+ * initalized depending on the NAND size */
+static struct mtd_partition qi_lb60_partitions_1gb[] = {
+ {
+ .name = "NAND BOOT partition",
+ .offset = 0 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND KERNEL partition",
+ .offset = 4 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND ROOTFS partition",
+ .offset = 8 * 0x100000,
+ .size = (504 + 512) * 0x100000,
+ },
+};
+
+static struct nand_ecclayout qi_lb60_ecclayout_2gb = {
+/* .eccbytes = 72,
+ .eccpos = {
+ 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27,
+ 28, 29, 30, 31, 32, 33, 34, 35,
+ 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, 62, 63, 64, 65, 66, 67,
+ 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 83
+ },*/
+ .oobfree = {
+ { .offset = 2, .length = 10 },
+ { .offset = 84, .length = 44 },
+ },
+};
+
+static struct mtd_partition qi_lb60_partitions_2gb[] = {
+ {
+ .name = "NAND BOOT partition",
+ .offset = 0 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND KERNEL partition",
+ .offset = 4 * 0x100000,
+ .size = 4 * 0x100000,
+ },
+ {
+ .name = "NAND ROOTFS partition",
+ .offset = 8 * 0x100000,
+ .size = (504 + 512 + 1024) * 0x100000,
+ },
+};
+
+static void qi_lb60_nand_ident(struct platform_device *pdev,
+ struct nand_chip *chip, struct mtd_partition **partitions,
+ int *num_partitions)
+{
+ if (chip->page_shift == 12) {
+ chip->ecc.layout = &qi_lb60_ecclayout_2gb;
+ *partitions = qi_lb60_partitions_2gb;
+ *num_partitions = ARRAY_SIZE(qi_lb60_partitions_2gb);
+ } else {
+ chip->ecc.layout = &qi_lb60_ecclayout_1gb;
+ *partitions = qi_lb60_partitions_1gb;
+ *num_partitions = ARRAY_SIZE(qi_lb60_partitions_1gb);
+ }
+}
+
+static struct jz_nand_platform_data qi_lb60_nand_pdata = {
+ .ident_callback = qi_lb60_nand_ident,
+ .busy_gpio = 94,
+};
+
+/* Keyboard*/
+
+#define KEY_QI_QI KEY_F13
+#define KEY_QI_UPRED KEY_RIGHTALT
+#define KEY_QI_VOLUP KEY_VOLUMEUP
+#define KEY_QI_VOLDOWN KEY_VOLUMEDOWN
+#define KEY_QI_FN KEY_LEFTCTRL
+
+static const uint32_t qi_lb60_keymap[] = {
+ KEY(0, 0, KEY_F1), /* S2 */
+ KEY(0, 1, KEY_F2), /* S3 */
+ KEY(0, 2, KEY_F3), /* S4 */
+ KEY(0, 3, KEY_F4), /* S5 */
+ KEY(0, 4, KEY_F5), /* S6 */
+ KEY(0, 5, KEY_F6), /* S7 */
+ KEY(0, 6, KEY_F7), /* S8 */
+
+ KEY(1, 0, KEY_Q), /* S10 */
+ KEY(1, 1, KEY_W), /* S11 */
+ KEY(1, 2, KEY_E), /* S12 */
+ KEY(1, 3, KEY_R), /* S13 */
+ KEY(1, 4, KEY_T), /* S14 */
+ KEY(1, 5, KEY_Y), /* S15 */
+ KEY(1, 6, KEY_U), /* S16 */
+ KEY(1, 7, KEY_I), /* S17 */
+ KEY(2, 0, KEY_A), /* S18 */
+ KEY(2, 1, KEY_S), /* S19 */
+ KEY(2, 2, KEY_D), /* S20 */
+ KEY(2, 3, KEY_F), /* S21 */
+ KEY(2, 4, KEY_G), /* S22 */
+ KEY(2, 5, KEY_H), /* S23 */
+ KEY(2, 6, KEY_J), /* S24 */
+ KEY(2, 7, KEY_K), /* S25 */
+ KEY(3, 0, KEY_ESC), /* S26 */
+ KEY(3, 1, KEY_Z), /* S27 */
+ KEY(3, 2, KEY_X), /* S28 */
+ KEY(3, 3, KEY_C), /* S29 */
+ KEY(3, 4, KEY_V), /* S30 */
+ KEY(3, 5, KEY_B), /* S31 */
+ KEY(3, 6, KEY_N), /* S32 */
+ KEY(3, 7, KEY_M), /* S33 */
+ KEY(4, 0, KEY_TAB), /* S34 */
+ KEY(4, 1, KEY_CAPSLOCK), /* S35 */
+ KEY(4, 2, KEY_BACKSLASH), /* S36 */
+ KEY(4, 3, KEY_APOSTROPHE), /* S37 */
+ KEY(4, 4, KEY_COMMA), /* S38 */
+ KEY(4, 5, KEY_DOT), /* S39 */
+ KEY(4, 6, KEY_SLASH), /* S40 */
+ KEY(4, 7, KEY_UP), /* S41 */
+ KEY(5, 0, KEY_O), /* S42 */
+ KEY(5, 1, KEY_L), /* S43 */
+ KEY(5, 2, KEY_EQUAL), /* S44 */
+ KEY(5, 3, KEY_QI_UPRED), /* S45 */
+ KEY(5, 4, KEY_SPACE), /* S46 */
+ KEY(5, 5, KEY_QI_QI), /* S47 */
+ KEY(5, 6, KEY_RIGHTCTRL), /* S48 */
+ KEY(5, 7, KEY_LEFT), /* S49 */
+ KEY(6, 0, KEY_F8), /* S50 */
+ KEY(6, 1, KEY_P), /* S51 */
+ KEY(6, 2, KEY_BACKSPACE),/* S52 */
+ KEY(6, 3, KEY_ENTER), /* S53 */
+ KEY(6, 4, KEY_QI_VOLUP), /* S54 */
+ KEY(6, 5, KEY_QI_VOLDOWN), /* S55 */
+ KEY(6, 6, KEY_DOWN), /* S56 */
+ KEY(6, 7, KEY_RIGHT), /* S57 */
+
+ KEY(7, 0, KEY_LEFTSHIFT), /* S58 */
+ KEY(7, 1, KEY_LEFTALT), /* S59 */
+ KEY(7, 2, KEY_QI_FN), /* S60 */
+};
+
+static const struct matrix_keymap_data qi_lb60_keymap_data = {
+ .keymap = qi_lb60_keymap,
+ .keymap_size = ARRAY_SIZE(qi_lb60_keymap),
+};
+
+static const unsigned int qi_lb60_keypad_cols[] = {
+ QI_LB60_GPIO_KEYOUT(0),
+ QI_LB60_GPIO_KEYOUT(1),
+ QI_LB60_GPIO_KEYOUT(2),
+ QI_LB60_GPIO_KEYOUT(3),
+ QI_LB60_GPIO_KEYOUT(4),
+ QI_LB60_GPIO_KEYOUT(5),
+ QI_LB60_GPIO_KEYOUT(6),
+ QI_LB60_GPIO_KEYOUT(7),
+};
+
+static const unsigned int qi_lb60_keypad_rows[] = {
+ QI_LB60_GPIO_KEYIN(0),
+ QI_LB60_GPIO_KEYIN(1),
+ QI_LB60_GPIO_KEYIN(2),
+ QI_LB60_GPIO_KEYIN(3),
+ QI_LB60_GPIO_KEYIN(4),
+ QI_LB60_GPIO_KEYIN(5),
+ QI_LB60_GPIO_KEYIN(7),
+ QI_LB60_GPIO_KEYIN8,
+};
+
+static struct matrix_keypad_platform_data qi_lb60_pdata = {
+ .keymap_data = &qi_lb60_keymap_data,
+ .col_gpios = qi_lb60_keypad_cols,
+ .row_gpios = qi_lb60_keypad_rows,
+ .num_col_gpios = ARRAY_SIZE(qi_lb60_keypad_cols),
+ .num_row_gpios = ARRAY_SIZE(qi_lb60_keypad_rows),
+ .col_scan_delay_us = 10,
+ .debounce_ms = 10,
+ .wakeup = 1,
+ .active_low = 1,
+};
+
+static struct platform_device qi_lb60_keypad = {
+ .name = "matrix-keypad",
+ .id = -1,
+ .dev = {
+ .platform_data = &qi_lb60_pdata,
+ },
+};
+
+/* Display */
+static struct fb_videomode qi_lb60_video_modes[] = {
+ {
+ .name = "320x240",
+ .xres = 320,
+ .yres = 240,
+ .refresh = 30,
+ .left_margin = 140,
+ .right_margin = 273,
+ .upper_margin = 20,
+ .lower_margin = 2,
+ .hsync_len = 1,
+ .vsync_len = 1,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ },
+};
+
+static struct jz4740_fb_platform_data qi_lb60_fb_pdata = {
+ .width = 60,
+ .height = 45,
+ .num_modes = ARRAY_SIZE(qi_lb60_video_modes),
+ .modes = qi_lb60_video_modes,
+ .bpp = 24,
+ .lcd_type = JZ_LCD_TYPE_8BIT_SERIAL,
+ .pixclk_falling_edge = 1,
+};
+
+struct spi_gpio_platform_data spigpio_platform_data = {
+ .sck = JZ_GPIO_PORTC(23),
+ .mosi = JZ_GPIO_PORTC(22),
+ .miso = -1,
+ .num_chipselect = 1,
+};
+
+static struct platform_device spigpio_device = {
+ .name = "spi_gpio",
+ .id = 1,
+ .dev = {
+ .platform_data = &spigpio_platform_data,
+ },
+};
+
+static struct spi_board_info qi_lb60_spi_board_info[] = {
+ {
+ .modalias = "ili8960",
+ .controller_data = (void *)JZ_GPIO_PORTC(21),
+ .chip_select = 0,
+ .bus_num = 1,
+ .max_speed_hz = 30 * 1000,
+ .mode = SPI_3WIRE,
+ },
+};
+
+/* Battery */
+static struct jz_battery_platform_data qi_lb60_battery_pdata = {
+ .gpio_charge = JZ_GPIO_PORTC(27),
+ .gpio_charge_active_low = 1,
+ .info = {
+ .name = "battery",
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+ .voltage_max_design = 4200000,
+ .voltage_min_design = 3600000,
+ },
+};
+
+/* GPIO Key: power */
+static struct gpio_keys_button qi_lb60_gpio_keys_buttons[] = {
+ [0] = {
+ .code = KEY_POWER,
+ .gpio = JZ_GPIO_PORTD(29),
+ .active_low = 1,
+ .desc = "Power",
+ .wakeup = 1,
+ },
+};
+
+static struct gpio_keys_platform_data qi_lb60_gpio_keys_data = {
+ .nbuttons = ARRAY_SIZE(qi_lb60_gpio_keys_buttons),
+ .buttons = qi_lb60_gpio_keys_buttons,
+};
+
+static struct platform_device qi_lb60_gpio_keys = {
+ .name = "gpio-keys",
+ .id = -1,
+ .dev = {
+ .platform_data = &qi_lb60_gpio_keys_data,
+ }
+};
+
+static struct jz4740_mmc_platform_data qi_lb60_mmc_pdata = {
+ .gpio_card_detect = QI_LB60_GPIO_SD_CD,
+ .gpio_read_only = -1,
+ .gpio_power = QI_LB60_GPIO_SD_VCC_EN_N,
+ .power_active_low = 1,
+};
+
+/* OHCI */
+static struct regulator_consumer_supply avt2_usb_regulator_consumer =
+ REGULATOR_SUPPLY("vbus", "jz4740-ohci");
+
+static struct regulator_init_data avt2_usb_regulator_init_data = {
+ .num_consumer_supplies = 1,
+ .consumer_supplies = &avt2_usb_regulator_consumer,
+ .constraints = {
+ .name = "USB power",
+ .min_uV = 5000000,
+ .max_uV = 5000000,
+ .valid_modes_mask = REGULATOR_MODE_NORMAL,
+ .valid_ops_mask = REGULATOR_CHANGE_STATUS,
+ },
+};
+
+static struct fixed_voltage_config avt2_usb_regulator_data = {
+ .supply_name = "USB power",
+ .microvolts = 5000000,
+ .gpio = JZ_GPIO_PORTB(17),
+ .init_data = &avt2_usb_regulator_init_data,
+};
+
+static struct platform_device avt2_usb_regulator_device = {
+ .name = "reg-fixed-voltage",
+ .id = -1,
+ .dev = {
+ .platform_data = &avt2_usb_regulator_data,
+ }
+};
+
+/* beeper */
+static struct platform_device qi_lb60_pwm_beeper = {
+ .name = "pwm-beeper",
+ .id = -1,
+ .dev = {
+ .platform_data = (void *)4,
+ },
+};
+
+static struct platform_device *jz_platform_devices[] __initdata = {
+ &jz4740_udc_device,
+ &jz4740_mmc_device,
+ &jz4740_nand_device,
+ &qi_lb60_keypad,
+ &spigpio_device,
+ &jz4740_framebuffer_device,
+ &jz4740_pcm_device,
+ &jz4740_i2s_device,
+ &jz4740_codec_device,
+ &jz4740_rtc_device,
+ &jz4740_adc_device,
+ &qi_lb60_gpio_keys,
+ &qi_lb60_pwm_beeper,
+};
+
+static void __init board_gpio_setup(void)
+{
+ /* We only need to enable/disable pullup here for pins used in generic
+ * drivers. Everything else is done by the drivers themselfs. */
+ jz_gpio_disable_pullup(QI_LB60_GPIO_SD_VCC_EN_N);
+ jz_gpio_disable_pullup(QI_LB60_GPIO_SD_CD);
+}
+
+static int __init qi_lb60_init_platform_devices(void)
+{
+ jz4740_framebuffer_device.dev.platform_data = &qi_lb60_fb_pdata;
+ jz4740_nand_device.dev.platform_data = &qi_lb60_nand_pdata;
+ jz4740_adc_device.dev.platform_data = &qi_lb60_battery_pdata;
+ jz4740_mmc_device.dev.platform_data = &qi_lb60_mmc_pdata;
+
+ jz4740_serial_device_register();
+
+ spi_register_board_info(qi_lb60_spi_board_info,
+ ARRAY_SIZE(qi_lb60_spi_board_info));
+
+ if (is_avt2) {
+ platform_device_register(&avt2_usb_regulator_device);
+ platform_device_register(&jz4740_usb_ohci_device);
+ }
+
+ return platform_add_devices(jz_platform_devices,
+ ARRAY_SIZE(jz_platform_devices));
+
+}
+
+struct jz4740_clock_board_data jz4740_clock_bdata = {
+ .ext_rate = 12000000,
+ .rtc_rate = 32768,
+};
+
+static __init int board_avt2(char *str)
+{
+ qi_lb60_mmc_pdata.card_detect_active_low = 1;
+ is_avt2 = true;
+
+ return 1;
+}
+__setup("avt2", board_avt2);
+
+static int __init qi_lb60_board_setup(void)
+{
+ printk(KERN_INFO "Qi Hardware JZ4740 QI %s setup\n",
+ is_avt2 ? "AVT2" : "LB60");
+
+ board_gpio_setup();
+
+ if (qi_lb60_init_platform_devices())
+ panic("Failed to initalize platform devices\n");
+
+ return 0;
+}
+arch_initcall(qi_lb60_board_setup);
--
1.5.6.5

2010-07-18 16:54:16

by Artem Bityutskiy

[permalink] [raw]
Subject: Re: [PATCH v3] MTD: Nand: Add JZ4740 NAND driver

On Sat, 2010-07-17 at 14:15 +0200, Lars-Peter Clausen wrote:
> This patch adds support for the NAND controller on JZ4740 SoCs.
>
> Signed-off-by: Lars-Peter Clausen <[email protected]>
> Cc: David Woodhouse <[email protected]>
> Cc: [email protected]
>

Do you expect this patch to go in via the MTD tree? I guess it might be
better if it was MIPS tree?
--
Best Regards,
Artem Bityutskiy (Артём Битюцкий)

2010-07-18 17:02:45

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [PATCH v3] MTD: Nand: Add JZ4740 NAND driver

Artem Bityutskiy wrote:
> On Sat, 2010-07-17 at 14:15 +0200, Lars-Peter Clausen wrote:
>> This patch adds support for the NAND controller on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <[email protected]>
>> Cc: David Woodhouse <[email protected]>
>> Cc: [email protected]
>>
>
> Do you expect this patch to go in via the MTD tree? I guess it might be
> better if it was MIPS tree?

Hi

Yes, letting it go through the MIPS tree is the plan.

- Lars