All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Alessandro Zummo,
	Andrew Morton, Anton Vorontsov, David Brownell, David Woodhouse,
	Greg Kroah-Hartman, Liam Girdwood, Mark Brown, Paul Gortmaker,
	Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc, linux-mtd,
	linux-usb, lm-sensors, rtc-linux

Foreword:
Ralf suggested that it might be a good idea in order to allow for reasonable
testing and to avoid build failures due to two-way dependencies in different
parts of the kernel, that he applies all the patches once they have been acked
by their respective maintainers, feeds them into -next and eventually sends the
whole series to Linus. One exception will be the ASoC patches which will, due to
major changes in the ASoC subsystem, go through the ASoC tree.
So if you are a maintainer for one of the subsystem touched by this series and
would rather see the patch going through your tree (given the patch is ok)
please tell.


This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

The JZ4740 has a mostly MIPS32 compatible core (no on cpu timers) and many on
chip peripherals like RTC, NAND, MMC, OHCI, UDC, ADC, I2C, SPI, AC97, I2S, I2S
Codec, UART and LCD controller.

The JZ4740 is mostly used in eBooks, PMPs and hand-held consoles.
This series contains patches for the Qi Ben NanoNote clamshell device as the
inital supported device.

Changes since v1:
There have been some minor changes since v1, mostly code cleanup and some
functional changes. One bigger change is that there is now a MFD driver for
the ADC core which does IRQ demultiplexing for the ADC unit and synchronizes
access to shared registers between the different users of the ADC core.
The patch adding a defconfig for the Qi LB60 has been dropped.
A detailed list of changes is present in each patch.

- Lars

Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Anton Vorontsov <cbouatmailru@gmail.com>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Greg Kroah-Hartman <gregkh@suse.de>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
Cc: Samuel Ortiz <sameo@linux.intel.com>
Cc: alsa-devel@alsa-project.org
Cc: linux-fbdev@vger.kernel.org
Cc: linux-mmc@vger.kernel.org
Cc: linux-mtd@lists.infradead.org
Cc: linux-usb@vger.kernel.org
Cc: lm-sensors@lm-sensors.org
Cc: rtc-linux@googlegroups.com

Lars-Peter Clausen (26):
  MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip
  MIPS: jz4740: Add IRQ handler code
  MIPS: JZ4740: Add clock API support.
  MIPS: JZ4740: Add timer support
  MIPS: JZ4740: Add clocksource/clockevent support.
  MIPS: JZ4740: Add power-management and system reset support
  MIPS: JZ4740: Add setup code
  MIPS: JZ4740: Add gpio support
  MIPS: JZ4740: Add DMA support.
  MIPS: JZ4740: Add PWM support
  MIPS: JZ4740: Add serial support
  MIPS: JZ4740: Add prom support
  MIPS: JZ4740: Add platform devices
  MIPS: JZ4740: Add Kbuild files
  RTC: Add JZ4740 RTC driver
  fbdev: Add JZ4740 framebuffer driver
  MTD: Nand: Add JZ4740 NAND driver
  MMC: Add JZ4740 mmc driver
  USB: Add JZ4740 ohci support
  alsa: ASoC: Add JZ4740 codec driver
  alsa: ASoC: Add JZ4740 ASoC support
  MFD: Add JZ4740 ADC driver
  hwmon: Add JZ4740 ADC driver
  power: Add JZ4740 battery driver.
  MIPS: JZ4740: Add qi_lb60 board support
  alsa: ASoC: JZ4740: Add qi_lb60 board driver

 arch/mips/Kbuild.platforms                   |    1 +
 arch/mips/Kconfig                            |   13 +
 arch/mips/include/asm/bootinfo.h             |    6 +
 arch/mips/include/asm/cpu.h                  |    9 +-
 arch/mips/include/asm/mach-jz4740/base.h     |   26 +
 arch/mips/include/asm/mach-jz4740/clock.h    |   28 +
 arch/mips/include/asm/mach-jz4740/dma.h      |   90 +++
 arch/mips/include/asm/mach-jz4740/gpio.h     |  398 +++++++++++
 arch/mips/include/asm/mach-jz4740/irq.h      |   57 ++
 arch/mips/include/asm/mach-jz4740/platform.h |   36 +
 arch/mips/include/asm/mach-jz4740/timer.h    |   22 +
 arch/mips/include/asm/mach-jz4740/war.h      |   25 +
 arch/mips/jz4740/Kconfig                     |   12 +
 arch/mips/jz4740/Makefile                    |   20 +
 arch/mips/jz4740/Platform                    |    5 +
 arch/mips/jz4740/board-qi_lb60.c             |  483 +++++++++++++
 arch/mips/jz4740/clock-debugfs.c             |  109 +++
 arch/mips/jz4740/clock.c                     |  920 ++++++++++++++++++++++++
 arch/mips/jz4740/clock.h                     |   76 ++
 arch/mips/jz4740/dma.c                       |  289 ++++++++
 arch/mips/jz4740/gpio.c                      |  601 ++++++++++++++++
 arch/mips/jz4740/irq.c                       |  169 +++++
 arch/mips/jz4740/irq.h                       |   21 +
 arch/mips/jz4740/platform.c                  |  284 ++++++++
 arch/mips/jz4740/pm.c                        |   56 ++
 arch/mips/jz4740/prom.c                      |   68 ++
 arch/mips/jz4740/pwm.c                       |  169 +++++
 arch/mips/jz4740/reset.c                     |   79 ++
 arch/mips/jz4740/reset.h                     |    7 +
 arch/mips/jz4740/serial.c                    |   33 +
 arch/mips/jz4740/serial.h                    |   21 +
 arch/mips/jz4740/setup.c                     |   29 +
 arch/mips/jz4740/time.c                      |  144 ++++
 arch/mips/jz4740/timer.c                     |   48 ++
 arch/mips/jz4740/timer.h                     |  136 ++++
 arch/mips/kernel/cpu-probe.c                 |   20 +
 arch/mips/mm/tlbex.c                         |    5 +
 drivers/hwmon/Kconfig                        |   11 +
 drivers/hwmon/Makefile                       |    1 +
 drivers/hwmon/jz4740-hwmon.c                 |  206 ++++++
 drivers/mfd/Kconfig                          |    8 +
 drivers/mfd/Makefile                         |    1 +
 drivers/mfd/jz4740-adc.c                     |  392 ++++++++++
 drivers/mmc/host/Kconfig                     |    8 +
 drivers/mmc/host/Makefile                    |    1 +
 drivers/mmc/host/jz4740_mmc.c                |  993 ++++++++++++++++++++++++++
 drivers/mtd/nand/Kconfig                     |    6 +
 drivers/mtd/nand/Makefile                    |    1 +
 drivers/mtd/nand/jz4740_nand.c               |  474 ++++++++++++
 drivers/power/Kconfig                        |   11 +
 drivers/power/Makefile                       |    1 +
 drivers/power/jz4740-battery.c               |  445 ++++++++++++
 drivers/rtc/Kconfig                          |   11 +
 drivers/rtc/Makefile                         |    1 +
 drivers/rtc/rtc-jz4740.c                     |  341 +++++++++
 drivers/usb/Kconfig                          |    1 +
 drivers/usb/host/ohci-hcd.c                  |    5 +
 drivers/usb/host/ohci-jz4740.c               |  276 +++++++
 drivers/video/Kconfig                        |    9 +
 drivers/video/Makefile                       |    1 +
 drivers/video/jz4740_fb.c                    |  817 +++++++++++++++++++++
 include/linux/jz4740-adc.h                   |   32 +
 include/linux/jz4740_fb.h                    |   58 ++
 include/linux/mmc/jz4740_mmc.h               |   15 +
 include/linux/mtd/jz4740_nand.h              |   34 +
 include/linux/power/jz4740-battery.h         |   24 +
 sound/soc/Kconfig                            |    1 +
 sound/soc/Makefile                           |    1 +
 sound/soc/codecs/Kconfig                     |    4 +
 sound/soc/codecs/Makefile                    |    2 +
 sound/soc/codecs/jz4740-codec.c              |  514 +++++++++++++
 sound/soc/codecs/jz4740-codec.h              |   20 +
 sound/soc/jz4740/Kconfig                     |   23 +
 sound/soc/jz4740/Makefile                    |   13 +
 sound/soc/jz4740/jz4740-i2s.c                |  540 ++++++++++++++
 sound/soc/jz4740/jz4740-i2s.h                |   18 +
 sound/soc/jz4740/jz4740-pcm.c                |  373 ++++++++++
 sound/soc/jz4740/jz4740-pcm.h                |   22 +
 sound/soc/jz4740/qi_lb60.c                   |  167 +++++
 79 files changed, 10396 insertions(+), 1 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-jz4740/base.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/dma.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/timer.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/war.h
 create mode 100644 arch/mips/jz4740/Kconfig
 create mode 100644 arch/mips/jz4740/Makefile
 create mode 100644 arch/mips/jz4740/Platform
 create mode 100644 arch/mips/jz4740/board-qi_lb60.c
 create mode 100644 arch/mips/jz4740/clock-debugfs.c
 create mode 100644 arch/mips/jz4740/clock.c
 create mode 100644 arch/mips/jz4740/clock.h
 create mode 100644 arch/mips/jz4740/dma.c
 create mode 100644 arch/mips/jz4740/gpio.c
 create mode 100644 arch/mips/jz4740/irq.c
 create mode 100644 arch/mips/jz4740/irq.h
 create mode 100644 arch/mips/jz4740/platform.c
 create mode 100644 arch/mips/jz4740/pm.c
 create mode 100644 arch/mips/jz4740/prom.c
 create mode 100644 arch/mips/jz4740/pwm.c
 create mode 100644 arch/mips/jz4740/reset.c
 create mode 100644 arch/mips/jz4740/reset.h
 create mode 100644 arch/mips/jz4740/serial.c
 create mode 100644 arch/mips/jz4740/serial.h
 create mode 100644 arch/mips/jz4740/setup.c
 create mode 100644 arch/mips/jz4740/time.c
 create mode 100644 arch/mips/jz4740/timer.c
 create mode 100644 arch/mips/jz4740/timer.h
 create mode 100644 drivers/hwmon/jz4740-hwmon.c
 create mode 100644 drivers/mfd/jz4740-adc.c
 create mode 100644 drivers/mmc/host/jz4740_mmc.c
 create mode 100644 drivers/mtd/nand/jz4740_nand.c
 create mode 100644 drivers/power/jz4740-battery.c
 create mode 100644 drivers/rtc/rtc-jz4740.c
 create mode 100644 drivers/usb/host/ohci-jz4740.c
 create mode 100644 drivers/video/jz4740_fb.c
 create mode 100644 include/linux/jz4740-adc.h
 create mode 100644 include/linux/jz4740_fb.h
 create mode 100644 include/linux/mmc/jz4740_mmc.h
 create mode 100644 include/linux/mtd/jz4740_nand.h
 create mode 100644 include/linux/power/jz4740-battery.h
 create mode 100644 sound/soc/codecs/jz4740-codec.c
 create mode 100644 sound/soc/codecs/jz4740-codec.h
 create mode 100644 sound/soc/jz4740/Kconfig
 create mode 100644 sound/soc/jz4740/Makefile
 create mode 100644 sound/soc/jz4740/jz4740-i2s.c
 create mode 100644 sound/soc/jz4740/jz4740-i2s.h
 create mode 100644 sound/soc/jz4740/jz4740-pcm.c
 create mode 100644 sound/soc/jz4740/jz4740-pcm.h
 create mode 100644 sound/soc/jz4740/qi_lb60.c


^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Alessandro Zummo,
	Andrew Morton, Anton Vorontsov, David Brownell, David Woodhouse,
	Greg Kroah-Hartman, Liam Girdwood, Mark Brown, Paul Gortmaker,
	Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc, linux-mtd,
	linux-usb, lm-sensors, rtc-linux

Foreword:
Ralf suggested that it might be a good idea in order to allow for reasonable
testing and to avoid build failures due to two-way dependencies in different
parts of the kernel, that he applies all the patches once they have been acked
by their respective maintainers, feeds them into -next and eventually sends the
whole series to Linus. One exception will be the ASoC patches which will, due to
major changes in the ASoC subsystem, go through the ASoC tree.
So if you are a maintainer for one of the subsystem touched by this series and
would rather see the patch going through your tree (given the patch is ok)
please tell.


This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

The JZ4740 has a mostly MIPS32 compatible core (no on cpu timers) and many on
chip peripherals like RTC, NAND, MMC, OHCI, UDC, ADC, I2C, SPI, AC97, I2S, I2S
Codec, UART and LCD controller.

The JZ4740 is mostly used in eBooks, PMPs and hand-held consoles.
This series contains patches for the Qi Ben NanoNote clamshell device as the
inital supported device.

Changes since v1:
There have been some minor changes since v1, mostly code cleanup and some
functional changes. One bigger change is that there is now a MFD driver for
the ADC core which does IRQ demultiplexing for the ADC unit and synchronizes
access to shared registers between the different users of the ADC core.
The patch adding a defconfig for the Qi LB60 has been dropped.
A detailed list of changes is present in each patch.

- Lars

Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Anton Vorontsov <cbouatmailru@gmail.com>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Greg Kroah-Hartman <gregkh@suse.de>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
Cc: Samuel Ortiz <sameo@linux.intel.com>
Cc: alsa-devel@alsa-project.org
Cc: linux-fbdev@vger.kernel.org
Cc: linux-mmc@vger.kernel.org
Cc: linux-mtd@lists.infradead.org
Cc: linux-usb@vger.kernel.org
Cc: lm-sensors@lm-sensors.org
Cc: rtc-linux@googlegroups.com

Lars-Peter Clausen (26):
  MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip
  MIPS: jz4740: Add IRQ handler code
  MIPS: JZ4740: Add clock API support.
  MIPS: JZ4740: Add timer support
  MIPS: JZ4740: Add clocksource/clockevent support.
  MIPS: JZ4740: Add power-management and system reset support
  MIPS: JZ4740: Add setup code
  MIPS: JZ4740: Add gpio support
  MIPS: JZ4740: Add DMA support.
  MIPS: JZ4740: Add PWM support
  MIPS: JZ4740: Add serial support
  MIPS: JZ4740: Add prom support
  MIPS: JZ4740: Add platform devices
  MIPS: JZ4740: Add Kbuild files
  RTC: Add JZ4740 RTC driver
  fbdev: Add JZ4740 framebuffer driver
  MTD: Nand: Add JZ4740 NAND driver
  MMC: Add JZ4740 mmc driver
  USB: Add JZ4740 ohci support
  alsa: ASoC: Add JZ4740 codec driver
  alsa: ASoC: Add JZ4740 ASoC support
  MFD: Add JZ4740 ADC driver
  hwmon: Add JZ4740 ADC driver
  power: Add JZ4740 battery driver.
  MIPS: JZ4740: Add qi_lb60 board support
  alsa: ASoC: JZ4740: Add qi_lb60 board driver

 arch/mips/Kbuild.platforms                   |    1 +
 arch/mips/Kconfig                            |   13 +
 arch/mips/include/asm/bootinfo.h             |    6 +
 arch/mips/include/asm/cpu.h                  |    9 +-
 arch/mips/include/asm/mach-jz4740/base.h     |   26 +
 arch/mips/include/asm/mach-jz4740/clock.h    |   28 +
 arch/mips/include/asm/mach-jz4740/dma.h      |   90 +++
 arch/mips/include/asm/mach-jz4740/gpio.h     |  398 +++++++++++
 arch/mips/include/asm/mach-jz4740/irq.h      |   57 ++
 arch/mips/include/asm/mach-jz4740/platform.h |   36 +
 arch/mips/include/asm/mach-jz4740/timer.h    |   22 +
 arch/mips/include/asm/mach-jz4740/war.h      |   25 +
 arch/mips/jz4740/Kconfig                     |   12 +
 arch/mips/jz4740/Makefile                    |   20 +
 arch/mips/jz4740/Platform                    |    5 +
 arch/mips/jz4740/board-qi_lb60.c             |  483 +++++++++++++
 arch/mips/jz4740/clock-debugfs.c             |  109 +++
 arch/mips/jz4740/clock.c                     |  920 ++++++++++++++++++++++++
 arch/mips/jz4740/clock.h                     |   76 ++
 arch/mips/jz4740/dma.c                       |  289 ++++++++
 arch/mips/jz4740/gpio.c                      |  601 ++++++++++++++++
 arch/mips/jz4740/irq.c                       |  169 +++++
 arch/mips/jz4740/irq.h                       |   21 +
 arch/mips/jz4740/platform.c                  |  284 ++++++++
 arch/mips/jz4740/pm.c                        |   56 ++
 arch/mips/jz4740/prom.c                      |   68 ++
 arch/mips/jz4740/pwm.c                       |  169 +++++
 arch/mips/jz4740/reset.c                     |   79 ++
 arch/mips/jz4740/reset.h                     |    7 +
 arch/mips/jz4740/serial.c                    |   33 +
 arch/mips/jz4740/serial.h                    |   21 +
 arch/mips/jz4740/setup.c                     |   29 +
 arch/mips/jz4740/time.c                      |  144 ++++
 arch/mips/jz4740/timer.c                     |   48 ++
 arch/mips/jz4740/timer.h                     |  136 ++++
 arch/mips/kernel/cpu-probe.c                 |   20 +
 arch/mips/mm/tlbex.c                         |    5 +
 drivers/hwmon/Kconfig                        |   11 +
 drivers/hwmon/Makefile                       |    1 +
 drivers/hwmon/jz4740-hwmon.c                 |  206 ++++++
 drivers/mfd/Kconfig                          |    8 +
 drivers/mfd/Makefile                         |    1 +
 drivers/mfd/jz4740-adc.c                     |  392 ++++++++++
 drivers/mmc/host/Kconfig                     |    8 +
 drivers/mmc/host/Makefile                    |    1 +
 drivers/mmc/host/jz4740_mmc.c                |  993 ++++++++++++++++++++++++++
 drivers/mtd/nand/Kconfig                     |    6 +
 drivers/mtd/nand/Makefile                    |    1 +
 drivers/mtd/nand/jz4740_nand.c               |  474 ++++++++++++
 drivers/power/Kconfig                        |   11 +
 drivers/power/Makefile                       |    1 +
 drivers/power/jz4740-battery.c               |  445 ++++++++++++
 drivers/rtc/Kconfig                          |   11 +
 drivers/rtc/Makefile                         |    1 +
 drivers/rtc/rtc-jz4740.c                     |  341 +++++++++
 drivers/usb/Kconfig                          |    1 +
 drivers/usb/host/ohci-hcd.c                  |    5 +
 drivers/usb/host/ohci-jz4740.c               |  276 +++++++
 drivers/video/Kconfig                        |    9 +
 drivers/video/Makefile                       |    1 +
 drivers/video/jz4740_fb.c                    |  817 +++++++++++++++++++++
 include/linux/jz4740-adc.h                   |   32 +
 include/linux/jz4740_fb.h                    |   58 ++
 include/linux/mmc/jz4740_mmc.h               |   15 +
 include/linux/mtd/jz4740_nand.h              |   34 +
 include/linux/power/jz4740-battery.h         |   24 +
 sound/soc/Kconfig                            |    1 +
 sound/soc/Makefile                           |    1 +
 sound/soc/codecs/Kconfig                     |    4 +
 sound/soc/codecs/Makefile                    |    2 +
 sound/soc/codecs/jz4740-codec.c              |  514 +++++++++++++
 sound/soc/codecs/jz4740-codec.h              |   20 +
 sound/soc/jz4740/Kconfig                     |   23 +
 sound/soc/jz4740/Makefile                    |   13 +
 sound/soc/jz4740/jz4740-i2s.c                |  540 ++++++++++++++
 sound/soc/jz4740/jz4740-i2s.h                |   18 +
 sound/soc/jz4740/jz4740-pcm.c                |  373 ++++++++++
 sound/soc/jz4740/jz4740-pcm.h                |   22 +
 sound/soc/jz4740/qi_lb60.c                   |  167 +++++
 79 files changed, 10396 insertions(+), 1 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-jz4740/base.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/dma.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/timer.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/war.h
 create mode 100644 arch/mips/jz4740/Kconfig
 create mode 100644 arch/mips/jz4740/Makefile
 create mode 100644 arch/mips/jz4740/Platform
 create mode 100644 arch/mips/jz4740/board-qi_lb60.c
 create mode 100644 arch/mips/jz4740/clock-debugfs.c
 create mode 100644 arch/mips/jz4740/clock.c
 create mode 100644 arch/mips/jz4740/clock.h
 create mode 100644 arch/mips/jz4740/dma.c
 create mode 100644 arch/mips/jz4740/gpio.c
 create mode 100644 arch/mips/jz4740/irq.c
 create mode 100644 arch/mips/jz4740/irq.h
 create mode 100644 arch/mips/jz4740/platform.c
 create mode 100644 arch/mips/jz4740/pm.c
 create mode 100644 arch/mips/jz4740/prom.c
 create mode 100644 arch/mips/jz4740/pwm.c
 create mode 100644 arch/mips/jz4740/reset.c
 create mode 100644 arch/mips/jz4740/reset.h
 create mode 100644 arch/mips/jz4740/serial.c
 create mode 100644 arch/mips/jz4740/serial.h
 create mode 100644 arch/mips/jz4740/setup.c
 create mode 100644 arch/mips/jz4740/time.c
 create mode 100644 arch/mips/jz4740/timer.c
 create mode 100644 arch/mips/jz4740/timer.h
 create mode 100644 drivers/hwmon/jz4740-hwmon.c
 create mode 100644 drivers/mfd/jz4740-adc.c
 create mode 100644 drivers/mmc/host/jz4740_mmc.c
 create mode 100644 drivers/mtd/nand/jz4740_nand.c
 create mode 100644 drivers/power/jz4740-battery.c
 create mode 100644 drivers/rtc/rtc-jz4740.c
 create mode 100644 drivers/usb/host/ohci-jz4740.c
 create mode 100644 drivers/video/jz4740_fb.c
 create mode 100644 include/linux/jz4740-adc.h
 create mode 100644 include/linux/jz4740_fb.h
 create mode 100644 include/linux/mmc/jz4740_mmc.h
 create mode 100644 include/linux/mtd/jz4740_nand.h
 create mode 100644 include/linux/power/jz4740-battery.h
 create mode 100644 sound/soc/codecs/jz4740-codec.c
 create mode 100644 sound/soc/codecs/jz4740-codec.h
 create mode 100644 sound/soc/jz4740/Kconfig
 create mode 100644 sound/soc/jz4740/Makefile
 create mode 100644 sound/soc/jz4740/jz4740-i2s.c
 create mode 100644 sound/soc/jz4740/jz4740-i2s.h
 create mode 100644 sound/soc/jz4740/jz4740-pcm.c
 create mode 100644 sound/soc/jz4740/jz4740-pcm.h
 create mode 100644 sound/soc/jz4740/qi_lb60.c


^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, Paul Gortmaker, Lars-Peter Clausen, Mark Brown,
	Samuel Ortiz, alsa-devel, Alessandro Zummo, Greg Kroah-Hartman,
	linux-mmc, linux-kernel, lm-sensors, David Brownell, linux-mtd,
	linux-fbdev, Anton Vorontsov, rtc-linux, Andrew Morton,
	David Woodhouse, linux-usb, Liam Girdwood

Foreword:
Ralf suggested that it might be a good idea in order to allow for reasonable
testing and to avoid build failures due to two-way dependencies in different
parts of the kernel, that he applies all the patches once they have been acked
by their respective maintainers, feeds them into -next and eventually sends the
whole series to Linus. One exception will be the ASoC patches which will, due to
major changes in the ASoC subsystem, go through the ASoC tree.
So if you are a maintainer for one of the subsystem touched by this series and
would rather see the patch going through your tree (given the patch is ok)
please tell.


This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

The JZ4740 has a mostly MIPS32 compatible core (no on cpu timers) and many on
chip peripherals like RTC, NAND, MMC, OHCI, UDC, ADC, I2C, SPI, AC97, I2S, I2S
Codec, UART and LCD controller.

The JZ4740 is mostly used in eBooks, PMPs and hand-held consoles.
This series contains patches for the Qi Ben NanoNote clamshell device as the
inital supported device.

Changes since v1:
There have been some minor changes since v1, mostly code cleanup and some
functional changes. One bigger change is that there is now a MFD driver for
the ADC core which does IRQ demultiplexing for the ADC unit and synchronizes
access to shared registers between the different users of the ADC core.
The patch adding a defconfig for the Qi LB60 has been dropped.
A detailed list of changes is present in each patch.

- Lars

Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Anton Vorontsov <cbouatmailru@gmail.com>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Greg Kroah-Hartman <gregkh@suse.de>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
Cc: Samuel Ortiz <sameo@linux.intel.com>
Cc: alsa-devel@alsa-project.org
Cc: linux-fbdev@vger.kernel.org
Cc: linux-mmc@vger.kernel.org
Cc: linux-mtd@lists.infradead.org
Cc: linux-usb@vger.kernel.org
Cc: lm-sensors@lm-sensors.org
Cc: rtc-linux@googlegroups.com

Lars-Peter Clausen (26):
  MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip
  MIPS: jz4740: Add IRQ handler code
  MIPS: JZ4740: Add clock API support.
  MIPS: JZ4740: Add timer support
  MIPS: JZ4740: Add clocksource/clockevent support.
  MIPS: JZ4740: Add power-management and system reset support
  MIPS: JZ4740: Add setup code
  MIPS: JZ4740: Add gpio support
  MIPS: JZ4740: Add DMA support.
  MIPS: JZ4740: Add PWM support
  MIPS: JZ4740: Add serial support
  MIPS: JZ4740: Add prom support
  MIPS: JZ4740: Add platform devices
  MIPS: JZ4740: Add Kbuild files
  RTC: Add JZ4740 RTC driver
  fbdev: Add JZ4740 framebuffer driver
  MTD: Nand: Add JZ4740 NAND driver
  MMC: Add JZ4740 mmc driver
  USB: Add JZ4740 ohci support
  alsa: ASoC: Add JZ4740 codec driver
  alsa: ASoC: Add JZ4740 ASoC support
  MFD: Add JZ4740 ADC driver
  hwmon: Add JZ4740 ADC driver
  power: Add JZ4740 battery driver.
  MIPS: JZ4740: Add qi_lb60 board support
  alsa: ASoC: JZ4740: Add qi_lb60 board driver

 arch/mips/Kbuild.platforms                   |    1 +
 arch/mips/Kconfig                            |   13 +
 arch/mips/include/asm/bootinfo.h             |    6 +
 arch/mips/include/asm/cpu.h                  |    9 +-
 arch/mips/include/asm/mach-jz4740/base.h     |   26 +
 arch/mips/include/asm/mach-jz4740/clock.h    |   28 +
 arch/mips/include/asm/mach-jz4740/dma.h      |   90 +++
 arch/mips/include/asm/mach-jz4740/gpio.h     |  398 +++++++++++
 arch/mips/include/asm/mach-jz4740/irq.h      |   57 ++
 arch/mips/include/asm/mach-jz4740/platform.h |   36 +
 arch/mips/include/asm/mach-jz4740/timer.h    |   22 +
 arch/mips/include/asm/mach-jz4740/war.h      |   25 +
 arch/mips/jz4740/Kconfig                     |   12 +
 arch/mips/jz4740/Makefile                    |   20 +
 arch/mips/jz4740/Platform                    |    5 +
 arch/mips/jz4740/board-qi_lb60.c             |  483 +++++++++++++
 arch/mips/jz4740/clock-debugfs.c             |  109 +++
 arch/mips/jz4740/clock.c                     |  920 ++++++++++++++++++++++++
 arch/mips/jz4740/clock.h                     |   76 ++
 arch/mips/jz4740/dma.c                       |  289 ++++++++
 arch/mips/jz4740/gpio.c                      |  601 ++++++++++++++++
 arch/mips/jz4740/irq.c                       |  169 +++++
 arch/mips/jz4740/irq.h                       |   21 +
 arch/mips/jz4740/platform.c                  |  284 ++++++++
 arch/mips/jz4740/pm.c                        |   56 ++
 arch/mips/jz4740/prom.c                      |   68 ++
 arch/mips/jz4740/pwm.c                       |  169 +++++
 arch/mips/jz4740/reset.c                     |   79 ++
 arch/mips/jz4740/reset.h                     |    7 +
 arch/mips/jz4740/serial.c                    |   33 +
 arch/mips/jz4740/serial.h                    |   21 +
 arch/mips/jz4740/setup.c                     |   29 +
 arch/mips/jz4740/time.c                      |  144 ++++
 arch/mips/jz4740/timer.c                     |   48 ++
 arch/mips/jz4740/timer.h                     |  136 ++++
 arch/mips/kernel/cpu-probe.c                 |   20 +
 arch/mips/mm/tlbex.c                         |    5 +
 drivers/hwmon/Kconfig                        |   11 +
 drivers/hwmon/Makefile                       |    1 +
 drivers/hwmon/jz4740-hwmon.c                 |  206 ++++++
 drivers/mfd/Kconfig                          |    8 +
 drivers/mfd/Makefile                         |    1 +
 drivers/mfd/jz4740-adc.c                     |  392 ++++++++++
 drivers/mmc/host/Kconfig                     |    8 +
 drivers/mmc/host/Makefile                    |    1 +
 drivers/mmc/host/jz4740_mmc.c                |  993 ++++++++++++++++++++++++++
 drivers/mtd/nand/Kconfig                     |    6 +
 drivers/mtd/nand/Makefile                    |    1 +
 drivers/mtd/nand/jz4740_nand.c               |  474 ++++++++++++
 drivers/power/Kconfig                        |   11 +
 drivers/power/Makefile                       |    1 +
 drivers/power/jz4740-battery.c               |  445 ++++++++++++
 drivers/rtc/Kconfig                          |   11 +
 drivers/rtc/Makefile                         |    1 +
 drivers/rtc/rtc-jz4740.c                     |  341 +++++++++
 drivers/usb/Kconfig                          |    1 +
 drivers/usb/host/ohci-hcd.c                  |    5 +
 drivers/usb/host/ohci-jz4740.c               |  276 +++++++
 drivers/video/Kconfig                        |    9 +
 drivers/video/Makefile                       |    1 +
 drivers/video/jz4740_fb.c                    |  817 +++++++++++++++++++++
 include/linux/jz4740-adc.h                   |   32 +
 include/linux/jz4740_fb.h                    |   58 ++
 include/linux/mmc/jz4740_mmc.h               |   15 +
 include/linux/mtd/jz4740_nand.h              |   34 +
 include/linux/power/jz4740-battery.h         |   24 +
 sound/soc/Kconfig                            |    1 +
 sound/soc/Makefile                           |    1 +
 sound/soc/codecs/Kconfig                     |    4 +
 sound/soc/codecs/Makefile                    |    2 +
 sound/soc/codecs/jz4740-codec.c              |  514 +++++++++++++
 sound/soc/codecs/jz4740-codec.h              |   20 +
 sound/soc/jz4740/Kconfig                     |   23 +
 sound/soc/jz4740/Makefile                    |   13 +
 sound/soc/jz4740/jz4740-i2s.c                |  540 ++++++++++++++
 sound/soc/jz4740/jz4740-i2s.h                |   18 +
 sound/soc/jz4740/jz4740-pcm.c                |  373 ++++++++++
 sound/soc/jz4740/jz4740-pcm.h                |   22 +
 sound/soc/jz4740/qi_lb60.c                   |  167 +++++
 79 files changed, 10396 insertions(+), 1 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-jz4740/base.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/dma.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/timer.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/war.h
 create mode 100644 arch/mips/jz4740/Kconfig
 create mode 100644 arch/mips/jz4740/Makefile
 create mode 100644 arch/mips/jz4740/Platform
 create mode 100644 arch/mips/jz4740/board-qi_lb60.c
 create mode 100644 arch/mips/jz4740/clock-debugfs.c
 create mode 100644 arch/mips/jz4740/clock.c
 create mode 100644 arch/mips/jz4740/clock.h
 create mode 100644 arch/mips/jz4740/dma.c
 create mode 100644 arch/mips/jz4740/gpio.c
 create mode 100644 arch/mips/jz4740/irq.c
 create mode 100644 arch/mips/jz4740/irq.h
 create mode 100644 arch/mips/jz4740/platform.c
 create mode 100644 arch/mips/jz4740/pm.c
 create mode 100644 arch/mips/jz4740/prom.c
 create mode 100644 arch/mips/jz4740/pwm.c
 create mode 100644 arch/mips/jz4740/reset.c
 create mode 100644 arch/mips/jz4740/reset.h
 create mode 100644 arch/mips/jz4740/serial.c
 create mode 100644 arch/mips/jz4740/serial.h
 create mode 100644 arch/mips/jz4740/setup.c
 create mode 100644 arch/mips/jz4740/time.c
 create mode 100644 arch/mips/jz4740/timer.c
 create mode 100644 arch/mips/jz4740/timer.h
 create mode 100644 drivers/hwmon/jz4740-hwmon.c
 create mode 100644 drivers/mfd/jz4740-adc.c
 create mode 100644 drivers/mmc/host/jz4740_mmc.c
 create mode 100644 drivers/mtd/nand/jz4740_nand.c
 create mode 100644 drivers/power/jz4740-battery.c
 create mode 100644 drivers/rtc/rtc-jz4740.c
 create mode 100644 drivers/usb/host/ohci-jz4740.c
 create mode 100644 drivers/video/jz4740_fb.c
 create mode 100644 include/linux/jz4740-adc.h
 create mode 100644 include/linux/jz4740_fb.h
 create mode 100644 include/linux/mmc/jz4740_mmc.h
 create mode 100644 include/linux/mtd/jz4740_nand.h
 create mode 100644 include/linux/power/jz4740-battery.h
 create mode 100644 sound/soc/codecs/jz4740-codec.c
 create mode 100644 sound/soc/codecs/jz4740-codec.h
 create mode 100644 sound/soc/jz4740/Kconfig
 create mode 100644 sound/soc/jz4740/Makefile
 create mode 100644 sound/soc/jz4740/jz4740-i2s.c
 create mode 100644 sound/soc/jz4740/jz4740-i2s.h
 create mode 100644 sound/soc/jz4740/jz4740-pcm.c
 create mode 100644 sound/soc/jz4740/jz4740-pcm.h
 create mode 100644 sound/soc/jz4740/qi_lb60.c

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [lm-sensors] [PATCH v2 00/26] Add support for the Ingenic JZ4740
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Alessandro Zummo,
	Andrew Morton, Anton Vorontsov, David Brownell, David Woodhouse,
	Greg Kroah-Hartman, Liam Girdwood, Mark Brown, Paul Gortmaker,
	Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc, linux-mtd,
	linux-usb, lm-sensors, rtc-linux

Foreword:
Ralf suggested that it might be a good idea in order to allow for reasonable
testing and to avoid build failures due to two-way dependencies in different
parts of the kernel, that he applies all the patches once they have been acked
by their respective maintainers, feeds them into -next and eventually sends the
whole series to Linus. One exception will be the ASoC patches which will, due to
major changes in the ASoC subsystem, go through the ASoC tree.
So if you are a maintainer for one of the subsystem touched by this series and
would rather see the patch going through your tree (given the patch is ok)
please tell.


This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

The JZ4740 has a mostly MIPS32 compatible core (no on cpu timers) and many on
chip peripherals like RTC, NAND, MMC, OHCI, UDC, ADC, I2C, SPI, AC97, I2S, I2S
Codec, UART and LCD controller.

The JZ4740 is mostly used in eBooks, PMPs and hand-held consoles.
This series contains patches for the Qi Ben NanoNote clamshell device as the
inital supported device.

Changes since v1:
There have been some minor changes since v1, mostly code cleanup and some
functional changes. One bigger change is that there is now a MFD driver for
the ADC core which does IRQ demultiplexing for the ADC unit and synchronizes
access to shared registers between the different users of the ADC core.
The patch adding a defconfig for the Qi LB60 has been dropped.
A detailed list of changes is present in each patch.

- Lars

Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Anton Vorontsov <cbouatmailru@gmail.com>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: Greg Kroah-Hartman <gregkh@suse.de>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
Cc: Samuel Ortiz <sameo@linux.intel.com>
Cc: alsa-devel@alsa-project.org
Cc: linux-fbdev@vger.kernel.org
Cc: linux-mmc@vger.kernel.org
Cc: linux-mtd@lists.infradead.org
Cc: linux-usb@vger.kernel.org
Cc: lm-sensors@lm-sensors.org
Cc: rtc-linux@googlegroups.com

Lars-Peter Clausen (26):
  MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip
  MIPS: jz4740: Add IRQ handler code
  MIPS: JZ4740: Add clock API support.
  MIPS: JZ4740: Add timer support
  MIPS: JZ4740: Add clocksource/clockevent support.
  MIPS: JZ4740: Add power-management and system reset support
  MIPS: JZ4740: Add setup code
  MIPS: JZ4740: Add gpio support
  MIPS: JZ4740: Add DMA support.
  MIPS: JZ4740: Add PWM support
  MIPS: JZ4740: Add serial support
  MIPS: JZ4740: Add prom support
  MIPS: JZ4740: Add platform devices
  MIPS: JZ4740: Add Kbuild files
  RTC: Add JZ4740 RTC driver
  fbdev: Add JZ4740 framebuffer driver
  MTD: Nand: Add JZ4740 NAND driver
  MMC: Add JZ4740 mmc driver
  USB: Add JZ4740 ohci support
  alsa: ASoC: Add JZ4740 codec driver
  alsa: ASoC: Add JZ4740 ASoC support
  MFD: Add JZ4740 ADC driver
  hwmon: Add JZ4740 ADC driver
  power: Add JZ4740 battery driver.
  MIPS: JZ4740: Add qi_lb60 board support
  alsa: ASoC: JZ4740: Add qi_lb60 board driver

 arch/mips/Kbuild.platforms                   |    1 +
 arch/mips/Kconfig                            |   13 +
 arch/mips/include/asm/bootinfo.h             |    6 +
 arch/mips/include/asm/cpu.h                  |    9 +-
 arch/mips/include/asm/mach-jz4740/base.h     |   26 +
 arch/mips/include/asm/mach-jz4740/clock.h    |   28 +
 arch/mips/include/asm/mach-jz4740/dma.h      |   90 +++
 arch/mips/include/asm/mach-jz4740/gpio.h     |  398 +++++++++++
 arch/mips/include/asm/mach-jz4740/irq.h      |   57 ++
 arch/mips/include/asm/mach-jz4740/platform.h |   36 +
 arch/mips/include/asm/mach-jz4740/timer.h    |   22 +
 arch/mips/include/asm/mach-jz4740/war.h      |   25 +
 arch/mips/jz4740/Kconfig                     |   12 +
 arch/mips/jz4740/Makefile                    |   20 +
 arch/mips/jz4740/Platform                    |    5 +
 arch/mips/jz4740/board-qi_lb60.c             |  483 +++++++++++++
 arch/mips/jz4740/clock-debugfs.c             |  109 +++
 arch/mips/jz4740/clock.c                     |  920 ++++++++++++++++++++++++
 arch/mips/jz4740/clock.h                     |   76 ++
 arch/mips/jz4740/dma.c                       |  289 ++++++++
 arch/mips/jz4740/gpio.c                      |  601 ++++++++++++++++
 arch/mips/jz4740/irq.c                       |  169 +++++
 arch/mips/jz4740/irq.h                       |   21 +
 arch/mips/jz4740/platform.c                  |  284 ++++++++
 arch/mips/jz4740/pm.c                        |   56 ++
 arch/mips/jz4740/prom.c                      |   68 ++
 arch/mips/jz4740/pwm.c                       |  169 +++++
 arch/mips/jz4740/reset.c                     |   79 ++
 arch/mips/jz4740/reset.h                     |    7 +
 arch/mips/jz4740/serial.c                    |   33 +
 arch/mips/jz4740/serial.h                    |   21 +
 arch/mips/jz4740/setup.c                     |   29 +
 arch/mips/jz4740/time.c                      |  144 ++++
 arch/mips/jz4740/timer.c                     |   48 ++
 arch/mips/jz4740/timer.h                     |  136 ++++
 arch/mips/kernel/cpu-probe.c                 |   20 +
 arch/mips/mm/tlbex.c                         |    5 +
 drivers/hwmon/Kconfig                        |   11 +
 drivers/hwmon/Makefile                       |    1 +
 drivers/hwmon/jz4740-hwmon.c                 |  206 ++++++
 drivers/mfd/Kconfig                          |    8 +
 drivers/mfd/Makefile                         |    1 +
 drivers/mfd/jz4740-adc.c                     |  392 ++++++++++
 drivers/mmc/host/Kconfig                     |    8 +
 drivers/mmc/host/Makefile                    |    1 +
 drivers/mmc/host/jz4740_mmc.c                |  993 ++++++++++++++++++++++++++
 drivers/mtd/nand/Kconfig                     |    6 +
 drivers/mtd/nand/Makefile                    |    1 +
 drivers/mtd/nand/jz4740_nand.c               |  474 ++++++++++++
 drivers/power/Kconfig                        |   11 +
 drivers/power/Makefile                       |    1 +
 drivers/power/jz4740-battery.c               |  445 ++++++++++++
 drivers/rtc/Kconfig                          |   11 +
 drivers/rtc/Makefile                         |    1 +
 drivers/rtc/rtc-jz4740.c                     |  341 +++++++++
 drivers/usb/Kconfig                          |    1 +
 drivers/usb/host/ohci-hcd.c                  |    5 +
 drivers/usb/host/ohci-jz4740.c               |  276 +++++++
 drivers/video/Kconfig                        |    9 +
 drivers/video/Makefile                       |    1 +
 drivers/video/jz4740_fb.c                    |  817 +++++++++++++++++++++
 include/linux/jz4740-adc.h                   |   32 +
 include/linux/jz4740_fb.h                    |   58 ++
 include/linux/mmc/jz4740_mmc.h               |   15 +
 include/linux/mtd/jz4740_nand.h              |   34 +
 include/linux/power/jz4740-battery.h         |   24 +
 sound/soc/Kconfig                            |    1 +
 sound/soc/Makefile                           |    1 +
 sound/soc/codecs/Kconfig                     |    4 +
 sound/soc/codecs/Makefile                    |    2 +
 sound/soc/codecs/jz4740-codec.c              |  514 +++++++++++++
 sound/soc/codecs/jz4740-codec.h              |   20 +
 sound/soc/jz4740/Kconfig                     |   23 +
 sound/soc/jz4740/Makefile                    |   13 +
 sound/soc/jz4740/jz4740-i2s.c                |  540 ++++++++++++++
 sound/soc/jz4740/jz4740-i2s.h                |   18 +
 sound/soc/jz4740/jz4740-pcm.c                |  373 ++++++++++
 sound/soc/jz4740/jz4740-pcm.h                |   22 +
 sound/soc/jz4740/qi_lb60.c                   |  167 +++++
 79 files changed, 10396 insertions(+), 1 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-jz4740/base.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/clock.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/dma.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/gpio.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/irq.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/platform.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/timer.h
 create mode 100644 arch/mips/include/asm/mach-jz4740/war.h
 create mode 100644 arch/mips/jz4740/Kconfig
 create mode 100644 arch/mips/jz4740/Makefile
 create mode 100644 arch/mips/jz4740/Platform
 create mode 100644 arch/mips/jz4740/board-qi_lb60.c
 create mode 100644 arch/mips/jz4740/clock-debugfs.c
 create mode 100644 arch/mips/jz4740/clock.c
 create mode 100644 arch/mips/jz4740/clock.h
 create mode 100644 arch/mips/jz4740/dma.c
 create mode 100644 arch/mips/jz4740/gpio.c
 create mode 100644 arch/mips/jz4740/irq.c
 create mode 100644 arch/mips/jz4740/irq.h
 create mode 100644 arch/mips/jz4740/platform.c
 create mode 100644 arch/mips/jz4740/pm.c
 create mode 100644 arch/mips/jz4740/prom.c
 create mode 100644 arch/mips/jz4740/pwm.c
 create mode 100644 arch/mips/jz4740/reset.c
 create mode 100644 arch/mips/jz4740/reset.h
 create mode 100644 arch/mips/jz4740/serial.c
 create mode 100644 arch/mips/jz4740/serial.h
 create mode 100644 arch/mips/jz4740/setup.c
 create mode 100644 arch/mips/jz4740/time.c
 create mode 100644 arch/mips/jz4740/timer.c
 create mode 100644 arch/mips/jz4740/timer.h
 create mode 100644 drivers/hwmon/jz4740-hwmon.c
 create mode 100644 drivers/mfd/jz4740-adc.c
 create mode 100644 drivers/mmc/host/jz4740_mmc.c
 create mode 100644 drivers/mtd/nand/jz4740_nand.c
 create mode 100644 drivers/power/jz4740-battery.c
 create mode 100644 drivers/rtc/rtc-jz4740.c
 create mode 100644 drivers/usb/host/ohci-jz4740.c
 create mode 100644 drivers/video/jz4740_fb.c
 create mode 100644 include/linux/jz4740-adc.h
 create mode 100644 include/linux/jz4740_fb.h
 create mode 100644 include/linux/mmc/jz4740_mmc.h
 create mode 100644 include/linux/mtd/jz4740_nand.h
 create mode 100644 include/linux/power/jz4740-battery.h
 create mode 100644 sound/soc/codecs/jz4740-codec.c
 create mode 100644 sound/soc/codecs/jz4740-codec.h
 create mode 100644 sound/soc/jz4740/Kconfig
 create mode 100644 sound/soc/jz4740/Makefile
 create mode 100644 sound/soc/jz4740/jz4740-i2s.c
 create mode 100644 sound/soc/jz4740/jz4740-i2s.h
 create mode 100644 sound/soc/jz4740/jz4740-pcm.c
 create mode 100644 sound/soc/jz4740/jz4740-pcm.h
 create mode 100644 sound/soc/jz4740/qi_lb60.c


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v2 01/26] MIPS: Add base support for Ingenic JZ4740 System-on-a-Chip
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (2 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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 <lars@metafoo.de>

---
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 <ralf@linux-mips.org>
+ */
+#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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 02/26] MIPS: jz4740: Add IRQ handler code
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (3 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-07-17 12:08   ` [PATCH v3] " Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 03/26] MIPS: JZ4740: Add clock API support.
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (4 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-06-28  1:24   ` [PATCH v3 " Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 04/26] MIPS: JZ4740: Add timer support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (5 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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 <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 05/26] MIPS: JZ4740: Add clocksource/clockevent support.
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (6 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 06/26] MIPS: JZ4740: Add power-management and system reset support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (7 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *	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 <lars@metafoo.de>
+ *
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 07/26] MIPS: JZ4740: Add setup code
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (8 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

This patch adds plat_mem_setup and get_system_type for JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 08/26] MIPS: JZ4740: Add gpio support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (9 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-07-17 12:11   ` [PATCH v3] " Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

This patch adds gpiolib support for JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 09/26] MIPS: JZ4740: Add DMA support.
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (10 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

This patch adds support for DMA transfers on JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 10/26] MIPS: JZ4740: Add PWM support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (11 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-06-28  1:23   ` [PATCH v3 " Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 11/26] MIPS: JZ4740: Add serial support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (12 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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 <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 12/26] MIPS: JZ4740: Add prom support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (13 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
---
 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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 13/26] MIPS: JZ4740: Add platform devices
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (14 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-07-17 12:13   ` [PATCH v3] " Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 14/26] MIPS: JZ4740: Add Kbuild files
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (15 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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 <lars@metafoo.de>

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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (16 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-06-19 10:43   ` Marek Vasut
  2010-06-19 19:29   ` [PATCH v3] " Lars-Peter Clausen
  -1 siblings, 2 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Alessandro Zummo,
	Paul Gortmaker, Wan ZongShun, Marek Vasut, rtc-linux

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
Cc: Wan ZongShun <mcuos.com@gmail.com>
Cc: Marek Vasut <marek.vasut@gmail.com>
Cc: rtc-linux@googlegroups.com

---
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 <lars@metafoo.de>
+ *	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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
+MODULE_ALIAS("platform:jz4740-rtc");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
  2010-06-19  5:08 ` Lars-Peter Clausen
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-fbdev

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-fbdev@vger.kernel.org

---
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 <lars@metafoo.de>
+ *	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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-fbdev

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-fbdev@vger.kernel.org

---
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 <lars@metafoo.de>
+ *	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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
  2010-06-19  5:08 ` Lars-Peter Clausen
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, David Woodhouse, linux-mtd

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: linux-mtd@lists.infradead.org

---
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, Lars-Peter Clausen, David Woodhouse, linux-kernel, linux-mtd

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: linux-mtd@lists.infradead.org

---
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *  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

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 18/26] MMC: Add JZ4740 mmc driver
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (19 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-06-19 14:46     ` Matt Fleming
  2010-06-28  1:20   ` [PATCH v3] " Lars-Peter Clausen
  -1 siblings, 2 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-mmc

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-mmc@vger.kernel.org

---
Changes since v1
- Do not request IRQ with IRQF_DISABLED since it is a noop now
- Use a generous slack for the timeout timer. It does not need to be accurate.
---
 drivers/mmc/host/Kconfig       |    8 +
 drivers/mmc/host/Makefile      |    1 +
 drivers/mmc/host/jz4740_mmc.c  |  993 ++++++++++++++++++++++++++++++++++++++++
 include/linux/mmc/jz4740_mmc.h |   15 +
 4 files changed, 1017 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mmc/host/jz4740_mmc.c
 create mode 100644 include/linux/mmc/jz4740_mmc.h

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f06d06e..546fc49 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -81,6 +81,14 @@ config MMC_RICOH_MMC
 
 	  If unsure, say Y.
 
+config MMC_JZ4740
+	tristate "JZ4740 SD/Multimedia Card Interface support"
+	depends on MACH_JZ4740
+	help
+	  This selects the Ingenic Z4740 SD/Multimedia card Interface.
+	  If you have an ngenic platform with a Multimedia Card slot,
+	  say Y or M here.
+
 config MMC_SDHCI_OF
 	tristate "SDHCI support on OpenFirmware platforms"
 	depends on MMC_SDHCI && PPC_OF
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e30c2ee..f4e53c9 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc.o
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
+obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
 
 obj-$(CONFIG_MMC_SDHCI_OF)	+= sdhci-of.o
 sdhci-of-y				:= sdhci-of-core.o
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c
new file mode 100644
index 0000000..5b73944
--- /dev/null
+++ b/drivers/mmc/host/jz4740_mmc.c
@@ -0,0 +1,993 @@
+/*
+ *  Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *	JZ4740 SD/MMC controller driver
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+#include <linux/mmc/jz4740_mmc.h>
+
+#include <linux/gpio.h>
+#include <asm/mach-jz4740/gpio.h>
+#include <asm/cacheflush.h>
+#include <linux/dma-mapping.h>
+
+#define JZ_REG_MMC_STRPCL	0x00
+#define JZ_REG_MMC_STATUS	0x04
+#define JZ_REG_MMC_CLKRT	0x08
+#define JZ_REG_MMC_CMDAT	0x0C
+#define JZ_REG_MMC_RESTO	0x10
+#define JZ_REG_MMC_RDTO		0x14
+#define JZ_REG_MMC_BLKLEN	0x18
+#define JZ_REG_MMC_NOB		0x1C
+#define JZ_REG_MMC_SNOB		0x20
+#define JZ_REG_MMC_IMASK	0x24
+#define JZ_REG_MMC_IREG		0x28
+#define JZ_REG_MMC_CMD		0x2C
+#define JZ_REG_MMC_ARG		0x30
+#define JZ_REG_MMC_RESP_FIFO	0x34
+#define JZ_REG_MMC_RXFIFO	0x38
+#define JZ_REG_MMC_TXFIFO	0x3C
+
+#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7)
+#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6)
+#define JZ_MMC_STRPCL_START_READWAIT BIT(5)
+#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4)
+#define JZ_MMC_STRPCL_RESET BIT(3)
+#define JZ_MMC_STRPCL_START_OP BIT(2)
+#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0))
+#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0)
+#define JZ_MMC_STRPCL_CLOCK_START BIT(1)
+
+
+#define JZ_MMC_STATUS_IS_RESETTING BIT(15)
+#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14)
+#define JZ_MMC_STATUS_PRG_DONE BIT(13)
+#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12)
+#define JZ_MMC_STATUS_END_CMD_RES BIT(11)
+#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10)
+#define JZ_MMC_STATUS_IS_READWAIT BIT(9)
+#define JZ_MMC_STATUS_CLK_EN BIT(8)
+#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7)
+#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6)
+#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5)
+#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4)
+#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3)
+#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2)
+#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1)
+#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0)
+
+#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0))
+#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2))
+
+
+#define JZ_MMC_CMDAT_IO_ABORT BIT(11)
+#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10)
+#define JZ_MMC_CMDAT_DMA_EN BIT(8)
+#define JZ_MMC_CMDAT_INIT BIT(7)
+#define JZ_MMC_CMDAT_BUSY BIT(6)
+#define JZ_MMC_CMDAT_STREAM BIT(5)
+#define JZ_MMC_CMDAT_WRITE BIT(4)
+#define JZ_MMC_CMDAT_DATA_EN BIT(3)
+#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0))
+#define JZ_MMC_CMDAT_RSP_R1 1
+#define JZ_MMC_CMDAT_RSP_R2 2
+#define JZ_MMC_CMDAT_RSP_R3 3
+
+#define JZ_MMC_IRQ_SDIO BIT(7)
+#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6)
+#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5)
+#define JZ_MMC_IRQ_END_CMD_RES BIT(2)
+#define JZ_MMC_IRQ_PRG_DONE BIT(1)
+#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0)
+
+
+#define JZ_MMC_CLK_RATE 24000000
+
+#define JZ4740_MMC_MAX_TIMEOUT 10000000
+
+struct jz4740_mmc_host {
+	struct mmc_host *mmc;
+	struct platform_device *pdev;
+	struct jz4740_mmc_platform_data *pdata;
+	struct clk *clk;
+
+	int irq;
+	int card_detect_irq;
+
+	struct resource *mem;
+	void __iomem *base;
+	struct mmc_request *req;
+	struct mmc_command *cmd;
+
+	int max_clock;
+	uint32_t cmdat;
+
+	uint16_t irq_mask;
+
+	spinlock_t lock;
+
+	struct timer_list timeout_timer;
+	unsigned waiting:1;
+};
+
+static void jz4740_mmc_cmd_done(struct jz4740_mmc_host *host);
+
+static void jz4740_mmc_enable_irq(struct jz4740_mmc_host *host, unsigned int irq)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&host->lock, flags);
+
+	host->irq_mask &= ~irq;
+	writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void jz4740_mmc_disable_irq(struct jz4740_mmc_host *host, unsigned int irq)
+{
+	unsigned long flags;
+	spin_lock_irqsave(&host->lock, flags);
+
+	host->irq_mask |= irq;
+	writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK);
+
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+
+static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host,
+	bool start_transfer)
+{
+	uint16_t val = JZ_MMC_STRPCL_CLOCK_START;
+
+	if (start_transfer)
+		val |= JZ_MMC_STRPCL_START_OP;
+
+	writew(val, host->base + JZ_REG_MMC_STRPCL);
+}
+
+static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+
+	writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_CLK_EN);
+}
+
+static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+
+	writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
+	udelay(10);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_IS_RESETTING);
+}
+
+static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
+{
+	struct mmc_request *req;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	req = host->req;
+	host->req = NULL;
+	host->waiting = 0;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	if (!unlikely(req))
+		return;
+
+	mmc_request_done(host->mmc, req);
+}
+
+static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host,
+	unsigned int irq)
+{
+	unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT;
+	uint16_t status;
+
+	do {
+		status = readw(host->base + JZ_REG_MMC_IREG);
+	} while (!(status & irq) && --timeout);
+
+	return timeout;
+}
+
+static void jz4740_mmc_write_data(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	struct scatterlist *sg;
+	uint32_t *sg_pointer;
+	int status;
+	unsigned int timeout;
+	size_t i, j;
+
+	for (sg = data->sg; sg; sg = sg_next(sg)) {
+		sg_pointer = sg_virt(sg);
+		i = sg->length / 4;
+		j = i >> 3;
+		i = i & 0x7;
+		while (j) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout == 0))
+				goto err_timeout;
+
+			writel(sg_pointer[0], host->base + JZ_REG_MMC_TXFIFO);
+			writel(sg_pointer[1], host->base + JZ_REG_MMC_TXFIFO);
+			writel(sg_pointer[2], host->base + JZ_REG_MMC_TXFIFO);
+			writel(sg_pointer[3], host->base + JZ_REG_MMC_TXFIFO);
+			writel(sg_pointer[4], host->base + JZ_REG_MMC_TXFIFO);
+			writel(sg_pointer[5], host->base + JZ_REG_MMC_TXFIFO);
+			writel(sg_pointer[6], host->base + JZ_REG_MMC_TXFIFO);
+			writel(sg_pointer[7], host->base + JZ_REG_MMC_TXFIFO);
+			sg_pointer += 8;
+			--j;
+		}
+		if (i) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout == 0))
+				goto err_timeout;
+
+			while (i) {
+				writel(*sg_pointer, host->base + JZ_REG_MMC_TXFIFO);
+				++sg_pointer;
+				--i;
+			}
+		}
+		data->bytes_xfered += sg->length;
+	}
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) {
+		if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) {
+			host->req->cmd->error = -ETIMEDOUT;
+			data->error = -ETIMEDOUT;
+		} else {
+			host->req->cmd->error = -EIO;
+			data->error = -EIO;
+		}
+		return;
+	}
+
+	timeout = JZ4740_MMC_MAX_TIMEOUT;
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while ((status & JZ_MMC_STATUS_DATA_TRAN_DONE) == 0 && --timeout);
+
+	if (unlikely(timeout == 0))
+		goto err_timeout;
+	writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG);
+
+	return;
+
+err_timeout:
+	host->req->cmd->error = -ETIMEDOUT;
+	data->error = -ETIMEDOUT;
+}
+
+static void jz4740_mmc_timeout(unsigned long data)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (!host->waiting) {
+		spin_unlock_irqrestore(&host->lock, flags);
+		return;
+	}
+
+	host->waiting = 0;
+
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	host->req->cmd->error = -ETIMEDOUT;
+	jz4740_mmc_request_done(host);
+}
+
+static void jz4740_mmc_read_data(struct jz4740_mmc_host *host,
+				struct mmc_data *data)
+{
+	struct scatterlist *sg;
+	uint32_t *sg_pointer;
+	uint32_t d;
+	uint16_t status = 0;
+	size_t i, j;
+	unsigned int timeout;
+
+	for (sg = data->sg; sg; sg = sg_next(sg)) {
+		sg_pointer = sg_virt(sg);
+		i = sg->length;
+		j = i >> 5;
+		i = i & 0x1f;
+		while (j) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout == 0))
+				goto err_timeout;
+
+			sg_pointer[0] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			sg_pointer[1] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			sg_pointer[2] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			sg_pointer[3] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			sg_pointer[4] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			sg_pointer[5] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			sg_pointer[6] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			sg_pointer[7] = readl(host->base + JZ_REG_MMC_RXFIFO);
+
+			sg_pointer += 8;
+			--j;
+		}
+
+		while (i >= 4) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout == 0))
+				goto err_timeout;
+
+			*sg_pointer = readl(host->base + JZ_REG_MMC_RXFIFO);
+			++sg_pointer;
+			i -= 4;
+		}
+		if (i > 0) {
+			d = readl(host->base + JZ_REG_MMC_RXFIFO);
+			memcpy(sg_pointer, &d, i);
+		}
+		data->bytes_xfered += sg->length;
+
+		flush_dcache_page(sg_page(sg));
+	}
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	if (status & JZ_MMC_STATUS_READ_ERROR_MASK) {
+		if (status & JZ_MMC_STATUS_TIMEOUT_READ) {
+			host->req->cmd->error = -ETIMEDOUT;
+			data->error = -ETIMEDOUT;
+		} else {
+			host->req->cmd->error = -EIO;
+			data->error = -EIO;
+		}
+		return;
+	}
+
+	/* For whatever reason there is sometime one word more in the fifo then
+	 * requested */
+	while ((status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) == 0 && --timeout) {
+		d = readl(host->base + JZ_REG_MMC_RXFIFO);
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	}
+	return;
+
+err_timeout:
+	host->req->cmd->error = -ETIMEDOUT;
+	data->error = -ETIMEDOUT;
+}
+
+static irqreturn_t jz_mmc_irq_worker(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid;
+
+	if (host->cmd->error)
+		jz4740_mmc_request_done(host);
+	else
+		jz4740_mmc_cmd_done(host);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t jz_mmc_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+	uint16_t irq_reg, status, tmp;
+	unsigned long flags;
+	irqreturn_t ret = IRQ_HANDLED;
+
+	irq_reg = readw(host->base + JZ_REG_MMC_IREG);
+
+	tmp = irq_reg;
+	spin_lock_irqsave(&host->lock, flags);
+	irq_reg &= ~host->irq_mask;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ |
+			JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE);
+
+	if (tmp != irq_reg)
+		writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG);
+
+	if (irq_reg & JZ_MMC_IRQ_SDIO) {
+		writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG);
+		mmc_signal_sdio_irq(host->mmc);
+	}
+
+	if (!host->req || !host->cmd)
+		goto handled;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (!host->waiting) {
+		spin_unlock_irqrestore(&host->lock, flags);
+		goto handled;
+	}
+
+	host->waiting = 0;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	del_timer(&host->timeout_timer);
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+
+	if (status & JZ_MMC_STATUS_TIMEOUT_RES) {
+		host->cmd->error = -ETIMEDOUT;
+	} else if (status & JZ_MMC_STATUS_CRC_RES_ERR) {
+		host->cmd->error = -EIO;
+	} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+			JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+		host->cmd->data->error = -EIO;
+	} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+			JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+		host->cmd->data->error = -EIO;
+	}
+
+	if (irq_reg & JZ_MMC_IRQ_END_CMD_RES) {
+		jz4740_mmc_disable_irq(host, JZ_MMC_IRQ_END_CMD_RES);
+		writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+		ret = IRQ_WAKE_THREAD;
+	}
+
+	return ret;
+
+handled:
+	writew(0xff, host->base + JZ_REG_MMC_IREG);
+	return IRQ_HANDLED;
+}
+
+static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate)
+{
+	int div = 0;
+	int real_rate;
+
+	jz4740_mmc_clock_disable(host);
+	clk_set_rate(host->clk, JZ_MMC_CLK_RATE);
+
+	real_rate = clk_get_rate(host->clk);
+
+	while (real_rate > rate && div < 7) {
+		++div;
+		real_rate >>= 1;
+	}
+
+	writew(div, host->base + JZ_REG_MMC_CLKRT);
+	return real_rate;
+}
+
+static void jz4740_mmc_read_response(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	int i;
+	uint16_t tmp;
+
+	if (cmd->flags & MMC_RSP_136) {
+		tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+		for (i = 0; i < 4; ++i) {
+			cmd->resp[i] = tmp << 24;
+			cmd->resp[i] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8;
+			tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+			cmd->resp[i] |= tmp >> 8;
+		}
+	} else {
+		cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff;
+	}
+}
+
+static void jz4740_mmc_send_command(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	uint32_t cmdat = host->cmdat;
+
+	host->cmdat &= ~JZ_MMC_CMDAT_INIT;
+	jz4740_mmc_clock_disable(host);
+
+	host->cmd = cmd;
+
+	if (cmd->flags & MMC_RSP_BUSY)
+		cmdat |= JZ_MMC_CMDAT_BUSY;
+
+	switch (mmc_resp_type(cmd)) {
+	case MMC_RSP_R1B:
+	case MMC_RSP_R1:
+		cmdat |= JZ_MMC_CMDAT_RSP_R1;
+		break;
+	case MMC_RSP_R2:
+		cmdat |= JZ_MMC_CMDAT_RSP_R2;
+		break;
+	case MMC_RSP_R3:
+		cmdat |= JZ_MMC_CMDAT_RSP_R3;
+		break;
+	default:
+		break;
+	}
+
+	if (cmd->data) {
+		cmdat |= JZ_MMC_CMDAT_DATA_EN;
+		if (cmd->data->flags & MMC_DATA_WRITE)
+			cmdat |= JZ_MMC_CMDAT_WRITE;
+		if (cmd->data->flags & MMC_DATA_STREAM)
+			cmdat |= JZ_MMC_CMDAT_STREAM;
+
+		writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN);
+		writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB);
+	}
+
+	writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD);
+	writel(cmd->arg, host->base + JZ_REG_MMC_ARG);
+	writel(cmdat, host->base + JZ_REG_MMC_CMDAT);
+
+	host->waiting = 1;
+	jz4740_mmc_clock_enable(host, 1);
+	mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+}
+
+static void jz4740_mmc_cmd_done(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_request *req = host->req;
+	unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT;
+
+	if (cmd->flags & MMC_RSP_PRESENT)
+		jz4740_mmc_read_response(host, cmd);
+
+	if (cmd->data) {
+		if (cmd->data->flags & MMC_DATA_READ)
+			jz4740_mmc_read_data(host, cmd->data);
+		else
+			jz4740_mmc_write_data(host, cmd->data);
+	}
+
+	if (req->stop) {
+		jz4740_mmc_send_command(host, req->stop);
+		do {
+			status = readw(host->base + JZ_REG_MMC_IREG);
+		} while ((status & JZ_MMC_IRQ_PRG_DONE) == 0 && --timeout);
+		writew(JZ_MMC_IRQ_PRG_DONE, host->base + JZ_REG_MMC_IREG);
+	}
+
+	if (unlikely(timeout == 0))
+		req->stop->error = -ETIMEDOUT;
+
+	jz4740_mmc_request_done(host);
+}
+
+static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+
+	host->req = req;
+
+	writew(0xffff, host->base + JZ_REG_MMC_IREG);
+
+	writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+	jz4740_mmc_enable_irq(host, JZ_MMC_IRQ_END_CMD_RES);
+	jz4740_mmc_send_command(host, req->cmd);
+}
+
+static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (ios->clock)
+		jz4740_mmc_set_clock_rate(host, ios->clock);
+
+	switch (ios->power_mode) {
+	case MMC_POWER_UP:
+		jz4740_mmc_reset(host);
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					!host->pdata->power_active_low);
+		host->cmdat |= JZ_MMC_CMDAT_INIT;
+		clk_enable(host->clk);
+		break;
+	case MMC_POWER_ON:
+		break;
+	default:
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					host->pdata->power_active_low);
+		clk_disable(host->clk);
+		break;
+	}
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_1:
+		host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	case MMC_BUS_WIDTH_4:
+		host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	default:
+		break;
+	}
+}
+
+static int jz4740_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_read_only))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_read_only) ^
+		host->pdata->read_only_active_low;
+}
+
+static int jz4740_mmc_get_cd(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_card_detect))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_card_detect) ^
+			host->pdata->card_detect_active_low;
+}
+
+static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+
+	mmc_detect_change(host->mmc, HZ / 3);
+
+	return IRQ_HANDLED;
+}
+
+static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+
+	if (enable)
+		jz4740_mmc_enable_irq(host, JZ_MMC_IRQ_SDIO);
+	else
+		jz4740_mmc_disable_irq(host, JZ_MMC_IRQ_SDIO);
+}
+
+static const struct mmc_host_ops jz4740_mmc_ops = {
+	.request	= jz4740_mmc_request,
+	.set_ios	= jz4740_mmc_set_ios,
+	.get_ro		= jz4740_mmc_get_ro,
+	.get_cd		= jz4740_mmc_get_cd,
+	.enable_sdio_irq = jz4740_mmc_enable_sdio_irq,
+};
+
+static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = {
+	JZ_GPIO_BULK_PIN(MSC_CMD),
+	JZ_GPIO_BULK_PIN(MSC_CLK),
+	JZ_GPIO_BULK_PIN(MSC_DATA0),
+	JZ_GPIO_BULK_PIN(MSC_DATA1),
+	JZ_GPIO_BULK_PIN(MSC_DATA2),
+	JZ_GPIO_BULK_PIN(MSC_DATA3),
+};
+
+static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return 0;
+
+	if (gpio_is_valid(pdata->gpio_card_detect)) {
+		ret = gpio_request(pdata->gpio_card_detect, "MMC detect change");
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request detect change gpio\n");
+			goto err;
+		}
+		gpio_direction_input(pdata->gpio_card_detect);
+	}
+
+	if (gpio_is_valid(pdata->gpio_read_only)) {
+		ret = gpio_request(pdata->gpio_read_only, "MMC read only");
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request read only gpio: %d\n", ret);
+			goto err_free_gpio_card_detect;
+		}
+		gpio_direction_input(pdata->gpio_read_only);
+	}
+
+	if (gpio_is_valid(pdata->gpio_power)) {
+		ret = gpio_request(pdata->gpio_power, "MMC power");
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request power gpio: %d\n", ret);
+			goto err_free_gpio_read_only;
+		}
+		gpio_direction_output(pdata->gpio_power, pdata->power_active_low);
+	}
+
+	return 0;
+
+err_free_gpio_read_only:
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+err_free_gpio_card_detect:
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+err:
+	return ret;
+}
+
+static void jz4740_mmc_free_gpios(struct platform_device *pdev)
+{
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return;
+
+	if (gpio_is_valid(pdata->gpio_power))
+		gpio_free(pdata->gpio_power);
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+}
+
+static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host)
+{
+	size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins);
+	if (host->pdata && host->pdata->data_1bit)
+		num_pins -= 3;
+
+	return num_pins;
+}
+
+static int __devinit jz4740_mmc_probe(struct platform_device* pdev)
+{
+	int ret;
+	struct mmc_host *mmc;
+	struct jz4740_mmc_host *host;
+	struct jz4740_mmc_platform_data *pdata;
+
+	pdata = pdev->dev.platform_data;
+
+	mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev);
+	if (!mmc) {
+		dev_err(&pdev->dev, "Failed to alloc mmc host structure\n");
+		return -ENOMEM;
+	}
+
+	host = mmc_priv(mmc);
+	host->pdata = pdata;
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq < 0) {
+		ret = host->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+		goto err_free_host;
+	}
+
+	host->clk = clk_get(&pdev->dev, "mmc");
+	if (!host->clk) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get mmc clock\n");
+		goto err_free_host;
+	}
+
+	host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!host->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get base platform memory\n");
+		goto err_clk_put;
+	}
+
+	host->mem = request_mem_region(host->mem->start,
+					resource_size(host->mem), pdev->name);
+	if (!host->mem) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to request base memory region\n");
+		goto err_clk_put;
+	}
+
+	host->base = ioremap_nocache(host->mem->start, resource_size(host->mem));
+	if (!host->base) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to ioremap base memory\n");
+		goto err_release_mem_region;
+	}
+
+	ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret);
+		goto err_iounmap;
+	}
+
+	ret = jz4740_mmc_request_gpios(pdev);
+	if (ret)
+		goto err_gpio_bulk_free;
+
+	mmc->ops = &jz4740_mmc_ops;
+	mmc->f_min = JZ_MMC_CLK_RATE / 128;
+	mmc->f_max = JZ_MMC_CLK_RATE;
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA;
+	mmc->caps |= MMC_CAP_SDIO_IRQ;
+
+	mmc->max_blk_size = (1 << 10) - 1;
+	mmc->max_blk_count = (1 << 15) - 1;
+	mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+	mmc->max_phys_segs = 128;
+	mmc->max_hw_segs = 128;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host->mmc = mmc;
+	host->pdev = pdev;
+	host->max_clock = JZ_MMC_CLK_RATE;
+	spin_lock_init(&host->lock);
+	host->irq_mask = 0xffff;
+
+	host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect);
+
+	if (host->card_detect_irq < 0) {
+		dev_warn(&pdev->dev, "Failed to get irq for card detect gpio\n");
+	} else {
+		ret = request_irq(host->card_detect_irq,
+			jz4740_mmc_card_detect_irq,
+			IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"MMC card detect", host);
+
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request card detect irq");
+			goto err_free_gpios;
+		}
+	}
+
+	ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0,
+			dev_name(&pdev->dev), host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+		goto err_free_card_detect_irq;
+	}
+
+	jz4740_mmc_reset(host);
+	jz4740_mmc_clock_disable(host);
+	setup_timer(&host->timeout_timer, jz4740_mmc_timeout,
+			(unsigned long)host);
+	/* It is not that important when it times out, it just needs to timeout. */
+	set_timer_slack(&host->timeout_timer, HZ);
+
+	platform_set_drvdata(pdev, host);
+	ret = mmc_add_host(mmc);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret);
+		goto err_free_irq;
+	}
+	dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n");
+
+	return 0;
+
+err_free_irq:
+	free_irq(host->irq, host);
+err_free_card_detect_irq:
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+err_free_gpios:
+	jz4740_mmc_free_gpios(pdev);
+err_gpio_bulk_free:
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+err_iounmap:
+	iounmap(host->base);
+err_release_mem_region:
+	release_mem_region(host->mem->start, resource_size(host->mem));
+err_clk_put:
+	clk_put(host->clk);
+err_free_host:
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(mmc);
+
+	return ret;
+}
+
+static int __devexit jz4740_mmc_remove(struct platform_device *pdev)
+{
+	struct jz4740_mmc_host *host = platform_get_drvdata(pdev);
+
+	del_timer_sync(&host->timeout_timer);
+	jz4740_mmc_disable_irq(host, 0xff);
+	jz4740_mmc_reset(host);
+
+	mmc_remove_host(host->mmc);
+
+	free_irq(host->irq, host);
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+
+	jz4740_mmc_free_gpios(pdev);
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	iounmap(host->base);
+	release_mem_region(host->mem->start, resource_size(host->mem));
+
+	clk_put(host->clk);
+
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(host->mmc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int jz4740_mmc_suspend(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	mmc_suspend_host(host->mmc);
+
+	jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	return 0;
+}
+
+static int jz4740_mmc_resume(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	mmc_resume_host(host->mmc);
+
+	return 0;
+}
+
+const struct dev_pm_ops jz4740_mmc_pm_ops = {
+	.suspend	= jz4740_mmc_suspend,
+	.resume		= jz4740_mmc_resume,
+	.poweroff	= jz4740_mmc_suspend,
+	.restore	= jz4740_mmc_resume,
+};
+
+#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops)
+#else
+#define JZ4740_MMC_PM_OPS NULL
+#endif
+
+static struct platform_driver jz4740_mmc_driver = {
+	.probe = jz4740_mmc_probe,
+	.remove = __devexit_p(jz4740_mmc_remove),
+	.driver = {
+		.name = "jz4740-mmc",
+		.owner = THIS_MODULE,
+		.pm = JZ4740_MMC_PM_OPS,
+	},
+};
+
+static int __init jz4740_mmc_init(void)
+{
+	return platform_driver_register(&jz4740_mmc_driver);
+}
+module_init(jz4740_mmc_init);
+
+static void __exit jz4740_mmc_exit(void)
+{
+	platform_driver_unregister(&jz4740_mmc_driver);
+}
+module_exit(jz4740_mmc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
diff --git a/include/linux/mmc/jz4740_mmc.h b/include/linux/mmc/jz4740_mmc.h
new file mode 100644
index 0000000..8543f43
--- /dev/null
+++ b/include/linux/mmc/jz4740_mmc.h
@@ -0,0 +1,15 @@
+#ifndef __LINUX_MMC_JZ4740_MMC
+#define __LINUX_MMC_JZ4740_MMC
+
+struct jz4740_mmc_platform_data {
+	int gpio_power;
+	int gpio_card_detect;
+	int gpio_read_only;
+	unsigned card_detect_active_low:1;
+	unsigned read_only_active_low:1;
+	unsigned power_active_low:1;
+
+	unsigned data_1bit:1;
+};
+
+#endif
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 19/26] USB: Add JZ4740 ohci support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (20 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-06-19 17:17   ` Greg KH
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Greg Kroah-Hartman,
	David Brownell, linux-usb

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Greg Kroah-Hartman <gregkh@suse.de>
Cc: David Brownell <dbrownell@users.sourceforge.net>
Cc: linux-usb@vger.kernel.org

---
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 <lars@metafoo.de>
+ *
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 20/26] alsa: ASoC: Add JZ4740 codec driver
  2010-06-19  5:08 ` Lars-Peter Clausen
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Mark Brown,
	Liam Girdwood, alsa-devel

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ * 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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 20/26] alsa: ASoC: Add JZ4740 codec driver
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, alsa-devel, Lars-Peter Clausen, Mark Brown,
	linux-kernel, Liam Girdwood

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ * 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

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 21/26] alsa: ASoC: Add JZ4740 ASoC support
  2010-06-19  5:08 ` Lars-Peter Clausen
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Mark Brown,
	Liam Girdwood, alsa-devel

This patch adds ASoC support for JZ4740 SoCs I2S module.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ *  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, <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>");
+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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 21/26] alsa: ASoC: Add JZ4740 ASoC support
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, alsa-devel, Lars-Peter Clausen, Mark Brown,
	linux-kernel, Liam Girdwood

This patch adds ASoC support for JZ4740 SoCs I2S module.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ *  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, <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>");
+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

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 22/26] MFD: Add JZ4740 ADC driver
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (23 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-07-04 22:47   ` Lars-Peter Clausen
                     ` (2 more replies)
  -1 siblings, 3 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Samuel Ortiz

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 <lars@metafoo.de>
Cc: Samuel Ortiz <sameo@linux.intel.com>
---
 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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver
  2010-06-19  5:08 ` Lars-Peter Clausen
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Jonathan Cameron,
	lm-sensors

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Jonathan Cameron <kernel@jic23.retrosnub.co.uk>
Cc: lm-sensors@lm-sensors.org

---
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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Jonathan Cameron,
	lm-sensors

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Jonathan Cameron <kernel@jic23.retrosnub.co.uk>
Cc: lm-sensors@lm-sensors.org

---
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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
-- 
1.5.6.5


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 24/26] power: Add JZ4740 battery driver.
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (25 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-06-27  1:58   ` Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Anton Vorontsov

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Anton Vorontsov <cbouatmailru@gmail.com>

---
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 <kzjeef@gmail.com>
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * based on tosa_battery.c
+ *
+ * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com>
+*
+ * 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 <lars@metafoo.de>");
+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 <kzjeef@gmail.com>
+ *
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 25/26] MIPS: JZ4740: Add qi_lb60 board support
  2010-06-19  5:08 ` Lars-Peter Clausen
                   ` (26 preceding siblings ...)
  (?)
@ 2010-06-19  5:08 ` Lars-Peter Clausen
  2010-07-17 12:16   ` [PATCH v3] " Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <xiangfu@qi-hardware.com>
+ * Copyright 2010, Lars-Petrer Clausen <lars@metafoo.de>
+ *
+ * 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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 26/26] alsa: ASoC: JZ4740: Add qi_lb60 board driver
  2010-06-19  5:08 ` Lars-Peter Clausen
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Mark Brown,
	Liam Girdwood, alsa-devel

This patch adds ASoC support for the qi_lb60 board.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v2 26/26] alsa: ASoC: JZ4740: Add qi_lb60 board driver
@ 2010-06-19  5:08   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19  5:08 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, alsa-devel, Lars-Peter Clausen, Mark Brown,
	linux-kernel, Liam Girdwood

This patch adds ASoC support for the qi_lb60 board.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver
@ 2010-06-19  8:36     ` Jean Delvare
  0 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-19  8:36 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, Jonathan Cameron, Lars-Peter Clausen,
	linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: Jonathan Cameron <kernel@jic23.retrosnub.co.uk>
> Cc: lm-sensors@lm-sensors.org
> 
> ---
> 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 <lars@metafoo.de>
> + * 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 <lars@metafoo.de>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-hwmon");


-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver
@ 2010-06-19  8:36     ` Jean Delvare
  0 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-19  8:36 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, Jonathan Cameron, linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: Jonathan Cameron <kernel@jic23.retrosnub.co.uk>
> Cc: lm-sensors@lm-sensors.org
> 
> ---
> 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 <lars@metafoo.de>
> + * 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 <lars@metafoo.de>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-hwmon");


-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver
@ 2010-06-19  8:36     ` Jean Delvare
  0 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-19  8:36 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, Jonathan Cameron, Lars-Peter Clausen,
	linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: Jonathan Cameron <kernel@jic23.retrosnub.co.uk>
> Cc: lm-sensors@lm-sensors.org
> 
> ---
> 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 <lars@metafoo.de>
> + * 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 <lars@metafoo.de>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-hwmon");


-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19  5:08 ` [PATCH v2 15/26] RTC: Add JZ4740 RTC driver Lars-Peter Clausen
@ 2010-06-19 10:43   ` Marek Vasut
  2010-06-19 13:05     ` Lars-Peter Clausen
  2010-06-19 19:29   ` [PATCH v3] " Lars-Peter Clausen
  1 sibling, 1 reply; 163+ messages in thread
From: Marek Vasut @ 2010-06-19 10:43 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Alessandro Zummo,
	Paul Gortmaker, Wan ZongShun, rtc-linux

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 <lars@metafoo.de>
> Cc: Alessandro Zummo <a.zummo@towertech.it>
> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
> Cc: Wan ZongShun <mcuos.com@gmail.com>
> Cc: Marek Vasut <marek.vasut@gmail.com>
> Cc: rtc-linux@googlegroups.com
> 
> ---
> 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 <lars@metafoo.de>
> + *	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 <lars@metafoo.de>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
> +MODULE_ALIAS("platform:jz4740-rtc");

Cheers

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver
  2010-06-19  8:36     ` Jean Delvare
@ 2010-06-19 12:58       ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 12:58 UTC (permalink / raw)
  To: Jean Delvare
  Cc: Ralf Baechle, linux-mips, Jonathan Cameron, linux-kernel, lm-sensors

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 <lars@metafoo.de>
>> Cc: Jonathan Cameron <kernel@jic23.retrosnub.co.uk>
>> Cc: lm-sensors@lm-sensors.org
>>
>> ---
>> 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 <lars@metafoo.de>
>> + * 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 <lars@metafoo.de>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:jz4740-hwmon");
>>     
>
>   
Thanks for reviewing
-Lars


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v2 23/26] hwmon: Add JZ4740 ADC driver
@ 2010-06-19 12:58       ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 12:58 UTC (permalink / raw)
  To: Jean Delvare
  Cc: Ralf Baechle, linux-mips, Jonathan Cameron, linux-kernel, lm-sensors

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 <lars@metafoo.de>
>> Cc: Jonathan Cameron <kernel@jic23.retrosnub.co.uk>
>> Cc: lm-sensors@lm-sensors.org
>>
>> ---
>> 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 <lars@metafoo.de>
>> + * 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 <lars@metafoo.de>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_ALIAS("platform:jz4740-hwmon");
>>     
>
>   
Thanks for reviewing
-Lars


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19 10:43   ` Marek Vasut
@ 2010-06-19 13:05     ` Lars-Peter Clausen
  2010-06-19 13:37       ` Wan ZongShun
  2010-06-19 14:04       ` Marek Vasut
  0 siblings, 2 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 13:05 UTC (permalink / raw)
  To: Marek Vasut
  Cc: Ralf Baechle, linux-mips, linux-kernel, Alessandro Zummo,
	Paul Gortmaker, Wan ZongShun, rtc-linux

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 <lars@metafoo.de>
>> Cc: Alessandro Zummo <a.zummo@towertech.it>
>> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
>> Cc: Wan ZongShun <mcuos.com@gmail.com>
>> Cc: Marek Vasut <marek.vasut@gmail.com>
>> Cc: rtc-linux@googlegroups.com
>>
>> ---
>> 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 <lars@metafoo.de>
>> + *	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 <lars@metafoo.de>");
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
>> +MODULE_ALIAS("platform:jz4740-rtc");
>>     
>
> Cheers
>   
Thanks for reviewing

- Lars

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19 13:05     ` Lars-Peter Clausen
@ 2010-06-19 13:37       ` Wan ZongShun
  2010-06-19 13:53         ` Lars-Peter Clausen
  2010-06-19 14:04       ` Marek Vasut
  1 sibling, 1 reply; 163+ messages in thread
From: Wan ZongShun @ 2010-06-19 13:37 UTC (permalink / raw)
  To: Lars-Peter Clausen, Andrew Morton
  Cc: Marek Vasut, Ralf Baechle, linux-mips, linux-kernel,
	Alessandro Zummo, Paul Gortmaker, rtc-linux

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 <lars@metafoo.de>
>>> Cc: Alessandro Zummo <a.zummo@towertech.it>
>>> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
>>> Cc: Wan ZongShun <mcuos.com@gmail.com>
>>> Cc: Marek Vasut <marek.vasut@gmail.com>
>>> Cc: rtc-linux@googlegroups.com
>>>
>>> ---
>>> 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 <lars@metafoo.de>
>>> + *	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 <lars@metafoo.de>");
>>> +MODULE_LICENSE("GPL");
>>> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
>>> +MODULE_ALIAS("platform:jz4740-rtc");
>>>     
>> Cheers
>>   
> Thanks for reviewing
> 
> - Lars
> 


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19 13:37       ` Wan ZongShun
@ 2010-06-19 13:53         ` Lars-Peter Clausen
  2010-06-19 14:36           ` Wan ZongShun
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 13:53 UTC (permalink / raw)
  To: Wan ZongShun
  Cc: Andrew Morton, Marek Vasut, Ralf Baechle, linux-mips,
	linux-kernel, Alessandro Zummo, Paul Gortmaker, rtc-linux

-----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 <lars@metafoo.de>
>>>> Cc: Alessandro Zummo <a.zummo@towertech.it>
>>>> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
>>>> Cc: Wan ZongShun <mcuos.com@gmail.com>
>>>> Cc: Marek Vasut <marek.vasut@gmail.com>
>>>> Cc: rtc-linux@googlegroups.com
>>>>
>>>> ---
>>>> 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 <lars@metafoo.de>
>>>> + *    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 <lars@metafoo.de>");
>>>> +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-----


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19 13:05     ` Lars-Peter Clausen
  2010-06-19 13:37       ` Wan ZongShun
@ 2010-06-19 14:04       ` Marek Vasut
  2010-06-19 17:42         ` Lars-Peter Clausen
  1 sibling, 1 reply; 163+ messages in thread
From: Marek Vasut @ 2010-06-19 14:04 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Alessandro Zummo,
	Paul Gortmaker, Wan ZongShun, rtc-linux

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 <lars@metafoo.de>
> >> Cc: Alessandro Zummo <a.zummo@towertech.it>
> >> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
> >> Cc: Wan ZongShun <mcuos.com@gmail.com>
> >> Cc: Marek Vasut <marek.vasut@gmail.com>
> >> Cc: rtc-linux@googlegroups.com
> >> 
> >> ---
> >> 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 <lars@metafoo.de>
> >> + *	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 <lars@metafoo.de>");
> >> +MODULE_LICENSE("GPL");
> >> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
> >> +MODULE_ALIAS("platform:jz4740-rtc");
> > 
> > Cheers
> 
> Thanks for reviewing
> 
> - Lars

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19 13:53         ` Lars-Peter Clausen
@ 2010-06-19 14:36           ` Wan ZongShun
  0 siblings, 0 replies; 163+ messages in thread
From: Wan ZongShun @ 2010-06-19 14:36 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Andrew Morton, Marek Vasut, Ralf Baechle, linux-mips,
	linux-kernel, Alessandro Zummo, Paul Gortmaker, rtc-linux

2010/6/19 Lars-Peter Clausen <lars@metafoo.de>:
> -----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 <lars@metafoo.de>
>>>>> Cc: Alessandro Zummo <a.zummo@towertech.it>
>>>>> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
>>>>> Cc: Wan ZongShun <mcuos.com@gmail.com>
>>>>> Cc: Marek Vasut <marek.vasut@gmail.com>
>>>>> Cc: rtc-linux@googlegroups.com
>>>>>
>>>>> ---
>>>>> 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 <lars@metafoo.de>
>>>>> + *    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 <lars@metafoo.de>");
>>>>> +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:linux-arm-kernel@lists.infradead.org
you can subscribe by:
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

* linux-arm-NUC900 mailing list
mail addr:NUC900@googlegroups.com
main web: https://groups.google.com/group/NUC900
you can subscribe it by sending me mail:
mcuos.com@gmail.com

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver
@ 2010-06-19 14:46     ` Matt Fleming
  0 siblings, 0 replies; 163+ messages in thread
From: Matt Fleming @ 2010-06-19 14:46 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	linux-mmc, Ralf Baechle

On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> 

Hey Lars-Peter,

I had a quick look over this patch and it looks OK. Just a few comments.

> +static void jz4740_mmc_timeout(unsigned long data)
> +{
> +	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&host->lock, flags);
> +	if (!host->waiting) {
> +		spin_unlock_irqrestore(&host->lock, flags);
> +		return;
> +	}
> +
> +	host->waiting = 0;
> +
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	host->req->cmd->error = -ETIMEDOUT;
> +	jz4740_mmc_request_done(host);
> +}
> +

Taking a spinlock and disabling interrupts seems like too much overhead
to simply test and clear a bit. Wouldn't it be better to implement this
with test_and_clear_bit(), which on MIPS will likely be implemented with
ll/sc instructions? It's particularly important to keep this
low-overhead since this bit is modified in the interrupt handler.

> +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
> +{
> +	struct mmc_request *req;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&host->lock, flags);
> +	req = host->req;
> +	host->req = NULL;
> +	host->waiting = 0;
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	if (!unlikely(req))
> +		return;
> +
> +	mmc_request_done(host->mmc, req);
> +}
> +

Am I right in thinking that this spinlock guards against the interrupt
handler and the timeout function running at the same time? So it's not
really possible to drop the spinlock from here?

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver
@ 2010-06-19 14:46     ` Matt Fleming
  0 siblings, 0 replies; 163+ messages in thread
From: Matt Fleming @ 2010-06-19 14:46 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-kernel, Andrew Morton, linux-mmc, Ralf Baechle

On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> 

Hey Lars-Peter,

I had a quick look over this patch and it looks OK. Just a few comments.

> +static void jz4740_mmc_timeout(unsigned long data)
> +{
> +	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&host->lock, flags);
> +	if (!host->waiting) {
> +		spin_unlock_irqrestore(&host->lock, flags);
> +		return;
> +	}
> +
> +	host->waiting = 0;
> +
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	host->req->cmd->error = -ETIMEDOUT;
> +	jz4740_mmc_request_done(host);
> +}
> +

Taking a spinlock and disabling interrupts seems like too much overhead
to simply test and clear a bit. Wouldn't it be better to implement this
with test_and_clear_bit(), which on MIPS will likely be implemented with
ll/sc instructions? It's particularly important to keep this
low-overhead since this bit is modified in the interrupt handler.

> +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
> +{
> +	struct mmc_request *req;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&host->lock, flags);
> +	req = host->req;
> +	host->req = NULL;
> +	host->waiting = 0;
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	if (!unlikely(req))
> +		return;
> +
> +	mmc_request_done(host->mmc, req);
> +}
> +

Am I right in thinking that this spinlock guards against the interrupt
handler and the timeout function running at the same time? So it's not
really possible to drop the spinlock from here?

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver
@ 2010-06-19 14:46     ` Matt Fleming
  0 siblings, 0 replies; 163+ messages in thread
From: Matt Fleming @ 2010-06-19 14:46 UTC (permalink / raw)
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	linux-mmc, Ralf Baechle

On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> 

Hey Lars-Peter,

I had a quick look over this patch and it looks OK. Just a few comments.

> +static void jz4740_mmc_timeout(unsigned long data)
> +{
> +	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&host->lock, flags);
> +	if (!host->waiting) {
> +		spin_unlock_irqrestore(&host->lock, flags);
> +		return;
> +	}
> +
> +	host->waiting = 0;
> +
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	host->req->cmd->error = -ETIMEDOUT;
> +	jz4740_mmc_request_done(host);
> +}
> +

Taking a spinlock and disabling interrupts seems like too much overhead
to simply test and clear a bit. Wouldn't it be better to implement this
with test_and_clear_bit(), which on MIPS will likely be implemented with
ll/sc instructions? It's particularly important to keep this
low-overhead since this bit is modified in the interrupt handler.

> +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
> +{
> +	struct mmc_request *req;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&host->lock, flags);
> +	req = host->req;
> +	host->req = NULL;
> +	host->waiting = 0;
> +	spin_unlock_irqrestore(&host->lock, flags);
> +
> +	if (!unlikely(req))
> +		return;
> +
> +	mmc_request_done(host->mmc, req);
> +}
> +

Am I right in thinking that this spinlock guards against the interrupt
handler and the timeout function running at the same time? So it's not
really possible to drop the spinlock from here?

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v3] hwmon: Add JZ4740 ADC driver
  2010-06-19  5:08   ` [lm-sensors] " Lars-Peter Clausen
@ 2010-06-19 14:47     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:47 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen, lm-sensors

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: lm-sensors@lm-sensors.org

--
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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [lm-sensors] [PATCH v3] hwmon: Add JZ4740 ADC driver
@ 2010-06-19 14:47     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:47 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen, lm-sensors

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: lm-sensors@lm-sensors.org

--
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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
-- 
1.5.6.5


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] alsa: ASoC: Add JZ4740 codec driver
  2010-06-19  5:08   ` Lars-Peter Clausen
@ 2010-06-19 14:49     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:49 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Mark Brown,
	Liam Girdwood, alsa-devel

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

--
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ * 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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] alsa: ASoC: Add JZ4740 codec driver
@ 2010-06-19 14:49     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:49 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, alsa-devel, Lars-Peter Clausen, Mark Brown,
	linux-kernel, Liam Girdwood

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

--
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ * 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

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] alsa: ASoC: Add JZ4740 ASoC support
  2010-06-19  5:08   ` Lars-Peter Clausen
@ 2010-06-19 14:50     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:50 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Mark Brown,
	Liam Girdwood, alsa-devel

This patch adds ASoC support for JZ4740 SoCs I2S module.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

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 <lars@metafoo.de>
+ *
+ *  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, <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>");
+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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] alsa: ASoC: Add JZ4740 ASoC support
@ 2010-06-19 14:50     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:50 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, alsa-devel, Lars-Peter Clausen, Mark Brown,
	linux-kernel, Liam Girdwood

This patch adds ASoC support for JZ4740 SoCs I2S module.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

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 <lars@metafoo.de>
+ *
+ *  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, <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>");
+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

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] alsa: ASoC: JZ4740: Add qi_lb60 board driver
  2010-06-19  5:08   ` Lars-Peter Clausen
@ 2010-06-19 14:52     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:52 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Mark Brown,
	Liam Girdwood, alsa-devel

This patch adds ASoC support for the qi_lb60 board.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

--
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] alsa: ASoC: JZ4740: Add qi_lb60 board driver
@ 2010-06-19 14:52     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 14:52 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, alsa-devel, Lars-Peter Clausen, Mark Brown,
	linux-kernel, Liam Girdwood

This patch adds ASoC support for the qi_lb60 board.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

--
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("ALSA SoC QI LB60 Audio support");
+MODULE_LICENSE("GPL v2");
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 18/26] MMC: Add JZ4740 mmc driver
  2010-06-19 14:46     ` Matt Fleming
  (?)
  (?)
@ 2010-06-19 15:29     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 15:29 UTC (permalink / raw)
  To: Matt Fleming
  Cc: linux-mips, linux-kernel, Andrew Morton, linux-mmc, Ralf Baechle

Hi

Matt Fleming wrote:
> On Sat, 19 Jun 2010 07:08:23 +0200, Lars-Peter Clausen
<lars@metafoo.de> wrote:
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>>
>
> Hey Lars-Peter,
>
> I had a quick look over this patch and it looks OK. Just a few comments.
>
>> +static void jz4740_mmc_timeout(unsigned long data)
>> +{
>> +    struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
>> +    unsigned long flags;
>> +
>> +    spin_lock_irqsave(&host->lock, flags);
>> +    if (!host->waiting) {
>> +        spin_unlock_irqrestore(&host->lock, flags);
>> +        return;
>> +    }
>> +
>> +    host->waiting = 0;
>> +
>> +    spin_unlock_irqrestore(&host->lock, flags);
>> +
>> +    host->req->cmd->error = -ETIMEDOUT;
>> +    jz4740_mmc_request_done(host);
>> +}
>> +
>
> Taking a spinlock and disabling interrupts seems like too much overhead
> to simply test and clear a bit. Wouldn't it be better to implement this
> with test_and_clear_bit(), which on MIPS will likely be implemented with
> ll/sc instructions? It's particularly important to keep this
> low-overhead since this bit is modified in the interrupt handler.
>
Sounds like a good idea :)
>> +static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
>> +{
>> +    struct mmc_request *req;
>> +    unsigned long flags;
>> +
>> +    spin_lock_irqsave(&host->lock, flags);
>> +    req = host->req;
>> +    host->req = NULL;
>> +    host->waiting = 0;
>> +    spin_unlock_irqrestore(&host->lock, flags);
>> +
>> +    if (!unlikely(req))
>> +        return;
>> +
>> +    mmc_request_done(host->mmc, req);
>> +}
>> +
>
> Am I right in thinking that this spinlock guards against the interrupt
> handler and the timeout function running at the same time? So it's not
> really possible to drop the spinlock from here?
>
Yes, at least that is what it was meant for. But it was there before
the waiting bit and right now I can not construct any code paths that
could lead to jz4740_mmc_request_done from two paths at the same time.
The timer wont call it if the waiting bit is not set and the irq
handler won't wake the threaded irq handler if the waiting bit is not
set. I'll think a bit more about it and eventually drop the spinlock here.
Thanks for your review :)

- Lars


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v3] hwmon: Add JZ4740 ADC driver
  2010-06-19 14:47     ` [lm-sensors] " Lars-Peter Clausen
@ 2010-06-19 16:24       ` Jean Delvare
  -1 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-19 16:24 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: lm-sensors@lm-sensors.org
> 
> --
> 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 <lars@metafoo.de>
> + * 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 <lars@metafoo.de>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-hwmon");


-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v3] hwmon: Add JZ4740 ADC driver
@ 2010-06-19 16:24       ` Jean Delvare
  0 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-19 16:24 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: lm-sensors@lm-sensors.org
> 
> --
> 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 <lars@metafoo.de>
> + * 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 <lars@metafoo.de>");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:jz4740-hwmon");


-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 19/26] USB: Add JZ4740 ohci support
  2010-06-19  5:08 ` [PATCH v2 19/26] USB: Add JZ4740 ohci support Lars-Peter Clausen
@ 2010-06-19 17:17   ` Greg KH
  0 siblings, 0 replies; 163+ messages in thread
From: Greg KH @ 2010-06-19 17:17 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, David Brownell, linux-usb

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 <lars@metafoo.de>
> Cc: Greg Kroah-Hartman <gregkh@suse.de>
> Cc: David Brownell <dbrownell@users.sourceforge.net>
> Cc: linux-usb@vger.kernel.org

Acked-by: Greg Kroah-Hartman <gregkh@suse.de>


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19 14:04       ` Marek Vasut
@ 2010-06-19 17:42         ` Lars-Peter Clausen
  2010-06-19 17:53           ` Geert Uytterhoeven
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 17:42 UTC (permalink / raw)
  To: Marek Vasut
  Cc: Ralf Baechle, linux-mips, linux-kernel, Alessandro Zummo,
	Paul Gortmaker, Wan ZongShun, rtc-linux

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 <lars@metafoo.de>
>>>> Cc: Alessandro Zummo <a.zummo@towertech.it>
>>>> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
>>>> Cc: Wan ZongShun <mcuos.com@gmail.com>
>>>> Cc: Marek Vasut <marek.vasut@gmail.com>
>>>> Cc: rtc-linux@googlegroups.com
>>>>
>>>> ---
>>>> 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 <lars@metafoo.de>
>>>> + *	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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 15/26] RTC: Add JZ4740 RTC driver
  2010-06-19 17:42         ` Lars-Peter Clausen
@ 2010-06-19 17:53           ` Geert Uytterhoeven
  0 siblings, 0 replies; 163+ messages in thread
From: Geert Uytterhoeven @ 2010-06-19 17:53 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Marek Vasut, Ralf Baechle, linux-mips, linux-kernel,
	Alessandro Zummo, Paul Gortmaker, Wan ZongShun, rtc-linux

On Sat, Jun 19, 2010 at 19:42, Lars-Peter Clausen <lars@metafoo.de> 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 -- geert@linux-m68k.org

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v3] hwmon: Add JZ4740 ADC driver
  2010-06-19 16:24       ` Jean Delvare
@ 2010-06-19 17:59         ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 17:59 UTC (permalink / raw)
  To: Jean Delvare; +Cc: Ralf Baechle, linux-mips, linux-kernel, lm-sensors

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 <lars@metafoo.de>
>> Cc: lm-sensors@lm-sensors.org
>>
>> --
>> 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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v3] hwmon: Add JZ4740 ADC driver
@ 2010-06-19 17:59         ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 17:59 UTC (permalink / raw)
  To: Jean Delvare; +Cc: Ralf Baechle, linux-mips, linux-kernel, lm-sensors

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 <lars@metafoo.de>
>> Cc: lm-sensors@lm-sensors.org
>>
>> --
>> 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

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v3] RTC: Add JZ4740 RTC driver
  2010-06-19  5:08 ` [PATCH v2 15/26] RTC: Add JZ4740 RTC driver Lars-Peter Clausen
  2010-06-19 10:43   ` Marek Vasut
@ 2010-06-19 19:29   ` Lars-Peter Clausen
  2010-06-20  1:13     ` [rtc-linux] " Wan ZongShun
  2010-06-22  5:53     ` Alessandro Zummo
  1 sibling, 2 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 19:29 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Alessandro Zummo,
	Paul Gortmaker, rtc-linux

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Alessandro Zummo <a.zummo@towertech.it>
Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
Cc: rtc-linux@googlegroups.com

--
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 <lars@metafoo.de>
+ *	 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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
+MODULE_ALIAS("platform:jz4740-rtc");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v4] hwmon: Add JZ4740 ADC driver
  2010-06-19 14:47     ` [lm-sensors] " Lars-Peter Clausen
@ 2010-06-19 19:32       ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 19:32 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen, lm-sensors

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: lm-sensors@lm-sensors.org

--
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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [lm-sensors] [PATCH v4] hwmon: Add JZ4740 ADC driver
@ 2010-06-19 19:32       ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-19 19:32 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen, lm-sensors

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: lm-sensors@lm-sensors.org

--
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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:jz4740-hwmon");
-- 
1.5.6.5


_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [rtc-linux] [PATCH v3] RTC: Add JZ4740 RTC driver
  2010-06-19 19:29   ` [PATCH v3] " Lars-Peter Clausen
@ 2010-06-20  1:13     ` Wan ZongShun
  2010-06-20  1:23       ` Lars-Peter Clausen
  2010-06-22  5:53     ` Alessandro Zummo
  1 sibling, 1 reply; 163+ messages in thread
From: Wan ZongShun @ 2010-06-20  1:13 UTC (permalink / raw)
  To: rtc-linux, Andrew Morton
  Cc: Ralf Baechle, linux-mips, linux-kernel, Lars-Peter Clausen,
	Alessandro Zummo, Paul Gortmaker


> This patch adds support for the RTC unit on JZ4740 SoCs.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Alessandro Zummo <a.zummo@towertech.it>
> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
> Cc: rtc-linux@googlegroups.com
> 
> --
> 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 <mcuos.com@gmail.com>

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 <lars@metafoo.de>
> + *	 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 <lars@metafoo.de>");
> +MODULE_LICENSE("GPL");
> +MODULE_DESCRIPTION("RTC driver for the JZ4740 SoC\n");
> +MODULE_ALIAS("platform:jz4740-rtc");


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [rtc-linux] [PATCH v3] RTC: Add JZ4740 RTC driver
  2010-06-20  1:13     ` [rtc-linux] " Wan ZongShun
@ 2010-06-20  1:23       ` Lars-Peter Clausen
  2010-06-20  1:30         ` Wan ZongShun
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-20  1:23 UTC (permalink / raw)
  To: Wan ZongShun
  Cc: rtc-linux, Andrew Morton, Ralf Baechle, linux-mips, linux-kernel,
	Alessandro Zummo, Paul Gortmaker

Wan ZongShun wrote:
>
>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>> Cc: Alessandro Zummo <a.zummo@towertech.it>
>> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
>> Cc: rtc-linux@googlegroups.com
>>
>> -- 
>> 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 <mcuos.com@gmail.com>
>
> 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



^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [rtc-linux] [PATCH v3] RTC: Add JZ4740 RTC driver
  2010-06-20  1:23       ` Lars-Peter Clausen
@ 2010-06-20  1:30         ` Wan ZongShun
  0 siblings, 0 replies; 163+ messages in thread
From: Wan ZongShun @ 2010-06-20  1:30 UTC (permalink / raw)
  To: Lars-Peter Clausen, Andrew Morton
  Cc: rtc-linux, Ralf Baechle, linux-mips, linux-kernel,
	Alessandro Zummo, Paul Gortmaker

Lars-Peter Clausen :
> Wan ZongShun wrote:
>>> This patch adds support for the RTC unit on JZ4740 SoCs.
>>>
>>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>>> Cc: Alessandro Zummo <a.zummo@towertech.it>
>>> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
>>> Cc: rtc-linux@googlegroups.com
>>>
>>> -- 
>>> 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 <mcuos.com@gmail.com>
>>
>> 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 <mcuos.com@gmail.com>

> Thanks,
> - Lars
> 
> 
> 


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v4] hwmon: Add JZ4740 ADC driver
@ 2010-06-20  6:32         ` Jean Delvare
  0 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-20  6:32 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, Lars-Peter Clausen, linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: lm-sensors@lm-sensors.org
> 
> --
> 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 <khali@linux-fr.org>

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v4] hwmon: Add JZ4740 ADC driver
@ 2010-06-20  6:32         ` Jean Delvare
  0 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-20  6:32 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: lm-sensors@lm-sensors.org
> 
> --
> 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 <khali@linux-fr.org>

-- 
Jean Delvare

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v4] hwmon: Add JZ4740 ADC driver
@ 2010-06-20  6:32         ` Jean Delvare
  0 siblings, 0 replies; 163+ messages in thread
From: Jean Delvare @ 2010-06-20  6:32 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, Lars-Peter Clausen, linux-kernel, lm-sensors

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 <lars@metafoo.de>
> Cc: lm-sensors@lm-sensors.org
> 
> --
> 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 <khali@linux-fr.org>

-- 
Jean Delvare

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-19  5:08 ` Lars-Peter Clausen
                     ` (2 preceding siblings ...)
  (?)
@ 2010-06-20  9:26   ` Thomas Bogendoerfer
  -1 siblings, 0 replies; 163+ messages in thread
From: Thomas Bogendoerfer @ 2010-06-20  9:26 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Alessandro Zummo,
	Andrew Morton, Anton Vorontsov, David Brownell, David Woodhouse,
	Greg Kroah-Hartman, Liam Girdwood, Mark Brown, Paul Gortmaker,
	Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc, linux-mtd,
	linux-usb, lm-sensors, rtc-linux

On Sat, Jun 19, 2010 at 07:08:05AM +0200, Lars-Peter Clausen wrote:
> This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

great stuff. I have a JZ4730 based netbook, for which I started magling
the provided sources quite some time ago, but I didn't reach the
point of submitting patches... there are a lot of common stuff between
JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
everthing to JZ4740 namewise. It might also a good idea to select
something like arch/mips/jzrisc as base directory, put the
factored out code there and add JZ4730/JZ4740 in either seperate
files or directories.

Thomas.

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740
@ 2010-06-20  9:26   ` Thomas Bogendoerfer
  0 siblings, 0 replies; 163+ messages in thread
From: Thomas Bogendoerfer @ 2010-06-20  9:26 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, Paul Gortmaker, David Brownell, Mark Brown,
	Samuel Ortiz, alsa-devel, Alessandro Zummo, Greg Kroah-Hartman,
	linux-mmc, linux-kernel, Ralf Baechle, lm-sensors, linux-usb,
	linux-mtd, linux-fbdev, Anton Vorontsov, rtc-linux,
	Andrew Morton, David Woodhouse, Liam Girdwood

On Sat, Jun 19, 2010 at 07:08:05AM +0200, Lars-Peter Clausen wrote:
> This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

great stuff. I have a JZ4730 based netbook, for which I started magling
the provided sources quite some time ago, but I didn't reach the
point of submitting patches... there are a lot of common stuff between
JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
everthing to JZ4740 namewise. It might also a good idea to select
something like arch/mips/jzrisc as base directory, put the
factored out code there and add JZ4730/JZ4740 in either seperate
files or directories.

Thomas.

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
@ 2010-06-20  9:26   ` Thomas Bogendoerfer
  0 siblings, 0 replies; 163+ messages in thread
From: Thomas Bogendoerfer @ 2010-06-20  9:26 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, Paul Gortmaker, David Brownell, Mark Brown,
	Samuel Ortiz, alsa-devel, Alessandro Zummo, Greg Kroah-Hartman,
	linux-mmc, linux-kernel, Ralf Baechle, lm-sensors, linux-usb,
	linux-mtd, linux-fbdev, Anton Vorontsov, rtc-linux,
	Andrew Morton, David Woodhouse, Liam Girdwood

On Sat, Jun 19, 2010 at 07:08:05AM +0200, Lars-Peter Clausen wrote:
> This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

great stuff. I have a JZ4730 based netbook, for which I started magling
the provided sources quite some time ago, but I didn't reach the
point of submitting patches... there are a lot of common stuff between
JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
everthing to JZ4740 namewise. It might also a good idea to select
something like arch/mips/jzrisc as base directory, put the
factored out code there and add JZ4730/JZ4740 in either seperate
files or directories.

Thomas.

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
@ 2010-06-20  9:26   ` Thomas Bogendoerfer
  0 siblings, 0 replies; 163+ messages in thread
From: Thomas Bogendoerfer @ 2010-06-20  9:26 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, Paul Gortmaker, David Brownell, Mark Brown,
	Samuel Ortiz, alsa-devel, Alessandro Zummo, Greg Kroah-Hartman,
	linux-mmc, linux-kernel, Ralf Baechle, lm-sensors, linux-usb,
	linux-mtd, linux-fbdev, Anton Vorontsov, rtc-linux,
	Andrew Morton, David Woodhouse, Liam Girdwood

On Sat, Jun 19, 2010 at 07:08:05AM +0200, Lars-Peter Clausen wrote:
> This patch series adds support for the Ingenic JZ4740 System-on-a-Chip.

great stuff. I have a JZ4730 based netbook, for which I started magling
the provided sources quite some time ago, but I didn't reach the
point of submitting patches... there are a lot of common stuff between
JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
everthing to JZ4740 namewise. It might also a good idea to select
something like arch/mips/jzrisc as base directory, put the
factored out code there and add JZ4730/JZ4740 in either seperate
files or directories.

Thomas.

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v2 00/26] Add support for the Ingenic
@ 2010-06-20  9:26   ` Thomas Bogendoerfer
  0 siblings, 0 replies; 163+ messages in thread
From: Thomas Bogendoerfer @ 2010-06-20  9:26 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Alessandro Zummo,
	Andrew Morton, Anton Vorontsov, David Brownell, David Woodhouse,
	Greg Kroah-Hartman, Liam Girdwood, Mark Brown, Paul Gortmaker,
	Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc, linux-mtd,
	linux-usb, lm-sensors, rtc-linux

On 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 ]

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] alsa: ASoC: Add JZ4740 codec driver
  2010-06-19 14:49     ` Lars-Peter Clausen
@ 2010-06-20 13:11       ` Mark Brown
  -1 siblings, 0 replies; 163+ messages in thread
From: Mark Brown @ 2010-06-20 13:11 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Liam Girdwood, alsa-devel

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.

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] alsa: ASoC: Add JZ4740 codec driver
@ 2010-06-20 13:11       ` Mark Brown
  0 siblings, 0 replies; 163+ messages in thread
From: Mark Brown @ 2010-06-20 13:11 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, alsa-devel, linux-kernel, Ralf Baechle, Liam Girdwood

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.

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-20  9:26   ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 Thomas Bogendoerfer
@ 2010-06-20 14:31     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-20 14:31 UTC (permalink / raw)
  To: Thomas Bogendoerfer; +Cc: Ralf Baechle, linux-mips, linux-kernel, Graham Gower

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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740        System-on-a-Chip
@ 2010-06-20 14:31     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-20 14:31 UTC (permalink / raw)
  To: Thomas Bogendoerfer; +Cc: Ralf Baechle, linux-mips, linux-kernel, Graham Gower

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-20 14:31     ` Lars-Peter Clausen
  (?)
@ 2010-06-20 16:34     ` Thomas Bogendoerfer
  2010-06-20 16:49       ` Lars-Peter Clausen
  -1 siblings, 1 reply; 163+ messages in thread
From: Thomas Bogendoerfer @ 2010-06-20 16:34 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel, Graham Gower

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 ]

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-20 16:34     ` Thomas Bogendoerfer
@ 2010-06-20 16:49       ` Lars-Peter Clausen
  2010-06-20 17:01         ` Thomas Bogendoerfer
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-20 16:49 UTC (permalink / raw)
  To: Thomas Bogendoerfer; +Cc: Ralf Baechle, linux-mips, linux-kernel, Graham Gower

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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-20 16:49       ` Lars-Peter Clausen
@ 2010-06-20 17:01         ` Thomas Bogendoerfer
  2010-06-20 17:57           ` Florian Fainelli
  0 siblings, 1 reply; 163+ messages in thread
From: Thomas Bogendoerfer @ 2010-06-20 17:01 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel, Graham Gower

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 ]

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-20 17:01         ` Thomas Bogendoerfer
@ 2010-06-20 17:57           ` Florian Fainelli
  2010-06-20 18:30             ` Lars-Peter Clausen
  0 siblings, 1 reply; 163+ messages in thread
From: Florian Fainelli @ 2010-06-20 17:57 UTC (permalink / raw)
  To: Thomas Bogendoerfer
  Cc: Lars-Peter Clausen, Ralf Baechle, linux-mips, linux-kernel, Graham Gower

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-20 17:57           ` Florian Fainelli
@ 2010-06-20 18:30             ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-20 18:30 UTC (permalink / raw)
  To: Florian Fainelli
  Cc: Thomas Bogendoerfer, Ralf Baechle, linux-mips, linux-kernel,
	Graham Gower

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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
  2010-06-20  9:26   ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 Thomas Bogendoerfer
  (?)
  (?)
@ 2010-06-21  2:56     ` Xiangfu Liu
  -1 siblings, 0 replies; 163+ messages in thread
From: Xiangfu Liu @ 2010-06-21  2:56 UTC (permalink / raw)
  To: Thomas Bogendoerfer
  Cc: Lars-Peter Clausen, Ralf Baechle, linux-mips, linux-kernel,
	Alessandro Zummo, Andrew Morton, Anton Vorontsov, David Brownell,
	David Woodhouse, Greg Kroah-Hartman, Liam Girdwood, Mark Brown,
	Paul Gortmaker, Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc,
	linux-mtd, linux-usb, lm-sensors, rtc-linux

On 06/20/2010 05:26 PM, Thomas Bogendoerfer wrote:
> great stuff. I have a JZ4730 based netbook, for which I started magling
> the provided sources quite some time ago, but I didn't reach the
> point of submitting patches... there are a lot of common stuff between
> JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
> everthing to JZ4740 namewise. It might also a good idea to select
> something like arch/mips/jzrisc as base directory, put the

Hi Thomas

I would advice "xburst" instead jzrisc. because the Ingenic call
their cpu "XBurst" series. like: XBurst JZ4740, XBurst JZ4750 ...


> factored out code there and add JZ4730/JZ4740 in either seperate
> files or directories.


-- 
Best Regards
Xiangfu Liu
http://www.openmobilefree.net

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
@ 2010-06-21  2:56     ` Xiangfu Liu
  0 siblings, 0 replies; 163+ messages in thread
From: Xiangfu Liu @ 2010-06-21  2:56 UTC (permalink / raw)
  To: Thomas Bogendoerfer
  Cc: Lars-Peter Clausen, Ralf Baechle, linux-mips, linux-kernel,
	Alessandro Zummo, Andrew Morton, Anton Vorontsov, David Brownell,
	David Woodhouse, Greg Kroah-Hartman, Liam Girdwood, Mark Brown,
	Paul Gortmaker, Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc,
	linux-mtd, linux-usb, lm-sensors, rtc-linux

On 06/20/2010 05:26 PM, Thomas Bogendoerfer wrote:
> great stuff. I have a JZ4730 based netbook, for which I started magling
> the provided sources quite some time ago, but I didn't reach the
> point of submitting patches... there are a lot of common stuff between
> JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
> everthing to JZ4740 namewise. It might also a good idea to select
> something like arch/mips/jzrisc as base directory, put the

Hi Thomas

I would advice "xburst" instead jzrisc. because the Ingenic call
their cpu "XBurst" series. like: XBurst JZ4740, XBurst JZ4750 ...


> factored out code there and add JZ4730/JZ4740 in either seperate
> files or directories.


-- 
Best Regards
Xiangfu Liu
http://www.openmobilefree.net

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip
@ 2010-06-21  2:56     ` Xiangfu Liu
  0 siblings, 0 replies; 163+ messages in thread
From: Xiangfu Liu @ 2010-06-21  2:56 UTC (permalink / raw)
  To: Thomas Bogendoerfer
  Cc: linux-mips, Paul Gortmaker, Lars-Peter Clausen, Mark Brown,
	Samuel Ortiz, alsa-devel, Alessandro Zummo, Greg Kroah-Hartman,
	linux-mmc, linux-kernel, Ralf Baechle, lm-sensors,
	David Brownell, linux-mtd, linux-fbdev, Anton Vorontsov,
	rtc-linux, Andrew Morton, David Woodhouse, linux-usb,
	Liam Girdwood

On 06/20/2010 05:26 PM, Thomas Bogendoerfer wrote:
> great stuff. I have a JZ4730 based netbook, for which I started magling
> the provided sources quite some time ago, but I didn't reach the
> point of submitting patches... there are a lot of common stuff between
> JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
> everthing to JZ4740 namewise. It might also a good idea to select
> something like arch/mips/jzrisc as base directory, put the

Hi Thomas

I would advice "xburst" instead jzrisc. because the Ingenic call
their cpu "XBurst" series. like: XBurst JZ4740, XBurst JZ4750 ...


> factored out code there and add JZ4730/JZ4740 in either seperate
> files or directories.


-- 
Best Regards
Xiangfu Liu
http://www.openmobilefree.net

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [lm-sensors] [PATCH v2 00/26] Add support for the Ingenic
@ 2010-06-21  2:56     ` Xiangfu Liu
  0 siblings, 0 replies; 163+ messages in thread
From: Xiangfu Liu @ 2010-06-21  2:56 UTC (permalink / raw)
  To: Thomas Bogendoerfer
  Cc: Lars-Peter Clausen, Ralf Baechle, linux-mips, linux-kernel,
	Alessandro Zummo, Andrew Morton, Anton Vorontsov, David Brownell,
	David Woodhouse, Greg Kroah-Hartman, Liam Girdwood, Mark Brown,
	Paul Gortmaker, Samuel Ortiz, alsa-devel, linux-fbdev, linux-mmc,
	linux-mtd, linux-usb, lm-sensors, rtc-linux

On 06/20/2010 05:26 PM, Thomas Bogendoerfer wrote:
> great stuff. I have a JZ4730 based netbook, for which I started magling
> the provided sources quite some time ago, but I didn't reach the
> point of submitting patches... there are a lot of common stuff between
> JZ4730 and JZ4740 so IMHO it would be a good thing not to nail
> everthing to JZ4740 namewise. It might also a good idea to select
> something like arch/mips/jzrisc as base directory, put the

Hi Thomas

I would advice "xburst" instead jzrisc. because the Ingenic call
their cpu "XBurst" series. like: XBurst JZ4740, XBurst JZ4750 ...


> factored out code there and add JZ4730/JZ4740 in either seperate
> files or directories.


-- 
Best Regards
Xiangfu Liu
http://www.openmobilefree.net

_______________________________________________
lm-sensors mailing list
lm-sensors@lm-sensors.org
http://lists.lm-sensors.org/mailman/listinfo/lm-sensors

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v4] alsa: ASoC: Add JZ4740 codec driver
  2010-06-19 14:49     ` Lars-Peter Clausen
@ 2010-06-21 22:46       ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-21 22:46 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Mark Brown,
	Liam Girdwood, alsa-devel

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ * 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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v4] alsa: ASoC: Add JZ4740 codec driver
@ 2010-06-21 22:46       ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-21 22:46 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, alsa-devel, Lars-Peter Clausen, Mark Brown,
	linux-kernel, Liam Girdwood

This patch adds support for the JZ4740 internal codec.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
Cc: Liam Girdwood <lrg@slimlogic.co.uk>
Cc: alsa-devel@alsa-project.org

---
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 <lars@metafoo.de>
+ *
+ * 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 <lars@metafoo.de>");
+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 <lars@metafoo.de>
+ *
+ * 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

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] RTC: Add JZ4740 RTC driver
  2010-06-19 19:29   ` [PATCH v3] " Lars-Peter Clausen
  2010-06-20  1:13     ` [rtc-linux] " Wan ZongShun
@ 2010-06-22  5:53     ` Alessandro Zummo
  1 sibling, 0 replies; 163+ messages in thread
From: Alessandro Zummo @ 2010-06-22  5:53 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Paul Gortmaker, rtc-linux

On Sat, 19 Jun 2010 21:29:50 +0200
Lars-Peter Clausen <lars@metafoo.de> wrote:

> This patch adds support for the RTC unit on JZ4740 SoCs.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Alessandro Zummo <a.zummo@towertech.it>
> Cc: Paul Gortmaker <p_gortmaker@yahoo.com>
> Cc: rtc-linux@googlegroups.com

 Acked-by: Alessandro Zummo <a.zummo@towertech.it>


-- 

 Best regards,

 Alessandro Zummo,
  Tower Technologies - Torino, Italy

  http://www.towertech.it


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v4] alsa: ASoC: Add JZ4740 codec driver
  2010-06-21 22:46       ` Lars-Peter Clausen
@ 2010-06-22 10:12         ` Liam Girdwood
  -1 siblings, 0 replies; 163+ messages in thread
From: Liam Girdwood @ 2010-06-22 10:12 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Mark Brown, alsa-devel

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 <lars@metafoo.de>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Liam Girdwood <lrg@slimlogic.co.uk>
> Cc: alsa-devel@alsa-project.org
> 
> ---
> 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 <lrg@slimlogic.co.uk>
-- 
Freelance Developer, SlimLogic Ltd
ASoC and Voltage Regulator Maintainer.
http://www.slimlogic.co.uk


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v4] alsa: ASoC: Add JZ4740 codec driver
@ 2010-06-22 10:12         ` Liam Girdwood
  0 siblings, 0 replies; 163+ messages in thread
From: Liam Girdwood @ 2010-06-22 10:12 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, alsa-devel, Mark Brown, linux-kernel, Ralf Baechle

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 <lars@metafoo.de>
> Cc: Mark Brown <broonie@opensource.wolfsonmicro.com>
> Cc: Liam Girdwood <lrg@slimlogic.co.uk>
> Cc: alsa-devel@alsa-project.org
> 
> ---
> 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 <lrg@slimlogic.co.uk>
-- 
Freelance Developer, SlimLogic Ltd
ASoC and Voltage Regulator Maintainer.
http://www.slimlogic.co.uk

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v4] alsa: ASoC: Add JZ4740 codec driver
  2010-06-21 22:46       ` Lars-Peter Clausen
@ 2010-06-22 23:12         ` Mark Brown
  -1 siblings, 0 replies; 163+ messages in thread
From: Mark Brown @ 2010-06-22 23:12 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Liam Girdwood, alsa-devel

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.

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v4] alsa: ASoC: Add JZ4740 codec driver
@ 2010-06-22 23:12         ` Mark Brown
  0 siblings, 0 replies; 163+ messages in thread
From: Mark Brown @ 2010-06-22 23:12 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, alsa-devel, linux-kernel, Ralf Baechle, Liam Girdwood

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.

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 24/26] power: Add JZ4740 battery driver.
  2010-06-19  5:08 ` [PATCH v2 24/26] power: Add JZ4740 battery driver Lars-Peter Clausen
@ 2010-06-27  1:58   ` Lars-Peter Clausen
  2010-06-28 11:43     ` Anton Vorontsov
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-27  1:58 UTC (permalink / raw)
  To: Anton Vorontsov; +Cc: linux-mips, linux-kernel, Ralf Baechle

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 <lars@metafoo.de>
> Cc: Anton Vorontsov <cbouatmailru@gmail.com>
> 
> ---
> 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 <kzjeef@gmail.com>
> + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
> + *
> + * based on tosa_battery.c
> + *
> + * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.com>
> +*
> + * 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 <lars@metafoo.de>");
> +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 <kzjeef@gmail.com>
> + *
> + *  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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v3] MMC: Add JZ4740 mmc driver
  2010-06-19  5:08 ` [PATCH v2 18/26] MMC: Add JZ4740 mmc driver Lars-Peter Clausen
  2010-06-19 14:46     ` Matt Fleming
@ 2010-06-28  1:20   ` Lars-Peter Clausen
  2010-06-29 20:17       ` Matt Fleming
                       ` (2 more replies)
  1 sibling, 3 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-28  1:20 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	Matt Fleming, linux-mmc

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Matt Fleming <matt@console-pimps.org>
Cc: linux-mmc@vger.kernel.org

---
Changes since v1
- Do not request IRQ with IRQF_DISABLED since it is a noop now
- Use a generous slack for the timeout timer. It does not need to be accurate.
Changes since v2
- Use sg_mapping_to iterate over sg elements in mmc read and write functions
- Use bitops instead of a spinlock and a variable for testing whether a request
  has been finished.
- Rework irq and timeout handling in order to get rid of locking in hot paths
---
 drivers/mmc/host/Kconfig       |    8 +
 drivers/mmc/host/Makefile      |    1 +
 drivers/mmc/host/jz4740_mmc.c  |  969 ++++++++++++++++++++++++++++++++++++++++
 include/linux/mmc/jz4740_mmc.h |   15 +
 4 files changed, 993 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mmc/host/jz4740_mmc.c
 create mode 100644 include/linux/mmc/jz4740_mmc.h

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f06d06e..546fc49 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -81,6 +81,14 @@ config MMC_RICOH_MMC
 
 	  If unsure, say Y.
 
+config MMC_JZ4740
+	tristate "JZ4740 SD/Multimedia Card Interface support"
+	depends on MACH_JZ4740
+	help
+	  This selects the Ingenic Z4740 SD/Multimedia card Interface.
+	  If you have an ngenic platform with a Multimedia Card slot,
+	  say Y or M here.
+
 config MMC_SDHCI_OF
 	tristate "SDHCI support on OpenFirmware platforms"
 	depends on MMC_SDHCI && PPC_OF
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e30c2ee..f4e53c9 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc.o
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
+obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
 
 obj-$(CONFIG_MMC_SDHCI_OF)	+= sdhci-of.o
 sdhci-of-y				:= sdhci-of-core.o
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c
new file mode 100644
index 0000000..b34f25b
--- /dev/null
+++ b/drivers/mmc/host/jz4740_mmc.c
@@ -0,0 +1,969 @@
+/*
+ *  Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *  JZ4740 SD/MMC controller driver
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+#include <linux/mmc/jz4740_mmc.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <asm/mach-jz4740/gpio.h>
+#include <asm/cacheflush.h>
+#include <linux/dma-mapping.h>
+
+
+#define JZ_REG_MMC_STRPCL	0x00
+#define JZ_REG_MMC_STATUS	0x04
+#define JZ_REG_MMC_CLKRT	0x08
+#define JZ_REG_MMC_CMDAT	0x0C
+#define JZ_REG_MMC_RESTO	0x10
+#define JZ_REG_MMC_RDTO		0x14
+#define JZ_REG_MMC_BLKLEN	0x18
+#define JZ_REG_MMC_NOB		0x1C
+#define JZ_REG_MMC_SNOB		0x20
+#define JZ_REG_MMC_IMASK	0x24
+#define JZ_REG_MMC_IREG		0x28
+#define JZ_REG_MMC_CMD		0x2C
+#define JZ_REG_MMC_ARG		0x30
+#define JZ_REG_MMC_RESP_FIFO	0x34
+#define JZ_REG_MMC_RXFIFO	0x38
+#define JZ_REG_MMC_TXFIFO	0x3C
+
+#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7)
+#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6)
+#define JZ_MMC_STRPCL_START_READWAIT BIT(5)
+#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4)
+#define JZ_MMC_STRPCL_RESET BIT(3)
+#define JZ_MMC_STRPCL_START_OP BIT(2)
+#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0))
+#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0)
+#define JZ_MMC_STRPCL_CLOCK_START BIT(1)
+
+
+#define JZ_MMC_STATUS_IS_RESETTING BIT(15)
+#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14)
+#define JZ_MMC_STATUS_PRG_DONE BIT(13)
+#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12)
+#define JZ_MMC_STATUS_END_CMD_RES BIT(11)
+#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10)
+#define JZ_MMC_STATUS_IS_READWAIT BIT(9)
+#define JZ_MMC_STATUS_CLK_EN BIT(8)
+#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7)
+#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6)
+#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5)
+#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4)
+#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3)
+#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2)
+#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1)
+#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0)
+
+#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0))
+#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2))
+
+
+#define JZ_MMC_CMDAT_IO_ABORT BIT(11)
+#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10)
+#define JZ_MMC_CMDAT_DMA_EN BIT(8)
+#define JZ_MMC_CMDAT_INIT BIT(7)
+#define JZ_MMC_CMDAT_BUSY BIT(6)
+#define JZ_MMC_CMDAT_STREAM BIT(5)
+#define JZ_MMC_CMDAT_WRITE BIT(4)
+#define JZ_MMC_CMDAT_DATA_EN BIT(3)
+#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0))
+#define JZ_MMC_CMDAT_RSP_R1 1
+#define JZ_MMC_CMDAT_RSP_R2 2
+#define JZ_MMC_CMDAT_RSP_R3 3
+
+#define JZ_MMC_IRQ_SDIO BIT(7)
+#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6)
+#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5)
+#define JZ_MMC_IRQ_END_CMD_RES BIT(2)
+#define JZ_MMC_IRQ_PRG_DONE BIT(1)
+#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0)
+
+
+#define JZ_MMC_CLK_RATE 24000000
+
+#define JZ4740_MMC_MAX_TIMEOUT 10000000
+
+struct jz4740_mmc_host {
+	struct mmc_host *mmc;
+	struct platform_device *pdev;
+	struct jz4740_mmc_platform_data *pdata;
+	struct clk *clk;
+
+	int irq;
+	int card_detect_irq;
+
+	struct resource *mem;
+	void __iomem *base;
+	struct mmc_request *req;
+	struct mmc_command *cmd;
+
+	unsigned long waiting;
+
+	int max_clock;
+	uint32_t cmdat;
+
+	uint16_t irq_mask;
+
+	spinlock_t lock;
+
+	struct timer_list timeout_timer;
+};
+
+static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host,
+	unsigned int irq, bool enabled)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (enabled)
+		host->irq_mask &= ~irq;
+	else
+		host->irq_mask |= irq;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK);
+}
+
+static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host,
+	bool start_transfer)
+{
+	uint16_t val = JZ_MMC_STRPCL_CLOCK_START;
+
+	if (start_transfer)
+		val |= JZ_MMC_STRPCL_START_OP;
+
+	writew(val, host->base + JZ_REG_MMC_STRPCL);
+}
+
+static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+
+	writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_CLK_EN);
+}
+
+static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+
+	writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
+	udelay(10);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_IS_RESETTING);
+}
+
+static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
+{
+	struct mmc_request *req;
+
+	req = host->req;
+	host->req = NULL;
+
+	mmc_request_done(host->mmc, req);
+}
+
+static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host,
+	unsigned int irq)
+{
+	unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT;
+	uint16_t status;
+
+	do {
+		status = readw(host->base + JZ_REG_MMC_IREG);
+	} while (!(status & irq) && --timeout);
+
+	return timeout;
+}
+
+static void jz4740_mmc_write_data(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	uint32_t *buf;
+	int status;
+	unsigned int timeout;
+	size_t i, j;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_FROM_SG);
+	while (sg_miter_next(&miter)) {
+		buf = miter.addr;
+		i = miter.length / 4;
+		j = i >> 3;
+		i = i & 0x7;
+		while (j) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout == 0)) {
+				sg_miter_stop(&miter);
+				goto err_timeout;
+			}
+
+			writel(buf[0], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[1], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[2], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[3], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[4], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[5], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[6], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[7], host->base + JZ_REG_MMC_TXFIFO);
+			buf += 8;
+			--j;
+		}
+		if (unlikely(i)) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout == 0)) {
+				sg_miter_stop(&miter);
+				goto err_timeout;
+			}
+
+			while (i) {
+				writel(*buf, host->base + JZ_REG_MMC_TXFIFO);
+				++buf;
+				--i;
+			}
+		}
+		data->bytes_xfered += miter.length;
+	}
+	sg_miter_stop(&miter);
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) {
+		if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) {
+			host->req->cmd->error = -ETIMEDOUT;
+			data->error = -ETIMEDOUT;
+		} else {
+			host->req->cmd->error = -EIO;
+			data->error = -EIO;
+		}
+		return;
+	}
+
+	timeout = JZ4740_MMC_MAX_TIMEOUT;
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while ((status & JZ_MMC_STATUS_DATA_TRAN_DONE) == 0 && --timeout);
+
+	if (unlikely(timeout == 0))
+		goto err_timeout;
+	writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG);
+
+	return;
+
+err_timeout:
+	host->req->cmd->error = -ETIMEDOUT;
+	data->error = -ETIMEDOUT;
+}
+
+static void jz4740_mmc_read_data(struct jz4740_mmc_host *host,
+				struct mmc_data *data)
+{
+	struct sg_mapping_iter miter;
+	uint32_t *buf;
+	uint32_t d;
+	uint16_t status = 0;
+	size_t i, j;
+	unsigned int timeout;
+
+	sg_miter_start(&miter, data->sg, data->sg_len, SG_MITER_TO_SG);
+	while (sg_miter_next(&miter)) {
+		buf = miter.addr;
+		i = miter.length;
+		j = i >> 5;
+		i = i & 0x1f;
+		while (j) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout == 0)) {
+				sg_miter_stop(&miter);
+				goto err_timeout;
+			}
+
+			buf[0] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[1] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[2] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[3] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[4] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[5] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[6] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[7] = readl(host->base + JZ_REG_MMC_RXFIFO);
+
+			buf += 8;
+			--j;
+		}
+
+		while (i >= 4) {
+			timeout = jz4740_mmc_wait_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout == 0)) {
+				sg_miter_stop(&miter);
+				goto err_timeout;
+			}
+
+			*buf++ = readl(host->base + JZ_REG_MMC_RXFIFO);
+			i -= 4;
+		}
+		if (unlikely(i > 0)) {
+			d = readl(host->base + JZ_REG_MMC_RXFIFO);
+			memcpy(buf, &d, i);
+		}
+		data->bytes_xfered += miter.length;
+
+		/* This can go away once MIPS implements flush_kernel_dcache_page */
+		flush_dcache_page(miter.page);
+	}
+	sg_miter_stop(&miter);
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	if (status & JZ_MMC_STATUS_READ_ERROR_MASK) {
+		if (status & JZ_MMC_STATUS_TIMEOUT_READ) {
+			host->req->cmd->error = -ETIMEDOUT;
+			data->error = -ETIMEDOUT;
+		} else {
+			host->req->cmd->error = -EIO;
+			data->error = -EIO;
+		}
+		return;
+	}
+
+	/* For whatever reason there is sometime one word more in the fifo then
+	 * requested */
+	while ((status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) == 0 && --timeout) {
+		d = readl(host->base + JZ_REG_MMC_RXFIFO);
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	}
+	return;
+
+err_timeout:
+	host->req->cmd->error = -ETIMEDOUT;
+	data->error = -ETIMEDOUT;
+}
+
+static void jz4740_mmc_timeout(unsigned long data)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
+
+	if (!test_and_clear_bit(0, &host->waiting))
+		return;
+
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false);
+
+	host->req->cmd->error = -ETIMEDOUT;
+	jz4740_mmc_request_done(host);
+}
+
+static void jz4740_mmc_read_response(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	int i;
+	uint16_t tmp;
+
+	if (cmd->flags & MMC_RSP_136) {
+		tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+		for (i = 0; i < 4; ++i) {
+			cmd->resp[i] = tmp << 24;
+			cmd->resp[i] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8;
+			tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+			cmd->resp[i] |= tmp >> 8;
+		}
+	} else {
+		cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff;
+	}
+}
+
+static void jz4740_mmc_send_command(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	uint32_t cmdat = host->cmdat;
+
+	host->cmdat &= ~JZ_MMC_CMDAT_INIT;
+	jz4740_mmc_clock_disable(host);
+
+	host->cmd = cmd;
+
+	if (cmd->flags & MMC_RSP_BUSY)
+		cmdat |= JZ_MMC_CMDAT_BUSY;
+
+	switch (mmc_resp_type(cmd)) {
+	case MMC_RSP_R1B:
+	case MMC_RSP_R1:
+		cmdat |= JZ_MMC_CMDAT_RSP_R1;
+		break;
+	case MMC_RSP_R2:
+		cmdat |= JZ_MMC_CMDAT_RSP_R2;
+		break;
+	case MMC_RSP_R3:
+		cmdat |= JZ_MMC_CMDAT_RSP_R3;
+		break;
+	default:
+		break;
+	}
+
+	if (cmd->data) {
+		cmdat |= JZ_MMC_CMDAT_DATA_EN;
+		if (cmd->data->flags & MMC_DATA_WRITE)
+			cmdat |= JZ_MMC_CMDAT_WRITE;
+		if (cmd->data->flags & MMC_DATA_STREAM)
+			cmdat |= JZ_MMC_CMDAT_STREAM;
+
+		writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN);
+		writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB);
+	}
+
+	writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD);
+	writel(cmd->arg, host->base + JZ_REG_MMC_ARG);
+	writel(cmdat, host->base + JZ_REG_MMC_CMDAT);
+
+	jz4740_mmc_clock_enable(host, 1);
+}
+
+
+static irqreturn_t jz_mmc_irq_worker(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid;
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_request *req = host->req;
+	unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT;
+	uint32_t status;
+
+	if (cmd->error)
+		goto done;
+
+	if (cmd->flags & MMC_RSP_PRESENT)
+		jz4740_mmc_read_response(host, cmd);
+
+	if (cmd->data) {
+		if (cmd->data->flags & MMC_DATA_READ)
+			jz4740_mmc_read_data(host, cmd->data);
+		else
+			jz4740_mmc_write_data(host, cmd->data);
+	}
+
+	if (req->stop) {
+		jz4740_mmc_send_command(host, req->stop);
+		do {
+			status = readw(host->base + JZ_REG_MMC_IREG);
+		} while ((status & JZ_MMC_IRQ_PRG_DONE) == 0 && --timeout);
+		writew(JZ_MMC_IRQ_PRG_DONE, host->base + JZ_REG_MMC_IREG);
+	}
+
+	if (unlikely(timeout == 0))
+		req->stop->error = -ETIMEDOUT;
+
+done:
+	jz4740_mmc_request_done(host);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t jz_mmc_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+	uint16_t irq_reg, status, tmp;
+	irqreturn_t ret = IRQ_HANDLED;
+
+	irq_reg = readw(host->base + JZ_REG_MMC_IREG);
+
+	tmp = irq_reg;
+	irq_reg &= ~host->irq_mask;
+
+	tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ |
+		    JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE);
+
+	if (tmp != irq_reg)
+		writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG);
+
+	if (irq_reg & JZ_MMC_IRQ_SDIO) {
+		writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG);
+		mmc_signal_sdio_irq(host->mmc);
+	}
+
+	if (!host->req || !host->cmd)
+		goto handled;
+
+	if (!(irq_reg & JZ_MMC_IRQ_END_CMD_RES))
+		goto handled;
+
+	if (test_and_clear_bit(0, &host->waiting)) {
+	    del_timer(&host->timeout_timer);
+
+	    status = readl(host->base + JZ_REG_MMC_STATUS);
+
+	    if (status & JZ_MMC_STATUS_TIMEOUT_RES) {
+			host->cmd->error = -ETIMEDOUT;
+		} else if (status & JZ_MMC_STATUS_CRC_RES_ERR) {
+			host->cmd->error = -EIO;
+		} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+			    JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+			host->cmd->data->error = -EIO;
+		} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+				JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+			host->cmd->data->error = -EIO;
+		}
+
+		ret = IRQ_WAKE_THREAD;
+	}
+
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false);
+	writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+
+handled:
+	return ret;
+}
+
+static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate)
+{
+	int div = 0;
+	int real_rate;
+
+	jz4740_mmc_clock_disable(host);
+	clk_set_rate(host->clk, JZ_MMC_CLK_RATE);
+
+	real_rate = clk_get_rate(host->clk);
+
+	while (real_rate > rate && div < 7) {
+		++div;
+		real_rate >>= 1;
+	}
+
+	writew(div, host->base + JZ_REG_MMC_CLKRT);
+	return real_rate;
+}
+
+static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+
+	host->req = req;
+
+	writew(0xffff, host->base + JZ_REG_MMC_IREG);
+
+	writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true);
+
+	set_bit(0, &host->waiting);
+	mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+	jz4740_mmc_send_command(host, req->cmd);
+}
+
+static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (ios->clock)
+		jz4740_mmc_set_clock_rate(host, ios->clock);
+
+	switch (ios->power_mode) {
+	case MMC_POWER_UP:
+		jz4740_mmc_reset(host);
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					!host->pdata->power_active_low);
+		host->cmdat |= JZ_MMC_CMDAT_INIT;
+		clk_enable(host->clk);
+		break;
+	case MMC_POWER_ON:
+		break;
+	default:
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					host->pdata->power_active_low);
+		clk_disable(host->clk);
+		break;
+	}
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_1:
+		host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	case MMC_BUS_WIDTH_4:
+		host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	default:
+		break;
+	}
+}
+
+static int jz4740_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_read_only))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_read_only) ^
+		host->pdata->read_only_active_low;
+}
+
+static int jz4740_mmc_get_cd(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_card_detect))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_card_detect) ^
+			host->pdata->card_detect_active_low;
+}
+
+static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+
+	mmc_detect_change(host->mmc, HZ / 3);
+
+	return IRQ_HANDLED;
+}
+
+static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable);
+}
+
+static const struct mmc_host_ops jz4740_mmc_ops = {
+	.request	= jz4740_mmc_request,
+	.set_ios	= jz4740_mmc_set_ios,
+	.get_ro		= jz4740_mmc_get_ro,
+	.get_cd		= jz4740_mmc_get_cd,
+	.enable_sdio_irq = jz4740_mmc_enable_sdio_irq,
+};
+
+static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = {
+	JZ_GPIO_BULK_PIN(MSC_CMD),
+	JZ_GPIO_BULK_PIN(MSC_CLK),
+	JZ_GPIO_BULK_PIN(MSC_DATA0),
+	JZ_GPIO_BULK_PIN(MSC_DATA1),
+	JZ_GPIO_BULK_PIN(MSC_DATA2),
+	JZ_GPIO_BULK_PIN(MSC_DATA3),
+};
+
+static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return 0;
+
+	if (gpio_is_valid(pdata->gpio_card_detect)) {
+		ret = gpio_request(pdata->gpio_card_detect, "MMC detect change");
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request detect change gpio\n");
+			goto err;
+		}
+		gpio_direction_input(pdata->gpio_card_detect);
+	}
+
+	if (gpio_is_valid(pdata->gpio_read_only)) {
+		ret = gpio_request(pdata->gpio_read_only, "MMC read only");
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request read only gpio: %d\n", ret);
+			goto err_free_gpio_card_detect;
+		}
+		gpio_direction_input(pdata->gpio_read_only);
+	}
+
+	if (gpio_is_valid(pdata->gpio_power)) {
+		ret = gpio_request(pdata->gpio_power, "MMC power");
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request power gpio: %d\n", ret);
+			goto err_free_gpio_read_only;
+		}
+		gpio_direction_output(pdata->gpio_power, pdata->power_active_low);
+	}
+
+	return 0;
+
+err_free_gpio_read_only:
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+err_free_gpio_card_detect:
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+err:
+	return ret;
+}
+
+static void jz4740_mmc_free_gpios(struct platform_device *pdev)
+{
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return;
+
+	if (gpio_is_valid(pdata->gpio_power))
+		gpio_free(pdata->gpio_power);
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+}
+
+static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host)
+{
+	size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins);
+	if (host->pdata && host->pdata->data_1bit)
+		num_pins -= 3;
+
+	return num_pins;
+}
+
+static int __devinit jz4740_mmc_probe(struct platform_device* pdev)
+{
+	int ret;
+	struct mmc_host *mmc;
+	struct jz4740_mmc_host *host;
+	struct jz4740_mmc_platform_data *pdata;
+
+	pdata = pdev->dev.platform_data;
+
+	mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev);
+	if (!mmc) {
+		dev_err(&pdev->dev, "Failed to alloc mmc host structure\n");
+		return -ENOMEM;
+	}
+
+	host = mmc_priv(mmc);
+	host->pdata = pdata;
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq < 0) {
+		ret = host->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+		goto err_free_host;
+	}
+
+	host->clk = clk_get(&pdev->dev, "mmc");
+	if (!host->clk) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get mmc clock\n");
+		goto err_free_host;
+	}
+
+	host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!host->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get base platform memory\n");
+		goto err_clk_put;
+	}
+
+	host->mem = request_mem_region(host->mem->start,
+					resource_size(host->mem), pdev->name);
+	if (!host->mem) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to request base memory region\n");
+		goto err_clk_put;
+	}
+
+	host->base = ioremap_nocache(host->mem->start, resource_size(host->mem));
+	if (!host->base) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to ioremap base memory\n");
+		goto err_release_mem_region;
+	}
+
+	ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret);
+		goto err_iounmap;
+	}
+
+	ret = jz4740_mmc_request_gpios(pdev);
+	if (ret)
+		goto err_gpio_bulk_free;
+
+	mmc->ops = &jz4740_mmc_ops;
+	mmc->f_min = JZ_MMC_CLK_RATE / 128;
+	mmc->f_max = JZ_MMC_CLK_RATE;
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA;
+	mmc->caps |= MMC_CAP_SDIO_IRQ;
+
+	mmc->max_blk_size = (1 << 10) - 1;
+	mmc->max_blk_count = (1 << 15) - 1;
+	mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+	mmc->max_phys_segs = 128;
+	mmc->max_hw_segs = 128;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host->mmc = mmc;
+	host->pdev = pdev;
+	host->max_clock = JZ_MMC_CLK_RATE;
+	spin_lock_init(&host->lock);
+	host->irq_mask = 0xffff;
+
+	host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect);
+
+	if (host->card_detect_irq < 0) {
+		dev_warn(&pdev->dev, "Failed to get irq for card detect gpio\n");
+	} else {
+		ret = request_irq(host->card_detect_irq,
+			jz4740_mmc_card_detect_irq,
+			IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"MMC card detect", host);
+
+		if (ret) {
+			dev_err(&pdev->dev, "Failed to request card detect irq");
+			goto err_free_gpios;
+		}
+	}
+
+	ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0,
+			dev_name(&pdev->dev), host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+		goto err_free_card_detect_irq;
+	}
+
+	jz4740_mmc_reset(host);
+	jz4740_mmc_clock_disable(host);
+	setup_timer(&host->timeout_timer, jz4740_mmc_timeout,
+			(unsigned long)host);
+	/* It is not that important when it times out, it just needs to timeout. */
+	set_timer_slack(&host->timeout_timer, HZ);
+
+	platform_set_drvdata(pdev, host);
+	ret = mmc_add_host(mmc);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret);
+		goto err_free_irq;
+	}
+	dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n");
+
+	return 0;
+
+err_free_irq:
+	free_irq(host->irq, host);
+err_free_card_detect_irq:
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+err_free_gpios:
+	jz4740_mmc_free_gpios(pdev);
+err_gpio_bulk_free:
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+err_iounmap:
+	iounmap(host->base);
+err_release_mem_region:
+	release_mem_region(host->mem->start, resource_size(host->mem));
+err_clk_put:
+	clk_put(host->clk);
+err_free_host:
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(mmc);
+
+	return ret;
+}
+
+static int __devexit jz4740_mmc_remove(struct platform_device *pdev)
+{
+	struct jz4740_mmc_host *host = platform_get_drvdata(pdev);
+
+	del_timer_sync(&host->timeout_timer);
+	jz4740_mmc_set_irq_enabled(host, 0xff, false);
+	jz4740_mmc_reset(host);
+
+	mmc_remove_host(host->mmc);
+
+	free_irq(host->irq, host);
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+
+	jz4740_mmc_free_gpios(pdev);
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	iounmap(host->base);
+	release_mem_region(host->mem->start, resource_size(host->mem));
+
+	clk_put(host->clk);
+
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(host->mmc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int jz4740_mmc_suspend(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	mmc_suspend_host(host->mmc);
+
+	jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	return 0;
+}
+
+static int jz4740_mmc_resume(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	mmc_resume_host(host->mmc);
+
+	return 0;
+}
+
+const struct dev_pm_ops jz4740_mmc_pm_ops = {
+	.suspend	= jz4740_mmc_suspend,
+	.resume		= jz4740_mmc_resume,
+	.poweroff	= jz4740_mmc_suspend,
+	.restore	= jz4740_mmc_resume,
+};
+
+#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops)
+#else
+#define JZ4740_MMC_PM_OPS NULL
+#endif
+
+static struct platform_driver jz4740_mmc_driver = {
+	.probe = jz4740_mmc_probe,
+	.remove = __devexit_p(jz4740_mmc_remove),
+	.driver = {
+		.name = "jz4740-mmc",
+		.owner = THIS_MODULE,
+		.pm = JZ4740_MMC_PM_OPS,
+	},
+};
+
+static int __init jz4740_mmc_init(void)
+{
+	return platform_driver_register(&jz4740_mmc_driver);
+}
+module_init(jz4740_mmc_init);
+
+static void __exit jz4740_mmc_exit(void)
+{
+	platform_driver_unregister(&jz4740_mmc_driver);
+}
+module_exit(jz4740_mmc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
diff --git a/include/linux/mmc/jz4740_mmc.h b/include/linux/mmc/jz4740_mmc.h
new file mode 100644
index 0000000..8543f43
--- /dev/null
+++ b/include/linux/mmc/jz4740_mmc.h
@@ -0,0 +1,15 @@
+#ifndef __LINUX_MMC_JZ4740_MMC
+#define __LINUX_MMC_JZ4740_MMC
+
+struct jz4740_mmc_platform_data {
+	int gpio_power;
+	int gpio_card_detect;
+	int gpio_read_only;
+	unsigned card_detect_active_low:1;
+	unsigned read_only_active_low:1;
+	unsigned power_active_low:1;
+
+	unsigned data_1bit:1;
+};
+
+#endif
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3 10/26] MIPS: JZ4740: Add PWM support
  2010-06-19  5:08 ` [PATCH v2 10/26] MIPS: JZ4740: Add PWM support Lars-Peter Clausen
@ 2010-06-28  1:23   ` Lars-Peter Clausen
  2010-07-17 12:12     ` [PATCH v4] " Lars-Peter Clausen
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-28  1:23 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3 03/26] MIPS: JZ4740: Add clock API support.
  2010-06-19  5:08 ` [PATCH v2 03/26] MIPS: JZ4740: Add clock API support Lars-Peter Clausen
@ 2010-06-28  1:24   ` Lars-Peter Clausen
  2010-07-17 12:10     ` [PATCH v4] " Lars-Peter Clausen
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-06-28  1:24 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---

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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 24/26] power: Add JZ4740 battery driver.
  2010-06-27  1:58   ` Lars-Peter Clausen
@ 2010-06-28 11:43     ` Anton Vorontsov
  0 siblings, 0 replies; 163+ messages in thread
From: Anton Vorontsov @ 2010-06-28 11:43 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: linux-mips, linux-kernel, Ralf Baechle

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 <cbouatmailru@gmail.com>

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 <lars@metafoo.de>
> > Cc: Anton Vorontsov <cbouatmailru@gmail.com>
> > 
> > ---
> > 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: cbouatmailru@gmail.com
irc://irc.freenode.net/bd2

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MMC: Add JZ4740 mmc driver
@ 2010-06-29 20:17       ` Matt Fleming
  0 siblings, 0 replies; 163+ messages in thread
From: Matt Fleming @ 2010-06-29 20:17 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	linux-mmc, Ralf Baechle

On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Matt Fleming <matt@console-pimps.org>
> Cc: linux-mmc@vger.kernel.org
> 
> ---
> Changes since v1
> - Do not request IRQ with IRQF_DISABLED since it is a noop now
> - Use a generous slack for the timeout timer. It does not need to be accurate.
> Changes since v2
> - Use sg_mapping_to iterate over sg elements in mmc read and write functions
> - Use bitops instead of a spinlock and a variable for testing whether a request
>   has been finished.
> - Rework irq and timeout handling in order to get rid of locking in hot paths

Acked-by: Matt Fleming <matt@console-pimps.org>

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MMC: Add JZ4740 mmc driver
@ 2010-06-29 20:17       ` Matt Fleming
  0 siblings, 0 replies; 163+ messages in thread
From: Matt Fleming @ 2010-06-29 20:17 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-kernel, Andrew Morton, linux-mmc, Ralf Baechle

On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Matt Fleming <matt@console-pimps.org>
> Cc: linux-mmc@vger.kernel.org
> 
> ---
> Changes since v1
> - Do not request IRQ with IRQF_DISABLED since it is a noop now
> - Use a generous slack for the timeout timer. It does not need to be accurate.
> Changes since v2
> - Use sg_mapping_to iterate over sg elements in mmc read and write functions
> - Use bitops instead of a spinlock and a variable for testing whether a request
>   has been finished.
> - Rework irq and timeout handling in order to get rid of locking in hot paths

Acked-by: Matt Fleming <matt@console-pimps.org>

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MMC: Add JZ4740 mmc driver
@ 2010-06-29 20:17       ` Matt Fleming
  0 siblings, 0 replies; 163+ messages in thread
From: Matt Fleming @ 2010-06-29 20:17 UTC (permalink / raw)
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	linux-mmc, Ralf Baechle

On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Matt Fleming <matt@console-pimps.org>
> Cc: linux-mmc@vger.kernel.org
> 
> ---
> Changes since v1
> - Do not request IRQ with IRQF_DISABLED since it is a noop now
> - Use a generous slack for the timeout timer. It does not need to be accurate.
> Changes since v2
> - Use sg_mapping_to iterate over sg elements in mmc read and write functions
> - Use bitops instead of a spinlock and a variable for testing whether a request
>   has been finished.
> - Rework irq and timeout handling in order to get rid of locking in hot paths

Acked-by: Matt Fleming <matt@console-pimps.org>

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MMC: Add JZ4740 mmc driver
  2010-06-28  1:20   ` [PATCH v3] " Lars-Peter Clausen
  2010-06-29 20:17       ` Matt Fleming
@ 2010-06-30 20:55     ` Andrew Morton
  2010-07-01 15:45       ` Lars-Peter Clausen
  2010-07-12 21:33     ` [PATCH v4] " Lars-Peter Clausen
  2 siblings, 1 reply; 163+ messages in thread
From: Andrew Morton @ 2010-06-30 20:55 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc

On Mon, 28 Jun 2010 03:20:41 +0200
Lars-Peter Clausen <lars@metafoo.de> wrote:

> This patch adds support for the mmc controller on JZ4740 SoCs.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Matt Fleming <matt@console-pimps.org>
> Cc: linux-mmc@vger.kernel.org
>
> ...
>
> +#define JZ4740_MMC_MAX_TIMEOUT 10000000

That was a really big timeout.  How long do 1e7 readw's take?  Oh well.

>
> ...
>
> +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
> +{
> +	uint32_t status;
> +
> +	writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
> +	do {
> +		status = readl(host->base + JZ_REG_MMC_STATUS);
> +	} while (status & JZ_MMC_STATUS_CLK_EN);
> +}
> +
> +static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
> +{
> +	uint32_t status;
> +
> +	writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
> +	udelay(10);
> +	do {
> +		status = readl(host->base + JZ_REG_MMC_STATUS);
> +	} while (status & JZ_MMC_STATUS_IS_RESETTING);
> +}

Maybe these should have a timeout too?

>
> ...
>
> +static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host,
> +	unsigned int irq)
> +{
> +	unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT;
> +	uint16_t status;
> +
> +	do {
> +		status = readw(host->base + JZ_REG_MMC_IREG);
> +	} while (!(status & irq) && --timeout);
> +
> +	return timeout;
> +}

This guy's too big to inline.  Recent gcc's know that and they tend to
uninline such things behind your back anwyay.

>
> ...
>
> +struct jz4740_mmc_platform_data {
> +	int gpio_power;
> +	int gpio_card_detect;
> +	int gpio_read_only;
> +	unsigned card_detect_active_low:1;
> +	unsigned read_only_active_low:1;
> +	unsigned power_active_low:1;
> +
> +	unsigned data_1bit:1;
> +};

The bitfields will all share the same word, so modification of one
field can race against modification of another field.  Hence some form
of locking which covers *all* the bitfields is needed.

Is that a problem in this driver?


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MMC: Add JZ4740 mmc driver
  2010-06-30 20:55     ` Andrew Morton
@ 2010-07-01 15:45       ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-01 15:45 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc

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

Hi

Andrew Morton wrote:
> On Mon, 28 Jun 2010 03:20:41 +0200
> Lars-Peter Clausen <lars@metafoo.de> wrote:
> 
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>> Cc: Andrew Morton <akpm@linux-foundation.org>
>> Cc: Matt Fleming <matt@console-pimps.org>
>> Cc: linux-mmc@vger.kernel.org
>>
>> ...
>>
>> +#define JZ4740_MMC_MAX_TIMEOUT 10000000
> 
> That was a really big timeout.  How long do 1e7 readw's take?  Oh well.
> 

Hm, right. It doesn't hurt though. I'll do some tests and to try to come up with a
more realistic value.

>> ...
>>
>> +static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
>> +{
>> +	uint32_t status;
>> +
>> +	writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
>> +	do {
>> +		status = readl(host->base + JZ_REG_MMC_STATUS);
>> +	} while (status & JZ_MMC_STATUS_CLK_EN);
>> +}
>> +
>> +static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
>> +{
>> +	uint32_t status;
>> +
>> +	writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
>> +	udelay(10);
>> +	do {
>> +		status = readl(host->base + JZ_REG_MMC_STATUS);
>> +	} while (status & JZ_MMC_STATUS_IS_RESETTING);
>> +}
> 
> Maybe these should have a timeout too?

Its very unlikely that these will ever timeout. But right, to be on the safe, there
should be a timeout as well.

> 
>> ...
>>
>> +static inline unsigned int jz4740_mmc_wait_irq(struct jz4740_mmc_host *host,
>> +	unsigned int irq)
>> +{
>> +	unsigned int timeout = JZ4740_MMC_MAX_TIMEOUT;
>> +	uint16_t status;
>> +
>> +	do {
>> +		status = readw(host->base + JZ_REG_MMC_IREG);
>> +	} while (!(status & irq) && --timeout);
>> +
>> +	return timeout;
>> +}
> 
> This guy's too big to inline.  Recent gcc's know that and they tend to
> uninline such things behind your back anwyay.

Actually, even without the inline attribute and compiling with -Os gcc will inline
this function by itself.

> 
>> ...
>>
>> +struct jz4740_mmc_platform_data {
>> +	int gpio_power;
>> +	int gpio_card_detect;
>> +	int gpio_read_only;
>> +	unsigned card_detect_active_low:1;
>> +	unsigned read_only_active_low:1;
>> +	unsigned power_active_low:1;
>> +
>> +	unsigned data_1bit:1;
>> +};
> 
> The bitfields will all share the same word, so modification of one
> field can race against modification of another field.  Hence some form
> of locking which covers *all* the bitfields is needed.
> 
> Is that a problem in this driver?
> 
They are all read-only in the driver.


Thanks for reviewing the patch
- - Lars

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

iEYEARECAAYFAkwsuDQACgkQBX4mSR26RiOiMACeMMNj4koCYFAUnxyM0LBr+wOZ
x6oAnizk5CaAvZjQ2doXrD6ZYgDeNjSr
=92D2
-----END PGP SIGNATURE-----

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MMC: Add JZ4740 mmc driver
  2010-06-29 20:17       ` Matt Fleming
  (?)
  (?)
@ 2010-07-01 15:47       ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-01 15:47 UTC (permalink / raw)
  To: Matt Fleming
  Cc: linux-mips, linux-kernel, Andrew Morton, linux-mmc, Ralf Baechle

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

Matt Fleming wrote:
> On Mon, 28 Jun 2010 03:20:41 +0200, Lars-Peter Clausen <lars@metafoo.de> wrote:
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>> Cc: Andrew Morton <akpm@linux-foundation.org>
>> Cc: Matt Fleming <matt@console-pimps.org>
>> Cc: linux-mmc@vger.kernel.org
>>
>> ---
>> Changes since v1
>> - Do not request IRQ with IRQF_DISABLED since it is a noop now
>> - Use a generous slack for the timeout timer. It does not need to be accurate.
>> Changes since v2
>> - Use sg_mapping_to iterate over sg elements in mmc read and write functions
>> - Use bitops instead of a spinlock and a variable for testing whether a request
>>   has been finished.
>> - Rework irq and timeout handling in order to get rid of locking in hot paths
> 
> Acked-by: Matt Fleming <matt@console-pimps.org>
> 
> Are you planning on maintaining this driver? If so, it'd be a good idea
> to update MAINTAINERS.

Hi

Thanks for reviewing the patch.
I guess I should send a MAINTAINERS patch which adds entries for all of the JZ4740
drivers.

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

iEYEARECAAYFAkwsuJUACgkQBX4mSR26RiNHVgCfTq+tc2I1QBniqijyUjNDxPIX
GsEAn1xgPWz+L0uqHWthzJ+lMtFaUBtY
=nVt9
-----END PGP SIGNATURE-----

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
  2010-06-19  5:08   ` Lars-Peter Clausen
@ 2010-07-04 22:27     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-04 22:27 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

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 <lars@metafoo.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: linux-fbdev@vger.kernel.org
> 
> ---
> 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 <lars@metafoo.de>
> + *	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 <lars@metafoo.de>");
> +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 <lars@metafoo.de>
> + *
> + *  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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
@ 2010-07-04 22:27     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-04 22:27 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

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 <lars@metafoo.de>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: linux-fbdev@vger.kernel.org
> 
> ---
> 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 <lars@metafoo.de>
> + *	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 <lars@metafoo.de>");
> +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 <lars@metafoo.de>
> + *
> + *  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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND
  2010-06-19  5:08   ` Lars-Peter Clausen
  (?)
@ 2010-07-04 22:35   ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-04 22:35 UTC (permalink / raw)
  To: driver linux-mtd; +Cc: linux-mips, linux-kernel, David Woodhouse, Ralf Baechle

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 <lars@metafoo.de>
> Cc: David Woodhouse <dwmw2@infradead.org>
> Cc: linux-mtd@lists.infradead.org
> 
> ---
> 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 <lars@metafoo.de>
> + *  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 <lars@metafoo.de>");
> +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 <lars@metafoo.de>
> + *  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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver
  2010-06-19  5:08 ` [PATCH v2 22/26] MFD: Add JZ4740 ADC driver Lars-Peter Clausen
@ 2010-07-04 22:47   ` Lars-Peter Clausen
  2010-07-05 14:53   ` Samuel Ortiz
  2010-07-12  1:48   ` [PATCH v3] " Lars-Peter Clausen
  2 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-04 22:47 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: linux-mips, linux-kernel, Ralf Baechle

-----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 <lars@metafoo.de>
> Cc: Samuel Ortiz <sameo@linux.intel.com>
> ---
>  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 <lars@metafoo.de>
> + * 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 <lars@metafoo.de>");
> +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-----

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver
  2010-06-19  5:08 ` [PATCH v2 22/26] MFD: Add JZ4740 ADC driver Lars-Peter Clausen
  2010-07-04 22:47   ` Lars-Peter Clausen
@ 2010-07-05 14:53   ` Samuel Ortiz
  2010-07-05 15:43     ` Lars-Peter Clausen
  2010-07-12  1:48   ` [PATCH v3] " Lars-Peter Clausen
  2 siblings, 1 reply; 163+ messages in thread
From: Samuel Ortiz @ 2010-07-05 14:53 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel

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/

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver
  2010-07-05 14:53   ` Samuel Ortiz
@ 2010-07-05 15:43     ` Lars-Peter Clausen
  2010-07-05 15:53       ` Samuel Ortiz
  0 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-05 15:43 UTC (permalink / raw)
  To: Samuel Ortiz; +Cc: Ralf Baechle, linux-mips, linux-kernel

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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 22/26] MFD: Add JZ4740 ADC driver
  2010-07-05 15:43     ` Lars-Peter Clausen
@ 2010-07-05 15:53       ` Samuel Ortiz
  0 siblings, 0 replies; 163+ messages in thread
From: Samuel Ortiz @ 2010-07-05 15:53 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel

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/

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
  2010-07-04 22:27     ` Lars-Peter Clausen
@ 2010-07-07 23:41       ` Andrew Morton
  -1 siblings, 0 replies; 163+ messages in thread
From: Andrew Morton @ 2010-07-07 23:41 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

On Mon, 05 Jul 2010 00:27:25 +0200
Lars-Peter Clausen <lars@metafoo.de> 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".


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
@ 2010-07-07 23:41       ` Andrew Morton
  0 siblings, 0 replies; 163+ messages in thread
From: Andrew Morton @ 2010-07-07 23:41 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

On Mon, 05 Jul 2010 00:27:25 +0200
Lars-Peter Clausen <lars@metafoo.de> 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".


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
  2010-06-19  5:08   ` Lars-Peter Clausen
@ 2010-07-08  6:06     ` Artem Bityutskiy
  -1 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-08  6:06 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, David Woodhouse, linux-kernel, linux-mtd

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 <lars@metafoo.de>
> + *  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 (Артём Битюцкий)


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-08  6:06     ` Artem Bityutskiy
  0 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-08  6:06 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-mtd, David Woodhouse, linux-kernel, Ralf Baechle

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 <lars@metafoo.de>
> + *  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 (Артём Битюцкий)

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
  2010-07-08 13:20       ` Lars-Peter Clausen
@ 2010-07-08 13:19         ` Artem Bityutskiy
  -1 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-08 13:19 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, David Woodhouse, linux-kernel, linux-mtd

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 (Артём Битюцкий)


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-08 13:19         ` Artem Bityutskiy
  0 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-08 13:19 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-mtd, David Woodhouse, linux-kernel, Ralf Baechle

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 (Артём Битюцкий)

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
  2010-07-08  6:06     ` Artem Bityutskiy
@ 2010-07-08 13:20       ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-08 13:20 UTC (permalink / raw)
  To: dedekind1
  Cc: Ralf Baechle, linux-mips, David Woodhouse, linux-kernel, linux-mtd

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 <lars@metafoo.de>
>> + *  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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-08 13:20       ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-08 13:20 UTC (permalink / raw)
  To: dedekind1
  Cc: linux-mips, linux-mtd, David Woodhouse, linux-kernel, Ralf Baechle

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 <lars@metafoo.de>
>> + *  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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
  2010-07-07 23:41       ` Andrew Morton
@ 2010-07-08 13:28         ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-08 13:28 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

Andrew Morton wrote:
> On Mon, 05 Jul 2010 00:27:25 +0200
> Lars-Peter Clausen <lars@metafoo.de> 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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
@ 2010-07-08 13:28         ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-08 13:28 UTC (permalink / raw)
  To: Andrew Morton; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

Andrew Morton wrote:
> On Mon, 05 Jul 2010 00:27:25 +0200
> Lars-Peter Clausen <lars@metafoo.de> 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


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
  2010-07-08 13:19         ` Artem Bityutskiy
@ 2010-07-08 14:02           ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-08 14:02 UTC (permalink / raw)
  To: dedekind1
  Cc: Ralf Baechle, linux-mips, David Woodhouse, linux-kernel, linux-mtd

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-08 14:02           ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-08 14:02 UTC (permalink / raw)
  To: dedekind1
  Cc: linux-mips, linux-mtd, David Woodhouse, linux-kernel, Ralf Baechle

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
  2010-07-08 14:02           ` Lars-Peter Clausen
@ 2010-07-08 14:14             ` Artem Bityutskiy
  -1 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-08 14:14 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, David Woodhouse, linux-kernel, linux-mtd

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 (Артём Битюцкий)


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-08 14:14             ` Artem Bityutskiy
  0 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-08 14:14 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-mtd, David Woodhouse, linux-kernel, Ralf Baechle

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 (Артём Битюцкий)

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
  2010-07-08 13:28         ` Lars-Peter Clausen
@ 2010-07-08 16:46           ` Andrew Morton
  -1 siblings, 0 replies; 163+ messages in thread
From: Andrew Morton @ 2010-07-08 16:46 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

On Thu, 08 Jul 2010 15:28:04 +0200 Lars-Peter Clausen <lars@metafoo.de> 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.

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
@ 2010-07-08 16:46           ` Andrew Morton
  0 siblings, 0 replies; 163+ messages in thread
From: Andrew Morton @ 2010-07-08 16:46 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

On Thu, 08 Jul 2010 15:28:04 +0200 Lars-Peter Clausen <lars@metafoo.de> 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.

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
  2010-07-04 22:27     ` Lars-Peter Clausen
@ 2010-07-09  1:26       ` Jaya Kumar
  -1 siblings, 0 replies; 163+ messages in thread
From: Jaya Kumar @ 2010-07-09  1:26 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Andrew Morton, linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

On Mon, Jul 5, 2010 at 6:27 AM, Lars-Peter Clausen <lars@metafoo.de> 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 <lars@metafoo.de>
>> + *   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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
@ 2010-07-09  1:26       ` Jaya Kumar
  0 siblings, 0 replies; 163+ messages in thread
From: Jaya Kumar @ 2010-07-09  1:26 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Andrew Morton, linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

On Mon, Jul 5, 2010 at 6:27 AM, Lars-Peter Clausen <lars@metafoo.de> 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 <lars@metafoo.de>
>> + *   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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
  2010-07-09  1:26       ` Jaya Kumar
@ 2010-07-09 15:31         ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-09 15:31 UTC (permalink / raw)
  To: Jaya Kumar
  Cc: Andrew Morton, linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

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

Jaya Kumar wrote:
> On Mon, Jul 5, 2010 at 6:27 AM, Lars-Peter Clausen <lars@metafoo.de> 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 <lars@metafoo.de>
>>> + *   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-----

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver
@ 2010-07-09 15:31         ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-09 15:31 UTC (permalink / raw)
  To: Jaya Kumar
  Cc: Andrew Morton, linux-mips, linux-kernel, Ralf Baechle, linux-fbdev

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

Jaya Kumar wrote:
> On Mon, Jul 5, 2010 at 6:27 AM, Lars-Peter Clausen <lars@metafoo.de> 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 <lars@metafoo.de>
>>> + *   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-----

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v3] MFD: Add JZ4740 ADC driver
  2010-06-19  5:08 ` [PATCH v2 22/26] MFD: Add JZ4740 ADC driver Lars-Peter Clausen
  2010-07-04 22:47   ` Lars-Peter Clausen
  2010-07-05 14:53   ` Samuel Ortiz
@ 2010-07-12  1:48   ` Lars-Peter Clausen
  2010-07-14  9:19     ` Samuel Ortiz
  2 siblings, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-12  1:48 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Samuel Ortiz

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 <lars@metafoo.de>
Cc: Samuel Ortiz <sameo@linux.intel.com>

---
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 <lars@metafoo.de>
+ * 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 <lars@metafoo.de>");
+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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v4] MMC: Add JZ4740 mmc driver
  2010-06-28  1:20   ` [PATCH v3] " Lars-Peter Clausen
  2010-06-29 20:17       ` Matt Fleming
  2010-06-30 20:55     ` Andrew Morton
@ 2010-07-12 21:33     ` Lars-Peter Clausen
  2010-07-12 21:41       ` Randy Dunlap
  2010-07-12 22:20       ` [PATCH v5] " Lars-Peter Clausen
  2 siblings, 2 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-12 21:33 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	Matt Fleming, linux-mmc

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Acked-by: Matt Fleming <matt@console-pimps.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Matt Fleming <matt@console-pimps.org>
Cc: linux-mmc@vger.kernel.org

---
Changes since v1
- Do not request IRQ with IRQF_DISABLED since it is a noop now
- Use a generous slack for the timeout timer. It does not need to be accurate.

Changes since v2
- Use sg_mapping_to iterate over sg elements in mmc read and write functions
- Use bitops instead of a spinlock and a variable for testing whether a request
  has been finished.
- Rework irq and timeout handling in order to get rid of locking in hot paths

Changes since v3
- Drastically decrease IRQ poll timeout. Now when the poll timeout is reached
  the IRQ which was polled is enabled. A variable keeps track of what state the
  driver is in and what has to be done next when the IRQ handler is entered
  again.
  By doing so busy looping is reduced, but overall performance can be maintained.
- Move header file from include/linux/mmc to arch/mips/include/asm/mach-jz4740
---
 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h |   15 +
 drivers/mmc/host/Kconfig                       |    8 +
 drivers/mmc/host/Makefile                      |    1 +
 drivers/mmc/host/jz4740_mmc.c                  | 1024 ++++++++++++++++++++++++
 4 files changed, 1048 insertions(+), 0 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
 create mode 100644 drivers/mmc/host/jz4740_mmc.c

diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
new file mode 100644
index 0000000..8543f43
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
@@ -0,0 +1,15 @@
+#ifndef __LINUX_MMC_JZ4740_MMC
+#define __LINUX_MMC_JZ4740_MMC
+
+struct jz4740_mmc_platform_data {
+	int gpio_power;
+	int gpio_card_detect;
+	int gpio_read_only;
+	unsigned card_detect_active_low:1;
+	unsigned read_only_active_low:1;
+	unsigned power_active_low:1;
+
+	unsigned data_1bit:1;
+};
+
+#endif
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f06d06e..546fc49 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -81,6 +81,14 @@ config MMC_RICOH_MMC
 
 	  If unsure, say Y.
 
+config MMC_JZ4740
+	tristate "JZ4740 SD/Multimedia Card Interface support"
+	depends on MACH_JZ4740
+	help
+	  This selects the Ingenic Z4740 SD/Multimedia card Interface.
+	  If you have an ngenic platform with a Multimedia Card slot,
+	  say Y or M here.
+
 config MMC_SDHCI_OF
 	tristate "SDHCI support on OpenFirmware platforms"
 	depends on MMC_SDHCI && PPC_OF
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e30c2ee..f4e53c9 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc.o
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
+obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
 
 obj-$(CONFIG_MMC_SDHCI_OF)	+= sdhci-of.o
 sdhci-of-y				:= sdhci-of-core.o
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c
new file mode 100644
index 0000000..82a449a
--- /dev/null
+++ b/drivers/mmc/host/jz4740_mmc.c
@@ -0,0 +1,1024 @@
+/*
+ *  Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *  JZ4740 SD/MMC controller driver
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <asm/mach-jz4740/gpio.h>
+#include <asm/cacheflush.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/mach-jz4740/jz4740_mmc.h>
+
+#define JZ_REG_MMC_STRPCL	0x00
+#define JZ_REG_MMC_STATUS	0x04
+#define JZ_REG_MMC_CLKRT	0x08
+#define JZ_REG_MMC_CMDAT	0x0C
+#define JZ_REG_MMC_RESTO	0x10
+#define JZ_REG_MMC_RDTO		0x14
+#define JZ_REG_MMC_BLKLEN	0x18
+#define JZ_REG_MMC_NOB		0x1C
+#define JZ_REG_MMC_SNOB		0x20
+#define JZ_REG_MMC_IMASK	0x24
+#define JZ_REG_MMC_IREG		0x28
+#define JZ_REG_MMC_CMD		0x2C
+#define JZ_REG_MMC_ARG		0x30
+#define JZ_REG_MMC_RESP_FIFO	0x34
+#define JZ_REG_MMC_RXFIFO	0x38
+#define JZ_REG_MMC_TXFIFO	0x3C
+
+#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7)
+#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6)
+#define JZ_MMC_STRPCL_START_READWAIT BIT(5)
+#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4)
+#define JZ_MMC_STRPCL_RESET BIT(3)
+#define JZ_MMC_STRPCL_START_OP BIT(2)
+#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0))
+#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0)
+#define JZ_MMC_STRPCL_CLOCK_START BIT(1)
+
+
+#define JZ_MMC_STATUS_IS_RESETTING BIT(15)
+#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14)
+#define JZ_MMC_STATUS_PRG_DONE BIT(13)
+#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12)
+#define JZ_MMC_STATUS_END_CMD_RES BIT(11)
+#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10)
+#define JZ_MMC_STATUS_IS_READWAIT BIT(9)
+#define JZ_MMC_STATUS_CLK_EN BIT(8)
+#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7)
+#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6)
+#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5)
+#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4)
+#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3)
+#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2)
+#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1)
+#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0)
+
+#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0))
+#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2))
+
+
+#define JZ_MMC_CMDAT_IO_ABORT BIT(11)
+#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10)
+#define JZ_MMC_CMDAT_DMA_EN BIT(8)
+#define JZ_MMC_CMDAT_INIT BIT(7)
+#define JZ_MMC_CMDAT_BUSY BIT(6)
+#define JZ_MMC_CMDAT_STREAM BIT(5)
+#define JZ_MMC_CMDAT_WRITE BIT(4)
+#define JZ_MMC_CMDAT_DATA_EN BIT(3)
+#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0))
+#define JZ_MMC_CMDAT_RSP_R1 1
+#define JZ_MMC_CMDAT_RSP_R2 2
+#define JZ_MMC_CMDAT_RSP_R3 3
+
+#define JZ_MMC_IRQ_SDIO BIT(7)
+#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6)
+#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5)
+#define JZ_MMC_IRQ_END_CMD_RES BIT(2)
+#define JZ_MMC_IRQ_PRG_DONE BIT(1)
+#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0)
+
+
+#define JZ_MMC_CLK_RATE 24000000
+
+enum jz4740_mmc_state {
+	JZ4740_MMC_STATE_READ_RESPONSE,
+	JZ4740_MMC_STATE_TRANSFER_DATA,
+	JZ4740_MMC_STATE_SEND_STOP,
+	JZ4740_MMC_STATE_DONE,
+};
+
+struct jz4740_mmc_host {
+	struct mmc_host *mmc;
+	struct platform_device *pdev;
+	struct jz4740_mmc_platform_data *pdata;
+	struct clk *clk;
+
+	int irq;
+	int card_detect_irq;
+
+	struct resource *mem;
+	void __iomem *base;
+	struct mmc_request *req;
+	struct mmc_command *cmd;
+
+	unsigned long waiting;
+
+	uint32_t cmdat;
+
+	uint16_t irq_mask;
+
+	spinlock_t lock;
+
+	struct timer_list timeout_timer;
+	struct sg_mapping_iter miter;
+	enum jz4740_mmc_state state;
+};
+
+static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host,
+	unsigned int irq, bool enabled)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (enabled)
+		host->irq_mask &= ~irq;
+	else
+		host->irq_mask |= irq;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK);
+}
+
+static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host,
+	bool start_transfer)
+{
+	uint16_t val = JZ_MMC_STRPCL_CLOCK_START;
+
+	if (start_transfer)
+		val |= JZ_MMC_STRPCL_START_OP;
+
+	writew(val, host->base + JZ_REG_MMC_STRPCL);
+}
+
+static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+	unsigned int timeout = 1000;
+
+	writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_CLK_EN && --timeout);
+}
+
+static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+	unsigned int timeout = 1000;
+
+	writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
+	udelay(10);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_IS_RESETTING && --timeout);
+}
+
+static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
+{
+	struct mmc_request *req;
+
+	req = host->req;
+	host->req = NULL;
+
+	mmc_request_done(host->mmc, req);
+}
+static unsigned int jz4740_mmc_poll_irq(struct jz4740_mmc_host *host,
+	unsigned int irq)
+{
+	unsigned int timeout = 1000;
+	uint16_t status;
+
+	do {
+		status = readw(host->base + JZ_REG_MMC_IREG);
+	} while (!(status & irq) && --timeout);
+
+	if (timeout == 0) {
+		set_bit(0, &host->waiting);
+		mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+		jz4740_mmc_set_irq_enabled(host, irq, true);
+		return true;
+	}
+
+	return false;
+}
+
+static void jz4740_mmc_transfer_check_state(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	int status;
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) {
+		if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) {
+			host->req->cmd->error = -ETIMEDOUT;
+			data->error = -ETIMEDOUT;
+		} else {
+			host->req->cmd->error = -EIO;
+			data->error = -EIO;
+		}
+	}
+}
+
+static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	struct sg_mapping_iter *miter = &host->miter;
+	uint32_t *buf;
+	bool timeout;
+	size_t i, j;
+
+	while (sg_miter_next(miter)) {
+		buf = miter->addr;
+		i = miter->length / 4;
+		j = i / 8;
+		i = i & 0x7;
+		while (j) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			writel(buf[0], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[1], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[2], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[3], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[4], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[5], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[6], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[7], host->base + JZ_REG_MMC_TXFIFO);
+			buf += 8;
+			--j;
+		}
+		if (unlikely(i)) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			while (i) {
+				writel(*buf, host->base + JZ_REG_MMC_TXFIFO);
+				++buf;
+				--i;
+			}
+		}
+		data->bytes_xfered += miter->length;
+	}
+	sg_miter_stop(miter);
+
+	return false;
+
+poll_timeout:
+	miter->consumed = (void *)buf - miter->addr;
+	data->bytes_xfered += miter->consumed;
+	sg_miter_stop(miter);
+
+	return true;
+}
+
+static bool jz4740_mmc_read_data(struct jz4740_mmc_host *host,
+				struct mmc_data *data)
+{
+	struct sg_mapping_iter *miter = &host->miter;
+	uint32_t *buf;
+	uint32_t d;
+	uint16_t status;
+	size_t i, j;
+	unsigned int timeout;
+
+	while (sg_miter_next(miter)) {
+		buf = miter->addr;
+		i = miter->length;
+		j = i / 32;
+		i = i & 0x1f;
+		while (j) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			buf[0] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[1] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[2] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[3] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[4] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[5] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[6] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[7] = readl(host->base + JZ_REG_MMC_RXFIFO);
+
+			buf += 8;
+			--j;
+		}
+
+		if (unlikely(i)) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			while (i >= 4) {
+				*buf++ = readl(host->base + JZ_REG_MMC_RXFIFO);
+				i -= 4;
+			}
+			if (unlikely(i > 0)) {
+				d = readl(host->base + JZ_REG_MMC_RXFIFO);
+				memcpy(buf, &d, i);
+			}
+		}
+		data->bytes_xfered += miter->length;
+
+		/* This can go away once MIPS implements
+		 * flush_kernel_dcache_page */
+		flush_dcache_page(miter->page);
+	}
+	sg_miter_stop(miter);
+
+	/* For whatever reason there is sometime one word more in the fifo then
+	 * requested */
+	timeout = 1000;
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	while (!(status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) && --timeout) {
+		d = readl(host->base + JZ_REG_MMC_RXFIFO);
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	}
+
+	return false;
+
+poll_timeout:
+	miter->consumed = (void *)buf - miter->addr;
+	data->bytes_xfered += miter->consumed;
+	sg_miter_stop(miter);
+
+	return true;
+}
+
+static void jz4740_mmc_timeout(unsigned long data)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
+
+	if (!test_and_clear_bit(0, &host->waiting))
+		return;
+
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false);
+
+	host->req->cmd->error = -ETIMEDOUT;
+	jz4740_mmc_request_done(host);
+}
+
+static void jz4740_mmc_read_response(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	int i;
+	uint16_t tmp;
+
+	if (cmd->flags & MMC_RSP_136) {
+		tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+		for (i = 0; i < 4; ++i) {
+			cmd->resp[i] = tmp << 24;
+			tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+			cmd->resp[i] |= tmp << 8;
+			tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+			cmd->resp[i] |= tmp >> 8;
+		}
+	} else {
+		cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff;
+	}
+}
+
+static void jz4740_mmc_send_command(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	uint32_t cmdat = host->cmdat;
+
+	host->cmdat &= ~JZ_MMC_CMDAT_INIT;
+	jz4740_mmc_clock_disable(host);
+
+	host->cmd = cmd;
+
+	if (cmd->flags & MMC_RSP_BUSY)
+		cmdat |= JZ_MMC_CMDAT_BUSY;
+
+	switch (mmc_resp_type(cmd)) {
+	case MMC_RSP_R1B:
+	case MMC_RSP_R1:
+		cmdat |= JZ_MMC_CMDAT_RSP_R1;
+		break;
+	case MMC_RSP_R2:
+		cmdat |= JZ_MMC_CMDAT_RSP_R2;
+		break;
+	case MMC_RSP_R3:
+		cmdat |= JZ_MMC_CMDAT_RSP_R3;
+		break;
+	default:
+		break;
+	}
+
+	if (cmd->data) {
+		cmdat |= JZ_MMC_CMDAT_DATA_EN;
+		if (cmd->data->flags & MMC_DATA_WRITE)
+			cmdat |= JZ_MMC_CMDAT_WRITE;
+		if (cmd->data->flags & MMC_DATA_STREAM)
+			cmdat |= JZ_MMC_CMDAT_STREAM;
+
+		writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN);
+		writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB);
+	}
+
+	writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD);
+	writel(cmd->arg, host->base + JZ_REG_MMC_ARG);
+	writel(cmdat, host->base + JZ_REG_MMC_CMDAT);
+
+	jz4740_mmc_clock_enable(host, 1);
+}
+
+static void jz_mmc_prepare_data_transfer(struct jz4740_mmc_host *host)
+{
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_data *data = cmd->data;
+	int direction;
+
+	if (cmd->data->flags & MMC_DATA_READ)
+		direction = SG_MITER_TO_SG;
+	else
+		direction = SG_MITER_FROM_SG;
+
+	sg_miter_start(&host->miter, data->sg, data->sg_len, direction);
+}
+
+
+static irqreturn_t jz_mmc_irq_worker(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid;
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_request *req = host->req;
+	bool timedout = false;
+
+	if (cmd->error)
+		host->state = JZ4740_MMC_STATE_DONE;
+
+	switch (host->state) {
+	case JZ4740_MMC_STATE_READ_RESPONSE:
+		if (cmd->flags & MMC_RSP_PRESENT)
+			jz4740_mmc_read_response(host, cmd);
+
+		if (!cmd->data)
+			break;
+
+		jz_mmc_prepare_data_transfer(host);
+
+	case JZ4740_MMC_STATE_TRANSFER_DATA:
+		if (cmd->data->flags & MMC_DATA_READ)
+			timedout = jz4740_mmc_read_data(host, cmd->data);
+		else
+			timedout = jz4740_mmc_write_data(host, cmd->data);
+
+		if (unlikely(timedout)) {
+			host->state = JZ4740_MMC_STATE_TRANSFER_DATA;
+			break;
+		}
+
+		jz4740_mmc_transfer_check_state(host, cmd->data);
+
+		timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE);
+		if (unlikely(timedout)) {
+			host->state = JZ4740_MMC_STATE_SEND_STOP;
+			break;
+		}
+		writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG);
+
+	case JZ4740_MMC_STATE_SEND_STOP:
+		if (!req->stop)
+			break;
+
+		jz4740_mmc_send_command(host, req->stop);
+
+		timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_PRG_DONE);
+		if (timedout) {
+			host->state = JZ4740_MMC_STATE_DONE;
+			break;
+		}
+	case JZ4740_MMC_STATE_DONE:
+		break;
+	}
+
+	if (!timedout)
+		jz4740_mmc_request_done(host);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t jz_mmc_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+	uint16_t irq_reg, status, tmp;
+
+	irq_reg = readw(host->base + JZ_REG_MMC_IREG);
+
+	tmp = irq_reg;
+	irq_reg &= ~host->irq_mask;
+
+	tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ |
+		JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE);
+
+	if (tmp != irq_reg)
+		writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG);
+
+	if (irq_reg & JZ_MMC_IRQ_SDIO) {
+		writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG);
+		mmc_signal_sdio_irq(host->mmc);
+		irq_reg &= ~JZ_MMC_IRQ_SDIO;
+	}
+
+	if (host->req && host->cmd && irq_reg) {
+		if (test_and_clear_bit(0, &host->waiting)) {
+			del_timer(&host->timeout_timer);
+
+			status = readl(host->base + JZ_REG_MMC_STATUS);
+
+			if (status & JZ_MMC_STATUS_TIMEOUT_RES) {
+				host->cmd->error = -ETIMEDOUT;
+			} else if (status & JZ_MMC_STATUS_CRC_RES_ERR) {
+				host->cmd->error = -EIO;
+			} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+				    JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+				host->cmd->data->error = -EIO;
+			} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+					JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+				host->cmd->data->error = -EIO;
+			}
+
+			jz4740_mmc_set_irq_enabled(host, irq_reg, false);
+			writew(irq_reg, host->base + JZ_REG_MMC_IREG);
+
+			return IRQ_WAKE_THREAD;
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate)
+{
+	int div = 0;
+	int real_rate;
+
+	jz4740_mmc_clock_disable(host);
+	clk_set_rate(host->clk, JZ_MMC_CLK_RATE);
+
+	real_rate = clk_get_rate(host->clk);
+
+	while (real_rate > rate && div < 7) {
+		++div;
+		real_rate >>= 1;
+	}
+
+	writew(div, host->base + JZ_REG_MMC_CLKRT);
+	return real_rate;
+}
+
+static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+
+	host->req = req;
+
+	writew(0xffff, host->base + JZ_REG_MMC_IREG);
+
+	writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true);
+
+	host->state = JZ4740_MMC_STATE_READ_RESPONSE;
+	set_bit(0, &host->waiting);
+	mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+	jz4740_mmc_send_command(host, req->cmd);
+}
+
+static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (ios->clock)
+		jz4740_mmc_set_clock_rate(host, ios->clock);
+
+	switch (ios->power_mode) {
+	case MMC_POWER_UP:
+		jz4740_mmc_reset(host);
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					!host->pdata->power_active_low);
+		host->cmdat |= JZ_MMC_CMDAT_INIT;
+		clk_enable(host->clk);
+		break;
+	case MMC_POWER_ON:
+		break;
+	default:
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					host->pdata->power_active_low);
+		clk_disable(host->clk);
+		break;
+	}
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_1:
+		host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	case MMC_BUS_WIDTH_4:
+		host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	default:
+		break;
+	}
+}
+
+static int jz4740_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_read_only))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_read_only) ^
+		host->pdata->read_only_active_low;
+}
+
+static int jz4740_mmc_get_cd(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_card_detect))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_card_detect) ^
+			host->pdata->card_detect_active_low;
+}
+
+static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+
+	mmc_detect_change(host->mmc, HZ / 2);
+
+	return IRQ_HANDLED;
+}
+
+static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable);
+}
+
+static const struct mmc_host_ops jz4740_mmc_ops = {
+	.request	= jz4740_mmc_request,
+	.set_ios	= jz4740_mmc_set_ios,
+	.get_ro		= jz4740_mmc_get_ro,
+	.get_cd		= jz4740_mmc_get_cd,
+	.enable_sdio_irq = jz4740_mmc_enable_sdio_irq,
+};
+
+static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = {
+	JZ_GPIO_BULK_PIN(MSC_CMD),
+	JZ_GPIO_BULK_PIN(MSC_CLK),
+	JZ_GPIO_BULK_PIN(MSC_DATA0),
+	JZ_GPIO_BULK_PIN(MSC_DATA1),
+	JZ_GPIO_BULK_PIN(MSC_DATA2),
+	JZ_GPIO_BULK_PIN(MSC_DATA3),
+};
+
+static int __devinit jz4740_mmc_request_gpio(struct device *dev, int gpio,
+	const char *name, bool output, int value)
+{
+	int ret;
+
+	if (!gpio_is_valid(gpio))
+		return 0;
+
+	ret = gpio_request(gpio, name);
+	if (ret) {
+		dev_err(dev, "Failed to request %s gpio: %d\n", name, ret);
+		return ret;
+	}
+
+	if (output)
+		gpio_direction_output(gpio, value);
+	else
+		gpio_direction_input(gpio);
+
+	return 0;
+}
+
+static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return 0;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_card_detect,
+			"MMC detect change", false, 0);
+	if (ret)
+		goto err;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_read_only,
+			"MMC read only", false, 0);
+	if (ret)
+		goto err_free_gpio_card_detect;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_power,
+			"MMC read only", true, pdata->power_active_low);
+	if (ret)
+		goto err_free_gpio_read_only;
+
+	return 0;
+
+err_free_gpio_read_only:
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+err_free_gpio_card_detect:
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+err:
+	return ret;
+}
+
+static int __devinit jz4740_mmc_request_cd_irq(struct platform_device *pdev,
+	struct jz4740_mmc_host *host)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		return 0;
+
+	host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect);
+
+	if (host->card_detect_irq < 0) {
+		dev_warn(&pdev->dev, "Failed to get card detect irq\n");
+		return 0;
+	}
+	return request_irq(host->card_detect_irq, jz4740_mmc_card_detect_irq,
+			IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"MMC card detect", host);
+
+
+	return ret;
+}
+
+static void jz4740_mmc_free_gpios(struct platform_device *pdev)
+{
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return;
+
+	if (gpio_is_valid(pdata->gpio_power))
+		gpio_free(pdata->gpio_power);
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+}
+
+static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host)
+{
+	size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins);
+	if (host->pdata && host->pdata->data_1bit)
+		num_pins -= 3;
+
+	return num_pins;
+}
+
+static int __devinit jz4740_mmc_probe(struct platform_device* pdev)
+{
+	int ret;
+	struct mmc_host *mmc;
+	struct jz4740_mmc_host *host;
+	struct jz4740_mmc_platform_data *pdata;
+
+	pdata = pdev->dev.platform_data;
+
+	mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev);
+	if (!mmc) {
+		dev_err(&pdev->dev, "Failed to alloc mmc host structure\n");
+		return -ENOMEM;
+	}
+
+	host = mmc_priv(mmc);
+	host->pdata = pdata;
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq < 0) {
+		ret = host->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+		goto err_free_host;
+	}
+
+	host->clk = clk_get(&pdev->dev, "mmc");
+	if (!host->clk) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get mmc clock\n");
+		goto err_free_host;
+	}
+
+	host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!host->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get base platform memory\n");
+		goto err_clk_put;
+	}
+
+	host->mem = request_mem_region(host->mem->start,
+					resource_size(host->mem), pdev->name);
+	if (!host->mem) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to request base memory region\n");
+		goto err_clk_put;
+	}
+
+	host->base = ioremap_nocache(host->mem->start, resource_size(host->mem));
+	if (!host->base) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to ioremap base memory\n");
+		goto err_release_mem_region;
+	}
+
+	ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret);
+		goto err_iounmap;
+	}
+
+	ret = jz4740_mmc_request_gpios(pdev);
+	if (ret)
+		goto err_gpio_bulk_free;
+
+	mmc->ops = &jz4740_mmc_ops;
+	mmc->f_min = JZ_MMC_CLK_RATE / 128;
+	mmc->f_max = JZ_MMC_CLK_RATE;
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA;
+	mmc->caps |= MMC_CAP_SDIO_IRQ;
+
+	mmc->max_blk_size = (1 << 10) - 1;
+	mmc->max_blk_count = (1 << 15) - 1;
+	mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+	mmc->max_phys_segs = 128;
+	mmc->max_hw_segs = 128;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host->mmc = mmc;
+	host->pdev = pdev;
+	spin_lock_init(&host->lock);
+	host->irq_mask = 0xffff;
+
+	ret = jz4740_mmc_request_cd_irq(pdev, host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request card detect irq\n");
+		goto err_free_gpios;
+	}
+
+	ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0,
+			dev_name(&pdev->dev), host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+		goto err_free_card_detect_irq;
+	}
+
+	jz4740_mmc_reset(host);
+	jz4740_mmc_clock_disable(host);
+	setup_timer(&host->timeout_timer, jz4740_mmc_timeout,
+			(unsigned long)host);
+	/* It is not important when it times out, it just needs to timeout. */
+	set_timer_slack(&host->timeout_timer, HZ);
+
+	platform_set_drvdata(pdev, host);
+	ret = mmc_add_host(mmc);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret);
+		goto err_free_irq;
+	}
+	dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n");
+
+	return 0;
+
+err_free_irq:
+	free_irq(host->irq, host);
+err_free_card_detect_irq:
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+err_free_gpios:
+	jz4740_mmc_free_gpios(pdev);
+err_gpio_bulk_free:
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+err_iounmap:
+	iounmap(host->base);
+err_release_mem_region:
+	release_mem_region(host->mem->start, resource_size(host->mem));
+err_clk_put:
+	clk_put(host->clk);
+err_free_host:
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(mmc);
+
+	return ret;
+}
+
+static int __devexit jz4740_mmc_remove(struct platform_device *pdev)
+{
+	struct jz4740_mmc_host *host = platform_get_drvdata(pdev);
+
+	del_timer_sync(&host->timeout_timer);
+	jz4740_mmc_set_irq_enabled(host, 0xff, false);
+	jz4740_mmc_reset(host);
+
+	mmc_remove_host(host->mmc);
+
+	free_irq(host->irq, host);
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+
+	jz4740_mmc_free_gpios(pdev);
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	iounmap(host->base);
+	release_mem_region(host->mem->start, resource_size(host->mem));
+
+	clk_put(host->clk);
+
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(host->mmc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int jz4740_mmc_suspend(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	mmc_suspend_host(host->mmc);
+
+	jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	return 0;
+}
+
+static int jz4740_mmc_resume(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	mmc_resume_host(host->mmc);
+
+	return 0;
+}
+
+const struct dev_pm_ops jz4740_mmc_pm_ops = {
+	.suspend	= jz4740_mmc_suspend,
+	.resume		= jz4740_mmc_resume,
+	.poweroff	= jz4740_mmc_suspend,
+	.restore	= jz4740_mmc_resume,
+};
+
+#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops)
+#else
+#define JZ4740_MMC_PM_OPS NULL
+#endif
+
+static struct platform_driver jz4740_mmc_driver = {
+	.probe = jz4740_mmc_probe,
+	.remove = __devexit_p(jz4740_mmc_remove),
+	.driver = {
+		.name = "jz4740-mmc",
+		.owner = THIS_MODULE,
+		.pm = JZ4740_MMC_PM_OPS,
+	},
+};
+
+static int __init jz4740_mmc_init(void)
+{
+	return platform_driver_register(&jz4740_mmc_driver);
+}
+module_init(jz4740_mmc_init);
+
+static void __exit jz4740_mmc_exit(void)
+{
+	platform_driver_unregister(&jz4740_mmc_driver);
+}
+module_exit(jz4740_mmc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [PATCH v4] MMC: Add JZ4740 mmc driver
  2010-07-12 21:33     ` [PATCH v4] " Lars-Peter Clausen
@ 2010-07-12 21:41       ` Randy Dunlap
  2010-07-12 22:07         ` Lars-Peter Clausen
  2010-07-12 22:20       ` [PATCH v5] " Lars-Peter Clausen
  1 sibling, 1 reply; 163+ messages in thread
From: Randy Dunlap @ 2010-07-12 21:41 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton,
	Matt Fleming, linux-mmc

Lars-Peter Clausen wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> 
> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
> Acked-by: Matt Fleming <matt@console-pimps.org>
> Cc: Andrew Morton <akpm@linux-foundation.org>
> Cc: Matt Fleming <matt@console-pimps.org>
> Cc: linux-mmc@vger.kernel.org
> 
> ---
>  arch/mips/include/asm/mach-jz4740/jz4740_mmc.h |   15 +
>  drivers/mmc/host/Kconfig                       |    8 +
>  drivers/mmc/host/Makefile                      |    1 +
>  drivers/mmc/host/jz4740_mmc.c                  | 1024 ++++++++++++++++++++++++
>  4 files changed, 1048 insertions(+), 0 deletions(-)
>  create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
>  create mode 100644 drivers/mmc/host/jz4740_mmc.c
> 
> diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
> new file mode 100644
> index 0000000..8543f43
> --- /dev/null
> +++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
> @@ -0,0 +1,15 @@
> +#ifndef __LINUX_MMC_JZ4740_MMC
> +#define __LINUX_MMC_JZ4740_MMC
> +
> +struct jz4740_mmc_platform_data {
> +	int gpio_power;
> +	int gpio_card_detect;
> +	int gpio_read_only;
> +	unsigned card_detect_active_low:1;
> +	unsigned read_only_active_low:1;
> +	unsigned power_active_low:1;
> +
> +	unsigned data_1bit:1;
> +};
> +
> +#endif
> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
> index f06d06e..546fc49 100644
> --- a/drivers/mmc/host/Kconfig
> +++ b/drivers/mmc/host/Kconfig
> @@ -81,6 +81,14 @@ config MMC_RICOH_MMC
>  
>  	  If unsure, say Y.
>  
> +config MMC_JZ4740
> +	tristate "JZ4740 SD/Multimedia Card Interface support"
> +	depends on MACH_JZ4740

What tree has the kconfig symbol MACH_JZ4740 in it?
I can't seem to find it...

Should the depends also say anything about GPIO?
I only ask because the header file above mentions gpio.

> +	help
> +	  This selects the Ingenic Z4740 SD/Multimedia card Interface.
> +	  If you have an ngenic platform with a Multimedia Card slot,

	                 Ingenic ?

> +	  say Y or M here.
> +
>  config MMC_SDHCI_OF
>  	tristate "SDHCI support on OpenFirmware platforms"
>  	depends on MMC_SDHCI && PPC_OF


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v4] MMC: Add JZ4740 mmc driver
  2010-07-12 21:41       ` Randy Dunlap
@ 2010-07-12 22:07         ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-12 22:07 UTC (permalink / raw)
  To: Randy Dunlap
  Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton,
	Matt Fleming, linux-mmc

Hi

Randy Dunlap wrote:
> Lars-Peter Clausen wrote:
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>>
>> Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
>> Acked-by: Matt Fleming <matt@console-pimps.org>
>> Cc: Andrew Morton <akpm@linux-foundation.org>
>> Cc: Matt Fleming <matt@console-pimps.org>
>> Cc: linux-mmc@vger.kernel.org
>>
>> ---
>>  arch/mips/include/asm/mach-jz4740/jz4740_mmc.h |   15 +
>>  drivers/mmc/host/Kconfig                       |    8 +
>>  drivers/mmc/host/Makefile                      |    1 +
>>  drivers/mmc/host/jz4740_mmc.c                  | 1024 ++++++++++++++++++++++++
>>  4 files changed, 1048 insertions(+), 0 deletions(-)
>>  create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
>>  create mode 100644 drivers/mmc/host/jz4740_mmc.c
>>
>> diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
>> new file mode 100644
>> index 0000000..8543f43
>> --- /dev/null
>> +++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
>> @@ -0,0 +1,15 @@
>> +#ifndef __LINUX_MMC_JZ4740_MMC
>> +#define __LINUX_MMC_JZ4740_MMC
>> +
>> +struct jz4740_mmc_platform_data {
>> +	int gpio_power;
>> +	int gpio_card_detect;
>> +	int gpio_read_only;
>> +	unsigned card_detect_active_low:1;
>> +	unsigned read_only_active_low:1;
>> +	unsigned power_active_low:1;
>> +
>> +	unsigned data_1bit:1;
>> +};
>> +
>> +#endif
>> diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
>> index f06d06e..546fc49 100644
>> --- a/drivers/mmc/host/Kconfig
>> +++ b/drivers/mmc/host/Kconfig
>> @@ -81,6 +81,14 @@ config MMC_RICOH_MMC
>>  
>>  	  If unsure, say Y.
>>  
>> +config MMC_JZ4740
>> +	tristate "JZ4740 SD/Multimedia Card Interface support"
>> +	depends on MACH_JZ4740
> 
> What tree has the kconfig symbol MACH_JZ4740 in it?
> I can't seem to find it...
> 
> Should the depends also say anything about GPIO?
> I only ask because the header file above mentions gpio.
> 

None yet, mips hopefully soon. Version 1 of this patch was part of a series adding
support for the JZ4740.
Since the jz4740 platform code provides the gpio functions I don't think it is
necessary to add an additional depends on GENERIC_GPIO.

>> +	help
>> +	  This selects the Ingenic Z4740 SD/Multimedia card Interface.
>> +	  If you have an ngenic platform with a Multimedia Card slot,
> 
> 	                 Ingenic ?
> 

Woops, yes.

>> +	  say Y or M here.
>> +
>>  config MMC_SDHCI_OF
>>  	tristate "SDHCI support on OpenFirmware platforms"
>>  	depends on MMC_SDHCI && PPC_OF
> 
> 

Thanks for reviewing
- Lars

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v5] MMC: Add JZ4740 mmc driver
  2010-07-12 21:33     ` [PATCH v4] " Lars-Peter Clausen
  2010-07-12 21:41       ` Randy Dunlap
@ 2010-07-12 22:20       ` Lars-Peter Clausen
  2010-07-12 22:45         ` Joe Perches
  2010-07-15 21:06         ` [PATCH v6] " Lars-Peter Clausen
  1 sibling, 2 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-12 22:20 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	Matt Fleming, linux-mmc

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Acked-by: Matt Fleming <matt@console-pimps.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Matt Fleming <matt@console-pimps.org>
Cc: linux-mmc@vger.kernel.org

---
Changes since v1
- Do not request IRQ with IRQF_DISABLED since it is a noop now
- Use a generous slack for the timeout timer. It does not need to be accurate.

Changes since v2
- Use sg_mapping_to iterate over sg elements in mmc read and write functions
- Use bitops instead of a spinlock and a variable for testing whether a request
  has been finished.
- Rework irq and timeout handling in order to get rid of locking in hot paths

Changes since v3
- Drastically decrease IRQ poll timeout. Now when the poll timeout is reached
  the IRQ which was polled is enabled. A variable keeps track of what state the
  driver is in and what has to be done next when the IRQ handler is entered
  again.
  By doing so busy looping is reduced, but overall performance can be maintained.
- Move header file from include/linux/mmc to arch/mips/include/asm/mach-jz4740

Changes since v4
- Rework Kconfig entry
---
 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h |   15 +
 drivers/mmc/host/Kconfig                       |    9 +
 drivers/mmc/host/Makefile                      |    1 +
 drivers/mmc/host/jz4740_mmc.c                  | 1024 ++++++++++++++++++++++++
 4 files changed, 1049 insertions(+), 0 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
 create mode 100644 drivers/mmc/host/jz4740_mmc.c

diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
new file mode 100644
index 0000000..8543f43
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
@@ -0,0 +1,15 @@
+#ifndef __LINUX_MMC_JZ4740_MMC
+#define __LINUX_MMC_JZ4740_MMC
+
+struct jz4740_mmc_platform_data {
+	int gpio_power;
+	int gpio_card_detect;
+	int gpio_read_only;
+	unsigned card_detect_active_low:1;
+	unsigned read_only_active_low:1;
+	unsigned power_active_low:1;
+
+	unsigned data_1bit:1;
+};
+
+#endif
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f06d06e..d25e22c 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -432,3 +432,12 @@ config MMC_SH_MMCIF
 	  This selects the MMC Host Interface controler (MMCIF).
 
 	  This driver supports MMCIF in sh7724/sh7757/sh7372.
+
+config MMC_JZ4740
+	tristate "JZ4740 SD/Multimedia Card Interface support"
+	depends on MACH_JZ4740
+	help
+	  This selects support for the SD/MMC controller on Ingenic JZ4740
+	  SoCs.
+	  If you have a board based on such a SoC and with a SD/MMC slot,
+	  say Y or M here.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e30c2ee..f4e53c9 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc.o
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
+obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
 
 obj-$(CONFIG_MMC_SDHCI_OF)	+= sdhci-of.o
 sdhci-of-y				:= sdhci-of-core.o
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c
new file mode 100644
index 0000000..82a449a
--- /dev/null
+++ b/drivers/mmc/host/jz4740_mmc.c
@@ -0,0 +1,1024 @@
+/*
+ *  Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *  JZ4740 SD/MMC controller driver
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <asm/mach-jz4740/gpio.h>
+#include <asm/cacheflush.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/mach-jz4740/jz4740_mmc.h>
+
+#define JZ_REG_MMC_STRPCL	0x00
+#define JZ_REG_MMC_STATUS	0x04
+#define JZ_REG_MMC_CLKRT	0x08
+#define JZ_REG_MMC_CMDAT	0x0C
+#define JZ_REG_MMC_RESTO	0x10
+#define JZ_REG_MMC_RDTO		0x14
+#define JZ_REG_MMC_BLKLEN	0x18
+#define JZ_REG_MMC_NOB		0x1C
+#define JZ_REG_MMC_SNOB		0x20
+#define JZ_REG_MMC_IMASK	0x24
+#define JZ_REG_MMC_IREG		0x28
+#define JZ_REG_MMC_CMD		0x2C
+#define JZ_REG_MMC_ARG		0x30
+#define JZ_REG_MMC_RESP_FIFO	0x34
+#define JZ_REG_MMC_RXFIFO	0x38
+#define JZ_REG_MMC_TXFIFO	0x3C
+
+#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7)
+#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6)
+#define JZ_MMC_STRPCL_START_READWAIT BIT(5)
+#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4)
+#define JZ_MMC_STRPCL_RESET BIT(3)
+#define JZ_MMC_STRPCL_START_OP BIT(2)
+#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0))
+#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0)
+#define JZ_MMC_STRPCL_CLOCK_START BIT(1)
+
+
+#define JZ_MMC_STATUS_IS_RESETTING BIT(15)
+#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14)
+#define JZ_MMC_STATUS_PRG_DONE BIT(13)
+#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12)
+#define JZ_MMC_STATUS_END_CMD_RES BIT(11)
+#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10)
+#define JZ_MMC_STATUS_IS_READWAIT BIT(9)
+#define JZ_MMC_STATUS_CLK_EN BIT(8)
+#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7)
+#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6)
+#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5)
+#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4)
+#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3)
+#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2)
+#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1)
+#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0)
+
+#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0))
+#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2))
+
+
+#define JZ_MMC_CMDAT_IO_ABORT BIT(11)
+#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10)
+#define JZ_MMC_CMDAT_DMA_EN BIT(8)
+#define JZ_MMC_CMDAT_INIT BIT(7)
+#define JZ_MMC_CMDAT_BUSY BIT(6)
+#define JZ_MMC_CMDAT_STREAM BIT(5)
+#define JZ_MMC_CMDAT_WRITE BIT(4)
+#define JZ_MMC_CMDAT_DATA_EN BIT(3)
+#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0))
+#define JZ_MMC_CMDAT_RSP_R1 1
+#define JZ_MMC_CMDAT_RSP_R2 2
+#define JZ_MMC_CMDAT_RSP_R3 3
+
+#define JZ_MMC_IRQ_SDIO BIT(7)
+#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6)
+#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5)
+#define JZ_MMC_IRQ_END_CMD_RES BIT(2)
+#define JZ_MMC_IRQ_PRG_DONE BIT(1)
+#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0)
+
+
+#define JZ_MMC_CLK_RATE 24000000
+
+enum jz4740_mmc_state {
+	JZ4740_MMC_STATE_READ_RESPONSE,
+	JZ4740_MMC_STATE_TRANSFER_DATA,
+	JZ4740_MMC_STATE_SEND_STOP,
+	JZ4740_MMC_STATE_DONE,
+};
+
+struct jz4740_mmc_host {
+	struct mmc_host *mmc;
+	struct platform_device *pdev;
+	struct jz4740_mmc_platform_data *pdata;
+	struct clk *clk;
+
+	int irq;
+	int card_detect_irq;
+
+	struct resource *mem;
+	void __iomem *base;
+	struct mmc_request *req;
+	struct mmc_command *cmd;
+
+	unsigned long waiting;
+
+	uint32_t cmdat;
+
+	uint16_t irq_mask;
+
+	spinlock_t lock;
+
+	struct timer_list timeout_timer;
+	struct sg_mapping_iter miter;
+	enum jz4740_mmc_state state;
+};
+
+static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host,
+	unsigned int irq, bool enabled)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (enabled)
+		host->irq_mask &= ~irq;
+	else
+		host->irq_mask |= irq;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK);
+}
+
+static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host,
+	bool start_transfer)
+{
+	uint16_t val = JZ_MMC_STRPCL_CLOCK_START;
+
+	if (start_transfer)
+		val |= JZ_MMC_STRPCL_START_OP;
+
+	writew(val, host->base + JZ_REG_MMC_STRPCL);
+}
+
+static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+	unsigned int timeout = 1000;
+
+	writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_CLK_EN && --timeout);
+}
+
+static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+	unsigned int timeout = 1000;
+
+	writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
+	udelay(10);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_IS_RESETTING && --timeout);
+}
+
+static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
+{
+	struct mmc_request *req;
+
+	req = host->req;
+	host->req = NULL;
+
+	mmc_request_done(host->mmc, req);
+}
+static unsigned int jz4740_mmc_poll_irq(struct jz4740_mmc_host *host,
+	unsigned int irq)
+{
+	unsigned int timeout = 1000;
+	uint16_t status;
+
+	do {
+		status = readw(host->base + JZ_REG_MMC_IREG);
+	} while (!(status & irq) && --timeout);
+
+	if (timeout == 0) {
+		set_bit(0, &host->waiting);
+		mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+		jz4740_mmc_set_irq_enabled(host, irq, true);
+		return true;
+	}
+
+	return false;
+}
+
+static void jz4740_mmc_transfer_check_state(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	int status;
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) {
+		if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) {
+			host->req->cmd->error = -ETIMEDOUT;
+			data->error = -ETIMEDOUT;
+		} else {
+			host->req->cmd->error = -EIO;
+			data->error = -EIO;
+		}
+	}
+}
+
+static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	struct sg_mapping_iter *miter = &host->miter;
+	uint32_t *buf;
+	bool timeout;
+	size_t i, j;
+
+	while (sg_miter_next(miter)) {
+		buf = miter->addr;
+		i = miter->length / 4;
+		j = i / 8;
+		i = i & 0x7;
+		while (j) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			writel(buf[0], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[1], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[2], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[3], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[4], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[5], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[6], host->base + JZ_REG_MMC_TXFIFO);
+			writel(buf[7], host->base + JZ_REG_MMC_TXFIFO);
+			buf += 8;
+			--j;
+		}
+		if (unlikely(i)) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			while (i) {
+				writel(*buf, host->base + JZ_REG_MMC_TXFIFO);
+				++buf;
+				--i;
+			}
+		}
+		data->bytes_xfered += miter->length;
+	}
+	sg_miter_stop(miter);
+
+	return false;
+
+poll_timeout:
+	miter->consumed = (void *)buf - miter->addr;
+	data->bytes_xfered += miter->consumed;
+	sg_miter_stop(miter);
+
+	return true;
+}
+
+static bool jz4740_mmc_read_data(struct jz4740_mmc_host *host,
+				struct mmc_data *data)
+{
+	struct sg_mapping_iter *miter = &host->miter;
+	uint32_t *buf;
+	uint32_t d;
+	uint16_t status;
+	size_t i, j;
+	unsigned int timeout;
+
+	while (sg_miter_next(miter)) {
+		buf = miter->addr;
+		i = miter->length;
+		j = i / 32;
+		i = i & 0x1f;
+		while (j) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			buf[0] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[1] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[2] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[3] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[4] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[5] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[6] = readl(host->base + JZ_REG_MMC_RXFIFO);
+			buf[7] = readl(host->base + JZ_REG_MMC_RXFIFO);
+
+			buf += 8;
+			--j;
+		}
+
+		if (unlikely(i)) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			while (i >= 4) {
+				*buf++ = readl(host->base + JZ_REG_MMC_RXFIFO);
+				i -= 4;
+			}
+			if (unlikely(i > 0)) {
+				d = readl(host->base + JZ_REG_MMC_RXFIFO);
+				memcpy(buf, &d, i);
+			}
+		}
+		data->bytes_xfered += miter->length;
+
+		/* This can go away once MIPS implements
+		 * flush_kernel_dcache_page */
+		flush_dcache_page(miter->page);
+	}
+	sg_miter_stop(miter);
+
+	/* For whatever reason there is sometime one word more in the fifo then
+	 * requested */
+	timeout = 1000;
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	while (!(status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) && --timeout) {
+		d = readl(host->base + JZ_REG_MMC_RXFIFO);
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	}
+
+	return false;
+
+poll_timeout:
+	miter->consumed = (void *)buf - miter->addr;
+	data->bytes_xfered += miter->consumed;
+	sg_miter_stop(miter);
+
+	return true;
+}
+
+static void jz4740_mmc_timeout(unsigned long data)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
+
+	if (!test_and_clear_bit(0, &host->waiting))
+		return;
+
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false);
+
+	host->req->cmd->error = -ETIMEDOUT;
+	jz4740_mmc_request_done(host);
+}
+
+static void jz4740_mmc_read_response(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	int i;
+	uint16_t tmp;
+
+	if (cmd->flags & MMC_RSP_136) {
+		tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+		for (i = 0; i < 4; ++i) {
+			cmd->resp[i] = tmp << 24;
+			tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+			cmd->resp[i] |= tmp << 8;
+			tmp = readw(host->base + JZ_REG_MMC_RESP_FIFO);
+			cmd->resp[i] |= tmp >> 8;
+		}
+	} else {
+		cmd->resp[0] = readw(host->base + JZ_REG_MMC_RESP_FIFO) << 24;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) << 8;
+		cmd->resp[0] |= readw(host->base + JZ_REG_MMC_RESP_FIFO) & 0xff;
+	}
+}
+
+static void jz4740_mmc_send_command(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	uint32_t cmdat = host->cmdat;
+
+	host->cmdat &= ~JZ_MMC_CMDAT_INIT;
+	jz4740_mmc_clock_disable(host);
+
+	host->cmd = cmd;
+
+	if (cmd->flags & MMC_RSP_BUSY)
+		cmdat |= JZ_MMC_CMDAT_BUSY;
+
+	switch (mmc_resp_type(cmd)) {
+	case MMC_RSP_R1B:
+	case MMC_RSP_R1:
+		cmdat |= JZ_MMC_CMDAT_RSP_R1;
+		break;
+	case MMC_RSP_R2:
+		cmdat |= JZ_MMC_CMDAT_RSP_R2;
+		break;
+	case MMC_RSP_R3:
+		cmdat |= JZ_MMC_CMDAT_RSP_R3;
+		break;
+	default:
+		break;
+	}
+
+	if (cmd->data) {
+		cmdat |= JZ_MMC_CMDAT_DATA_EN;
+		if (cmd->data->flags & MMC_DATA_WRITE)
+			cmdat |= JZ_MMC_CMDAT_WRITE;
+		if (cmd->data->flags & MMC_DATA_STREAM)
+			cmdat |= JZ_MMC_CMDAT_STREAM;
+
+		writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN);
+		writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB);
+	}
+
+	writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD);
+	writel(cmd->arg, host->base + JZ_REG_MMC_ARG);
+	writel(cmdat, host->base + JZ_REG_MMC_CMDAT);
+
+	jz4740_mmc_clock_enable(host, 1);
+}
+
+static void jz_mmc_prepare_data_transfer(struct jz4740_mmc_host *host)
+{
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_data *data = cmd->data;
+	int direction;
+
+	if (cmd->data->flags & MMC_DATA_READ)
+		direction = SG_MITER_TO_SG;
+	else
+		direction = SG_MITER_FROM_SG;
+
+	sg_miter_start(&host->miter, data->sg, data->sg_len, direction);
+}
+
+
+static irqreturn_t jz_mmc_irq_worker(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid;
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_request *req = host->req;
+	bool timedout = false;
+
+	if (cmd->error)
+		host->state = JZ4740_MMC_STATE_DONE;
+
+	switch (host->state) {
+	case JZ4740_MMC_STATE_READ_RESPONSE:
+		if (cmd->flags & MMC_RSP_PRESENT)
+			jz4740_mmc_read_response(host, cmd);
+
+		if (!cmd->data)
+			break;
+
+		jz_mmc_prepare_data_transfer(host);
+
+	case JZ4740_MMC_STATE_TRANSFER_DATA:
+		if (cmd->data->flags & MMC_DATA_READ)
+			timedout = jz4740_mmc_read_data(host, cmd->data);
+		else
+			timedout = jz4740_mmc_write_data(host, cmd->data);
+
+		if (unlikely(timedout)) {
+			host->state = JZ4740_MMC_STATE_TRANSFER_DATA;
+			break;
+		}
+
+		jz4740_mmc_transfer_check_state(host, cmd->data);
+
+		timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE);
+		if (unlikely(timedout)) {
+			host->state = JZ4740_MMC_STATE_SEND_STOP;
+			break;
+		}
+		writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG);
+
+	case JZ4740_MMC_STATE_SEND_STOP:
+		if (!req->stop)
+			break;
+
+		jz4740_mmc_send_command(host, req->stop);
+
+		timedout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_PRG_DONE);
+		if (timedout) {
+			host->state = JZ4740_MMC_STATE_DONE;
+			break;
+		}
+	case JZ4740_MMC_STATE_DONE:
+		break;
+	}
+
+	if (!timedout)
+		jz4740_mmc_request_done(host);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t jz_mmc_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+	uint16_t irq_reg, status, tmp;
+
+	irq_reg = readw(host->base + JZ_REG_MMC_IREG);
+
+	tmp = irq_reg;
+	irq_reg &= ~host->irq_mask;
+
+	tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ |
+		JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE);
+
+	if (tmp != irq_reg)
+		writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG);
+
+	if (irq_reg & JZ_MMC_IRQ_SDIO) {
+		writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG);
+		mmc_signal_sdio_irq(host->mmc);
+		irq_reg &= ~JZ_MMC_IRQ_SDIO;
+	}
+
+	if (host->req && host->cmd && irq_reg) {
+		if (test_and_clear_bit(0, &host->waiting)) {
+			del_timer(&host->timeout_timer);
+
+			status = readl(host->base + JZ_REG_MMC_STATUS);
+
+			if (status & JZ_MMC_STATUS_TIMEOUT_RES) {
+				host->cmd->error = -ETIMEDOUT;
+			} else if (status & JZ_MMC_STATUS_CRC_RES_ERR) {
+				host->cmd->error = -EIO;
+			} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+				    JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+				host->cmd->data->error = -EIO;
+			} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+					JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+				host->cmd->data->error = -EIO;
+			}
+
+			jz4740_mmc_set_irq_enabled(host, irq_reg, false);
+			writew(irq_reg, host->base + JZ_REG_MMC_IREG);
+
+			return IRQ_WAKE_THREAD;
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate)
+{
+	int div = 0;
+	int real_rate;
+
+	jz4740_mmc_clock_disable(host);
+	clk_set_rate(host->clk, JZ_MMC_CLK_RATE);
+
+	real_rate = clk_get_rate(host->clk);
+
+	while (real_rate > rate && div < 7) {
+		++div;
+		real_rate >>= 1;
+	}
+
+	writew(div, host->base + JZ_REG_MMC_CLKRT);
+	return real_rate;
+}
+
+static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+
+	host->req = req;
+
+	writew(0xffff, host->base + JZ_REG_MMC_IREG);
+
+	writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true);
+
+	host->state = JZ4740_MMC_STATE_READ_RESPONSE;
+	set_bit(0, &host->waiting);
+	mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+	jz4740_mmc_send_command(host, req->cmd);
+}
+
+static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (ios->clock)
+		jz4740_mmc_set_clock_rate(host, ios->clock);
+
+	switch (ios->power_mode) {
+	case MMC_POWER_UP:
+		jz4740_mmc_reset(host);
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					!host->pdata->power_active_low);
+		host->cmdat |= JZ_MMC_CMDAT_INIT;
+		clk_enable(host->clk);
+		break;
+	case MMC_POWER_ON:
+		break;
+	default:
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					host->pdata->power_active_low);
+		clk_disable(host->clk);
+		break;
+	}
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_1:
+		host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	case MMC_BUS_WIDTH_4:
+		host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	default:
+		break;
+	}
+}
+
+static int jz4740_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_read_only))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_read_only) ^
+		host->pdata->read_only_active_low;
+}
+
+static int jz4740_mmc_get_cd(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_card_detect))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_card_detect) ^
+			host->pdata->card_detect_active_low;
+}
+
+static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+
+	mmc_detect_change(host->mmc, HZ / 2);
+
+	return IRQ_HANDLED;
+}
+
+static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable);
+}
+
+static const struct mmc_host_ops jz4740_mmc_ops = {
+	.request	= jz4740_mmc_request,
+	.set_ios	= jz4740_mmc_set_ios,
+	.get_ro		= jz4740_mmc_get_ro,
+	.get_cd		= jz4740_mmc_get_cd,
+	.enable_sdio_irq = jz4740_mmc_enable_sdio_irq,
+};
+
+static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = {
+	JZ_GPIO_BULK_PIN(MSC_CMD),
+	JZ_GPIO_BULK_PIN(MSC_CLK),
+	JZ_GPIO_BULK_PIN(MSC_DATA0),
+	JZ_GPIO_BULK_PIN(MSC_DATA1),
+	JZ_GPIO_BULK_PIN(MSC_DATA2),
+	JZ_GPIO_BULK_PIN(MSC_DATA3),
+};
+
+static int __devinit jz4740_mmc_request_gpio(struct device *dev, int gpio,
+	const char *name, bool output, int value)
+{
+	int ret;
+
+	if (!gpio_is_valid(gpio))
+		return 0;
+
+	ret = gpio_request(gpio, name);
+	if (ret) {
+		dev_err(dev, "Failed to request %s gpio: %d\n", name, ret);
+		return ret;
+	}
+
+	if (output)
+		gpio_direction_output(gpio, value);
+	else
+		gpio_direction_input(gpio);
+
+	return 0;
+}
+
+static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return 0;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_card_detect,
+			"MMC detect change", false, 0);
+	if (ret)
+		goto err;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_read_only,
+			"MMC read only", false, 0);
+	if (ret)
+		goto err_free_gpio_card_detect;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_power,
+			"MMC read only", true, pdata->power_active_low);
+	if (ret)
+		goto err_free_gpio_read_only;
+
+	return 0;
+
+err_free_gpio_read_only:
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+err_free_gpio_card_detect:
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+err:
+	return ret;
+}
+
+static int __devinit jz4740_mmc_request_cd_irq(struct platform_device *pdev,
+	struct jz4740_mmc_host *host)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		return 0;
+
+	host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect);
+
+	if (host->card_detect_irq < 0) {
+		dev_warn(&pdev->dev, "Failed to get card detect irq\n");
+		return 0;
+	}
+	return request_irq(host->card_detect_irq, jz4740_mmc_card_detect_irq,
+			IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"MMC card detect", host);
+
+
+	return ret;
+}
+
+static void jz4740_mmc_free_gpios(struct platform_device *pdev)
+{
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return;
+
+	if (gpio_is_valid(pdata->gpio_power))
+		gpio_free(pdata->gpio_power);
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+}
+
+static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host)
+{
+	size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins);
+	if (host->pdata && host->pdata->data_1bit)
+		num_pins -= 3;
+
+	return num_pins;
+}
+
+static int __devinit jz4740_mmc_probe(struct platform_device* pdev)
+{
+	int ret;
+	struct mmc_host *mmc;
+	struct jz4740_mmc_host *host;
+	struct jz4740_mmc_platform_data *pdata;
+
+	pdata = pdev->dev.platform_data;
+
+	mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev);
+	if (!mmc) {
+		dev_err(&pdev->dev, "Failed to alloc mmc host structure\n");
+		return -ENOMEM;
+	}
+
+	host = mmc_priv(mmc);
+	host->pdata = pdata;
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq < 0) {
+		ret = host->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+		goto err_free_host;
+	}
+
+	host->clk = clk_get(&pdev->dev, "mmc");
+	if (!host->clk) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get mmc clock\n");
+		goto err_free_host;
+	}
+
+	host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!host->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get base platform memory\n");
+		goto err_clk_put;
+	}
+
+	host->mem = request_mem_region(host->mem->start,
+					resource_size(host->mem), pdev->name);
+	if (!host->mem) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to request base memory region\n");
+		goto err_clk_put;
+	}
+
+	host->base = ioremap_nocache(host->mem->start, resource_size(host->mem));
+	if (!host->base) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to ioremap base memory\n");
+		goto err_release_mem_region;
+	}
+
+	ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret);
+		goto err_iounmap;
+	}
+
+	ret = jz4740_mmc_request_gpios(pdev);
+	if (ret)
+		goto err_gpio_bulk_free;
+
+	mmc->ops = &jz4740_mmc_ops;
+	mmc->f_min = JZ_MMC_CLK_RATE / 128;
+	mmc->f_max = JZ_MMC_CLK_RATE;
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA;
+	mmc->caps |= MMC_CAP_SDIO_IRQ;
+
+	mmc->max_blk_size = (1 << 10) - 1;
+	mmc->max_blk_count = (1 << 15) - 1;
+	mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+	mmc->max_phys_segs = 128;
+	mmc->max_hw_segs = 128;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host->mmc = mmc;
+	host->pdev = pdev;
+	spin_lock_init(&host->lock);
+	host->irq_mask = 0xffff;
+
+	ret = jz4740_mmc_request_cd_irq(pdev, host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request card detect irq\n");
+		goto err_free_gpios;
+	}
+
+	ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0,
+			dev_name(&pdev->dev), host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+		goto err_free_card_detect_irq;
+	}
+
+	jz4740_mmc_reset(host);
+	jz4740_mmc_clock_disable(host);
+	setup_timer(&host->timeout_timer, jz4740_mmc_timeout,
+			(unsigned long)host);
+	/* It is not important when it times out, it just needs to timeout. */
+	set_timer_slack(&host->timeout_timer, HZ);
+
+	platform_set_drvdata(pdev, host);
+	ret = mmc_add_host(mmc);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret);
+		goto err_free_irq;
+	}
+	dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n");
+
+	return 0;
+
+err_free_irq:
+	free_irq(host->irq, host);
+err_free_card_detect_irq:
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+err_free_gpios:
+	jz4740_mmc_free_gpios(pdev);
+err_gpio_bulk_free:
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+err_iounmap:
+	iounmap(host->base);
+err_release_mem_region:
+	release_mem_region(host->mem->start, resource_size(host->mem));
+err_clk_put:
+	clk_put(host->clk);
+err_free_host:
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(mmc);
+
+	return ret;
+}
+
+static int __devexit jz4740_mmc_remove(struct platform_device *pdev)
+{
+	struct jz4740_mmc_host *host = platform_get_drvdata(pdev);
+
+	del_timer_sync(&host->timeout_timer);
+	jz4740_mmc_set_irq_enabled(host, 0xff, false);
+	jz4740_mmc_reset(host);
+
+	mmc_remove_host(host->mmc);
+
+	free_irq(host->irq, host);
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+
+	jz4740_mmc_free_gpios(pdev);
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	iounmap(host->base);
+	release_mem_region(host->mem->start, resource_size(host->mem));
+
+	clk_put(host->clk);
+
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(host->mmc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int jz4740_mmc_suspend(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	mmc_suspend_host(host->mmc);
+
+	jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	return 0;
+}
+
+static int jz4740_mmc_resume(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	mmc_resume_host(host->mmc);
+
+	return 0;
+}
+
+const struct dev_pm_ops jz4740_mmc_pm_ops = {
+	.suspend	= jz4740_mmc_suspend,
+	.resume		= jz4740_mmc_resume,
+	.poweroff	= jz4740_mmc_suspend,
+	.restore	= jz4740_mmc_resume,
+};
+
+#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops)
+#else
+#define JZ4740_MMC_PM_OPS NULL
+#endif
+
+static struct platform_driver jz4740_mmc_driver = {
+	.probe = jz4740_mmc_probe,
+	.remove = __devexit_p(jz4740_mmc_remove),
+	.driver = {
+		.name = "jz4740-mmc",
+		.owner = THIS_MODULE,
+		.pm = JZ4740_MMC_PM_OPS,
+	},
+};
+
+static int __init jz4740_mmc_init(void)
+{
+	return platform_driver_register(&jz4740_mmc_driver);
+}
+module_init(jz4740_mmc_init);
+
+static void __exit jz4740_mmc_exit(void)
+{
+	platform_driver_unregister(&jz4740_mmc_driver);
+}
+module_exit(jz4740_mmc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [PATCH v5] MMC: Add JZ4740 mmc driver
  2010-07-12 22:20       ` [PATCH v5] " Lars-Peter Clausen
@ 2010-07-12 22:45         ` Joe Perches
  2010-07-12 23:45           ` Lars-Peter Clausen
  2010-07-15 21:06         ` [PATCH v6] " Lars-Peter Clausen
  1 sibling, 1 reply; 163+ messages in thread
From: Joe Perches @ 2010-07-12 22:45 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton,
	Matt Fleming, linux-mmc

On Tue, 2010-07-13 at 00:20 +0200, Lars-Peter Clausen wrote:
> This patch adds support for the mmc controller on JZ4740 SoCs.
> +static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host,
> +	struct mmc_data *data)
> +{
> +	struct sg_mapping_iter *miter = &host->miter;
> +	uint32_t *buf;
> +	bool timeout;
> +	size_t i, j;
> +
> +	while (sg_miter_next(miter)) {
> +		buf = miter->addr;
> +		i = miter->length / 4;
> +		j = i / 8;
> +		i = i & 0x7;
> +		while (j) {
> +			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
> +			if (unlikely(timeout))
> +				goto poll_timeout;
> +
> +			writel(buf[0], host->base + JZ_REG_MMC_TXFIFO);

Perhaps it'd be better to use a temporary for
host->base + JZ_REG_MMC_TXFIFO

> +			writel(buf[1], host->base + JZ_REG_MMC_TXFIFO);
> +			writel(buf[2], host->base + JZ_REG_MMC_TXFIFO);
> +			writel(buf[3], host->base + JZ_REG_MMC_TXFIFO);
> +			writel(buf[4], host->base + JZ_REG_MMC_TXFIFO);
> +			writel(buf[5], host->base + JZ_REG_MMC_TXFIFO);
> +			writel(buf[6], host->base + JZ_REG_MMC_TXFIFO);
> +			writel(buf[7], host->base + JZ_REG_MMC_TXFIFO);
> +			buf += 8;
> +			--j;
> +		}
> +		if (unlikely(i)) {
> +			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
> +			if (unlikely(timeout))
> +				goto poll_timeout;
> +
> +			while (i) {
> +				writel(*buf, host->base + JZ_REG_MMC_TXFIFO);
> +				++buf;
> +				--i;
> +			}
> +		}
> +		data->bytes_xfered += miter->length;
> +	}



^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v5] MMC: Add JZ4740 mmc driver
  2010-07-12 22:45         ` Joe Perches
@ 2010-07-12 23:45           ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-12 23:45 UTC (permalink / raw)
  To: Joe Perches
  Cc: Ralf Baechle, linux-mips, linux-kernel, Andrew Morton,
	Matt Fleming, linux-mmc

Hi

Joe Perches wrote:
> On Tue, 2010-07-13 at 00:20 +0200, Lars-Peter Clausen wrote:
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>> +static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host,
>> +	struct mmc_data *data)
>> +{
>> +	struct sg_mapping_iter *miter = &host->miter;
>> +	uint32_t *buf;
>> +	bool timeout;
>> +	size_t i, j;
>> +
>> +	while (sg_miter_next(miter)) {
>> +		buf = miter->addr;
>> +		i = miter->length / 4;
>> +		j = i / 8;
>> +		i = i & 0x7;
>> +		while (j) {
>> +			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
>> +			if (unlikely(timeout))
>> +				goto poll_timeout;
>> +
>> +			writel(buf[0], host->base + JZ_REG_MMC_TXFIFO);
> 
> Perhaps it'd be better to use a temporary for
> host->base + JZ_REG_MMC_TXFIFO
Hm, indeed host->addr is reloaded before each write.
> 
>> +			writel(buf[1], host->base + JZ_REG_MMC_TXFIFO);
>> +			writel(buf[2], host->base + JZ_REG_MMC_TXFIFO);
>> +			writel(buf[3], host->base + JZ_REG_MMC_TXFIFO);
>> +			writel(buf[4], host->base + JZ_REG_MMC_TXFIFO);
>> +			writel(buf[5], host->base + JZ_REG_MMC_TXFIFO);
>> +			writel(buf[6], host->base + JZ_REG_MMC_TXFIFO);
>> +			writel(buf[7], host->base + JZ_REG_MMC_TXFIFO);
>> +			buf += 8;
>> +			--j;
>> +		}
>> +		if (unlikely(i)) {
>> +			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
>> +			if (unlikely(timeout))
>> +				goto poll_timeout;
>> +
>> +			while (i) {
>> +				writel(*buf, host->base + JZ_REG_MMC_TXFIFO);
>> +				++buf;
>> +				--i;
>> +			}
>> +		}
>> +		data->bytes_xfered += miter->length;
>> +	}
>  
> 

Thanks for reviewing
- Lars


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MFD: Add JZ4740 ADC driver
  2010-07-12  1:48   ` [PATCH v3] " Lars-Peter Clausen
@ 2010-07-14  9:19     ` Samuel Ortiz
  0 siblings, 0 replies; 163+ messages in thread
From: Samuel Ortiz @ 2010-07-14  9:19 UTC (permalink / raw)
  To: Lars-Peter Clausen; +Cc: Ralf Baechle, linux-mips, linux-kernel

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 <lars@metafoo.de>
> Cc: Samuel Ortiz <sameo@linux.intel.com>
Patch applied, many thanks.

Cheers,
Samuel.

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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v6] MMC: Add JZ4740 mmc driver
  2010-07-12 22:20       ` [PATCH v5] " Lars-Peter Clausen
  2010-07-12 22:45         ` Joe Perches
@ 2010-07-15 21:06         ` Lars-Peter Clausen
  2010-07-15 21:16           ` Andrew Morton
  1 sibling, 1 reply; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-15 21:06 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton,
	Matt Fleming, linux-mmc

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Acked-by: Matt Fleming <matt@console-pimps.org>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: Matt Fleming <matt@console-pimps.org>
Cc: linux-mmc@vger.kernel.org

---
Changes since v1
- Do not request IRQ with IRQF_DISABLED since it is a noop now
- Use a generous slack for the timeout timer. It does not need to be accurate.

Changes since v2
- Use sg_mapping_to iterate over sg elements in mmc read and write functions
- Use bitops instead of a spinlock and a variable for testing whether a request
  has been finished.
- Rework irq and timeout handling in order to get rid of locking in hot paths

Changes since v3
- Drastically decrease IRQ poll timeout. Now when the poll timeout is reached
  the IRQ which was polled is enabled. A variable keeps track of what state the
  driver is in and what has to be done next when the IRQ handler is entered
  again.
  By doing so busy looping is reduced, but overall performance can be maintained.
- Move header file from include/linux/mmc to arch/mips/include/asm/mach-jz4740

Changes since v4
- Rework Kconfig entry

Changes since v5
- Avoid reloading the fifo address before each read/write
---
 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h |   15 +
 drivers/mmc/host/Kconfig                       |    9 +
 drivers/mmc/host/Makefile                      |    1 +
 drivers/mmc/host/jz4740_mmc.c                  | 1033 ++++++++++++++++++++++++
 4 files changed, 1058 insertions(+), 0 deletions(-)
 create mode 100644 arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
 create mode 100644 drivers/mmc/host/jz4740_mmc.c

diff --git a/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
new file mode 100644
index 0000000..8543f43
--- /dev/null
+++ b/arch/mips/include/asm/mach-jz4740/jz4740_mmc.h
@@ -0,0 +1,15 @@
+#ifndef __LINUX_MMC_JZ4740_MMC
+#define __LINUX_MMC_JZ4740_MMC
+
+struct jz4740_mmc_platform_data {
+	int gpio_power;
+	int gpio_card_detect;
+	int gpio_read_only;
+	unsigned card_detect_active_low:1;
+	unsigned read_only_active_low:1;
+	unsigned power_active_low:1;
+
+	unsigned data_1bit:1;
+};
+
+#endif
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f06d06e..d25e22c 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -432,3 +432,12 @@ config MMC_SH_MMCIF
 	  This selects the MMC Host Interface controler (MMCIF).
 
 	  This driver supports MMCIF in sh7724/sh7757/sh7372.
+
+config MMC_JZ4740
+	tristate "JZ4740 SD/Multimedia Card Interface support"
+	depends on MACH_JZ4740
+	help
+	  This selects support for the SD/MMC controller on Ingenic JZ4740
+	  SoCs.
+	  If you have a board based on such a SoC and with a SD/MMC slot,
+	  say Y or M here.
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index e30c2ee..f4e53c9 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_MMC_CB710)	+= cb710-mmc.o
 obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc.o
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
+obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
 
 obj-$(CONFIG_MMC_SDHCI_OF)	+= sdhci-of.o
 sdhci-of-y				:= sdhci-of-core.o
diff --git a/drivers/mmc/host/jz4740_mmc.c b/drivers/mmc/host/jz4740_mmc.c
new file mode 100644
index 0000000..12efd9c
--- /dev/null
+++ b/drivers/mmc/host/jz4740_mmc.c
@@ -0,0 +1,1033 @@
+/*
+ *  Copyright (C) 2009-2010, Lars-Peter Clausen <lars@metafoo.de>
+ *  JZ4740 SD/MMC controller driver
+ *
+ *  This program is free software; you can redistribute  it and/or modify it
+ *  under  the terms of  the GNU General  Public License as published by the
+ *  Free Software Foundation;  either version 2 of the  License, or (at your
+ *  option) any later version.
+ *
+ *  You should have received a copy of the  GNU General Public License along
+ *  with this program; if not, write  to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/delay.h>
+#include <linux/scatterlist.h>
+#include <linux/clk.h>
+
+#include <linux/bitops.h>
+#include <linux/gpio.h>
+#include <asm/mach-jz4740/gpio.h>
+#include <asm/cacheflush.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/mach-jz4740/jz4740_mmc.h>
+
+#define JZ_REG_MMC_STRPCL	0x00
+#define JZ_REG_MMC_STATUS	0x04
+#define JZ_REG_MMC_CLKRT	0x08
+#define JZ_REG_MMC_CMDAT	0x0C
+#define JZ_REG_MMC_RESTO	0x10
+#define JZ_REG_MMC_RDTO		0x14
+#define JZ_REG_MMC_BLKLEN	0x18
+#define JZ_REG_MMC_NOB		0x1C
+#define JZ_REG_MMC_SNOB		0x20
+#define JZ_REG_MMC_IMASK	0x24
+#define JZ_REG_MMC_IREG		0x28
+#define JZ_REG_MMC_CMD		0x2C
+#define JZ_REG_MMC_ARG		0x30
+#define JZ_REG_MMC_RESP_FIFO	0x34
+#define JZ_REG_MMC_RXFIFO	0x38
+#define JZ_REG_MMC_TXFIFO	0x3C
+
+#define JZ_MMC_STRPCL_EXIT_MULTIPLE BIT(7)
+#define JZ_MMC_STRPCL_EXIT_TRANSFER BIT(6)
+#define JZ_MMC_STRPCL_START_READWAIT BIT(5)
+#define JZ_MMC_STRPCL_STOP_READWAIT BIT(4)
+#define JZ_MMC_STRPCL_RESET BIT(3)
+#define JZ_MMC_STRPCL_START_OP BIT(2)
+#define JZ_MMC_STRPCL_CLOCK_CONTROL (BIT(1) | BIT(0))
+#define JZ_MMC_STRPCL_CLOCK_STOP BIT(0)
+#define JZ_MMC_STRPCL_CLOCK_START BIT(1)
+
+
+#define JZ_MMC_STATUS_IS_RESETTING BIT(15)
+#define JZ_MMC_STATUS_SDIO_INT_ACTIVE BIT(14)
+#define JZ_MMC_STATUS_PRG_DONE BIT(13)
+#define JZ_MMC_STATUS_DATA_TRAN_DONE BIT(12)
+#define JZ_MMC_STATUS_END_CMD_RES BIT(11)
+#define JZ_MMC_STATUS_DATA_FIFO_AFULL BIT(10)
+#define JZ_MMC_STATUS_IS_READWAIT BIT(9)
+#define JZ_MMC_STATUS_CLK_EN BIT(8)
+#define JZ_MMC_STATUS_DATA_FIFO_FULL BIT(7)
+#define JZ_MMC_STATUS_DATA_FIFO_EMPTY BIT(6)
+#define JZ_MMC_STATUS_CRC_RES_ERR BIT(5)
+#define JZ_MMC_STATUS_CRC_READ_ERROR BIT(4)
+#define JZ_MMC_STATUS_TIMEOUT_WRITE BIT(3)
+#define JZ_MMC_STATUS_CRC_WRITE_ERROR BIT(2)
+#define JZ_MMC_STATUS_TIMEOUT_RES BIT(1)
+#define JZ_MMC_STATUS_TIMEOUT_READ BIT(0)
+
+#define JZ_MMC_STATUS_READ_ERROR_MASK (BIT(4) | BIT(0))
+#define JZ_MMC_STATUS_WRITE_ERROR_MASK (BIT(3) | BIT(2))
+
+
+#define JZ_MMC_CMDAT_IO_ABORT BIT(11)
+#define JZ_MMC_CMDAT_BUS_WIDTH_4BIT BIT(10)
+#define JZ_MMC_CMDAT_DMA_EN BIT(8)
+#define JZ_MMC_CMDAT_INIT BIT(7)
+#define JZ_MMC_CMDAT_BUSY BIT(6)
+#define JZ_MMC_CMDAT_STREAM BIT(5)
+#define JZ_MMC_CMDAT_WRITE BIT(4)
+#define JZ_MMC_CMDAT_DATA_EN BIT(3)
+#define JZ_MMC_CMDAT_RESPONSE_FORMAT (BIT(2) | BIT(1) | BIT(0))
+#define JZ_MMC_CMDAT_RSP_R1 1
+#define JZ_MMC_CMDAT_RSP_R2 2
+#define JZ_MMC_CMDAT_RSP_R3 3
+
+#define JZ_MMC_IRQ_SDIO BIT(7)
+#define JZ_MMC_IRQ_TXFIFO_WR_REQ BIT(6)
+#define JZ_MMC_IRQ_RXFIFO_RD_REQ BIT(5)
+#define JZ_MMC_IRQ_END_CMD_RES BIT(2)
+#define JZ_MMC_IRQ_PRG_DONE BIT(1)
+#define JZ_MMC_IRQ_DATA_TRAN_DONE BIT(0)
+
+
+#define JZ_MMC_CLK_RATE 24000000
+
+enum jz4740_mmc_state {
+	JZ4740_MMC_STATE_READ_RESPONSE,
+	JZ4740_MMC_STATE_TRANSFER_DATA,
+	JZ4740_MMC_STATE_SEND_STOP,
+	JZ4740_MMC_STATE_DONE,
+};
+
+struct jz4740_mmc_host {
+	struct mmc_host *mmc;
+	struct platform_device *pdev;
+	struct jz4740_mmc_platform_data *pdata;
+	struct clk *clk;
+
+	int irq;
+	int card_detect_irq;
+
+	struct resource *mem;
+	void __iomem *base;
+	struct mmc_request *req;
+	struct mmc_command *cmd;
+
+	unsigned long waiting;
+
+	uint32_t cmdat;
+
+	uint16_t irq_mask;
+
+	spinlock_t lock;
+
+	struct timer_list timeout_timer;
+	struct sg_mapping_iter miter;
+	enum jz4740_mmc_state state;
+};
+
+static void jz4740_mmc_set_irq_enabled(struct jz4740_mmc_host *host,
+	unsigned int irq, bool enabled)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	if (enabled)
+		host->irq_mask &= ~irq;
+	else
+		host->irq_mask |= irq;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	writew(host->irq_mask, host->base + JZ_REG_MMC_IMASK);
+}
+
+static void jz4740_mmc_clock_enable(struct jz4740_mmc_host *host,
+	bool start_transfer)
+{
+	uint16_t val = JZ_MMC_STRPCL_CLOCK_START;
+
+	if (start_transfer)
+		val |= JZ_MMC_STRPCL_START_OP;
+
+	writew(val, host->base + JZ_REG_MMC_STRPCL);
+}
+
+static void jz4740_mmc_clock_disable(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+	unsigned int timeout = 1000;
+
+	writew(JZ_MMC_STRPCL_CLOCK_STOP, host->base + JZ_REG_MMC_STRPCL);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_CLK_EN && --timeout);
+}
+
+static void jz4740_mmc_reset(struct jz4740_mmc_host *host)
+{
+	uint32_t status;
+	unsigned int timeout = 1000;
+
+	writew(JZ_MMC_STRPCL_RESET, host->base + JZ_REG_MMC_STRPCL);
+	udelay(10);
+	do {
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	} while (status & JZ_MMC_STATUS_IS_RESETTING && --timeout);
+}
+
+static void jz4740_mmc_request_done(struct jz4740_mmc_host *host)
+{
+	struct mmc_request *req;
+
+	req = host->req;
+	host->req = NULL;
+
+	mmc_request_done(host->mmc, req);
+}
+
+static unsigned int jz4740_mmc_poll_irq(struct jz4740_mmc_host *host,
+	unsigned int irq)
+{
+	unsigned int timeout = 0x800;
+	uint16_t status;
+
+	do {
+		status = readw(host->base + JZ_REG_MMC_IREG);
+	} while (!(status & irq) && --timeout);
+
+	if (timeout == 0) {
+		set_bit(0, &host->waiting);
+		mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+		jz4740_mmc_set_irq_enabled(host, irq, true);
+		return true;
+	}
+
+	return false;
+}
+
+static void jz4740_mmc_transfer_check_state(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	int status;
+
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	if (status & JZ_MMC_STATUS_WRITE_ERROR_MASK) {
+		if (status & (JZ_MMC_STATUS_TIMEOUT_WRITE)) {
+			host->req->cmd->error = -ETIMEDOUT;
+			data->error = -ETIMEDOUT;
+		} else {
+			host->req->cmd->error = -EIO;
+			data->error = -EIO;
+		}
+	}
+}
+
+static bool jz4740_mmc_write_data(struct jz4740_mmc_host *host,
+	struct mmc_data *data)
+{
+	struct sg_mapping_iter *miter = &host->miter;
+	void __iomem *fifo_addr = host->base + JZ_REG_MMC_TXFIFO;
+	uint32_t *buf;
+	bool timeout;
+	size_t i, j;
+
+	while (sg_miter_next(miter)) {
+		buf = miter->addr;
+		i = miter->length / 4;
+		j = i / 8;
+		i = i & 0x7;
+		while (j) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			writel(buf[0], fifo_addr);
+			writel(buf[1], fifo_addr);
+			writel(buf[2], fifo_addr);
+			writel(buf[3], fifo_addr);
+			writel(buf[4], fifo_addr);
+			writel(buf[5], fifo_addr);
+			writel(buf[6], fifo_addr);
+			writel(buf[7], fifo_addr);
+			buf += 8;
+			--j;
+		}
+		if (unlikely(i)) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_TXFIFO_WR_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			while (i) {
+				writel(*buf, fifo_addr);
+				++buf;
+				--i;
+			}
+		}
+		data->bytes_xfered += miter->length;
+	}
+	sg_miter_stop(miter);
+
+	return false;
+
+poll_timeout:
+	miter->consumed = (void *)buf - miter->addr;
+	data->bytes_xfered += miter->consumed;
+	sg_miter_stop(miter);
+
+	return true;
+}
+
+static bool jz4740_mmc_read_data(struct jz4740_mmc_host *host,
+				struct mmc_data *data)
+{
+	struct sg_mapping_iter *miter = &host->miter;
+	void __iomem *fifo_addr = host->base + JZ_REG_MMC_RXFIFO;
+	uint32_t *buf;
+	uint32_t d;
+	uint16_t status;
+	size_t i, j;
+	unsigned int timeout;
+
+	while (sg_miter_next(miter)) {
+		buf = miter->addr;
+		i = miter->length;
+		j = i / 32;
+		i = i & 0x1f;
+		while (j) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			buf[0] = readl(fifo_addr);
+			buf[1] = readl(fifo_addr);
+			buf[2] = readl(fifo_addr);
+			buf[3] = readl(fifo_addr);
+			buf[4] = readl(fifo_addr);
+			buf[5] = readl(fifo_addr);
+			buf[6] = readl(fifo_addr);
+			buf[7] = readl(fifo_addr);
+
+			buf += 8;
+			--j;
+		}
+
+		if (unlikely(i)) {
+			timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_RXFIFO_RD_REQ);
+			if (unlikely(timeout))
+				goto poll_timeout;
+
+			while (i >= 4) {
+				*buf++ = readl(fifo_addr);
+				i -= 4;
+			}
+			if (unlikely(i > 0)) {
+				d = readl(fifo_addr);
+				memcpy(buf, &d, i);
+			}
+		}
+		data->bytes_xfered += miter->length;
+
+		/* This can go away once MIPS implements
+		 * flush_kernel_dcache_page */
+		flush_dcache_page(miter->page);
+	}
+	sg_miter_stop(miter);
+
+	/* For whatever reason there is sometime one word more in the fifo then
+	 * requested */
+	timeout = 1000;
+	status = readl(host->base + JZ_REG_MMC_STATUS);
+	while (!(status & JZ_MMC_STATUS_DATA_FIFO_EMPTY) && --timeout) {
+		d = readl(fifo_addr);
+		status = readl(host->base + JZ_REG_MMC_STATUS);
+	}
+
+	return false;
+
+poll_timeout:
+	miter->consumed = (void *)buf - miter->addr;
+	data->bytes_xfered += miter->consumed;
+	sg_miter_stop(miter);
+
+	return true;
+}
+
+static void jz4740_mmc_timeout(unsigned long data)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)data;
+
+	if (!test_and_clear_bit(0, &host->waiting))
+		return;
+
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, false);
+
+	host->req->cmd->error = -ETIMEDOUT;
+	jz4740_mmc_request_done(host);
+}
+
+static void jz4740_mmc_read_response(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	int i;
+	uint16_t tmp;
+	void __iomem *fifo_addr = host->base + JZ_REG_MMC_RESP_FIFO;
+
+	if (cmd->flags & MMC_RSP_136) {
+		tmp = readw(fifo_addr);
+		for (i = 0; i < 4; ++i) {
+			cmd->resp[i] = tmp << 24;
+			tmp = readw(fifo_addr);
+			cmd->resp[i] |= tmp << 8;
+			tmp = readw(fifo_addr);
+			cmd->resp[i] |= tmp >> 8;
+		}
+	} else {
+		cmd->resp[0] = readw(fifo_addr) << 24;
+		cmd->resp[0] |= readw(fifo_addr) << 8;
+		cmd->resp[0] |= readw(fifo_addr) & 0xff;
+	}
+}
+
+static void jz4740_mmc_send_command(struct jz4740_mmc_host *host,
+	struct mmc_command *cmd)
+{
+	uint32_t cmdat = host->cmdat;
+
+	host->cmdat &= ~JZ_MMC_CMDAT_INIT;
+	jz4740_mmc_clock_disable(host);
+
+	host->cmd = cmd;
+
+	if (cmd->flags & MMC_RSP_BUSY)
+		cmdat |= JZ_MMC_CMDAT_BUSY;
+
+	switch (mmc_resp_type(cmd)) {
+	case MMC_RSP_R1B:
+	case MMC_RSP_R1:
+		cmdat |= JZ_MMC_CMDAT_RSP_R1;
+		break;
+	case MMC_RSP_R2:
+		cmdat |= JZ_MMC_CMDAT_RSP_R2;
+		break;
+	case MMC_RSP_R3:
+		cmdat |= JZ_MMC_CMDAT_RSP_R3;
+		break;
+	default:
+		break;
+	}
+
+	if (cmd->data) {
+		cmdat |= JZ_MMC_CMDAT_DATA_EN;
+		if (cmd->data->flags & MMC_DATA_WRITE)
+			cmdat |= JZ_MMC_CMDAT_WRITE;
+		if (cmd->data->flags & MMC_DATA_STREAM)
+			cmdat |= JZ_MMC_CMDAT_STREAM;
+
+		writew(cmd->data->blksz, host->base + JZ_REG_MMC_BLKLEN);
+		writew(cmd->data->blocks, host->base + JZ_REG_MMC_NOB);
+	}
+
+	writeb(cmd->opcode, host->base + JZ_REG_MMC_CMD);
+	writel(cmd->arg, host->base + JZ_REG_MMC_ARG);
+	writel(cmdat, host->base + JZ_REG_MMC_CMDAT);
+
+	jz4740_mmc_clock_enable(host, 1);
+}
+
+static void jz_mmc_prepare_data_transfer(struct jz4740_mmc_host *host)
+{
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_data *data = cmd->data;
+	int direction;
+
+	if (data->flags & MMC_DATA_READ)
+		direction = SG_MITER_TO_SG;
+	else
+		direction = SG_MITER_FROM_SG;
+
+	sg_miter_start(&host->miter, data->sg, data->sg_len, direction);
+}
+
+
+static irqreturn_t jz_mmc_irq_worker(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = (struct jz4740_mmc_host *)devid;
+	struct mmc_command *cmd = host->req->cmd;
+	struct mmc_request *req = host->req;
+	bool timeout = false;
+
+	if (cmd->error)
+		host->state = JZ4740_MMC_STATE_DONE;
+
+	switch (host->state) {
+	case JZ4740_MMC_STATE_READ_RESPONSE:
+		if (cmd->flags & MMC_RSP_PRESENT)
+			jz4740_mmc_read_response(host, cmd);
+
+		if (!cmd->data)
+			break;
+
+		jz_mmc_prepare_data_transfer(host);
+
+	case JZ4740_MMC_STATE_TRANSFER_DATA:
+		if (cmd->data->flags & MMC_DATA_READ)
+			timeout = jz4740_mmc_read_data(host, cmd->data);
+		else
+			timeout = jz4740_mmc_write_data(host, cmd->data);
+
+		if (unlikely(timeout)) {
+			host->state = JZ4740_MMC_STATE_TRANSFER_DATA;
+			break;
+		}
+
+		jz4740_mmc_transfer_check_state(host, cmd->data);
+
+		timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_DATA_TRAN_DONE);
+		if (unlikely(timeout)) {
+			host->state = JZ4740_MMC_STATE_SEND_STOP;
+			break;
+		}
+		writew(JZ_MMC_IRQ_DATA_TRAN_DONE, host->base + JZ_REG_MMC_IREG);
+
+	case JZ4740_MMC_STATE_SEND_STOP:
+		if (!req->stop)
+			break;
+
+		jz4740_mmc_send_command(host, req->stop);
+
+		timeout = jz4740_mmc_poll_irq(host, JZ_MMC_IRQ_PRG_DONE);
+		if (timeout) {
+			host->state = JZ4740_MMC_STATE_DONE;
+			break;
+		}
+	case JZ4740_MMC_STATE_DONE:
+		break;
+	}
+
+	if (!timeout)
+		jz4740_mmc_request_done(host);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t jz_mmc_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+	struct mmc_command *cmd = host->cmd;
+	uint16_t irq_reg, status, tmp;
+
+	irq_reg = readw(host->base + JZ_REG_MMC_IREG);
+
+	tmp = irq_reg;
+	irq_reg &= ~host->irq_mask;
+
+	tmp &= ~(JZ_MMC_IRQ_TXFIFO_WR_REQ | JZ_MMC_IRQ_RXFIFO_RD_REQ |
+		JZ_MMC_IRQ_PRG_DONE | JZ_MMC_IRQ_DATA_TRAN_DONE);
+
+	if (tmp != irq_reg)
+		writew(tmp & ~irq_reg, host->base + JZ_REG_MMC_IREG);
+
+	if (irq_reg & JZ_MMC_IRQ_SDIO) {
+		writew(JZ_MMC_IRQ_SDIO, host->base + JZ_REG_MMC_IREG);
+		mmc_signal_sdio_irq(host->mmc);
+		irq_reg &= ~JZ_MMC_IRQ_SDIO;
+	}
+
+	if (host->req && cmd && irq_reg) {
+		if (test_and_clear_bit(0, &host->waiting)) {
+			del_timer(&host->timeout_timer);
+
+			status = readl(host->base + JZ_REG_MMC_STATUS);
+
+			if (status & JZ_MMC_STATUS_TIMEOUT_RES) {
+					cmd->error = -ETIMEDOUT;
+			} else if (status & JZ_MMC_STATUS_CRC_RES_ERR) {
+					cmd->error = -EIO;
+			} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+				    JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+					if (cmd->data)
+							cmd->data->error = -EIO;
+					cmd->error = -EIO;
+			} else if (status & (JZ_MMC_STATUS_CRC_READ_ERROR |
+					JZ_MMC_STATUS_CRC_WRITE_ERROR)) {
+					if (cmd->data)
+							cmd->data->error = -EIO;
+					cmd->error = -EIO;
+			}
+
+			jz4740_mmc_set_irq_enabled(host, irq_reg, false);
+			writew(irq_reg, host->base + JZ_REG_MMC_IREG);
+
+			return IRQ_WAKE_THREAD;
+		}
+	}
+
+	return IRQ_HANDLED;
+}
+
+static int jz4740_mmc_set_clock_rate(struct jz4740_mmc_host *host, int rate)
+{
+	int div = 0;
+	int real_rate;
+
+	jz4740_mmc_clock_disable(host);
+	clk_set_rate(host->clk, JZ_MMC_CLK_RATE);
+
+	real_rate = clk_get_rate(host->clk);
+
+	while (real_rate > rate && div < 7) {
+		++div;
+		real_rate >>= 1;
+	}
+
+	writew(div, host->base + JZ_REG_MMC_CLKRT);
+	return real_rate;
+}
+
+static void jz4740_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+
+	host->req = req;
+
+	writew(0xffff, host->base + JZ_REG_MMC_IREG);
+
+	writew(JZ_MMC_IRQ_END_CMD_RES, host->base + JZ_REG_MMC_IREG);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_END_CMD_RES, true);
+
+	host->state = JZ4740_MMC_STATE_READ_RESPONSE;
+	set_bit(0, &host->waiting);
+	mod_timer(&host->timeout_timer, jiffies + 5*HZ);
+	jz4740_mmc_send_command(host, req->cmd);
+}
+
+static void jz4740_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (ios->clock)
+		jz4740_mmc_set_clock_rate(host, ios->clock);
+
+	switch (ios->power_mode) {
+	case MMC_POWER_UP:
+		jz4740_mmc_reset(host);
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					!host->pdata->power_active_low);
+		host->cmdat |= JZ_MMC_CMDAT_INIT;
+		clk_enable(host->clk);
+		break;
+	case MMC_POWER_ON:
+		break;
+	default:
+		if (gpio_is_valid(host->pdata->gpio_power))
+			gpio_set_value(host->pdata->gpio_power,
+					host->pdata->power_active_low);
+		clk_disable(host->clk);
+		break;
+	}
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_1:
+		host->cmdat &= ~JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	case MMC_BUS_WIDTH_4:
+		host->cmdat |= JZ_MMC_CMDAT_BUS_WIDTH_4BIT;
+		break;
+	default:
+		break;
+	}
+}
+
+static int jz4740_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_read_only))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_read_only) ^
+		host->pdata->read_only_active_low;
+}
+
+static int jz4740_mmc_get_cd(struct mmc_host *mmc)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	if (!gpio_is_valid(host->pdata->gpio_card_detect))
+		return -ENOSYS;
+
+	return gpio_get_value(host->pdata->gpio_card_detect) ^
+			host->pdata->card_detect_active_low;
+}
+
+static irqreturn_t jz4740_mmc_card_detect_irq(int irq, void *devid)
+{
+	struct jz4740_mmc_host *host = devid;
+
+	mmc_detect_change(host->mmc, HZ / 2);
+
+	return IRQ_HANDLED;
+}
+
+static void jz4740_mmc_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{
+	struct jz4740_mmc_host *host = mmc_priv(mmc);
+	jz4740_mmc_set_irq_enabled(host, JZ_MMC_IRQ_SDIO, enable);
+}
+
+static const struct mmc_host_ops jz4740_mmc_ops = {
+	.request	= jz4740_mmc_request,
+	.set_ios	= jz4740_mmc_set_ios,
+	.get_ro		= jz4740_mmc_get_ro,
+	.get_cd		= jz4740_mmc_get_cd,
+	.enable_sdio_irq = jz4740_mmc_enable_sdio_irq,
+};
+
+static const struct jz_gpio_bulk_request jz4740_mmc_pins[] = {
+	JZ_GPIO_BULK_PIN(MSC_CMD),
+	JZ_GPIO_BULK_PIN(MSC_CLK),
+	JZ_GPIO_BULK_PIN(MSC_DATA0),
+	JZ_GPIO_BULK_PIN(MSC_DATA1),
+	JZ_GPIO_BULK_PIN(MSC_DATA2),
+	JZ_GPIO_BULK_PIN(MSC_DATA3),
+};
+
+static int __devinit jz4740_mmc_request_gpio(struct device *dev, int gpio,
+	const char *name, bool output, int value)
+{
+	int ret;
+
+	if (!gpio_is_valid(gpio))
+		return 0;
+
+	ret = gpio_request(gpio, name);
+	if (ret) {
+		dev_err(dev, "Failed to request %s gpio: %d\n", name, ret);
+		return ret;
+	}
+
+	if (output)
+		gpio_direction_output(gpio, value);
+	else
+		gpio_direction_input(gpio);
+
+	return 0;
+}
+
+static int __devinit jz4740_mmc_request_gpios(struct platform_device *pdev)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return 0;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_card_detect,
+			"MMC detect change", false, 0);
+	if (ret)
+		goto err;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_read_only,
+			"MMC read only", false, 0);
+	if (ret)
+		goto err_free_gpio_card_detect;
+
+	ret = jz4740_mmc_request_gpio(&pdev->dev, pdata->gpio_power,
+			"MMC read only", true, pdata->power_active_low);
+	if (ret)
+		goto err_free_gpio_read_only;
+
+	return 0;
+
+err_free_gpio_read_only:
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+err_free_gpio_card_detect:
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+err:
+	return ret;
+}
+
+static int __devinit jz4740_mmc_request_cd_irq(struct platform_device *pdev,
+	struct jz4740_mmc_host *host)
+{
+	int ret;
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		return 0;
+
+	host->card_detect_irq = gpio_to_irq(pdata->gpio_card_detect);
+
+	if (host->card_detect_irq < 0) {
+		dev_warn(&pdev->dev, "Failed to get card detect irq\n");
+		return 0;
+	}
+	return request_irq(host->card_detect_irq, jz4740_mmc_card_detect_irq,
+			IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+			"MMC card detect", host);
+
+
+	return ret;
+}
+
+static void jz4740_mmc_free_gpios(struct platform_device *pdev)
+{
+	struct jz4740_mmc_platform_data *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return;
+
+	if (gpio_is_valid(pdata->gpio_power))
+		gpio_free(pdata->gpio_power);
+	if (gpio_is_valid(pdata->gpio_read_only))
+		gpio_free(pdata->gpio_read_only);
+	if (gpio_is_valid(pdata->gpio_card_detect))
+		gpio_free(pdata->gpio_card_detect);
+}
+
+static inline size_t jz4740_mmc_num_pins(struct jz4740_mmc_host *host)
+{
+	size_t num_pins = ARRAY_SIZE(jz4740_mmc_pins);
+	if (host->pdata && host->pdata->data_1bit)
+		num_pins -= 3;
+
+	return num_pins;
+}
+
+static int __devinit jz4740_mmc_probe(struct platform_device* pdev)
+{
+	int ret;
+	struct mmc_host *mmc;
+	struct jz4740_mmc_host *host;
+	struct jz4740_mmc_platform_data *pdata;
+
+	pdata = pdev->dev.platform_data;
+
+	mmc = mmc_alloc_host(sizeof(struct jz4740_mmc_host), &pdev->dev);
+	if (!mmc) {
+		dev_err(&pdev->dev, "Failed to alloc mmc host structure\n");
+		return -ENOMEM;
+	}
+
+	host = mmc_priv(mmc);
+	host->pdata = pdata;
+
+	host->irq = platform_get_irq(pdev, 0);
+	if (host->irq < 0) {
+		ret = host->irq;
+		dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+		goto err_free_host;
+	}
+
+	host->clk = clk_get(&pdev->dev, "mmc");
+	if (!host->clk) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get mmc clock\n");
+		goto err_free_host;
+	}
+
+	host->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!host->mem) {
+		ret = -ENOENT;
+		dev_err(&pdev->dev, "Failed to get base platform memory\n");
+		goto err_clk_put;
+	}
+
+	host->mem = request_mem_region(host->mem->start,
+					resource_size(host->mem), pdev->name);
+	if (!host->mem) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to request base memory region\n");
+		goto err_clk_put;
+	}
+
+	host->base = ioremap_nocache(host->mem->start, resource_size(host->mem));
+	if (!host->base) {
+		ret = -EBUSY;
+		dev_err(&pdev->dev, "Failed to ioremap base memory\n");
+		goto err_release_mem_region;
+	}
+
+	ret = jz_gpio_bulk_request(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request mmc pins: %d\n", ret);
+		goto err_iounmap;
+	}
+
+	ret = jz4740_mmc_request_gpios(pdev);
+	if (ret)
+		goto err_gpio_bulk_free;
+
+	mmc->ops = &jz4740_mmc_ops;
+	mmc->f_min = JZ_MMC_CLK_RATE / 128;
+	mmc->f_max = JZ_MMC_CLK_RATE;
+	mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+	mmc->caps = (pdata && pdata->data_1bit) ? 0 : MMC_CAP_4_BIT_DATA;
+	mmc->caps |= MMC_CAP_SDIO_IRQ;
+
+	mmc->max_blk_size = (1 << 10) - 1;
+	mmc->max_blk_count = (1 << 15) - 1;
+	mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+
+	mmc->max_phys_segs = 128;
+	mmc->max_hw_segs = 128;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	host->mmc = mmc;
+	host->pdev = pdev;
+	spin_lock_init(&host->lock);
+	host->irq_mask = 0xffff;
+
+	ret = jz4740_mmc_request_cd_irq(pdev, host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request card detect irq\n");
+		goto err_free_gpios;
+	}
+
+	ret = request_threaded_irq(host->irq, jz_mmc_irq, jz_mmc_irq_worker, 0,
+			dev_name(&pdev->dev), host);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to request irq: %d\n", ret);
+		goto err_free_card_detect_irq;
+	}
+
+	jz4740_mmc_reset(host);
+	jz4740_mmc_clock_disable(host);
+	setup_timer(&host->timeout_timer, jz4740_mmc_timeout,
+			(unsigned long)host);
+	/* It is not important when it times out, it just needs to timeout. */
+	set_timer_slack(&host->timeout_timer, HZ);
+
+	platform_set_drvdata(pdev, host);
+	ret = mmc_add_host(mmc);
+
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to add mmc host: %d\n", ret);
+		goto err_free_irq;
+	}
+	dev_info(&pdev->dev, "JZ SD/MMC card driver registered\n");
+
+	return 0;
+
+err_free_irq:
+	free_irq(host->irq, host);
+err_free_card_detect_irq:
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+err_free_gpios:
+	jz4740_mmc_free_gpios(pdev);
+err_gpio_bulk_free:
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+err_iounmap:
+	iounmap(host->base);
+err_release_mem_region:
+	release_mem_region(host->mem->start, resource_size(host->mem));
+err_clk_put:
+	clk_put(host->clk);
+err_free_host:
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(mmc);
+
+	return ret;
+}
+
+static int __devexit jz4740_mmc_remove(struct platform_device *pdev)
+{
+	struct jz4740_mmc_host *host = platform_get_drvdata(pdev);
+
+	del_timer_sync(&host->timeout_timer);
+	jz4740_mmc_set_irq_enabled(host, 0xff, false);
+	jz4740_mmc_reset(host);
+
+	mmc_remove_host(host->mmc);
+
+	free_irq(host->irq, host);
+	if (host->card_detect_irq >= 0)
+		free_irq(host->card_detect_irq, host);
+
+	jz4740_mmc_free_gpios(pdev);
+	jz_gpio_bulk_free(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	iounmap(host->base);
+	release_mem_region(host->mem->start, resource_size(host->mem));
+
+	clk_put(host->clk);
+
+	platform_set_drvdata(pdev, NULL);
+	mmc_free_host(host->mmc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int jz4740_mmc_suspend(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	mmc_suspend_host(host->mmc);
+
+	jz_gpio_bulk_suspend(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	return 0;
+}
+
+static int jz4740_mmc_resume(struct device *dev)
+{
+	struct jz4740_mmc_host *host = dev_get_drvdata(dev);
+
+	jz_gpio_bulk_resume(jz4740_mmc_pins, jz4740_mmc_num_pins(host));
+
+	mmc_resume_host(host->mmc);
+
+	return 0;
+}
+
+const struct dev_pm_ops jz4740_mmc_pm_ops = {
+	.suspend	= jz4740_mmc_suspend,
+	.resume		= jz4740_mmc_resume,
+	.poweroff	= jz4740_mmc_suspend,
+	.restore	= jz4740_mmc_resume,
+};
+
+#define JZ4740_MMC_PM_OPS (&jz4740_mmc_pm_ops)
+#else
+#define JZ4740_MMC_PM_OPS NULL
+#endif
+
+static struct platform_driver jz4740_mmc_driver = {
+	.probe = jz4740_mmc_probe,
+	.remove = __devexit_p(jz4740_mmc_remove),
+	.driver = {
+		.name = "jz4740-mmc",
+		.owner = THIS_MODULE,
+		.pm = JZ4740_MMC_PM_OPS,
+	},
+};
+
+static int __init jz4740_mmc_init(void)
+{
+	return platform_driver_register(&jz4740_mmc_driver);
+}
+module_init(jz4740_mmc_init);
+
+static void __exit jz4740_mmc_exit(void)
+{
+	platform_driver_unregister(&jz4740_mmc_driver);
+}
+module_exit(jz4740_mmc_exit);
+
+MODULE_DESCRIPTION("JZ4740 SD/MMC controller driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [PATCH v6] MMC: Add JZ4740 mmc driver
  2010-07-15 21:06         ` [PATCH v6] " Lars-Peter Clausen
@ 2010-07-15 21:16           ` Andrew Morton
  2010-07-15 21:37             ` Lars-Peter Clausen
  0 siblings, 1 reply; 163+ messages in thread
From: Andrew Morton @ 2010-07-15 21:16 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc

On Thu, 15 Jul 2010 23:06:04 +0200
Lars-Peter Clausen <lars@metafoo.de> wrote:

> This patch adds support for the mmc controller on JZ4740 SoCs.
> 
>
> ...
>
> +		if (gpio_is_valid(host->pdata->gpio_power))
> +			gpio_set_value(host->pdata->gpio_power,
> +					!host->pdata->power_active_low);
>
> ...
>

Should this driver have a `depends on GPIOLIB' in Kconfig?

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v6] MMC: Add JZ4740 mmc driver
  2010-07-15 21:16           ` Andrew Morton
@ 2010-07-15 21:37             ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-15 21:37 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Ralf Baechle, linux-mips, linux-kernel, Matt Fleming, linux-mmc

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

Andrew Morton wrote:
> On Thu, 15 Jul 2010 23:06:04 +0200
> Lars-Peter Clausen <lars@metafoo.de> wrote:
> 
>> This patch adds support for the mmc controller on JZ4740 SoCs.
>>
>>
>> ...
>>
>> +		if (gpio_is_valid(host->pdata->gpio_power))
>> +			gpio_set_value(host->pdata->gpio_power,
>> +					!host->pdata->power_active_low);
>>
>> ...
>>
> 
> Should this driver have a `depends on GPIOLIB' in Kconfig?
> 

The driver depends on MACH_JZ4740 which selects ARCH_REQUIRE_GPIOLIB, so there
already is an implicit depends on GPIOLIB.

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

iEUEARECAAYFAkw/f60ACgkQBX4mSR26RiNexQCfUXt0cqiMqEf17k+z+q6XVwRO
ImEAmPKxeyX9ANVasUNL60f51GxKofg=
=3NrE
-----END PGP SIGNATURE-----

^ permalink raw reply	[flat|nested] 163+ messages in thread

* [PATCH v3] MIPS: jz4740: Add IRQ handler code
  2010-06-19  5:08 ` [PATCH v2 02/26] MIPS: jz4740: Add IRQ handler code Lars-Peter Clausen
@ 2010-07-17 12:08   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:08 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

--
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v4] MIPS: JZ4740: Add clock API support.
  2010-06-28  1:24   ` [PATCH v3 " Lars-Peter Clausen
@ 2010-07-17 12:10     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:10 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] MIPS: JZ4740: Add gpio support
  2010-06-19  5:08 ` [PATCH v2 08/26] MIPS: JZ4740: Add gpio support Lars-Peter Clausen
@ 2010-07-17 12:11   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:11 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

This patch adds gpiolib support for JZ4740 SoCs.

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v4] MIPS: JZ4740: Add PWM support
  2010-06-28  1:23   ` [PATCH v3 " Lars-Peter Clausen
@ 2010-07-17 12:12     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:12 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] MIPS: JZ4740: Add platform devices
  2010-06-19  5:08 ` [PATCH v2 13/26] MIPS: JZ4740: Add platform devices Lars-Peter Clausen
@ 2010-07-17 12:13   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:13 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] fbdev: Add JZ4740 framebuffer driver
  2010-06-19  5:08   ` Lars-Peter Clausen
@ 2010-07-17 12:14     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:14 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-fbdev

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-fbdev@vger.kernel.org

--
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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>
+ *	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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver");
+MODULE_ALIAS("platform:jz4740-fb");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] fbdev: Add JZ4740 framebuffer driver
@ 2010-07-17 12:14     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:14 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, Andrew Morton, linux-fbdev

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: Andrew Morton <akpm@linux-foundation.org>
Cc: linux-fbdev@vger.kernel.org

--
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 <lars@metafoo.de>
+ *
+ *  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 <lars@metafoo.de>
+ *	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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("JZ4740 SoC LCD framebuffer driver");
+MODULE_ALIAS("platform:jz4740-fb");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] MTD: Nand: Add JZ4740 NAND driver
  2010-06-19  5:08   ` Lars-Peter Clausen
@ 2010-07-17 12:15     ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:15 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, linux-kernel, Lars-Peter Clausen, David Woodhouse, linux-mtd

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: linux-mtd@lists.infradead.org

--
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC");
+MODULE_ALIAS("platform:jz4740-nand");
-- 
1.5.6.5


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-17 12:15     ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:15 UTC (permalink / raw)
  To: Ralf Baechle
  Cc: linux-mips, Lars-Peter Clausen, David Woodhouse, linux-kernel, linux-mtd

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>
Cc: David Woodhouse <dwmw2@infradead.org>
Cc: linux-mtd@lists.infradead.org

--
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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>
+ *  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 <lars@metafoo.de>");
+MODULE_DESCRIPTION("NAND controller driver for JZ4740 SoC");
+MODULE_ALIAS("platform:jz4740-nand");
-- 
1.5.6.5

^ permalink raw reply related	[flat|nested] 163+ messages in thread

* [PATCH v3] MIPS: JZ4740: Add qi_lb60 board support
  2010-06-19  5:08 ` [PATCH v2 25/26] MIPS: JZ4740: Add qi_lb60 board support Lars-Peter Clausen
@ 2010-07-17 12:16   ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-17 12:16 UTC (permalink / raw)
  To: Ralf Baechle; +Cc: linux-mips, linux-kernel, Lars-Peter Clausen

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

Signed-off-by: Lars-Peter Clausen <lars@metafoo.de>

---
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 <xiangfu@qi-hardware.com>
+ * Copyright 2010, Lars-Petrer Clausen <lars@metafoo.de>
+ *
+ * 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


^ permalink raw reply related	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MTD: Nand: Add JZ4740 NAND driver
  2010-07-17 12:15     ` Lars-Peter Clausen
@ 2010-07-18 16:54       ` Artem Bityutskiy
  -1 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-18 16:54 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: Ralf Baechle, linux-mips, linux-kernel, David Woodhouse, linux-mtd

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 <lars@metafoo.de>
> Cc: David Woodhouse <dwmw2@infradead.org>
> Cc: linux-mtd@lists.infradead.org
> 

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 (Артём Битюцкий)


^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-18 16:54       ` Artem Bityutskiy
  0 siblings, 0 replies; 163+ messages in thread
From: Artem Bityutskiy @ 2010-07-18 16:54 UTC (permalink / raw)
  To: Lars-Peter Clausen
  Cc: linux-mips, linux-mtd, David Woodhouse, linux-kernel, Ralf Baechle

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 <lars@metafoo.de>
> Cc: David Woodhouse <dwmw2@infradead.org>
> Cc: linux-mtd@lists.infradead.org
> 

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 (Артём Битюцкий)

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MTD: Nand: Add JZ4740 NAND driver
  2010-07-18 16:54       ` Artem Bityutskiy
@ 2010-07-18 17:02         ` Lars-Peter Clausen
  -1 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-18 17:02 UTC (permalink / raw)
  To: dedekind1
  Cc: Ralf Baechle, linux-mips, linux-kernel, David Woodhouse, linux-mtd

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 <lars@metafoo.de>
>> Cc: David Woodhouse <dwmw2@infradead.org>
>> Cc: linux-mtd@lists.infradead.org
>>
> 
> 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

^ permalink raw reply	[flat|nested] 163+ messages in thread

* Re: [PATCH v3] MTD: Nand: Add JZ4740 NAND driver
@ 2010-07-18 17:02         ` Lars-Peter Clausen
  0 siblings, 0 replies; 163+ messages in thread
From: Lars-Peter Clausen @ 2010-07-18 17:02 UTC (permalink / raw)
  To: dedekind1
  Cc: linux-mips, linux-mtd, David Woodhouse, linux-kernel, Ralf Baechle

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 <lars@metafoo.de>
>> Cc: David Woodhouse <dwmw2@infradead.org>
>> Cc: linux-mtd@lists.infradead.org
>>
> 
> 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

^ permalink raw reply	[flat|nested] 163+ messages in thread

end of thread, other threads:[~2010-07-18 17:02 UTC | newest]

Thread overview: 163+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2010-06-19  5:08 [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Lars-Peter Clausen
2010-06-19  5:08 ` [lm-sensors] [PATCH v2 00/26] Add support for the Ingenic JZ4740 Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Lars-Peter Clausen
2010-06-19  5:08 ` Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 01/26] MIPS: Add base support for " Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 02/26] MIPS: jz4740: Add IRQ handler code Lars-Peter Clausen
2010-07-17 12:08   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 03/26] MIPS: JZ4740: Add clock API support Lars-Peter Clausen
2010-06-28  1:24   ` [PATCH v3 " Lars-Peter Clausen
2010-07-17 12:10     ` [PATCH v4] " Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 04/26] MIPS: JZ4740: Add timer support Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 05/26] MIPS: JZ4740: Add clocksource/clockevent support Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 06/26] MIPS: JZ4740: Add power-management and system reset support Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 07/26] MIPS: JZ4740: Add setup code Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 08/26] MIPS: JZ4740: Add gpio support Lars-Peter Clausen
2010-07-17 12:11   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 09/26] MIPS: JZ4740: Add DMA support Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 10/26] MIPS: JZ4740: Add PWM support Lars-Peter Clausen
2010-06-28  1:23   ` [PATCH v3 " Lars-Peter Clausen
2010-07-17 12:12     ` [PATCH v4] " Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 11/26] MIPS: JZ4740: Add serial support Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 12/26] MIPS: JZ4740: Add prom support Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 13/26] MIPS: JZ4740: Add platform devices Lars-Peter Clausen
2010-07-17 12:13   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 14/26] MIPS: JZ4740: Add Kbuild files Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 15/26] RTC: Add JZ4740 RTC driver Lars-Peter Clausen
2010-06-19 10:43   ` Marek Vasut
2010-06-19 13:05     ` Lars-Peter Clausen
2010-06-19 13:37       ` Wan ZongShun
2010-06-19 13:53         ` Lars-Peter Clausen
2010-06-19 14:36           ` Wan ZongShun
2010-06-19 14:04       ` Marek Vasut
2010-06-19 17:42         ` Lars-Peter Clausen
2010-06-19 17:53           ` Geert Uytterhoeven
2010-06-19 19:29   ` [PATCH v3] " Lars-Peter Clausen
2010-06-20  1:13     ` [rtc-linux] " Wan ZongShun
2010-06-20  1:23       ` Lars-Peter Clausen
2010-06-20  1:30         ` Wan ZongShun
2010-06-22  5:53     ` Alessandro Zummo
2010-06-19  5:08 ` [PATCH v2 16/26] fbdev: Add JZ4740 framebuffer driver Lars-Peter Clausen
2010-06-19  5:08   ` Lars-Peter Clausen
2010-07-04 22:27   ` Lars-Peter Clausen
2010-07-04 22:27     ` Lars-Peter Clausen
2010-07-07 23:41     ` Andrew Morton
2010-07-07 23:41       ` Andrew Morton
2010-07-08 13:28       ` Lars-Peter Clausen
2010-07-08 13:28         ` Lars-Peter Clausen
2010-07-08 16:46         ` Andrew Morton
2010-07-08 16:46           ` Andrew Morton
2010-07-09  1:26     ` Jaya Kumar
2010-07-09  1:26       ` Jaya Kumar
2010-07-09 15:31       ` Lars-Peter Clausen
2010-07-09 15:31         ` Lars-Peter Clausen
2010-07-17 12:14   ` [PATCH v3] " Lars-Peter Clausen
2010-07-17 12:14     ` Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver Lars-Peter Clausen
2010-06-19  5:08   ` Lars-Peter Clausen
2010-07-04 22:35   ` [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND Lars-Peter Clausen
2010-07-08  6:06   ` [PATCH v2 17/26] MTD: Nand: Add JZ4740 NAND driver Artem Bityutskiy
2010-07-08  6:06     ` Artem Bityutskiy
2010-07-08 13:20     ` Lars-Peter Clausen
2010-07-08 13:20       ` Lars-Peter Clausen
2010-07-08 13:19       ` Artem Bityutskiy
2010-07-08 13:19         ` Artem Bityutskiy
2010-07-08 14:02         ` Lars-Peter Clausen
2010-07-08 14:02           ` Lars-Peter Clausen
2010-07-08 14:14           ` Artem Bityutskiy
2010-07-08 14:14             ` Artem Bityutskiy
2010-07-17 12:15   ` [PATCH v3] " Lars-Peter Clausen
2010-07-17 12:15     ` Lars-Peter Clausen
2010-07-18 16:54     ` Artem Bityutskiy
2010-07-18 16:54       ` Artem Bityutskiy
2010-07-18 17:02       ` Lars-Peter Clausen
2010-07-18 17:02         ` Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 18/26] MMC: Add JZ4740 mmc driver Lars-Peter Clausen
2010-06-19 14:46   ` Matt Fleming
2010-06-19 14:46     ` Matt Fleming
2010-06-19 14:46     ` Matt Fleming
2010-06-19 15:29     ` Lars-Peter Clausen
2010-06-28  1:20   ` [PATCH v3] " Lars-Peter Clausen
2010-06-29 20:17     ` Matt Fleming
2010-06-29 20:17       ` Matt Fleming
2010-06-29 20:17       ` Matt Fleming
2010-07-01 15:47       ` Lars-Peter Clausen
2010-06-30 20:55     ` Andrew Morton
2010-07-01 15:45       ` Lars-Peter Clausen
2010-07-12 21:33     ` [PATCH v4] " Lars-Peter Clausen
2010-07-12 21:41       ` Randy Dunlap
2010-07-12 22:07         ` Lars-Peter Clausen
2010-07-12 22:20       ` [PATCH v5] " Lars-Peter Clausen
2010-07-12 22:45         ` Joe Perches
2010-07-12 23:45           ` Lars-Peter Clausen
2010-07-15 21:06         ` [PATCH v6] " Lars-Peter Clausen
2010-07-15 21:16           ` Andrew Morton
2010-07-15 21:37             ` Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 19/26] USB: Add JZ4740 ohci support Lars-Peter Clausen
2010-06-19 17:17   ` Greg KH
2010-06-19  5:08 ` [PATCH v2 20/26] alsa: ASoC: Add JZ4740 codec driver Lars-Peter Clausen
2010-06-19  5:08   ` Lars-Peter Clausen
2010-06-19 14:49   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19 14:49     ` Lars-Peter Clausen
2010-06-20 13:11     ` Mark Brown
2010-06-20 13:11       ` Mark Brown
2010-06-21 22:46     ` [PATCH v4] " Lars-Peter Clausen
2010-06-21 22:46       ` Lars-Peter Clausen
2010-06-22 10:12       ` Liam Girdwood
2010-06-22 10:12         ` Liam Girdwood
2010-06-22 23:12       ` Mark Brown
2010-06-22 23:12         ` Mark Brown
2010-06-19  5:08 ` [PATCH v2 21/26] alsa: ASoC: Add JZ4740 ASoC support Lars-Peter Clausen
2010-06-19  5:08   ` Lars-Peter Clausen
2010-06-19 14:50   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19 14:50     ` Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 22/26] MFD: Add JZ4740 ADC driver Lars-Peter Clausen
2010-07-04 22:47   ` Lars-Peter Clausen
2010-07-05 14:53   ` Samuel Ortiz
2010-07-05 15:43     ` Lars-Peter Clausen
2010-07-05 15:53       ` Samuel Ortiz
2010-07-12  1:48   ` [PATCH v3] " Lars-Peter Clausen
2010-07-14  9:19     ` Samuel Ortiz
2010-06-19  5:08 ` [PATCH v2 23/26] hwmon: " Lars-Peter Clausen
2010-06-19  5:08   ` [lm-sensors] " Lars-Peter Clausen
2010-06-19  8:36   ` Jean Delvare
2010-06-19  8:36     ` Jean Delvare
2010-06-19  8:36     ` Jean Delvare
2010-06-19 12:58     ` Lars-Peter Clausen
2010-06-19 12:58       ` Lars-Peter Clausen
2010-06-19 14:47   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19 14:47     ` [lm-sensors] " Lars-Peter Clausen
2010-06-19 16:24     ` Jean Delvare
2010-06-19 16:24       ` Jean Delvare
2010-06-19 17:59       ` Lars-Peter Clausen
2010-06-19 17:59         ` Lars-Peter Clausen
2010-06-19 19:32     ` [PATCH v4] " Lars-Peter Clausen
2010-06-19 19:32       ` [lm-sensors] " Lars-Peter Clausen
2010-06-20  6:32       ` Jean Delvare
2010-06-20  6:32         ` Jean Delvare
2010-06-20  6:32         ` Jean Delvare
2010-06-19  5:08 ` [PATCH v2 24/26] power: Add JZ4740 battery driver Lars-Peter Clausen
2010-06-27  1:58   ` Lars-Peter Clausen
2010-06-28 11:43     ` Anton Vorontsov
2010-06-19  5:08 ` [PATCH v2 25/26] MIPS: JZ4740: Add qi_lb60 board support Lars-Peter Clausen
2010-07-17 12:16   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19  5:08 ` [PATCH v2 26/26] alsa: ASoC: JZ4740: Add qi_lb60 board driver Lars-Peter Clausen
2010-06-19  5:08   ` Lars-Peter Clausen
2010-06-19 14:52   ` [PATCH v3] " Lars-Peter Clausen
2010-06-19 14:52     ` Lars-Peter Clausen
2010-06-20  9:26 ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Thomas Bogendoerfer
2010-06-20  9:26   ` [lm-sensors] [PATCH v2 00/26] Add support for the Ingenic Thomas Bogendoerfer
2010-06-20  9:26   ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Thomas Bogendoerfer
2010-06-20  9:26   ` Thomas Bogendoerfer
2010-06-20  9:26   ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 Thomas Bogendoerfer
2010-06-20 14:31   ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Lars-Peter Clausen
2010-06-20 14:31     ` Lars-Peter Clausen
2010-06-20 16:34     ` Thomas Bogendoerfer
2010-06-20 16:49       ` Lars-Peter Clausen
2010-06-20 17:01         ` Thomas Bogendoerfer
2010-06-20 17:57           ` Florian Fainelli
2010-06-20 18:30             ` Lars-Peter Clausen
2010-06-21  2:56   ` Xiangfu Liu
2010-06-21  2:56     ` [lm-sensors] [PATCH v2 00/26] Add support for the Ingenic Xiangfu Liu
2010-06-21  2:56     ` [PATCH v2 00/26] Add support for the Ingenic JZ4740 System-on-a-Chip Xiangfu Liu
2010-06-21  2:56     ` Xiangfu Liu

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.