All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/34] omap drivers going upstream
@ 2008-08-30 17:16 Felipe Balbi
  2008-08-30 17:16 ` [PATCH 01/33] add lp5521 driver Felipe Balbi
                   ` (2 more replies)
  0 siblings, 3 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

The following drivers are going upstream for integration.
They have been sitting on linux-omap for quite a while just
increasing the diff against mainline and probability of
merge conflicts.

Idealy, we would have people maintaining those drivers
but unfortunately it looks like it won't happen soon.

linux-omap will be kept on the loop so we can comment
on the patches and see the comments coming for the
proper mailing lists.

If anyone has objections to the patches, please say now
before I send them to mainline. I'm planning to send them
on next 9/11.

Here's the dirstat between l-o and mainline tree before
and after the patches:

before:

$ git diff --dirstat linus | sort -n
   3.2% drivers/input/keyboard/
   4.2% arch/arm/plat-omap/
   4.2% drivers/cbus/
   4.2% drivers/spi/
   4.8% sound/oss/
   6.0% drivers/i2c/chips/
   7.0% sound/arm/omap/
   7.2% drivers/media/video/
   8.4% drivers/dsp/dspgateway/
   9.0% arch/arm/configs/
  15.6% arch/arm/mach-omap2/
  20.6% drivers/

after:

$ git diff --dirstat linus | sort -n
   4.8% drivers/bluetooth/
   7.0% arch/arm/plat-omap/
   7.7% drivers/
   7.9% sound/oss/
  11.6% sound/arm/omap/
  13.9% drivers/dsp/dspgateway/
  14.9% arch/arm/configs/
  25.7% arch/arm/mach-omap2/

Felipe Balbi (33):
  add lp5521 driver
  Add cbus driver
  add omap-sha1 driver
  add omap gpio expander driver
  add tlv320aic23 driver
  add tsl2563 driver
  add twl4030 drivers
  add innovator ps2 keypad driver
  add lm8323 keypad driver
  Add twl4030 keypad driver
  add tsc2301 keypad driver
  Add tsc2005 touchscreen driver
  Add omap touchscreen driver
  add tsc210x touchscreen driver
  add tsc2301 touchscreen driver
  add omap led drivers
  add tea5761 radio driver
  add ov9640 sensor driver
  add omap video drivers
  add omap Serial Trace Interface driver
  add OMAP HighSpeed mmc controller driver
  add omap nand drivers
  add omap irda driver
  add bq27x00 battery driver
  add TWL4030 Battery Charger Interface driver
  add TWL4030 RealTime Clock driver
  add tsc210x driver
  add tsc2301 driver
  add omap ehci bus glue
  add omap backlight support
  add several omap lcd drivers
  add omap 1-wire interface driver
  add bq27000 1-wire slave driver

 arch/arm/mach-omap2/board-2430sdp.c         |   59 +
 arch/arm/plat-omap/include/mach/irqs.h      |    2 +
 arch/arm/plat-omap/include/mach/keypad.h    |    1 +
 drivers/Makefile                            |    2 +-
 drivers/cbus/Kconfig                        |   89 ++
 drivers/cbus/Makefile                       |   14 +
 drivers/cbus/cbus.c                         |  293 ++++
 drivers/cbus/cbus.h                         |   36 +
 drivers/cbus/retu-headset.c                 |  355 +++++
 drivers/cbus/retu-pwrbutton.c               |  118 ++
 drivers/cbus/retu-rtc.c                     |  477 +++++++
 drivers/cbus/retu-user.c                    |  423 ++++++
 drivers/cbus/retu-wdt.c                     |  202 +++
 drivers/cbus/retu.c                         |  466 +++++++
 drivers/cbus/retu.h                         |   77 ++
 drivers/cbus/tahvo-usb.c                    |  777 +++++++++++
 drivers/cbus/tahvo-user.c                   |  405 ++++++
 drivers/cbus/tahvo.c                        |  441 ++++++
 drivers/cbus/tahvo.h                        |   61 +
 drivers/cbus/user_retu_tahvo.h              |   75 ++
 drivers/crypto/Kconfig                      |    7 +
 drivers/crypto/Makefile                     |    1 +
 drivers/crypto/omap-sha1-md5.c              |  575 ++++++++
 drivers/i2c/chips/Kconfig                   |   90 ++
 drivers/i2c/chips/Makefile                  |   11 +-
 drivers/i2c/chips/gpio_expander_omap.c      |   71 +
 drivers/i2c/chips/lp5521.c                  |  585 ++++++++
 drivers/i2c/chips/tlv320aic23.c             |  675 ++++++++++
 drivers/i2c/chips/tsl2563.c                 |  739 +++++++++++
 drivers/i2c/chips/twl4030-core.c            | 1006 ++++++++++++++
 drivers/i2c/chips/twl4030-gpio.c            |  788 +++++++++++
 drivers/i2c/chips/twl4030-madc.c            |  455 +++++++
 drivers/i2c/chips/twl4030-poweroff.c        |   76 ++
 drivers/i2c/chips/twl4030-pwrbutton.c       |  163 +++
 drivers/i2c/chips/twl4030-pwrirq.c          |  226 ++++
 drivers/i2c/chips/twl4030-usb.c             |  779 +++++++++++
 drivers/input/keyboard/Kconfig              |   33 +
 drivers/input/keyboard/Makefile             |    4 +
 drivers/input/keyboard/innovator_ps2.c      | 1280 ++++++++++++++++++
 drivers/input/keyboard/lm8323.c             |  925 +++++++++++++
 drivers/input/keyboard/omap-twl4030keypad.c |  415 ++++++
 drivers/input/keyboard/tsc2301_kp.c         |  475 +++++++
 drivers/input/keyboard/twl4030-keypad.h     |   82 ++
 drivers/input/touchscreen/Kconfig           |   25 +
 drivers/input/touchscreen/Makefile          |    4 +
 drivers/input/touchscreen/omap/Makefile     |   10 +
 drivers/input/touchscreen/omap/omap_ts.c    |  267 ++++
 drivers/input/touchscreen/omap/omap_ts.h    |   57 +
 drivers/input/touchscreen/omap/ts_hx.c      |  184 +++
 drivers/input/touchscreen/tsc2005.c         |  736 +++++++++++
 drivers/input/touchscreen/tsc210x_ts.c      |  160 +++
 drivers/input/touchscreen/tsc2301_ts.c      |  676 ++++++++++
 drivers/leds/Kconfig                        |   21 +
 drivers/leds/Makefile                       |    2 +
 drivers/leds/leds-omap-pwm.c                |  376 ++++++
 drivers/leds/leds-omap.c                    |  135 ++
 drivers/media/radio/Kconfig                 |   13 +
 drivers/media/radio/Makefile                |    1 +
 drivers/media/radio/radio-tea5761.c         |  517 ++++++++
 drivers/media/video/Kconfig                 |   19 +
 drivers/media/video/Makefile                |    3 +
 drivers/media/video/omap/Kconfig            |    7 +
 drivers/media/video/omap/Makefile           |    9 +
 drivers/media/video/omap/camera_core.c      | 1280 ++++++++++++++++++
 drivers/media/video/omap/camera_core.h      |  156 +++
 drivers/media/video/omap/camera_hw_if.h     |   50 +
 drivers/media/video/omap/omap16xxcam.c      |  579 ++++++++
 drivers/media/video/omap/omap16xxcam.h      |  106 ++
 drivers/media/video/omap24xxcam-dma.c       |  601 +++++++++
 drivers/media/video/omap24xxcam.c           | 1908 +++++++++++++++++++++++++++
 drivers/media/video/omap24xxcam.h           |  593 +++++++++
 drivers/media/video/ov9640.c                | 1279 ++++++++++++++++++
 drivers/media/video/ov9640.h                |  194 +++
 drivers/misc/Kconfig                        |   14 +
 drivers/misc/Makefile                       |    1 +
 drivers/misc/sti/Makefile                   |    8 +
 drivers/misc/sti/sdti.c                     |  185 +++
 drivers/misc/sti/sti-console.c              |  189 +++
 drivers/misc/sti/sti-fifo.c                 |  117 ++
 drivers/misc/sti/sti-netlink.c              |  152 +++
 drivers/misc/sti/sti.c                      |  432 ++++++
 drivers/mmc/host/Kconfig                    |   13 +-
 drivers/mmc/host/Makefile                   |    1 +
 drivers/mmc/host/omap_hsmmc.c               | 1069 +++++++++++++++
 drivers/mtd/nand/Kconfig                    |   19 +
 drivers/mtd/nand/Makefile                   |    3 +
 drivers/mtd/nand/omap-hw.c                  |  860 ++++++++++++
 drivers/mtd/nand/omap-nand-flash.c          |  186 +++
 drivers/mtd/nand/omap2.c                    |  757 +++++++++++
 drivers/net/irda/Kconfig                    |   10 +
 drivers/net/irda/Makefile                   |    1 +
 drivers/net/irda/omap-ir.c                  |  901 +++++++++++++
 drivers/power/Kconfig                       |   29 +
 drivers/power/Makefile                      |    2 +
 drivers/power/bq27x00_battery.c             |  564 ++++++++
 drivers/power/twl4030_bci_battery.c         | 1080 +++++++++++++++
 drivers/rtc/Kconfig                         |   10 +
 drivers/rtc/Makefile                        |    1 +
 drivers/rtc/rtc-twl4030.c                   |  649 +++++++++
 drivers/spi/Kconfig                         |   41 +
 drivers/spi/Makefile                        |    4 +
 drivers/spi/tsc210x.c                       | 1262 ++++++++++++++++++
 drivers/spi/tsc2301-core.c                  |  301 +++++
 drivers/spi/tsc2301-mixer.c                 | 1004 ++++++++++++++
 drivers/usb/host/Kconfig                    |   19 +
 drivers/usb/host/ehci-hcd.c                 |    5 +
 drivers/usb/host/ehci-omap.c                |  562 ++++++++
 drivers/usb/host/ehci-omap.h                |  125 ++
 drivers/video/backlight/Kconfig             |    9 +
 drivers/video/backlight/Makefile            |    1 +
 drivers/video/backlight/omap_bl.c           |  218 +++
 drivers/video/omap/Kconfig                  |   18 +
 drivers/video/omap/Makefile                 |   11 +
 drivers/video/omap/lcd_2430sdp.c            |  182 +++
 drivers/video/omap/lcd_ams_delta.c          |  140 ++
 drivers/video/omap/lcd_apollon.c            |  137 ++
 drivers/video/omap/lcd_h2.c                 |  155 +++
 drivers/video/omap/lcd_mipid.c              |  617 +++++++++
 drivers/video/omap/lcd_omap2evm.c           |  195 +++
 drivers/video/omap/lcd_omap3beagle.c        |  133 ++
 drivers/video/omap/lcd_omap3evm.c           |  197 +++
 drivers/video/omap/lcd_p2.c                 |  342 +++++
 drivers/w1/masters/Kconfig                  |    7 +
 drivers/w1/masters/Makefile                 |    1 +
 drivers/w1/masters/omap_hdq.c               |  704 ++++++++++
 drivers/w1/slaves/Kconfig                   |    8 +
 drivers/w1/slaves/Makefile                  |    2 +-
 drivers/w1/slaves/w1_bq27000.c              |  120 ++
 drivers/w1/w1.h                             |    1 +
 drivers/w1/w1_io.c                          |    3 +-
 include/linux/i2c/lm8323.h                  |   37 +
 include/linux/i2c/twl4030-gpio.h            |   76 ++
 include/linux/i2c/twl4030-madc.h            |  134 ++
 include/linux/i2c/twl4030-pwrirq.h          |   37 +
 include/linux/i2c/twl4030-rtc.h             |  230 ++++
 include/linux/i2c/twl4030.h                 |  137 ++
 include/linux/spi/tsc2005.h                 |   29 +
 include/linux/spi/tsc210x.h                 |  231 ++++
 138 files changed, 38485 insertions(+), 5 deletions(-)
 create mode 100644 drivers/cbus/Kconfig
 create mode 100644 drivers/cbus/Makefile
 create mode 100644 drivers/cbus/cbus.c
 create mode 100644 drivers/cbus/cbus.h
 create mode 100644 drivers/cbus/retu-headset.c
 create mode 100644 drivers/cbus/retu-pwrbutton.c
 create mode 100644 drivers/cbus/retu-rtc.c
 create mode 100644 drivers/cbus/retu-user.c
 create mode 100644 drivers/cbus/retu-wdt.c
 create mode 100644 drivers/cbus/retu.c
 create mode 100644 drivers/cbus/retu.h
 create mode 100644 drivers/cbus/tahvo-usb.c
 create mode 100644 drivers/cbus/tahvo-user.c
 create mode 100644 drivers/cbus/tahvo.c
 create mode 100644 drivers/cbus/tahvo.h
 create mode 100644 drivers/cbus/user_retu_tahvo.h
 create mode 100644 drivers/crypto/omap-sha1-md5.c
 create mode 100644 drivers/i2c/chips/gpio_expander_omap.c
 create mode 100644 drivers/i2c/chips/lp5521.c
 create mode 100644 drivers/i2c/chips/tlv320aic23.c
 create mode 100644 drivers/i2c/chips/tsl2563.c
 create mode 100644 drivers/i2c/chips/twl4030-core.c
 create mode 100644 drivers/i2c/chips/twl4030-gpio.c
 create mode 100644 drivers/i2c/chips/twl4030-madc.c
 create mode 100644 drivers/i2c/chips/twl4030-poweroff.c
 create mode 100644 drivers/i2c/chips/twl4030-pwrbutton.c
 create mode 100644 drivers/i2c/chips/twl4030-pwrirq.c
 create mode 100644 drivers/i2c/chips/twl4030-usb.c
 create mode 100644 drivers/input/keyboard/innovator_ps2.c
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 drivers/input/keyboard/omap-twl4030keypad.c
 create mode 100644 drivers/input/keyboard/tsc2301_kp.c
 create mode 100644 drivers/input/keyboard/twl4030-keypad.h
 create mode 100644 drivers/input/touchscreen/omap/Makefile
 create mode 100644 drivers/input/touchscreen/omap/omap_ts.c
 create mode 100644 drivers/input/touchscreen/omap/omap_ts.h
 create mode 100644 drivers/input/touchscreen/omap/ts_hx.c
 create mode 100644 drivers/input/touchscreen/tsc2005.c
 create mode 100644 drivers/input/touchscreen/tsc210x_ts.c
 create mode 100644 drivers/input/touchscreen/tsc2301_ts.c
 create mode 100644 drivers/leds/leds-omap-pwm.c
 create mode 100644 drivers/leds/leds-omap.c
 create mode 100644 drivers/media/radio/radio-tea5761.c
 create mode 100644 drivers/media/video/omap/Kconfig
 create mode 100644 drivers/media/video/omap/Makefile
 create mode 100644 drivers/media/video/omap/camera_core.c
 create mode 100644 drivers/media/video/omap/camera_core.h
 create mode 100644 drivers/media/video/omap/camera_hw_if.h
 create mode 100644 drivers/media/video/omap/omap16xxcam.c
 create mode 100644 drivers/media/video/omap/omap16xxcam.h
 create mode 100644 drivers/media/video/omap24xxcam-dma.c
 create mode 100644 drivers/media/video/omap24xxcam.c
 create mode 100644 drivers/media/video/omap24xxcam.h
 create mode 100644 drivers/media/video/ov9640.c
 create mode 100644 drivers/media/video/ov9640.h
 create mode 100644 drivers/misc/sti/Makefile
 create mode 100644 drivers/misc/sti/sdti.c
 create mode 100644 drivers/misc/sti/sti-console.c
 create mode 100644 drivers/misc/sti/sti-fifo.c
 create mode 100644 drivers/misc/sti/sti-netlink.c
 create mode 100644 drivers/misc/sti/sti.c
 create mode 100644 drivers/mmc/host/omap_hsmmc.c
 create mode 100644 drivers/mtd/nand/omap-hw.c
 create mode 100644 drivers/mtd/nand/omap-nand-flash.c
 create mode 100644 drivers/mtd/nand/omap2.c
 create mode 100644 drivers/net/irda/omap-ir.c
 create mode 100644 drivers/power/bq27x00_battery.c
 create mode 100644 drivers/power/twl4030_bci_battery.c
 create mode 100644 drivers/rtc/rtc-twl4030.c
 create mode 100644 drivers/spi/tsc210x.c
 create mode 100644 drivers/spi/tsc2301-core.c
 create mode 100644 drivers/spi/tsc2301-mixer.c
 create mode 100644 drivers/usb/host/ehci-omap.c
 create mode 100644 drivers/usb/host/ehci-omap.h
 create mode 100644 drivers/video/backlight/omap_bl.c
 create mode 100644 drivers/video/omap/lcd_2430sdp.c
 create mode 100644 drivers/video/omap/lcd_ams_delta.c
 create mode 100644 drivers/video/omap/lcd_apollon.c
 create mode 100644 drivers/video/omap/lcd_h2.c
 create mode 100644 drivers/video/omap/lcd_mipid.c
 create mode 100644 drivers/video/omap/lcd_omap2evm.c
 create mode 100644 drivers/video/omap/lcd_omap3beagle.c
 create mode 100644 drivers/video/omap/lcd_omap3evm.c
 create mode 100644 drivers/video/omap/lcd_p2.c
 create mode 100644 drivers/w1/masters/omap_hdq.c
 create mode 100644 drivers/w1/slaves/w1_bq27000.c
 create mode 100644 include/linux/i2c/lm8323.h
 create mode 100644 include/linux/i2c/twl4030-gpio.h
 create mode 100644 include/linux/i2c/twl4030-madc.h
 create mode 100644 include/linux/i2c/twl4030-pwrirq.h
 create mode 100644 include/linux/i2c/twl4030-rtc.h
 create mode 100644 include/linux/i2c/twl4030.h
 create mode 100644 include/linux/spi/tsc2005.h
 create mode 100644 include/linux/spi/tsc210x.h


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

* [PATCH 01/33] add lp5521 driver
  2008-08-30 17:16 [PATCH 00/34] omap drivers going upstream Felipe Balbi
@ 2008-08-30 17:16 ` Felipe Balbi
       [not found]   ` <1220116593-862-3-git-send-email-me@felipebalbi.com>
  2008-08-31 20:39   ` [PATCH 01/33] add lp5521 driver David Brownell
  2008-08-31 22:37 ` [PATCH 00/34] omap drivers going upstream Felipe Balbi
  2008-09-01  1:46 ` andrzej zaborowski
  2 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/i2c/chips/Kconfig  |    7 +
 drivers/i2c/chips/Makefile |    2 +-
 drivers/i2c/chips/lp5521.c |  585 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 593 insertions(+), 1 deletions(-)
 create mode 100644 drivers/i2c/chips/lp5521.c

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index a95cb94..193c3ae 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -162,6 +162,13 @@ config SENSORS_TSL2550
 	  This driver can also be built as a module.  If so, the module
 	  will be called tsl2550.
 
+config LP5521
+	tristate "LP5521 LED driver chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LP5521 LED driver.
+
 config MENELAUS
 	bool "TWL92330/Menelaus PM chip"
 	depends on I2C=y && ARCH_OMAP24XX
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 39e3e69..987f600 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -21,8 +21,8 @@ obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
 obj-$(CONFIG_TPS65010)		+= tps65010.o
 obj-$(CONFIG_MENELAUS)		+= menelaus.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
+obj-$(CONFIG_LP5521)		+= lp5521.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
 EXTRA_CFLAGS += -DDEBUG
 endif
-
diff --git a/drivers/i2c/chips/lp5521.c b/drivers/i2c/chips/lp5521.c
new file mode 100644
index 0000000..c0862d9
--- /dev/null
+++ b/drivers/i2c/chips/lp5521.c
@@ -0,0 +1,585 @@
+/*
+ * drivers/i2c/chips/lp5521.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Mathias Nyman <mathias.nyman@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <mach/gpio.h>
+
+#define LP5521_DRIVER_NAME		"lp5521"
+
+#ifdef LED_CONNECTED_WRONG
+#define LP5521_REG_R_PWM		0x04
+#define LP5521_REG_B_PWM		0x02
+#else
+#define LP5521_REG_R_PWM		0x02
+#define LP5521_REG_B_PWM		0x04
+#endif
+#define LP5521_REG_ENABLE		0x00
+#define LP5521_REG_OP_MODE		0x01
+#define LP5521_REG_G_PWM		0x03
+#define LP5521_REG_R_CNTRL		0x05
+#define LP5521_REG_G_CNTRL		0x06
+#define LP5521_REG_B_CNTRL		0x07
+#define LP5521_REG_MISC			0x08
+#define LP5521_REG_R_CHANNEL_PC		0x09
+#define LP5521_REG_G_CHANNEL_PC		0x0a
+#define LP5521_REG_B_CHANNEL_PC		0x0b
+#define LP5521_REG_STATUS		0x0c
+#define LP5521_REG_RESET		0x0d
+#define LP5521_REG_GPO			0x0e
+#define LP5521_REG_R_PROG_MEM		0x10
+#define LP5521_REG_G_PROG_MEM		0x30
+#define LP5521_REG_B_PROG_MEM		0x50
+
+#define LP5521_MODE_LOAD		"load"
+#define LP5521_MODE_RUN			"run"
+#define LP5521_MODE_DIRECT_CONTROL	"direct"
+
+#define LP5521_CURRENT_1m5		0x0f
+#define LP5521_CURRENT_3m1		0x1f
+#define LP5521_CURRENT_4m7		0x2f
+#define LP5521_CURRENT_6m3		0x3f
+#define LP5521_CURRENT_7m9		0x4f
+#define LP5521_CURRENT_9m5		0x5f
+#define LP5521_CURRENT_11m1		0x6f
+#define LP5521_CURRENT_12m7		0x7f
+#define LP5521_CURRENT_14m3		0x8f
+#define LP5521_CURRENT_15m9		0x9f
+#define LP5521_CURRENT_17m5		0xaf
+#define LP5521_CURRENT_19m1		0xbf
+#define LP5521_CURRENT_20m7		0xcf
+#define LP5521_CURRENT_22m3		0xdf
+#define LP5521_CURRENT_23m9		0xef
+#define LP5521_CURRENT_25m5		0xff
+
+#define LP5521_PROGRAM_LENGTH		32	/* in bytes */
+
+struct lp5521_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	char			*mode;
+	int			red;
+	int			green;
+	int			blue;
+};
+
+static int lp5521_set_mode(struct lp5521_chip *chip, char *mode);
+
+static int lp5521_write(struct i2c_client *client, u8 reg, u8 value)
+{
+	return i2c_smbus_write_byte_data(client, reg, value);
+}
+
+static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf)
+{
+	s32 ret = i2c_smbus_read_byte_data(client, reg);
+
+	if (ret < 0)
+		return -EIO;
+
+	*buf = ret;
+	return 0;
+}
+
+static int lp5521_configure(struct i2c_client *client)
+{
+	int ret = 0;
+
+	/* Enable chip and set light to logarithmic mode*/
+	ret |= lp5521_write(client, LP5521_REG_ENABLE, 0xc0);
+
+	/* setting all color pwms to direct control mode */
+	ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3f);
+
+	/* setting current to 4.7 mA for all channels */
+	ret |= lp5521_write(client, LP5521_REG_R_CNTRL, LP5521_CURRENT_4m7);
+	ret |= lp5521_write(client, LP5521_REG_G_CNTRL, LP5521_CURRENT_4m7);
+	ret |= lp5521_write(client, LP5521_REG_B_CNTRL, LP5521_CURRENT_4m7);
+
+	/* Enable auto-powersave, set charge pump to auto, red to battery */
+	ret |= lp5521_write(client, LP5521_REG_MISC, 0x3c);
+
+	/* initialize all channels pwm to zero */
+	ret |= lp5521_write(client, LP5521_REG_R_PWM, 0);
+	ret |= lp5521_write(client, LP5521_REG_G_PWM, 0);
+	ret |= lp5521_write(client, LP5521_REG_B_PWM, 0);
+
+	/* Not much can be done about errors at this point */
+	return ret;
+}
+
+static int lp5521_load_program(struct lp5521_chip *chip, u8 *pattern)
+{
+	struct i2c_client *client = chip->client;
+	int ret = 0;
+
+	/* Enter load program mode for all led channels */
+	ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15); /* 0001 0101 */
+	if (ret)
+		return ret;
+
+	if (chip->red)
+		ret |= i2c_smbus_write_i2c_block_data(client,
+						      LP5521_REG_R_PROG_MEM,
+						      LP5521_PROGRAM_LENGTH,
+						      pattern);
+	if (chip->green)
+		ret |= i2c_smbus_write_i2c_block_data(client,
+						      LP5521_REG_G_PROG_MEM,
+						      LP5521_PROGRAM_LENGTH,
+						      pattern);
+	if (chip->blue)
+		ret |= i2c_smbus_write_i2c_block_data(client,
+						      LP5521_REG_B_PROG_MEM,
+						      LP5521_PROGRAM_LENGTH,
+						      pattern);
+
+	return ret;
+}
+
+static int lp5521_run_program(struct lp5521_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+	u8 mask = 0xc0;
+	u8 exec_state = 0;
+	u8 enable_reg;
+
+	ret = lp5521_read(client, LP5521_REG_ENABLE, &enable_reg);
+	if (ret)
+		goto fail;
+
+	enable_reg &= mask;
+
+	/* set all active channels exec state to countinous run*/
+	exec_state |= (chip->red   << 5);
+	exec_state |= (chip->green << 3);
+	exec_state |= (chip->blue  << 1);
+
+	enable_reg |= exec_state;
+
+	ret |= lp5521_write(client, LP5521_REG_ENABLE, enable_reg);
+
+	/* set op-mode to run for active channels, disabled for others */
+	ret |= lp5521_write(client, LP5521_REG_OP_MODE, exec_state);
+
+fail:
+	return ret;
+}
+
+/*--------------------------------------------------------------*/
+/*			Sysfs interface				*/
+/*--------------------------------------------------------------*/
+
+static ssize_t show_active_channels(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+	char channels[4];
+	int pos = 0;
+
+#ifdef LED_CONNECTED_WRONG
+	if (chip->blue)
+		pos += sprintf(channels + pos, "r");
+	if (chip->green)
+		pos += sprintf(channels + pos, "g");
+	if (chip->red)
+		pos += sprintf(channels + pos, "b");
+
+#else
+	if (chip->red)
+		pos += sprintf(channels + pos, "r");
+	if (chip->green)
+		pos += sprintf(channels + pos, "g");
+	if (chip->blue)
+		pos += sprintf(channels + pos, "b");
+#endif
+
+	channels[pos] = '\0';
+
+	return sprintf(buf, "%s\n", channels);
+}
+
+static ssize_t store_active_channels(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+
+	chip->red = 0;
+	chip->green = 0;
+	chip->blue = 0;
+
+#ifdef LED_CONNECTED_WRONG
+	if (strchr(buf, 'r') != NULL)
+		chip->blue = 1;
+	if (strchr(buf, 'b') != NULL)
+		chip->red = 1;
+#else
+	if (strchr(buf, 'r') != NULL)
+		chip->red = 1;
+	if (strchr(buf, 'b') != NULL)
+		chip->blue = 1;
+#endif
+	if (strchr(buf, 'g') != NULL)
+		chip->green = 1;
+
+	return len;
+}
+
+static ssize_t show_color(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 r, g, b;
+
+	ret |= lp5521_read(client, LP5521_REG_R_PWM, &r);
+	ret |= lp5521_read(client, LP5521_REG_G_PWM, &g);
+	ret |= lp5521_read(client, LP5521_REG_B_PWM, &b);
+
+	if (ret)
+		return ret;
+
+	return sprintf(buf, "%.2x:%.2x:%.2x\n", r, g, b);
+}
+
+static ssize_t store_color(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct lp5521_chip *chip = i2c_get_clientdata(client);
+	int ret;
+	unsigned r, g, b;
+
+
+	ret = sscanf(buf, "%2x:%2x:%2x", &r, &g, &b);
+	if (ret != 3)
+		return  -EINVAL;
+
+	mutex_lock(&chip->lock);
+
+	ret = lp5521_write(client, LP5521_REG_R_PWM, (u8)r);
+	ret = lp5521_write(client, LP5521_REG_G_PWM, (u8)g);
+	ret = lp5521_write(client, LP5521_REG_B_PWM, (u8)b);
+
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static ssize_t store_load(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+	int  ret, nrchars, offset = 0, i = 0;
+	char c[3];
+	unsigned cmd;
+	u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
+
+	while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) {
+
+		/* separate sscanfs because length is working only for %s */
+		ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
+		ret = sscanf(c, "%2x", &cmd);
+		if (ret != 1)
+			goto fail;
+		pattern[i] = (u8)cmd;
+
+		offset += nrchars;
+		i++;
+	}
+
+	/* pattern commands are always two bytes long */
+	if (i % 2)
+		goto fail;
+
+	mutex_lock(&chip->lock);
+
+	ret = lp5521_load_program(chip, pattern);
+	mutex_unlock(&chip->lock);
+
+	if (ret) {
+		dev_err(dev, "lp5521 failed loading pattern\n");
+		return ret;
+	}
+
+	return len;
+fail:
+	dev_err(dev, "lp5521 wrong pattern format\n");
+	return -EINVAL;
+}
+
+static ssize_t show_mode(struct device *dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%s\n", chip->mode);
+}
+
+static ssize_t store_mode(struct device *dev,
+			  struct device_attribute *attr,
+			  const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+
+	mutex_lock(&chip->lock);
+
+	if (!strncmp(buf, "run", 3))
+		lp5521_set_mode(chip, LP5521_MODE_RUN);
+	else if (!strncmp(buf, "load", 4))
+		lp5521_set_mode(chip, LP5521_MODE_LOAD);
+	else if (!strncmp(buf, "direct", 6))
+		lp5521_set_mode(chip, LP5521_MODE_DIRECT_CONTROL);
+
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static ssize_t show_current(struct device *dev,
+			    struct device_attribute *attr,
+			    char *buf)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	int ret = 0;
+	u8 r_curr, g_curr, b_curr;
+
+	ret |= lp5521_read(client, LP5521_REG_R_CNTRL, &r_curr);
+	ret |= lp5521_read(client, LP5521_REG_G_CNTRL, &g_curr);
+	ret |= lp5521_read(client, LP5521_REG_B_CNTRL, &b_curr);
+
+	if (ret)
+		return ret;
+
+	r_curr = r_curr >> 4;
+	g_curr = g_curr >> 4;
+	b_curr = b_curr >> 4;
+
+	if (r_curr == g_curr && g_curr == b_curr)
+		return sprintf(buf, "%x\n", r_curr);
+	else
+		return sprintf(buf, "%x %x %x\n", r_curr, g_curr, b_curr);
+}
+
+static ssize_t store_current(struct device *dev,
+			     struct device_attribute *attr,
+			     const char *buf, size_t len)
+{
+	struct lp5521_chip *chip = dev_get_drvdata(dev);
+	struct i2c_client *client = chip->client;
+	int ret;
+	unsigned curr;
+
+	ret = sscanf(buf, "%1x", &curr);
+	if (ret != 1)
+		return  -EINVAL;
+
+	/* current level is determined by the 4 upper bits, rest is ones */
+	curr = (curr << 4) | 0x0f;
+
+	mutex_lock(&chip->lock);
+
+	ret |= lp5521_write(client, LP5521_REG_R_CNTRL, (u8)curr);
+	ret |= lp5521_write(client, LP5521_REG_G_CNTRL, (u8)curr);
+	ret |= lp5521_write(client, LP5521_REG_B_CNTRL, (u8)curr);
+
+	mutex_unlock(&chip->lock);
+
+	return len;
+}
+
+static DEVICE_ATTR(color, S_IRUGO | S_IWUGO, show_color, store_color);
+static DEVICE_ATTR(load, S_IWUGO, NULL, store_load);
+static DEVICE_ATTR(mode, S_IRUGO | S_IWUGO, show_mode, store_mode);
+static DEVICE_ATTR(active_channels, S_IRUGO | S_IWUGO,
+		   show_active_channels, store_active_channels);
+static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current);
+
+static int lp5521_register_sysfs(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+	int ret;
+
+	ret = device_create_file(dev, &dev_attr_color);
+	if (ret)
+		goto fail1;
+	ret = device_create_file(dev, &dev_attr_load);
+	if (ret)
+		goto fail2;
+	ret = device_create_file(dev, &dev_attr_active_channels);
+	if (ret)
+		goto fail3;
+	ret = device_create_file(dev, &dev_attr_mode);
+	if (ret)
+		goto fail4;
+	ret = device_create_file(dev, &dev_attr_led_current);
+	if (ret)
+		goto fail5;
+	return 0;
+
+fail5:
+	device_remove_file(dev, &dev_attr_mode);
+fail4:
+	device_remove_file(dev, &dev_attr_active_channels);
+fail3:
+	device_remove_file(dev, &dev_attr_load);
+fail2:
+	device_remove_file(dev, &dev_attr_color);
+fail1:
+	return ret;
+}
+
+static void lp5521_unregister_sysfs(struct i2c_client *client)
+{
+	struct lp5521_chip *chip = i2c_get_clientdata(client);
+	struct device *dev = &client->dev;
+
+	device_remove_file(dev, &dev_attr_led_current);
+	device_remove_file(dev, &dev_attr_mode);
+	device_remove_file(dev, &dev_attr_active_channels);
+	device_remove_file(dev, &dev_attr_color);
+
+	if (!strcmp(chip->mode, LP5521_MODE_LOAD))
+		device_remove_file(dev, &dev_attr_load);
+}
+
+/*--------------------------------------------------------------*/
+/*			Set chip operating mode			*/
+/*--------------------------------------------------------------*/
+
+static int lp5521_set_mode(struct lp5521_chip *chip, char *mode)
+{
+	struct i2c_client *client = chip->client ;
+	int ret = 0;
+
+	/* if in that mode already do nothing, except for run */
+	if (!strcmp(mode, chip->mode) && strcmp(mode, LP5521_MODE_RUN))
+		return 0;
+
+	if (!strcmp(mode, LP5521_MODE_RUN))
+		ret = lp5521_run_program(chip);
+
+	if (!strcmp(mode, LP5521_MODE_LOAD))
+		ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x15);
+
+	if (!strcmp(mode, LP5521_MODE_DIRECT_CONTROL))
+		ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3F);
+
+	chip->mode = mode;
+
+	return ret;
+}
+
+/*--------------------------------------------------------------*/
+/*			Probe, Attach, Remove			*/
+/*--------------------------------------------------------------*/
+static struct i2c_driver lp5521_driver;
+
+static int lp5521_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct lp5521_chip *chip;
+	int ret = 0;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->client	= client;
+	strncpy(client->name, LP5521_DRIVER_NAME, I2C_NAME_SIZE);
+	i2c_set_clientdata(client, chip);
+
+	mutex_init(&chip->lock);
+
+	ret = lp5521_configure(client);
+	if (ret < 0) {
+		dev_err(&client->dev, "lp5521 error configuring chip \n");
+		goto fail1;
+	}
+
+	/* Set default values */
+	chip->mode	= LP5521_MODE_DIRECT_CONTROL;
+	chip->red	= 1;
+	chip->green	= 1;
+	chip->blue	= 1;
+
+	ret = lp5521_register_sysfs(client);
+	if (ret)
+		dev_err(&client->dev, "lp5521 registering sysfs failed \n");
+
+	return ret;
+
+fail1:
+	kfree(chip);
+	return ret;
+}
+
+static int lp5521_remove(struct i2c_client *client)
+{
+	struct lp5521_chip *chip = i2c_get_clientdata(client);
+
+	lp5521_unregister_sysfs(client);
+	kfree(chip);
+
+	return 0;
+}
+
+static const struct i2c_device_id lp5521_id[] = {
+	{ LP5521_DRIVER_NAME, 0},
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, lp5521_id);
+
+static struct i2c_driver lp5521_driver = {
+	.driver = {
+		.name	= LP5521_DRIVER_NAME,
+	},
+	.probe		= lp5521_probe,
+	.remove		= __devexit_p(lp5521_remove),
+	.id_table	= lp5521_id,
+};
+
+static int __init lp5521_init(void)
+{
+	return i2c_add_driver(&lp5521_driver);
+}
+
+static void __exit lp5521_exit(void)
+{
+	i2c_del_driver(&lp5521_driver);
+}
+
+MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>");
+MODULE_DESCRIPTION("lp5521 LED driver");
+MODULE_LICENSE("GPL");
+
+module_init(lp5521_init);
+module_exit(lp5521_exit);
-- 
1.6.0.1.141.g445ca


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

* [PATCH 03/33] add omap-sha1 driver
       [not found]   ` <1220116593-862-3-git-send-email-me@felipebalbi.com>
@ 2008-08-30 17:16     ` Felipe Balbi
  2008-08-30 17:16       ` [PATCH 04/33] add omap gpio expander driver Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/crypto/Kconfig         |    7 +
 drivers/crypto/Makefile        |    1 +
 drivers/crypto/omap-sha1-md5.c |  575 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 583 insertions(+), 0 deletions(-)
 create mode 100644 drivers/crypto/omap-sha1-md5.c

diff --git a/drivers/crypto/Kconfig b/drivers/crypto/Kconfig
index e522144..7fb9494 100644
--- a/drivers/crypto/Kconfig
+++ b/drivers/crypto/Kconfig
@@ -83,6 +83,13 @@ config ZCRYPT_MONOLITHIC
 	  that contains all parts of the crypto device driver (ap bus,
 	  request router and all the card drivers).
 
+config OMAP_SHA1_MD5
+	tristate "Support for OMAP SHA1/MD5 hw engine"
+	depends on ARCH_OMAP24XX && CRYPTO_SHA1 && CRYPTO_MD5
+	help
+	  OMAP processors have SHA1/MD5 module accelerator. Select this if you
+	  want to use the OMAP module for SHA1/MD5 algorithms.
+
 config CRYPTO_SHA1_S390
 	tristate "SHA1 digest algorithm"
 	depends on S390
diff --git a/drivers/crypto/Makefile b/drivers/crypto/Makefile
index 73557b2..5da41a1 100644
--- a/drivers/crypto/Makefile
+++ b/drivers/crypto/Makefile
@@ -1,6 +1,7 @@
 obj-$(CONFIG_CRYPTO_DEV_PADLOCK_AES) += padlock-aes.o
 obj-$(CONFIG_CRYPTO_DEV_PADLOCK_SHA) += padlock-sha.o
 obj-$(CONFIG_CRYPTO_DEV_GEODE) += geode-aes.o
+obj-$(CONFIG_OMAP_SHA1_MD5) += omap-sha1-md5.o
 obj-$(CONFIG_CRYPTO_DEV_HIFN_795X) += hifn_795x.o
 obj-$(CONFIG_CRYPTO_DEV_TALITOS) += talitos.o
 obj-$(CONFIG_CRYPTO_DEV_IXP4XX) += ixp4xx_crypto.o
diff --git a/drivers/crypto/omap-sha1-md5.c b/drivers/crypto/omap-sha1-md5.c
new file mode 100644
index 0000000..6d77703
--- /dev/null
+++ b/drivers/crypto/omap-sha1-md5.c
@@ -0,0 +1,575 @@
+/*
+ * Cryptographic API.
+ *
+ * Support for OMAP SHA1/MD5 HW acceleration.
+ *
+ * Copyright (c) 2007 Instituto Nokia de Tecnologia - INdT
+ * Author: David Cohen <david.cohen@indt.org.br>
+ *
+ * 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.
+ *
+ * This driver is based on padlock-sha.c driver.
+ */
+
+#include <asm/arch-omap/irqs.h>
+#include <crypto/algapi.h>
+#include <crypto/sha.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/cryptohash.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/clk.h>
+#include <linux/irq.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+
+#define SHA_REG_DIGEST(x)		(0x00 + ((x) * 0x04))
+#define SHA_REG_DIN(x)			(0x1C + ((x) * 0x04))
+
+#define SHA1_MD5_BLOCK_SIZE		SHA1_BLOCK_SIZE
+#define MD5_DIGEST_SIZE			16
+
+#define SHA_REG_DIGCNT			0x14
+
+#define SHA_REG_CTRL			0x18
+#define SHA_REG_CTRL_LENGTH		(0xFFFFFFFF << 5)
+#define SHA_REG_CTRL_CLOSE_HASH		(1 << 4)
+#define SHA_REG_CTRL_ALGO_CONST		(1 << 3)
+#define SHA_REG_CTRL_ALGO		(1 << 2)
+#define SHA_REG_CTRL_INPUT_READY	(1 << 1)
+#define SHA_REG_CTRL_OUTPUT_READY	(1 << 0)
+
+#define SHA_REG_REV			0x5C
+#define SHA_REG_REV_MAJOR		0xF0
+#define SHA_REG_REV_MINOR		0x0F
+
+#define SHA_REG_MASK			0x60
+#define SHA_REG_MASK_DMA_EN		(1 << 3)
+#define SHA_REG_MASK_IT_EN		(1 << 2)
+#define SHA_REG_MASK_SOFTRESET		(1 << 1)
+#define SHA_REG_AUTOIDLE		(1 << 0)
+
+#define SHA_REG_SYSSTATUS		0x64
+#define SHA_REG_SYSSTATUS_RESETDONE	(1 << 0)
+
+#define DRIVER_NAME			"OMAP SHA1/MD5"
+
+struct omap_sha1_md5_ctx {
+	unsigned int		type_algo;
+	unsigned int		bufcnt;
+	unsigned int		digcnt;
+	int			algo_const;
+	int			bypass;
+	int			digsize;
+	u8			hash[SHA1_DIGEST_SIZE];
+	u8			buffer[SHA1_BLOCK_SIZE];
+	struct			hash_desc fallback;
+};
+
+struct omap_sha1_md5_dev {
+	unsigned long		base_address;
+	int			irq;
+	int			digready;
+	struct clk		*sha1_ick;
+	struct omap_sha1_md5_ctx
+				*hw_ctx;
+	struct device		*dev;
+	wait_queue_head_t	wq;
+};
+
+static struct omap_sha1_md5_dev *sha1_md5_data;
+
+#define SHA_REG_IOADDR(d, x) (void *)IO_ADDRESS((d)->base_address + (x))
+
+static u32 omap_sha1_md5_read(struct omap_sha1_md5_dev *data, u32 offset)
+{
+	return __raw_readl(SHA_REG_IOADDR(data, offset));
+}
+
+static void omap_sha1_md5_write(struct omap_sha1_md5_dev *data,
+					u32 value, u32 offset)
+{
+	__raw_writel(value, SHA_REG_IOADDR(data, offset));
+}
+
+static void omap_sha1_md5_write_mask(struct omap_sha1_md5_dev *data,
+					u32 value, u32 mask, u32 address)
+{
+	u32 val;
+
+	val = omap_sha1_md5_read(data, address);
+	val &= ~mask;
+	val |= value;
+	omap_sha1_md5_write(data, val, address);
+}
+
+static inline void omap_sha1_md5_enable_clk(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+
+	clk_enable(data->sha1_ick);
+}
+
+static inline void omap_sha1_md5_disable_clk(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+
+	clk_disable(data->sha1_ick);
+}
+
+static void omap_sha1_md5_copy_hash(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+
+	u32 *hash = (u32 *)ctx->hash;
+
+	if (ctx->type_algo) {
+		/* SHA1 results are in big endian */
+		hash[0] = be32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(0)));
+		hash[1] = be32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(1)));
+		hash[2] = be32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(2)));
+		hash[3] = be32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(3)));
+		hash[4] = be32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(4)));
+	} else {
+		/* MD5 results are in little endian */
+		hash[0] = le32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(0)));
+		hash[1] = le32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(1)));
+		hash[2] = le32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(2)));
+		hash[3] = le32_to_cpu(
+				omap_sha1_md5_read(data, SHA_REG_DIGEST(3)));
+	}
+}
+
+static void omap_sha1_md5_bypass(struct crypto_tfm *tfm,
+				u8 *data, unsigned int length)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+
+	if (unlikely(!ctx->bypass))
+		return;
+
+	if (ctx->bypass == 1) {
+		crypto_hash_init(&ctx->fallback);
+		ctx->bypass++;
+	}
+
+	if (length) {
+		struct scatterlist sg;
+
+		sg_set_buf(&sg, data, length);
+		crypto_hash_update(&ctx->fallback, &sg, sg.length);
+	}
+}
+
+static void omap_sha1_md5_digest_buffer(struct crypto_tfm *tfm,
+				u8 *buf, unsigned int len, int close_hash)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+	unsigned int algo_const = 0;
+	int c;
+	u32 *buffer = (u32 *)buf;
+
+	if (unlikely(ctx->bypass)) {
+		omap_sha1_md5_bypass(tfm, buf, len);
+		return;
+	}
+
+	if (unlikely(ctx->algo_const)) {
+		algo_const = SHA_REG_CTRL_ALGO_CONST;
+		ctx->algo_const = 0;
+	} else
+		omap_sha1_md5_write(data, ctx->digcnt, SHA_REG_DIGCNT);
+
+	if (unlikely(close_hash))
+		close_hash = SHA_REG_CTRL_CLOSE_HASH;
+
+	/* Setting ALGO_CONST only for the first iteration
+	 * and CLOSE_HASH only for the last one. */
+	omap_sha1_md5_write_mask(data,
+			ctx->type_algo | algo_const | close_hash | (len << 5),
+			SHA_REG_CTRL_ALGO_CONST | SHA_REG_CTRL_CLOSE_HASH |
+			SHA_REG_CTRL_ALGO | SHA_REG_CTRL_LENGTH,
+			SHA_REG_CTRL);
+
+	ctx->digcnt += len;
+	while (!(omap_sha1_md5_read(data, SHA_REG_CTRL)
+		& SHA_REG_CTRL_INPUT_READY));
+
+	if (len % 4)
+		len = (len/4) + 1;
+	else
+		len /= 4;
+	for (c = 0; c < len; c++)
+		omap_sha1_md5_write(data, buffer[c], SHA_REG_DIN(c));
+}
+
+static void omap_sha1_md5_append_buffer(struct crypto_tfm *tfm,
+				const uint8_t *data, unsigned int length)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+
+	BUG_ON((ctx->bufcnt + length) > SHA1_MD5_BLOCK_SIZE);
+
+	memcpy(&ctx->buffer[ctx->bufcnt], data, length);
+	ctx->bufcnt += length;
+}
+
+static void omap_sha1_md5_dia_update(struct crypto_tfm *tfm,
+				const uint8_t *data, unsigned int length)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+
+	/* We need to save the last buffer <= 64 to digest it with
+	 * CLOSE_HASH = 1 */
+	if (ctx->bufcnt && ((ctx->bufcnt + length) > SHA1_MD5_BLOCK_SIZE)) {
+		unsigned int c = SHA1_MD5_BLOCK_SIZE - ctx->bufcnt;
+
+		omap_sha1_md5_append_buffer(tfm, data, c);
+		data += c;
+		length -= c;
+		if (length) {
+			ctx->bufcnt = 0;
+			omap_sha1_md5_digest_buffer(tfm, ctx->buffer,
+					SHA1_MD5_BLOCK_SIZE, 0);
+		}
+	}
+
+	while (length > SHA1_MD5_BLOCK_SIZE) {
+		/* Revisit: use DMA here */
+		omap_sha1_md5_digest_buffer(tfm, (u8 *)data,
+				SHA1_MD5_BLOCK_SIZE, 0);
+		length -= SHA1_MD5_BLOCK_SIZE;
+		data += SHA1_MD5_BLOCK_SIZE;
+	}
+
+	if (length)
+		omap_sha1_md5_append_buffer(tfm, data, length);
+}
+
+static void omap_sha1_md5_start_reset(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+
+	omap_sha1_md5_write_mask(data, SHA_REG_MASK_SOFTRESET,
+			SHA_REG_MASK_SOFTRESET, SHA_REG_MASK);
+}
+
+static void omap_sha1_md5_wait_reset(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+
+	while (!(omap_sha1_md5_read(data, SHA_REG_SYSSTATUS)
+			& SHA_REG_SYSSTATUS_RESETDONE));
+}
+
+static void omap_sha1_md5_dia_init(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+
+	if (unlikely(data->hw_ctx))
+		ctx->bypass = 1;
+	else {
+		data->hw_ctx = ctx;
+		ctx->bypass = 0;
+		omap_sha1_md5_enable_clk(tfm);
+		omap_sha1_md5_start_reset(tfm);
+		data->digready = 0;
+	}
+
+	if (ctx->bypass) {
+		omap_sha1_md5_bypass(tfm, NULL, 0);
+		return;
+	}
+
+	ctx->algo_const = 1;
+	ctx->bufcnt = 0;
+	ctx->digcnt = 0;
+
+	omap_sha1_md5_wait_reset(tfm);
+	omap_sha1_md5_write_mask(data, SHA_REG_MASK_IT_EN,
+		SHA_REG_MASK_DMA_EN | SHA_REG_MASK_IT_EN, SHA_REG_MASK);
+}
+
+static void omap_sha1_md5_dia_final(struct crypto_tfm *tfm, uint8_t *out)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+	int digsize = ctx->digsize;
+
+	/* The buffer should be >= 9 */
+	if (((ctx->digcnt + ctx->bufcnt) < 9) && !ctx->bypass)
+		ctx->bypass = 1;
+
+	omap_sha1_md5_digest_buffer(tfm, ctx->buffer, ctx->bufcnt, 1);
+
+	if (unlikely(ctx->bypass)) {
+		crypto_hash_final(&ctx->fallback, out);
+		ctx->bypass = 0;
+		goto bypass;
+	} else
+		data->digready = 1;
+
+	wait_event_interruptible(data->wq, (data->digready == 2));
+	omap_sha1_md5_copy_hash(tfm);
+
+	memcpy(out, ctx->hash, digsize);
+
+bypass:
+	if (data->hw_ctx == ctx) {
+		omap_sha1_md5_disable_clk(tfm);
+		data->hw_ctx = NULL;
+	}
+}
+
+static irqreturn_t omap_sha1_md5_irq(int irq, void *dev_id)
+{
+	struct omap_sha1_md5_dev *data = dev_id;
+
+	omap_sha1_md5_write_mask(data, SHA_REG_CTRL_OUTPUT_READY,
+			SHA_REG_CTRL_OUTPUT_READY, SHA_REG_CTRL);
+
+	if (likely(!data->digready))
+		return IRQ_HANDLED;
+
+	if (data->hw_ctx == NULL) {
+		dev_err(data->dev, "unknown interrupt.\n");
+		return IRQ_HANDLED;
+	}
+
+	data->digready = 2;
+	wake_up_interruptible(&data->wq);
+
+	return IRQ_HANDLED;
+}
+
+static int omap_sha1_md5_cra_init(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+	struct omap_sha1_md5_dev *data = sha1_md5_data;
+	const char *fallback_driver_name = tfm->__crt_alg->cra_name;
+	struct crypto_hash *fallback_tfm;
+
+	/* Allocate a fallback and abort if it failed. */
+	fallback_tfm = crypto_alloc_hash(fallback_driver_name, 0,
+					 CRYPTO_ALG_ASYNC |
+					 CRYPTO_ALG_NEED_FALLBACK);
+	if (IS_ERR(fallback_tfm)) {
+		dev_err(data->dev, "fallback driver '%s' could not be"
+				"loaded.\n", fallback_driver_name);
+		return PTR_ERR(fallback_tfm);
+	}
+
+	ctx->fallback.tfm = fallback_tfm;
+
+	return 0;
+}
+
+static int omap_sha1_cra_init(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+
+	ctx->type_algo = SHA_REG_CTRL_ALGO;
+	ctx->digsize = SHA1_DIGEST_SIZE;
+
+	return omap_sha1_md5_cra_init(tfm);
+}
+
+static int omap_md5_cra_init(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+
+	ctx->type_algo = 0;
+	ctx->digsize = MD5_DIGEST_SIZE;
+
+	return omap_sha1_md5_cra_init(tfm);
+}
+
+static void omap_sha1_md5_cra_exit(struct crypto_tfm *tfm)
+{
+	struct omap_sha1_md5_ctx *ctx = crypto_tfm_ctx(tfm);
+
+	crypto_free_hash(ctx->fallback.tfm);
+	ctx->fallback.tfm = NULL;
+}
+
+static struct crypto_alg omap_sha1_alg = {
+	.cra_name		=	"sha1",
+	.cra_driver_name	=	"omap-sha1",
+	.cra_flags		=	CRYPTO_ALG_TYPE_DIGEST |
+					CRYPTO_ALG_NEED_FALLBACK,
+	.cra_blocksize		=	SHA1_MD5_BLOCK_SIZE,
+	.cra_ctxsize		=	sizeof(struct omap_sha1_md5_ctx),
+	.cra_module		=	THIS_MODULE,
+	.cra_list		=	LIST_HEAD_INIT(omap_sha1_alg.cra_list),
+	.cra_init		=	omap_sha1_cra_init,
+	.cra_exit		=	omap_sha1_md5_cra_exit,
+	.cra_u			=	{
+		.digest = {
+			.dia_digestsize	=	SHA1_DIGEST_SIZE,
+			.dia_init	=	omap_sha1_md5_dia_init,
+			.dia_update	=	omap_sha1_md5_dia_update,
+			.dia_final	=	omap_sha1_md5_dia_final,
+		}
+	}
+};
+
+static struct crypto_alg omap_md5_alg = {
+	.cra_name		=	"md5",
+	.cra_driver_name	=	"omap-md5",
+	.cra_flags		=	CRYPTO_ALG_TYPE_DIGEST |
+					CRYPTO_ALG_NEED_FALLBACK,
+	.cra_blocksize		=	SHA1_MD5_BLOCK_SIZE,
+	.cra_ctxsize		=	sizeof(struct omap_sha1_md5_ctx),
+	.cra_module		=	THIS_MODULE,
+	.cra_list		=	LIST_HEAD_INIT(omap_md5_alg.cra_list),
+	.cra_init		=	omap_md5_cra_init,
+	.cra_exit		=	omap_sha1_md5_cra_exit,
+	.cra_u			=	{
+		.digest = {
+			.dia_digestsize	=	MD5_DIGEST_SIZE,
+			.dia_init	=	omap_sha1_md5_dia_init,
+			.dia_update	=	omap_sha1_md5_dia_update,
+			.dia_final	=	omap_sha1_md5_dia_final,
+		}
+	}
+};
+
+static int omap_sha1_md5_probe(struct platform_device *pdev)
+{
+	struct omap_sha1_md5_dev *data;
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	int rc;
+
+	rc = crypto_register_alg(&omap_sha1_alg);
+	if (rc)
+		goto sha1_err;
+	rc = crypto_register_alg(&omap_md5_alg);
+	if (rc)
+		goto md5_err;
+
+	data = kzalloc(sizeof(struct omap_sha1_md5_dev), GFP_KERNEL);
+	if (data == NULL) {
+		dev_err(dev, "unable to alloc data struct.\n");
+		goto data_err;
+	}
+	platform_set_drvdata(pdev, data);
+	data->dev = dev;
+
+	/* Get the base address */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res) {
+		dev_err(dev, "invalid resource type\n");
+		rc = -ENODEV;
+		goto res_err;
+	}
+	data->base_address = res->start;
+
+	/* Set the private data */
+	sha1_md5_data = data;
+
+	/* Get the IRQ */
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (!res) {
+		dev_err(dev, "invalid resource type\n");
+		rc = -ENODEV;
+		goto res_err;
+	}
+	data->irq = res->start;
+
+	rc = request_irq(res->start, omap_sha1_md5_irq,
+			IRQF_TRIGGER_LOW, DRIVER_NAME, data);
+	if (rc) {
+		dev_err(dev, "unable to request irq.\n");
+		goto res_err;
+	}
+
+	/* Initializing the clock */
+	data->sha1_ick = clk_get(0, "sha_ick");
+	if (!data->sha1_ick) {
+		dev_err(dev, "clock intialization failed.\n");
+		rc = -ENODEV;
+		goto clk_err;
+	}
+
+	init_waitqueue_head(&data->wq);
+
+	dev_info(dev, "hw accel on OMAP rev %u.%u\n",
+		(omap_sha1_md5_read(data, SHA_REG_REV) & SHA_REG_REV_MAJOR)>>4,
+		omap_sha1_md5_read(data, SHA_REG_REV) & SHA_REG_REV_MINOR);
+
+	return 0;
+
+clk_err:
+	free_irq(data->irq, data);
+res_err:
+	kfree(data);
+data_err:
+	crypto_unregister_alg(&omap_md5_alg);
+md5_err:
+	crypto_unregister_alg(&omap_sha1_alg);
+sha1_err:
+	dev_err(dev, "initialization failed.\n");
+	return rc;
+}
+
+static int omap_sha1_md5_remove(struct platform_device *pdev)
+{
+	struct omap_sha1_md5_dev *data = platform_get_drvdata(pdev);
+
+	free_irq(data->irq, data);
+	kfree(data);
+	crypto_unregister_alg(&omap_sha1_alg);
+	crypto_unregister_alg(&omap_md5_alg);
+
+	return 0;
+}
+
+static struct platform_driver omap_sha1_md5_driver = {
+	.probe	= omap_sha1_md5_probe,
+	.remove	= omap_sha1_md5_remove,
+	.driver	= {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init omap_sha1_md5_init(void)
+{
+	int ret;
+
+	ret = platform_driver_register(&omap_sha1_md5_driver);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static void __exit omap_sha1_md5_exit(void)
+{
+	platform_driver_unregister(&omap_sha1_md5_driver);
+}
+
+module_init(omap_sha1_md5_init);
+module_exit(omap_sha1_md5_exit);
+
+MODULE_DESCRIPTION("OMAP SHA1/MD5 hw acceleration support.");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Cohen");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 04/33] add omap gpio expander driver
  2008-08-30 17:16     ` [PATCH 03/33] add omap-sha1 driver Felipe Balbi
@ 2008-08-30 17:16       ` Felipe Balbi
  2008-08-30 17:16         ` [PATCH 05/33] add tlv320aic23 driver Felipe Balbi
  2008-08-31 20:51         ` [PATCH 04/33] add omap gpio expander driver David Brownell
  0 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/i2c/chips/Kconfig              |    7 +++
 drivers/i2c/chips/Makefile             |    1 +
 drivers/i2c/chips/gpio_expander_omap.c |   71 ++++++++++++++++++++++++++++++++
 3 files changed, 79 insertions(+), 0 deletions(-)
 create mode 100644 drivers/i2c/chips/gpio_expander_omap.c

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index 193c3ae..a6269c0 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -137,6 +137,13 @@ config TPS65010
 	  This driver can also be built as a module.  If so, the module
 	  will be called tps65010.
 
+config GPIOEXPANDER_OMAP
+	bool "GPIO Expander PCF8574PWR for OMAP"
+	depends on I2C && (ARCH_OMAP16XX || ARCH_OMAP24XX)
+	help
+	  If you say yes here you get support for I/O expander calls
+	  to configure IrDA, Camera and audio devices.
+
 config SENSORS_MAX6875
 	tristate "Maxim MAX6875 Power supply supervisor"
 	depends on EXPERIMENTAL
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 987f600..616b3f9 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_PCF8575)		+= pcf8575.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
 obj-$(CONFIG_TPS65010)		+= tps65010.o
+obj-$(CONFIG_GPIOEXPANDER_OMAP)	+= gpio_expander_omap.o
 obj-$(CONFIG_MENELAUS)		+= menelaus.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
 obj-$(CONFIG_LP5521)		+= lp5521.o
diff --git a/drivers/i2c/chips/gpio_expander_omap.c b/drivers/i2c/chips/gpio_expander_omap.c
new file mode 100644
index 0000000..dfe7f04
--- /dev/null
+++ b/drivers/i2c/chips/gpio_expander_omap.c
@@ -0,0 +1,71 @@
+/*
+ * drivers/i2c/chips/gpio_expander_omap.c
+ *
+ * Copyright (C) 2004 Texas Instruments Inc
+ * Author:
+ *
+ * gpio expander is used to configure IrDA, camera and audio devices on omap 1710 processor.
+ *
+ * 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/types.h>
+#include <linux/i2c.h>
+#include <linux/errno.h>
+
+int read_gpio_expa(u8 * val, int addr);
+int write_gpio_expa(u8 val, int addr);
+
+int write_gpio_expa(u8 val, int addr)
+{
+	struct i2c_adapter *adap;
+	int err;
+	struct i2c_msg msg[1];
+	unsigned char data[1];
+
+	adap = i2c_get_adapter(1);
+	if (!adap)
+		return -ENODEV;
+	msg->addr = addr;	/* I2C address of GPIO EXPA */
+	msg->flags = 0;
+	msg->len = 1;
+	msg->buf = data;
+	data[0] = val;
+	err = i2c_transfer(adap, msg, 1);
+	if (err >= 0)
+		return 0;
+	return err;
+}
+
+/* Read from I/O EXPANDER on the H3 board.
+ * The IO expanders need an independent I2C client driver.
+ */
+
+int read_gpio_expa(u8 * val, int addr)
+{
+	struct i2c_adapter *adap;
+	int err;
+	struct i2c_msg msg[1];
+	unsigned char data[1];
+
+	adap = i2c_get_adapter(1);
+	if (!adap)
+		return -ENODEV;
+	msg->addr = addr;	/* I2C address of GPIO EXPA */
+	msg->flags = I2C_M_RD;
+	msg->len = 2;
+	msg->buf = data;
+	err = i2c_transfer(adap, msg, 1);
+	*val = data[0];
+
+	if (err >= 0)
+		return 0;
+	return err;
+}
+
+EXPORT_SYMBOL(read_gpio_expa);
+EXPORT_SYMBOL(write_gpio_expa);
+
-- 
1.6.0.1.141.g445ca


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

* [PATCH 05/33] add tlv320aic23 driver
  2008-08-30 17:16       ` [PATCH 04/33] add omap gpio expander driver Felipe Balbi
@ 2008-08-30 17:16         ` Felipe Balbi
  2008-08-30 17:16           ` [PATCH 06/33] add tsl2563 driver Felipe Balbi
  2008-09-01  7:22           ` [PATCH 05/33] add tlv320aic23 driver Jarkko Nikula
  2008-08-31 20:51         ` [PATCH 04/33] add omap gpio expander driver David Brownell
  1 sibling, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/i2c/chips/Kconfig       |    7 +
 drivers/i2c/chips/Makefile      |    1 +
 drivers/i2c/chips/tlv320aic23.c |  675 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 683 insertions(+), 0 deletions(-)
 create mode 100644 drivers/i2c/chips/tlv320aic23.c

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index a6269c0..75d8a26 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -137,6 +137,13 @@ config TPS65010
 	  This driver can also be built as a module.  If so, the module
 	  will be called tps65010.
 
+config SENSORS_TLV320AIC23
+	tristate "Texas Instruments TLV320AIC23 Codec"
+	depends on I2C && I2C_OMAP
+	help
+	  If you say yes here you get support for the I2C control
+	  interface for Texas Instruments TLV320AIC23 audio codec.
+
 config GPIOEXPANDER_OMAP
 	bool "GPIO Expander PCF8574PWR for OMAP"
 	depends on I2C && (ARCH_OMAP16XX || ARCH_OMAP24XX)
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 616b3f9..32096cb 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_PCF8575)		+= pcf8575.o
 obj-$(CONFIG_SENSORS_PCF8591)	+= pcf8591.o
 obj-$(CONFIG_ISP1301_OMAP)	+= isp1301_omap.o
 obj-$(CONFIG_TPS65010)		+= tps65010.o
+obj-$(CONFIG_SENSORS_TLV320AIC23) += tlv320aic23.o
 obj-$(CONFIG_GPIOEXPANDER_OMAP)	+= gpio_expander_omap.o
 obj-$(CONFIG_MENELAUS)		+= menelaus.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
diff --git a/drivers/i2c/chips/tlv320aic23.c b/drivers/i2c/chips/tlv320aic23.c
new file mode 100644
index 0000000..10671e8
--- /dev/null
+++ b/drivers/i2c/chips/tlv320aic23.c
@@ -0,0 +1,675 @@
+/*
+ *   Texas Instrumens TLV320AIC23 audio codec's i2c interface.
+ *
+ *   Copyright (c) by Kai Svahn <kai.svahn@nokia.com>
+ *   Copyright (c) by Jussi Laako <jussi.laako@nokia.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.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <asm/io.h>
+#include <mach/aic23.h>
+#include <mach/mcbsp.h>
+
+#define TLV320AIC23_VERSION	"1.8"
+#define TLV320AIC23_DATE	"10-Feb-2006"
+#define MAX_VOL			100
+#define MIN_VOL			0
+#define MAX_GAIN		100
+#define MIN_GAIN		0
+#define OUTPUT_VOLUME_MIN       LHV_MIN
+#define OUTPUT_VOLUME_MAX       LHV_MAX
+#define OUTPUT_VOLUME_RANGE     (OUTPUT_VOLUME_MAX - OUTPUT_VOLUME_MIN)
+#define INPUT_VOLUME_MIN 	LIV_MIN
+#define INPUT_VOLUME_MAX 	LIV_MAX
+#define INPUT_VOLUME_RANGE 	(INPUT_VOLUME_MAX - INPUT_VOLUME_MIN)
+
+/* I2C Addresses to scan */
+static unsigned short normal_i2c[] = { TLV320AIC23ID1, TLV320AIC23ID2, \
+				       I2C_CLIENT_END };
+/*static unsigned short normal_i2c_range[] = { I2C_CLIENT_END };*/
+
+/* This makes all addr_data:s */
+I2C_CLIENT_INSMOD;
+
+static struct i2c_driver aic23_driver;
+static struct i2c_client *new_client;
+static int selftest;
+static struct platform_device audio_i2c_device;
+
+static struct aic23_info {
+	u16 volume_reg_left;
+	u16 volume_reg_right;
+	u16 input_gain_reg_left;
+	u16 input_gain_reg_right;
+	u16 power;			/* For POWER_DOWN_CONTROL_ADDR */
+	u16 mask;			/* For ANALOG_AUDIO_CONTROL_ADDR */
+	int mic_loopback;
+	int mic_enable;
+	int sta;
+	int power_down;
+	int initialized;
+} aic23_info_l;
+
+static int _aic23_write_value(struct i2c_client *client, u8 reg, u16 value)
+{
+	u8 val, wreg;
+
+	/* TLV320AIC23 has 7 bit address and 9 bits of data
+	 * so we need to switch one data bit into reg and rest
+	 * of data into val
+	 */
+
+	wreg = (reg << 1);
+	val = (0x01 & (value >> 8));
+	wreg = (wreg | val);
+	val = (0x00ff & value);
+
+	return i2c_smbus_write_byte_data(client, wreg, val);
+}
+
+int aic23_write_value(u8 reg, u16 value)
+{
+	static struct i2c_client *client;
+	client = new_client;
+	_aic23_write_value(client, reg, value);
+
+	return 0;
+}
+
+/*
+ * Configures the McBSP3 which is used to send clock to the AIC23 codec.
+ * The input clock rate from DSP is 12MHz.
+ * The DSP clock must be on before this is called.
+ */
+static int omap_mcbsp3_aic23_clock_init(void)
+{
+	u16 w;
+
+	/* enable 12MHz clock to mcbsp 1 & 3 */
+	__raw_writew(__raw_readw(DSP_IDLECT2) | (1<<1), DSP_IDLECT2);
+	__raw_writew(__raw_readw(DSP_RSTCT2) | 1 | 1<<1, DSP_RSTCT2);
+
+	/* disable sample rate generator */
+	OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SPCR1, 0x0000);
+	OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SPCR2, 0x0000);
+
+	/* pin control register */
+	OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, PCR0,(CLKXM | CLKXP | CLKRP));
+
+	/* configure srg to send 12MHz pulse from dsp peripheral clock */
+	OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SRGR1, 0x0000);
+	OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SRGR2, CLKSM);
+
+	/* enable sample rate generator */
+	w = OMAP_MCBSP_READ(OMAP1610_MCBSP3_BASE, SPCR2);
+	OMAP_MCBSP_WRITE(OMAP1610_MCBSP3_BASE, SPCR2, (w | FREE | GRST));
+	printk("Clock enabled to MCBSP1 & 3 \n");
+
+	return 0;
+}
+
+static int aic23_detect_client(struct i2c_adapter *adapter, int address,
+				     int kind)
+{
+	int err = 0;
+	const char *client_name = "TLV320AIC23 Audio Codec";
+
+	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA |
+				     I2C_FUNC_SMBUS_WRITE_BYTE)) {
+		printk(KERN_WARNING "%s functionality check failed\n",
+		       client_name);
+		return err;
+	}
+
+	if (!(new_client = kmalloc(sizeof(struct i2c_client),
+				   GFP_KERNEL))) {
+		err = -ENOMEM;
+		printk(KERN_WARNING "Couldn't allocate memory for %s\n",
+		       client_name);
+		return err;
+	}
+
+	memset(new_client, 0x00, sizeof(struct i2c_client));
+	new_client->addr = address;
+	new_client->adapter = adapter;
+	new_client->driver = &aic23_driver;
+	new_client->flags = 0;
+	strlcpy(new_client->name, client_name, I2C_NAME_SIZE);
+
+	if ((err = i2c_attach_client(new_client))) {
+		printk(KERN_WARNING "Couldn't attach %s\n", client_name);
+		kfree(new_client);
+		return err;
+	}
+
+	if (platform_device_register(&audio_i2c_device)) {
+		printk(KERN_WARNING "Failed to register audio i2c device\n");
+		selftest = -ENODEV;
+		return selftest;
+	}
+	/* FIXME: Do in board-specific file */
+	omap_mcbsp3_aic23_clock_init();
+
+	if (!aic23_info_l.power_down)
+		aic23_power_up();
+	aic23_info_l.initialized = 1;
+
+	return 0;
+}
+
+static int aic23_detach_client(struct i2c_client *client)
+{
+	int err;
+
+	platform_device_unregister(&audio_i2c_device);
+
+	if ((err = i2c_detach_client(client))) {
+		printk("aic23.o: Client deregistration failed, \
+		       client not detached.\n");
+		return err;
+	}
+	kfree(client);
+	return 0;
+}
+
+static int aic23_attach_adapter(struct i2c_adapter *adapter)
+{
+	int res;
+
+	res = i2c_probe(adapter, &addr_data, &aic23_detect_client);
+	return res;
+}
+
+static struct i2c_driver aic23_driver = {
+	.driver = {
+		.name	= "OMAP+TLV320AIC23 codec",
+		/*.flags	= I2C_DF_NOTIFY,*/
+	},
+	.id		= I2C_DRIVERID_MISC, /* Experimental ID */
+	.attach_adapter	= aic23_attach_adapter,
+	.detach_client	= aic23_detach_client,
+};
+
+static void update_volume_left(int volume)
+{
+	u16 val = 0;
+	val = ((volume * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN;
+	aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, val);
+	aic23_info_l.volume_reg_left = volume;
+}
+
+static void update_volume_right(int volume)
+{
+	u16 val = 0;
+	val = ((volume * OUTPUT_VOLUME_RANGE) / 100) + OUTPUT_VOLUME_MIN;
+	aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, val);
+	aic23_info_l.volume_reg_right = volume;
+}
+
+static void set_mic(int mic_en)
+{
+	u16 dg_ctrl;
+
+	if (mic_en) {
+		aic23_info_l.power = OSC_OFF | LINE_OFF;
+		dg_ctrl = ADCHP_ON;
+		aic23_info_l.mask &= ~MICM_MUTED;
+		aic23_info_l.mask |= MICB_20DB; /* STE_ENABLED */
+	} else {
+		aic23_info_l.power =
+			OSC_OFF | ADC_OFF | MIC_OFF | LINE_OFF;
+		dg_ctrl = 0x00;
+		aic23_info_l.mask =
+			DAC_SELECTED | INSEL_MIC | MICM_MUTED;
+	}
+	aic23_write_value(POWER_DOWN_CONTROL_ADDR,
+				aic23_info_l.power);
+	aic23_write_value(DIGITAL_AUDIO_CONTROL_ADDR, dg_ctrl);
+	aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR,
+				aic23_info_l.mask);
+	aic23_info_l.mic_enable = mic_en;
+
+	printk(KERN_INFO "aic23 mic state: %i\n", mic_en);
+}
+
+static void aic23_init_power(void)
+{
+	aic23_write_value(RESET_CONTROL_ADDR, 0x00);
+
+	if (aic23_info_l.initialized == 0) {
+		aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, LHV_MIN);
+		aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, LHV_MIN);
+	}
+	else {
+		update_volume_left(aic23_info_l.volume_reg_left);
+		update_volume_right(aic23_info_l.volume_reg_right);
+	}
+
+	aic23_info_l.mask = DAC_SELECTED | INSEL_MIC | MICM_MUTED;
+	aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR,
+			        aic23_info_l.mask);
+	aic23_write_value(DIGITAL_AUDIO_CONTROL_ADDR, 0x00);
+	aic23_write_value(DIGITAL_AUDIO_FORMAT_ADDR, LRP_ON | FOR_DSP);
+	aic23_write_value(SAMPLE_RATE_CONTROL_ADDR, USB_CLK_ON);
+	aic23_write_value(DIGITAL_INTERFACE_ACT_ADDR, ACT_ON);
+	aic23_info_l.power = OSC_OFF | ADC_OFF | MIC_OFF | LINE_OFF;
+	aic23_write_value(POWER_DOWN_CONTROL_ADDR,
+			        aic23_info_l.power);
+
+	/* enable mic input */
+	if (aic23_info_l.mic_enable)
+		set_mic(aic23_info_l.mic_enable);
+
+	printk(KERN_INFO "aic23_init_power() done\n");
+}
+
+void aic23_power_down(void)
+{
+	if (aic23_info_l.initialized) {
+		printk("aic23 powering down\n");
+		aic23_write_value(POWER_DOWN_CONTROL_ADDR, 0xff);
+	}
+	aic23_info_l.power_down = 1;
+}
+
+void aic23_power_up(void)
+{
+	if (aic23_info_l.initialized) {
+		printk("aic23 powering up\n");
+		aic23_init_power();
+	}
+	aic23_info_l.power_down = 0;
+}
+
+/*----------------------------------------------------------------------*/
+/*			sysfs initializations				*/
+/*----------------------------------------------------------------------*/
+
+static ssize_t store_volume_left(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	signed volume;
+
+	sscanf(buf, "%i", &volume);
+
+	if (volume < MIN_VOL) {
+		aic23_power_down();
+		return count;
+	} else if (volume > MIN_VOL && aic23_info_l.power_down) {
+		aic23_info_l.volume_reg_left = volume;
+		aic23_power_up();
+		return count;
+	}
+	if (volume > MAX_VOL)
+		volume = MAX_VOL;
+
+	update_volume_left(volume);
+	return count;
+}
+
+static ssize_t show_volume_left(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", aic23_info_l.volume_reg_left);
+}
+
+static DEVICE_ATTR(volume_left, S_IRUGO | S_IWUGO,
+		   show_volume_left, store_volume_left);
+
+static ssize_t store_volume_right(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	signed volume;
+
+	sscanf(buf, "%i", &volume);
+	if (volume < MIN_VOL) {
+		aic23_power_down();
+		return count;
+	} else if (volume > MIN_VOL && aic23_info_l.power_down) {
+		aic23_info_l.volume_reg_right = volume;
+		aic23_power_up();
+		return count;
+	}
+	if (volume > MAX_VOL)
+		volume = MAX_VOL;
+
+	update_volume_right(volume);
+	return count;
+}
+
+static ssize_t show_volume_right(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", aic23_info_l.volume_reg_right);
+}
+
+static DEVICE_ATTR(volume_right, S_IRUGO | S_IWUGO,
+		   show_volume_right, store_volume_right);
+
+static ssize_t store_gain_left(struct device *dev,
+			       struct device_attribute *attr,
+			       const char *buf, size_t count)
+{
+	u16 val = 0;
+	unsigned gain;
+
+	sscanf(buf, "%u", &gain);
+	if (gain > MAX_VOL)
+		gain = MAX_VOL;
+
+	val = ((gain * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN;
+	aic23_write_value(LEFT_LINE_VOLUME_ADDR, val);
+	aic23_info_l.input_gain_reg_left = gain;
+
+	return count;
+}
+
+static ssize_t show_gain_left(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", aic23_info_l.input_gain_reg_left);
+}
+
+static DEVICE_ATTR(gain_left, S_IRUGO | S_IWUSR, show_gain_left,
+		   store_gain_left);
+
+static ssize_t store_gain_right(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	u16 val = 0;
+	unsigned gain;
+
+	sscanf(buf, "%u", &gain);
+	if (gain > MAX_VOL)
+		gain = MAX_VOL;
+
+	val = ((gain * INPUT_VOLUME_RANGE) / 100) + INPUT_VOLUME_MIN;
+	aic23_write_value(RIGHT_LINE_VOLUME_ADDR, val);
+	aic23_info_l.input_gain_reg_right = gain;
+
+	return count;
+}
+
+static ssize_t show_gain_right(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%u\n", aic23_info_l.input_gain_reg_right);
+}
+
+static DEVICE_ATTR(gain_right, S_IRUGO | S_IWUSR, show_gain_right,
+		   store_gain_right);
+
+static ssize_t store_mic_loopback(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	int mic;
+
+	sscanf(buf, "%i", &mic);
+	if (mic > 0) {
+		aic23_write_value(POWER_DOWN_CONTROL_ADDR, \
+					OSC_OFF | ADC_OFF | LINE_OFF);
+		aic23_info_l.mask = STE_ENABLED | DAC_SELECTED \
+					  | INSEL_MIC | MICB_20DB;
+		aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR,
+					aic23_info_l.mask);
+		mic = 1;
+	}
+	else {
+		aic23_write_value(POWER_DOWN_CONTROL_ADDR, \
+					OSC_OFF | ADC_OFF | MIC_OFF | LINE_OFF);
+		mic = 0;
+	}
+	aic23_info_l.mic_loopback = mic;
+
+	return count;
+}
+
+static ssize_t show_mic_loopback(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%i\n", aic23_info_l.mic_loopback);
+}
+
+static DEVICE_ATTR(mic_loopback, S_IRUGO | S_IWUSR,
+		   show_mic_loopback, store_mic_loopback);
+
+static ssize_t store_st_attenuation(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	unsigned sta;
+	u16 tmp;
+
+	sscanf(buf, "%u", &sta);
+	if (sta > 3)
+		sta = 3;
+
+	tmp = aic23_info_l.mask;
+	tmp &= 0x3f;
+
+	aic23_info_l.mask =  tmp | STA_REG(sta);
+	aic23_write_value(ANALOG_AUDIO_CONTROL_ADDR,
+				aic23_info_l.mask);
+	aic23_info_l.sta = sta;
+
+	return count;
+}
+
+static ssize_t show_st_attenuation(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%i\n", aic23_info_l.sta);
+}
+
+static DEVICE_ATTR(st_attenuation, S_IRUGO | S_IWUSR,
+		   show_st_attenuation, store_st_attenuation);
+
+static ssize_t store_mic_enable(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	int mic;
+
+	sscanf(buf, "%i", &mic);
+	set_mic(mic);
+
+	return count;
+}
+
+static ssize_t show_mic_enable(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%i\n", aic23_info_l.mic_enable);
+}
+
+static DEVICE_ATTR(mic_enable, S_IRUGO | S_IWUSR,
+	show_mic_enable, store_mic_enable);
+
+static ssize_t show_audio_selftest(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%i\n", selftest);
+}
+
+static DEVICE_ATTR(audio_selftest, S_IRUGO | S_IWUSR,
+		show_audio_selftest, NULL);
+
+static int audio_i2c_probe(struct platform_device *dev)
+{
+	int r;
+
+	if ((r = device_create_file(&dev->dev, &dev_attr_volume_left)) != 0)
+		return r;
+	else if ((r = device_create_file(&dev->dev,
+		&dev_attr_volume_right)) != 0)
+		goto err_volume_left;
+	else if ((r = device_create_file(&dev->dev,
+		&dev_attr_gain_right)) != 0)
+		goto err_volume_right;
+	else if ((r = device_create_file(&dev->dev,
+		&dev_attr_gain_left)) != 0)
+		goto err_gain_right;
+	else if ((r = device_create_file(&dev->dev,
+		&dev_attr_mic_loopback)) != 0)
+		goto err_gain_left;
+	else if ((r = device_create_file(&dev->dev,
+		&dev_attr_mic_enable)) != 0)
+		goto err_mic_loopback;
+	else if ((r = device_create_file(&dev->dev,
+		&dev_attr_st_attenuation)) != 0)
+		goto err_mic_enable;
+	else if ((r = device_create_file(&dev->dev,
+		&dev_attr_audio_selftest)) != 0)
+		goto err_st_attenuation;
+	else
+		return r;
+
+err_st_attenuation:
+	device_remove_file(&dev->dev, &dev_attr_st_attenuation);
+err_mic_enable:
+	device_remove_file(&dev->dev, &dev_attr_mic_enable);
+err_mic_loopback:
+	device_remove_file(&dev->dev, &dev_attr_mic_loopback);
+err_gain_left:
+	device_remove_file(&dev->dev, &dev_attr_gain_left);
+err_gain_right:
+	device_remove_file(&dev->dev, &dev_attr_gain_right);
+err_volume_right:
+	device_remove_file(&dev->dev, &dev_attr_volume_right);
+err_volume_left:
+	device_remove_file(&dev->dev, &dev_attr_volume_left);
+
+	return r;
+}
+
+static int audio_i2c_remove(struct platform_device *dev)
+{
+	device_remove_file(&dev->dev, &dev_attr_st_attenuation);
+	device_remove_file(&dev->dev, &dev_attr_mic_enable);
+	device_remove_file(&dev->dev, &dev_attr_mic_loopback);
+	device_remove_file(&dev->dev, &dev_attr_gain_left);
+	device_remove_file(&dev->dev, &dev_attr_gain_right);
+	device_remove_file(&dev->dev, &dev_attr_volume_right);
+	device_remove_file(&dev->dev, &dev_attr_volume_left);
+
+	return 0;
+}
+
+/*----------------------------------------------------------------*/
+/*			PM functions				  */
+/*----------------------------------------------------------------*/
+
+static void audio_i2c_shutdown(struct platform_device *dev)
+{
+	/* Let's mute the codec before powering off to prevent
+	* glitch in the sound
+	*/
+	aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, LHV_MIN);
+	aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, LHV_MIN);
+	aic23_power_down();
+}
+
+static int audio_i2c_suspend(struct platform_device *dev, pm_message_t state)
+{
+	/* Let's mute the codec before powering off to prevent
+	 * glitch in the sound
+	 */
+	aic23_write_value(LEFT_CHANNEL_VOLUME_ADDR, LHV_MIN);
+	aic23_write_value(RIGHT_CHANNEL_VOLUME_ADDR, LHV_MIN);
+	aic23_power_down();
+
+	return 0;
+}
+
+static int audio_i2c_resume(struct platform_device *dev)
+{
+	aic23_power_up();
+
+	return 0;
+}
+
+static struct platform_driver audio_i2c_driver = {
+	.driver = {
+		.owner	= THIS_MODULE,
+		.name	= "audio-i2c",
+	},
+	.shutdown	= audio_i2c_shutdown,
+	.probe		= audio_i2c_probe,
+	.remove		= audio_i2c_remove,
+	.suspend	= audio_i2c_suspend,
+	.resume		= audio_i2c_resume,
+};
+
+static struct platform_device audio_i2c_device = {
+	.name		= "audio-i2c",
+	.id		= -1,
+};
+
+/*----------------------------------------------------------------*/
+
+static int __init aic23_init(void)
+{
+	selftest =  0;
+	aic23_info_l.initialized = 0;
+
+	if (i2c_add_driver(&aic23_driver)) {
+		printk("aic23 i2c: Driver registration failed, \
+		      module not inserted.\n");
+		selftest = -ENODEV;
+		return selftest;
+	}
+
+	if (platform_driver_register(&audio_i2c_driver)) {
+		printk(KERN_WARNING "Failed to register audio i2c driver\n");
+		selftest = -ENODEV;
+		return selftest;
+	}
+
+	printk("TLV320AIC23 I2C version %s (%s)\n",
+	       TLV320AIC23_VERSION, TLV320AIC23_DATE);
+
+	return selftest;
+}
+
+static void __exit aic23_exit(void)
+{
+	aic23_power_down();
+	i2c_del_driver(&aic23_driver);
+
+	platform_driver_unregister(&audio_i2c_driver);
+}
+
+MODULE_AUTHOR("Kai Svahn <kai.svahn@nokia.com>");
+MODULE_DESCRIPTION("I2C interface for TLV320AIC23 codec.");
+MODULE_LICENSE("GPL");
+
+module_init(aic23_init)
+module_exit(aic23_exit)
+
+EXPORT_SYMBOL(aic23_write_value);
+EXPORT_SYMBOL(aic23_power_up);
+EXPORT_SYMBOL(aic23_power_down);
-- 
1.6.0.1.141.g445ca


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

* [PATCH 06/33] add tsl2563 driver
  2008-08-30 17:16         ` [PATCH 05/33] add tlv320aic23 driver Felipe Balbi
@ 2008-08-30 17:16           ` Felipe Balbi
       [not found]             ` <1220116593-862-8-git-send-email-me@felipebalbi.com>
  2008-09-01  7:22           ` [PATCH 05/33] add tlv320aic23 driver Jarkko Nikula
  1 sibling, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/i2c/chips/Kconfig   |   10 +
 drivers/i2c/chips/Makefile  |    1 +
 drivers/i2c/chips/tsl2563.c |  739 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 750 insertions(+), 0 deletions(-)
 create mode 100644 drivers/i2c/chips/tsl2563.c

diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig
index 75d8a26..bd2a3a5 100644
--- a/drivers/i2c/chips/Kconfig
+++ b/drivers/i2c/chips/Kconfig
@@ -176,6 +176,16 @@ config SENSORS_TSL2550
 	  This driver can also be built as a module.  If so, the module
 	  will be called tsl2550.
 
+config SENSORS_TSL2563
+       tristate "Taos TSL2563 ambient light sensor"
+       depends on I2C && HWMON
+       help
+         If you say yes here you get support for the Taos TSL2563
+         ambient light sensor.
+
+         This driver can also be built as a module.  If so, the module
+         will be called tsl2563.
+
 config LP5521
 	tristate "LP5521 LED driver chip"
 	depends on I2C
diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile
index 32096cb..97e9acf 100644
--- a/drivers/i2c/chips/Makefile
+++ b/drivers/i2c/chips/Makefile
@@ -23,6 +23,7 @@ obj-$(CONFIG_SENSORS_TLV320AIC23) += tlv320aic23.o
 obj-$(CONFIG_GPIOEXPANDER_OMAP)	+= gpio_expander_omap.o
 obj-$(CONFIG_MENELAUS)		+= menelaus.o
 obj-$(CONFIG_SENSORS_TSL2550)	+= tsl2550.o
+obj-$(CONFIG_SENSORS_TSL2563)	+= tsl2563.o
 obj-$(CONFIG_LP5521)		+= lp5521.o
 
 ifeq ($(CONFIG_I2C_DEBUG_CHIP),y)
diff --git a/drivers/i2c/chips/tsl2563.c b/drivers/i2c/chips/tsl2563.c
new file mode 100644
index 0000000..e05b880
--- /dev/null
+++ b/drivers/i2c/chips/tsl2563.c
@@ -0,0 +1,739 @@
+/*
+ * drivers/i2c/chips/tsl2563.c
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Written by Timo O. Karjalainen <timo.o.karjalainen@nokia.com>
+ * Contact: Mathias Nyman <mathias.nyman@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/hwmon.h>
+#include <linux/err.h>
+#include <mach/board.h>
+
+#define DRIVER_NAME  "tsl2563"
+
+/* Use this many bits for fraction part. */
+#define ADC_FRAC_BITS		(14)
+
+/* Given number of 1/10000's in ADC_FRAC_BITS precision. */
+#define FRAC10K(f)		(((f) * (1L << (ADC_FRAC_BITS))) / (10000))
+
+/* Bits used for fraction in calibration coefficients.*/
+#define CALIB_FRAC_BITS		(10)
+/* 0.5 in CALIB_FRAC_BITS precision */
+#define CALIB_FRAC_HALF		(1 << (CALIB_FRAC_BITS - 1))
+/* Make a fraction from a number n that was multiplied with b. */
+#define CALIB_FRAC(n, b)	(((n) << CALIB_FRAC_BITS) / (b))
+/* Decimal 10^(digits in sysfs presentation) */
+#define CALIB_BASE_SYSFS	(1000)
+
+#define TSL2563_CMD		(0x80)
+#define TSL2563_CLEARINT	(0x40)
+
+#define TSL2563_REG_CTRL	(0x00)
+#define TSL2563_REG_TIMING	(0x01)
+#define TSL2563_REG_LOWLOW	(0x02) /* data0 low threshold, 2 bytes */
+#define TSL2563_REG_LOWHIGH	(0x03)
+#define TSL2563_REG_HIGHLOW	(0x04) /* data0 high threshold, 2 bytes */
+#define TSL2563_REG_HIGHHIGH	(0x05)
+#define TSL2563_REG_INT		(0x06)
+#define TSL2563_REG_ID		(0x0a)
+#define TSL2563_REG_DATA0LOW	(0x0c) /* broadband sensor value, 2 bytes */
+#define TSL2563_REG_DATA0HIGH	(0x0d)
+#define TSL2563_REG_DATA1LOW	(0x0e) /* infrared sensor value, 2 bytes */
+#define TSL2563_REG_DATA1HIGH	(0x0f)
+
+#define TSL2563_CMD_POWER_ON	(0x03)
+#define TSL2563_CMD_POWER_OFF	(0x00)
+#define TSL2563_CTRL_POWER_MASK	(0x03)
+
+#define TSL2563_TIMING_13MS	(0x00)
+#define TSL2563_TIMING_100MS	(0x01)
+#define TSL2563_TIMING_400MS	(0x02)
+#define TSL2563_TIMING_MASK	(0x03)
+#define TSL2563_TIMING_GAIN16	(0x10)
+#define TSL2563_TIMING_GAIN1	(0x00)
+
+#define TSL2563_INT_DISBLED	(0x00)
+#define TSL2563_INT_LEVEL	(0x10)
+#define TSL2563_INT_PERSIST(n)	((n) & 0x0F)
+
+struct tsl2563_gainlevel_coeff {
+	u8 gaintime;
+	u16 min;
+	u16 max;
+};
+
+static struct tsl2563_gainlevel_coeff tsl2563_gainlevel_table[] = {
+	{
+		.gaintime	= TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN16,
+		.min		= 0,
+		.max		= 65534,
+	}, {
+		.gaintime	= TSL2563_TIMING_400MS | TSL2563_TIMING_GAIN1,
+		.min		= 2048,
+		.max		= 65534,
+	}, {
+		.gaintime	= TSL2563_TIMING_100MS | TSL2563_TIMING_GAIN1,
+		.min		= 4095,
+		.max		= 37177,
+	}, {
+		.gaintime	= TSL2563_TIMING_13MS | TSL2563_TIMING_GAIN1,
+		.min		= 3000,
+		.max		= 65535,
+	},
+};
+
+struct tsl2563_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct device		*hwmon_dev;
+
+	/* Remember state for suspend and resume functions */
+	pm_message_t		state;
+
+	struct tsl2563_gainlevel_coeff *gainlevel;
+
+	/* Thresholds are in lux */
+	u16			low_thres;
+	u16			high_thres;
+	u8			intr;
+
+	/* Calibration coefficients */
+	u32			calib0;
+	u32			calib1;
+
+	/* Cache current values, to be returned while suspended */
+	u32			data0;
+	u32			data1;
+};
+
+static int tsl2563_write(struct i2c_client *client, u8 reg, u8 value)
+{
+	int ret;
+	u8 buf[2];
+
+	buf[0] = TSL2563_CMD | reg;
+	buf[1] = value;
+
+	ret = i2c_master_send(client, buf, sizeof(buf));
+	return (ret == sizeof(buf)) ? 0 : ret;
+}
+
+static int tsl2563_read(struct i2c_client *client, u8 reg, void *buf, int len)
+{
+	int ret;
+	u8 cmd = TSL2563_CMD | reg;
+
+	ret = i2c_master_send(client, &cmd, sizeof(cmd));
+	if (ret != sizeof(cmd))
+		return ret;
+
+	return i2c_master_recv(client, buf, len);
+}
+
+static int tsl2563_set_power(struct tsl2563_chip *chip, int on)
+{
+	struct i2c_client *client = chip->client;
+	u8 cmd;
+
+	cmd = on ? TSL2563_CMD_POWER_ON : TSL2563_CMD_POWER_OFF;
+	return tsl2563_write(client, TSL2563_REG_CTRL, cmd);
+}
+
+/*
+ * Return value is 0 for off, 1 for on, or a negative error
+ * code if reading failed.
+ */
+static int tsl2563_get_power(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+	u8 val;
+
+	ret = tsl2563_read(client, TSL2563_REG_CTRL, &val, sizeof(val));
+	if (ret != sizeof(val))
+		return ret;
+
+	return (val & TSL2563_CTRL_POWER_MASK) == TSL2563_CMD_POWER_ON;
+}
+
+static int tsl2563_configure(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	ret = tsl2563_write(client, TSL2563_REG_TIMING,
+			chip->gainlevel->gaintime);
+	if (ret)
+		goto out;
+
+	ret = tsl2563_write(client, TSL2563_REG_INT, chip->intr);
+
+out:
+	return ret;
+}
+
+static int tsl2563_detect(struct tsl2563_chip *chip)
+{
+	int ret;
+
+	ret = tsl2563_set_power(chip, 1);
+	if (ret)
+		return ret;
+
+	ret = tsl2563_get_power(chip);
+	if (ret < 0)
+		return ret;
+
+	return ret ? 0 : -ENODEV;
+}
+
+static int tsl2563_read_id(struct tsl2563_chip *chip, u8 *id)
+{
+	struct i2c_client *client = chip->client;
+	int ret;
+
+	ret = tsl2563_read(client, TSL2563_REG_ID, id, sizeof(*id));
+	if (ret != sizeof(*id))
+		return ret;
+
+	return 0;
+}
+
+/*
+ * "Normalized" ADC value is one obtained with 400ms of integration time and
+ * 16x gain. This function returns the number of bits of shift needed to
+ * convert between normalized values and HW values obtained using given
+ * timing and gain settings.
+ */
+static int adc_shiftbits(u8 timing)
+{
+	int shift = 0;
+
+	switch (timing & TSL2563_TIMING_MASK) {
+	case TSL2563_TIMING_13MS:
+		shift += 5;
+		break;
+	case TSL2563_TIMING_100MS:
+		shift += 2;
+		break;
+	case TSL2563_TIMING_400MS:
+		/* no-op */
+		break;
+	}
+
+	if (!(timing & TSL2563_TIMING_GAIN16))
+		shift += 4;
+
+	return shift;
+}
+
+/* Convert a HW ADC value to normalized scale. */
+static u32 normalize_adc(u16 adc, u8 timing)
+{
+	return adc << adc_shiftbits(timing);
+}
+
+static void tsl2563_wait_adc(struct tsl2563_chip *chip)
+{
+	unsigned int delay;
+
+	switch (chip->gainlevel->gaintime & TSL2563_TIMING_MASK) {
+	case TSL2563_TIMING_13MS:
+		delay = 14;
+		break;
+	case TSL2563_TIMING_100MS:
+		delay = 101;
+		break;
+	default:
+		delay = 402;
+	}
+	/*
+	 * TODO: Make sure that we wait at least required delay but why we
+	 * have to extend it one tick more?
+	 */
+	schedule_timeout_interruptible(msecs_to_jiffies(delay) + 2);
+}
+
+static int tsl2563_adjust_gainlevel(struct tsl2563_chip *chip, u16 adc)
+{
+	struct i2c_client *client = chip->client;
+
+	if (adc > chip->gainlevel->max || adc < chip->gainlevel->min) {
+
+		(adc > chip->gainlevel->max) ?
+			chip->gainlevel++ : chip->gainlevel--;
+
+		tsl2563_write(client, TSL2563_REG_TIMING,
+			      chip->gainlevel->gaintime);
+
+		tsl2563_wait_adc(chip);
+		tsl2563_wait_adc(chip);
+
+		return 1;
+	} else
+		return 0;
+}
+
+static int tsl2563_get_adc(struct tsl2563_chip *chip)
+{
+	struct i2c_client *client = chip->client;
+	u8 buf0[2], buf1[2];
+	u16 adc0, adc1;
+	int retry = 1;
+	int ret = 0;
+
+	if (chip->state.event != PM_EVENT_ON)
+		goto out;
+
+	while (retry) {
+		ret = tsl2563_read(client,
+				   TSL2563_REG_DATA0LOW | TSL2563_CLEARINT,
+				   buf0, sizeof(buf0));
+		if (ret != sizeof(buf0))
+			goto out;
+
+		ret = tsl2563_read(client, TSL2563_REG_DATA1LOW,
+				   buf1, sizeof(buf1));
+		if (ret != sizeof(buf1))
+			goto out;
+
+		adc0 = (buf0[1] << 8) + buf0[0];
+		adc1 = (buf1[1] << 8) + buf1[0];
+
+		retry = tsl2563_adjust_gainlevel(chip, adc0);
+	}
+
+	chip->data0 = normalize_adc(adc0, chip->gainlevel->gaintime);
+	chip->data1 = normalize_adc(adc1, chip->gainlevel->gaintime);
+
+	ret = 0;
+out:
+	return ret;
+}
+
+static inline int calib_to_sysfs(u32 calib)
+{
+	return (int) (((calib * CALIB_BASE_SYSFS) +
+		       CALIB_FRAC_HALF) >> CALIB_FRAC_BITS);
+}
+
+static inline u32 calib_from_sysfs(int value)
+{
+	return (((u32) value) << CALIB_FRAC_BITS) / CALIB_BASE_SYSFS;
+}
+
+/*
+ * Conversions between lux and ADC values.
+ *
+ * The basic formula is lux = c0 * adc0 - c1 * adc1, where c0 and c1 are
+ * appropriate constants. Different constants are needed for different
+ * kinds of light, determined by the ratio adc1/adc0 (basically the ratio
+ * of the intensities in infrared and visible wavelengths). lux_table below
+ * lists the upper threshold of the adc1/adc0 ratio and the corresponding
+ * constants.
+ */
+
+struct tsl2563_lux_coeff {
+	unsigned long ch_ratio;
+	unsigned long ch0_coeff;
+	unsigned long ch1_coeff;
+};
+
+static const struct tsl2563_lux_coeff lux_table[] = {
+	{
+		.ch_ratio	= FRAC10K(1300),
+		.ch0_coeff	= FRAC10K(315),
+		.ch1_coeff	= FRAC10K(262),
+	}, {
+		.ch_ratio	= FRAC10K(2600),
+		.ch0_coeff	= FRAC10K(337),
+		.ch1_coeff	= FRAC10K(430),
+	}, {
+		.ch_ratio	= FRAC10K(3900),
+		.ch0_coeff	= FRAC10K(363),
+		.ch1_coeff	= FRAC10K(529),
+	}, {
+		.ch_ratio	= FRAC10K(5200),
+		.ch0_coeff	= FRAC10K(392),
+		.ch1_coeff	= FRAC10K(605),
+	}, {
+		.ch_ratio	= FRAC10K(6500),
+		.ch0_coeff	= FRAC10K(229),
+		.ch1_coeff	= FRAC10K(291),
+	}, {
+		.ch_ratio	= FRAC10K(8000),
+		.ch0_coeff	= FRAC10K(157),
+		.ch1_coeff	= FRAC10K(180),
+	}, {
+		.ch_ratio	= FRAC10K(13000),
+		.ch0_coeff	= FRAC10K(34),
+		.ch1_coeff	= FRAC10K(26),
+	}, {
+		.ch_ratio	= ULONG_MAX,
+		.ch0_coeff	= 0,
+		.ch1_coeff	= 0,
+	},
+};
+
+/*
+ * Convert normalized, scaled ADC values to lux.
+ */
+static unsigned int adc_to_lux(u32 adc0, u32 adc1)
+{
+	const struct tsl2563_lux_coeff *lp = lux_table;
+	unsigned long ratio, lux, ch0 = adc0, ch1 = adc1;
+
+	ratio = ch0 ? ((ch1 << ADC_FRAC_BITS) / ch0) : ULONG_MAX;
+
+	while (lp->ch_ratio < ratio)
+		lp++;
+
+	lux = ch0 * lp->ch0_coeff - ch1 * lp->ch1_coeff;
+
+	return (unsigned int) (lux >> ADC_FRAC_BITS);
+}
+
+/*--------------------------------------------------------------*/
+/*                      Sysfs interface                         */
+/*--------------------------------------------------------------*/
+
+static ssize_t tsl2563_adc0_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_get_adc(chip);
+	if (ret)
+		return ret;
+
+	ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data0);
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+static ssize_t tsl2563_adc1_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_get_adc(chip);
+	if (ret)
+		return ret;
+
+	ret = snprintf(buf, PAGE_SIZE, "%d\n", chip->data1);
+	mutex_unlock(&chip->lock);
+
+	return ret;
+}
+
+/* Apply calibration coefficient to ADC count. */
+static u32 calib_adc(u32 adc, u32 calib)
+{
+	unsigned long scaled = adc;
+
+	scaled *= calib;
+	scaled >>= CALIB_FRAC_BITS;
+
+	return (u32) scaled;
+}
+
+static ssize_t tsl2563_lux_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	u32 calib0, calib1;
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_get_adc(chip);
+	if (ret)
+		goto out;
+
+	calib0 = calib_adc(chip->data0, chip->calib0);
+	calib1 = calib_adc(chip->data1, chip->calib1);
+
+	ret = snprintf(buf, PAGE_SIZE, "%d\n", adc_to_lux(calib0, calib1));
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static ssize_t format_calib(char *buf, int len, u32 calib)
+{
+	return snprintf(buf, PAGE_SIZE, "%d\n", calib_to_sysfs(calib));
+}
+
+static ssize_t tsl2563_calib0_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = format_calib(buf, PAGE_SIZE, chip->calib0);
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static ssize_t tsl2563_calib1_show(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int ret;
+
+	mutex_lock(&chip->lock);
+	ret = format_calib(buf, PAGE_SIZE, chip->calib1);
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int do_calib_store(struct device *dev, const char *buf, size_t len,
+			  int ch)
+{
+	struct tsl2563_chip *chip = dev_get_drvdata(dev);
+	int value;
+	u32 calib;
+
+	if (1 != sscanf(buf, "%d", &value))
+		return -EINVAL;
+
+	calib = calib_from_sysfs(value);
+
+	if (ch)
+		chip->calib1 = calib;
+	else
+		chip->calib0 = calib;
+
+	return len;
+}
+
+static ssize_t tsl2563_calib0_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t len)
+{
+	return do_calib_store(dev, buf, len, 0);
+}
+
+static ssize_t tsl2563_calib1_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t len)
+{
+	return do_calib_store(dev, buf, len, 1);
+}
+
+static DEVICE_ATTR(adc0, S_IRUGO, tsl2563_adc0_show, NULL);
+static DEVICE_ATTR(adc1, S_IRUGO, tsl2563_adc1_show, NULL);
+static DEVICE_ATTR(lux, S_IRUGO, tsl2563_lux_show, NULL);
+static DEVICE_ATTR(calib0, S_IRUGO | S_IWUSR,
+		   tsl2563_calib0_show, tsl2563_calib0_store);
+static DEVICE_ATTR(calib1, S_IRUGO | S_IWUSR,
+		   tsl2563_calib1_show, tsl2563_calib1_store);
+
+static struct attribute *tsl2563_attributes[] = {
+	&dev_attr_adc0.attr,
+	&dev_attr_adc1.attr,
+	&dev_attr_lux.attr,
+	&dev_attr_calib0.attr,
+	&dev_attr_calib1.attr,
+	NULL
+};
+
+static const struct attribute_group tsl2563_group = {
+	.attrs = tsl2563_attributes,
+};
+
+static int tsl2563_register_sysfs(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+
+	return sysfs_create_group(&dev->kobj, &tsl2563_group);
+}
+
+static void tsl2563_unregister_sysfs(struct i2c_client *client)
+{
+	struct device *dev = &client->dev;
+
+	sysfs_remove_group(&dev->kobj, &tsl2563_group);
+}
+
+/*--------------------------------------------------------------*/
+/*                      Probe, Attach, Remove                   */
+/*--------------------------------------------------------------*/
+static struct i2c_driver tsl2563_i2c_driver;
+
+static int tsl2563_probe(struct i2c_client *client,
+		const struct i2c_device_id *device_id)
+{
+	struct tsl2563_chip *chip;
+	int err = 0;
+	u8 id;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, chip);
+	chip->client = client;
+
+	err = tsl2563_detect(chip);
+	if (err) {
+		dev_err(&client->dev, "device not found, error %d \n", -err);
+		goto fail1;
+	}
+
+	err = tsl2563_read_id(chip, &id);
+	if (err)
+		goto fail1;
+
+	mutex_init(&chip->lock);
+
+	/* Default values used until userspace says otherwise */
+	chip->low_thres = 0x0;
+	chip->high_thres = 0xffff;
+	chip->gainlevel = tsl2563_gainlevel_table;
+	chip->intr = TSL2563_INT_PERSIST(4);
+	chip->calib0 = calib_from_sysfs(CALIB_BASE_SYSFS);
+	chip->calib1 = calib_from_sysfs(CALIB_BASE_SYSFS);
+
+	dev_info(&client->dev, "model %d, rev. %d\n", id >> 4, id & 0x0f);
+
+	err = tsl2563_configure(chip);
+	if (err)
+		goto fail1;
+
+	chip->hwmon_dev = hwmon_device_register(&client->dev);
+	if (IS_ERR(chip->hwmon_dev))
+		goto fail1;
+
+	err = tsl2563_register_sysfs(client);
+	if (err) {
+		dev_err(&client->dev, "sysfs registration failed, %d\n", err);
+		goto fail2;
+	}
+
+	return 0;
+fail2:
+	hwmon_device_unregister(chip->hwmon_dev);
+fail1:
+	kfree(chip);
+	return err;
+}
+
+static int tsl2563_remove(struct i2c_client *client)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(client);
+
+	tsl2563_unregister_sysfs(client);
+	hwmon_device_unregister(chip->hwmon_dev);
+
+	kfree(chip);
+	return 0;
+}
+
+static int tsl2563_suspend(struct i2c_client *client, pm_message_t state)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(client);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_set_power(chip, 0);
+	if (ret)
+		goto out;
+
+	chip->state = state;
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static int tsl2563_resume(struct i2c_client *client)
+{
+	struct tsl2563_chip *chip = i2c_get_clientdata(client);
+	int ret;
+
+	mutex_lock(&chip->lock);
+
+	ret = tsl2563_set_power(chip, 1);
+	if (ret)
+		goto out;
+
+	ret = tsl2563_configure(chip);
+	if (ret)
+		goto out;
+
+	chip->state.event = PM_EVENT_ON;
+
+out:
+	mutex_unlock(&chip->lock);
+	return ret;
+}
+
+static const struct i2c_device_id tsl2563_id[] = {
+	{ DRIVER_NAME, 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, tsl2563_id);
+
+static struct i2c_driver tsl2563_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.suspend	= tsl2563_suspend,
+	.resume		= tsl2563_resume,
+	.probe		= tsl2563_probe,
+	.remove		= __devexit_p(tsl2563_remove),
+	.id_table	= tsl2563_id,
+};
+
+static int __init tsl2563_init(void)
+{
+	return i2c_add_driver(&tsl2563_i2c_driver);
+}
+
+static void __exit tsl2563_exit(void)
+{
+	i2c_del_driver(&tsl2563_i2c_driver);
+}
+
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("tsl2563 light sensor driver");
+MODULE_LICENSE("GPL");
+
+module_init(tsl2563_init);
+module_exit(tsl2563_exit);
-- 
1.6.0.1.141.g445ca


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

* [PATCH 08/33] add innovator ps2 keypad driver
       [not found]             ` <1220116593-862-8-git-send-email-me@felipebalbi.com>
@ 2008-08-30 17:16               ` Felipe Balbi
  2008-08-30 17:16                 ` [PATCH 09/33] add lm8323 " Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/input/keyboard/Kconfig         |   10 +
 drivers/input/keyboard/Makefile        |    1 +
 drivers/input/keyboard/innovator_ps2.c | 1280 ++++++++++++++++++++++++++++++++
 3 files changed, 1291 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/innovator_ps2.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index efd70a9..01d6421 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -259,6 +259,16 @@ config KEYBOARD_OMAP
 	  To compile this driver as a module, choose M here: the
 	  module will be called omap-keypad.
 
+config OMAP_PS2
+	tristate "TI OMAP Innovator 1510 PS/2 keyboard & mouse support"
+	depends on ARCH_OMAP15XX && MACH_OMAP_INNOVATOR
+	help
+	  Say Y here if you want to use the OMAP Innovator 1510 PS/2
+	  keyboard and mouse.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called innovator_ps2.
+
 config KEYBOARD_PXA27x
 	tristate "PXA27x/PXA3xx keypad support"
 	depends on PXA27x || PXA3xx
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 0edc8f2..a48205b 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_KEYBOARD_TOSA)		+= tosakbd.o
 obj-$(CONFIG_KEYBOARD_HIL)		+= hil_kbd.o
 obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
+obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
 obj-$(CONFIG_KEYBOARD_GPIO)		+= gpio_keys.o
diff --git a/drivers/input/keyboard/innovator_ps2.c b/drivers/input/keyboard/innovator_ps2.c
new file mode 100644
index 0000000..b3c4a06
--- /dev/null
+++ b/drivers/input/keyboard/innovator_ps2.c
@@ -0,0 +1,1280 @@
+/*
+ * drivers/char/innovator_ps2.c
+ *
+ * Basic PS/2 keyboard/mouse driver for the Juno® USAR HID controller
+ * present on the TI Innovator/OMAP1510 Break-out-board.
+ *
+ *
+ * Author: MontaVista Software, Inc.
+ *         <gdavis@mvista.com> or <source@mvista.com>
+ *
+ *
+ * 2003 (c) MontaVista Software, Inc. This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ *
+ *
+ * REFERENCES:
+ *
+ * 1.	Technical Reference Manual
+ *	Juno® 01
+ *	Multi-function ICs family
+ *	UR8HC007-001 HID & Power management controller
+ *	Document Number: DOC8-007-001-TR-075
+ *	Date: February 2002
+ *	Copyright ©1998-2002 Semtech Corporation
+ *	http://www.semtech.com/pdf/doc8-007-001-tr.pdf
+ *
+ * 2.	Juno® 01 UR8HC007-001 Data Sheet
+ *	Extremely Low-power Input Device and Power Management IC
+ *	Copyright ©1998-2002 Semtech Corporation
+ *	DOC8-007-001-DS-112
+ *	http://www.semtech.com/pdf/doc8-007-001-ds.pdf
+ *
+ *
+ * HISTORY:
+ *
+ * 20030626: George G. Davis <gdavis@mvista.com>
+ *      Initially based on the following RidgeRun DSPlinux Version 1.6 files:
+ *		linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_hid.c
+ *		linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_hid.h
+ *		linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_ps2.c
+ *		linux-2.4.15-rmk1-dsplinux/arch/arm/dsplinux/hid/omap1510_spi.c
+ *	All original files above are
+ *		Copyright (C) 2001 RidgeRun, Inc.
+ *		Author: Alex McMains <aam@ridgerun.com>
+ *
+ * 20040812: Thiago Radicchi <trr@dcc.ufmg.br>
+ *      Cleanup of old code from 2.4 driver and some debug code.
+ *      Minor changes in interrupt handling code.
+ *
+ * NOTES:
+ *
+ * 1. This driver does not provide support for setting keyboard/mouse
+ *    configuration parameters. Both devices are managed directly by
+ *    the Juno UR8HC007-001 on behalf of the host. This minimises the
+ *    amount of host processing required to manage HID events and state
+ *    changes, e.g. both keyboard and mouse devices are hot pluggable
+ *    with no host intervention required. However, we cannot customise
+ *    keyboard/mouse settings in this case. So we live with the defaults
+ *    as setup by the Juno UR8HC007-001 whatever they may be.
+ * 2. Keyboard auto repeat does not work. See 1 above. : )
+ *
+ *
+ * TODO:
+ *
+ * 1. Complete DPM/LDM stubs and test.
+ * 2. Add SPI error handling support, i.e. resend, etc.,.
+ * 3. Determine why innovator_hid_interrupt() is called for every
+ *    invocation of Innovator FPGA IRQ demux. It appears that the
+ *    missed Innovator ethernet workaround may be to blame. However,
+ *    it does not adversely affect operation of this driver since we
+ *    check for assertion of ATN prior to servicing the interrupt. If
+ *    ATN is negated, we bug out right away.
+ *
+ */
+
+#include <linux/version.h>
+#include <linux/stddef.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/ptrace.h>
+#include <linux/sched.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/poll.h>
+#include <linux/string.h>
+#include <linux/ioport.h>
+#include <linux/platform_device.h>
+
+#include <asm/io.h>
+#include <asm/hardware.h>
+#include <asm/irq.h>
+#include <asm/uaccess.h>
+#include <asm/arch/fpga.h>
+
+#undef	INNOVATOR_KEYB_DEBUG
+#ifdef	INNOVATOR_KEYB_DEBUG
+#define	dbg(format, arg...) printk(KERN_DEBUG "%s:%d: " format , \
+				   __FUNCTION__ , __LINE__ , ## arg)
+#define	entry()	printk(KERN_DEBUG "%s:%d: Entry\n" , __FUNCTION__ , __LINE__)
+#define	exit()	printk(KERN_DEBUG "%s:%d: Exit\n" , __FUNCTION__ , __LINE__)
+#define dump_packet(p, n)					\
+	{							\
+		int i;						\
+		printk(KERN_DEBUG "%s:%d: %08x:" ,		\
+		       __FUNCTION__ , __LINE__ , (int) p);	\
+		for (i = 0; i < n; i += 1) {			\
+			printk(" %02x", (int) p[i]);		\
+		}						\
+		printk("\n");					\
+	}
+#else
+#define	dbg(format, arg...) do {} while (0)
+#define	entry()	do {} while (0)
+#define	exit()	do {} while (0)
+#define dump_packet(p, n) do {} while (0)
+#endif
+
+
+#define	PFX	"innovator_ps2"
+#define err(format, arg...)	printk(KERN_ERR PFX ": " format , ## arg)
+#define info(format, arg...)	printk(KERN_INFO PFX ": " format , ## arg)
+#define warn(format, arg...)	printk(KERN_WARNING PFX ": " format , ## arg)
+
+
+/****************************************************************************/
+
+/*
+ * Synchronous communications timing parameters (Reference [1] pg 7-7)
+ */
+
+#define tMSA	5000	/* -/5ms	_SS to _ATN (master transfer) */
+#define tMAC	100	/* 100us/5ms	_ATN to first clock pulse (master
+					transfer) */
+#define tMIB	150	/* 150us/5ms	Beginning of byte transfer to beginning
+					of next byte transfer */
+#define tSIB	150	/* 150us/5ms	Beginning of byte transfer to beginning
+					of next byte transfer */
+#define tMSP	100	/* -/100us	Last clock pulse of packet to _SS
+					de-assertion */
+#define tMNSA	100	/* -/100us	_SS de-assertion to _ATN de-assertion */
+#define tMNEXT	120	/* 120uS/-	_ATN release to _SS re-assertion
+					(master transfer) */
+#define	tSAS	5000	/* -/5ms	_ATN to _SS (slave transfer) */
+#define tSSC	100	/* 100us/5ms	_SS to first clock pulse (slave
+					transfer) */
+#define tSNA	100	/* -/100us	Last clock pulse of packet to _ATN
+					de-assertion */
+#define tSNAS	100	/* -/100us	_ATN release to _SS de-assertion */
+#define tSNEXT	120	/* 120us/-	_SS release to _ATN re-assertion
+					(slave transfer) */
+#define tSCK	4	/* 4us/-	Clock period */
+#define tSLOW	2	/* 2us/-	Clock LOW period */
+#define tHOLD	200	/* 200ns/-	Master data hold time */
+#define tSETUP	100	/* 100ns/-	Master data setup Time */
+#define tSSETUP	500	/* -/500ns	Slave data setup time from clock
+					falling edge */
+
+
+/*
+ * Protocol Headers (Reference [1], pg. 5-1):
+ */
+
+
+/* Protocols used in commands issued by the host: */
+#define SIMPLE			0x80	/* Simple commands
+					 * Common for both host and controller
+					 * protocol headers.
+					 */
+#define WRITE_REGISTER_BIT	0x81	/* Write register bit */
+#define READ_REGISTER_BIT	0x82	/* Read register bit */
+#define WRITE_REGISTER		0x83	/* Write register */
+#define READ_REGISTER		0x84	/* Read register */
+#define WRITE_BLOCK		0x85	/* Write block */
+#define READ_BLOCK		0x86	/* Read block */
+
+
+/* Protocols used in responses, reports and alerts issued by the controller: */
+#define REPORT_REGISTER_BIT	0x81	/* Report register bit & event alerts */
+#define REPORT_REGISTER		0x83	/* Report register */
+#define REPORT_BLOCK		0x85	/* Report block */
+#define POINTING_REPORT		0x87	/* Pointing device data report */
+#define KEYBOARD_REPORT		0x88	/* Keyboard device data report */
+
+
+/* Simple Commands (Reference [1], pg 5-3): */
+#define INITIALIZE		0x00	/* Forces the recipient to enter the
+					 * known default power-on state.
+					 */
+#define INITIALIZATION_COMPLETE	0x01	/* Issued as a hand-shake response only
+					 * to the "Initialize" command.
+					 */
+#define RESEND_REQUEST		0x05	/* Issued upon error in the reception
+					 * of a package. The recipient resends
+					 * the last transmitted packet.
+					 */
+
+/* Register offsets (Reference [1], pg 6-1 thru 6-9): */
+
+#define REG_PM_COMM		0
+#define REG_PM_STATUS		1
+#define REG_PAGENO		255
+
+/* Power management bits ((Reference [1], pg 6-10): */
+
+#define SUS_STATE		0x2	/* in REG_PM_COMM */
+
+/* Miscellaneous constants: */
+
+#define X_MSB_SHIFT	(8-4)
+#define X_MSB_MASK	(3<<4)
+#define Y_MSB_SHIFT	(8-6)
+#define Y_MSB_MASK	(3<<6)
+
+
+#define JUNO_BLOCK_SIZE     32
+#define JUNO_BUFFER_SIZE    256
+
+
+/*
+ * Errors:
+ */
+
+#define E_BAD_HEADER	1
+#define E_BAD_LRC	2
+#define E_ZERO_BYTES	3
+#define E_BAD_VALUE	4
+#define E_BAD_MODE	5
+#define E_REPORT_MODE	6
+#define E_BAD_ACK	7
+#define E_BAD_DEVICE_ID	8
+#define E_PKT_SZ	9
+
+
+/*
+ * Host/Controller Command/Response Formats:
+ */
+
+typedef struct _simple_t {
+	u8 header;
+	u8 cmd_code;
+	u8 LRC;
+} __attribute__ ((packed)) simple_t;
+
+typedef struct _write_bit_t {
+	u8 header;
+	u8 offset;
+	u8 value_bit;
+	u8 LRC;
+} __attribute__ ((packed)) write_bit_t;
+
+typedef struct _read_bit_t {
+	u8 header;
+	u8 offset;
+	u8 bit;
+	u8 LRC;
+} __attribute__ ((packed)) read_bit_t;
+
+typedef struct _write_reg_t {
+	u8 header;
+	u8 offset;
+	u8 value;
+	u8 LRC;
+} __attribute__ ((packed)) write_reg_t;
+
+typedef struct _read_reg_t {
+	u8 header;
+	u8 offset;
+	u8 LRC;
+} __attribute__ ((packed)) read_reg_t;
+
+typedef struct _write_block_t {
+	u8 header;
+	u8 offset;
+	u8 length;
+	u8 block[JUNO_BLOCK_SIZE + 1]; /* Hack: LRC is last element of block[] */
+} __attribute__ ((packed)) write_block_t;
+
+typedef struct _read_block_t {
+	u8 header;
+	u8 offset;
+	u8 length;
+	u8 LRC;
+} __attribute__ ((packed)) read_block_t;
+
+typedef struct _report_bit_t {
+	u8 header;
+	u8 offset;
+	u8 value_bit;
+	u8 LRC;
+} __attribute__ ((packed)) report_bit_t;
+
+typedef struct _report_reg_t {
+	u8 header;
+	u8 offset;
+	u8 value;
+	u8 LRC;
+} __attribute__ ((packed)) report_reg_t;
+
+typedef struct _report_block_t {
+	u8 header;
+	u8 offset;
+	u8 length;
+	u8 block[32];
+	u8 LRC;
+} __attribute__ ((packed)) report_block_t;
+
+typedef struct _mse_report_t {
+	u8 header;
+	u8 buttons;
+	u8 Xdisplacement;
+	u8 Ydisplacement;
+	u8 Zdisplacement;
+	u8 LRC;
+} __attribute__ ((packed)) mse_report_t;
+
+typedef struct _kdb_report_t {
+	u8 header;
+	u8 keynum;		/* up > 0x80, down < 0x7E, all keys up 0x00 */
+	u8 LRC;
+} __attribute__ ((packed)) kdb_report_t;
+
+
+static u8 buffer[JUNO_BUFFER_SIZE];
+
+static void do_hid_tasklet(unsigned long);
+DECLARE_TASKLET(hid_tasklet, do_hid_tasklet, 0);
+static struct innovator_hid_dev *hid;
+
+struct innovator_hid_dev {
+	struct input_dev *mouse, *keyboard;
+	int open;
+	int irq_enabled;
+};
+
+/****************************************************************************/
+
+/*
+ * Low-level TI Innovator/OMAP1510 FPGA HID SPI interface helper functions:
+ */
+
+static u8
+innovator_fpga_hid_rd(void)
+{
+	u8 val = inb(INNOVATOR_FPGA_HID_SPI);
+	return val;
+}
+
+static void
+innovator_fpga_hid_wr(u8 val)
+{
+	outb(val, INNOVATOR_FPGA_HID_SPI);
+}
+
+static void
+innovator_fpga_hid_frob(u8 mask, u8 val)
+{
+	unsigned long flags;
+	local_irq_save(flags);
+	innovator_fpga_hid_wr((innovator_fpga_hid_rd() & ~mask) | val);
+	local_irq_restore(flags);
+}
+
+static void
+innovator_fpga_hid_set_bits(u8 x)
+{
+	innovator_fpga_hid_frob(x, x);
+}
+
+static void
+SS(int value)
+{
+	innovator_fpga_hid_frob(OMAP1510_FPGA_HID_nSS, value ? OMAP1510_FPGA_HID_nSS : 0);
+}
+
+static void
+SCLK(int value)
+{
+	innovator_fpga_hid_frob(OMAP1510_FPGA_HID_SCLK, value ? OMAP1510_FPGA_HID_SCLK : 0);
+}
+
+static void
+MOSI(int value)
+{
+	innovator_fpga_hid_frob(OMAP1510_FPGA_HID_MOSI, value ? OMAP1510_FPGA_HID_MOSI : 0);
+}
+
+static u8
+MISO(void)
+{
+	return ((innovator_fpga_hid_rd() & OMAP1510_FPGA_HID_MISO) ? 1 : 0);
+}
+
+static u8 
+ATN(void)
+{
+	return ((innovator_fpga_hid_rd() & OMAP1510_FPGA_HID_ATN) ? 1 : 0);
+}
+
+static int
+wait_for_ATN(int assert, int timeout)
+{
+	do {
+		if (ATN() == assert)
+			return 0;
+		udelay(1);
+	} while (timeout -= 1);
+	return -1;
+}
+
+static u8
+innovator_fpga_hid_xfer_byte(u8 xbyte)
+{
+	int i;
+	u8 rbyte;
+
+	for (rbyte = 0, i = 7; i >= 0; i -= 1) {
+		SCLK(0);
+		MOSI((xbyte >> i) & 1);
+		udelay(tSLOW);
+		SCLK(1);
+		rbyte = (rbyte << 1) | MISO();
+		udelay(tSLOW);
+	}
+
+	return rbyte;
+}
+
+static void
+innovator_fpga_hid_reset(void)
+{
+	innovator_fpga_hid_wr(OMAP1510_FPGA_HID_SCLK | OMAP1510_FPGA_HID_MOSI);
+	mdelay(1);
+	innovator_fpga_hid_set_bits(OMAP1510_FPGA_HID_RESETn);
+}
+
+
+/*****************************************************************************
+
+  Refer to Reference [1], Chapter 7 / Low-level communications, Serial
+  Peripheral Interface (SPI) implementation Host (master) packet
+  transmission timing, pg. 7-3, for timing and implementation details
+  for spi_xmt().
+
+ *****************************************************************************/
+
+int
+spi_xmt(u8 * p, u8 n)
+{
+	unsigned long flags;
+
+	dump_packet(p, n);
+	local_irq_save(flags);
+	disable_irq(OMAP1510_INT_FPGA_ATN);
+
+	if (ATN()) {
+		/* Oops, we have a collision. */
+		enable_irq(OMAP1510_INT_FPGA_ATN);
+		local_irq_restore(flags);
+		dbg("Protocol error: ATN is asserted\n");
+		return -EAGAIN;
+	}
+
+	SS(1);
+
+	if (wait_for_ATN(1, tMSA) < 0) {
+		SS(0);
+		enable_irq(OMAP1510_INT_FPGA_ATN);
+		local_irq_restore(flags);
+		dbg("timeout waiting for ATN assertion\n");
+		return -EREMOTEIO;
+	}
+
+	udelay(tMAC);
+
+	while (n--) {
+		innovator_fpga_hid_xfer_byte(*p++);
+		if (n) {
+			udelay(tMIB - 8 * tSCK);
+		}
+	}
+
+	MOSI(1);	/* Set MOSI to idle high. */
+
+	/* NOTE: The data sheet does not specify a minimum delay
+	 * here. But innovator_fpga_hid_xfer_byte() gives us a half-clock
+	 * delay (tSLOW) after the last bit is sent. So I'm happy with
+	 * that.
+	 */
+
+	SS(0);
+
+	if (wait_for_ATN(0, tMNSA) < 0) {
+		enable_irq(OMAP1510_INT_FPGA_ATN);
+		local_irq_restore(flags);
+		dbg("timeout waiting for ATN negation\n");
+		return -EREMOTEIO;
+	}
+
+	udelay(tMNEXT);
+	enable_irq(OMAP1510_INT_FPGA_ATN);
+	local_irq_restore(flags);
+	return 0;
+}
+
+
+/*****************************************************************************
+
+  Refer to Reference [1],  Chapter 7 / Low-level communications, Serial
+  Peripheral Interface (SPI) implementation, Slave packet transmission
+  timing, pg. 7-5, for timing and implementation details for spi_rcv().
+
+ *****************************************************************************/
+
+int
+spi_rcv(u8 * p, int len)
+{
+	unsigned long flags;
+	int ret = 0;
+
+	if (len > 256) {
+		/* Limit packet size to something reasonable */
+		return -1;
+	}
+
+	local_irq_save(flags);
+
+	if (wait_for_ATN(1, tMSA) < 0) {
+		local_irq_restore(flags);
+		dbg("Protocol error: ATN is not asserted\n");
+		return -EREMOTEIO;
+	}
+
+	SS(1);
+
+	udelay(tSSC);
+
+	while (ATN()) {
+		if (ret >= len) {
+			err("over run error\n");
+			ret = -1;
+			break;
+		}
+		p[ret++] = innovator_fpga_hid_xfer_byte(0xff);
+		udelay(tSNA);	/* Wait long enough to detect negation of ATN
+				 * after last clock pulse of packet.
+				 *
+				 * NOTE: Normally, we need a minimum delay of
+				 *	 tSIB between the start of one byte
+				 *	 and the start of the next. However,
+				 *	 we also need to wait long enough
+				 *	 for the USAR to negate ATN before
+				 *	 starting the next byte. So we use
+				 *	 max(tSIB - 8 * tSCK, tSNA) here to
+				 *	 satisfy both constraints.
+				 */
+	}
+
+	SS(0);	/* NOTE: The data sheet does not specify a minimum delay
+		 * here. But innovator_fpga_hid_xfer_byte() gives us a
+		 * half-clock delay (tSLOW) after the last bit is sent. So
+		 * I'm happy with that (rather than no delay at all : ).
+		 */
+
+
+	udelay(tSNEXT);	/* This isn't quite right. Assertion of ATN after
+			 * negation of SS is an USAR timing constraint.
+			 * What we need here is a spec for the minimum
+			 * delay from SS negation to SS assertion. But
+			 * for now, just use this brain dead delay.
+			 */
+
+	local_irq_restore(flags);
+
+	if (ret > 0) {
+		dump_packet(p, ret);
+	}
+
+	return ret;
+}
+
+
+/*****************************************************************************
+  Calculate Host/Controller Command/Response Longitudinal Redundancy Check (LRC)
+
+  The algorithm implemented in calculate_LRC() below is taken directly from
+  the reference [1], Chapter 7 / Low-level communications, LRC (Longitudinal
+  Redundancy Check), pg 5-10.
+
+ *****************************************************************************/
+
+static u8
+calculate_LRC(u8 * p, int n)
+{
+	u8 LRC;
+	int i;
+
+	/*
+	 * Init the LRC using the first two message bytes.
+	 */
+	LRC = p[0] ^ p[1];
+
+	/*
+	 * Update the LRC using the remainder of the p.
+	 */
+	for (i = 2; i < n; i++)
+		LRC ^= p[i];
+
+	/*
+	 * If the MSB is set then clear the MSB and change the next
+	 * most significant bit
+	 */
+	if (LRC & 0x80)
+		LRC ^= 0xC0;
+
+	return LRC;
+}
+
+
+/*
+ * Controller response helper functions:
+ */
+
+static inline int
+report_mouse(mse_report_t * p, int n)
+{
+	if (p->header != POINTING_REPORT)
+		return -E_BAD_HEADER;
+
+	if (n != sizeof(mse_report_t))
+		return -E_PKT_SZ;
+
+	return (p->LRC != calculate_LRC((u8 *) p, sizeof(mse_report_t) - 1)) ?
+		-E_BAD_LRC : POINTING_REPORT;
+}
+
+static inline int
+report_keyboard(kdb_report_t * p, int n)
+{
+	if (p->header != KEYBOARD_REPORT)
+		return -E_BAD_HEADER;
+
+	if (n != sizeof(kdb_report_t))
+		return -E_PKT_SZ;
+
+	return (p->LRC != calculate_LRC((u8 *) p, sizeof(kdb_report_t) - 1)) ?
+		-E_BAD_LRC : KEYBOARD_REPORT;
+}
+
+
+/*
+ * Miscellaneous helper functions:
+ */
+
+static inline int
+report_type(u8 * type)
+{
+	/* check the header to find out what kind of report it is */
+	if ((*type) == KEYBOARD_REPORT)
+		return KEYBOARD_REPORT;
+	else if ((*type) == POINTING_REPORT)
+		return POINTING_REPORT;
+	else
+		return -E_BAD_HEADER;
+}
+
+static inline int
+report_async(void * p, int n)
+{
+	int ret;
+
+	if ((ret = spi_rcv((u8 *) p, n)) < 0)
+		return ret;
+
+	if (report_type((u8 *) p) == POINTING_REPORT)
+		ret = report_mouse((mse_report_t *) p, ret);
+	else if (report_type((u8 *) p) == KEYBOARD_REPORT)
+		ret = report_keyboard((kdb_report_t *) p, ret);
+
+	return ret;
+}
+
+/*
+ * Host command helper functions:
+ */
+
+#if	0
+/* REVISIT/TODO: Wrapper for command/response with resend handing. */
+static int
+spi_xfer(u8 * optr, u8 osz, u8 * iptr, u8 isz)
+{
+	static u8 buf[256];
+	int ret;
+	int xretries = 3;
+
+	do {
+		if (optr != NULL && osz) {
+			do {
+				ret = spi_xmt((u8 *) optr, osz);
+			} while (ret < 0);
+		}
+
+		ret = spi_rcv((u8 *) buf, 256);
+
+		if (ret == -EREMOTEIO) {
+			if (iptr == NULL) {
+				break;
+			}
+		}
+	} while (xretries--);
+
+	return ret;
+}
+#endif
+
+/* REVISIT: Enable these when/if additional Juno features are required. */
+static inline int
+simple(u8 cmd)
+{
+	static simple_t p;
+	int ret;
+
+	p.header = SIMPLE;
+	p.cmd_code = cmd;
+	p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1);
+
+	if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0)
+		return ret;
+
+	if ((ret = spi_rcv((u8 *) & p, sizeof(p))) < 0)
+		return ret;
+
+	if (ret == 0)
+		return -E_ZERO_BYTES;
+
+	if (ret != sizeof(p))
+		return -E_PKT_SZ;
+
+	if (p.header != SIMPLE)
+		return -E_BAD_HEADER;
+
+	if (p.LRC != calculate_LRC((u8 *) & p, sizeof(p) - 1))
+		return -E_BAD_LRC;
+
+	/* REVISIT: Need to check or return response code here? */
+}
+
+static inline int
+write_bit(u8 offset, u8 bit, u8 value)
+{
+	static write_bit_t p;
+
+	p.header = WRITE_REGISTER_BIT;
+	p.offset = offset;
+	p.value_bit = (bit << 1) | (value & 1);
+	p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1);
+
+	return spi_xmt((u8 *) & p, sizeof(p));
+}
+
+static inline int
+read_bit(u8 offset, u8 bit, u8 * data)
+{
+	static read_bit_t p;
+	static report_bit_t q;
+	int ret;
+
+	p.header = READ_REGISTER_BIT;
+	p.offset = offset;
+	p.bit = bit;
+	p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1);
+
+	if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0)
+		return ret;
+
+	if ((ret = spi_rcv((u8 *) & q, sizeof(q))) < 0)
+		return ret;
+
+	if (ret == 0)
+		return -E_ZERO_BYTES;
+
+	if (ret != sizeof(q))
+		return -E_PKT_SZ;
+
+	if (q.header != REPORT_REGISTER_BIT)
+		return -E_BAD_HEADER;
+
+	if (q.LRC != calculate_LRC((u8 *) & q, sizeof(q) - 1))
+		return -E_BAD_LRC;
+
+	*data = q.value_bit;
+
+	return 0;
+}
+
+static inline int
+write_reg(u8 offset, u8 value)
+{
+	static write_reg_t p;
+
+	p.header = WRITE_REGISTER;
+	p.offset = offset;
+	p.value = value;
+	p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1);
+
+	return spi_xmt((u8 *) & p, sizeof(p));
+}
+
+static inline int
+read_reg(u8 offset, u8 * data)
+{
+	static read_reg_t p;
+	static report_reg_t q;
+	int ret;
+
+	p.header = READ_REGISTER;
+	p.offset = offset;
+	p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1);
+
+	if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0)
+		return ret;
+
+	if ((ret = spi_rcv((u8 *) & q, sizeof(q))) < 0)
+		return ret;
+
+	if (ret == 0)
+		return -E_ZERO_BYTES;
+
+	if (ret != sizeof(q))
+		return -E_PKT_SZ;
+
+	if (q.header != REPORT_REGISTER)
+		return -E_BAD_HEADER;
+
+	if (q.LRC != calculate_LRC((u8 *) & q, sizeof(q) - 1))
+		return -E_BAD_LRC;
+
+	*data = q.value;
+
+	return 0;
+}
+
+static inline int
+write_block(u8 offset, u8 length, u8 * block)
+{
+	static write_block_t p;
+
+	p.header = WRITE_BLOCK;
+	p.offset = offset;
+	p.length = length;
+	memcpy(&p.block, block, length);
+	p.block[length] = calculate_LRC((u8 *) & p, 3 + length);
+
+	return spi_xmt((u8 *) & p, 4 + length);
+}
+
+static inline int
+read_block(u8 offset, u8 length, u8 * buf)
+{
+	static read_block_t p;
+	static report_block_t q;
+	int ret;
+
+	p.header = READ_BLOCK;
+	p.offset = offset;
+	p.length = length;
+	p.LRC = calculate_LRC((u8 *) & p, sizeof(p) - 1);
+
+	if ((ret = spi_xmt((u8 *) & p, sizeof(p))) < 0)
+		return ret;
+
+	if ((ret = spi_rcv((u8 *) & q, sizeof(q))) < 0)
+		return ret;
+
+	if (ret == 0)
+		return -E_ZERO_BYTES;
+
+	if (ret != sizeof(4 + q.length))
+		return -E_PKT_SZ;
+
+	if (q.header != REPORT_BLOCK)
+		return -E_BAD_HEADER;
+
+	if (q.block[q.length] != calculate_LRC((u8 *) & q, 3 + q.length))
+		return -E_BAD_LRC;
+
+	if (length != q.length)
+		return -E_PKT_SZ;
+
+	memcpy(buf, &q.block, length);
+
+	return 0;
+}
+
+#ifdef	INNOVATOR_KEYB_DEBUG
+static void
+ctrl_dump_regs(void)
+{
+	int i;
+	int n;
+
+	for (i = 0; i < 256; i += 8) {
+		read_block(i, 16, buffer);
+		mdelay(1);
+	}
+}
+#endif
+
+/*****************************************************************************/
+
+static void
+process_pointing_report(struct innovator_hid_dev *hid, u8 * buffer)
+{
+	static int prev_x, prev_y, prev_btn;
+	int x, y, btn;
+	hid->keyboard = input_allocate_device();
+	hid->mouse = input_allocate_device();
+
+	if (buffer[1] & (1 << 3)) {
+		/* relative pointing device report */
+		x = buffer[2];
+		y = buffer[3];
+
+		/* check the sign and convert from 2's complement if negative */
+		if (buffer[1] & (1<<4))
+			x = ~(-x) - 255;
+
+		/* input driver wants -y */
+		if (buffer[1] & (1<<5))
+			y = -(~(-y) - 255);
+		else
+			y = -y;
+
+		input_report_key(hid->mouse,
+				 BTN_LEFT, buffer[1] & (1<<0));
+		input_report_key(hid->mouse,
+				 BTN_RIGHT, buffer[1] & (1<<1));
+		input_report_key(hid->mouse,
+				 BTN_MIDDLE, buffer[1] & (1<<2));
+		input_report_rel(hid->mouse, REL_X, x);
+		input_report_rel(hid->mouse, REL_Y, y);
+	} else {
+		/* REVISIT: Does this work? */
+		/* absolute pointing device report */
+		x = buffer[2] + ((buffer[1] & X_MSB_MASK) << X_MSB_SHIFT);
+		y = buffer[3] + ((buffer[1] & Y_MSB_MASK) << Y_MSB_SHIFT);
+		btn = buffer[1] & (1<<0);
+
+		if ((prev_x == x) && (prev_y == y)
+		    && (prev_btn == btn))
+			return;
+
+		input_report_key(hid->mouse, BTN_LEFT, btn);
+		input_report_abs(hid->mouse, ABS_X, x);
+		input_report_abs(hid->mouse, ABS_Y, y);
+		prev_x = x;
+		prev_y = y;
+		prev_btn = btn;
+	}
+	input_sync(hid->mouse);
+	dbg("HID X: %d Y: %d Functions: %x\n", x, y, buffer[1]);
+}
+
+/*
+ * Reference [1], Appendix A, Semtech standard PS/2 key number definitions,
+ * pgs. A-1 through A-3. The following table lists standard PS/2 key numbers
+ * used by the Juno® 01 keyboard manager.
+ *
+ * NOTES:
+ * 1. The following table indices are E0 codes which require special handling:
+ *	53..62, 77..78, 94, 96, 100, 102..104, 108..110
+ * 2. The following table indices are E1 codes which require special handling:
+ *	101
+ */
+
+static unsigned char usar2scancode[128] = {
+	0x00, 0x29, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+	0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+	0x18, 0x19, 0x1a, 0x1b, 0x2b, 0x1e, 0x1f, 0x20,
+	0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+	0x1c, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32,
+	0x33, 0x34, 0x35, 0x39, 0x01, 0x52, 0x53, 0x4b,
+	0x47, 0x4f, 0x48, 0x50, 0x49, 0x51, 0x4d, 0x37,
+	0x4e, 0x4f, 0x50, 0x51, 0x4b, 0x4c, 0x4d, 0x47,
+	0x48, 0x49, 0x52, 0x53, 0x4a, 0x1c, 0x35, 0x3b,
+	0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41, 0x42, 0x43,
+	0x44, 0x57, 0x58, 0x2a, 0x36, 0x38, 0x38, 0x1d,
+	0x1d, 0x3a, 0x45, 0x46, 0x2a, 0x1d, 0x5b, 0x5c,
+	0x5d, 0xff, 0x00, 0x00, 0x5e, 0x5f, 0x63, 0x70,
+	0x7b, 0x79, 0x7d, 0x73, 0x5b, 0x5c, 0x5d, 0x63,
+	0x65, 0x66, 0x68, 0x69, 0x6b, 0x56, 0x54, 0x00
+};
+
+/*
+ * The following are bit masks used to encode E0 scan codes which
+ * require special handling. However, scan codes 100 and 101 are
+ * excludable here since they each require unique multi-byte scan
+ * code translations and are therefore dealt with individually via
+ * handle_print_scr() and handle_pause() respectively below.
+ */
+
+static unsigned long int e0_codes1 = 0x030003ff; /* scan codes 53..84 */
+static unsigned long int e0_codes2 = 0x038e0a00; /* scan codes 85..116 */
+
+static void
+handle_print_scr(int up)
+{
+	if (up) {
+		input_report_key(hid->keyboard, 0xe0, 1);
+		input_report_key(hid->keyboard, 0xb7, 1);
+		input_report_key(hid->keyboard, 0xe0, 1);
+		input_report_key(hid->keyboard, 0xaa, 1);
+	} else {
+		input_report_key(hid->keyboard, 0xe0, 0);
+		input_report_key(hid->keyboard, 0x2a, 0);
+		input_report_key(hid->keyboard, 0xe0, 0);
+		input_report_key(hid->keyboard, 0x37, 0);
+	}
+}
+
+static void
+handle_pause(void)
+{
+	input_report_key(hid->keyboard, 0xe1, 0);
+	input_report_key(hid->keyboard, 0x1d, 0);
+	input_report_key(hid->keyboard, 0x45, 0);
+	input_report_key(hid->keyboard, 0xe1, 0);
+	input_report_key(hid->keyboard, 0x9d, 0);
+	input_report_key(hid->keyboard, 0xc5, 0);
+}
+
+static void
+process_keyboard_report(struct innovator_hid_dev *hid, u8 * buffer)
+{
+	unsigned char ch = buffer[1] & 0x7f;
+	int up = buffer[1] & 0x80 ? 1 : 0;
+	int is_e0 = 0;
+	hid->keyboard = input_allocate_device();
+	hid->mouse = input_allocate_device();
+
+	if ((ch == 106) || (ch == 107))
+		return;		/* no code */
+
+	if (ch == 100) {
+		handle_print_scr(up);
+		return;
+	}
+
+	if (ch == 101) {
+		handle_pause();
+		return;
+	}
+
+	if ((ch >= 53) && (ch <= 84)) {
+		/* first block of e0 codes */
+		is_e0 = e0_codes1 & (1 << (ch - 53));
+	} else if ((ch >= 85) && (ch <= 116)) {
+		/* second block of e0 codes */
+		is_e0 = e0_codes2 & (1 << (ch - 85));
+	}
+
+	if (is_e0) {
+		input_report_key(hid->keyboard, 0xe0, !up);
+	}
+	input_report_key(hid->keyboard, usar2scancode[ch], !up);
+	input_sync(hid->keyboard);
+}
+
+static irqreturn_t
+innovator_hid_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	if (ATN()) {
+		disable_irq(OMAP1510_INT_FPGA_ATN);
+		tasklet_schedule(&hid_tasklet);
+	}
+	return IRQ_HANDLED;
+}
+
+static void
+do_hid_tasklet(unsigned long unused)
+{
+	int ret;
+	if ((ret = report_async(buffer, 256)) == -1) {
+		dbg("Error: Bad Juno return value: %d\n", ret);
+	} else if (ret == KEYBOARD_REPORT) {
+		process_keyboard_report(hid, buffer);
+	} else if (ret == POINTING_REPORT) {
+		process_pointing_report(hid, buffer);
+	} else {
+		dbg("ERROR: bad report\n");
+	}
+	enable_irq(OMAP1510_INT_FPGA_ATN);
+}
+
+static int
+innovator_hid_open(struct input_dev *dev)
+{
+	if (hid->open++)
+		return 0;
+
+	if (request_irq(OMAP1510_INT_FPGA_ATN, (void *) innovator_hid_interrupt,
+			IRQF_DISABLED, PFX, hid) < 0)
+		return -EINVAL;
+
+	return 0;
+}
+
+static void
+innovator_hid_close(struct input_dev *dev)
+{
+	if (!--hid->open)
+		return;
+
+	if (hid == NULL)
+		return;
+
+	kfree(hid);
+}
+
+static int innovator_ps2_remove(struct device *dev)
+{
+	return 0;
+}
+
+static void innovator_ps2_device_release(struct device *dev)
+{
+	/* Nothing */
+}
+
+static int innovator_ps2_suspend(struct device *dev, pm_message_t state)
+{
+	u8 pmcomm = 0;
+
+	/*
+	 * Set SUS_STATE in REG_PM_COMM (Page 0 R0).  This will cause
+	 * PM_MOD bits of REG_PM_STATUS to show suspended state,
+	 * but the SUS_STAT bit of REG_PM_STATUS will continue to
+	 * reflect the state of the _HSUS pin.
+	 */
+
+	if (write_reg(REG_PAGENO, 0) < 0)
+		printk("ps2 suspend: write_reg REG_PAGENO error\n");
+
+	if (read_reg(REG_PM_COMM, &pmcomm) < 0)
+		printk("ps2 suspend: read_reg REG_PM_COMM error\n");
+		
+	if (write_reg(REG_PM_COMM, pmcomm | SUS_STATE) < 0)
+		printk("ps2 suspend: write_reg REG_PM_COMM error\n");
+
+	return 0;
+}
+
+static int innovator_ps2_resume(struct device *dev)
+{
+	u8 pmcomm = 0;
+
+	/*
+	 * Clear SUS_STATE from REG_PM_COMM (Page 0 R0).
+	 */
+
+	if (write_reg(REG_PAGENO, 0) < 0)
+		printk("ps2 resume: write_reg REG_PAGENO error\n");
+
+	if (read_reg(REG_PM_COMM, &pmcomm) < 0)
+		printk("ps2 resume: read_reg REG_PM_COMM error\n");
+
+	if (write_reg(REG_PM_COMM, pmcomm & ~SUS_STATE) < 0)
+		printk("ps2 resume: write_reg REG_PM_COMM error\n");
+
+	return 0;
+}
+
+static struct device_driver innovator_ps2_driver = {
+	.name		= "innovator_ps2",
+	.bus		= &platform_bus_type,
+	.remove		= innovator_ps2_remove,
+	.suspend	= innovator_ps2_suspend,
+	.resume		= innovator_ps2_resume,
+};
+
+static struct platform_device innovator_ps2_device = {
+	.name		= "ps2",
+	.id		= -1,
+	.dev = {
+		.driver		= &innovator_ps2_driver,
+		.release	= innovator_ps2_device_release,
+	},
+};
+
+static int __init
+innovator_kbd_init(void)
+{
+	int i;
+	info("Innovator PS/2 keyboard/mouse driver v1.0\n");
+
+	innovator_fpga_hid_reset();
+
+	if ((hid = kmalloc(sizeof(struct innovator_hid_dev),
+	     GFP_KERNEL)) == NULL) {
+		warn("unable to allocate space for HID device\n");
+		return -ENOMEM;
+	}
+
+	/* setup the mouse */
+	memset(hid, 0, sizeof(struct innovator_hid_dev));
+	hid->mouse = input_allocate_device();
+	hid->mouse->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);
+	hid->mouse->keybit[BIT_WORD(BTN_MOUSE)] =
+	    BIT(BTN_LEFT) | BIT(BTN_RIGHT) |
+	    BIT(BTN_MIDDLE) | BIT(BTN_TOUCH);
+	hid->mouse->relbit[0] = BIT(REL_X) | BIT(REL_Y);
+	hid->mouse->private = hid;
+	hid->mouse->open = innovator_hid_open;
+	hid->mouse->close = innovator_hid_close;
+	hid->mouse->name = "innovator_mouse";
+	hid->mouse->id.bustype = 0;
+	hid->mouse->id.vendor = 0;
+	hid->mouse->id.product = 0;
+	hid->mouse->id.version = 0;
+       hid->keyboard = input_allocate_device();
+	hid->keyboard->evbit[0] = BIT(EV_KEY) | BIT(EV_REP);
+       hid->keyboard->keycodesize = sizeof(unsigned char);
+       hid->keyboard->keycodemax = ARRAY_SIZE(usar2scancode);
+	for(i = 0; i < 128; i++)
+		set_bit(usar2scancode[i], hid->keyboard->keybit);
+	hid->keyboard->private = hid;
+	hid->keyboard->open = innovator_hid_open;
+	hid->keyboard->close = innovator_hid_close;
+	hid->keyboard->name = "innovator_keyboard";
+	hid->keyboard->id.bustype = 0;
+	hid->keyboard->id.vendor = 0;
+	hid->keyboard->id.product = 0;
+	hid->keyboard->id.version = 0;
+	input_register_device(hid->mouse);
+	input_register_device(hid->keyboard);
+	innovator_hid_open(hid->mouse);
+	innovator_hid_open(hid->keyboard);
+
+	if (driver_register(&innovator_ps2_driver) != 0)
+		printk(KERN_ERR "Driver register failed for innovator_ps2\n");
+
+	if (platform_device_register(&innovator_ps2_device) != 0) {
+		printk(KERN_ERR "Device register failed for ps2\n");
+		driver_unregister(&innovator_ps2_driver);
+	}
+
+#ifdef	INNOVATOR_KEYB_DEBUG
+	ctrl_dump_regs();
+#endif
+	return 0;
+}
+
+static void __exit
+innovator_kbd_exit(void)
+{
+	input_unregister_device(hid->mouse);
+	input_unregister_device(hid->keyboard);
+	free_irq(OMAP1510_INT_FPGA_ATN, hid);
+	if (hid != NULL)
+		kfree(hid);
+	driver_unregister(&innovator_ps2_driver);
+	platform_device_unregister(&innovator_ps2_device);
+	return;
+}
+
+module_init(innovator_kbd_init);
+module_exit(innovator_kbd_exit);
+
+MODULE_AUTHOR("George G. Davis <gdavis@mvista.com>");
+MODULE_DESCRIPTION("Innovator PS/2 Driver");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 09/33] add lm8323 keypad driver
  2008-08-30 17:16               ` [PATCH 08/33] add innovator ps2 keypad driver Felipe Balbi
@ 2008-08-30 17:16                 ` Felipe Balbi
  2008-08-30 17:16                   ` [PATCH 10/33] Add twl4030 " Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/input/keyboard/Kconfig  |    7 +
 drivers/input/keyboard/Makefile |    1 +
 drivers/input/keyboard/lm8323.c |  925 +++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/lm8323.h      |   37 ++
 4 files changed, 970 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/lm8323.c
 create mode 100644 include/linux/i2c/lm8323.h

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 01d6421..53239cb 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -269,6 +269,13 @@ config OMAP_PS2
 	  To compile this driver as a module, choose M here: the
 	  module will be called innovator_ps2.
 
+config KEYBOARD_LM8323
+	tristate "LM8323 keypad chip"
+	depends on I2C
+	help
+	  If you say yes here you get support for the National Semiconductor
+	  LM8323 keypad controller.
+
 config KEYBOARD_PXA27x
 	tristate "PXA27x/PXA3xx keypad support"
 	depends on PXA27x || PXA3xx
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index a48205b..52e9b70 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_KEYBOARD_HIL)		+= hil_kbd.o
 obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
+obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
 obj-$(CONFIG_KEYBOARD_GPIO)		+= gpio_keys.o
diff --git a/drivers/input/keyboard/lm8323.c b/drivers/input/keyboard/lm8323.c
new file mode 100644
index 0000000..72bb587
--- /dev/null
+++ b/drivers/input/keyboard/lm8323.c
@@ -0,0 +1,925 @@
+/*
+ * drivers/i2c/chips/lm8323.c
+ *
+ * Copyright (C) 2007 Nokia Corporation
+ *
+ * Written by Daniel Stone <daniel.stone@nokia.com>
+ *            Timo O. Karjalainen <timo.o.karjalainen@nokia.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 (version 2 of the License only).
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include <linux/i2c/lm8323.h>
+
+#include <asm/mach-types.h>
+#include <asm/mach/irq.h>
+
+#ifdef VERBOSE
+#define debug dev_dbg
+#else
+#define debug(...)
+#endif
+
+/* Commands to send to the chip. */
+#define LM8323_CMD_READ_ID		0x80 /* Read chip ID. */
+#define LM8323_CMD_WRITE_CFG		0x81 /* Set configuration item. */
+#define LM8323_CMD_READ_INT		0x82 /* Get interrupt status. */
+#define LM8323_CMD_RESET		0x83 /* Reset, same as external one */
+#define LM8323_CMD_WRITE_PORT_SEL	0x85 /* Set GPIO in/out. */
+#define LM8323_CMD_WRITE_PORT_STATE	0x86 /* Set GPIO pullup. */
+#define LM8323_CMD_READ_PORT_SEL	0x87 /* Get GPIO in/out. */
+#define LM8323_CMD_READ_PORT_STATE	0x88 /* Get GPIO pullup. */
+#define LM8323_CMD_READ_FIFO		0x89 /* Read byte from FIFO. */
+#define LM8323_CMD_RPT_READ_FIFO	0x8a /* Read FIFO (no increment). */
+#define LM8323_CMD_SET_ACTIVE		0x8b /* Set active time. */
+#define LM8323_CMD_READ_ERR		0x8c /* Get error status. */
+#define LM8323_CMD_READ_ROTATOR		0x8e /* Read rotator status. */
+#define LM8323_CMD_SET_DEBOUNCE		0x8f /* Set debouncing time. */
+#define LM8323_CMD_SET_KEY_SIZE		0x90 /* Set keypad size. */
+#define LM8323_CMD_READ_KEY_SIZE	0x91 /* Get keypad size. */
+#define LM8323_CMD_READ_CFG		0x92 /* Get configuration item. */
+#define LM8323_CMD_WRITE_CLOCK		0x93 /* Set clock config. */
+#define LM8323_CMD_READ_CLOCK		0x94 /* Get clock config. */
+#define LM8323_CMD_PWM_WRITE		0x95 /* Write PWM script. */
+#define LM8323_CMD_START_PWM		0x96 /* Start PWM engine. */
+#define LM8323_CMD_STOP_PWM		0x97 /* Stop PWM engine. */
+
+/* Interrupt status. */
+#define INT_KEYPAD			0x01 /* Key event. */
+#define INT_ROTATOR			0x02 /* Rotator event. */
+#define INT_ERROR			0x08 /* Error: use CMD_READ_ERR. */
+#define INT_NOINIT			0x10 /* Lost configuration. */
+#define INT_PWM1			0x20 /* PWM1 stopped. */
+#define INT_PWM2			0x40 /* PWM2 stopped. */
+#define INT_PWM3			0x80 /* PWM3 stopped. */
+
+/* Errors (signalled by INT_ERROR, read with CMD_READ_ERR). */
+#define ERR_BADPAR			0x01 /* Bad parameter. */
+#define ERR_CMDUNK			0x02 /* Unknown command. */
+#define ERR_KEYOVR			0x04 /* Too many keys pressed. */
+#define ERR_FIFOOVER			0x40 /* FIFO overflow. */
+
+/* Configuration keys (CMD_{WRITE,READ}_CFG). */
+#define CFG_MUX1SEL			0x01 /* Select MUX1_OUT input. */
+#define CFG_MUX1EN			0x02 /* Enable MUX1_OUT. */
+#define CFG_MUX2SEL			0x04 /* Select MUX2_OUT input. */
+#define CFG_MUX2EN			0x08 /* Enable MUX2_OUT. */
+#define CFG_PSIZE			0x20 /* Package size (must be 0). */
+#define CFG_ROTEN			0x40 /* Enable rotator. */
+
+/* Clock settings (CMD_{WRITE,READ}_CLOCK). */
+#define CLK_RCPWM_INTERNAL		0x00
+#define CLK_RCPWM_EXTERNAL		0x03
+#define CLK_SLOWCLKEN			0x08 /* Enable 32.768kHz clock. */
+#define CLK_SLOWCLKOUT			0x40 /* Enable slow pulse output. */
+
+/* The possible addresses corresponding to CONFIG1 and CONFIG2 pin wirings. */
+#define LM8323_I2C_ADDR00		(0x84 >> 1)	/* 1000 010x */
+#define LM8323_I2C_ADDR01		(0x86 >> 1)	/* 1000 011x */
+#define LM8323_I2C_ADDR10		(0x88 >> 1)	/* 1000 100x */
+#define LM8323_I2C_ADDR11		(0x8A >> 1)	/* 1000 101x */
+
+/* Key event fifo length */
+#define LM8323_FIFO_LEN			15
+
+/* Commands for PWM engine; feed in with PWM_WRITE. */
+/* Load ramp counter from duty cycle field (range 0 - 0xff). */
+#define PWM_SET(v)			(0x4000 | ((v) & 0xff))
+/* Go to start of script. */
+#define PWM_GOTOSTART			0x0000
+/*
+ * Stop engine (generates interrupt).  If reset is 1, clear the program
+ * counter, else leave it.
+ */
+#define PWM_END(reset)			(0xc000 | (!!(reset) << 11))
+/*
+ * Ramp.  If s is 1, divide clock by 512, else divide clock by 16.
+ * Take t clock scales (up to 63) per step, for n steps (up to 126).
+ * If u is set, ramp up, else ramp down.
+ */
+#define PWM_RAMP(s, t, n, u)		((!!(s) << 14) | ((t) & 0x3f) << 8 | \
+					 ((n) & 0x7f) | ((u) ? 0 : 0x80))
+/*
+ * Loop (i.e. jump back to pos) for a given number of iterations (up to 63).
+ * If cnt is zero, execute until PWM_END is encountered.
+ */
+#define PWM_LOOP(cnt, pos)		(0xa000 | (((cnt) & 0x3f) << 7) | \
+					 ((pos) & 0x3f))
+/*
+ * Wait for trigger.  Argument is a mask of channels, shifted by the channel
+ * number, e.g. 0xa for channels 3 and 1.  Note that channels are numbered
+ * from 1, not 0.
+ */
+#define PWM_WAIT_TRIG(chans)		(0xe000 | (((chans) & 0x7) << 6))
+/* Send trigger.  Argument is same as PWM_WAIT_TRIG. */
+#define PWM_SEND_TRIG(chans)		(0xe000 | ((chans) & 0x7))
+
+#define DRIVER_NAME  "lm8323"
+
+struct lm8323_pwm {
+	int			id;
+	int			enabled;
+	int			fade_time;
+	int			brightness;
+	int			desired_brightness;
+	struct work_struct	work;
+	struct led_classdev	cdev;
+};
+
+struct lm8323_chip {
+	struct mutex		lock;
+	struct i2c_client	*client;
+	struct work_struct	work;
+	struct input_dev	*idev;
+	unsigned		kp_enabled : 1;
+	unsigned		pm_suspend : 1;
+	unsigned		keys_down;
+	char			phys[32];
+	s16			keymap[LM8323_KEYMAP_SIZE];
+	int			size_x;
+	int			size_y;
+	int			debounce_time;
+	int			active_time;
+	struct lm8323_pwm	pwm1;
+	struct lm8323_pwm	pwm2;
+	struct lm8323_pwm	pwm3;
+};
+
+#define client_to_lm8323(c)	container_of(c, struct lm8323_chip, client)
+#define dev_to_lm8323(d)	container_of(d, struct lm8323_chip, client->dev)
+#define work_to_lm8323(w)	container_of(w, struct lm8323_chip, work)
+#define cdev_to_pwm(c)		container_of(c, struct lm8323_pwm, cdev)
+#define work_to_pwm(w)		container_of(w, struct lm8323_pwm, work)
+
+static struct lm8323_chip *pwm_to_lm8323(struct lm8323_pwm *pwm)
+{
+	switch (pwm->id) {
+	case 1:
+		return container_of(pwm, struct lm8323_chip, pwm1);
+	case 2:
+		return container_of(pwm, struct lm8323_chip, pwm2);
+	case 3:
+		return container_of(pwm, struct lm8323_chip, pwm3);
+	default:
+		return NULL;
+	}
+}
+
+static struct lm8323_platform_data *lm8323_pdata;
+
+
+#define LM8323_MAX_DATA 8
+
+/*
+ * To write, we just access the chip's address in write mode, and dump the
+ * command and data out on the bus.  The command byte and data are taken as
+ * sequential u8s out of varargs, to a maximum of LM8323_MAX_DATA.
+ */
+static int lm8323_write(struct lm8323_chip *lm, int len, ...)
+{
+	int ret, i;
+	va_list ap;
+	u8 data[LM8323_MAX_DATA];
+
+	va_start(ap, len);
+
+	if (unlikely(len > LM8323_MAX_DATA)) {
+		dev_err(&lm->client->dev, "tried to send %d bytes\n", len);
+		va_end(ap);
+		return 0;
+	}
+
+	for (i = 0; i < len; i++)
+		data[i] = va_arg(ap, int);
+
+	va_end(ap);
+
+	/*
+	 * If the host is asleep while we send the data, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, data, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "sent %d bytes of %d total\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * To read, we first send the command byte to the chip and end the transaction,
+ * then access the chip in read mode, at which point it will send the data.
+ */
+static int lm8323_read(struct lm8323_chip *lm, u8 cmd, u8 *buf, int len)
+{
+	int ret;
+
+	/*
+	 * If the host is asleep while we send the byte, we can get a NACK
+	 * back while it wakes up, so try again, once.
+	 */
+	ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret == -EREMOTEIO))
+		ret = i2c_master_send(lm->client, &cmd, 1);
+	if (unlikely(ret != 1)) {
+		dev_err(&lm->client->dev, "sending read cmd 0x%02x failed\n",
+			cmd);
+		return 0;
+	}
+
+	ret = i2c_master_recv(lm->client, buf, len);
+	if (unlikely(ret != len))
+		dev_err(&lm->client->dev, "wanted %d bytes, got %d\n",
+			len, ret);
+
+	return ret;
+}
+
+/*
+ * Set the chip active time (idle time before it enters halt).
+ */
+static void lm8323_set_active_time(struct lm8323_chip *lm, int time)
+{
+	lm8323_write(lm, 2, LM8323_CMD_SET_ACTIVE, time >> 2);
+}
+
+/*
+ * The signals are AT-style: the low 7 bits are the keycode, and the top
+ * bit indicates the state (1 for down, 0 for up).
+ */
+static inline u8 lm8323_whichkey(u8 event)
+{
+	return event & 0x7f;
+}
+
+static inline int lm8323_ispress(u8 event)
+{
+	return (event & 0x80) ? 1 : 0;
+}
+
+static void process_keys(struct lm8323_chip *lm)
+{
+	u8 event;
+	u8 key_fifo[LM8323_FIFO_LEN + 1];
+	int old_keys_down = lm->keys_down;
+	int ret;
+	int i = 0;
+
+	/*
+	 * Read all key events from the FIFO at once. Next READ_FIFO clears the
+	 * FIFO even if we didn't read all events previously.
+	 */
+	ret = lm8323_read(lm, LM8323_CMD_READ_FIFO, key_fifo, LM8323_FIFO_LEN);
+
+	if (ret < 0) {
+		dev_err(&lm->client->dev, "Failed reading fifo \n");
+		return;
+	}
+	key_fifo[ret] = 0;
+
+	while ((event = key_fifo[i])) {
+		u8 key = lm8323_whichkey(event);
+		int isdown = lm8323_ispress(event);
+		s16 keycode = lm->keymap[key];
+
+		if (likely(keycode > 0)) {
+			debug(&lm->client->dev, "key 0x%02x %s\n", key,
+			      isdown ? "down" : "up");
+			if (likely(lm->kp_enabled)) {
+				input_report_key(lm->idev, keycode, isdown);
+				input_sync(lm->idev);
+			}
+			if (isdown)
+				lm->keys_down++;
+			else
+				lm->keys_down--;
+		} else {
+			dev_err(&lm->client->dev, "keycode 0x%02x not mapped "
+				"to any key\n", key);
+		}
+		i++;
+	}
+
+	/*
+	 * Errata: We need to ensure that the chip never enters halt mode
+	 * during a keypress, so set active time to 0.  When it's released,
+	 * we can enter halt again, so set the active time back to normal.
+	 */
+	if (!old_keys_down && lm->keys_down)
+		lm8323_set_active_time(lm, 0);
+	if (old_keys_down && !lm->keys_down)
+		lm8323_set_active_time(lm, lm->active_time);
+}
+
+static void lm8323_process_error(struct lm8323_chip *lm)
+{
+	u8 error;
+
+	if (lm8323_read(lm, LM8323_CMD_READ_ERR, &error, 1) == 1) {
+		if (error & ERR_FIFOOVER)
+			debug(&lm->client->dev, "fifo overflow!\n");
+		if (error & ERR_KEYOVR)
+			debug(&lm->client->dev, "more than two keys pressed\n");
+		if (error & ERR_CMDUNK)
+			debug(&lm->client->dev, "unknown command submitted\n");
+		if (error & ERR_BADPAR)
+			debug(&lm->client->dev, "bad command parameter\n");
+	}
+}
+
+static void lm8323_reset(struct lm8323_chip *lm)
+{
+	/* The docs say we must pass 0xAA as the data byte. */
+	lm8323_write(lm, 2, LM8323_CMD_RESET, 0xAA);
+}
+
+static int lm8323_configure(struct lm8323_chip *lm)
+{
+	int keysize = (lm->size_x << 4) | lm->size_y;
+	int clock = (CLK_SLOWCLKEN | CLK_RCPWM_EXTERNAL);
+	int debounce = lm->debounce_time >> 2;
+	int active = lm->active_time >> 2;
+
+	/*
+	 * Active time must be greater than the debounce time: if it's
+	 * a close-run thing, give ourselves a 12ms buffer.
+	 */
+	if (debounce >= active)
+		active = debounce + 3;
+
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CFG, 0);
+	lm8323_write(lm, 2, LM8323_CMD_WRITE_CLOCK, clock);
+	lm8323_write(lm, 2, LM8323_CMD_SET_KEY_SIZE, keysize);
+	lm8323_set_active_time(lm, lm->active_time);
+	lm8323_write(lm, 2, LM8323_CMD_SET_DEBOUNCE, debounce);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_STATE, 0xff, 0xff);
+	lm8323_write(lm, 3, LM8323_CMD_WRITE_PORT_SEL, 0, 0);
+
+	/*
+	 * Not much we can do about errors at this point, so just hope
+	 * for the best.
+	 */
+
+	return 0;
+}
+
+/*
+ * Bottom half: handle the interrupt by posting key events, or dealing with
+ * errors appropriately.
+ */
+static void lm8323_work(struct work_struct *work)
+{
+	struct lm8323_chip *lm = work_to_lm8323(work);
+	u8 ints;
+
+	mutex_lock(&lm->lock);
+
+	while ((lm8323_read(lm, LM8323_CMD_READ_INT, &ints, 1) == 1) && ints) {
+		if (likely(ints & INT_KEYPAD))
+			process_keys(lm);
+		if (ints & INT_ROTATOR) {
+			/* We don't currently support the rotator. */
+			debug(&lm->client->dev, "rotator fired\n");
+		}
+		if (ints & INT_ERROR) {
+			debug(&lm->client->dev, "error!\n");
+			lm8323_process_error(lm);
+		}
+		if (ints & INT_NOINIT) {
+			dev_err(&lm->client->dev, "chip lost config; "
+						  "reinitialising\n");
+			lm8323_configure(lm);
+		}
+		if (ints & INT_PWM1)
+			debug(&lm->client->dev, "pwm1 engine completed\n");
+		if (ints & INT_PWM2)
+			debug(&lm->client->dev, "pwm2 engine completed\n");
+		if (ints & INT_PWM3)
+			debug(&lm->client->dev, "pwm3 engine completed\n");
+	}
+
+	mutex_unlock(&lm->lock);
+}
+
+/*
+ * We cannot use I2C in interrupt context, so we just schedule work.
+ */
+static irqreturn_t lm8323_irq(int irq, void *data)
+{
+	struct lm8323_chip *lm = data;
+
+	schedule_work(&lm->work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Read the chip ID.
+ */
+static int lm8323_read_id(struct lm8323_chip *lm, u8 *buf)
+{
+	int bytes;
+
+	bytes = lm8323_read(lm, LM8323_CMD_READ_ID, buf, 2);
+	if (unlikely(bytes != 2))
+		return -EIO;
+
+	return 0;
+}
+
+static void lm8323_write_pwm_one(struct lm8323_pwm *pwm, int pos, u16 cmd)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	lm8323_write(lm, 4, LM8323_CMD_PWM_WRITE, (pos << 2) | pwm->id,
+		     (cmd & 0xff00) >> 8, cmd & 0x00ff);
+}
+
+/*
+ * Write a script into a given PWM engine, concluding with PWM_END.
+ * If 'keepalive' is specified, the engine will be kept running
+ * indefinitely.
+ */
+static void lm8323_write_pwm(struct lm8323_pwm *pwm, int keepalive,
+			     int len, ...)
+{
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+	int i, cmd;
+	va_list ap;
+
+	/*
+	 * If there are any scripts running at the moment, terminate them
+	 * and make sure the duty cycle is as if it finished.
+	 */
+	lm8323_write(lm, 2, LM8323_CMD_STOP_PWM, pwm->id);
+
+	va_start(ap, len);
+	for (i = 0; i < len; i++) {
+		cmd = va_arg(ap, int);
+		lm8323_write_pwm_one(pwm, i, cmd);
+	}
+	va_end(ap);
+
+	/* Wait for a trigger from any channel. This keeps the engine alive. */
+	if (keepalive)
+		lm8323_write_pwm_one(pwm, i++, PWM_WAIT_TRIG(0xe));
+	else
+		lm8323_write_pwm_one(pwm, i++, PWM_END(1));
+
+	lm8323_write(lm, 2, LM8323_CMD_START_PWM, pwm->id);
+}
+
+static void lm8323_pwm_work(struct work_struct *work)
+{
+	struct lm8323_pwm *pwm = work_to_pwm(work);
+	int div, perstep, steps, hz, direction, keepalive;
+
+	/* Do nothing if we're already at the requested level. */
+	if (pwm->desired_brightness == pwm->brightness)
+		return;
+
+	keepalive = (pwm->desired_brightness > 0);
+	direction = (pwm->desired_brightness > pwm->brightness);
+	steps = abs(pwm->desired_brightness - pwm->brightness);
+
+	/*
+	 * Convert time (in ms) into a divisor (512 or 16 on a refclk of
+	 * 32768Hz), and number of ticks per step.
+	 */
+	if ((pwm->fade_time / steps) > (32768 / 512))
+		div = 512;
+	else
+		div = 16;
+
+	hz = 32768 / div;
+	if (pwm->fade_time < ((steps * 1000) / hz))
+		perstep = 1;
+	else
+		perstep = (hz * pwm->fade_time) / (steps * 1000);
+
+	if (perstep == 0)
+		perstep = 1;
+	else if (perstep > 63)
+		perstep = 63;
+
+	if (steps > 252) {
+		lm8323_write_pwm(pwm, keepalive, 3,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 252,
+					  direction));
+	} else if (steps > 126) {
+		lm8323_write_pwm(pwm, keepalive, 2,
+				 PWM_RAMP((div == 512), perstep, 126,
+					  direction),
+				 PWM_RAMP((div == 512), perstep, steps - 126,
+					  direction));
+	} else {
+		lm8323_write_pwm(pwm, keepalive, 1,
+				 PWM_RAMP((div == 512), perstep, steps,
+					  direction));
+	}
+
+	pwm->brightness = pwm->desired_brightness;
+}
+
+static void lm8323_pwm_set_brightness(struct led_classdev *led_cdev,
+				      enum led_brightness brightness)
+{
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	struct lm8323_chip *lm = pwm_to_lm8323(pwm);
+
+	pwm->desired_brightness = brightness;
+
+	if (in_interrupt()) {
+		schedule_work(&pwm->work);
+	} else {
+		/*
+		 * Schedule PWM work as usual unless we are going into suspend
+		 */
+		mutex_lock(&lm->lock);
+		if (likely(!lm->pm_suspend))
+			schedule_work(&pwm->work);
+		else
+			lm8323_pwm_work(&pwm->work);
+		mutex_unlock(&lm->lock);
+	}
+}
+
+static ssize_t lm8323_pwm_show_time(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+
+	return sprintf(buf, "%d\n", pwm->fade_time);
+}
+
+static ssize_t lm8323_pwm_store_time(struct device *dev,
+		struct device_attribute *attr, const char *buf, size_t len)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct lm8323_pwm *pwm = cdev_to_pwm(led_cdev);
+	int ret;
+	int time;
+
+	ret = sscanf(buf, "%d", &time);
+	/* Numbers only, please. */
+	if (ret)
+		return -EINVAL;
+
+	pwm->fade_time = time;
+
+	return strlen(buf);
+}
+static DEVICE_ATTR(time, 0644, lm8323_pwm_show_time, lm8323_pwm_store_time);
+
+static int init_pwm(struct lm8323_chip *lm, int id, struct device *dev,
+		    const char *name)
+{
+	struct lm8323_pwm *pwm = NULL;
+
+	BUG_ON(id > 3);
+
+	switch (id) {
+	case 1:
+		pwm = &lm->pwm1;
+		break;
+	case 2:
+		pwm = &lm->pwm2;
+		break;
+	case 3:
+		pwm = &lm->pwm3;
+		break;
+	}
+
+	pwm->id = id;
+	pwm->fade_time = 0;
+	pwm->brightness = 0;
+	pwm->desired_brightness = 0;
+	if (name) {
+		pwm->cdev.name = name;
+		pwm->cdev.brightness_set = lm8323_pwm_set_brightness;
+		if (led_classdev_register(dev, &pwm->cdev) < 0) {
+			dev_err(dev, "couldn't register PWM %d\n", id);
+			return -1;
+		}
+		if (device_create_file(pwm->cdev.dev,
+					     &dev_attr_time) < 0) {
+			dev_err(dev, "couldn't register time attribute\n");
+			led_classdev_unregister(&pwm->cdev);
+			return -1;
+		}
+		INIT_WORK(&pwm->work, lm8323_pwm_work);
+		pwm->enabled = 1;
+	} else {
+		pwm->enabled = 0;
+	}
+
+	return 0;
+}
+
+static struct i2c_driver lm8323_i2c_driver;
+
+static ssize_t lm8323_show_disable(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", !lm->kp_enabled);
+}
+
+static ssize_t lm8323_set_disable(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct lm8323_chip *lm = dev_get_drvdata(dev);
+	int ret;
+	int i;
+
+	i = sscanf(buf, "%d", &ret);
+
+	mutex_lock(&lm->lock);
+	lm->kp_enabled = !i;
+	mutex_unlock(&lm->lock);
+
+	return count;
+}
+static DEVICE_ATTR(disable_kp, 0644, lm8323_show_disable, lm8323_set_disable);
+
+static int lm8323_probe(struct i2c_client *client,
+					const struct i2c_device_id *id)
+{
+	struct input_dev *idev;
+	struct lm8323_chip *lm;
+	int i, err = 0;
+	unsigned long tmo;
+	u8 data[2];
+
+	lm = kzalloc(sizeof *lm, GFP_KERNEL);
+	if (!lm)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, lm);
+	lm->client = client;
+	lm8323_pdata = client->dev.platform_data;
+	if (!lm8323_pdata)
+		return -EINVAL; /* ? */
+
+	lm->size_x = lm8323_pdata->size_x;
+	if (lm->size_x == 0) {
+		lm->size_x = 8;
+	} else if (lm->size_x > 8) {
+		dev_err(&client->dev, "invalid x size %d specified\n",
+				lm->size_x);
+		lm->size_x = 8;
+	}
+
+	lm->size_y = lm8323_pdata->size_y;
+	if (lm->size_y == 0) {
+		lm->size_y = 12;
+	} else if (lm->size_y > 12) {
+		dev_err(&client->dev, "invalid y size %d specified\n",
+				lm->size_y);
+		lm->size_x = 12;
+	}
+
+	debug(&c->dev, "Keypad size: %d x %d\n", lm->size_x, lm->size_y);
+
+	lm->debounce_time = lm8323_pdata->debounce_time;
+	if (lm->debounce_time == 0) /* Default. */
+		lm->debounce_time = 12;
+	else if (lm->debounce_time == -1) /* Disable debounce. */
+		lm->debounce_time = 0;
+
+	lm->active_time = lm8323_pdata->active_time;
+	if (lm->active_time == 0) /* Default. */
+		lm->active_time = 500;
+	else if (lm->active_time == -1) /* Disable sleep. */
+		lm->active_time = 0;
+
+	lm8323_reset(lm);
+
+	/* Nothing's set up to service the IRQ yet, so just spin for max.
+	 * 100ms until we can configure. */
+	tmo = jiffies + msecs_to_jiffies(100);
+	while (lm8323_read(lm, LM8323_CMD_READ_INT, data, 1) == 1) {
+		if (data[0] & INT_NOINIT)
+			break;
+
+		if (time_after(jiffies, tmo)) {
+			dev_err(&client->dev,
+					"timeout waiting for initialisation\n");
+			break;
+		}
+
+		msleep(1);
+	}
+	lm8323_configure(lm);
+
+	/* If a true probe check the device */
+	if (lm8323_read_id(lm, data) != 0) {
+		dev_err(&client->dev, "device not found\n");
+		err = -ENODEV;
+		goto fail2;
+	}
+
+	if (init_pwm(lm, 1, &client->dev, lm8323_pdata->pwm1_name) < 0)
+		goto fail3;
+	if (init_pwm(lm, 2, &client->dev, lm8323_pdata->pwm2_name) < 0)
+		goto fail4;
+	if (init_pwm(lm, 3, &client->dev, lm8323_pdata->pwm3_name) < 0)
+		goto fail5;
+
+	mutex_init(&lm->lock);
+	INIT_WORK(&lm->work, lm8323_work);
+
+	err = request_irq(client->irq, lm8323_irq,
+			  IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			  IRQF_SAMPLE_RANDOM, DRIVER_NAME, lm);
+	if (err) {
+		dev_err(&client->dev, "could not get IRQ %d\n", client->irq);
+		goto fail6;
+	}
+
+	set_irq_wake(client->irq, 1);
+
+	lm->kp_enabled = 1;
+	err = device_create_file(&client->dev, &dev_attr_disable_kp);
+	if (err < 0)
+		goto fail7;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		err = -ENOMEM;
+		goto fail8;
+	}
+
+	if (lm8323_pdata->name)
+		idev->name = lm8323_pdata->name;
+	else
+		idev->name = "LM8323 keypad";
+	snprintf(lm->phys, sizeof(lm->phys), "%s/input-kp", client->dev.bus_id);
+	idev->phys = lm->phys;
+
+	lm->keys_down = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < LM8323_KEYMAP_SIZE; i++) {
+		if (lm8323_pdata->keymap[i] > 0)
+			set_bit(lm8323_pdata->keymap[i], idev->keybit);
+
+		lm->keymap[i] = lm8323_pdata->keymap[i];
+	}
+
+	if (lm8323_pdata->repeat)
+		set_bit(EV_REP, idev->evbit);
+
+	lm->idev = idev;
+	err = input_register_device(idev);
+	if (err) {
+		dev_dbg(&client->dev, "error registering input device\n");
+		goto fail8;
+	}
+
+	return 0;
+
+fail8:
+	device_remove_file(&client->dev, &dev_attr_disable_kp);
+fail7:
+	free_irq(client->irq, lm);
+fail6:
+	if (lm->pwm3.enabled)
+		led_classdev_unregister(&lm->pwm3.cdev);
+fail5:
+	if (lm->pwm2.enabled)
+		led_classdev_unregister(&lm->pwm2.cdev);
+fail4:
+	if (lm->pwm1.enabled)
+		led_classdev_unregister(&lm->pwm1.cdev);
+fail3:
+fail2:
+	kfree(lm);
+	return err;
+}
+
+static int lm8323_remove(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	free_irq(client->irq, lm);
+	cancel_work_sync(&lm->work);
+	input_unregister_device(lm->idev);
+	device_remove_file(&lm->client->dev, &dev_attr_disable_kp);
+	if (lm->pwm3.enabled)
+		led_classdev_unregister(&lm->pwm3.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_unregister(&lm->pwm2.cdev);
+	if (lm->pwm1.enabled)
+		led_classdev_unregister(&lm->pwm1.cdev);
+	kfree(lm);
+
+	return 0;
+}
+
+/*
+ * We don't need to explicitly suspend the chip, as it already switches off
+ * when there's no activity.
+ */
+static int lm8323_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	set_irq_wake(client->irq, 0);
+	disable_irq(client->irq);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 1;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_suspend(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_suspend(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_suspend(&lm->pwm3.cdev);
+
+	return 0;
+}
+
+static int lm8323_resume(struct i2c_client *client)
+{
+	struct lm8323_chip *lm = i2c_get_clientdata(client);
+
+	mutex_lock(&lm->lock);
+	lm->pm_suspend = 0;
+	mutex_unlock(&lm->lock);
+
+	if (lm->pwm1.enabled)
+		led_classdev_resume(&lm->pwm1.cdev);
+	if (lm->pwm2.enabled)
+		led_classdev_resume(&lm->pwm2.cdev);
+	if (lm->pwm3.enabled)
+		led_classdev_resume(&lm->pwm3.cdev);
+
+	enable_irq(client->irq);
+	set_irq_wake(client->irq, 1);
+
+	return 0;
+}
+
+static const struct i2c_device_id lm8323_id[] = {
+	{ DRIVER_NAME, 0 },
+	{ }
+};
+
+static struct i2c_driver lm8323_i2c_driver = {
+	.driver = {
+		.name	 = DRIVER_NAME,
+	},
+	.probe		= lm8323_probe,
+	.remove		= __devexit_p(lm8323_remove),
+	.suspend	= lm8323_suspend,
+	.resume		= lm8323_resume,
+	.id_table	= lm8323_id,
+};
+MODULE_DEVICE_TABLE(i2c, lm8323_id);
+
+static int __init lm8323_init(void)
+{
+	return i2c_add_driver(&lm8323_i2c_driver);
+}
+
+static void __exit lm8323_exit(void)
+{
+	i2c_del_driver(&lm8323_i2c_driver);
+}
+
+MODULE_AUTHOR("Daniel Stone");
+MODULE_DESCRIPTION("LM8323 keypad driver");
+MODULE_LICENSE("GPL");
+
+module_init(lm8323_init);
+module_exit(lm8323_exit);
diff --git a/include/linux/i2c/lm8323.h b/include/linux/i2c/lm8323.h
new file mode 100644
index 0000000..17d6b33
--- /dev/null
+++ b/include/linux/i2c/lm8323.h
@@ -0,0 +1,37 @@
+/*
+ * include/lm8323.h
+ *
+ * Configuration for LM8323 keypad driver.
+ */
+
+#ifndef __LINUX_LM8323_H
+#define __LINUX_LM8323_H
+
+#include <linux/types.h>
+
+/*
+ * Largest keycode that the chip can send, plus one,
+ * so keys can be mapped directly at the index of the
+ * LM8323 keycode instead of subtracting one.
+ */
+#define LM8323_KEYMAP_SIZE (0x7f + 1)
+
+struct lm8323_platform_data {
+	int debounce_time; /* Time to watch for key bouncing, in ms. */
+	int active_time; /* Idle time until sleep, in ms. */
+
+	int size_x;
+	int size_y;
+	int repeat : 1;
+	const s16 *keymap;
+
+	char *pwm1_name; /* Device name for PWM1. */
+	char *pwm2_name; /* Device name for PWM2. */
+	char *pwm3_name; /* Device name for PWM3. */
+
+	char *name; /* Device name. */
+};
+
+void __init lm8323_set_platform_data(struct lm8323_platform_data *pdata);
+
+#endif /* __LINUX_LM8323_H */
-- 
1.6.0.1.141.g445ca


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

* [PATCH 10/33] Add twl4030 keypad driver
  2008-08-30 17:16                 ` [PATCH 09/33] add lm8323 " Felipe Balbi
@ 2008-08-30 17:16                   ` Felipe Balbi
  2008-08-30 17:16                     ` [PATCH 11/33] add tsc2301 " Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/mach-omap2/board-2430sdp.c         |   59 ++++
 arch/arm/plat-omap/include/mach/keypad.h    |    1 +
 drivers/input/keyboard/Kconfig              |   10 +
 drivers/input/keyboard/Makefile             |    1 +
 drivers/input/keyboard/omap-twl4030keypad.c |  415 +++++++++++++++++++++++++++
 drivers/input/keyboard/twl4030-keypad.h     |   82 ++++++
 6 files changed, 568 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/omap-twl4030keypad.c
 create mode 100644 drivers/input/keyboard/twl4030-keypad.h

diff --git a/arch/arm/mach-omap2/board-2430sdp.c b/arch/arm/mach-omap2/board-2430sdp.c
index b72ca13..975318d 100644
--- a/arch/arm/mach-omap2/board-2430sdp.c
+++ b/arch/arm/mach-omap2/board-2430sdp.c
@@ -19,6 +19,8 @@
 #include <linux/mtd/mtd.h>
 #include <linux/mtd/partitions.h>
 #include <linux/delay.h>
+#include <linux/input.h>
+#include <linux/i2c/twl4030.h>
 #include <linux/err.h>
 #include <linux/clk.h>
 
@@ -32,6 +34,7 @@
 #include <mach/mux.h>
 #include <mach/board.h>
 #include <mach/common.h>
+#include <mach/keypad.h>
 #include <mach/gpmc.h>
 
 #include <asm/io.h>
@@ -114,8 +117,64 @@ static struct platform_device sdp2430_smc91x_device = {
 	.resource	= sdp2430_smc91x_resources,
 };
 
+/*
+ * Key mapping for 2430 SDP board
+ */
+
+static int sdp2430_keymap[] = {
+	KEY(0, 0, KEY_LEFT),
+	KEY(0, 1, KEY_RIGHT),
+	KEY(0, 2, KEY_A),
+	KEY(0, 3, KEY_B),
+	KEY(0, 4, KEY_C),
+	KEY(1, 0, KEY_DOWN),
+	KEY(1, 1, KEY_UP),
+	KEY(1, 2, KEY_E),
+	KEY(1, 3, KEY_F),
+	KEY(1, 4, KEY_G),
+	KEY(2, 0, KEY_ENTER),
+	KEY(2, 1, KEY_I),
+	KEY(2, 2, KEY_J),
+	KEY(2, 3, KEY_K),
+	KEY(2, 4, KEY_3),
+	KEY(3, 0, KEY_M),
+	KEY(3, 1, KEY_N),
+	KEY(3, 2, KEY_O),
+	KEY(3, 3, KEY_P),
+	KEY(3, 4, KEY_Q),
+	KEY(4, 0, KEY_R),
+	KEY(4, 1, KEY_4),
+	KEY(4, 2, KEY_T),
+	KEY(4, 3, KEY_U),
+	KEY(4, 4, KEY_D),
+	KEY(5, 0, KEY_V),
+	KEY(5, 1, KEY_W),
+	KEY(5, 2, KEY_L),
+	KEY(5, 3, KEY_S),
+	KEY(5, 4, KEY_H),
+	0
+};
+
+static struct omap_kp_platform_data sdp2430_kp_data = {
+	.rows		= 5,
+	.cols		= 6,
+	.keymap		= sdp2430_keymap,
+	.keymapsize	= ARRAY_SIZE(sdp2430_keymap),
+	.rep		= 1,
+	.irq		= TWL4030_MODIRQ_KEYPAD,
+};
+
+static struct platform_device sdp2430_kp_device = {
+	.name		= "omap_twl4030keypad",
+	.id		= -1,
+	.dev		= {
+		.platform_data	= &sdp2430_kp_data,
+	},
+};
+
 static struct platform_device *sdp2430_devices[] __initdata = {
 	&sdp2430_smc91x_device,
+	&sdp2430_kp_device,
 	&sdp2430_flash_device,
 };
 
diff --git a/arch/arm/plat-omap/include/mach/keypad.h b/arch/arm/plat-omap/include/mach/keypad.h
index 232923a..ba1c95c 100644
--- a/arch/arm/plat-omap/include/mach/keypad.h
+++ b/arch/arm/plat-omap/include/mach/keypad.h
@@ -14,6 +14,7 @@ struct omap_kp_platform_data {
 	int rows;
 	int cols;
 	int *keymap;
+	int irq;
 	unsigned int keymapsize;
 	unsigned int rep:1;
 	unsigned long delay;
diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 53239cb..04e6c77 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -259,6 +259,16 @@ config KEYBOARD_OMAP
 	  To compile this driver as a module, choose M here: the
 	  module will be called omap-keypad.
 
+config KEYBOARD_TWL4030
+	tristate "TI TWL4030 keypad support"
+	depends on TWL4030_CORE && (MACH_OMAP_2430SDP || MACH_OMAP2EVM || MACH_OMAP_3430SDP || MACH_OMAP3EVM)
+	help
+	  Say Y here if you want to use the OMAP TWL4030 keypad.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called omap-twl4030keypad. This driver depends on
+	  TWL4030 Core and TWL4030 GPIO I2C client driver
+
 config OMAP_PS2
 	tristate "TI OMAP Innovator 1510 PS/2 keyboard & mouse support"
 	depends on ARCH_OMAP15XX && MACH_OMAP_INNOVATOR
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 52e9b70..a19e04b 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
 obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
+obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
 obj-$(CONFIG_KEYBOARD_AAED2000)		+= aaed2000_kbd.o
 obj-$(CONFIG_KEYBOARD_GPIO)		+= gpio_keys.o
diff --git a/drivers/input/keyboard/omap-twl4030keypad.c b/drivers/input/keyboard/omap-twl4030keypad.c
new file mode 100644
index 0000000..3893d63
--- /dev/null
+++ b/drivers/input/keyboard/omap-twl4030keypad.c
@@ -0,0 +1,415 @@
+/*
+ * drivers/input/keyboard/omap-twl4030keypad.c
+ *
+ * Copyright (C) 2007 Texas Instruments, Inc.
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Code re-written for 2430SDP by:
+ * Syed Mohammed Khasim <x0khasim@ti.com>
+ *
+ * Initial Code:
+ * Manjunatha G K <manjugk@ti.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/bitops.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/i2c/twl4030.h>
+#include <linux/irq.h>
+#include <mach/keypad.h>
+#include "twl4030-keypad.h"
+
+#define PTV_PRESCALER		4
+
+#define MAX_ROWS		8 /* TWL4030 hardlimit */
+#define ROWCOL_MASK		0xFF000000
+#define KEYNUM_MASK		0x00FFFFFF
+
+/* Global variables */
+
+struct omap_keypad {
+	int		*keymap;
+	unsigned int	keymapsize;
+	u16		kp_state[MAX_ROWS];
+	int		n_rows;
+	int		n_cols;
+	int		irq;
+
+	struct device	*dbg_dev;
+	struct input_dev *omap_twl4030kp;
+
+	/* sync read/write */
+	struct mutex	mutex;
+};
+
+static int twl4030_kpread(struct omap_keypad *kp,
+		u32 module, u8 *data, u32 reg, u8 num_bytes)
+{
+	int ret;
+
+	ret = twl4030_i2c_read(module, data, reg, num_bytes);
+	if (ret < 0) {
+		dev_warn(kp->dbg_dev,
+			"Couldn't read TWL4030: %X - ret %d[%x]\n",
+			 reg, ret, ret);
+		return ret;
+	}
+	return ret;
+}
+
+static int twl4030_kpwrite_u8(struct omap_keypad *kp,
+		u32 module, u8 data, u32 reg)
+{
+	int ret;
+
+	ret = twl4030_i2c_write_u8(module, data, reg);
+	if (ret < 0) {
+		dev_warn(kp->dbg_dev,
+			"Could not write TWL4030: %X - ret %d[%x]\n",
+			 reg, ret, ret);
+		return ret;
+	}
+	return ret;
+}
+
+static int omap_kp_find_key(struct omap_keypad *kp, int col, int row)
+{
+	int i, rc;
+
+	rc = KEY(col, row, 0);
+	for (i = 0; i < kp->keymapsize; i++)
+		if ((kp->keymap[i] & ROWCOL_MASK) == rc)
+			return kp->keymap[i] & KEYNUM_MASK;
+
+	return -EINVAL;
+}
+
+static inline u16 omap_kp_col_xlate(struct omap_keypad *kp, u8 col)
+{
+	/* If all bits in a row are active for all coloumns then
+	 * we have that row line connected to gnd. Mark this
+	 * key on as if it was on matrix position n_cols (ie
+	 * one higher than the size of the matrix).
+	 */
+	if (col == 0xFF)
+		return 1 << kp->n_cols;
+	else
+		return col & ((1 << kp->n_cols) - 1);
+}
+
+static int omap_kp_read_kp_matrix_state(struct omap_keypad *kp, u16 *state)
+{
+	u8 new_state[MAX_ROWS];
+	int row;
+	int ret = twl4030_kpread(kp, TWL4030_MODULE_KEYPAD,
+				 new_state, KEYP_FULL_CODE_7_0, kp->n_rows);
+	if (ret >= 0) {
+		for (row = 0; row < kp->n_rows; row++)
+			state[row] = omap_kp_col_xlate(kp, new_state[row]);
+	}
+	return ret;
+}
+
+static int omap_kp_is_in_ghost_state(struct omap_keypad *kp, u16 *key_state)
+{
+	int i;
+	u16 check = 0;
+
+	for (i = 0; i < kp->n_rows; i++) {
+		u16 col = key_state[i];
+
+		if ((col & check) && hweight16(col) > 1)
+			return 1;
+		check |= col;
+	}
+
+	return 0;
+}
+
+static void twl4030_kp_scan(struct omap_keypad *kp, int release_all)
+{
+	u16 new_state[MAX_ROWS];
+	int col, row;
+
+	if (release_all)
+		memset(new_state, 0, sizeof(new_state));
+	else {
+		/* check for any changes */
+		int ret = omap_kp_read_kp_matrix_state(kp, new_state);
+		if (ret < 0)	/* panic ... */
+			return;
+
+		if (omap_kp_is_in_ghost_state(kp, new_state))
+			return;
+	}
+
+	mutex_lock(&kp->mutex);
+
+	/* check for changes and print those */
+	for (row = 0; row < kp->n_rows; row++) {
+		int changed = new_state[row] ^ kp->kp_state[row];
+
+		if (!changed)
+			continue;
+
+		for (col = 0; col < kp->n_cols + 1; col++) {
+			int key;
+
+			if (!(changed & (1 << col)))
+				continue;
+
+			dev_dbg(kp->dbg_dev, "key [%d:%d] %s\n", row, col,
+				(new_state[row] & (1 << col)) ?
+				"press" : "release");
+
+			key = omap_kp_find_key(kp, col, row);
+			if (key < 0)
+				dev_warn(kp->dbg_dev,
+					"Spurious key event %d-%d\n",
+					 col, row);
+			else
+				input_report_key(kp->omap_twl4030kp, key,
+						 new_state[row] & (1 << col));
+		}
+		kp->kp_state[row] = new_state[row];
+	}
+
+	mutex_unlock(&kp->mutex);
+}
+
+/*
+ * Keypad interrupt handler
+ */
+static irqreturn_t do_kp_irq(int irq, void *_kp)
+{
+	struct omap_keypad *kp = _kp;
+	u8 reg;
+	int ret;
+
+	/* Read & Clear TWL4030 pending interrupt */
+	ret = twl4030_kpread(kp, TWL4030_MODULE_KEYPAD, &reg, KEYP_ISR1, 1);
+
+	/* Release all keys if I2C has gone bad or
+	 * the KEYP has gone to idle state */
+	if ((ret >= 0) && (reg & KEYP_IMR1_KP))
+		twl4030_kp_scan(kp, 0);
+	else
+		twl4030_kp_scan(kp, 1);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Registers keypad device with input sub system
+ * and configures TWL4030 keypad registers
+ */
+static int __init omap_kp_probe(struct platform_device *pdev)
+{
+	u8 reg;
+	int i;
+	int ret = 0;
+	struct omap_keypad *kp;
+	struct omap_kp_platform_data *pdata = pdev->dev.platform_data;
+
+	kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+	if (!kp)
+		return -ENOMEM;
+
+	if (!pdata->rows || !pdata->cols || !pdata->keymap) {
+		dev_err(kp->dbg_dev, "No rows, cols or keymap from pdata\n");
+		kfree(kp);
+		return -EINVAL;
+	}
+
+	dev_set_drvdata(&pdev->dev, kp);
+
+	/* Get the debug Device */
+	kp->dbg_dev = &pdev->dev;
+
+	kp->omap_twl4030kp = input_allocate_device();
+	if (!kp->omap_twl4030kp) {
+		kfree(kp);
+		return -ENOMEM;
+	}
+
+	mutex_init(&kp->mutex);
+
+	kp->keymap = pdata->keymap;
+	kp->keymapsize = pdata->keymapsize;
+	kp->n_rows = pdata->rows;
+	kp->n_cols = pdata->cols;
+	kp->irq = pdata->irq;
+
+	/* setup input device */
+	set_bit(EV_KEY, kp->omap_twl4030kp->evbit);
+
+	/* Enable auto repeat feature of Linux input subsystem */
+	if (pdata->rep)
+		set_bit(EV_REP, kp->omap_twl4030kp->evbit);
+
+	for (i = 0; i < kp->keymapsize; i++)
+		set_bit(kp->keymap[i] & KEYNUM_MASK,
+				kp->omap_twl4030kp->keybit);
+
+	kp->omap_twl4030kp->name	= "omap_twl4030keypad";
+	kp->omap_twl4030kp->phys	= "omap_twl4030keypad/input0";
+	kp->omap_twl4030kp->dev.parent	= &pdev->dev;
+
+	kp->omap_twl4030kp->id.bustype	= BUS_HOST;
+	kp->omap_twl4030kp->id.vendor	= 0x0001;
+	kp->omap_twl4030kp->id.product	= 0x0001;
+	kp->omap_twl4030kp->id.version	= 0x0003;
+
+	kp->omap_twl4030kp->keycode	= kp->keymap;
+	kp->omap_twl4030kp->keycodesize	= sizeof(unsigned int);
+	kp->omap_twl4030kp->keycodemax	= kp->keymapsize;
+
+	ret = input_register_device(kp->omap_twl4030kp);
+	if (ret < 0) {
+		dev_err(kp->dbg_dev,
+			"Unable to register twl4030 keypad device\n");
+		goto err2;
+	}
+
+	/* Disable auto-repeat */
+	reg = KEYP_CTRL_NOAUTORPT;
+	ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD, reg, KEYP_CTRL);
+	if (ret < 0)
+		goto err3;
+
+	/* Enable TO rising and KP rising and falling edge detection */
+	reg = KEYP_EDR_KP_BOTH | KEYP_EDR_TO_RISING;
+	ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD, reg, KEYP_EDR);
+	if (ret < 0)
+		goto err3;
+
+	/* Set PTV prescaler Field */
+	reg = (PTV_PRESCALER << KEYP_LK_PTV_PTV_SHIFT);
+	ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD, reg, KEYP_LK_PTV);
+	if (ret < 0)
+		goto err3;
+
+	/* Set key debounce time to 20 ms */
+	i = KEYP_PERIOD_US(20000, PTV_PRESCALER);
+	ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD, i, KEYP_DEB);
+	if (ret < 0)
+		goto err3;
+
+	/* Set timeout period to 100 ms */
+	i = KEYP_PERIOD_US(200000, PTV_PRESCALER);
+	ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD,
+				 (i & 0xFF), KEYP_TIMEOUT_L);
+	if (ret < 0)
+		goto err3;
+
+	ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD,
+				 (i >> 8), KEYP_TIMEOUT_H);
+	if (ret < 0)
+		goto err3;
+
+	/* Enable Clear-on-Read */
+	reg = KEYP_SIH_CTRL_COR | KEYP_SIH_CTRL_PEND_DIS;
+	ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD,
+				 reg, KEYP_SIH_CTRL);
+	if (ret < 0)
+		goto err3;
+
+	/*
+	 * This ISR will always execute in kernel thread context because of
+	 * the need to access the TWL4030 over the I2C bus.
+	 */
+	ret = request_irq(kp->irq, do_kp_irq, IRQF_DISABLED,
+			"TWL4030 Keypad", kp);
+	if (ret < 0) {
+		dev_info(kp->dbg_dev, "request_irq failed for irq no=%d\n",
+			kp->irq);
+		goto err3;
+	} else {
+		/* Enable KP and TO interrupts now. */
+		reg = ~(KEYP_IMR1_KP | KEYP_IMR1_TO);
+		ret = twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD,
+					 reg, KEYP_IMR1);
+		if (ret < 0)
+			goto err5;
+	}
+
+	ret = omap_kp_read_kp_matrix_state(kp, kp->kp_state);
+	if (ret < 0)
+		goto err4;
+
+	return ret;
+err5:
+	/* mask all events - we don't care about the result */
+	(void) twl4030_kpwrite_u8(kp, TWL4030_MODULE_KEYPAD, 0xff, KEYP_IMR1);
+err4:
+	free_irq(kp->irq, NULL);
+err3:
+	input_unregister_device(kp->omap_twl4030kp);
+err2:
+	input_free_device(kp->omap_twl4030kp);
+
+	return -ENODEV;
+}
+
+static int omap_kp_remove(struct platform_device *pdev)
+{
+	struct omap_keypad *kp = dev_get_drvdata(&pdev->dev);
+
+	free_irq(kp->irq, kp);
+	input_unregister_device(kp->omap_twl4030kp);
+	kfree(kp);
+
+	return 0;
+}
+
+
+static struct platform_driver omap_kp_driver = {
+	.probe		= omap_kp_probe,
+	.remove		= __devexit_p(omap_kp_remove),
+	.driver		= {
+		.name	= "omap_twl4030keypad",
+		.owner	= THIS_MODULE,
+	},
+};
+
+/*
+ * OMAP TWL4030 Keypad init
+ */
+static int __devinit omap_kp_init(void)
+{
+	return platform_driver_register(&omap_kp_driver);
+}
+
+static void __exit omap_kp_exit(void)
+{
+	platform_driver_unregister(&omap_kp_driver);
+}
+
+module_init(omap_kp_init);
+module_exit(omap_kp_exit);
+MODULE_ALIAS("platform:omap_twl4030keypad");
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("OMAP TWL4030 Keypad Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/keyboard/twl4030-keypad.h b/drivers/input/keyboard/twl4030-keypad.h
new file mode 100644
index 0000000..b903a77
--- /dev/null
+++ b/drivers/input/keyboard/twl4030-keypad.h
@@ -0,0 +1,82 @@
+/*
+ * drivers/input/keyboard/twl4030-keypad.h
+ *
+ * Copyright (C) 2006-2007 Texas Instruments, Inc.
+ *
+ * Intial Code:
+ *	Syed Mohammed Khasim <x0khasim@ti.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __TWL4030_KEYPAD_H__
+#define __TWL4030_KEYPAD_H__
+
+/* Register Definitions */
+#define KEYP_CTRL				0x00
+#define KEYP_DEB				0x01
+#define KEYP_LONG_KEY				0x02
+#define KEYP_LK_PTV				0x03
+#define KEYP_TIMEOUT_L				0x04
+#define KEYP_TIMEOUT_H				0x05
+#define KEYP_FULL_CODE_7_0			0x09
+#define KEYP_ISR1				0x11
+#define KEYP_IMR1				0x12
+#define KEYP_EDR				0x16
+#define KEYP_SIH_CTRL				0x17
+
+/* KEYP_CTRL_REG Fields */
+#define KEYP_CTRL_SOFT_NRST			0x01
+#define KEYP_CTRL_SOFTMODEN			0x02
+#define KEYP_CTRL_LK_EN				0x04
+#define KEYP_CTRL_TOE_EN			0x08
+#define KEYP_CTRL_TOLE_EN			0x10
+#define KEYP_CTRL_RP_EN				0x20
+#define KEYP_CTRL_KBD_ON			0x40
+
+
+#define KEYP_CTRL_NOAUTORPT			(KEYP_CTRL_SOFT_NRST |	\
+						 KEYP_CTRL_SOFTMODEN |	\
+						 KEYP_CTRL_TOE_EN |	\
+						 KEYP_CTRL_KBD_ON)
+
+/* KEYP_DEB, KEYP_LONG_KEY, KEYP_TIMEOUT_x*/
+#define KEYP_PERIOD_US(T, prescale)		(T / (31 << (prescale + 1)) - 1)
+
+/* KEYP_LK_PTV_REG Fields */
+#define KEYP_LK_PTV_PTV_SHIFT			5
+
+/* KEYP_IMR1 Fields */
+#define KEYP_IMR1_MIS				0x08
+#define KEYP_IMR1_TO				0x04
+#define KEYP_IMR1_LK				0x02
+#define KEYP_IMR1_KP				0x01
+
+/* KEYP_EDR Fields */
+#define KEYP_EDR_KP_FALLING			0x01
+#define KEYP_EDR_KP_RISING			0x02
+#define KEYP_EDR_KP_BOTH			0x03
+#define KEYP_EDR_LK_FALLING			0x04
+#define KEYP_EDR_LK_RISING			0x08
+#define KEYP_EDR_TO_FALLING			0x10
+#define KEYP_EDR_TO_RISING			0x20
+#define KEYP_EDR_MIS_FALLING			0x40
+#define KEYP_EDR_MIS_RISING			0x80
+
+/* KEYP_SIH_CTRL Fields */
+#define KEYP_SIH_CTRL_COR			0x04
+#define KEYP_SIH_CTRL_PEND_DIS			0x02
+#define KEYP_SIH_CTRL_EXCL_EN			0x01
+
+#endif	/* End of __TWL4030-KEYPAD_H__ */
-- 
1.6.0.1.141.g445ca


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

* [PATCH 11/33] add tsc2301 keypad driver
  2008-08-30 17:16                   ` [PATCH 10/33] Add twl4030 " Felipe Balbi
@ 2008-08-30 17:16                     ` Felipe Balbi
  2008-08-30 17:16                       ` [PATCH 12/33] Add tsc2005 touchscreen driver Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/input/keyboard/Kconfig      |    6 +
 drivers/input/keyboard/Makefile     |    1 +
 drivers/input/keyboard/tsc2301_kp.c |  475 +++++++++++++++++++++++++++++++++++
 3 files changed, 482 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/keyboard/tsc2301_kp.c

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 04e6c77..e4d0436 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -279,6 +279,12 @@ config OMAP_PS2
 	  To compile this driver as a module, choose M here: the
 	  module will be called innovator_ps2.
 
+config KEYBOARD_TSC2301
+	tristate "TSC2301 keypad support"
+	depends on SPI_TSC2301
+	help
+	  Say Y here for if you are using the keypad features of TSC2301.
+
 config KEYBOARD_LM8323
 	tristate "LM8323 keypad chip"
 	depends on I2C
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index a19e04b..ae47fff 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_KEYBOARD_HIL)		+= hil_kbd.o
 obj-$(CONFIG_KEYBOARD_HIL_OLD)		+= hilkbd.o
 obj-$(CONFIG_KEYBOARD_OMAP)		+= omap-keypad.o
 obj-$(CONFIG_OMAP_PS2)			+= innovator_ps2.o
+obj-$(CONFIG_KEYBOARD_TSC2301)		+= tsc2301_kp.o
 obj-$(CONFIG_KEYBOARD_LM8323)		+= lm8323.o
 obj-$(CONFIG_KEYBOARD_TWL4030)		+= omap-twl4030keypad.o
 obj-$(CONFIG_KEYBOARD_PXA27x)		+= pxa27x_keypad.o
diff --git a/drivers/input/keyboard/tsc2301_kp.c b/drivers/input/keyboard/tsc2301_kp.c
new file mode 100644
index 0000000..a974a5f
--- /dev/null
+++ b/drivers/input/keyboard/tsc2301_kp.c
@@ -0,0 +1,475 @@
+/*
+ * TSC2301 keypad driver
+ *
+ * Copyright (C) 2005-2006 Nokia Corporation
+ *
+ * Written by Jarkko Oikarinen
+ * Rewritten by Juha Yrjola <juha.yrjola@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#include <linux/spi/tsc2301.h>
+
+#define TSC2301_KEYBOARD_PRODUCT_ID      0x0051
+#define TSC2301_KEYBOARD_PRODUCT_VERSION 0x0001
+#define TSC2301_DEBOUNCE_TIME_2MS        0x0000
+#define TSC2301_DEBOUNCE_TIME_10MS       0x0800
+#define TSC2301_DEBOUNCE_TIME_20MS       0x1000
+#define TSC2301_DEBOUNCE_TIME_50MS       0x1800
+#define TSC2301_DEBOUNCE_TIME_60MS       0x2000
+#define TSC2301_DEBOUNCE_TIME_80MS       0x2800
+#define TSC2301_DEBOUNCE_TIME_100MS      0x3000
+#define TSC2301_DEBOUNCE_TIME_120MS      0x3800
+
+#define TSC2301_DEBOUNCE_TIME		TSC2301_DEBOUNCE_TIME_20MS
+
+#define TSC2301_RELEASE_TIMEOUT		50
+
+struct tsc2301_kp {
+	struct input_dev	*idev;
+	char			phys[32];
+	spinlock_t		lock;
+	struct mutex		mutex;
+	struct timer_list	timer;
+	u16			keys_pressed;
+	unsigned		pending:1;
+	unsigned		user_disabled:1;
+	unsigned		disable_depth;
+
+	struct spi_transfer	read_xfer[4];
+	struct spi_message	read_msg;
+
+	u16			data;
+	u16			mask;
+
+	int			irq;
+	s16			keymap[16];
+};
+
+static inline int tsc2301_kp_disabled(struct tsc2301 *tsc)
+{
+	return tsc->kp->disable_depth != 0;
+}
+
+static void tsc2301_kp_send_key_events(struct tsc2301 *tsc,
+				       u16 prev_state,
+				       u16 new_state)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	u16 common, released, pressed;
+	int i;
+
+	common = prev_state & new_state;
+	released = common ^ prev_state;
+	pressed = common ^ new_state;
+	if (!released && !pressed)
+		return;
+	for (i = 0; i < 16 && (released || pressed); i++) {
+		if (released & 1) {
+			dev_dbg(&tsc->spi->dev, "key %d released\n", i);
+			input_report_key(kp->idev, kp->keymap[i], 0);
+		}
+		released >>= 1;
+		if (pressed & 1) {
+			dev_dbg(&tsc->spi->dev, "key %d pressed\n", i);
+			input_report_key(kp->idev, kp->keymap[i], 1);
+		}
+		pressed >>= 1;
+	}
+	input_sync(kp->idev);
+}
+
+static inline void _filter_out(struct tsc2301 *tsc, u16 prev_state,
+			       u16 *new_state, int row1, int row2, u8 rect_pat)
+{
+	u16 mask;
+
+	mask = (rect_pat << (row1 * 4)) | (rect_pat << (row2 * 4));
+	mask &= ~prev_state;
+	*new_state &= ~mask;
+	dev_dbg(&tsc->spi->dev, "filtering ghost keys %02x\n", mask);
+}
+
+static void tsc2301_filter_ghost_keys(struct tsc2301 *tsc, u16 prev_state,
+				      u16 *new_state)
+{
+	int row1, row2;
+	u16 key_map;
+	u16 row1_map;
+	static const u8 rect_pat[] = {
+		0x3, 0x5, 0x9, 0x6, 0xa, 0xc, 0,
+	};
+
+	key_map = *new_state;
+	for (row1 = 0; row1 < 4; row1++) {
+		row1_map = (key_map >> (row1 * 4)) & 0xf;
+		if (!row1_map)
+			continue;
+		for (row2 = row1 + 1; row2 < 4; row2++) {
+			u16 rect_map = (key_map >> (row2 * 4)) & 0xf;
+			const u8 *rp;
+
+			rect_map &= row1_map;
+			if (!rect_map)
+				continue;
+			for (rp = rect_pat; *rp; rp++)
+				if ((rect_map & *rp) == *rp)
+					_filter_out(tsc, prev_state, new_state,
+						    row1, row2, *rp);
+		}
+	}
+}
+
+static void tsc2301_kp_timer(unsigned long arg)
+{
+	struct tsc2301 *tsc = (void *) arg;
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+
+	tsc2301_kp_send_key_events(tsc, kp->keys_pressed, 0);
+	spin_lock_irqsave(&kp->lock, flags);
+	kp->keys_pressed = 0;
+	spin_unlock_irqrestore(&kp->lock, flags);
+}
+
+static void tsc2301_kp_rx(void *arg)
+{
+	struct tsc2301 *tsc = arg;
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+	u16 kp_data;
+
+	kp_data = kp->data;
+	dev_dbg(&tsc->spi->dev, "KP data %04x\n", kp_data);
+
+	tsc2301_filter_ghost_keys(tsc, kp->keys_pressed, &kp_data);
+	tsc2301_kp_send_key_events(tsc, kp->keys_pressed, kp_data);
+	spin_lock_irqsave(&kp->lock, flags);
+	kp->keys_pressed = kp_data;
+	kp->pending = 0;
+	spin_unlock_irqrestore(&kp->lock, flags);
+}
+
+static irqreturn_t tsc2301_kp_irq_handler(int irq, void *dev_id)
+{
+	struct tsc2301 *tsc = dev_id;
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+	int r;
+
+	spin_lock_irqsave(&kp->lock, flags);
+	if (tsc2301_kp_disabled(tsc)) {
+		spin_unlock_irqrestore(&kp->lock, flags);
+		return IRQ_HANDLED;
+	}
+	kp->pending = 1;
+	spin_unlock_irqrestore(&kp->lock, flags);
+	mod_timer(&kp->timer,
+		 jiffies + msecs_to_jiffies(TSC2301_RELEASE_TIMEOUT));
+	r = spi_async(tsc->spi, &tsc->kp->read_msg);
+	if (r)
+		dev_err(&tsc->spi->dev, "kp: spi_async() failed");
+	return IRQ_HANDLED;
+}
+
+static void tsc2301_kp_start_scan(struct tsc2301 *tsc)
+{
+	tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, tsc->kp->mask);
+	tsc2301_write_reg(tsc, TSC2301_REG_KEY, TSC2301_DEBOUNCE_TIME);
+}
+
+static void tsc2301_kp_stop_scan(struct tsc2301 *tsc)
+{
+	tsc2301_write_reg(tsc, TSC2301_REG_KEY, 1 << 14);
+}
+
+/* Must be called with the mutex held */
+static void tsc2301_kp_enable(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&kp->lock, flags);
+	BUG_ON(!tsc2301_kp_disabled(tsc));
+	if (--kp->disable_depth != 0) {
+		spin_unlock_irqrestore(&kp->lock, flags);
+		return;
+	}
+	spin_unlock_irqrestore(&kp->lock, flags);
+
+	set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING);
+	tsc2301_kp_start_scan(tsc);
+	enable_irq(kp->irq);
+}
+
+/* Must be called with the mutex held */
+static int tsc2301_kp_disable(struct tsc2301 *tsc, int release_keys)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	unsigned long flags;
+
+	spin_lock_irqsave(&kp->lock, flags);
+	if (kp->disable_depth++ != 0) {
+		spin_unlock_irqrestore(&kp->lock, flags);
+		goto out;
+	}
+	disable_irq_nosync(kp->irq);
+	set_irq_type(kp->irq, IRQ_TYPE_NONE);
+	spin_unlock_irqrestore(&kp->lock, flags);
+
+	while (kp->pending) {
+		msleep(1);
+	}
+
+	tsc2301_kp_stop_scan(tsc);
+out:
+	if (!release_keys)
+		del_timer(&kp->timer); /* let timeout release keys */
+
+	return 0;
+}
+
+/* The following workaround is needed for a HW bug triggered by the
+ * following:
+ * 1. keep any key pressed
+ * 2. disable keypad
+ * 3. release all keys
+ * 4. reenable keypad
+ * 5. disable touch screen controller
+ *
+ * After this the keypad scanner will get stuck in busy state and won't
+ * report any interrupts for further keypresses. One way to recover is to
+ * restart the keypad scanner whenever we enable / disable the
+ * touchscreen controller.
+ */
+void tsc2301_kp_restart(struct tsc2301 *tsc)
+{
+	if (!tsc2301_kp_disabled(tsc)) {
+		tsc2301_kp_start_scan(tsc);
+	}
+}
+
+static ssize_t tsc2301_kp_disable_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct tsc2301		*tsc = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", tsc2301_kp_disabled(tsc) ? 1 : 0);
+}
+
+static ssize_t tsc2301_kp_disable_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct tsc2301		*tsc = dev_get_drvdata(dev);
+	struct tsc2301_kp	*kp = tsc->kp;
+	char *endp;
+	int i;
+
+	i = simple_strtoul(buf, &endp, 10);
+	i = i ? 1 : 0;
+
+	mutex_lock(&kp->mutex);
+	if (i == kp->user_disabled) {
+		mutex_unlock(&kp->mutex);
+		return count;
+	}
+	kp->user_disabled = i;
+
+	if (i)
+		tsc2301_kp_disable(tsc, 1);
+	else
+		tsc2301_kp_enable(tsc);
+	mutex_unlock(&kp->mutex);
+
+	return count;
+}
+
+static DEVICE_ATTR(disable_kp, 0664, tsc2301_kp_disable_show,
+		   tsc2301_kp_disable_store);
+
+static const u16 tsc2301_kp_read_data = 0x8000 | TSC2301_REG_KPDATA;
+
+static void tsc2301_kp_setup_spi_xfer(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+	struct spi_message *m = &kp->read_msg;
+	struct spi_transfer *x = &kp->read_xfer[0];
+
+	spi_message_init(&kp->read_msg);
+
+	x->tx_buf = &tsc2301_kp_read_data;
+	x->len = 2;
+	spi_message_add_tail(x, m);
+	x++;
+
+	x->rx_buf = &kp->data;
+	x->len = 2;
+	spi_message_add_tail(x, m);
+
+	m->complete = tsc2301_kp_rx;
+	m->context = tsc;
+}
+
+#ifdef CONFIG_PM
+int tsc2301_kp_suspend(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+
+	mutex_lock(&kp->mutex);
+	tsc2301_kp_disable(tsc, 1);
+	mutex_unlock(&kp->mutex);
+	return 0;
+}
+
+void tsc2301_kp_resume(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+
+	mutex_lock(&kp->mutex);
+	tsc2301_kp_enable(tsc);
+	mutex_unlock(&kp->mutex);
+}
+#endif
+
+int __devinit tsc2301_kp_init(struct tsc2301 *tsc,
+			      struct tsc2301_platform_data *pdata)
+{
+	struct input_dev *idev;
+	struct tsc2301_kp *kp;
+	int r, i;
+	u16 mask;
+
+	if (pdata->keyb_int < 0) {
+		dev_err(&tsc->spi->dev, "need kbirq");
+		return -EINVAL;
+	}
+
+	kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+	if (kp == NULL)
+		return -ENOMEM;
+	tsc->kp = kp;
+
+	kp->irq = pdata->keyb_int;
+	spin_lock_init(&kp->lock);
+	mutex_init(&kp->mutex);
+
+	init_timer(&kp->timer);
+	kp->timer.data = (unsigned long) tsc;
+	kp->timer.function = tsc2301_kp_timer;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		r = -ENOMEM;
+		goto err1;
+	}
+	if (pdata->keyb_name)
+		idev->name = pdata->keyb_name;
+	else
+		idev->name = "TSC2301 keypad";
+	snprintf(kp->phys, sizeof(kp->phys), "%s/input-kp", tsc->spi->dev.bus_id);
+	idev->phys = kp->phys;
+
+	mask = 0;
+	idev->evbit[0] = BIT(EV_KEY);
+	for (i = 0; i < 16; i++) {
+		if (pdata->keymap[i] > 0) {
+			set_bit(pdata->keymap[i], idev->keybit);
+			kp->keymap[i] = pdata->keymap[i];
+		} else {
+			kp->keymap[i] = -1;
+			mask |= 1 << i;
+		}
+	}
+
+	if (pdata->kp_rep)
+		set_bit(EV_REP, idev->evbit);
+
+	kp->idev = idev;
+
+	tsc2301_kp_setup_spi_xfer(tsc);
+
+	r = device_create_file(&tsc->spi->dev, &dev_attr_disable_kp);
+	if (r < 0)
+		goto err2;
+
+	tsc2301_kp_start_scan(tsc);
+
+	/* IRQ mode 0 is faulty, it can cause the KBIRQ to get stuck.
+	 * Mode 2 deasserts the IRQ at:
+	 * - HW or SW reset
+	 * - Setting SCS flag in REG_KEY register
+	 * - Releasing all keys
+	 * - Reading the REG_KPDATA
+	 */
+	tsc2301_write_kbc(tsc, 2);
+
+	tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, mask);
+	kp->mask = mask;
+
+	set_irq_type(kp->irq, IRQ_TYPE_EDGE_FALLING);
+
+	r = request_irq(kp->irq, tsc2301_kp_irq_handler, IRQF_SAMPLE_RANDOM,
+			"tsc2301-kp", tsc);
+	if (r < 0) {
+		dev_err(&tsc->spi->dev, "unable to get kbirq IRQ");
+		goto err3;
+	}
+	set_irq_wake(kp->irq, 1);
+
+	/* We need to read the register once..? */
+	tsc2301_read_reg(tsc, TSC2301_REG_KPDATA);
+
+	r = input_register_device(idev);
+	if (r < 0) {
+		dev_err(&tsc->spi->dev, "can't register keypad device\n");
+		goto err4;
+	}
+
+	return 0;
+
+err4:
+	free_irq(kp->irq, tsc);
+err3:
+	tsc2301_kp_stop_scan(tsc);
+	device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp);
+err2:
+	input_free_device(kp->idev);
+err1:
+	kfree(kp);
+	return r;
+}
+
+void __devexit tsc2301_kp_exit(struct tsc2301 *tsc)
+{
+	struct tsc2301_kp *kp = tsc->kp;
+
+	tsc2301_kp_disable(tsc, 1);
+	input_unregister_device(kp->idev);
+	free_irq(kp->irq, tsc);
+	device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp);
+
+	kfree(kp);
+}
-- 
1.6.0.1.141.g445ca


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

* [PATCH 12/33] Add tsc2005 touchscreen driver
  2008-08-30 17:16                     ` [PATCH 11/33] add tsc2301 " Felipe Balbi
@ 2008-08-30 17:16                       ` Felipe Balbi
  2008-08-30 17:16                         ` [PATCH 13/33] Add omap " Felipe Balbi
                                           ` (3 more replies)
  0 siblings, 4 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/input/touchscreen/Kconfig   |    5 +
 drivers/input/touchscreen/Makefile  |    1 +
 drivers/input/touchscreen/tsc2005.c |  736 +++++++++++++++++++++++++++++++++++
 include/linux/spi/tsc2005.h         |   29 ++
 4 files changed, 771 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/tsc2005.c
 create mode 100644 include/linux/spi/tsc2005.h

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index 25287e8..a115f38 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -217,6 +217,11 @@ config TOUCHSCREEN_ATMEL_TSADCC
 	  To compile this driver as a module, choose M here: the
 	  module will be called atmel_tsadcc.
 
+config TOUCHSCREEN_TSC2005
+	tristate "TSC2005 touchscreen support"
+	help
+	  Say Y here for if you are using the touchscreen features of TSC2301.
+
 config TOUCHSCREEN_UCB1400
 	tristate "Philips UCB1400 touchscreen"
 	select AC97_BUS
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 15cf290..0342389 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)	+= touchit213.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
+obj-$(CONFIG_TOUCHSCREEN_TSC2005)	+= tsc2005.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX)	+= wm97xx-ts.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705)	+= wm9705.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c
new file mode 100644
index 0000000..7fb107e
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2005.c
@@ -0,0 +1,736 @@
+/*
+ * TSC2005 touchscreen driver
+ *
+ * Copyright (C) 2006-2008 Nokia Corporation
+ *
+ * Author: Lauri Leukkunen <lauri.leukkunen@nokia.com>
+ * based on TSC2301 driver by Klaus K. Pedersen <klaus.k.pedersen@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#ifdef CONFIG_ARCH_OMAP
+#include <mach/gpio.h>
+#endif
+
+#include <linux/spi/tsc2005.h>
+
+/**
+ * The touchscreen interface operates as follows:
+ *
+ * Initialize:
+ *    Request access to GPIO103 (DAV)
+ *    tsc2005_dav_irq_handler will trigger when DAV line goes down
+ *
+ *  1) Pen is pressed against touchscreeen
+ *  2) TSC2005 performs AD conversion
+ *  3) After the conversion is done TSC2005 drives DAV line down
+ *  4) GPIO IRQ is received and tsc2005_dav_irq_handler is called
+ *  5) tsc2005_ts_irq_handler queues up an spi transfer to fetch
+ *     the x, y, z1, z2 values
+ *  6) tsc2005_ts_rx() reports coordinates to input layer and
+ *     sets up tsc2005_ts_timer() to be called after TSC2005_TS_SCAN_TIME
+ *  7)  When the penup_timer expires, there have not been DAV interrupts
+ *     during the last 20ms which means the pen has been lifted.
+ */
+
+#define TSC2005_VDD_LOWER_27
+
+#ifdef TSC2005_VDD_LOWER_27
+#define TSC2005_HZ     (10000000)
+#else
+#define TSC2005_HZ     (25000000)
+#endif
+
+#define TSC2005_CMD	(0x80)
+#define TSC2005_REG	(0x00)
+
+#define TSC2005_CMD_STOP	(1)
+#define TSC2005_CMD_10BIT	(0 << 2)
+#define TSC2005_CMD_12BIT	(1 << 2)
+
+#define TSC2005_CMD_SCAN_XYZZ	(0 << 3)
+#define TSC2005_CMD_SCAN_XY	(1 << 3)
+#define TSC2005_CMD_SCAN_X	(2 << 3)
+#define TSC2005_CMD_SCAN_Y	(3 << 3)
+#define TSC2005_CMD_SCAN_ZZ	(4 << 3)
+#define TSC2005_CMD_AUX_SINGLE	(5 << 3)
+#define TSC2005_CMD_TEMP1	(6 << 3)
+#define TSC2005_CMD_TEMP2	(7 << 3)
+#define TSC2005_CMD_AUX_CONT	(8 << 3)
+#define TSC2005_CMD_TEST_X_CONN	(9 << 3)
+#define TSC2005_CMD_TEST_Y_CONN	(10 << 3)
+/* command 11 reserved */
+#define TSC2005_CMD_TEST_SHORT	(12 << 3)
+#define TSC2005_CMD_DRIVE_XX	(13 << 3)
+#define TSC2005_CMD_DRIVE_YY	(14 << 3)
+#define TSC2005_CMD_DRIVE_YX	(15 << 3)
+
+#define TSC2005_REG_X		(0 << 3)
+#define TSC2005_REG_Y		(1 << 3)
+#define TSC2005_REG_Z1		(2 << 3)
+#define TSC2005_REG_Z2		(3 << 3)
+#define TSC2005_REG_AUX		(4 << 3)
+#define TSC2005_REG_TEMP1	(5 << 3)
+#define TSC2005_REG_TEMP2	(6 << 3)
+#define TSC2005_REG_STATUS	(7 << 3)
+#define TSC2005_REG_AUX_HIGH	(8 << 3)
+#define TSC2005_REG_AUX_LOW	(9 << 3)
+#define TSC2005_REG_TEMP_HIGH	(10 << 3)
+#define TSC2005_REG_TEMP_LOW	(11 << 3)
+#define TSC2005_REG_CFR0	(12 << 3)
+#define TSC2005_REG_CFR1	(13 << 3)
+#define TSC2005_REG_CFR2	(14 << 3)
+#define TSC2005_REG_FUNCTION	(15 << 3)
+
+#define TSC2005_REG_PND0	(1 << 1)
+#define TSC2005_REG_READ	(0x01)
+#define TSC2005_REG_WRITE	(0x00)
+
+
+#define TSC2005_CFR0_LONGSAMPLING	(1)
+#define TSC2005_CFR0_DETECTINWAIT	(1 << 1)
+#define TSC2005_CFR0_SENSETIME_32US	(0)
+#define TSC2005_CFR0_SENSETIME_96US	(1 << 2)
+#define TSC2005_CFR0_SENSETIME_544US	(1 << 3)
+#define TSC2005_CFR0_SENSETIME_2080US	(1 << 4)
+#define TSC2005_CFR0_SENSETIME_2656US	(0x001C)
+#define TSC2005_CFR0_PRECHARGE_20US	(0x0000)
+#define TSC2005_CFR0_PRECHARGE_84US	(0x0020)
+#define TSC2005_CFR0_PRECHARGE_276US	(0x0040)
+#define TSC2005_CFR0_PRECHARGE_1044US	(0x0080)
+#define TSC2005_CFR0_PRECHARGE_1364US	(0x00E0)
+#define TSC2005_CFR0_STABTIME_0US	(0x0000)
+#define TSC2005_CFR0_STABTIME_100US	(0x0100)
+#define TSC2005_CFR0_STABTIME_500US	(0x0200)
+#define TSC2005_CFR0_STABTIME_1MS	(0x0300)
+#define TSC2005_CFR0_STABTIME_5MS	(0x0400)
+#define TSC2005_CFR0_STABTIME_100MS	(0x0700)
+#define TSC2005_CFR0_CLOCK_4MHZ		(0x0000)
+#define TSC2005_CFR0_CLOCK_2MHZ		(0x0800)
+#define TSC2005_CFR0_CLOCK_1MHZ		(0x1000)
+#define TSC2005_CFR0_RESOLUTION12	(0x2000)
+#define TSC2005_CFR0_STATUS		(0x4000)
+#define TSC2005_CFR0_PENMODE		(0x8000)
+
+#define TSC2005_CFR0_INITVALUE	(TSC2005_CFR0_STABTIME_1MS  |	\
+				 TSC2005_CFR0_CLOCK_1MHZ    |	\
+				 TSC2005_CFR0_RESOLUTION12  |	\
+				 TSC2005_CFR0_PRECHARGE_276US | \
+				 TSC2005_CFR0_PENMODE)
+
+#define TSC2005_CFR1_BATCHDELAY_0MS	(0x0000)
+#define TSC2005_CFR1_BATCHDELAY_1MS	(0x0001)
+#define TSC2005_CFR1_BATCHDELAY_2MS	(0x0002)
+#define TSC2005_CFR1_BATCHDELAY_4MS	(0x0003)
+#define TSC2005_CFR1_BATCHDELAY_10MS	(0x0004)
+#define TSC2005_CFR1_BATCHDELAY_20MS	(0x0005)
+#define TSC2005_CFR1_BATCHDELAY_40MS	(0x0006)
+#define TSC2005_CFR1_BATCHDELAY_100MS	(0x0007)
+
+#define TSC2005_CFR1_INITVALUE	(TSC2005_CFR1_BATCHDELAY_2MS)
+
+#define TSC2005_CFR2_MAVE_TEMP	(0x0001)
+#define TSC2005_CFR2_MAVE_AUX	(0x0002)
+#define TSC2005_CFR2_MAVE_Z	(0x0004)
+#define TSC2005_CFR2_MAVE_Y	(0x0008)
+#define TSC2005_CFR2_MAVE_X	(0x0010)
+#define TSC2005_CFR2_AVG_1	(0x0000)
+#define TSC2005_CFR2_AVG_3	(0x0400)
+#define TSC2005_CFR2_AVG_7	(0x0800)
+#define TSC2005_CFR2_MEDIUM_1	(0x0000)
+#define TSC2005_CFR2_MEDIUM_3	(0x1000)
+#define TSC2005_CFR2_MEDIUM_7	(0x2000)
+#define TSC2005_CFR2_MEDIUM_15	(0x3000)
+
+#define TSC2005_CFR2_IRQ_DAV	(0x4000)
+#define TSC2005_CFR2_IRQ_PEN	(0x8000)
+#define TSC2005_CFR2_IRQ_PENDAV	(0x0000)
+
+#define TSC2005_CFR2_INITVALUE	(TSC2005_CFR2_IRQ_DAV   |	\
+				 TSC2005_CFR2_MAVE_X    |	\
+				 TSC2005_CFR2_MAVE_Y    |	\
+				 TSC2005_CFR2_MAVE_Z    |	\
+				 TSC2005_CFR2_MEDIUM_15 |	\
+				 TSC2005_CFR2_AVG_7)
+
+#define MAX_12BIT					((1 << 12) - 1)
+#define TS_SAMPLES					4
+#define TS_RECT_SIZE					8
+#define TSC2005_TS_PENUP_TIME				20
+
+static const u32 tsc2005_read_reg[] = {
+	(TSC2005_REG | TSC2005_REG_X | TSC2005_REG_READ) << 16,
+	(TSC2005_REG | TSC2005_REG_Y | TSC2005_REG_READ) << 16,
+	(TSC2005_REG | TSC2005_REG_Z1 | TSC2005_REG_READ) << 16,
+	(TSC2005_REG | TSC2005_REG_Z2 | TSC2005_REG_READ) << 16,
+};
+#define NUM_READ_REGS	(sizeof(tsc2005_read_reg)/sizeof(tsc2005_read_reg[0]))
+
+struct tsc2005 {
+	struct spi_device	*spi;
+
+	struct input_dev	*idev;
+	char			phys[32];
+	struct timer_list	penup_timer;
+	spinlock_t		lock;
+	struct mutex		mutex;
+
+	struct spi_message	read_msg;
+	struct spi_transfer	read_xfer[NUM_READ_REGS];
+	u32                     data[NUM_READ_REGS];
+
+	/* previous x,y,z */
+	int			x;
+	int			y;
+	int			p;
+	/* average accumulators for each component */
+	int			sample_cnt;
+	int			avg_x;
+	int			avg_y;
+	int			avg_z1;
+	int			avg_z2;
+	/* configuration */
+	int			x_plate_ohm;
+	int			hw_avg_max;
+	int			stab_time;
+	int			p_max;
+	int			touch_pressure;
+	int			irq;
+	s16			dav_gpio;
+	/* status */
+	u8			sample_sent;
+	u8			pen_down;
+	u8			disabled;
+	u8			disable_depth;
+	u8			spi_active;
+};
+
+static void tsc2005_cmd(struct tsc2005 *ts, u8 cmd)
+{
+	u16 data = TSC2005_CMD | TSC2005_CMD_12BIT | cmd;
+	struct spi_message msg;
+	struct spi_transfer xfer = { 0 };
+
+	xfer.tx_buf = &data;
+	xfer.rx_buf = NULL;
+	xfer.len = 1;
+	xfer.bits_per_word = 8;
+
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfer, &msg);
+	spi_sync(ts->spi, &msg);
+}
+
+static void tsc2005_write(struct tsc2005 *ts, u8 reg, u16 value)
+{
+	u32 tx;
+	struct spi_message msg;
+	struct spi_transfer xfer = { 0 };
+
+	tx = (TSC2005_REG | reg | TSC2005_REG_PND0 |
+	       TSC2005_REG_WRITE) << 16;
+	tx |= value;
+
+	xfer.tx_buf = &tx;
+	xfer.rx_buf = NULL;
+	xfer.len = 4;
+	xfer.bits_per_word = 24;
+
+	spi_message_init(&msg);
+	spi_message_add_tail(&xfer, &msg);
+	spi_sync(ts->spi, &msg);
+}
+
+static void tsc2005_ts_update_pen_state(struct tsc2005 *ts,
+					int x, int y, int pressure)
+{
+	if (pressure) {
+		input_report_abs(ts->idev, ABS_X, x);
+		input_report_abs(ts->idev, ABS_Y, y);
+		input_report_abs(ts->idev, ABS_PRESSURE, pressure);
+		if (!ts->pen_down) {
+			input_report_key(ts->idev, BTN_TOUCH, 1);
+			ts->pen_down = 1;
+		}
+	} else {
+		input_report_abs(ts->idev, ABS_PRESSURE, 0);
+		if (ts->pen_down) {
+			input_report_key(ts->idev, BTN_TOUCH, 0);
+			ts->pen_down = 0;
+		}
+	}
+
+	input_sync(ts->idev);
+}
+
+/*
+ * This function is called by the SPI framework after the coordinates
+ * have been read from TSC2005
+ */
+static void tsc2005_ts_rx(void *arg)
+{
+	struct tsc2005 *ts = arg;
+	unsigned long flags;
+	int inside_rect, pressure_limit;
+	int x, y, z1, z2, pressure;
+
+	spin_lock_irqsave(&ts->lock, flags);
+
+	x = ts->data[0];
+	y = ts->data[1];
+	z1 = ts->data[2];
+	z2 = ts->data[3];
+
+	/* validate pressure and position */
+	if (x > MAX_12BIT || y > MAX_12BIT)
+		goto out;
+
+	/* skip coords if the pressure-components are out of range */
+	if (z1 < 100 || z2 > 4000)
+		goto out;
+
+	/* don't run average on the "pen down" event */
+	if (ts->sample_sent) {
+		ts->avg_x += x;
+		ts->avg_y += y;
+		ts->avg_z1 += z1;
+		ts->avg_z2 += z2;
+
+		if (++ts->sample_cnt < TS_SAMPLES)
+			goto out;
+
+		x = ts->avg_x / TS_SAMPLES;
+		y = ts->avg_y / TS_SAMPLES;
+		z1 = ts->avg_z1 / TS_SAMPLES;
+		z2 = ts->avg_z2 / TS_SAMPLES;
+	}
+
+	ts->sample_cnt = 0;
+	ts->avg_x = 0;
+	ts->avg_y = 0;
+	ts->avg_z1 = 0;
+	ts->avg_z2 = 0;
+
+	if (z1) {
+		pressure = x * (z2 - z1) / z1;
+		pressure = pressure * ts->x_plate_ohm / 4096;
+	} else
+		goto out;
+
+	pressure_limit = ts->sample_sent? ts->p_max: ts->touch_pressure;
+	if (pressure > pressure_limit)
+		goto out;
+
+	/* discard the event if it still is within the previous rect - unless
+	 * if the pressure is harder, but then use previous x,y position */
+	inside_rect = (ts->sample_sent &&
+		x > (int)ts->x - TS_RECT_SIZE &&
+		x < (int)ts->x + TS_RECT_SIZE &&
+		y > (int)ts->y - TS_RECT_SIZE &&
+		y < (int)ts->y + TS_RECT_SIZE);
+	if (inside_rect)
+		x = ts->x, y = ts->y;
+
+	if (!inside_rect || pressure < ts->p) {
+		tsc2005_ts_update_pen_state(ts, x, y, pressure);
+		ts->sample_sent = 1;
+		ts->x = x;
+		ts->y = y;
+		ts->p = pressure;
+	}
+out:
+	ts->spi_active = 0;
+	spin_unlock_irqrestore(&ts->lock, flags);
+
+	/* kick pen up timer - to make sure it expires again(!) */
+	if (ts->sample_sent)
+		mod_timer(&ts->penup_timer,
+			  jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME));
+}
+
+static void tsc2005_ts_penup_timer_handler(unsigned long data)
+{
+	struct tsc2005 *ts = (struct tsc2005 *)data;
+
+	if (ts->sample_sent) {
+		tsc2005_ts_update_pen_state(ts, 0, 0, 0);
+		ts->sample_sent = 0;
+	}
+}
+
+/*
+ * This interrupt is called when pen is down and coordinates are
+ * available. That is indicated by a falling edge on DAV line.
+ */
+static irqreturn_t tsc2005_ts_irq_handler(int irq, void *dev_id)
+{
+	struct tsc2005 *ts = dev_id;
+	int r;
+
+	if (ts->spi_active)
+		return IRQ_HANDLED;
+
+	ts->spi_active = 1;
+	r = spi_async(ts->spi, &ts->read_msg);
+	if (r)
+		dev_err(&ts->spi->dev, "ts: spi_async() failed");
+
+	/* kick pen up timer */
+	mod_timer(&ts->penup_timer,
+		  jiffies + msecs_to_jiffies(TSC2005_TS_PENUP_TIME));
+
+	return IRQ_HANDLED;
+}
+
+static void tsc2005_ts_setup_spi_xfer(struct tsc2005 *ts)
+{
+	struct spi_message *m = &ts->read_msg;
+	struct spi_transfer *x = &ts->read_xfer[0];
+	int i;
+
+	spi_message_init(m);
+
+	for (i = 0; i < NUM_READ_REGS; i++, x++) {
+		x->tx_buf = &tsc2005_read_reg[i];
+		x->rx_buf = &ts->data[i];
+		x->len = 4;
+		x->bits_per_word = 24;
+		x->cs_change = i < (NUM_READ_REGS - 1);
+		spi_message_add_tail(x, m);
+	}
+
+	m->complete = tsc2005_ts_rx;
+	m->context = ts;
+}
+
+static ssize_t tsc2005_ts_pen_down_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct tsc2005 *tsc = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", tsc->pen_down);
+}
+
+static DEVICE_ATTR(pen_down, S_IRUGO, tsc2005_ts_pen_down_show, NULL);
+
+static int tsc2005_configure(struct tsc2005 *tsc, int flags)
+{
+	tsc2005_write(tsc, TSC2005_REG_CFR0, TSC2005_CFR0_INITVALUE);
+	tsc2005_write(tsc, TSC2005_REG_CFR1, TSC2005_CFR1_INITVALUE);
+	tsc2005_write(tsc, TSC2005_REG_CFR2, TSC2005_CFR2_INITVALUE);
+	tsc2005_cmd(tsc, flags);
+
+	return 0;
+}
+
+static void tsc2005_start_scan(struct tsc2005 *tsc)
+{
+	tsc2005_configure(tsc, TSC2005_CMD_SCAN_XYZZ);
+}
+
+static void tsc2005_stop_scan(struct tsc2005 *tsc)
+{
+	tsc2005_cmd(tsc, TSC2005_CMD_STOP);
+}
+
+/* Must be called with mutex held */
+static void tsc2005_disable(struct tsc2005 *ts)
+{
+	if (ts->disable_depth++ != 0)
+		return;
+
+	disable_irq(ts->irq);
+
+	/* wait until penup timer expire normally */
+	do {
+		msleep(4);
+	} while (ts->sample_sent);
+
+	tsc2005_stop_scan(ts);
+}
+
+static void tsc2005_enable(struct tsc2005 *ts)
+{
+	if (--ts->disable_depth != 0)
+		return;
+
+	enable_irq(ts->irq);
+
+	tsc2005_start_scan(ts);
+}
+
+static ssize_t tsc2005_disable_show(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct tsc2005 *ts = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", ts->disabled);
+}
+
+static ssize_t tsc2005_disable_store(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct tsc2005		*tsc = dev_get_drvdata(dev);
+	unsigned long res;
+	int i;
+
+	i = strict_strtoul(buf, 10, &res);
+	i = i ? 1 : 0;
+
+	mutex_lock(&tsc->mutex);
+	if (i == tsc->disabled)
+		goto out;
+	tsc->disabled = i;
+
+	if (i)
+		tsc2005_disable(tsc);
+	else
+		tsc2005_enable(tsc);
+out:
+	mutex_unlock(&tsc->mutex);
+	return count;
+}
+
+static DEVICE_ATTR(disable_ts, 0664, tsc2005_disable_show,
+		   tsc2005_disable_store);
+
+
+static int __devinit tsc2005_ts_init(struct tsc2005 *ts,
+				     struct tsc2005_platform_data *pdata)
+{
+	struct input_dev *idev;
+	int dav_gpio, r;
+	int x_max, y_max;
+	int x_fudge, y_fudge, p_fudge;
+
+	if (pdata->dav_gpio < 0) {
+		dev_err(&ts->spi->dev, "need DAV GPIO");
+		return -EINVAL;
+	}
+	dav_gpio = pdata->dav_gpio;
+	ts->dav_gpio = dav_gpio;
+	dev_dbg(&ts->spi->dev, "TSC2005: DAV GPIO = %d\n", dav_gpio);
+
+#ifdef CONFIG_ARCH_OMAP
+	r = omap_request_gpio(dav_gpio);
+	if (r < 0) {
+		dev_err(&ts->spi->dev, "unable to get DAV GPIO");
+		goto err1;
+	}
+	omap_set_gpio_direction(dav_gpio, 1);
+	ts->irq = OMAP_GPIO_IRQ(dav_gpio);
+	dev_dbg(&ts->spi->dev, "TSC2005: DAV IRQ = %d\n", ts->irq);
+#endif
+	init_timer(&ts->penup_timer);
+	setup_timer(&ts->penup_timer, tsc2005_ts_penup_timer_handler,
+			(unsigned long)ts);
+
+	spin_lock_init(&ts->lock);
+	mutex_init(&ts->mutex);
+
+	ts->x_plate_ohm		= pdata->ts_x_plate_ohm ? : 280;
+	ts->hw_avg_max		= pdata->ts_hw_avg;
+	ts->stab_time		= pdata->ts_stab_time;
+	x_max			= pdata->ts_x_max ? : 4096;
+	x_fudge			= pdata->ts_x_fudge ? : 4;
+	y_max			= pdata->ts_y_max ? : 4096;
+	y_fudge			= pdata->ts_y_fudge ? : 8;
+	ts->p_max		= pdata->ts_pressure_max ? : MAX_12BIT;
+	ts->touch_pressure	= pdata->ts_touch_pressure ? : ts->p_max;
+	p_fudge			= pdata->ts_pressure_fudge ? : 2;
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		r = -ENOMEM;
+		goto err2;
+	}
+
+	idev->name = "TSC2005 touchscreen";
+	snprintf(ts->phys, sizeof(ts->phys), "%s/input-ts",
+		 ts->spi->dev.bus_id);
+	idev->phys = ts->phys;
+
+	idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
+	idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
+	ts->idev = idev;
+
+	tsc2005_ts_setup_spi_xfer(ts);
+
+	input_set_abs_params(idev, ABS_X, 0, x_max, x_fudge, 0);
+	input_set_abs_params(idev, ABS_Y, 0, y_max, y_fudge, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0, ts->p_max, p_fudge, 0);
+
+	tsc2005_start_scan(ts);
+
+	r = request_irq(ts->irq, tsc2005_ts_irq_handler,
+			IRQF_TRIGGER_FALLING | IRQF_DISABLED |
+			IRQF_SAMPLE_RANDOM, "tsc2005", ts);
+	if (r < 0) {
+		dev_err(&ts->spi->dev, "unable to get DAV IRQ");
+		goto err3;
+	}
+
+	set_irq_wake(ts->irq, 1);
+
+	r = input_register_device(idev);
+	if (r < 0) {
+		dev_err(&ts->spi->dev, "can't register touchscreen device\n");
+		goto err4;
+	}
+
+	/* We can tolerate these failing */
+	if (device_create_file(&ts->spi->dev, &dev_attr_pen_down));
+	if (device_create_file(&ts->spi->dev, &dev_attr_disable_ts));
+
+	return 0;
+err4:
+	free_irq(ts->irq, ts);
+err3:
+	tsc2005_stop_scan(ts);
+	input_free_device(idev);
+err2:
+#ifdef CONFIG_ARCH_OMAP
+	omap_free_gpio(dav_gpio);
+#endif
+err1:
+	return r;
+}
+
+static int __devinit tsc2005_probe(struct spi_device *spi)
+{
+	struct tsc2005			*tsc;
+	struct tsc2005_platform_data	*pdata = spi->dev.platform_data;
+	int r;
+
+	if (!pdata) {
+		dev_dbg(&spi->dev, "no platform data?\n");
+		return -ENODEV;
+	}
+
+	tsc = kzalloc(sizeof(*tsc), GFP_KERNEL);
+	if (tsc == NULL)
+		return -ENOMEM;
+
+	dev_set_drvdata(&spi->dev, tsc);
+	tsc->spi = spi;
+	spi->dev.power.power_state = PMSG_ON;
+
+	spi->mode = SPI_MODE_0;
+	spi->bits_per_word = 8;
+	/* The max speed might've been defined by the board-specific
+	 * struct */
+	if (!spi->max_speed_hz)
+		spi->max_speed_hz = TSC2005_HZ;
+
+	spi_setup(spi);
+
+	r = tsc2005_ts_init(tsc, pdata);
+	if (r)
+		goto err1;
+
+	return 0;
+
+err1:
+	kfree(tsc);
+	return r;
+}
+
+static int __devexit tsc2005_remove(struct spi_device *spi)
+{
+	struct tsc2005 *ts = dev_get_drvdata(&spi->dev);
+
+	mutex_lock(&ts->mutex);
+	tsc2005_disable(ts);
+	mutex_unlock(&ts->mutex);
+
+	device_remove_file(&ts->spi->dev, &dev_attr_disable_ts);
+	device_remove_file(&ts->spi->dev, &dev_attr_pen_down);
+
+	free_irq(ts->irq, ts);
+	input_unregister_device(ts->idev);
+
+#ifdef CONFIG_ARCH_OMAP
+	omap_free_gpio(ts->dav_gpio);
+#endif
+	kfree(ts);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int tsc2005_suspend(struct spi_device *spi, pm_message_t mesg)
+{
+	struct tsc2005 *ts = dev_get_drvdata(&spi->dev);
+
+	mutex_lock(&ts->mutex);
+	tsc2005_disable(ts);
+	mutex_unlock(&ts->mutex);
+
+	return 0;
+}
+
+static int tsc2005_resume(struct spi_device *spi)
+{
+	struct tsc2005 *ts = dev_get_drvdata(&spi->dev);
+
+	mutex_lock(&ts->mutex);
+	tsc2005_enable(ts);
+	mutex_unlock(&ts->mutex);
+
+	return 0;
+}
+#endif
+
+static struct spi_driver tsc2005_driver = {
+	.driver = {
+		.name = "tsc2005",
+		.owner = THIS_MODULE,
+	},
+#ifdef CONFIG_PM
+	.suspend = tsc2005_suspend,
+	.resume = tsc2005_resume,
+#endif
+	.probe = tsc2005_probe,
+	.remove = __devexit_p(tsc2005_remove),
+};
+
+static int __init tsc2005_init(void)
+{
+	printk(KERN_INFO "TSC2005 driver initializing\n");
+
+	return spi_register_driver(&tsc2005_driver);
+}
+module_init(tsc2005_init);
+
+static void __exit tsc2005_exit(void)
+{
+	spi_unregister_driver(&tsc2005_driver);
+}
+module_exit(tsc2005_exit);
+
+MODULE_AUTHOR("Lauri Leukkunen <lauri.leukkunen@nokia.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:tsc2005");
diff --git a/include/linux/spi/tsc2005.h b/include/linux/spi/tsc2005.h
new file mode 100644
index 0000000..dbc01f7
--- /dev/null
+++ b/include/linux/spi/tsc2005.h
@@ -0,0 +1,29 @@
+#ifndef _LINUX_SPI_TSC2005_H
+#define _LINUX_SPI_TSC2005_H
+
+#include <linux/types.h>
+
+struct tsc2005_platform_data {
+	s16	reset_gpio;
+	s16	dav_gpio;
+	s16	pen_int_gpio;
+	u16	ts_x_plate_ohm;
+	u32	ts_stab_time;	/* voltage settling time */
+	u8	ts_hw_avg;	/* HW assiseted averaging. Can be
+				   0, 4, 8, 16 samples per reading */
+	u32	ts_touch_pressure;	/* Pressure limit until we report a
+					   touch event. After that we switch
+					   to ts_max_pressure. */
+	u32	ts_pressure_max;/* Samples with bigger pressure value will
+				   be ignored, since the corresponding X, Y
+				   values are unreliable */
+	u32	ts_pressure_fudge;
+	u32	ts_x_max;
+	u32	ts_x_fudge;
+	u32	ts_y_max;
+	u32	ts_y_fudge;
+
+	unsigned ts_ignore_last : 1;
+};
+
+#endif
-- 
1.6.0.1.141.g445ca


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

* [PATCH 13/33] Add omap touchscreen driver
  2008-08-30 17:16                       ` [PATCH 12/33] Add tsc2005 touchscreen driver Felipe Balbi
@ 2008-08-30 17:16                         ` Felipe Balbi
  2008-08-30 17:16                           ` [PATCH 14/33] add tsc210x " Felipe Balbi
  2008-08-31 20:53                         ` [PATCH 12/33] Add tsc2005 touchscreen driver David Brownell
                                           ` (2 subsequent siblings)
  3 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/input/touchscreen/Makefile       |    1 +
 drivers/input/touchscreen/omap/Makefile  |   10 +
 drivers/input/touchscreen/omap/omap_ts.c |  267 ++++++++++++++++++++++++++++++
 drivers/input/touchscreen/omap/omap_ts.h |   57 +++++++
 drivers/input/touchscreen/omap/ts_hx.c   |  184 ++++++++++++++++++++
 5 files changed, 519 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/omap/Makefile
 create mode 100644 drivers/input/touchscreen/omap/omap_ts.c
 create mode 100644 drivers/input/touchscreen/omap/omap_ts.h
 create mode 100644 drivers/input/touchscreen/omap/ts_hx.c

diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 0342389..e0bc8fb 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -27,6 +27,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)	+= touchright.o
 obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2005)	+= tsc2005.o
+obj-$(CONFIG_TOUCHSCREEN_OMAP)	+= omap/
 obj-$(CONFIG_TOUCHSCREEN_WM97XX)	+= wm97xx-ts.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705)	+= wm9705.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
diff --git a/drivers/input/touchscreen/omap/Makefile b/drivers/input/touchscreen/omap/Makefile
new file mode 100644
index 0000000..af6344e
--- /dev/null
+++ b/drivers/input/touchscreen/omap/Makefile
@@ -0,0 +1,10 @@
+#
+# Makefile for the OMAP touchscreen input driver
+#
+
+obj-$(CONFIG_TOUCHSCREEN_OMAP) += omapts.o
+
+objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_H2) += ts_hx.o
+objs-$(CONFIG_ARCH_OMAP16XX)$(CONFIG_MACH_OMAP_H3) += ts_hx.o
+
+omapts-objs := omap_ts.o $(objs-yy)
diff --git a/drivers/input/touchscreen/omap/omap_ts.c b/drivers/input/touchscreen/omap/omap_ts.c
new file mode 100644
index 0000000..ee85755
--- /dev/null
+++ b/drivers/input/touchscreen/omap/omap_ts.c
@@ -0,0 +1,267 @@
+/*
+ * input/touchscreen/omap/omap_ts.c
+ *
+ * touchscreen input device driver for various TI OMAP boards
+ * Copyright (c) 2002 MontaVista Software Inc.
+ * Copyright (c) 2004 Texas Instruments, Inc.
+ * Cleanup and modularization 2004 by Dirk Behme <dirk.behme@de.bosch.com>
+ *
+ * Assembled using driver code copyright the companies above.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * History:
+ * 12/12/2004    Srinath Modified and intergrated code for H2 and H3
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/interrupt.h>
+#include <linux/suspend.h>
+#include <linux/platform_device.h>
+
+#include <asm/mach-types.h>
+
+//#define DEBUG
+
+#include "omap_ts.h"
+
+#define OMAP_TS_NAME	"omap_ts"
+
+static struct ts_device *__initdata ts_devs[] = {
+#if defined(CONFIG_MACH_OMAP_H2) || defined(CONFIG_MACH_OMAP_H3)
+	&hx_ts,
+#endif
+};
+
+static struct omap_ts_t ts_omap;
+
+static int omap_ts_read(void)
+{
+	u16 data[4] = { 0, 0, 0, 0 };
+
+	ts_omap.dev->read(data);
+
+	input_report_abs(ts_omap.inputdevice, ABS_X, data[0]);
+	input_report_abs(ts_omap.inputdevice, ABS_Y, data[1]);
+	input_report_abs(ts_omap.inputdevice, ABS_PRESSURE, data[2]);
+	input_sync(ts_omap.inputdevice);
+
+	DEBUG_TS("omap_ts_read: read x=%d,y=%d,p=%d\n", data[0], data[1],
+		 data[2]);
+
+	return 0;
+}
+
+static void omap_ts_timer(unsigned long data)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ts_omap.lock, flags);
+
+	if (!ts_omap.dev->penup()) {
+		if (!ts_omap.touched) {
+			DEBUG_TS("omap_ts_timer: pen down\n");
+			input_report_key(ts_omap.inputdevice, BTN_TOUCH, 1);
+		}
+		ts_omap.touched = 1;
+		omap_ts_read();
+		ts_omap.ts_timer.expires = jiffies + HZ / 100;
+		add_timer(&(ts_omap.ts_timer));
+	} else {
+		if (ts_omap.touched) {
+			DEBUG_TS("omap_ts_timer: pen up\n");
+			ts_omap.touched = 0;
+			input_report_abs(ts_omap.inputdevice, ABS_X, 0);
+			input_report_abs(ts_omap.inputdevice, ABS_Y, 0);
+			input_report_abs(ts_omap.inputdevice, ABS_PRESSURE,
+					 0);
+			input_sync(ts_omap.inputdevice);
+			input_report_key(ts_omap.inputdevice, BTN_TOUCH, 0);
+		}
+		if (!ts_omap.irq_enabled) {
+			ts_omap.irq_enabled = 1;
+			enable_irq(ts_omap.irq);
+		}
+	}
+
+	spin_unlock_irqrestore(&ts_omap.lock, flags);
+}
+
+static irqreturn_t omap_ts_handler(int irq, void *dev_id)
+{
+	spin_lock(&ts_omap.lock);
+
+	if (ts_omap.irq_enabled) {
+		ts_omap.irq_enabled = 0;
+		disable_irq(irq);
+	}
+	// restart acquire
+	mod_timer(&ts_omap.ts_timer, jiffies + HZ / 100);
+
+	spin_unlock(&ts_omap.lock);
+
+	return IRQ_HANDLED;
+}
+
+static int __init omap_ts_probe(struct platform_device *pdev)
+{
+	int i;
+	int status = -ENODEV;
+
+	memset(&ts_omap, 0, sizeof(ts_omap));
+
+	ts_omap.inputdevice = input_allocate_device();
+	if (!ts_omap.inputdevice) {
+		return -ENOMEM;
+	}
+
+	spin_lock_init(&ts_omap.lock);
+
+	for (i = 0; i < ARRAY_SIZE(ts_devs); i++) {
+		if (!ts_devs[i] || !ts_devs[i]->probe)
+			continue;
+		status = ts_devs[i]->probe(&ts_omap);
+		if (status == 0) {
+			ts_omap.dev = ts_devs[i];
+			break;
+		}
+	}
+
+	if (status != 0) {
+	    	input_free_device(ts_omap.inputdevice);
+		return status;
+	}
+
+	// Init acquisition timer function
+	init_timer(&ts_omap.ts_timer);
+	ts_omap.ts_timer.function = omap_ts_timer;
+
+	/* request irq */
+	if (ts_omap.irq != -1) {
+		if (request_irq(ts_omap.irq, omap_ts_handler,
+				IRQF_SAMPLE_RANDOM | ts_omap.irq_type,
+				OMAP_TS_NAME, &ts_omap)) {
+			printk(KERN_ERR
+	  "omap_ts.c: Could not allocate touchscreen IRQ!\n");
+			ts_omap.irq = -1;
+			ts_omap.dev->remove();
+			input_free_device(ts_omap.inputdevice);
+			return -EINVAL;
+		}
+		ts_omap.irq_enabled = 1;
+	} else {
+		printk(KERN_ERR "omap_ts.c: No touchscreen IRQ assigned!\n");
+		ts_omap.dev->remove();
+		input_free_device(ts_omap.inputdevice);
+		return -EINVAL;
+	}
+
+	ts_omap.inputdevice->name = OMAP_TS_NAME;
+	ts_omap.inputdevice->dev = &pdev->dev;
+	ts_omap.inputdevice->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
+	ts_omap.inputdevice->keybit[BIT_WORD(BTN_TOUCH)] |= BIT(BTN_TOUCH);
+	ts_omap.inputdevice->absbit[0] =
+	    BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
+	input_register_device(ts_omap.inputdevice);
+
+	ts_omap.dev->enable();
+
+	printk("OMAP touchscreen driver initialized\n");
+
+	return 0;
+}
+
+static int omap_ts_remove(struct platform_device *pdev)
+{
+	ts_omap.dev->disable();
+	input_unregister_device(ts_omap.inputdevice);
+	if (ts_omap.irq != -1)
+		free_irq(ts_omap.irq, &ts_omap);
+
+	ts_omap.dev->remove();
+
+	return 0;
+}
+
+static int omap_ts_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	ts_omap.dev->disable();
+	return 0;
+}
+
+static int omap_ts_resume(struct platform_device *pdev)
+{
+	ts_omap.dev->enable();
+	return 0;
+}
+
+static void omap_ts_device_release(struct device *dev)
+{
+	/* Nothing */
+}
+static struct platform_driver omap_ts_driver = {
+	.probe 		= omap_ts_probe,
+	.remove 	= omap_ts_remove,
+	.suspend 	= omap_ts_suspend,
+	.resume 	= omap_ts_resume,
+	.driver = {
+		.name	= OMAP_TS_NAME,
+	},
+};
+
+static struct platform_device omap_ts_device = {
+	.name 		= OMAP_TS_NAME,
+	.id 		= -1,
+	.dev = {
+		.release 	= omap_ts_device_release,
+	},
+};
+
+static int __init omap_ts_init(void)
+{
+	int ret;
+
+	if (machine_is_omap_osk() || machine_is_omap_innovator())
+		return -ENODEV;
+
+	ret = platform_device_register(&omap_ts_device);
+	if (ret != 0)
+		return -ENODEV;
+
+	ret = platform_driver_register(&omap_ts_driver);
+	if (ret != 0) {
+		platform_device_unregister(&omap_ts_device);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void __exit omap_ts_exit(void)
+{
+	platform_driver_unregister(&omap_ts_driver);
+	platform_device_unregister(&omap_ts_device);
+}
+
+module_init(omap_ts_init);
+module_exit(omap_ts_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/touchscreen/omap/omap_ts.h b/drivers/input/touchscreen/omap/omap_ts.h
new file mode 100644
index 0000000..bef8e17
--- /dev/null
+++ b/drivers/input/touchscreen/omap/omap_ts.h
@@ -0,0 +1,57 @@
+/*
+ * omap_ts.h - header file for OMAP touchscreen support
+ * 
+ * Copyright (c) 2002 MontaVista Software Inc.
+ * Copyright (c) 2004 Texas Instruments, Inc.
+ *
+ * Assembled using driver code copyright the companies above.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef __OMAP_TS_H
+#define __OMAP_TS_H
+
+#ifdef DEBUG
+#define DEBUG_TS(fmt...)   printk(fmt)
+#else
+#define DEBUG_TS(fmt...)   do { } while (0)
+#endif
+
+struct omap_ts_t;
+
+struct ts_device {
+        int  (*probe)   (struct omap_ts_t *);
+        void (*read)    (u16 *);
+        void (*enable)  (void);
+        void (*disable) (void);
+        void (*remove)  (void);
+        int  (*penup)  (void);
+};
+
+struct omap_ts_t{
+	struct input_dev * inputdevice;
+	struct timer_list ts_timer;      // Timer for triggering acquisitions
+	int touched;
+	int irq;
+	int irq_type;
+	int irq_enabled;
+	struct ts_device *dev;
+	spinlock_t lock;
+};
+
+extern struct ts_device hx_ts;
+
+#endif /* __OMAP_TS_H */
diff --git a/drivers/input/touchscreen/omap/ts_hx.c b/drivers/input/touchscreen/omap/ts_hx.c
new file mode 100644
index 0000000..9f82f5a
--- /dev/null
+++ b/drivers/input/touchscreen/omap/ts_hx.c
@@ -0,0 +1,184 @@
+/*
+ * input/touchscreen/omap/ts_hx.c
+ * touchscreen support for OMAP H3 and H2  boards
+ *
+ * Copyright (c) 2002 MontaVista Software Inc.
+ * Copyright (c) 2004 Texas Instruments, Inc.
+ *
+ * Assembled using driver code copyright the companies above.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * History:
+ * 9/12/2004  	Srinath Modified and integrated  H2 and H3 code
+ *
+ */
+
+#include <linux/input.h>
+#include <linux/device.h>
+#include <asm/mach-types.h>
+#include <asm/arch/gpio.h>
+#include <asm/arch/mux.h>
+#include <asm/arch/hardware.h>
+#include <asm/hardware/tsc2101.h>
+
+#include "../drivers/ssi/omap-tsc2101.h"
+#include "omap_ts.h"
+
+#define	H2_GPIO_NUM		4
+#define	H3_GPIO_NUM		48
+
+#define OMAP_TSC2101_XRES		       500
+#define TOUCHSCREEN_DATA_REGISTERS_PAGE	 0x0
+#define TOUCHSCREEN_CONTROL_REGISTERS_PAGE      0x1
+#define OMAP_TSC2101_READ_MAX		   0x4
+#define TSC2101_GETSTATUS(ret)		  (((ret) >> 11) & 0x1)
+#define TSC2101_MASKVAL			 0xFFF
+#define TSC2101_PRESSUREVAL(x)		  ((x) << 12)
+
+static int hx_ts_penup(void);
+static int hx_ts_probe(struct omap_ts_t *ts);
+static void hx_ts_read(u16 * data);
+static void hx_ts_enable(void);
+static void hx_ts_disable(void);
+#ifdef	MODULE
+static void hx_ts_remove(void);
+#endif
+
+struct ts_device hx_ts = {
+	.probe 		= hx_ts_probe,
+	.read 		= hx_ts_read,
+	.enable 	= hx_ts_enable,
+	.disable 	= hx_ts_disable,
+	.remove 	= __exit_p(hx_ts_remove),
+	.penup 		= hx_ts_penup,
+};
+
+static int hx_ts_penup(void)
+{
+	int ret = 0;
+	/* Read the status register */
+	ret = omap_tsc2101_read(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+				TSC2101_TS_STATUS);
+	/* Check for availability of data in status register */
+	ret = TSC2101_GETSTATUS(ret);
+	return !ret;
+
+}
+
+static int __init hx_ts_probe(struct omap_ts_t *ts)
+{
+	unsigned	gpio;
+
+	if (machine_is_omap_h2()) {
+		gpio = H2_GPIO_NUM;
+		omap_cfg_reg(P20_1610_GPIO4);
+	} else if (machine_is_omap_h3()) {
+		gpio = H3_GPIO_NUM;
+		omap_cfg_reg(W19_1610_GPIO48);
+	} else
+		return -ENODEV;
+
+	ts->irq = OMAP_GPIO_IRQ(gpio);
+	if (omap_request_gpio(gpio) != 0) {
+		printk(KERN_ERR "hX_ts_init.c: Could not reserve GPIO!\n");
+		return -EINVAL;
+	};
+
+	omap_set_gpio_direction(gpio, 1);
+	ts->irq_type = IRQF_TRIGGER_FALLING;
+	return 0;
+}
+
+static void hx_ts_read(u16 * values)
+{
+	s32 t, p = 0;
+	int i;
+
+	/* Read X, Y, Z1 and Z2 */
+	omap_tsc2101_reads(TOUCHSCREEN_DATA_REGISTERS_PAGE, TSC2101_TS_X,
+			   values, OMAP_TSC2101_READ_MAX);
+
+	for (i = 0; i < OMAP_TSC2101_READ_MAX; i++)
+		values[i] &= TSC2101_MASKVAL;
+
+	/* Calculate Pressure */
+	if (values[TSC2101_TS_Z1] != 0) {
+		t = ((OMAP_TSC2101_XRES * values[TSC2101_TS_X]) *
+		     (values[TSC2101_TS_Z2] - values[TSC2101_TS_Z1]));
+		p = t / (u32) (TSC2101_PRESSUREVAL(values[TSC2101_TS_Z1]));
+		if (p < 0)
+			p = 0;
+	}
+
+	values[TSC2101_TS_Z1] = p;
+}
+
+static void hx_ts_enable(void)
+{
+	int ret = omap_tsc2101_enable();
+	if (ret) {
+		printk(KERN_ERR "FAILED TO INITIALIZE TSC CODEC\n");
+		return;
+	}
+
+	/* PINTDAV is data available only */
+	omap_tsc2101_write(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+			   TSC2101_TS_STATUS, TSC2101_DATA_AVAILABLE);
+	/* disable buffer mode */
+	omap_tsc2101_write(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+			   TSC2101_TS_BUFFER_CTRL, TSC2101_BUFFERMODE_DISABLE);
+	/* use internal reference, 100 usec power-up delay,
+	 *	  * power down between conversions, 1.25V internal reference */
+	omap_tsc2101_write(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+			   TSC2101_TS_REF_CTRL, TSC2101_REF_POWERUP);
+	/* enable touch detection, 84usec precharge time, 32 usec sense time */
+	omap_tsc2101_write(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+			   TSC2101_TS_CONFIG_CTRL, TSC2101_ENABLE_TOUCHDETECT);
+	/* 3 msec conversion delays  */
+	omap_tsc2101_write(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+			   TSC2101_TS_PROG_DELAY, TSC2101_PRG_DELAY);
+	/*
+	 * TSC2101-controlled conversions
+	 * 12-bit samples
+	 * continuous X,Y,Z1,Z2 scan mode
+	 * average (mean) 4 samples per coordinate
+	 * 1 MHz internal conversion clock
+	 * 500 usec panel voltage stabilization delay
+	 */
+	omap_tsc2101_write(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+			   TSC2101_TS_ADC_CTRL, TSC2101_ADC_CONTROL);
+
+	return;
+
+}
+
+static void hx_ts_disable(void)
+{
+	/* stop conversions and power down */
+	omap_tsc2101_write(TOUCHSCREEN_CONTROL_REGISTERS_PAGE,
+			   TSC2101_TS_ADC_CTRL, TSC2101_ADC_POWERDOWN);
+	omap_tsc2101_disable();
+}
+
+#ifdef	MODULE
+static void __exit hx_ts_remove(void)
+{
+	if (machine_is_omap_h2())
+		omap_free_gpio(H2_GPIO_NUM);
+	else if (machine_is_omap_h3())
+		omap_free_gpio(H3_GPIO_NUM);
+}
+#endif
-- 
1.6.0.1.141.g445ca


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

* [PATCH 14/33] add tsc210x touchscreen driver
  2008-08-30 17:16                         ` [PATCH 13/33] Add omap " Felipe Balbi
@ 2008-08-30 17:16                           ` Felipe Balbi
  2008-08-30 17:16                             ` [PATCH 15/33] add tsc2301 " Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/input/touchscreen/Kconfig      |   14 ++
 drivers/input/touchscreen/Makefile     |    1 +
 drivers/input/touchscreen/tsc210x_ts.c |  160 ++++++++++++++++++++++
 include/linux/spi/tsc210x.h            |  231 ++++++++++++++++++++++++++++++++
 4 files changed, 406 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/tsc210x_ts.c
 create mode 100644 include/linux/spi/tsc210x.h

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index a115f38..f45cdac 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -222,6 +222,20 @@ config TOUCHSCREEN_TSC2005
 	help
 	  Say Y here for if you are using the touchscreen features of TSC2301.
 
+config TOUCHSCREEN_TSC210X
+	tristate "TI TSC210x based touchscreens"
+	depends on SPI_MASTER
+	select SPI_TSC210X
+	help
+	  Say Y here if you have a touchscreen interface using a
+	  TI TSC210x controller, and your board-specific initialisation
+	  code includes that in its table of SPI devices.
+
+	  If unsure, say N (but it's safe to say "Y").
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tsc210x_ts.
+
 config TOUCHSCREEN_UCB1400
 	tristate "Philips UCB1400 touchscreen"
 	select AC97_BUS
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index e0bc8fb..411c44a 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -28,6 +28,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)	+= touchwin.o
 obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2005)	+= tsc2005.o
 obj-$(CONFIG_TOUCHSCREEN_OMAP)	+= omap/
+obj-$(CONFIG_TOUCHSCREEN_TSC210X)	+= tsc210x_ts.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX)	+= wm97xx-ts.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705)	+= wm9705.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
diff --git a/drivers/input/touchscreen/tsc210x_ts.c b/drivers/input/touchscreen/tsc210x_ts.c
new file mode 100644
index 0000000..5828b6d
--- /dev/null
+++ b/drivers/input/touchscreen/tsc210x_ts.c
@@ -0,0 +1,160 @@
+/*
+ * tsc210x_ts.c - touchscreen input device for TI TSC210x chips
+ *
+ * Copyright (c) 2006-2007 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package 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.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+
+#include <linux/spi/tsc210x.h>
+
+
+/*
+ * The sensor ADC on tsc210x chips is most often used with the smart
+ * touchscreen controller.   Those controllers can be made to improve
+ * sample quality directly by multi-sampling and by taking the mean or
+ * median of various numbers of samples.  They also take X, Y, and
+ * pressure measurements automatically, so this driver has relatively
+ * little to do.
+ *
+ * There are a few chips in this family that don't have quite the same
+ * touchscreen interface, e.g. no "median" mode.
+ */
+
+static void tsc210x_touch(void *context, int touching)
+{
+	struct input_dev *dev = context;
+
+	if (!touching) {
+		input_report_abs(dev, ABS_X, 0);
+		input_report_abs(dev, ABS_Y, 0);
+		input_report_abs(dev, ABS_PRESSURE, 0);
+		input_sync(dev);
+	}
+
+	input_report_key(dev, BTN_TOUCH, touching);
+}
+
+static void tsc210x_coords(void *context, int x, int y, int z1, int z2)
+{
+	struct input_dev *dev = context;
+	int p;
+
+	/* Calculate the touch resistance a la equation #1 */
+	if (z1 != 0)
+		p = x * (z2 - z1) / (z1 << 4);
+	else
+		p = 1;
+
+	input_report_abs(dev, ABS_X, x);
+	input_report_abs(dev, ABS_Y, y);
+	input_report_abs(dev, ABS_PRESSURE, p);
+	input_sync(dev);
+}
+
+static int tsc210x_ts_probe(struct platform_device *pdev)
+{
+	int status;
+	struct input_dev *dev;
+
+	dev = input_allocate_device();
+	if (!dev)
+		return -ENOMEM;
+
+	status = tsc210x_touch_cb(pdev->dev.parent, tsc210x_touch, dev);
+	if (status) {
+		input_free_device(dev);
+		return status;
+	}
+
+	status = tsc210x_coords_cb(pdev->dev.parent, tsc210x_coords, dev);
+	if (status) {
+		tsc210x_touch_cb(pdev->dev.parent, NULL, NULL);
+		input_free_device(dev);
+		return status;
+	}
+
+	dev->name = "TSC210x Touchscreen";
+	dev->dev.parent = &pdev->dev;
+	dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS);
+	dev->keybit[BIT_WORD(BTN_TOUCH)] |= BIT_MASK(BTN_TOUCH);
+	dev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
+	dev->phys = "tsc210x/input0";
+	dev->id.bustype = BUS_HOST;
+	dev->id.vendor = 0x0001;
+	dev->id.product = 0x2100;
+	dev->id.version = 0x0001;
+
+	status = input_register_device(dev);
+	if (status) {
+		tsc210x_coords_cb(pdev->dev.parent, NULL, NULL);
+		tsc210x_touch_cb(pdev->dev.parent, NULL, NULL);
+		input_free_device(dev);
+		return status;
+	}
+
+	platform_set_drvdata(pdev, dev);
+	printk(KERN_INFO "TSC210x touchscreen initialised\n");
+	return 0;
+}
+
+static int __exit tsc210x_ts_remove(struct platform_device *pdev)
+{
+	struct input_dev *dev = platform_get_drvdata(pdev);
+
+	tsc210x_touch_cb(pdev->dev.parent, NULL, NULL);
+	tsc210x_coords_cb(pdev->dev.parent, NULL, NULL);
+	platform_set_drvdata(pdev, NULL);
+	input_unregister_device(dev);
+	input_free_device(dev);
+
+	return 0;
+}
+
+static struct platform_driver tsc210x_ts_driver = {
+	.probe		= tsc210x_ts_probe,
+	.remove		= __exit_p(tsc210x_ts_remove),
+	/* Nothing to do on suspend/resume */
+	.driver		= {
+		.name	= "tsc210x-ts",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init tsc210x_ts_init(void)
+{
+	/* can't use driver_probe() here since the parent device
+	 * gets registered "late"
+	 */
+	return platform_driver_register(&tsc210x_ts_driver);
+}
+module_init(tsc210x_ts_init);
+
+static void __exit tsc210x_ts_exit(void)
+{
+	platform_driver_unregister(&tsc210x_ts_driver);
+}
+module_exit(tsc210x_ts_exit);
+
+MODULE_AUTHOR("Andrzej Zaborowski");
+MODULE_DESCRIPTION("Touchscreen input driver for TI TSC2101/2102.");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/spi/tsc210x.h b/include/linux/spi/tsc210x.h
new file mode 100644
index 0000000..b1a9ae6
--- /dev/null
+++ b/include/linux/spi/tsc210x.h
@@ -0,0 +1,231 @@
+/*
+ * include/linux/spi/tsc2102.h
+ *
+ * TI TSC2101/2102 control register definitions.
+ *
+ * Copyright (c) 2005-2007 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package 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.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#ifndef __LINUX_SPI_TSC210X_H
+#define __LINUX_SPI_TSC210X_H
+
+struct apm_power_info;
+
+struct tsc210x_config {
+	int use_internal;	/* Use internal reference voltage */
+	u32 monitor;		/* What inputs are wired on this board */
+	int temp_at25c[2];	/* Thermometer calibration data */
+	void (*apm_report)(struct apm_power_info *info, int battery[]);
+				/* Report status to APM based on battery[] */
+	void *alsa_config;	/* .platform_data for the ALSA device */
+	const char *mclk;	/* Optional: mclk name */
+	const char *bclk;	/* Optional: bclk name */
+};
+
+#define TSC_BAT1	(1 << 0)
+#define TSC_BAT2	(1 << 1)
+#define TSC_AUX1	(1 << 2)
+#define TSC_AUX2	(1 << 3)
+#define TSC_TEMP	(1 << 4)
+
+#define TSC_AUX		TSC_AUX1
+#define TSC_VBAT	TSC_BAT1
+
+struct tsc210x_dev;
+
+/* Drivers for tsc210x components like touchscreen, sensor, and audio
+ * are packaged as platform drivers which can issue synchronous register
+ * acceses, and may also register a callback to process their particular
+ * type of data when that data is automatically sampled.  The platform
+ * device is a child of the TSC spi device.
+ */
+
+extern int tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address);
+extern int tsc210x_reads_sync(struct tsc210x_dev *dev, int page,
+		u8 startaddress, u16 *data, int numregs);
+extern int tsc210x_write_sync(struct tsc210x_dev *dev, int page,
+		u8 address, u16 data);
+
+typedef void (*tsc210x_touch_t)(void *context, int touching);
+typedef void (*tsc210x_coords_t)(void *context, int x, int y, int z1, int z2);
+typedef void (*tsc210x_ports_t)(void *context, int bat[], int aux[]);
+typedef void (*tsc210x_temp_t)(void *context, int temp);
+
+extern int tsc210x_touch_cb(struct device *dev,
+		tsc210x_touch_t handler, void *context);
+extern int tsc210x_coords_cb(struct device *dev,
+		tsc210x_coords_t handler, void *context);
+extern int tsc210x_ports_cb(struct device *dev,
+		tsc210x_ports_t handler, void *context);
+extern int tsc210x_temp1_cb(struct device *dev,
+		tsc210x_temp_t handler, void *context);
+extern int tsc210x_temp2_cb(struct device *dev,
+		tsc210x_temp_t handler, void *context);
+
+#if defined(CONFIG_SOUND) || defined(CONFIG_SOUND_MODULE)
+extern void tsc210x_set_dac_volume(struct device *dev, u8 left, u8 right);
+extern void tsc210x_set_dac_mute(struct device *dev, int left, int right);
+extern void tsc210x_get_dac_mute(struct device *dev, int *left, int *right);
+extern void tsc210x_dac_power(struct device *dev, int on);
+extern int tsc210x_set_rate(struct device *dev, int rate);
+extern void tsc210x_set_i2s_master(struct device *dev, int state);
+extern void tsc210x_set_deemphasis(struct device *dev, int enable);
+extern void tsc2102_set_bassboost(struct device *dev, int enable);
+#endif
+
+/*
+ * Emit a short keyclick typically in order to give feedback to
+ * user on specific events.
+ *
+ * amplitude must be between 0 (lowest) and 2 (highest).
+ * freq must be between 0 (corresponds to 62.5 Hz) and 7 (8 kHz).
+ * length should be between 2 and 32 periods.
+ *
+ * This function sleeps but for a period unrelated to the length of
+ * the sound, i.e. returning doesn't indicate that the sound has
+ * finished.
+ */
+extern void tsc210x_keyclick(struct tsc210x_dev *dev,
+		int amplitude, int freq, int length);
+
+/* Page 0, Touch Screen & Keypad Data registers */
+#define TSC210X_TS_X			0, 0x00
+#define TSC210X_TS_Y			0, 0x01
+#define TSC210X_TS_Z1			0, 0x02
+#define TSC210X_TS_Z2			0, 0x03
+#define TSC210X_TS_BAT1			0, 0x05
+#define TSC2102_TS_BAT2			0, 0x06
+#define TSC210X_TS_AUX1			0, 0x07
+#define TSC2101_TS_AUX2			0, 0x08
+#define TSC210X_TS_TEMP1		0, 0x09
+#define TSC210X_TS_TEMP2		0, 0x0a
+
+/* Page 1, Touch Screen & Keypad Control registers */
+#define TSC210X_TS_ADC_CTRL		1, 0x00
+#define TSC210X_TS_STATUS_CTRL		1, 0x01
+#define TSC2101_TS_BUFFER_CTRL		1, 0x02
+#define TSC210X_TS_REF_CTRL		1, 0x03
+#define TSC210X_TS_RESET_CTRL		1, 0x04
+#define TSC210X_TS_CONFIG_CTRL		1, 0x05
+#define TSC2101_TS_TEMPMAX_CTRL		1, 0x06
+#define TSC2101_TS_TEMPMIN_CTRL		1, 0x07
+#define TSC2101_TS_AUX1MAX_CTRL		1, 0x08
+#define TSC2101_TS_AUX1MIN_CTRL		1, 0x09
+#define TSC2101_TS_AUX2MAX_CTRL		1, 0x0a
+#define TSC2101_TS_AUX2MIN_CTRL		1, 0x0b
+#define TSC2101_TS_MCONFIG_CTRL		1, 0x0c
+#define TSC2101_TS_DELAY_CTRL		1, 0x0d
+
+/* Page 2, Audio Control registers */
+#define TSC210X_AUDIO1_CTRL		2, 0x00
+#define TSC2101_HEADSET_GAIN_CTRL	2, 0x01
+#define TSC210X_DAC_GAIN_CTRL		2, 0x02
+#define TSC2101_MIXER_GAIN_CTRL		2, 0x03
+#define TSC210X_AUDIO2_CTRL		2, 0x04
+#define TSC210X_POWER_CTRL		2, 0x05
+#define TSC210X_AUDIO3_CTRL		2, 0x06
+#define TSC210X_LCH_BASS_BOOST_N0	2, 0x07
+#define TSC210X_LCH_BASS_BOOST_N1	2, 0x08
+#define TSC210X_LCH_BASS_BOOST_N2	2, 0x09
+#define TSC210X_LCH_BASS_BOOST_N3	2, 0x0a
+#define TSC210X_LCH_BASS_BOOST_N4	2, 0x0b
+#define TSC210X_LCH_BASS_BOOST_N5	2, 0x0c
+#define TSC210X_LCH_BASS_BOOST_D1	2, 0x0d
+#define TSC210X_LCH_BASS_BOOST_D2	2, 0x0e
+#define TSC210X_LCH_BASS_BOOST_D4	2, 0x0f
+#define TSC210X_LCH_BASS_BOOST_D5	2, 0x10
+#define TSC210X_RCH_BASS_BOOST_N0	2, 0x11
+#define TSC210X_RCH_BASS_BOOST_N1	2, 0x12
+#define TSC210X_RCH_BASS_BOOST_N2	2, 0x13
+#define TSC210X_RCH_BASS_BOOST_N3	2, 0x14
+#define TSC210X_RCH_BASS_BOOST_N4	2, 0x15
+#define TSC210X_RCH_BASS_BOOST_N5	2, 0x16
+#define TSC210X_RCH_BASS_BOOST_D1	2, 0x17
+#define TSC210X_RCH_BASS_BOOST_D2	2, 0x18
+#define TSC210X_RCH_BASS_BOOST_D4	2, 0x19
+#define TSC210X_RCH_BASS_BOOST_D5	2, 0x1a
+#define TSC210X_PLL1_CTRL		2, 0x1b
+#define TSC210X_PLL2_CTRL		2, 0x1c
+#define TSC210X_AUDIO4_CTRL		2, 0x1d
+#define TSC2101_HANDSET_GAIN_CTRL	2, 0x1e
+#define TSC2101_CELL_GAIN_CTRL		2, 0x1f
+#define TSC2101_AUIDO5_CTRL		2, 0x20
+#define TSC2101_AUDIO6_CTRL		2, 0x21
+#define TSC2101_AUDIO7_CTRL		2, 0x22
+#define TSC2101_GPIO_CTRL		2, 0x23
+#define TSC2101_IN_AGC_CTRL		2, 0x24
+#define TSC2101_POWER_STATUS		2, 0x25
+#define TSC2101_MIX_AGC_CTRL		2, 0x26
+#define TSC2101_CELL_AGC_CTRL		2, 0x27
+
+/* Field masks for Audio Control 1 */
+#define AC1_WLEN(ARG)			(((ARG) & 0x03) << 10)
+#define AC1_DATFM(ARG)			(((ARG) & 0x03) << 8)
+#define AC1_DACFS(ARG)			((ARG) & 0x3f)
+
+/* Field masks for TSC2102_DAC_GAIN_CTRL */
+#define DGC_DALMU			(1 << 15)
+#define DGC_DALVL(ARG)			(((ARG) & 0x7f) << 8)
+#define DGC_DARMU			(1 << 7)
+#define DGC_DARVL(ARG)			(((ARG) & 0x7f))
+
+/* Field formats for TSC210X_AUDIO2_CTRL */
+#define AC2_KCLEN			(1 << 15)
+#define AC2_KCLAC(ARG)			(((ARG) & 0x07) << 12)
+#define AC2_KCLFRQ(ARG)			(((ARG) & 0x07) << 8)
+#define AC2_KCLLN(ARG)			(((ARG) & 0x0f) << 4)
+#define AC2_DLGAF			(1 << 3)
+#define AC2_DRGAF			(1 << 2)
+#define AC2_DASTC			(1 << 1)
+
+/* Field masks for TSC210X_DAC_POWER_CTRL */
+#define CPC_PWDNC			(1 << 15)
+#define CPC_DAODRC			(1 << 12)
+#define CPC_DAPWDN			(1 << 10)
+#define CPC_VGPWDN			(1 << 8)
+#define CPC_DAPWDF			(1 << 6)
+#define CPC_BASSBC			(1 << 1)
+#define CPC_DEEMPF			(0x01)
+
+/* Field masks for TSC210X_AUDIO3_CTRL */
+#define AC3_DMSVOL(ARG)			(((ARG) & 0x03) << 14)
+#define AC3_REFFS			(1 << 13)
+#define AC3_DAXFM			(1 << 12)
+#define AC3_SLVMS			(1 << 11)
+#define AC3_DALOVF			(1 << 7)
+#define AC3_DAROVF			(1 << 6)
+#define AC3_REVID(ARG)			(((ARG) & 0x07))
+
+/* Field masks for TSC210X_PLL1_CTRL */
+#define PLL1_PLLEN			(1 << 15)
+#define PLL1_Q_VAL(ARG)			(((ARG) & 0x0f) << 11)
+#define PLL1_P_VAL(ARG)			(((ARG) & 0x07) << 8)
+#define PLL1_I_VAL(ARG)			(((ARG) & 0x3f) << 2)
+
+/* Field masks for TSC210X_PLL2_CTRL */
+#define PLL2_D_VAL(ARG)			(((ARG) & 0x3fff) << 2)
+
+/* Field masks for TSC210X_AUDIO4_CTRL */
+#define AC4_DASTPD			(1 << 14)
+
+struct tsc210x_rate_info_s {
+	u16 sample_rate;
+	u8 divisor;
+	u8 fs_44k;	/* 44.1 kHz Fsref if 1, 48 kHz if 0 */
+};
+
+#endif	/* __LINUX_SPI_TSC210X_H */
-- 
1.6.0.1.141.g445ca


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

* [PATCH 15/33] add tsc2301 touchscreen driver
  2008-08-30 17:16                           ` [PATCH 14/33] add tsc210x " Felipe Balbi
@ 2008-08-30 17:16                             ` Felipe Balbi
  2008-08-30 17:16                               ` [PATCH 16/33] add omap led drivers Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/input/touchscreen/Kconfig      |    6 +
 drivers/input/touchscreen/Makefile     |    1 +
 drivers/input/touchscreen/tsc2301_ts.c |  676 ++++++++++++++++++++++++++++++++
 3 files changed, 683 insertions(+), 0 deletions(-)
 create mode 100644 drivers/input/touchscreen/tsc2301_ts.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index f45cdac..1bab54a 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -236,6 +236,12 @@ config TOUCHSCREEN_TSC210X
 	  To compile this driver as a module, choose M here: the
 	  module will be called tsc210x_ts.
 
+config TOUCHSCREEN_TSC2301
+	tristate "TSC2301 touchscreen support"
+	depends on SPI_TSC2301
+	help
+	  Say Y here for if you are using the touchscreen features of TSC2301.
+
 config TOUCHSCREEN_UCB1400
 	tristate "Philips UCB1400 touchscreen"
 	select AC97_BUS
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index 411c44a..d14ed30 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -29,6 +29,7 @@ obj-$(CONFIG_TOUCHSCREEN_UCB1400)	+= ucb1400_ts.o
 obj-$(CONFIG_TOUCHSCREEN_TSC2005)	+= tsc2005.o
 obj-$(CONFIG_TOUCHSCREEN_OMAP)	+= omap/
 obj-$(CONFIG_TOUCHSCREEN_TSC210X)	+= tsc210x_ts.o
+obj-$(CONFIG_TOUCHSCREEN_TSC2301)	+= tsc2301_ts.o
 obj-$(CONFIG_TOUCHSCREEN_WM97XX)	+= wm97xx-ts.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705)	+= wm9705.o
 wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712)	+= wm9712.o
diff --git a/drivers/input/touchscreen/tsc2301_ts.c b/drivers/input/touchscreen/tsc2301_ts.c
new file mode 100644
index 0000000..6462cc2
--- /dev/null
+++ b/drivers/input/touchscreen/tsc2301_ts.c
@@ -0,0 +1,676 @@
+/*
+ * TSC2301 touchscreen driver
+ *
+ * Copyright (C) 2005-2008 Nokia Corporation
+ *
+ * Written by Jarkko Oikarinen, Imre Deak and Juha Yrjola
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#include <linux/spi/tsc2301.h>
+
+/**
+ * The touchscreen interface operates as follows:
+ *
+ * Initialize:
+ *    Request access to GPIO103 (DAV)
+ *    tsc2301_ts_irq_handler will trigger when DAV line goes down
+ *
+ *  1) Pen is pressed against touchscreeen
+ *  2) TSC2301 performs AD conversion
+ *  3) After the conversion is done TSC2301 drives DAV line down
+ *  4) GPIO IRQ is received and tsc2301_ts_irq_handler is called
+ *  5) tsc2301_ts_irq_handler queues up an spi transfer to fetch
+ *     the x, y, z1, z2 values
+ *  6) SPI framework calls tsc2301_ts_rx after the coordinates are read
+ *  7) When the penup_timer expires, there have not been DAV interrupts
+ *     during the last 20ms which means the pen has been lifted.
+ */
+
+
+#define TSC2301_TOUCHSCREEN_PRODUCT_ID      		0x0052
+#define TSC2301_TOUCHSCREEN_PRODUCT_VERSION 		0x0001
+
+#define TSC2301_TS_PENUP_TIME		     		20
+
+#define TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301	0x8000
+#define TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST		0x0000
+
+#define TSC2301_ADCREG_FUNCTION_NONE			0x0000
+#define TSC2301_ADCREG_FUNCTION_XY			0x0400
+#define TSC2301_ADCREG_FUNCTION_XYZ			0x0800
+#define TSC2301_ADCREG_FUNCTION_X			0x0C00
+#define TSC2301_ADCREG_FUNCTION_Y			0x1000
+#define TSC2301_ADCREG_FUNCTION_Z			0x1400
+#define TSC2301_ADCREG_FUNCTION_DAT1			0x1800
+#define TSC2301_ADCREG_FUNCTION_DAT2			0x1C00
+#define TSC2301_ADCREG_FUNCTION_AUX1			0x2000
+#define TSC2301_ADCREG_FUNCTION_AUX2			0x2400
+#define TSC2301_ADCREG_FUNCTION_TEMP			0x2800
+
+#define TSC2301_ADCREG_RESOLUTION_8BIT			0x0100
+#define TSC2301_ADCREG_RESOLUTION_10BIT			0x0200
+#define TSC2301_ADCREG_RESOLUTION_12BIT			0x0300
+
+#define TSC2301_ADCREG_AVERAGING_NONE			0x0000
+#define TSC2301_ADCREG_AVERAGING_4AVG			0x0040
+#define TSC2301_ADCREG_AVERAGING_8AVG			0x0080
+#define TSC2301_ADCREG_AVERAGING_16AVG			0x00C0
+
+#define TSC2301_ADCREG_CLOCK_8MHZ			0x0000
+#define TSC2301_ADCREG_CLOCK_4MHZ			0x0010
+#define TSC2301_ADCREG_CLOCK_2MHZ			0x0020
+#define TSC2301_ADCREG_CLOCK_1MHZ			0x0030
+
+#define TSC2301_ADCREG_VOLTAGE_STAB_0US			0x0000
+#define TSC2301_ADCREG_VOLTAGE_STAB_100US		0x0002
+#define TSC2301_ADCREG_VOLTAGE_STAB_500US		0x0004
+#define TSC2301_ADCREG_VOLTAGE_STAB_1MS			0x0006
+#define TSC2301_ADCREG_VOLTAGE_STAB_5MS			0x0008
+#define TSC2301_ADCREG_VOLTAGE_STAB_10MS		0x000A
+#define TSC2301_ADCREG_VOLTAGE_STAB_50MS		0x000C
+#define TSC2301_ADCREG_VOLTAGE_STAB_100MS		0x000E
+
+#define TSC2301_ADCREG_STOP_CONVERSION			0x4000
+
+#define MAX_12BIT					((1 << 12) - 1)
+
+#define TS_RECT_SIZE					8
+#define TSF_MIN_Z1					100
+#define TSF_MAX_Z2					4000
+
+#define TSF_SAMPLES					4
+
+struct ts_filter {
+	int			sample_cnt;
+
+	int 			avg_x;
+	int 			avg_y;
+	int 			avg_z1;
+	int 			avg_z2;
+};
+
+struct ts_coords {
+	u16 			x;
+	u16 			y;
+	u16 			z1;
+	u16 			z2;
+};
+
+struct tsc2301_ts {
+	struct input_dev	*idev;
+	char			phys[32];
+	struct timer_list	penup_timer;
+	struct mutex		mutex;
+
+	struct spi_transfer	read_xfer[2];
+	struct spi_message	read_msg;
+	struct ts_coords	*coords;
+
+	struct ts_filter	filter;
+
+	int			hw_avg_max;
+	u16			x;
+	u16			y;
+	u16			p;
+
+	u16			x_plate_ohm;
+	int			stab_time;
+	int			max_pressure;
+	int			touch_pressure;
+
+	u8			event_sent;
+	u8			pen_down;
+	u8			disabled;
+	u8			disable_depth;
+
+	int			hw_flags;
+	int			irq;
+};
+
+
+static const u16 tsc2301_ts_read_data = 0x8000 | TSC2301_REG_X;
+
+static int tsc2301_ts_check_config(struct tsc2301_ts *ts, int *hw_flags)
+{
+	int flags;
+
+	flags = 0;
+	switch (ts->hw_avg_max) {
+	case 0:
+		flags |= TSC2301_ADCREG_AVERAGING_NONE;
+		break;
+	case 4:
+		flags |= TSC2301_ADCREG_AVERAGING_4AVG;
+		break;
+	case 8:
+		flags |= TSC2301_ADCREG_AVERAGING_8AVG;
+		break;
+	case 16:
+		flags |= TSC2301_ADCREG_AVERAGING_16AVG;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (ts->stab_time) {
+	case 0:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_0US;
+		break;
+	case 100:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_100US;
+		break;
+	case 500:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_500US;
+		break;
+	case 1000:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_1MS;
+		break;
+	case 5000:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_5MS;
+		break;
+	case 10000:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_10MS;
+		break;
+	case 50000:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_50MS;
+		break;
+	case 100000:
+		flags |= TSC2301_ADCREG_VOLTAGE_STAB_100MS;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	*hw_flags = flags;
+	return 0;
+}
+
+/*
+ * This odd three-time initialization is to work around a bug in TSC2301.
+ * See TSC2301 errata for details.
+ */
+static int tsc2301_ts_configure(struct tsc2301 *tsc, int flags)
+{
+	struct spi_transfer xfer[5];
+	struct spi_transfer *x;
+	struct spi_message m;
+	int i;
+	u16 val1, val2, val3;
+	u16 data[10];
+
+	val1 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST |
+		TSC2301_ADCREG_STOP_CONVERSION |
+		TSC2301_ADCREG_FUNCTION_NONE |
+		TSC2301_ADCREG_RESOLUTION_12BIT |
+		TSC2301_ADCREG_AVERAGING_NONE |
+		TSC2301_ADCREG_CLOCK_2MHZ |
+		TSC2301_ADCREG_VOLTAGE_STAB_100MS;
+
+	val2 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST |
+		TSC2301_ADCREG_FUNCTION_XYZ |
+		TSC2301_ADCREG_RESOLUTION_12BIT |
+		TSC2301_ADCREG_AVERAGING_16AVG |
+		TSC2301_ADCREG_CLOCK_1MHZ |
+		TSC2301_ADCREG_VOLTAGE_STAB_100MS;
+
+	/* Averaging and voltage stabilization settings in flags */
+	val3 = TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301 |
+		TSC2301_ADCREG_FUNCTION_XYZ |
+		TSC2301_ADCREG_RESOLUTION_12BIT |
+		TSC2301_ADCREG_CLOCK_2MHZ |
+		flags;
+
+	/* Now we prepare the command for transferring */
+	data[0] = TSC2301_REG_ADC;
+	data[1] = val1;
+	data[2] = TSC2301_REG_ADC;
+	data[3] = val2;
+	data[4] = TSC2301_REG_ADC;
+	data[5] = val3;
+	data[6] = TSC2301_REG_REF;
+	data[7] = 1 << 4 | 1 << 2 | 1; /* intref, 100uS settl, 2.5V ref */
+	data[8] = TSC2301_REG_CONFIG;
+	data[9] = 3 << 3 | 2 << 0; /* 340uS pre-chrg, 544us delay */
+
+	spi_message_init(&m);
+	m.spi = tsc->spi;
+
+	memset(xfer, 0, sizeof(xfer));
+	x = &xfer[0];
+
+	for (i = 0; i < 10; i += 2) {
+		x->tx_buf = &data[i];
+		x->len = 4;
+		if (i != 8)
+			x->cs_change = 1;
+		spi_message_add_tail(x, &m);
+		x++;
+	}
+	spi_sync(m.spi, &m);
+
+	return 0;
+}
+
+static void tsc2301_ts_start_scan(struct tsc2301 *tsc)
+{
+	tsc2301_ts_configure(tsc, tsc->ts->hw_flags);
+	tsc2301_kp_restart(tsc);
+}
+
+static void tsc2301_ts_stop_scan(struct tsc2301 *tsc)
+{
+	tsc2301_write_reg(tsc, TSC2301_REG_ADC, TSC2301_ADCREG_STOP_CONVERSION);
+	tsc2301_kp_restart(tsc);
+}
+
+static void update_pen_state(struct tsc2301_ts *ts, int x, int y, int pressure)
+{
+	if (pressure) {
+		input_report_abs(ts->idev, ABS_X, x);
+		input_report_abs(ts->idev, ABS_Y, y);
+		input_report_abs(ts->idev, ABS_PRESSURE, pressure);
+		if (!ts->pen_down)
+			input_report_key(ts->idev, BTN_TOUCH, 1);
+		ts->pen_down = 1;
+	} else {
+		input_report_abs(ts->idev, ABS_PRESSURE, 0);
+		if (ts->pen_down)
+			input_report_key(ts->idev, BTN_TOUCH, 0);
+		ts->pen_down = 0;
+	}
+
+	input_sync(ts->idev);
+
+#ifdef VERBOSE
+	dev_dbg(&tsc->spi->dev, "x %4d y %4d p %4d\n", x, y, pressure);
+#endif
+}
+
+static int filter(struct tsc2301_ts *ts, int x, int y, int z1, int z2)
+{
+	int inside_rect, pressure_limit, Rt;
+	struct ts_filter *tsf = &ts->filter;
+
+	/* validate pressure and position */
+	if (x > MAX_12BIT || y > MAX_12BIT)
+		return 0;
+
+	/* skip coords if the pressure-components are out of range */
+	if (z1 < TSF_MIN_Z1 || z2 > TSF_MAX_Z2)
+		return 0;
+
+	/* Use the x,y,z1,z2 directly on the first "pen down" event */
+	if (ts->event_sent) {
+		tsf->avg_x  += x;
+		tsf->avg_y  += y;
+		tsf->avg_z1 += z1;
+		tsf->avg_z2 += z2;
+
+		if (++tsf->sample_cnt < TSF_SAMPLES)
+			return 0;
+		x = tsf->avg_x / TSF_SAMPLES;
+		y = tsf->avg_y / TSF_SAMPLES;
+		z1 = tsf->avg_z1 / TSF_SAMPLES;
+		z2 = tsf->avg_z2 / TSF_SAMPLES;
+	}
+	tsf->sample_cnt = 0;
+	tsf->avg_x  = 0;
+	tsf->avg_y  = 0;
+	tsf->avg_z1 = 0;
+	tsf->avg_z2 = 0;
+
+	pressure_limit = ts->event_sent? ts->max_pressure: ts->touch_pressure;
+
+	/* z1 is always at least 100: */
+	Rt = x * (z2 - z1) / z1;
+	Rt = Rt * ts->x_plate_ohm / 4096;
+	if (Rt > pressure_limit)
+		return 0;
+
+	/* discard the event if it still is within the previous rect - unless
+	 * if the pressure is harder, but then use previous x,y position */
+	inside_rect = (
+	    x > (int)ts->x - TS_RECT_SIZE && x < (int)ts->x + TS_RECT_SIZE &&
+	    y > (int)ts->y - TS_RECT_SIZE && y < (int)ts->y + TS_RECT_SIZE);
+
+	if (!ts->event_sent || !inside_rect) {
+		ts->x = x;
+		ts->y = y;
+		ts->p = Rt;
+		return 1;
+	} else if (Rt < ts->p) {
+		ts->p = Rt;
+		return 1;
+	}
+	return 0;
+}
+
+/*
+ * This procedure is called by the SPI framework after the coordinates
+ * have been read from TSC2301
+ */
+static void tsc2301_ts_rx(void *arg)
+{
+	struct tsc2301 *tsc = arg;
+	struct tsc2301_ts *ts = tsc->ts;
+	int send_event;
+	int x, y, z1, z2;
+
+	x  = ts->coords->x;
+	y  = ts->coords->y;
+	z1 = ts->coords->z1;
+	z2 = ts->coords->z2;
+
+	send_event = filter(ts, x, y, z1, z2);
+	if (send_event) {
+		update_pen_state(ts, ts->x, ts->y, ts->p);
+		ts->event_sent = 1;
+	}
+
+	mod_timer(&ts->penup_timer,
+		  jiffies + msecs_to_jiffies(TSC2301_TS_PENUP_TIME));
+}
+
+/*
+ * Timer is called TSC2301_TS_PENUP_TIME after pen is up
+ */
+static void tsc2301_ts_timer_handler(unsigned long data)
+{
+	struct tsc2301 *tsc = (struct tsc2301 *)data;
+	struct tsc2301_ts *ts = tsc->ts;
+
+	if (ts->event_sent) {
+		ts->event_sent = 0;
+		update_pen_state(ts, 0, 0, 0);
+	}
+}
+
+/*
+ * This interrupt is called when pen is down and coordinates are
+ * available. That is indicated by a falling edge on DEV line.
+ */
+static irqreturn_t tsc2301_ts_irq_handler(int irq, void *dev_id)
+{
+	struct tsc2301 *tsc = dev_id;
+	struct tsc2301_ts *ts = tsc->ts;
+	int r;
+
+	r = spi_async(tsc->spi, &ts->read_msg);
+	if (r)
+		dev_err(&tsc->spi->dev, "ts: spi_async() failed");
+
+	mod_timer(&ts->penup_timer,
+		  jiffies + msecs_to_jiffies(TSC2301_TS_PENUP_TIME));
+
+	return IRQ_HANDLED;
+}
+
+static void tsc2301_ts_disable(struct tsc2301 *tsc)
+{
+	struct tsc2301_ts *ts = tsc->ts;
+
+	if (ts->disable_depth++ != 0)
+		return;
+
+	disable_irq(ts->irq);
+
+	/* wait until penup timer expire normally */
+	do {
+		msleep(1);
+	} while (ts->event_sent);
+
+	tsc2301_ts_stop_scan(tsc);
+}
+
+static void tsc2301_ts_enable(struct tsc2301 *tsc)
+{
+	struct tsc2301_ts *ts = tsc->ts;
+
+	if (--ts->disable_depth != 0)
+		return;
+
+	enable_irq(ts->irq);
+
+	tsc2301_ts_start_scan(tsc);
+}
+
+#ifdef CONFIG_PM
+int tsc2301_ts_suspend(struct tsc2301 *tsc)
+{
+	struct tsc2301_ts *ts = tsc->ts;
+
+	mutex_lock(&ts->mutex);
+	tsc2301_ts_disable(tsc);
+	mutex_unlock(&ts->mutex);
+
+	return 0;
+}
+
+void tsc2301_ts_resume(struct tsc2301 *tsc)
+{
+	struct tsc2301_ts *ts = tsc->ts;
+
+	mutex_lock(&ts->mutex);
+	tsc2301_ts_enable(tsc);
+	mutex_unlock(&ts->mutex);
+}
+#endif
+
+static void tsc2301_ts_setup_spi_xfer(struct tsc2301 *tsc)
+{
+	struct tsc2301_ts *ts = tsc->ts;
+	struct spi_message *m = &ts->read_msg;
+	struct spi_transfer *x = &ts->read_xfer[0];
+
+	spi_message_init(m);
+
+	x->tx_buf = &tsc2301_ts_read_data;
+	x->len = 2;
+	spi_message_add_tail(x, m);
+
+	x++;
+	x->rx_buf = ts->coords;
+	x->len = 8;
+	spi_message_add_tail(x, m);
+
+	m->complete = tsc2301_ts_rx;
+	m->context = tsc;
+}
+
+static ssize_t tsc2301_ts_pen_down_show(struct device *dev,
+					struct device_attribute *attr,
+					char *buf)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", tsc->ts->pen_down);
+}
+
+static DEVICE_ATTR(pen_down, S_IRUGO, tsc2301_ts_pen_down_show, NULL);
+
+static ssize_t tsc2301_ts_disable_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct tsc2301		*tsc = dev_get_drvdata(dev);
+	struct tsc2301_ts	*ts = tsc->ts;
+
+	return sprintf(buf, "%u\n", ts->disabled);
+}
+
+static ssize_t tsc2301_ts_disable_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct tsc2301		*tsc = dev_get_drvdata(dev);
+	struct tsc2301_ts	*ts = tsc->ts;
+	char *endp;
+	int i;
+
+	i = simple_strtoul(buf, &endp, 10);
+	i = i ? 1 : 0;
+	mutex_lock(&ts->mutex);
+	if (i == ts->disabled) goto out;
+	ts->disabled = i;
+
+	if (i)
+		tsc2301_ts_disable(tsc);
+	else
+		tsc2301_ts_enable(tsc);
+out:
+	mutex_unlock(&ts->mutex);
+	return count;
+}
+
+static DEVICE_ATTR(disable_ts, 0664, tsc2301_ts_disable_show,
+		   tsc2301_ts_disable_store);
+
+int __devinit tsc2301_ts_init(struct tsc2301 *tsc,
+			      struct tsc2301_platform_data *pdata)
+{
+	struct tsc2301_ts *ts;
+	struct input_dev *idev;
+	int r;
+	int x_max, y_max;
+	int x_fudge, y_fudge, p_fudge;
+
+	if (pdata->dav_int <= 0) {
+		dev_err(&tsc->spi->dev, "need DAV IRQ");
+		return -EINVAL;
+	}
+
+	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+	if (ts == NULL)
+		return -ENOMEM;
+	tsc->ts = ts;
+
+	ts->coords = kzalloc(sizeof(*ts->coords), GFP_KERNEL);
+	if (ts->coords == NULL) {
+		kfree(ts);
+		return -ENOMEM;
+	}
+
+	ts->irq = pdata->dav_int;
+
+	init_timer(&ts->penup_timer);
+	setup_timer(&ts->penup_timer, tsc2301_ts_timer_handler,
+			(unsigned long)tsc);
+
+	mutex_init(&ts->mutex);
+
+	ts->x_plate_ohm	= pdata->ts_x_plate_ohm ? : 280;
+	ts->hw_avg_max	= pdata->ts_hw_avg;
+	ts->max_pressure = pdata->ts_max_pressure ? : MAX_12BIT;
+	ts->touch_pressure = pdata->ts_touch_pressure ? : ts->max_pressure;
+	ts->stab_time	= pdata->ts_stab_time;
+
+	x_max		= pdata->ts_x_max ? : 4096;
+	y_max		= pdata->ts_y_max ? : 4096;
+	x_fudge		= pdata->ts_x_fudge ? : 4;
+	y_fudge		= pdata->ts_y_fudge ? : 8;
+	p_fudge		= pdata->ts_pressure_fudge ? : 2;
+
+	if ((r = tsc2301_ts_check_config(ts, &ts->hw_flags))) {
+		dev_err(&tsc->spi->dev, "invalid configuration\n");
+		goto err2;
+	}
+
+	idev = input_allocate_device();
+	if (idev == NULL) {
+		r = -ENOMEM;
+		goto err2;
+	}
+	idev->name = "TSC2301 touchscreen";
+	snprintf(ts->phys, sizeof(ts->phys),
+		 "%s/input-ts", tsc->spi->dev.bus_id);
+	idev->phys = ts->phys;
+	idev->dev.parent = &tsc->spi->dev;
+
+	idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
+	idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
+	ts->idev = idev;
+
+	tsc2301_ts_setup_spi_xfer(tsc);
+
+	/* These parameters should perhaps be configurable? */
+	input_set_abs_params(idev, ABS_X, 0, x_max, x_fudge, 0);
+	input_set_abs_params(idev, ABS_Y, 0, y_max, y_fudge, 0);
+	input_set_abs_params(idev, ABS_PRESSURE, 0, ts->max_pressure,
+			     p_fudge, 0);
+
+	tsc2301_ts_start_scan(tsc);
+
+	r = request_irq(ts->irq, tsc2301_ts_irq_handler,
+			IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_FALLING,
+			"tsc2301-ts", tsc);
+	if (r < 0) {
+		dev_err(&tsc->spi->dev, "unable to get DAV IRQ");
+		goto err3;
+	}
+	set_irq_wake(ts->irq, 1);
+
+	if (device_create_file(&tsc->spi->dev, &dev_attr_pen_down) < 0)
+		goto err4;
+	if (device_create_file(&tsc->spi->dev, &dev_attr_disable_ts) < 0)
+		goto err5;
+
+	r = input_register_device(idev);
+	if (r < 0) {
+		dev_err(&tsc->spi->dev, "can't register touchscreen device\n");
+		goto err6;
+	}
+
+	return 0;
+err6:
+	device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts);
+err5:
+	device_remove_file(&tsc->spi->dev, &dev_attr_pen_down);
+err4:
+	free_irq(ts->irq, tsc);
+err3:
+	tsc2301_ts_stop_scan(tsc);
+	input_free_device(idev);
+err2:
+	kfree(ts->coords);
+	kfree(ts);
+	return r;
+}
+
+void __devexit tsc2301_ts_exit(struct tsc2301 *tsc)
+{
+	struct tsc2301_ts *ts = tsc->ts;
+
+	tsc2301_ts_disable(tsc);
+
+	device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts);
+	device_remove_file(&tsc->spi->dev, &dev_attr_pen_down);
+
+	free_irq(ts->irq, tsc);
+	input_unregister_device(ts->idev);
+
+	kfree(ts->coords);
+	kfree(ts);
+}
+MODULE_AUTHOR("Jarkko Oikarinen <jarkko.oikarinen@nokia.com>");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 16/33] add omap led drivers
  2008-08-30 17:16                             ` [PATCH 15/33] add tsc2301 " Felipe Balbi
@ 2008-08-30 17:16                               ` Felipe Balbi
  2008-08-30 17:16                                 ` [PATCH 17/33] add tea5761 radio driver Felipe Balbi
  2008-08-31 21:01                                 ` [PATCH 16/33] add omap led drivers David Brownell
  0 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/leds/Kconfig         |   21 +++
 drivers/leds/Makefile        |    2 +
 drivers/leds/leds-omap-pwm.c |  376 ++++++++++++++++++++++++++++++++++++++++++
 drivers/leds/leds-omap.c     |  135 +++++++++++++++
 4 files changed, 534 insertions(+), 0 deletions(-)
 create mode 100644 drivers/leds/leds-omap-pwm.c
 create mode 100644 drivers/leds/leds-omap.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 9556262..6a2f441 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -77,6 +77,27 @@ config LEDS_WRAP
 	help
 	  This option enables support for the PCEngines WRAP programmable LEDs.
 
+config LEDS_OMAP_DEBUG
+	boolean "LED Support for OMAP debug board LEDs"
+	depends on LEDS_CLASS=y && ARCH_OMAP
+	help
+	  Enables support for the LEDs on the debug board used with OMAP
+	  reference boards like H2/H3/H4 and Perseus2.  Up to six of these
+	  may be claimed by the original ARM debug LED API.
+
+config LEDS_OMAP
+	tristate "LED Support for OMAP GPIO LEDs"
+	depends on LEDS_CLASS && ARCH_OMAP
+	help
+	  This option enables support for the LEDs on OMAP processors.
+
+config LEDS_OMAP_PWM
+	tristate "LED Support for OMAP PWM-controlled LEDs"
+	depends on LEDS_CLASS && ARCH_OMAP && OMAP_DM_TIMER
+	help
+	  This options enables support for LEDs connected to GPIO lines
+	  controlled by a PWM timer on OMAP CPUs.
+
 config LEDS_H1940
 	tristate "LED Support for iPAQ H1940 device"
 	depends on LEDS_CLASS && ARCH_H1940
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index ff7982b..fd6316e 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -13,6 +13,8 @@ obj-$(CONFIG_LEDS_S3C24XX)		+= leds-s3c24xx.o
 obj-$(CONFIG_LEDS_AMS_DELTA)		+= leds-ams-delta.o
 obj-$(CONFIG_LEDS_NET48XX)		+= leds-net48xx.o
 obj-$(CONFIG_LEDS_WRAP)			+= leds-wrap.o
+obj-$(CONFIG_LEDS_OMAP)			+= leds-omap.o
+obj-$(CONFIG_LEDS_OMAP_PWM)		+= leds-omap-pwm.o
 obj-$(CONFIG_LEDS_H1940)		+= leds-h1940.o
 obj-$(CONFIG_LEDS_COBALT_QUBE)		+= leds-cobalt-qube.o
 obj-$(CONFIG_LEDS_COBALT_RAQ)		+= leds-cobalt-raq.o
diff --git a/drivers/leds/leds-omap-pwm.c b/drivers/leds/leds-omap-pwm.c
new file mode 100644
index 0000000..57eb383
--- /dev/null
+++ b/drivers/leds/leds-omap-pwm.c
@@ -0,0 +1,376 @@
+/* drivers/leds/leds-omap_pwm.c
+ *
+ * Driver to blink LEDs using OMAP PWM timers
+ *
+ * Copyright (C) 2006 Nokia Corporation
+ * Author: Timo Teras
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/ctype.h>
+#include <linux/sched.h>
+#include <asm/delay.h>
+#include <mach/board.h>
+#include <mach/dmtimer.h>
+
+struct omap_pwm_led {
+	struct led_classdev cdev;
+	struct work_struct work;
+	struct omap_pwm_led_platform_data *pdata;
+	struct omap_dm_timer *intensity_timer;
+	struct omap_dm_timer *blink_timer;
+	int powered;
+	unsigned int on_period, off_period;
+	enum led_brightness brightness;
+};
+
+static inline struct omap_pwm_led *pdev_to_omap_pwm_led(struct platform_device *pdev)
+{
+	return platform_get_drvdata(pdev);
+}
+
+static inline struct omap_pwm_led *cdev_to_omap_pwm_led(struct led_classdev *led_cdev)
+{
+	return container_of(led_cdev, struct omap_pwm_led, cdev);
+}
+
+static inline struct omap_pwm_led *work_to_omap_pwm_led(struct work_struct *work)
+{
+	return container_of(work, struct omap_pwm_led, work);
+}
+
+static void omap_pwm_led_set_blink(struct omap_pwm_led *led)
+{
+	if (!led->powered)
+		return;
+
+	if (led->on_period != 0 && led->off_period != 0) {
+		unsigned long load_reg, cmp_reg;
+
+		load_reg = 32768 * (led->on_period + led->off_period) / 1000;
+		cmp_reg = 32768 * led->on_period / 1000;
+
+		omap_dm_timer_stop(led->blink_timer);
+		omap_dm_timer_set_load(led->blink_timer, 1, -load_reg);
+		omap_dm_timer_set_match(led->blink_timer, 1, -cmp_reg);
+		omap_dm_timer_set_pwm(led->blink_timer, 1, 1,
+				      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
+		omap_dm_timer_write_counter(led->blink_timer, -2);
+		omap_dm_timer_start(led->blink_timer);
+	} else {
+		omap_dm_timer_set_pwm(led->blink_timer, 1, 1,
+				      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
+		omap_dm_timer_stop(led->blink_timer);
+	}
+}
+
+static void omap_pwm_led_power_on(struct omap_pwm_led *led)
+{
+	if (led->powered)
+		return;
+	led->powered = 1;
+
+	/* Select clock */
+	omap_dm_timer_enable(led->intensity_timer);
+	omap_dm_timer_set_source(led->intensity_timer, OMAP_TIMER_SRC_32_KHZ);
+
+	/* Turn voltage on */
+	if (led->pdata->set_power != NULL)
+		led->pdata->set_power(led->pdata, 1);
+
+	/* Enable PWM timers */
+	if (led->blink_timer != NULL) {
+		omap_dm_timer_enable(led->blink_timer);
+		omap_dm_timer_set_source(led->blink_timer,
+					 OMAP_TIMER_SRC_32_KHZ);
+		omap_pwm_led_set_blink(led);
+	}
+
+	omap_dm_timer_set_load(led->intensity_timer, 1, 0xffffff00);
+}
+
+static void omap_pwm_led_power_off(struct omap_pwm_led *led)
+{
+	if (!led->powered)
+		return;
+	led->powered = 0;
+
+	/* Everything off */
+	omap_dm_timer_stop(led->intensity_timer);
+	omap_dm_timer_disable(led->intensity_timer);
+
+	if (led->blink_timer != NULL) {
+		omap_dm_timer_stop(led->blink_timer);
+		omap_dm_timer_disable(led->blink_timer);
+	}
+
+	if (led->pdata->set_power != NULL)
+		led->pdata->set_power(led->pdata, 0);
+}
+
+static void omap_pwm_led_set_pwm_cycle(struct omap_pwm_led *led, int cycle)
+{
+	int n;
+
+	if (cycle == 0)
+		n = 0xff;
+	else	n = cycle - 1;
+
+	if (cycle == LED_FULL) {
+		omap_dm_timer_set_pwm(led->intensity_timer, 1, 1,
+				      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
+		omap_dm_timer_stop(led->intensity_timer);
+	} else {
+		omap_dm_timer_set_pwm(led->intensity_timer, 0, 1,
+				      OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
+		omap_dm_timer_set_match(led->intensity_timer, 1,
+					(0xffffff00) | cycle);
+		omap_dm_timer_start(led->intensity_timer);
+	}
+}
+
+static void omap_pwm_led_set(struct led_classdev *led_cdev,
+			     enum led_brightness value)
+{
+	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
+
+	led->brightness = value;
+	schedule_work(&led->work);
+}
+
+static void omap_pwm_led_work(struct work_struct *work)
+{
+	struct omap_pwm_led *led = work_to_omap_pwm_led(work);
+
+	if (led->brightness != LED_OFF) {
+		omap_pwm_led_power_on(led);
+		omap_pwm_led_set_pwm_cycle(led, led->brightness);
+	} else {
+		omap_pwm_led_power_off(led);
+	}
+}
+
+static ssize_t omap_pwm_led_on_period_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
+
+	return sprintf(buf, "%u\n", led->on_period) + 1;
+}
+
+static ssize_t omap_pwm_led_on_period_store(struct device *dev,
+				struct device_attribute *attr,
+				const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
+	int ret = -EINVAL;
+	unsigned long val;
+	char *after;
+	size_t count;
+
+	val = simple_strtoul(buf, &after, 10);
+	count = after - buf;
+	if (*after && isspace(*after))
+		count++;
+
+	if (count == size) {
+		led->on_period = val;
+		omap_pwm_led_set_blink(led);
+		ret = count;
+	}
+
+	return ret;
+}
+
+static ssize_t omap_pwm_led_off_period_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
+
+	return sprintf(buf, "%u\n", led->off_period) + 1;
+}
+
+static ssize_t omap_pwm_led_off_period_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t size)
+{
+	struct led_classdev *led_cdev = dev_get_drvdata(dev);
+	struct omap_pwm_led *led = cdev_to_omap_pwm_led(led_cdev);
+	int ret = -EINVAL;
+	unsigned long val;
+	char *after;
+	size_t count;
+
+	val = simple_strtoul(buf, &after, 10);
+	count = after - buf;
+	if (*after && isspace(*after))
+		count++;
+
+	if (count == size) {
+		led->off_period = val;
+		omap_pwm_led_set_blink(led);
+		ret = count;
+	}
+
+	return ret;
+}
+
+static DEVICE_ATTR(on_period, 0644, omap_pwm_led_on_period_show,
+				omap_pwm_led_on_period_store);
+static DEVICE_ATTR(off_period, 0644, omap_pwm_led_off_period_show,
+				omap_pwm_led_off_period_store);
+
+static int omap_pwm_led_probe(struct platform_device *pdev)
+{
+	struct omap_pwm_led_platform_data *pdata = pdev->dev.platform_data;
+	struct omap_pwm_led *led;
+	int ret;
+
+	led = kzalloc(sizeof(struct omap_pwm_led), GFP_KERNEL);
+	if (led == NULL) {
+		dev_err(&pdev->dev, "No memory for device\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, led);
+	led->cdev.brightness_set = omap_pwm_led_set;
+	led->cdev.default_trigger = NULL;
+	led->cdev.name = pdata->name;
+	led->pdata = pdata;
+	led->brightness = LED_OFF;
+	INIT_WORK(&led->work, omap_pwm_led_work);
+
+	dev_info(&pdev->dev, "OMAP PWM LED (%s) at GP timer %d/%d\n",
+		 pdata->name, pdata->intensity_timer, pdata->blink_timer);
+
+	/* register our new led device */
+	ret = led_classdev_register(&pdev->dev, &led->cdev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "led_classdev_register failed\n");
+		goto error_classdev;
+	}
+
+	/* get related dm timers */
+	led->intensity_timer = omap_dm_timer_request_specific(pdata->intensity_timer);
+	if (led->intensity_timer == NULL) {
+		dev_err(&pdev->dev, "failed to request intensity pwm timer\n");
+		ret = -ENODEV;
+		goto error_intensity;
+	}
+	omap_dm_timer_disable(led->intensity_timer);
+
+	if (pdata->blink_timer != 0) {
+		led->blink_timer = omap_dm_timer_request_specific(pdata->blink_timer);
+		if (led->blink_timer == NULL) {
+			dev_err(&pdev->dev, "failed to request blinking pwm timer\n");
+			ret = -ENODEV;
+			goto error_blink1;
+		}
+		omap_dm_timer_disable(led->blink_timer);
+
+		ret = device_create_file(led->cdev.dev,
+					       &dev_attr_on_period);
+		if(ret)
+			goto error_blink2;
+
+		ret = device_create_file(led->cdev.dev,
+					&dev_attr_off_period);
+		if(ret)
+			goto error_blink3;
+
+	}
+
+	return 0;
+
+error_blink3:
+	device_remove_file(led->cdev.dev,
+				 &dev_attr_on_period);
+error_blink2:
+	dev_err(&pdev->dev, "failed to create device file(s)\n");
+error_blink1:
+	omap_dm_timer_free(led->intensity_timer);
+error_intensity:
+	led_classdev_unregister(&led->cdev);
+error_classdev:
+	kfree(led);
+	return ret;
+}
+
+static int omap_pwm_led_remove(struct platform_device *pdev)
+{
+	struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev);
+
+	device_remove_file(led->cdev.dev,
+				 &dev_attr_on_period);
+	device_remove_file(led->cdev.dev,
+				 &dev_attr_off_period);
+	led_classdev_unregister(&led->cdev);
+
+	omap_pwm_led_set(&led->cdev, LED_OFF);
+	if (led->blink_timer != NULL)
+		omap_dm_timer_free(led->blink_timer);
+	omap_dm_timer_free(led->intensity_timer);
+	kfree(led);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int omap_pwm_led_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev);
+
+	led_classdev_suspend(&led->cdev);
+	return 0;
+}
+
+static int omap_pwm_led_resume(struct platform_device *pdev)
+{
+	struct omap_pwm_led *led = pdev_to_omap_pwm_led(pdev);
+
+	led_classdev_resume(&led->cdev);
+	return 0;
+}
+#else
+#define omap_pwm_led_suspend NULL
+#define omap_pwm_led_resume NULL
+#endif
+
+static struct platform_driver omap_pwm_led_driver = {
+	.probe		= omap_pwm_led_probe,
+	.remove		= omap_pwm_led_remove,
+	.suspend	= omap_pwm_led_suspend,
+	.resume		= omap_pwm_led_resume,
+	.driver		= {
+		.name		= "omap_pwm_led",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init omap_pwm_led_init(void)
+{
+	return platform_driver_register(&omap_pwm_led_driver);
+}
+
+static void __exit omap_pwm_led_exit(void)
+{
+	platform_driver_unregister(&omap_pwm_led_driver);
+}
+
+module_init(omap_pwm_led_init);
+module_exit(omap_pwm_led_exit);
+
+MODULE_AUTHOR("Timo Teras");
+MODULE_DESCRIPTION("OMAP PWM LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-omap.c b/drivers/leds/leds-omap.c
new file mode 100644
index 0000000..5c14c4b
--- /dev/null
+++ b/drivers/leds/leds-omap.c
@@ -0,0 +1,135 @@
+/* drivers/leds/leds-omap.c
+ *
+ * (C) 2006 Samsung Electronics
+ * Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * OMAP - LEDs GPIO driver
+ *
+ * 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/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+
+#include <mach/gpio.h>
+#include <mach/hardware.h>
+#include <mach/led.h>
+
+/* our context */
+
+static void omap_set_led_gpio(struct led_classdev *led_cdev,
+			    enum led_brightness value)
+{
+	struct omap_led_config *led_dev;
+
+	led_dev = container_of(led_cdev, struct omap_led_config, cdev);
+
+	if (value)
+		omap_set_gpio_dataout(led_dev->gpio, 1);
+	else
+		omap_set_gpio_dataout(led_dev->gpio, 0);
+}
+
+static void omap_configure_led_gpio(int gpio)
+{
+	if (omap_request_gpio(gpio) < 0) {
+		printk(KERN_ERR "Failed to request GPIO%d for LEDs\n", gpio);
+		return;
+	}
+	omap_set_gpio_direction(gpio, 0);	/* OUT */
+}
+
+static int omap_led_probe(struct platform_device *dev)
+{
+	struct omap_led_platform_data *pdata = dev->dev.platform_data;
+	struct omap_led_config *leds = pdata->leds;
+	int i, ret = 0;
+
+	for (i = 0; ret >= 0 && i < pdata->nr_leds; i++) {
+		omap_configure_led_gpio(leds[i].gpio);
+		if (!leds[i].cdev.brightness_set)
+			leds[i].cdev.brightness_set = omap_set_led_gpio;
+
+		ret = led_classdev_register(&dev->dev, &leds[i].cdev);
+	}
+
+	if (ret < 0 && i > 1) {
+		for (i = i - 2; i >= 0; i--)
+			led_classdev_unregister(&leds[i].cdev);
+	}
+
+	return ret;
+}
+
+static int omap_led_remove(struct platform_device *dev)
+{
+	struct omap_led_platform_data *pdata = dev->dev.platform_data;
+	struct omap_led_config *leds = pdata->leds;
+	int i;
+
+	for (i = 0; i < pdata->nr_leds; i++)
+		led_classdev_unregister(&leds[i].cdev);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int omap_led_suspend(struct platform_device *dev, pm_message_t state)
+{
+	struct omap_led_platform_data *pdata = dev->dev.platform_data;
+	struct omap_led_config *leds = pdata->leds;
+	int i;
+
+	for (i = 0; i < pdata->nr_leds; i++)
+		led_classdev_suspend(&leds[i].cdev);
+
+	return 0;
+}
+
+static int omap_led_resume(struct platform_device *dev)
+{
+	struct omap_led_platform_data *pdata = dev->dev.platform_data;
+	struct omap_led_config *leds = pdata->leds;
+	int i;
+
+	for (i = 0; i < pdata->nr_leds; i++)
+		led_classdev_resume(&leds[i].cdev);
+
+	return 0;
+}
+#else
+#define omap_led_suspend	NULL
+#define omap_led_resume		NULL
+#endif
+
+static struct platform_driver omap_led_driver = {
+	.probe		= omap_led_probe,
+	.remove		= omap_led_remove,
+	.suspend	= omap_led_suspend,
+	.resume		= omap_led_resume,
+	.driver		= {
+		.name		= "omap-led",
+		.owner		= THIS_MODULE,
+	},
+};
+
+static int __init omap_led_init(void)
+{
+	return platform_driver_register(&omap_led_driver);
+}
+
+static void __exit omap_led_exit(void)
+{
+ 	platform_driver_unregister(&omap_led_driver);
+}
+
+module_init(omap_led_init);
+module_exit(omap_led_exit);
+
+MODULE_AUTHOR("Kyungmin Park<kyungmin.park@samsung.com>");
+MODULE_DESCRIPTION("OMAP LED driver");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 17/33] add tea5761 radio driver
  2008-08-30 17:16                               ` [PATCH 16/33] add omap led drivers Felipe Balbi
@ 2008-08-30 17:16                                 ` Felipe Balbi
  2008-08-30 17:16                                   ` [PATCH 18/33] add ov9640 sensor driver Felipe Balbi
  2008-09-01  7:24                                   ` [PATCH 17/33] add tea5761 radio driver Jarkko Nikula
  2008-08-31 21:01                                 ` [PATCH 16/33] add omap led drivers David Brownell
  1 sibling, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/media/radio/Kconfig         |   13 +
 drivers/media/radio/Makefile        |    1 +
 drivers/media/radio/radio-tea5761.c |  517 +++++++++++++++++++++++++++++++++++
 3 files changed, 531 insertions(+), 0 deletions(-)
 create mode 100644 drivers/media/radio/radio-tea5761.c

diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 1b41b3f..de6ca27 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -339,6 +339,19 @@ config RADIO_ZOLTRIX_PORT
 	help
 	  Enter the I/O port of your Zoltrix radio card.
 
+config RADIO_TEA5761
+	tristate "Philips Semiconductors TEA5761 I2C FM Radio"
+	help
+	  Choose Y here if you have one of these AM/FM radio cards.
+
+	  In order to control your radio card, you will need to use programs
+	  that are compatible with the Video For Linux 2 API.  Information on
+	  this API and pointers to "v4l" programs may be found at
+	  <file:Documentation/video4linux/API.html>.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called radio-tea5761.
+
 config USB_DSBR
 	tristate "D-Link/GemTek USB FM radio support"
 	depends on USB && VIDEO_V4L2
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index a30159f..f5bffcc 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o
 obj-$(CONFIG_RADIO_GEMTEK_PCI) += radio-gemtek-pci.o
 obj-$(CONFIG_RADIO_TRUST) += radio-trust.o
 obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o
+obj-$(CONFIG_RADIO_TEA5761) += radio-tea5761.o
 obj-$(CONFIG_USB_DSBR) += dsbr100.o
 obj-$(CONFIG_USB_SI470X) += radio-si470x.o
 
diff --git a/drivers/media/radio/radio-tea5761.c b/drivers/media/radio/radio-tea5761.c
new file mode 100644
index 0000000..1c6d8ab
--- /dev/null
+++ b/drivers/media/radio/radio-tea5761.c
@@ -0,0 +1,517 @@
+/*
+ * drivers/media/radio/radio-tea5761.c
+ *
+ * Copyright (C) 2005-2008 Nokia Corporation
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/init.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#define DRIVER_NAME "tea5761"
+
+#define TEA5761_VERSION		KERNEL_VERSION(0, 0, 1)
+
+#define TEA5761_I2C_ADDR	0x10
+
+#define TEA5761_MANID		0x002b
+#define TEA5761_CHIPID		0x5761
+
+#define TEA5761_INTREG_BLMSK	0x0001
+#define TEA5761_INTREG_FRRMSK	0x0002
+#define TEA5761_INTREG_LEVMSK	0x0008
+#define TEA5761_INTREG_IFMSK	0x0010
+#define TEA5761_INTREG_BLMFLAG	0x0100
+#define TEA5761_INTREG_FRRFLAG	0x0200
+#define TEA5761_INTREG_LEVFLAG	0x0800
+#define TEA5761_INTREG_IFFLAG	0x1000
+
+#define TEA5761_FRQSET_SUD	0x8000
+#define TEA5761_FRQSET_SM	0x4000
+
+#define TEA5761_TNCTRL_PUPD0	0x4000
+#define TEA5761_TNCTRL_BLIM	0x2000
+#define TEA5761_TNCTRL_SWPM	0x1000
+#define TEA5761_TNCTRL_IFCTC	0x0800
+#define TEA5761_TNCTRL_AFM	0x0400
+#define TEA5761_TNCTRL_SMUTE	0x0200
+#define TEA5761_TNCTRL_SNC	0x0100
+#define TEA5761_TNCTRL_MU	0x0080
+#define TEA5761_TNCTRL_SSL1	0x0040
+#define TEA5761_TNCTRL_SSL0	0x0020
+#define TEA5761_TNCTRL_HLSI	0x0010
+#define TEA5761_TNCTRL_MST	0x0008
+#define TEA5761_TNCTRL_SWP	0x0004
+#define TEA5761_TNCTRL_DTC	0x0002
+#define TEA5761_TNCTRL_AHLSI	0x0001
+
+#define TEA5761_TUNCHK_LEVEL(x)	(((x) & 0x00F0) >> 4)
+#define TEA5761_TUNCHK_IFCNT(x) (((x) & 0xFE00) >> 9)
+#define TEA5761_TUNCHK_TUNTO	0x0100
+#define TEA5761_TUNCHK_LD	0x0008
+#define TEA5761_TUNCHK_STEREO	0x0004
+
+#define TEA5761_TESTREG_TRIGFR	0x0800
+
+#define TEA5761_FREQ_LOW	87500
+#define TEA5761_FREQ_HIGH	108000
+
+struct tea5761_regs {
+	u16 intreg;
+	u16 frqset;
+	u16 tnctrl;
+	u16 frqchk;
+	u16 tunchk;
+	u16 testreg;
+	u16 manid;
+	u16 chipid;
+} __attribute__ ((packed));
+
+struct tea5761_write_regs {
+	u8 intreg;
+	u16 frqset;
+	u16 tnctrl;
+	u16 testreg;
+} __attribute__ ((packed));
+
+struct tea5761_device {
+	struct video_device	*video_dev;
+	struct i2c_client	*i2c_dev;
+	struct tea5761_regs	regs;
+	struct mutex		mutex;
+	int			users;
+};
+
+static struct tea5761_device tea5761;
+
+static struct i2c_driver	tea5761_driver;
+static int radio_nr = -1;
+
+static int tea5761_read_regs(struct tea5761_device *tea)
+{
+	int rc, i;
+	u16 *p = (u16 *) &tea->regs;
+	struct i2c_client *client = tea->i2c_dev;
+
+	rc = i2c_master_recv(client, (void*) &tea->regs, sizeof(tea->regs));
+	for (i = 0; i < 8; i++) {
+		p[i] = __be16_to_cpu(p[i]);
+	}
+
+	dev_dbg(&client->dev,
+		"chip state: %04x %04x %04x %04x %04x %04x %04x %04x\n",
+		p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7]);
+
+	if (rc < 0)
+		dev_err(&client->dev, "read\n");
+
+	return rc;
+}
+
+static void tea5761_write_regs(struct tea5761_device *tea)
+{
+	struct tea5761_write_regs wr;
+	struct tea5761_regs *r = &tea->regs;
+	struct i2c_client *client = tea->i2c_dev;
+	u8 *p = (u8 *) r;
+
+	wr.intreg = r->intreg & 0xff;
+	wr.frqset = __cpu_to_be16(r->frqset);
+	wr.tnctrl = __cpu_to_be16(r->tnctrl);
+	wr.testreg = __cpu_to_be16(r->testreg);
+
+	dev_dbg(&client->dev,
+		"writing state: %02x %02x %02x %02x %02x %02x %02x\n",
+		p[0], p[1], p[2], p[3], p[4], p[5], p[6]);
+	if (i2c_master_send(client, (void *) &wr, sizeof(wr)) < 0)
+		dev_err(&client->dev, "write\n");
+}
+
+static void tea5761_power_up(struct tea5761_device *tea)
+{
+	struct tea5761_regs *r = &tea->regs;
+
+	if (!(r->tnctrl & TEA5761_TNCTRL_PUPD0)) {
+		r->tnctrl &= ~(TEA5761_TNCTRL_AFM | TEA5761_TNCTRL_MU |
+			       TEA5761_TNCTRL_HLSI);
+		r->testreg |= TEA5761_TESTREG_TRIGFR;
+		r->tnctrl |= TEA5761_TNCTRL_PUPD0;
+		return tea5761_write_regs(tea);
+	}
+}
+
+static void tea5761_power_down(struct tea5761_device *tea)
+{
+	struct tea5761_regs *r = &tea->regs;
+
+	if (r->tnctrl & TEA5761_TNCTRL_PUPD0) {
+		r->tnctrl &= ~TEA5761_TNCTRL_PUPD0;
+		return tea5761_write_regs(tea);
+	}
+}
+
+static void tea5761_set_freq(struct tea5761_device *tea, int freq)
+{
+	struct tea5761_regs *r = &tea->regs;
+
+	if (r->tnctrl & TEA5761_TNCTRL_HLSI)
+		r->frqset = (freq + 225000) / 8192;
+	else
+		r->frqset = (freq - 225000) / 8192;
+}
+
+static int tea5761_get_freq(struct tea5761_device *tea)
+{
+	struct tea5761_regs *r = &tea->regs;
+
+	if (r->tnctrl & TEA5761_TNCTRL_HLSI)
+		return (r->frqchk * 8192) - 225000;
+	else
+		return (r->frqchk * 8192) + 225000;
+}
+
+static void tea5761_tune(struct tea5761_device *tea, int freq)
+{
+	tea5761_set_freq(tea, freq);
+	tea5761_write_regs(tea);
+}
+
+static void tea5761_set_audout_mode(struct tea5761_device *tea, int audmode)
+{
+	struct tea5761_regs *r = &tea->regs;
+	int tnctrl = r->tnctrl;
+
+	if (audmode == V4L2_TUNER_MODE_MONO)
+		r->tnctrl |= TEA5761_TNCTRL_MST;
+	else
+		r->tnctrl &= ~TEA5761_TNCTRL_MST;
+	if (tnctrl != r->tnctrl)
+		tea5761_write_regs(tea);
+}
+
+static int tea5761_get_audout_mode(struct tea5761_device *tea)
+{
+	struct tea5761_regs *r = &tea->regs;
+
+	if (r->tnctrl & TEA5761_TNCTRL_MST)
+		return V4L2_TUNER_MODE_MONO;
+	else
+		return V4L2_TUNER_MODE_STEREO;
+}
+
+static void tea5761_mute(struct tea5761_device *tea, int on)
+{
+	struct tea5761_regs *r = &tea->regs;
+	int tnctrl = r->tnctrl;
+
+	if (on)
+		r->tnctrl |= TEA5761_TNCTRL_MU;
+	else
+		r->tnctrl &= ~TEA5761_TNCTRL_MU;
+	if (tnctrl != r->tnctrl)
+		tea5761_write_regs(tea);
+}
+
+static int tea5761_is_muted(struct tea5761_device *tea)
+{
+	return tea->regs.tnctrl & TEA5761_TNCTRL_MU;
+}
+
+static int tea5761_do_ioctl(struct inode *inode, struct file *file,
+			    unsigned int cmd, void *arg)
+{
+	struct tea5761_device *tea = file->private_data;
+	struct video_device *dev = tea->video_dev;
+	struct i2c_client *client = tea->i2c_dev;
+	struct tea5761_regs *r = &tea->regs;
+
+	union {
+		struct v4l2_capability c;
+		struct v4l2_tuner t;
+		struct v4l2_frequency f;
+		struct v4l2_queryctrl qc;
+		struct v4l2_control ct;
+	} *u = arg;
+
+	tea5761_read_regs(tea);
+
+	switch (cmd) {
+	case VIDIOC_QUERYCAP:
+		dev_dbg(&client->dev, "VIDIOC_QUERYCAP\n");
+		memset(&u->c, 0, sizeof(u->c));
+		strlcpy(u->c.driver,
+			dev->parent->driver->name,
+			sizeof(u->c.driver));
+		strlcpy(u->c.card, dev->name, sizeof(u->c.card));
+		snprintf(u->c.bus_info, sizeof(u->c.bus_info), "I2C:%s",
+			 dev->parent->bus_id);
+		u->c.version = TEA5761_VERSION;
+		u->c.capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO;
+		break;
+
+	case VIDIOC_G_TUNER:
+		/* Only one tuner chip */
+		dev_dbg(&client->dev, "VIDIOC_G_TUNER\n");
+		if (u->t.index != 0)
+			return -EINVAL;
+
+		memset(&u->t, 0, sizeof(u->t));
+		u->t.type = V4L2_TUNER_RADIO;
+		strlcpy(u->t.name, "FM", sizeof(u->t.name));
+		/* Freq in 62.5Hz units */
+		u->t.rangelow = TEA5761_FREQ_LOW * 16;
+		u->t.rangehigh = TEA5761_FREQ_HIGH * 16;
+		u->t.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
+		if (r->tunchk & TEA5761_TUNCHK_STEREO)
+			u->t.rxsubchans = V4L2_TUNER_SUB_STEREO;
+		u->t.audmode = tea5761_get_audout_mode(tea);
+		u->t.signal = TEA5761_TUNCHK_LEVEL(r->tunchk) * 0xffff / 0xf;
+		u->t.afc = TEA5761_TUNCHK_IFCNT(r->tunchk);
+		break;
+
+	case VIDIOC_S_TUNER:
+		/* Only tuner nro 0 can be selected. */
+		dev_dbg(&client->dev, "VIDIOC_S_TUNER\n");
+		if (u->t.index != 0)
+			return -EINVAL;
+		tea5761_set_audout_mode(tea, u->t.audmode);
+		break;
+
+	case VIDIOC_G_FREQUENCY:
+		dev_dbg(&client->dev, "VIDIOC_G_FREQUENCY\n");
+		memset(&u->f, 0, sizeof(u->f));
+		u->f.type = V4L2_TUNER_RADIO;
+		if (r->tnctrl & TEA5761_TNCTRL_PUPD0)
+			u->f.frequency = (tea5761_get_freq(tea) * 2) / 125;
+		else
+			u->f.frequency = 0;
+		break;
+
+	case VIDIOC_S_FREQUENCY:
+		dev_dbg(&client->dev, "VIDIOC_S_FREQUENCY %u\n",
+			u->f.frequency);
+		if (u->f.tuner != 0)
+			return -EINVAL;
+		if (u->f.frequency == 0) {
+			/* We special case this as a power down
+			 * control. */
+			tea5761_power_down(tea);
+			break;
+		}
+		if (u->f.frequency < 16 * TEA5761_FREQ_LOW)
+			return -EINVAL;
+		if (u->f.frequency > 16 * TEA5761_FREQ_HIGH)
+			return -EINVAL;
+
+		tea5761_power_up(tea);
+		tea5761_tune(tea, (u->f.frequency * 125) / 2);
+		break;
+
+	case VIDIOC_QUERYCTRL:
+		dev_dbg(&client->dev, "VIDIOC_QUERYCTRL %d\n", u->qc.id);
+		if (u->qc.id != V4L2_CID_AUDIO_MUTE)
+			return -EINVAL;
+		strlcpy(u->qc.name, "Mute", sizeof(u->qc.name));
+		u->qc.minimum = 0;
+		u->qc.maximum = 1;
+		u->qc.step = 1;
+		u->qc.default_value = 0;
+		u->qc.type = V4L2_CTRL_TYPE_BOOLEAN;
+		break;
+
+	case VIDIOC_G_CTRL:
+		dev_dbg(&client->dev, "VIDIOC_G_CTRL %d\n", u->ct.id);
+		if (u->ct.id != V4L2_CID_AUDIO_MUTE)
+			return -EINVAL;
+		if (r->tnctrl & TEA5761_TNCTRL_PUPD0)
+			u->ct.value = tea5761_is_muted(tea) ? 1 : 0;
+		else
+			u->ct.value = 0;
+		break;
+
+	case VIDIOC_S_CTRL:
+		dev_dbg(&client->dev, "VIDIOC_S_CTRL %d\n", u->ct.id);
+		if (u->ct.id != V4L2_CID_AUDIO_MUTE)
+			return -EINVAL;
+		tea5761_mute(tea, u->ct.value);
+		break;
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+
+	return 0;
+}
+
+static int tea5761_ioctl(struct inode *inode, struct file *file,
+			 unsigned int cmd, unsigned long arg)
+{
+	return video_usercopy(inode, file, cmd, arg, tea5761_do_ioctl);
+}
+
+static int tea5761_open(struct inode *inode, struct file *file)
+{
+	int minor = iminor(file->f_dentry->d_inode);
+	/* Currently we support only one device */
+	struct tea5761_device *tea = &tea5761;
+
+	if (tea->video_dev->minor != minor)
+		return -ENODEV;
+
+	mutex_lock(&tea->mutex);
+	/* Only exclusive access */
+	if (tea->users) {
+		mutex_unlock(&tea->mutex);
+		return -EBUSY;
+	}
+	tea->users++;
+	mutex_unlock(&tea->mutex);
+
+	file->private_data = tea;
+	return 0;
+}
+
+static int tea5761_release(struct inode *inode, struct file *file)
+{
+	struct tea5761_device *tea = file->private_data;
+
+	mutex_lock(&tea->mutex);
+	tea->users--;
+	mutex_unlock(&tea->mutex);
+
+	return 0;
+}
+
+static struct file_operations tea5761_fops = {
+	.owner		= THIS_MODULE,
+	.open           = tea5761_open,
+	.release	= tea5761_release,
+	.ioctl		= tea5761_ioctl,
+	.llseek         = no_llseek,
+};
+
+static struct video_device tea5761_video_device = {
+	.name          = "TEA5761 FM-Radio",
+	.vfl_type      = VID_TYPE_TUNER,
+	.fops          = &tea5761_fops,
+	.release       = video_device_release
+};
+
+static int tea5761_i2c_driver_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct video_device *video_dev;
+	int err = 0;
+	struct tea5761_device *tea = &tea5761;
+
+	mutex_init(&tea->mutex);
+
+	tea->i2c_dev = client;
+
+	/* V4L initialization */
+	video_dev = video_device_alloc();
+	if (video_dev == NULL) {
+		dev_err(&client->dev, "couldn't allocate memory\n");
+		err = -ENOMEM;
+		goto exit;
+	}
+	tea->video_dev = video_dev;
+
+	*video_dev = tea5761_video_device;
+	video_dev->parent = &client->dev;
+	i2c_set_clientdata(client, video_dev);
+
+	/* initialize and power off the chip */
+	tea5761_read_regs(tea);
+	tea5761_set_audout_mode(tea, V4L2_TUNER_MODE_STEREO);
+	tea5761_mute(tea, 0);
+	tea5761_power_down(tea);
+
+	tea5761.video_dev = video_dev;
+	tea5761.i2c_dev = client;
+
+	err = video_register_device(video_dev, VFL_TYPE_RADIO, radio_nr);
+	if (err) {
+		dev_err(&client->dev, "couldn't register video device\n");
+		goto err_video_alloc;
+	}
+
+	dev_info(&client->dev, "tea5761 (version %d) detected\n",
+		(tea->regs.manid >> 12) & 0xf);
+
+	return 0;
+
+err_video_alloc:
+	video_device_release(video_dev);
+exit:
+	kfree(client);
+	return err;
+}
+
+static int tea5761_i2c_driver_remove(struct i2c_client *client)
+{
+	struct video_device *vd = i2c_get_clientdata(client);
+
+	video_unregister_device(vd);
+
+	return 0;
+}
+
+static const struct i2c_device_id tea5761_id[] = {
+	{ DRIVER_NAME, 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, tea5761_id);
+
+static struct i2c_driver tea5761_driver = {
+	.driver = {
+		.name	= DRIVER_NAME,
+	},
+	.probe	= tea5761_i2c_driver_probe,
+	.remove = __devexit_p(tea5761_i2c_driver_remove),
+	.id_table = tea5761_id,
+};
+
+static int __init tea5761_init(void)
+{
+	int res;
+
+	if ((res = i2c_add_driver(&tea5761_driver))) {
+		printk(KERN_ERR DRIVER_NAME ": driver registration failed\n");
+		return res;
+	}
+
+	return 0;
+}
+
+static void __exit tea5761_exit(void)
+{
+	i2c_del_driver(&tea5761_driver);
+}
+
+MODULE_AUTHOR("Timo Teräs");
+MODULE_DESCRIPTION("I2C interface for TEA5761.");
+MODULE_LICENSE("GPL");
+
+module_param(radio_nr, int, 0);
+MODULE_PARM_DESC(nr_radio, "video4linux device number to use");
+
+module_init(tea5761_init)
+module_exit(tea5761_exit)
-- 
1.6.0.1.141.g445ca

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 18/33] add ov9640 sensor driver
  2008-08-30 17:16                                 ` [PATCH 17/33] add tea5761 radio driver Felipe Balbi
@ 2008-08-30 17:16                                   ` Felipe Balbi
       [not found]                                     ` <1220116593-862-20-git-send-email-me@felipebalbi.com>
  2008-09-01  7:24                                   ` [PATCH 17/33] add tea5761 radio driver Jarkko Nikula
  1 sibling, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/media/video/Kconfig  |    8 +
 drivers/media/video/Makefile |    1 +
 drivers/media/video/ov9640.c | 1279 ++++++++++++++++++++++++++++++++++++++++++
 drivers/media/video/ov9640.h |  194 +++++++
 4 files changed, 1482 insertions(+), 0 deletions(-)
 create mode 100644 drivers/media/video/ov9640.c
 create mode 100644 drivers/media/video/ov9640.h

diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index ecbfa1b..ec976f6 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -295,6 +295,14 @@ config VIDEO_TCM825X
 	  This is a driver for the Toshiba TCM825x VGA camera sensor.
 	  It is used for example in Nokia N800.
 
+config VIDEO_OV9640
+	tristate "OmniVision OV9640 sensor support"
+	depends on I2C && VIDEO_V4L2
+	---help---
+	  This is a Video4Linux2 sensor-level driver for the OmniVision
+	  OV9640 camera.  It is currently working with the TI OMAP2
+	  camera controller.
+
 config VIDEO_SAA7110
 	tristate "Philips SAA7110 video decoder"
 	depends on VIDEO_V4L1 && I2C
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index bbc6f8b..0b529dd 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -106,6 +106,7 @@ obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o
 obj-$(CONFIG_VIDEO_OV7670) 	+= ov7670.o
 
 obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o
+obj-$(CONFIG_VIDEO_OV9640)	+= ov9640.o
 
 obj-$(CONFIG_USB_DABUSB)        += dabusb.o
 obj-$(CONFIG_USB_OV511)         += ov511.o
diff --git a/drivers/media/video/ov9640.c b/drivers/media/video/ov9640.c
new file mode 100644
index 0000000..a0f7406
--- /dev/null
+++ b/drivers/media/video/ov9640.c
@@ -0,0 +1,1279 @@
+/*
+ * drivers/media/video/ov9640.c
+ *
+ * OV9640 sensor driver
+ *
+ * Author: Andy Lowe (source@mvista.com)
+ * Contact: Trilok Soni <soni.trilok@gmail.com>
+ *
+ * Copyright (C) 2004 MontaVista Software, Inc.
+ * Copyright (C) 2004 Texas Instruments.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <media/v4l2-int-device.h>
+
+#include "ov9640.h"
+
+#define DRIVER_NAME  "ov9640"
+
+struct ov9640_sensor {
+	const struct ov9640_platform_data *pdata;
+	struct v4l2_int_device *v4l2_int_device;
+	struct i2c_client *i2c_client;
+	struct v4l2_pix_format pix;
+	struct v4l2_fract timeperframe;
+	int ver;				/*ov9640 chip version*/
+};
+
+static struct ov9640_sensor ov9640;
+static struct i2c_driver ov9640sensor_i2c_driver;
+
+/* list of image formats supported by OV9640 sensor */
+const static struct v4l2_fmtdesc ov9640_formats[] = {
+	{
+		/* Note:  V4L2 defines RGB565 as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	g2 g1 g0 r4 r3 r2 r1 r0	  b4 b3 b2 b1 b0 g5 g4 g3
+		 *
+		 * We interpret RGB565 as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	g2 g1 g0 b4 b3 b2 b1 b0	  r4 r3 r2 r1 r0 g5 g4 g3
+		 */
+		.description	= "RGB565, le",
+		.pixelformat	= V4L2_PIX_FMT_RGB565,
+	},
+	{
+		/* Note:  V4L2 defines RGB565X as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	b4 b3 b2 b1 b0 g5 g4 g3	  g2 g1 g0 r4 r3 r2 r1 r0
+		 *
+		 * We interpret RGB565X as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	r4 r3 r2 r1 r0 g5 g4 g3	  g2 g1 g0 b4 b3 b2 b1 b0
+		 */
+		.description	= "RGB565, be",
+		.pixelformat	= V4L2_PIX_FMT_RGB565X,
+	},
+	{
+		.description	= "YUYV (YUV 4:2:2), packed",
+		.pixelformat	= V4L2_PIX_FMT_YUYV,
+	},
+	{
+		.description	= "UYVY, packed",
+		.pixelformat	= V4L2_PIX_FMT_UYVY,
+	},
+	{
+		/* Note:  V4L2 defines RGB555 as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	g2 g1 g0 r4 r3 r2 r1 r0	  x  b4 b3 b2 b1 b0 g4 g3
+		 *
+		 * We interpret RGB555 as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	g2 g1 g0 b4 b3 b2 b1 b0	  x  r4 r3 r2 r1 r0 g4 g3
+		 */
+		.description	= "RGB555, le",
+		.pixelformat	= V4L2_PIX_FMT_RGB555,
+	},
+	{
+		/* Note:  V4L2 defines RGB555X as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	x  b4 b3 b2 b1 b0 g4 g3	  g2 g1 g0 r4 r3 r2 r1 r0
+		 *
+		 * We interpret RGB555X as:
+		 *
+		 *	Byte 0			  Byte 1
+		 *	x  r4 r3 r2 r1 r0 g4 g3	  g2 g1 g0 b4 b3 b2 b1 b0
+		 */
+		.description	= "RGB555, be",
+		.pixelformat	= V4L2_PIX_FMT_RGB555X,
+	},
+};
+
+#define NUM_CAPTURE_FORMATS ARRAY_SIZE(ov9640_formats)
+
+/*
+ * OV9640 register configuration for all combinations of pixel format and
+ * image size
+ */
+	/* YUV (YCbCr) QQCIF */
+const static struct ov9640_reg qqcif_yuv[] = {
+	{ 0x12, 0x08 }, { 0x3C, 0x46 }, { 0x40, 0xC0 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x24 }, { 0x0C, 0x00 }, { 0x0D, 0x40 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x50 }, { 0x50, 0x43 }, { 0x51, 0x0D },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x19 }, { 0x53, 0x4C }, { 0x54, 0x65 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x40 }, { 0x56, 0x40 }, { 0x57, 0x40 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x0F },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* YUV (YCbCr) QQVGA */
+const static struct ov9640_reg qqvga_yuv[] = {
+	{ 0x12, 0x10 }, { 0x3C, 0x46 }, { 0x40, 0xC0 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x24 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x50 }, { 0x50, 0x43 }, { 0x51, 0x0D },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x19 }, { 0x53, 0x4C }, { 0x54, 0x65 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x40 }, { 0x56, 0x40 }, { 0x57, 0x40 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x0F },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* YUV (YCbCr) QCIF */
+const static struct ov9640_reg qcif_yuv[] = {
+	{ 0x12, 0x08 }, { 0x3C, 0x46 }, { 0x40, 0xC0 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x50 }, { 0x50, 0x43 }, { 0x51, 0x0D },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x19 }, { 0x53, 0x4C }, { 0x54, 0x65 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x40 }, { 0x56, 0x40 }, { 0x57, 0x40 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x0F },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* YUV (YCbCr) QVGA */
+const static struct ov9640_reg qvga_yuv[] = {
+	{ 0x12, 0x10 }, { 0x3C, 0x46 }, { 0x40, 0xC0 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x50 }, { 0x50, 0x43 }, { 0x51, 0x0D },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x19 }, { 0x53, 0x4C }, { 0x54, 0x65 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x40 }, { 0x56, 0x40 }, { 0x57, 0x40 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x0F },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* YUV (YCbCr) CIF */
+const static struct ov9640_reg cif_yuv[] = {
+	{ 0x12, 0x20 }, { 0x3C, 0x46 }, { 0x40, 0xC0 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x50 }, { 0x50, 0x43 }, { 0x51, 0x0D },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x19 }, { 0x53, 0x4C }, { 0x54, 0x65 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x40 }, { 0x56, 0x40 }, { 0x57, 0x40 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x0F },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* YUV (YCbCr) VGA */
+const static struct ov9640_reg vga_yuv[] = {
+	{ 0x12, 0x40 }, { 0x3C, 0x46 }, { 0x40, 0xC0 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x50 }, { 0x50, 0x43 }, { 0x51, 0x0D },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x19 }, { 0x53, 0x4C }, { 0x54, 0x65 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x40 }, { 0x56, 0x40 }, { 0x57, 0x40 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x0F },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* YUV (YCbCr) SXGA */
+const static struct ov9640_reg sxga_yuv[] = {
+	{ 0x12, 0x00 }, { 0x3C, 0x46 }, { 0x40, 0xC0 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x00 }, { 0x0D, 0x40 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x50 }, { 0x50, 0x43 }, { 0x51, 0x0D },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x19 }, { 0x53, 0x4C }, { 0x54, 0x65 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x40 }, { 0x56, 0x40 }, { 0x57, 0x40 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x0F },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB565 QQCIF */
+const static struct ov9640_reg qqcif_565[] = {
+	{ 0x12, 0x0C }, { 0x3C, 0x40 }, { 0x40, 0x10 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x24 }, { 0x0C, 0x00 }, { 0x0D, 0x40 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB565 QQVGA */
+const static struct ov9640_reg qqvga_565[] = {
+	{ 0x12, 0x14 }, { 0x3C, 0x40 }, { 0x40, 0x10 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x24 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB565 QCIF */
+const static struct ov9640_reg qcif_565[] = {
+	{ 0x12, 0x0C }, { 0x3C, 0x40 }, { 0x40, 0x10 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB565 QVGA */
+const static struct ov9640_reg qvga_565[] = {
+	{ 0x12, 0x14 }, { 0x3C, 0x40 }, { 0x40, 0x10 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB565 CIF */
+const static struct ov9640_reg cif_565[] = {
+	{ 0x12, 0x24 }, { 0x3C, 0x40 }, { 0x40, 0x10 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB565 VGA */
+const static struct ov9640_reg vga_565[] = {
+	{ 0x12, 0x44 }, { 0x3C, 0x40 }, { 0x40, 0x10 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB565 SXGA */
+const static struct ov9640_reg sxga_565[] = {
+	{ 0x12, 0x04 }, { 0x3C, 0x40 }, { 0x40, 0x10 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x00 }, { 0x0D, 0x40 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB555 QQCIF */
+const static struct ov9640_reg qqcif_555[] = {
+	{ 0x12, 0x0C }, { 0x3C, 0x40 }, { 0x40, 0x30 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x24 }, { 0x0C, 0x00 }, { 0x0D, 0x40 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB555 QQVGA */
+const static struct ov9640_reg qqvga_555[] = {
+	{ 0x12, 0x14 }, { 0x3C, 0x40 }, { 0x40, 0x30 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x24 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB555 QCIF */
+const static struct ov9640_reg qcif_555[] = {
+	{ 0x12, 0x0C }, { 0x3C, 0x40 }, { 0x40, 0x30 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB555 QVGA */
+const static struct ov9640_reg qvga_555[] = {
+	{ 0x12, 0x14 }, { 0x3C, 0x40 }, { 0x40, 0x30 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB555 CIF */
+const static struct ov9640_reg cif_555[] = {
+	{ 0x12, 0x24 }, { 0x3C, 0x40 }, { 0x40, 0x30 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB555 VGA */
+const static struct ov9640_reg vga_555[] = {
+	{ 0x12, 0x44 }, { 0x3C, 0x40 }, { 0x40, 0x30 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x04 }, { 0x0D, 0xC0 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+	/* RGB555 SXGA */
+const static struct ov9640_reg sxga_555[] = {
+	{ 0x12, 0x04 }, { 0x3C, 0x40 }, { 0x40, 0x30 },	/* COM7, COM12, COM15 */
+	{ 0x04, 0x00 }, { 0x0C, 0x00 }, { 0x0D, 0x40 },	/* COM1, COM3, COM4 */
+	{ 0x4F, 0x71 }, { 0x50, 0x3E }, { 0x51, 0x0C },	/* MTX1, MTX2, MTX3 */
+	{ 0x52, 0x33 }, { 0x53, 0x72 }, { 0x54, 0x00 },	/* MTX4, MTX5, MTX6 */
+	{ 0x55, 0x2B }, { 0x56, 0x66 }, { 0x57, 0xD2 },	/* MTX7, MTX8, MTX9 */
+	{ 0x58, 0x65 },					/* MTXS */
+	{ OV9640_REG_TERM, OV9640_VAL_TERM }
+};
+
+
+#define DEF_GAIN         31
+#define DEF_AUTOGAIN      1
+#define DEF_EXPOSURE    154
+#define DEF_AEC           1
+#define DEF_FREEZE_AGCAEC 0
+#define DEF_BLUE        153
+#define DEF_RED         (255 - DEF_BLUE)
+#define DEF_AWB           1
+#define DEF_HFLIP         0
+#define DEF_VFLIP         0
+
+/* Our own specific controls */
+#define V4L2_CID_FREEZE_AGCAEC		V4L2_CID_PRIVATE_BASE
+#define V4L2_CID_AUTOEXPOSURE		V4L2_CID_PRIVATE_BASE + 1
+#define V4L2_CID_LAST_PRIV		V4L2_CID_AUTOEXPOSURE
+
+/*  Video controls  */
+static struct vcontrol {
+	struct v4l2_queryctrl qc;
+	int current_value;
+	u8 reg;
+	u8 mask;
+	u8 start_bit;
+} video_control[] = {
+	{
+		{
+			.id = V4L2_CID_GAIN,
+			.type = V4L2_CTRL_TYPE_INTEGER,
+			.name = "Gain",
+			.minimum = 0,
+			.maximum = 63,
+			.step = 1,
+			.default_value = DEF_GAIN,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_GAIN,
+		.mask		= 0x3f,
+		.start_bit	= 0,
+	},
+	{
+		{
+			.id = V4L2_CID_AUTOGAIN,
+			.type = V4L2_CTRL_TYPE_BOOLEAN,
+			.name = "Auto Gain",
+			.minimum = 0,
+			.maximum = 1,
+			.step = 0,
+			.default_value = DEF_AUTOGAIN,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_COM8,
+		.mask		= 0x04,
+		.start_bit	= 2,
+	},
+	{
+		{
+			.id = V4L2_CID_EXPOSURE,
+			.type = V4L2_CTRL_TYPE_INTEGER,
+			.name = "Exposure",
+			.minimum = 0,
+			.maximum = 255,
+			.step = 1,
+			.default_value = DEF_EXPOSURE,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_AECH,
+		.mask		= 0xff,
+		.start_bit	= 0,
+	},
+	{
+		{
+			.id = V4L2_CID_AUTOEXPOSURE,
+			.type = V4L2_CTRL_TYPE_BOOLEAN,
+			.name = "Auto Exposure",
+			.minimum = 0,
+			.maximum = 1,
+			.step = 0,
+			.default_value = DEF_AEC,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_COM8,
+		.mask		= 0x01,
+		.start_bit	= 0,
+	},
+	{
+		{
+			.id = V4L2_CID_FREEZE_AGCAEC,
+			.type = V4L2_CTRL_TYPE_BOOLEAN,
+			.name = "Freeze AGC/AEC",
+			.minimum = 0,
+			.maximum = 1,
+			.step = 0,
+			.default_value = DEF_FREEZE_AGCAEC,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_COM9,
+		.mask		= 0x01,
+		.start_bit	= 0,
+	},
+	{
+		{
+			.id = V4L2_CID_RED_BALANCE,
+			.type = V4L2_CTRL_TYPE_INTEGER,
+			.name = "Red Balance",
+			.minimum = 0,
+			.maximum = 255,
+			.step = 1,
+			.default_value = DEF_RED,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_RED,
+		.mask		= 0xff,
+		.start_bit	= 0,
+	},
+	{
+		{
+			.id = V4L2_CID_BLUE_BALANCE,
+			.type = V4L2_CTRL_TYPE_INTEGER,
+			.name = "Blue Balance",
+			.minimum = 0,
+			.maximum = 255,
+			.step = 1,
+			.default_value = DEF_BLUE,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_BLUE,
+		.mask		= 0xff,
+		.start_bit	= 0,
+	},
+	{
+		{
+			.id = V4L2_CID_AUTO_WHITE_BALANCE,
+			.type = V4L2_CTRL_TYPE_BOOLEAN,
+			.name = "Auto White Balance",
+			.minimum = 0,
+			.maximum = 1,
+			.step = 0,
+			.default_value = DEF_AWB,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_COM8,
+		.mask		= 0x02,
+		.start_bit	= 1,
+	},
+	{
+		{
+			.id = V4L2_CID_HFLIP,
+			.type = V4L2_CTRL_TYPE_BOOLEAN,
+			.name = "Mirror Image",
+			.minimum = 0,
+			.maximum = 1,
+			.step = 0,
+			.default_value = DEF_HFLIP,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_MVFP,
+		.mask		= 0x20,
+		.start_bit	= 5,
+	},
+	{
+		{
+			.id = V4L2_CID_VFLIP,
+			.type = V4L2_CTRL_TYPE_BOOLEAN,
+			.name = "Vertical Flip",
+			.minimum = 0,
+			.maximum = 1,
+			.step = 0,
+			.default_value = DEF_VFLIP,
+		},
+		.current_value	= 0,
+		.reg		= OV9640_MVFP,
+		.mask		= 0x10,
+		.start_bit	= 4,
+	},
+};
+
+const static struct ov9640_reg *
+	ov9640_reg_init[NUM_PIXEL_FORMATS][NUM_IMAGE_SIZES] =
+{
+ { qqcif_yuv, qqvga_yuv, qcif_yuv, qvga_yuv, cif_yuv, vga_yuv, sxga_yuv },
+ { qqcif_565, qqvga_565, qcif_565, qvga_565, cif_565, vga_565, sxga_565 },
+ { qqcif_555, qqvga_555, qcif_555, qvga_555, cif_555, vga_555, sxga_555 },
+};
+
+
+/*
+ * Read a value from a register in an OV9640 sensor device.
+ * The value is returned in 'val'.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int
+ov9640_read_reg(struct i2c_client *client, u8 reg, u8 *val)
+{
+	int err;
+	struct i2c_msg msg[1];
+	unsigned char data[1];
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	msg->addr = client->addr;
+	msg->flags = 0;
+	msg->len = 1;
+	msg->buf = data;
+	*data = reg;
+	err = i2c_transfer(client->adapter, msg, 1);
+	if (err >= 0) {
+		msg->flags = I2C_M_RD;
+		err = i2c_transfer(client->adapter, msg, 1);
+	}
+	if (err >= 0) {
+		*val = *data;
+		return 0;
+	}
+	return err;
+}
+
+/*
+ * Write a value to a register in an OV9640 sensor device.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int
+ov9640_write_reg(struct i2c_client *client, u8 reg, u8 val)
+{
+	int err;
+	struct i2c_msg msg[1];
+	unsigned char data[2];
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	msg->addr = client->addr;
+	msg->flags = 0;
+	msg->len = 2;
+	msg->buf = data;
+	data[0] = reg;
+	data[1] = val;
+	err = i2c_transfer(client->adapter, msg, 1);
+	if (err >= 0)
+		return 0;
+	return err;
+}
+
+static int
+ov9640_write_reg_mask(struct i2c_client *client, u8 reg, u8 *val, u8 mask)
+{
+	u8 oldval, newval;
+	int rc;
+
+	if (mask == 0xff)
+		newval = *val;
+	else {
+		/* need to do read - modify - write */
+		rc = ov9640_read_reg(client, reg, &oldval);
+		if (rc)
+			return rc;
+		oldval &= (~mask);              /* Clear the masked bits */
+		*val &= mask;                  /* Enforce mask on value */
+		newval = oldval | *val;        /* Set the desired bits */
+	}
+
+	/* write the new value to the register */
+	rc = ov9640_write_reg(client, reg, newval);
+	if (rc)
+		return rc;
+
+	rc = ov9640_read_reg(client, reg, &newval);
+	if (rc)
+		return rc;
+
+	*val = newval & mask;
+	return 0;
+}
+
+static int
+ov9640_read_reg_mask(struct i2c_client *client, u8 reg, u8 *val, u8 mask)
+{
+	int rc;
+
+	rc = ov9640_read_reg(client, reg, val);
+	if (rc)
+		return rc;
+	(*val) &= mask;
+
+	return 0;
+}
+
+/*
+ * Initialize a list of OV9640 registers.
+ * The list of registers is terminated by the pair of values
+ * { OV9640_REG_TERM, OV9640_VAL_TERM }.
+ * Returns zero if successful, or non-zero otherwise.
+ */
+static int
+ov9640_write_regs(struct i2c_client *client, const struct ov9640_reg reglist[])
+{
+	int err;
+	const struct ov9640_reg *next = reglist;
+
+	while (!((next->reg == OV9640_REG_TERM)
+		&& (next->val == OV9640_VAL_TERM))) {
+		err = ov9640_write_reg(client, next->reg, next->val);
+		udelay(100);
+		if (err)
+			return err;
+		next++;
+	}
+	return 0;
+}
+
+/* Returns the index of the requested ID from the control structure array */
+static int
+find_vctrl(int id)
+{
+	int i;
+
+	if (id < V4L2_CID_BASE)
+		return -EDOM;
+
+	for (i = (ARRAY_SIZE(video_control) - 1); i >= 0; i--)
+		if (video_control[i].qc.id == id)
+			break;
+	if (i < 0)
+		i = -EINVAL;
+	return i;
+}
+
+/*
+ * Calculate the internal clock divisor (value of the CLKRC register) of the
+ * OV9640 given the image size, the frequency (in Hz) of its XCLK input and a
+ * desired frame period (in seconds).  The frame period 'fper' is expressed as
+ * a fraction.  The frame period is an input/output parameter.
+ * Returns the value of the OV9640 CLKRC register that will yield the frame
+ * period returned in 'fper' at the specified xclk frequency.  The
+ * returned period will be as close to the requested period as possible.
+ */
+static unsigned char
+ov9640_clkrc(enum image_size isize, unsigned long xclk, struct v4l2_fract *fper)
+{
+	unsigned long fpm, fpm_max;	/* frames per minute */
+	unsigned long divisor;
+	const unsigned long divisor_max = 64;
+	/* FIXME
+	 * clks_per_frame should come from platform data
+	 */
+#ifdef CONFIG_ARCH_OMAP24XX
+	const static unsigned long clks_per_frame[] =
+		{ 200000, 400000, 200000, 400000, 400000, 800000, 3200000 };
+      /*         QQCIF   QQVGA    QCIF    QVGA  CIF     VGA	SXGA
+       *         199680,400000, 199680, 400000, 399360, 800000, 3200000
+       */
+#else
+	const static unsigned long clks_per_frame[] =
+		{ 200000, 200000, 200000, 200000, 400000, 800000, 3200000 };
+#endif
+
+	if (fper->numerator > 0)
+		fpm = (fper->denominator*60)/fper->numerator;
+	else
+		fpm = 0xffffffff;
+	fpm_max = (xclk*60)/clks_per_frame[isize];
+	if (fpm_max == 0)
+		fpm_max = 1;
+	if (fpm > fpm_max)
+		fpm = fpm_max;
+	if (fpm == 0)
+		fpm = 1;
+	divisor = fpm_max/fpm;
+	if (divisor > divisor_max)
+		divisor = divisor_max;
+	fper->numerator = divisor*60;
+	fper->denominator = fpm_max;
+
+	/* try to reduce the fraction */
+	while (!(fper->denominator % 5) && !(fper->numerator % 5)) {
+		fper->numerator /= 5;
+		fper->denominator /= 5;
+	}
+	while (!(fper->denominator % 3) && !(fper->numerator % 3)) {
+		fper->numerator /= 3;
+		fper->denominator /= 3;
+	}
+	while (!(fper->denominator % 2) && !(fper->numerator % 2)) {
+		fper->numerator /= 2;
+		fper->denominator /= 2;
+	}
+	if (fper->numerator < fper->denominator) {
+		if (!(fper->denominator % fper->numerator)) {
+			fper->denominator /= fper->numerator;
+			fper->numerator = 1;
+		}
+	} else {
+		if (!(fper->numerator % fper->denominator)) {
+			fper->numerator /= fper->denominator;
+			fper->denominator = 1;
+		}
+	}
+
+	/* we set bit 7 in CLKRC to enable the digital PLL */
+	return (0x80 | (divisor - 1));
+}
+
+/*
+ * Find the best match for a requested image capture size.  The best match
+ * is chosen as the nearest match that has the same number or fewer pixels
+ * as the requested size, or the smallest image size if the requested size
+ * has fewer pixels than the smallest image.
+ */
+static enum image_size
+ov9640_find_size(unsigned int width, unsigned int height)
+{
+	enum image_size isize;
+	unsigned long pixels = width*height;
+
+	for (isize = QQCIF; isize < SXGA; isize++) {
+		if (ov9640_sizes[isize + 1].height *
+			ov9640_sizes[isize + 1].width > pixels)
+			return isize;
+	}
+	return SXGA;
+}
+
+/*
+ * Given the image capture format in pix, the nominal frame period in
+ * timeperframe, calculate the required xclk frequency
+ */
+static unsigned long
+ov9640sensor_calc_xclk(struct i2c_client *c)
+{
+	struct ov9640_sensor *sensor = i2c_get_clientdata(c);
+	struct v4l2_fract *timeperframe = &sensor->timeperframe;
+	struct v4l2_pix_format *pix = &sensor->pix;
+
+	unsigned long tgt_xclk;			/* target xclk */
+	unsigned long tgt_fpm;			/* target frames per minute */
+	enum image_size isize;
+
+	/*
+	 * We use arbitrary rules to select the xclk frequency.  If the
+	 * capture size is VGA and the frame rate is greater than 900
+	 * frames per minute, or if the capture size is SXGA and the
+	 * frame rate is greater than 450 frames per minutes, then the
+	 * xclk frequency will be set to 48MHz.  Otherwise, the xclk
+	 * frequency will be set to 24MHz.  If the mclk frequency is such that
+	 * the target xclk frequency is not achievable, then xclk will be set
+	 * as close as to the target as possible.
+	 */
+	tgt_fpm = (timeperframe->denominator*60)
+		/ timeperframe->numerator;
+	tgt_xclk = OV9640_XCLK_NOM;
+	isize = ov9640_find_size(pix->width, pix->height);
+	switch (isize) {
+	case SXGA:
+		if (tgt_fpm > 450)
+			tgt_xclk = OV9640_XCLK_MAX;
+		break;
+	case VGA:
+		if (tgt_fpm > 900)
+			tgt_xclk = OV9640_XCLK_MAX;
+		break;
+	default:
+		break;
+	}
+	return tgt_xclk;
+}
+
+/*
+ * Configure the OV9640 for a specified image size, pixel format, and frame
+ * period.  xclk is the frequency (in Hz) of the xclk input to the OV9640.
+ * fper is the frame period (in seconds) expressed as a fraction.
+ * Returns zero if successful, or non-zero otherwise.
+ * The actual frame period is returned in fper.
+ */
+static int ov9640_configure(struct v4l2_int_device *s)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct v4l2_pix_format *pix = &sensor->pix;
+	struct v4l2_fract *fper = &sensor->timeperframe;
+	struct i2c_client *client = sensor->i2c_client;
+	enum image_size isize;
+	unsigned long xclk;
+
+	int err;
+	unsigned char clkrc;
+	enum pixel_format pfmt = YUV;
+
+	switch (pix->pixelformat) {
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB565X:
+		pfmt = RGB565;
+		break;
+	case V4L2_PIX_FMT_RGB555:
+	case V4L2_PIX_FMT_RGB555X:
+		pfmt = RGB555;
+		break;
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_UYVY:
+	default:
+		pfmt = YUV;
+	}
+
+	xclk = ov9640sensor_calc_xclk(client);
+
+	isize = ov9640_find_size(pix->width, pix->height);
+
+	/* common register initialization */
+	err = ov9640_write_regs(client, sensor->pdata->default_regs);
+	if (err)
+		return err;
+
+	/* configure image size and pixel format */
+	err = ov9640_write_regs(client, ov9640_reg_init[pfmt][isize]);
+	if (err)
+		return err;
+
+	/* configure frame rate */
+	clkrc = ov9640_clkrc(isize, xclk, fper);
+	err = ov9640_write_reg(client, OV9640_CLKRC, clkrc);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+/*
+ * Detect if an OV9640 is present, and if so which revision.
+ * A device is considered to be detected if the manufacturer ID (MIDH and MIDL)
+ * and the product ID (PID) registers match the expected values.
+ * Any value of the version ID (VER) register is accepted.
+ * Here are the version numbers we know about:
+ *	0x48 --> OV9640 Revision 1 or OV9640 Revision 2
+ *	0x49 --> OV9640 Revision 3
+ * Returns a negative error number if no device is detected, or the
+ * non-negative value of the version ID register if a device is detected.
+ */
+static int
+ov9640_detect(struct i2c_client *client)
+{
+	u8 midh, midl, pid, ver;
+
+	if (!client)
+		return -ENODEV;
+
+	if (ov9640_read_reg(client, OV9640_MIDH, &midh))
+		return -ENODEV;
+	if (ov9640_read_reg(client, OV9640_MIDL, &midl))
+		return -ENODEV;
+	if (ov9640_read_reg(client, OV9640_PID, &pid))
+		return -ENODEV;
+	if (ov9640_read_reg(client, OV9640_VER, &ver))
+		return -ENODEV;
+
+	if ((midh != OV9640_MIDH_MAGIC)
+		|| (midl != OV9640_MIDL_MAGIC)
+		|| (pid != OV9640_PID_MAGIC))
+		/*
+		 * We didn't read the values we expected, so
+		 * this must not be an OV9640.
+		 */
+		return -ENODEV;
+
+	return ver;
+}
+
+/*
+ * following are sensor interface functions implemented by
+ * OV9640 sensor driver.
+ */
+static int ioctl_queryctrl(struct v4l2_int_device *s,
+				struct v4l2_queryctrl *qc)
+{
+	int i;
+
+	i = find_vctrl(qc->id);
+	if (i == -EINVAL) {
+		qc->flags = V4L2_CTRL_FLAG_DISABLED;
+		return 0;
+	}
+	if (i < 0)
+		return -EINVAL;
+
+	*qc = video_control[i].qc;
+	return 0;
+}
+
+static int ioctl_g_ctrl(struct v4l2_int_device *s,
+			     struct v4l2_control *vc)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct i2c_client *client = sensor->i2c_client;
+	int i, val;
+	struct vcontrol *lvc;
+
+	i = find_vctrl(vc->id);
+	if (i < 0)
+		return -EINVAL;
+
+	lvc = &video_control[i];
+	if (ov9640_read_reg_mask(client, lvc->reg, (u8 *)&val, lvc->mask))
+		return -EIO;
+
+	val = val >> lvc->start_bit;
+	if (val >= 0) {
+		vc->value = lvc->current_value = val;
+		return 0;
+	} else
+		return val;
+}
+
+static int ioctl_s_ctrl(struct v4l2_int_device *s,
+			     struct v4l2_control *vc)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct i2c_client *client = sensor->i2c_client;
+	struct vcontrol *lvc;
+	int val = vc->value;
+	int i;
+
+	i = find_vctrl(vc->id);
+	if (i < 0)
+		return -EINVAL;
+
+	lvc = &video_control[i];
+	val = val << lvc->start_bit;
+	if (ov9640_write_reg_mask(client, lvc->reg, (u8 *)&val, (u8)lvc->mask))
+		return -EIO;
+
+	val = val >> lvc->start_bit;
+	if (val >= 0) {
+		lvc->current_value = val;
+		return 0;
+	} else
+		return val;
+}
+
+/*
+ * Implement the VIDIOC_ENUM_FMT ioctl for the CAPTURE buffer type.
+ */
+static int ioctl_enum_fmt_cap(struct v4l2_int_device *s,
+				   struct v4l2_fmtdesc *fmt)
+{
+	int index = fmt->index;
+	enum v4l2_buf_type type = fmt->type;
+
+	memset(fmt, 0, sizeof(*fmt));
+	fmt->index = index;
+	fmt->type = type;
+
+	switch (fmt->type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		if (index >= NUM_CAPTURE_FORMATS)
+			return -EINVAL;
+	break;
+	default:
+		return -EINVAL;
+	}
+
+	fmt->flags = ov9640_formats[index].flags;
+	strlcpy(fmt->description, ov9640_formats[index].description,
+					sizeof(fmt->description));
+	fmt->pixelformat = ov9640_formats[index].pixelformat;
+
+	return 0;
+}
+
+/*
+ * Implement the VIDIOC_TRY_FMT ioctl for the CAPTURE buffer type.  This
+ * ioctl is used to negotiate the image capture size and pixel format
+ * without actually making it take effect.
+ */
+static int ioctl_try_fmt_cap(struct v4l2_int_device *s,
+			     struct v4l2_format *f)
+{
+	enum image_size isize;
+	int ifmt;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+
+	isize = ov9640_find_size(pix->width, pix->height);
+	pix->width = ov9640_sizes[isize].width;
+	pix->height = ov9640_sizes[isize].height;
+	for (ifmt = 0; ifmt < NUM_CAPTURE_FORMATS; ifmt++) {
+		if (pix->pixelformat == ov9640_formats[ifmt].pixelformat)
+			break;
+	}
+	if (ifmt == NUM_CAPTURE_FORMATS)
+		ifmt = 0;
+	pix->pixelformat = ov9640_formats[ifmt].pixelformat;
+	pix->field = V4L2_FIELD_NONE;
+	pix->bytesperline = pix->width*2;
+	pix->sizeimage = pix->bytesperline*pix->height;
+	pix->priv = 0;
+	switch (pix->pixelformat) {
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_UYVY:
+	default:
+		pix->colorspace = V4L2_COLORSPACE_JPEG;
+		break;
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB565X:
+	case V4L2_PIX_FMT_RGB555:
+	case V4L2_PIX_FMT_RGB555X:
+		pix->colorspace = V4L2_COLORSPACE_SRGB;
+		break;
+	}
+	return 0;
+}
+
+static int ioctl_s_fmt_cap(struct v4l2_int_device *s,
+				struct v4l2_format *f)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct v4l2_pix_format *pix = &f->fmt.pix;
+	int rval;
+
+	rval = ioctl_try_fmt_cap(s, f);
+	if (rval)
+		return rval;
+
+	rval = ov9640_configure(s);
+
+	if (!rval)
+		sensor->pix = *pix;
+
+	return rval;
+}
+
+static int ioctl_g_fmt_cap(struct v4l2_int_device *s,
+				struct v4l2_format *f)
+{
+	struct ov9640_sensor *sensor = s->priv;
+
+	f->fmt.pix = sensor->pix;
+
+	return 0;
+}
+
+static int ioctl_g_parm(struct v4l2_int_device *s,
+			     struct v4l2_streamparm *a)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct v4l2_captureparm *cparm = &a->parm.capture;
+
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	memset(a, 0, sizeof(*a));
+	a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+	cparm->capability = V4L2_CAP_TIMEPERFRAME;
+	cparm->timeperframe = sensor->timeperframe;
+
+	return 0;
+}
+
+static int ioctl_s_parm(struct v4l2_int_device *s,
+			     struct v4l2_streamparm *a)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe;
+	struct v4l2_fract timeperframe_old;
+	int rval;
+
+	timeperframe_old = sensor->timeperframe;
+	sensor->timeperframe = *timeperframe;
+
+	rval = ov9640_configure(s);
+
+	if (rval)
+		sensor->timeperframe = timeperframe_old;
+	else
+		*timeperframe = sensor->timeperframe;
+
+	return rval;
+}
+
+static int ioctl_g_ifparm(struct v4l2_int_device *s, struct v4l2_ifparm *p)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct i2c_client *client = sensor->i2c_client;
+	u32 xclk;	/* target xclk */
+	int rval;
+
+	rval = sensor->pdata->ifparm(p);
+	if (rval)
+		return rval;
+
+	xclk = ov9640sensor_calc_xclk(client);
+
+	p->u.bt656.clock_curr = xclk;
+
+	return 0;
+}
+
+static int ioctl_s_power(struct v4l2_int_device *s, int on)
+{
+	struct ov9640_sensor *sensor = s->priv;
+
+	return sensor->pdata->power_set(on);
+}
+
+static int ioctl_init(struct v4l2_int_device *s)
+{
+	return ov9640_configure(s);
+}
+
+static int ioctl_dev_exit(struct v4l2_int_device *s)
+{
+	return 0;
+}
+
+static int ioctl_dev_init(struct v4l2_int_device *s)
+{
+	struct ov9640_sensor *sensor = s->priv;
+	struct i2c_client *c = sensor->i2c_client;
+	int err;
+
+	err = ov9640_detect(c);
+	if (err < 0) {
+		dev_err(&c->dev, "Unable to detect " DRIVER_NAME " sensor\n");
+		return err;
+	}
+
+	sensor->ver = err;
+	pr_info(DRIVER_NAME " chip version 0x%02x detected\n", sensor->ver);
+
+	return 0;
+}
+
+static struct v4l2_int_ioctl_desc ov9640_ioctl_desc[] = {
+	{ vidioc_int_dev_init_num,
+	  (v4l2_int_ioctl_func *)ioctl_dev_init },
+	{ vidioc_int_dev_exit_num,
+	  (v4l2_int_ioctl_func *)ioctl_dev_exit },
+	{ vidioc_int_s_power_num,
+	  (v4l2_int_ioctl_func *)ioctl_s_power },
+	{ vidioc_int_g_ifparm_num,
+	  (v4l2_int_ioctl_func *)ioctl_g_ifparm },
+	{ vidioc_int_init_num,
+	  (v4l2_int_ioctl_func *)ioctl_init },
+	{ vidioc_int_enum_fmt_cap_num,
+	  (v4l2_int_ioctl_func *)ioctl_enum_fmt_cap },
+	{ vidioc_int_try_fmt_cap_num,
+	  (v4l2_int_ioctl_func *)ioctl_try_fmt_cap },
+	{ vidioc_int_g_fmt_cap_num,
+	  (v4l2_int_ioctl_func *)ioctl_g_fmt_cap },
+	{ vidioc_int_s_fmt_cap_num,
+	  (v4l2_int_ioctl_func *)ioctl_s_fmt_cap },
+	{ vidioc_int_g_parm_num,
+	  (v4l2_int_ioctl_func *)ioctl_g_parm },
+	{ vidioc_int_s_parm_num,
+	  (v4l2_int_ioctl_func *)ioctl_s_parm },
+	{ vidioc_int_queryctrl_num,
+	  (v4l2_int_ioctl_func *)ioctl_queryctrl },
+	{ vidioc_int_g_ctrl_num,
+	  (v4l2_int_ioctl_func *)ioctl_g_ctrl },
+	{ vidioc_int_s_ctrl_num,
+	  (v4l2_int_ioctl_func *)ioctl_s_ctrl },
+};
+
+static struct v4l2_int_slave ov9640_slave = {
+	.ioctls		= ov9640_ioctl_desc,
+	.num_ioctls	= ARRAY_SIZE(ov9640_ioctl_desc),
+};
+
+static struct v4l2_int_device ov9640_int_device = {
+	.module	= THIS_MODULE,
+	.name	= DRIVER_NAME,
+	.priv	= &ov9640,
+	.type	= v4l2_int_type_slave,
+	.u	= {
+		.slave = &ov9640_slave,
+	},
+};
+
+static int __init
+ov9640_probe(struct i2c_client *client)
+{
+	struct ov9640_sensor *sensor = &ov9640;
+	int err;
+
+	if (i2c_get_clientdata(client))
+		return -EBUSY;
+
+	sensor->pdata = client->dev.platform_data;
+
+	if (!sensor->pdata || !sensor->pdata->default_regs) {
+		dev_err(&client->dev, "no platform data?\n");
+		return -ENODEV;
+	}
+
+	sensor->v4l2_int_device = &ov9640_int_device;
+	sensor->i2c_client = client;
+
+	i2c_set_clientdata(client, sensor);
+
+	/* Make the default capture format QCIF RGB565 */
+	sensor->pix.width = ov9640_sizes[QCIF].width;
+	sensor->pix.height = ov9640_sizes[QCIF].height;
+	sensor->pix.pixelformat = V4L2_PIX_FMT_RGB565;
+
+	err = v4l2_int_device_register(sensor->v4l2_int_device);
+	if (err)
+		i2c_set_clientdata(client, NULL);
+
+	return 0;
+}
+
+static int __exit
+ov9640_remove(struct i2c_client *client)
+{
+	struct ov9640_sensor *sensor = i2c_get_clientdata(client);
+
+	if (!client->adapter)
+		return -ENODEV;	/* our client isn't attached */
+
+	v4l2_int_device_unregister(sensor->v4l2_int_device);
+	i2c_set_clientdata(client, NULL);
+
+	return 0;
+}
+
+static struct i2c_driver ov9640sensor_i2c_driver = {
+	.driver = {
+		.name	= DRIVER_NAME,
+	},
+	.probe	= ov9640_probe,
+	.remove	= __exit_p(ov9640_remove),
+};
+
+static struct ov9640_sensor ov9640 = {
+	.timeperframe = {
+		.numerator = 1,
+		.denominator = 15,
+	},
+};
+
+static int ov9640sensor_init(void)
+{
+	int err;
+
+	err = i2c_add_driver(&ov9640sensor_i2c_driver);
+	if (err) {
+		printk(KERN_ERR "Failed to register" DRIVER_NAME ".\n");
+		return err;
+	}
+	return 0;
+}
+module_init(ov9640sensor_init);
+
+static void __exit ov9640sensor_cleanup(void)
+{
+	i2c_del_driver(&ov9640sensor_i2c_driver);
+}
+module_exit(ov9640sensor_cleanup);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("OV9640 camera sensor driver");
diff --git a/drivers/media/video/ov9640.h b/drivers/media/video/ov9640.h
new file mode 100644
index 0000000..a13fba9
--- /dev/null
+++ b/drivers/media/video/ov9640.h
@@ -0,0 +1,194 @@
+/*
+ * drivers/media/video/ov9640.h
+ *
+ * Register definitions for the OmniVision OV9640 CameraChip.
+ *
+ * Author: Andy Lowe (source@mvista.com)
+ *
+ * Copyright (C) 2004 MontaVista Software, Inc.
+ * Copyright (C) 2004 Texas Instruments.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#ifndef OV9640_H
+#define OV9640_H
+
+#define OV9640_I2C_ADDR		0x30
+
+/* define register offsets for the OV9640 sensor chip */
+#define OV9640_GAIN		0x00
+#define OV9640_BLUE		0x01
+#define OV9640_RED		0x02
+#define OV9640_VREF		0x03
+#define OV9640_COM1		0x04
+#define OV9640_BAVE		0x05
+#define OV9640_GEAVE		0x06
+#define OV9640_RAVE		0x08
+#define OV9640_COM2		0x09
+#define OV9640_PID		0x0A
+#define OV9640_VER		0x0B
+#define OV9640_COM3		0x0C
+#define OV9640_COM4		0x0D
+#define OV9640_COM5		0x0E
+#define OV9640_COM6		0x0F
+#define OV9640_AECH		0x10
+#define OV9640_CLKRC		0x11
+#define OV9640_COM7		0x12
+#define OV9640_COM8		0x13
+#define OV9640_COM9		0x14
+#define OV9640_COM10		0x15
+#define OV9640_HSTRT		0x17
+#define OV9640_HSTOP		0x18
+#define OV9640_VSTRT		0x19
+#define OV9640_VSTOP		0x1A
+#define OV9640_PSHFT		0x1B
+#define OV9640_MIDH		0x1C
+#define OV9640_MIDL		0x1D
+#define OV9640_MVFP		0x1E
+#define OV9640_LAEC		0x1F
+#define OV9640_BOS		0x20
+#define OV9640_GBOS		0x21
+#define OV9640_GROS		0x22
+#define OV9640_ROS		0x23
+#define OV9640_AEW		0x24
+#define OV9640_AEB		0x25
+#define OV9640_VPT		0x26
+#define OV9640_BBIAS		0x27
+#define OV9640_GBBIAS		0x28
+#define OV9640_EXHCH		0x2A
+#define OV9640_EXHCL		0x2B
+#define OV9640_RBIAS		0x2C
+#define OV9640_ADVFL		0x2D
+#define OV9640_ADVFH		0x2E
+#define OV9640_YAVE		0x2F
+#define OV9640_HSYST		0x30
+#define OV9640_HSYEN		0x31
+#define OV9640_HREF		0x32
+#define OV9640_CHLF		0x33
+#define OV9640_ARBLM		0x34
+#define OV9640_ADC		0x37
+#define OV9640_ACOM		0x38
+#define OV9640_OFON		0x39
+#define OV9640_TSLB		0x3A
+#define OV9640_COM11		0x3B
+#define OV9640_COM12		0x3C
+#define OV9640_COM13		0x3D
+#define OV9640_COM14		0x3E
+#define OV9640_EDGE		0x3F
+#define OV9640_COM15		0x40
+#define OV9640_COM16		0x41
+#define OV9640_COM17		0x42
+#define OV9640_MTX1		0x4F
+#define OV9640_MTX2		0x50
+#define OV9640_MTX3		0x51
+#define OV9640_MTX4		0x52
+#define OV9640_MTX5		0x53
+#define OV9640_MTX6		0x54
+#define OV9640_MTX7		0x55
+#define OV9640_MTX8		0x56
+#define OV9640_MTX9		0x57
+#define OV9640_MTXS		0x58
+#define OV9640_LCC1		0x62
+#define OV9640_LCC2		0x63
+#define OV9640_LCC3		0x64
+#define OV9640_LCC4		0x65
+#define OV9640_LCC5		0x66
+#define OV9640_MANU		0x67
+#define OV9640_MANV		0x68
+#define OV9640_HV		0x69
+#define OV9640_MBD		0x6A
+#define OV9640_DBLV		0x6B
+#define OV9640_GSP1		0x6C
+#define OV9640_GSP2		0x6D
+#define OV9640_GSP3		0x6E
+#define OV9640_GSP4		0x6F
+#define OV9640_GSP5		0x70
+#define OV9640_GSP6		0x71
+#define OV9640_GSP7		0x72
+#define OV9640_GSP8		0x73
+#define OV9640_GSP9		0x74
+#define OV9640_GSP10		0x75
+#define OV9640_GSP11		0x76
+#define OV9640_GSP12		0x77
+#define OV9640_GSP13		0x78
+#define OV9640_GSP14		0x79
+#define OV9640_GSP15		0x7A
+#define OV9640_GSP16		0x7B
+#define OV9640_GST1		0x7C
+#define OV9640_GST2		0x7D
+#define OV9640_GST3		0x7E
+#define OV9640_GST4		0x7F
+#define OV9640_GST5		0x80
+#define OV9640_GST6		0x81
+#define OV9640_GST7		0x82
+#define OV9640_GST8		0x83
+#define OV9640_GST9		0x84
+#define OV9640_GST10		0x85
+#define OV9640_GST11		0x86
+#define OV9640_GST12		0x87
+#define OV9640_GST13		0x88
+#define OV9640_GST14		0x89
+#define OV9640_GST15		0x8A
+
+#define OV9640_NUM_REGS		(OV9640_GST15 + 1)
+
+#define OV9640_PID_MAGIC	0x96	/* high byte of product ID number */
+#define OV9640_VER_REV2		0x48	/* low byte of product ID number */
+#define OV9640_VER_REV3		0x49	/* low byte of product ID number */
+#define OV9640_MIDH_MAGIC	0x7F	/* high byte of mfg ID */
+#define OV9640_MIDL_MAGIC	0xA2	/* low byte of mfg ID */
+
+#define OV9640_REG_TERM 0xFF	/* terminating list entry for reg */
+#define OV9640_VAL_TERM 0xFF	/* terminating list entry for val */
+
+/*
+ * The nominal xclk input frequency of the OV9640 is 24MHz, maximum
+ * frequency is 48MHz, and minimum frequency is 10MHz.
+ */
+#define OV9640_XCLK_MIN 10000000
+#define OV9640_XCLK_MAX 48000000
+#define OV9640_XCLK_NOM 24000000
+
+/* define a structure for ov9640 register initialization values */
+struct ov9640_reg {
+	unsigned char reg;
+	unsigned char val;
+};
+
+enum image_size { QQCIF, QQVGA, QCIF, QVGA, CIF, VGA, SXGA };
+enum pixel_format { YUV, RGB565, RGB555 };
+
+#define NUM_IMAGE_SIZES 7
+#define NUM_PIXEL_FORMATS 3
+
+struct capture_size {
+	unsigned long width;
+	unsigned long height;
+};
+
+struct ov9640_platform_data {
+	/* Set power state, zero is off, non-zero is on. */
+	int (*power_set)(int power);
+	/* Default registers written after power-on or reset. */
+	const struct ov9640_reg *default_regs;
+	int (*ifparm)(struct v4l2_ifparm *p);
+};
+
+/*
+ * Array of image sizes supported by OV9640.  These must be ordered from
+ * smallest image size to largest.
+ */
+const static struct capture_size ov9640_sizes[] = {
+	{   88,  72 },	/* QQCIF */
+	{  160, 120 },	/* QQVGA */
+	{  176, 144 },	/* QCIF */
+	{  320, 240 },	/* QVGA */
+	{  352, 288 },	/* CIF */
+	{  640, 480 },	/* VGA */
+	{ 1280, 960 },	/* SXGA */
+};
+
+#endif /* ifndef OV9640_H */
-- 
1.6.0.1.141.g445ca


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

* [PATCH 20/33] add omap Serial Trace Interface driver
       [not found]                                     ` <1220116593-862-20-git-send-email-me@felipebalbi.com>
@ 2008-08-30 17:16                                       ` Felipe Balbi
  2008-08-30 17:16                                         ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/misc/Kconfig           |   14 ++
 drivers/misc/Makefile          |    1 +
 drivers/misc/sti/Makefile      |    8 +
 drivers/misc/sti/sdti.c        |  185 +++++++++++++++++
 drivers/misc/sti/sti-console.c |  189 ++++++++++++++++++
 drivers/misc/sti/sti-fifo.c    |  117 +++++++++++
 drivers/misc/sti/sti-netlink.c |  152 ++++++++++++++
 drivers/misc/sti/sti.c         |  432 ++++++++++++++++++++++++++++++++++++++++
 8 files changed, 1098 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/sti/Makefile
 create mode 100644 drivers/misc/sti/sdti.c
 create mode 100644 drivers/misc/sti/sti-console.c
 create mode 100644 drivers/misc/sti/sti-fifo.c
 create mode 100644 drivers/misc/sti/sti-netlink.c
 create mode 100644 drivers/misc/sti/sti.c

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index a726f3b..e90dc44 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -415,6 +415,20 @@ config EEEPC_LAPTOP
 
 	  If you have an Eee PC laptop, say Y or M here.
 
+config OMAP_STI
+	bool "Serial Trace Interface support"
+	depends on ARCH_OMAP16XX || ARCH_OMAP24XX || ARCH_OMAP34XX
+	default n
+	help
+	  Serial Trace Interface. The protocols suported for OMAP1/2/3 are
+	  STI/CSTI/XTIv2 correspondingly.
+
+config OMAP_STI_CONSOLE
+	bool "STI console support"
+	depends on OMAP_STI
+	help
+	  This enables a console driver by way of STI/XTI.
+
 config ENCLOSURE_SERVICES
 	tristate "Enclosure Services"
 	default n
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c6c13f6..b6167e7 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,6 +5,7 @@ obj- := misc.o	# Dummy rule to force built-in.o to be made
 
 obj-$(CONFIG_IBM_ASM)		+= ibmasm/
 obj-$(CONFIG_HDPU_FEATURES)	+= hdpuftrs/
+obj-$(CONFIG_OMAP_STI)		+= sti/
 obj-$(CONFIG_ASUS_LAPTOP)	+= asus-laptop.o
 obj-$(CONFIG_EEEPC_LAPTOP)	+= eeepc-laptop.o
 obj-$(CONFIG_MSI_LAPTOP)	+= msi-laptop.o
diff --git a/drivers/misc/sti/Makefile b/drivers/misc/sti/Makefile
new file mode 100644
index 0000000..aee30bd
--- /dev/null
+++ b/drivers/misc/sti/Makefile
@@ -0,0 +1,8 @@
+ifeq ($(CONFIG_ARCH_OMAP3),y)
+obj-$(CONFIG_OMAP_STI)	+= sdti.o
+else
+obj-$(CONFIG_OMAP_STI)	+= sti.o sti-fifo.o
+obj-$(CONFIG_NET)	+= sti-netlink.o
+endif
+
+obj-$(CONFIG_OMAP_STI_CONSOLE)	+= sti-console.o
diff --git a/drivers/misc/sti/sdti.c b/drivers/misc/sti/sdti.c
new file mode 100644
index 0000000..c35821d
--- /dev/null
+++ b/drivers/misc/sti/sdti.c
@@ -0,0 +1,185 @@
+/*
+ * Support functions for OMAP3 SDTI (Serial Debug Tracing Interface)
+ *
+ * Copyright (C) 2008 Nokia Corporation
+ * Written by: Roman Tereshonkov <roman.tereshonkov@nokia.com>
+ *
+ * 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.
+ */
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <mach/sti.h>
+#include <asm/byteorder.h>
+#include <asm/io.h>
+
+#define SDTI_REVISION		0x000
+#define SDTI_SYSCONFIG		0x010
+#define SDTI_SYSSTATUS		0x014
+#define SDTI_WINCTRL		0x024
+#define SDTI_SCONFIG		0x028
+#define SDTI_TESTCTRL		0x02C
+#define SDTI_LOCK_ACCESS	0xFB0
+
+#define CPU1_TRACE_EN		0x01
+#define CPU2_TRACE_EN		0x02
+
+static struct clk *sdti_ck;
+unsigned long sti_base, sti_channel_base;
+static DEFINE_SPINLOCK(sdti_lock);
+
+void omap_sti_channel_write_trace(int len, int id, void *data,
+				unsigned int channel)
+{
+	const u8 *tpntr = data;
+
+	spin_lock_irq(&sdti_lock);
+
+	sti_channel_writeb(id, channel);
+	while (len--)
+		sti_channel_writeb(*tpntr++, channel);
+	sti_channel_flush(channel);
+
+	spin_unlock_irq(&sdti_lock);
+}
+EXPORT_SYMBOL(omap_sti_channel_write_trace);
+
+static void omap_sdti_reset(void)
+{
+	int i;
+
+	sti_writel(0x02, SDTI_SYSCONFIG);
+
+	for (i = 0; i < 10000; i++)
+		if (sti_readl(SDTI_SYSSTATUS) & 1)
+			break;
+	if (i == 10000)
+		printk(KERN_WARNING "XTI: no real reset\n");
+}
+
+static int __init omap_sdti_init(void)
+{
+	char buf[64];
+	int i;
+
+	sdti_ck = clk_get(NULL, "emu_per_alwon_ck");
+	if (IS_ERR(sdti_ck)) {
+		printk(KERN_ERR "Cannot get clk emu_per_alwon_ck\n");
+		return PTR_ERR(sdti_ck);
+	}
+	clk_enable(sdti_ck);
+
+	omap_sdti_reset();
+	sti_writel(0xC5ACCE55, SDTI_LOCK_ACCESS);
+
+	/* Claim SDTI */
+	sti_writel(1 << 30, SDTI_WINCTRL);
+	i = sti_readl(SDTI_WINCTRL);
+	if (!(i & (1 << 30)))
+		printk(KERN_WARNING "SDTI: cannot claim SDTI\n");
+
+	/* 4 bits dual, fclk/3 */
+	sti_writel(0x43, SDTI_SCONFIG);
+
+	/* CPU2 trace enable */
+	sti_writel(i | CPU2_TRACE_EN, SDTI_WINCTRL);
+	i = sti_readl(SDTI_WINCTRL);
+
+	/* Enable SDTI */
+	sti_writel((1 << 31) | (i & 0x3FFFFFFF), SDTI_WINCTRL);
+
+	i = sti_readl(SDTI_REVISION);
+	snprintf(buf, sizeof(buf), "OMAP SDTI support loaded (HW v%u.%u)\n",
+		(i >> 4) & 0x0f, i & 0x0f);
+	printk(KERN_INFO "%s", buf);
+	omap_sti_channel_write_trace(strlen(buf), 0xc3, buf, 239);
+
+	return 0;
+}
+
+static void omap_sdti_exit(void)
+{
+	sti_writel(0, SDTI_WINCTRL);
+	clk_disable(sdti_ck);
+	clk_put(sdti_ck);
+}
+
+static int __devinit omap_sdti_probe(struct platform_device *pdev)
+{
+	struct resource *res, *cres;
+	unsigned int size;
+
+	if (pdev->num_resources != 2) {
+		dev_err(&pdev->dev, "invalid number of resources: %d\n",
+			pdev->num_resources);
+		return -ENODEV;
+	}
+
+	/* SDTI base */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (unlikely(!res)) {
+		dev_err(&pdev->dev, "invalid mem resource\n");
+		return -ENODEV;
+	}
+
+	/* Channel base */
+	cres = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (unlikely(!cres)) {
+		dev_err(&pdev->dev, "invalid channel mem resource\n");
+		return -ENODEV;
+	}
+
+	size = res->end - res->start;
+	sti_base = (unsigned long)ioremap(res->start, size);
+	if (unlikely(!sti_base))
+		return -ENODEV;
+
+	size = cres->end - cres->start;
+	sti_channel_base = (unsigned long)ioremap(cres->start, size);
+	if (unlikely(!sti_channel_base)) {
+		iounmap((void *)sti_base);
+		return -ENODEV;
+	}
+
+	return omap_sdti_init();
+}
+
+static int __devexit omap_sdti_remove(struct platform_device *pdev)
+{
+	iounmap((void *)sti_channel_base);
+	iounmap((void *)sti_base);
+	omap_sdti_exit();
+
+	return 0;
+}
+
+static struct platform_driver omap_sdti_driver = {
+	.probe		= omap_sdti_probe,
+	.remove		= __devexit_p(omap_sdti_remove),
+	.driver		= {
+		.name	= "sti",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init omap_sdti_module_init(void)
+{
+	return platform_driver_register(&omap_sdti_driver);
+}
+
+static void __exit omap_sdti_module_exit(void)
+{
+	platform_driver_unregister(&omap_sdti_driver);
+}
+subsys_initcall(omap_sdti_module_init);
+module_exit(omap_sdti_module_exit);
+
+MODULE_AUTHOR("Roman Tereshonkov");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/sti/sti-console.c b/drivers/misc/sti/sti-console.c
new file mode 100644
index 0000000..2062d23
--- /dev/null
+++ b/drivers/misc/sti/sti-console.c
@@ -0,0 +1,189 @@
+/*
+ * Console support for OMAP STI/XTI
+ *
+ * Copyright (C) 2004, 2005, 2006 Nokia Corporation
+ * Written by: Paul Mundt <paul.mundt@nokia.com>
+ *
+ * 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.
+ */
+#include <linux/console.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <mach/sti.h>
+#include <mach/board.h>
+
+#define DRV_NAME "sticon"
+
+static struct tty_driver *tty_driver;
+static DEFINE_SPINLOCK(sti_console_lock);
+static unsigned int sti_console_channel = -1;
+static int sti_line_done = -1;
+
+/*
+ * Write a string to any channel (including terminating NULL)
+ * Returns number of characters written.
+ */
+static int sti_channel_puts(const char *string, unsigned int channel, int len)
+{
+	int count = 0;
+
+	/*
+	 * sti_line_done is needed to determine when we have reached the
+	 * end of the line. write() has a tendency to hand us small
+	 * strings which otherwise end up creating newlines.. we need to
+	 * keep the channel open and in append mode until the line has
+	 * been terminated.
+	 */
+	if (sti_line_done != 0) {
+#ifdef __LITTLE_ENDIAN
+		sti_channel_writeb(0xc3, channel);
+#else
+		sti_channel_writeb(0xc0, channel);
+#endif
+		xchg(&sti_line_done, 0);
+	}
+
+	while (*string && count != len) {
+		char c = *string++;
+
+		count++;
+
+		if (c == '\n') {
+			xchg(&sti_line_done, 1);
+			sti_channel_writeb(0, channel);
+			break;
+		} else
+			sti_channel_writeb(c, channel);
+	}
+
+	if (sti_line_done)
+		sti_channel_flush(channel);
+
+	return count;
+}
+
+static int sti_tty_open(struct tty_struct *tty, struct file *filp)
+{
+	return 0;
+}
+
+static int sti_tty_write(struct tty_struct *tty,
+			 const unsigned char *buf, int len)
+{
+	unsigned long flags;
+	int bytes;
+
+	spin_lock_irqsave(&sti_console_lock, flags);
+	bytes = sti_channel_puts(buf, sti_console_channel, len);
+	spin_unlock_irqrestore(&sti_console_lock, flags);
+
+	return bytes;
+}
+
+static int sti_tty_write_room(struct tty_struct *tty)
+{
+	return 0x100000;
+}
+
+static int sti_tty_chars_in_buffer(struct tty_struct *tty)
+{
+	return 0;
+}
+
+static struct tty_operations sti_tty_ops = {
+	.open			= sti_tty_open,
+	.write			= sti_tty_write,
+	.write_room		= sti_tty_write_room,
+	.chars_in_buffer	= sti_tty_chars_in_buffer,
+};
+
+static void sti_console_write(struct console *c, const char *s, unsigned n)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&sti_console_lock, flags);
+	sti_channel_puts(s, sti_console_channel, n);
+	spin_unlock_irqrestore(&sti_console_lock, flags);
+}
+
+static struct tty_driver *sti_console_device(struct console *c, int *index)
+{
+	*index = c->index;
+	return tty_driver;
+}
+
+static int sti_console_setup(struct console *c, char *opts)
+{
+	return 0;
+}
+
+static struct console sti_console = {
+	.name		= DRV_NAME,
+	.write		= sti_console_write,
+	.device		= sti_console_device,
+	.setup		= sti_console_setup,
+	.flags		= CON_PRINTBUFFER | CON_ENABLED,
+	.index		= -1,
+};
+
+static int __init sti_console_init(void)
+{
+	const struct omap_sti_console_config *info;
+
+	info = omap_get_config(OMAP_TAG_STI_CONSOLE,
+			       struct omap_sti_console_config);
+	if (info && info->enable) {
+		add_preferred_console(DRV_NAME, 0, NULL);
+
+		sti_console_channel = info->channel;
+	}
+
+	if (unlikely(sti_console_channel == -1))
+		return -EINVAL;
+
+	register_console(&sti_console);
+
+	return 0;
+}
+__initcall(sti_console_init);
+
+static int __init sti_tty_init(void)
+{
+	struct tty_driver *tty;
+	int ret;
+
+	tty = alloc_tty_driver(1);
+	if (!tty)
+		return -ENOMEM;
+
+	tty->name		= DRV_NAME;
+	tty->driver_name	= DRV_NAME;
+	tty->major		= 0;	/* dynamic major */
+	tty->minor_start	= 0;
+	tty->type		= TTY_DRIVER_TYPE_SYSTEM;
+	tty->subtype		= SYSTEM_TYPE_SYSCONS;
+	tty->init_termios	= tty_std_termios;
+
+	tty_set_operations(tty, &sti_tty_ops);
+
+	ret = tty_register_driver(tty);
+	if (ret) {
+		put_tty_driver(tty);
+		return ret;
+	}
+
+	tty_driver = tty;
+	return 0;
+}
+late_initcall(sti_tty_init);
+
+module_param(sti_console_channel, uint, 0);
+MODULE_PARM_DESC(sti_console_channel, "STI console channel");
+MODULE_AUTHOR("Paul Mundt");
+MODULE_DESCRIPTION("OMAP STI console support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/sti/sti-fifo.c b/drivers/misc/sti/sti-fifo.c
new file mode 100644
index 0000000..1ea5b18
--- /dev/null
+++ b/drivers/misc/sti/sti-fifo.c
@@ -0,0 +1,117 @@
+/*
+ * STI RX FIFO Support
+ *
+ * Copyright (C) 2005, 2006 Nokia Corporation
+ * Written by:  Paul Mundt <paul.mundt@nokia.com> and
+ *		Roman Tereshonkov <roman.tereshonkov@nokia.com>
+ *
+ * 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.
+ */
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <mach/sti.h>
+
+#define STI_READ_BUFFER_SIZE	1024
+#define sti_buf_pos(pos)	((sti_crb->bufpos + (pos)) % \
+				 STI_READ_BUFFER_SIZE)
+
+static struct sti_cycle_buffer {
+	int bufpos;
+	int datalen;
+	unsigned char *buf;
+} *sti_crb;
+
+/**
+ * sti_read_packet - STI read packet (read an entire STI packet)
+ * @buf: Buffer to store the packet.
+ * @maxsize: Maximum size requested.
+ *
+ * This reads in a single completed STI packet from the RX FIFOs and
+ * places it in @buf for further processing.
+ *
+ * The return value is < 0 on error, and >= 0 for the number of bytes
+ * actually read. As per the STI specification, we require a 0xC1 to
+ * indicate the end of the packet, and we don't return the packet until
+ * we've read the entire thing in.
+ *
+ * Due to the size of the FIFOs, it's unrealistic to constantly drain
+ * this for 1 or 2 bytes at a time, so we assemble it here and return
+ * the whole thing.
+ */
+int sti_read_packet(unsigned char *buf, int maxsize)
+{
+	unsigned int pos;
+
+	if (unlikely(!buf))
+		return -EINVAL;
+	if (!sti_crb->datalen)
+		return 0;
+
+	pos = sti_buf_pos(sti_crb->datalen - 1);
+	/* End of packet */
+	if (sti_crb->buf[pos] == 0xC1) {
+		int i;
+
+		for (i = 0; i < sti_crb->datalen && i < maxsize; i++) {
+			pos = sti_buf_pos(i);
+			buf[i] = sti_crb->buf[pos];
+		}
+
+		sti_crb->bufpos = sti_buf_pos(i);
+		sti_crb->datalen -= i;
+
+		return i;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(sti_read_packet);
+
+static void sti_fifo_irq(unsigned long arg)
+{
+	/* If there is data read it */
+	while (!(sti_readl(STI_RX_STATUS) & STI_RXFIFO_EMPTY)) {
+		unsigned int pos = sti_buf_pos(sti_crb->datalen);
+
+		sti_crb->buf[pos] = sti_readl(STI_RX_DR);
+		sti_crb->datalen++;
+	}
+
+	sti_ack_irq(STI_RX_INT);
+}
+
+static int __init sti_fifo_init(void)
+{
+	unsigned int size;
+	int ret;
+
+	size = sizeof(struct sti_cycle_buffer) + STI_READ_BUFFER_SIZE;
+	sti_crb = kmalloc(size, GFP_KERNEL);
+	if (!sti_crb)
+		return -ENOMEM;
+
+	sti_crb->bufpos = sti_crb->datalen = 0;
+	sti_crb->buf = (unsigned char *)(sti_crb + sizeof(*sti_crb));
+
+	ret = sti_request_irq(STI_RX_INT, sti_fifo_irq, 0);
+	if (ret != 0)
+		kfree(sti_crb);
+
+	return ret;
+}
+
+static void __exit sti_fifo_exit(void)
+{
+	sti_free_irq(STI_RX_INT);
+	kfree(sti_crb);
+}
+
+module_init(sti_fifo_init);
+module_exit(sti_fifo_exit);
+
+MODULE_AUTHOR("Paul Mundt, Roman Tereshonkov");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/sti/sti-netlink.c b/drivers/misc/sti/sti-netlink.c
new file mode 100644
index 0000000..f1f8a48
--- /dev/null
+++ b/drivers/misc/sti/sti-netlink.c
@@ -0,0 +1,152 @@
+/*
+ * OMAP STI/XTI communications interface via netlink socket.
+ *
+ * Copyright (C) 2004, 2005, 2006 Nokia Corporation
+ * Written by: Paul Mundt <paul.mundt@nokia.com>
+ *
+ * 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.
+ */
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/netlink.h>
+#include <linux/socket.h>
+#include <linux/skbuff.h>
+#include <linux/mutex.h>
+#include <net/sock.h>
+#include <mach/sti.h>
+
+static struct sock *sti_sock;
+static DEFINE_MUTEX(sti_netlink_mutex);
+
+enum {
+	STI_READ,
+	STI_WRITE,
+};
+
+static int sti_netlink_read(int pid, int seq, void *payload, int size)
+{
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	int ret, len = NLMSG_SPACE(size);
+	unsigned char *tail;
+
+	skb = alloc_skb(len, GFP_KERNEL);
+	if (!skb)
+		return -ENOMEM;
+
+	tail = skb->tail;
+	nlh = NLMSG_PUT(skb, pid, seq, STI_READ,
+			len - (sizeof(struct nlmsghdr)));
+	nlh->nlmsg_flags = 0;
+	memcpy(NLMSG_DATA(nlh), payload, size);
+	nlh->nlmsg_len = skb->tail - tail;
+
+	ret = netlink_unicast(sti_sock, skb, pid, MSG_DONTWAIT);
+	if (ret > 0)
+		ret = 0;
+
+	return ret;
+
+nlmsg_failure:
+	if (skb)
+		kfree_skb(skb);
+
+	return -EINVAL;
+}
+
+/*
+ * We abuse nlmsg_type and nlmsg_flags for our purposes.
+ *
+ * The ID is encoded into the upper 8 bits of the nlmsg_type, while the
+ * channel number is encoded into the upper 8 bits of the nlmsg_flags.
+ */
+static int sti_netlink_receive_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+	void *data;
+	u8 chan, id;
+	int size, ret = 0, len = 0;
+
+	data	= NLMSG_DATA(nlh);
+	chan	= (nlh->nlmsg_flags >> 8) & 0xff;
+	id	= (nlh->nlmsg_type  >> 8) & 0xff;
+	size	= (int)(nlh->nlmsg_len - ((char *)data - (char *)nlh));
+
+	switch (nlh->nlmsg_type & 0xff) {
+	case STI_WRITE:
+		sti_channel_write_trace(size, id, data, chan);
+		break;
+	case STI_READ:
+		data = kmalloc(size, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+		memset(data, 0, size);
+
+		len = sti_read_packet(data, size);
+		ret = sti_netlink_read(NETLINK_CB(skb).pid, nlh->nlmsg_seq,
+				       data, len);
+		kfree(data);
+		break;
+	default:
+		return -ENOTTY;
+	}
+
+	return ret;
+}
+
+static int sti_netlink_receive_skb(struct sk_buff *skb)
+{
+	while (skb->len >= NLMSG_SPACE(0)) {
+		struct nlmsghdr *nlh;
+		u32 rlen;
+		int ret;
+
+		nlh = (struct nlmsghdr *)skb->data;
+		if (nlh->nlmsg_len < sizeof(struct nlmsghdr) ||
+		    skb->len < nlh->nlmsg_len)
+			break;
+
+		rlen = NLMSG_ALIGN(nlh->nlmsg_len);
+		if (rlen > skb->len)
+			rlen = skb->len;
+
+		ret = sti_netlink_receive_msg(skb, nlh);
+		if (ret)
+			netlink_ack(skb, nlh, -ret);
+		else if (nlh->nlmsg_flags & NLM_F_ACK)
+			netlink_ack(skb, nlh, 0);
+
+		skb_pull(skb, rlen);
+	}
+
+	return 0;
+}
+
+static void sti_netlink_receive(struct sk_buff *skb)
+{
+	if (!mutex_trylock(&sti_netlink_mutex))
+		return;
+
+	sti_netlink_receive_skb(skb);
+	mutex_unlock(&sti_netlink_mutex);
+}
+
+static int __init sti_netlink_init(void)
+{
+	sti_sock = netlink_kernel_create(&init_net, NETLINK_USERSOCK, 0,
+					 sti_netlink_receive, NULL,
+					 THIS_MODULE);
+	if (!sti_sock) {
+		printk(KERN_ERR "STI: Failed to create netlink socket\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+module_init(sti_netlink_init);
+
+MODULE_AUTHOR("Paul Mundt");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("STI netlink-driven communications interface");
diff --git a/drivers/misc/sti/sti.c b/drivers/misc/sti/sti.c
new file mode 100644
index 0000000..28fac62
--- /dev/null
+++ b/drivers/misc/sti/sti.c
@@ -0,0 +1,432 @@
+/*
+ * Support functions for OMAP STI/XTI (Serial Tracing Interface)
+ *
+ * Copyright (C) 2004, 2005, 2006 Nokia Corporation
+ * Written by: Paul Mundt <paul.mundt@nokia.com>
+ *
+ * STI initialization code and channel handling
+ * from Juha Yrjölä <juha.yrjola@nokia.com>.
+ *
+ * XTI initialization
+ * from Roman Tereshonkov <roman.tereshonkov@nokia.com>.
+ *
+ * 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.
+ */
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <mach/sti.h>
+#include <asm/byteorder.h>
+
+static struct clk *sti_ck;
+unsigned long sti_base, sti_channel_base;
+static unsigned long sti_kern_mask = STIEn;
+static unsigned long sti_irq_mask = STI_IRQSTATUS_MASK;
+static DEFINE_SPINLOCK(sti_lock);
+
+static struct sti_irqdesc {
+	irqreturn_t (*func)(unsigned long);
+	unsigned long data;
+} ____cacheline_aligned sti_irq_desc[STI_NR_IRQS];
+
+void sti_channel_write_trace(int len, int id, void *data, unsigned int channel)
+{
+	const u8 *tpntr = data;
+
+	sti_channel_writeb(id, channel);
+
+	if (cpu_is_omap16xx())
+		/* Check u32 boundary */
+		if (!((u32)data & (STI_PERCHANNEL_SIZE - 1)) &&
+		     (len >= STI_PERCHANNEL_SIZE)) {
+			const u32 *asrc = data;
+
+			do {
+				sti_channel_writel(cpu_to_be32(*asrc++),
+						   channel);
+				len -= STI_PERCHANNEL_SIZE;
+			} while (len >= STI_PERCHANNEL_SIZE);
+
+			tpntr = (const u8 *)asrc;
+		}
+
+	while (len--)
+		sti_channel_writeb(*tpntr++, channel);
+
+	sti_channel_flush(channel);
+}
+EXPORT_SYMBOL(sti_channel_write_trace);
+
+void sti_enable_irq(unsigned int id)
+{
+	spin_lock_irq(&sti_lock);
+	sti_writel(1 << id, STI_IRQSETEN);
+	spin_unlock_irq(&sti_lock);
+}
+EXPORT_SYMBOL(sti_enable_irq);
+
+void sti_disable_irq(unsigned int id)
+{
+	spin_lock_irq(&sti_lock);
+
+	if (cpu_is_omap16xx())
+		sti_writel(1 << id, STI_IRQCLREN);
+	else if (cpu_is_omap24xx())
+		sti_writel(sti_readl(STI_IRQSETEN) & ~(1 << id), STI_IRQSETEN);
+	else
+		BUG();
+
+	spin_unlock_irq(&sti_lock);
+}
+EXPORT_SYMBOL(sti_disable_irq);
+
+void sti_ack_irq(unsigned int id)
+{
+	/* Even though the clear state is 0, we have to write 1 to clear */
+	sti_writel(1 << id, STI_IRQSTATUS);
+}
+EXPORT_SYMBOL(sti_ack_irq);
+
+int sti_request_irq(unsigned int irq, void *handler, unsigned long arg)
+{
+	struct sti_irqdesc *desc;
+
+	if (unlikely(!handler || irq > STI_NR_IRQS))
+		return -EINVAL;
+
+	desc = sti_irq_desc + irq;
+	if (unlikely(desc->func)) {
+		printk(KERN_WARNING "STI: Attempting to request in-use IRQ "
+				    "%d, consider fixing your code..\n", irq);
+		return -EBUSY;
+	}
+
+	desc->func = handler;
+	desc->data = arg;
+
+	sti_enable_irq(irq);
+	return 0;
+}
+EXPORT_SYMBOL(sti_request_irq);
+
+void sti_free_irq(unsigned int irq)
+{
+	struct sti_irqdesc *desc = sti_irq_desc + irq;
+
+	if (unlikely(irq > STI_NR_IRQS))
+		return;
+
+	sti_disable_irq(irq);
+
+	desc->func = NULL;
+	desc->data = 0;
+}
+EXPORT_SYMBOL(sti_free_irq);
+
+/*
+ * This is a bit heavy, so normally we would defer this to a tasklet.
+ * Unfortunately tasklets are too slow for the RX FIFO interrupt (and
+ * possibly some others), so we just do the irqdesc walking here.
+ */
+static irqreturn_t sti_interrupt(int irq, void *dev_id)
+{
+	int ret = IRQ_NONE;
+	u16 status;
+	int i;
+
+	status = sti_readl(STI_IRQSTATUS) & sti_irq_mask;
+
+	for (i = 0; status; i++) {
+		struct sti_irqdesc *desc = sti_irq_desc + i;
+		u16 id = 1 << i;
+
+		if (!(status & id))
+			continue;
+
+		if (likely(desc && desc->func))
+			ret |= desc->func(desc->data);
+		if (unlikely(ret == IRQ_NONE)) {
+			printk("STI: spurious interrupt (id %d)\n", id);
+			sti_disable_irq(i);
+			sti_ack_irq(i);
+			ret = IRQ_HANDLED;
+		}
+
+		status &= ~id;
+	}
+
+	return IRQ_RETVAL(ret);
+}
+
+static void omap_sti_reset(void)
+{
+	int i;
+
+	/* Reset STI module */
+	sti_writel(0x02, STI_SYSCONFIG);
+
+	/* Wait a while for the STI module to complete its reset */
+	for (i = 0; i < 10000; i++)
+		if (sti_readl(STI_SYSSTATUS) & 1)
+			break;
+}
+
+static int __init sti_init(void)
+{
+	char buf[64];
+	int i;
+
+	if (cpu_is_omap16xx()) {
+		/* Release ARM Rhea buses peripherals enable */
+		sti_writel(sti_readl(ARM_RSTCT2) | 0x0001, ARM_RSTCT2);
+
+		/* Enable TC1_CK (functional clock) */
+		sti_ck = clk_get(NULL, "tc1_ck");
+	} else if (cpu_is_omap24xx())
+		/* Enable emulation tools clock */
+		sti_ck = clk_get(NULL, "emul_ck");
+
+	if (IS_ERR(sti_ck))
+		return PTR_ERR(sti_ck);
+
+	clk_enable(sti_ck);
+
+	/* Reset STI module */
+	omap_sti_reset();
+
+	/* Enable STI */
+	sti_trace_enable(MPUCmdEn);
+
+	/* Change to custom serial protocol */
+	sti_writel(0x01, STI_SERIAL_CFG);
+
+	/* Set STI clock control register to normal mode */
+	sti_writel(0x00, STI_CLK_CTRL);
+
+	i = sti_readl(STI_REVISION);
+	snprintf(buf, sizeof(buf), "OMAP STI support loaded (HW v%u.%u)\n",
+	        (i >> 4) & 0x0f, i & 0x0f);
+	printk(KERN_INFO "%s", buf);
+
+	sti_channel_write_trace(strlen(buf), 0xc3, buf, 239);
+
+	return 0;
+}
+
+static void sti_exit(void)
+{
+	u32 tmp;
+
+	/*
+	 * This should have already been done by reset, but we switch off
+	 * STI entirely just for added sanity..
+	 */
+	tmp = sti_readl(STI_ER);
+	tmp &= ~STIEn;
+	sti_writel(tmp, STI_ER);
+
+	clk_disable(sti_ck);
+	clk_put(sti_ck);
+}
+
+static void __sti_trace_enable(int event)
+{
+	u32 tmp;
+
+	tmp = sti_readl(STI_ER);
+	tmp |= sti_kern_mask | event;
+	sti_writel(tmp, STI_ER);
+}
+
+int sti_trace_enable(int event)
+{
+	spin_lock_irq(&sti_lock);
+	sti_kern_mask |= event;
+	__sti_trace_enable(event);
+	spin_unlock_irq(&sti_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(sti_trace_enable);
+
+static void __sti_trace_disable(int event)
+{
+	u32 tmp;
+
+	tmp = sti_readl(STI_DR);
+
+	if (cpu_is_omap16xx()) {
+		tmp |= event;
+		tmp &= ~sti_kern_mask;
+	} else if (cpu_is_omap24xx()) {
+		tmp &= ~event;
+		tmp |= sti_kern_mask;
+	} else
+		BUG();
+
+	sti_writel(tmp, STI_DR);
+}
+
+void sti_trace_disable(int event)
+{
+	spin_lock_irq(&sti_lock);
+	sti_kern_mask &= ~event;
+	__sti_trace_disable(event);
+	spin_unlock_irq(&sti_lock);
+}
+EXPORT_SYMBOL(sti_trace_disable);
+
+static ssize_t
+sti_trace_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "0x%08lx\n", sti_readl(STI_ER));
+}
+
+static ssize_t
+sti_trace_store(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	int evt = simple_strtoul(buf, NULL, 0);
+	int mask = ~evt;
+
+	spin_lock_irq(&sti_lock);
+	__sti_trace_disable(mask);
+	__sti_trace_enable(evt);
+	spin_unlock_irq(&sti_lock);
+
+	return count;
+}
+static DEVICE_ATTR(trace, S_IRUGO | S_IWUSR, sti_trace_show, sti_trace_store);
+
+static ssize_t
+sti_imask_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "0x%04lx\n", sti_irq_mask);
+}
+
+static ssize_t
+sti_imask_store(struct device *dev, struct device_attribute *attr,
+		const char *buf, size_t count)
+{
+	spin_lock_irq(&sti_lock);
+	sti_irq_mask = simple_strtoul(buf, NULL, 0);
+	spin_unlock_irq(&sti_lock);
+
+	return count;
+}
+static DEVICE_ATTR(imask, S_IRUGO | S_IWUSR, sti_imask_show, sti_imask_store);
+
+static int __devinit sti_probe(struct platform_device *pdev)
+{
+	struct resource *res, *cres;
+	int ret;
+
+	if (pdev->num_resources != 3) {
+		dev_err(&pdev->dev, "invalid number of resources: %d\n",
+			pdev->num_resources);
+		return -ENODEV;
+	}
+
+	/* STI base */
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (unlikely(!res)) {
+		dev_err(&pdev->dev, "invalid mem resource\n");
+		return -ENODEV;
+	}
+
+	/* Channel base */
+	cres = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (unlikely(!cres)) {
+		dev_err(&pdev->dev, "invalid channel mem resource\n");
+		return -ENODEV;
+	}
+
+	ret = device_create_file(&pdev->dev, &dev_attr_trace);
+	if (unlikely(ret != 0))
+		return ret;
+
+	ret = device_create_file(&pdev->dev, &dev_attr_imask);
+	if (unlikely(ret != 0))
+		goto err;
+
+	sti_base = io_p2v(res->start);
+
+	/*
+	 * OMAP 16xx keeps channels in a relatively sane location,
+	 * whereas 24xx maps them much further out, and so they must be
+	 * remapped.
+	 */
+	if (cpu_is_omap16xx())
+		sti_channel_base = io_p2v(cres->start);
+	else if (cpu_is_omap24xx()) {
+		unsigned int size = cres->end - cres->start;
+
+		sti_channel_base = (unsigned long)ioremap(cres->start, size);
+		if (unlikely(!sti_channel_base)) {
+			ret = -ENODEV;
+			goto err_badremap;
+		}
+	}
+
+	ret = request_irq(platform_get_irq(pdev, 0), sti_interrupt,
+			  IRQF_DISABLED, "sti", NULL);
+	if (unlikely(ret != 0))
+		goto err_badirq;
+
+	return sti_init();
+
+err_badirq:
+	iounmap((void *)sti_channel_base);
+err_badremap:
+	device_remove_file(&pdev->dev, &dev_attr_imask);
+err:
+	device_remove_file(&pdev->dev, &dev_attr_trace);
+
+	return ret;
+}
+
+static int __devexit sti_remove(struct platform_device *pdev)
+{
+	unsigned int irq = platform_get_irq(pdev, 0);
+
+	if (cpu_is_omap24xx())
+		iounmap((void *)sti_channel_base);
+
+	device_remove_file(&pdev->dev, &dev_attr_trace);
+	device_remove_file(&pdev->dev, &dev_attr_imask);
+	free_irq(irq, NULL);
+	sti_exit();
+
+	return 0;
+}
+
+static struct platform_driver sti_driver = {
+	.probe		= sti_probe,
+	.remove		= __devexit_p(sti_remove),
+	.driver		= {
+		.name	= "sti",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init sti_module_init(void)
+{
+	return platform_driver_register(&sti_driver);
+}
+
+static void __exit sti_module_exit(void)
+{
+	platform_driver_unregister(&sti_driver);
+}
+subsys_initcall(sti_module_init);
+module_exit(sti_module_exit);
+
+MODULE_AUTHOR("Paul Mundt, Juha Yrjölä, Roman Tereshonkov");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-30 17:16                                       ` [PATCH 20/33] add omap Serial Trace Interface driver Felipe Balbi
@ 2008-08-30 17:16                                         ` Felipe Balbi
  2008-08-30 17:16                                           ` [PATCH 22/33] add omap nand drivers Felipe Balbi
                                                             ` (2 more replies)
  0 siblings, 3 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/mmc/host/Kconfig      |   13 +-
 drivers/mmc/host/Makefile     |    1 +
 drivers/mmc/host/omap_hsmmc.c | 1069 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1082 insertions(+), 1 deletions(-)
 create mode 100644 drivers/mmc/host/omap_hsmmc.c

diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index ea8d7a3..08e57ae 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -67,7 +67,7 @@ config MMC_RICOH_MMC
 
 config MMC_OMAP
 	tristate "TI OMAP Multimedia Card Interface support"
-	depends on ARCH_OMAP
+	depends on ARCH_OMAP1 || (ARCH_OMAP2 && ARCH_OMAP2420)
 	select TPS65010 if MACH_OMAP_H2
 	help
 	  This selects the TI OMAP Multimedia card Interface.
@@ -76,6 +76,17 @@ config MMC_OMAP
 
 	  If unsure, say N.
 
+config MMC_OMAP_HS
+	tristate "TI OMAP High Speed Multimedia Card Interface support"
+	depends on (ARCH_OMAP2 && ARCH_OMAP2430) || ARCH_OMAP3
+	select TWL4030_CORE if MACH_OMAP_2430SDP || MACH_OMAP_3430SDP
+	help
+	  This selects the TI OMAP High Speed Multimedia card Interface.
+	  If you have an OMAP2(2430) or OMAP3 board with a Multimedia Card slot,
+	  say Y or M here.
+
+	  If unsure, say N.
+
 config MMC_WBSD
 	tristate "Winbond W83L51xD SD/MMC Card Interface support"
 	depends on ISA_DMA_API
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index c794cc5..ba477bc 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_MMC_RICOH_MMC)	+= ricoh_mmc.o
 obj-$(CONFIG_MMC_WBSD)		+= wbsd.o
 obj-$(CONFIG_MMC_AU1X)		+= au1xmmc.o
 obj-$(CONFIG_MMC_OMAP)		+= omap.o
+obj-$(CONFIG_MMC_OMAP_HS)	+= omap_hsmmc.o
 obj-$(CONFIG_MMC_AT91)		+= at91_mci.o
 obj-$(CONFIG_MMC_ATMELMCI)	+= atmel-mci.o
 obj-$(CONFIG_MMC_TIFM_SD)	+= tifm_sd.o
diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c
new file mode 100644
index 0000000..af34871
--- /dev/null
+++ b/drivers/mmc/host/omap_hsmmc.c
@@ -0,0 +1,1069 @@
+/*
+ * drivers/mmc/host/omap_hsmmc.c
+ *
+ * Driver for OMAP2430/3430 MMC controller.
+ *
+ * Copyright (C) 2007 Texas Instruments.
+ *
+ * Authors:
+ *	Syed Mohammed Khasim	<x0khasim@ti.com>
+ *	Madhusudhan		<madhu.cr@ti.com>
+ *	Mohit Jalori		<mjalori@ti.com>
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/timer.h>
+#include <linux/clk.h>
+#include <linux/mmc/host.h>
+#include <linux/io.h>
+#include <linux/semaphore.h>
+#include <asm/dma.h>
+#include <mach/hardware.h>
+#include <mach/board.h>
+#include <mach/mmc.h>
+#include <mach/cpu.h>
+
+/* OMAP HSMMC Host Controller Registers */
+#define OMAP_HSMMC_SYSCONFIG	0x0010
+#define OMAP_HSMMC_CON		0x002C
+#define OMAP_HSMMC_BLK		0x0104
+#define OMAP_HSMMC_ARG		0x0108
+#define OMAP_HSMMC_CMD		0x010C
+#define OMAP_HSMMC_RSP10	0x0110
+#define OMAP_HSMMC_RSP32	0x0114
+#define OMAP_HSMMC_RSP54	0x0118
+#define OMAP_HSMMC_RSP76	0x011C
+#define OMAP_HSMMC_DATA		0x0120
+#define OMAP_HSMMC_HCTL		0x0128
+#define OMAP_HSMMC_SYSCTL	0x012C
+#define OMAP_HSMMC_STAT		0x0130
+#define OMAP_HSMMC_IE		0x0134
+#define OMAP_HSMMC_ISE		0x0138
+#define OMAP_HSMMC_CAPA		0x0140
+
+#define VS18			(1<<26)
+#define VS30			(1<<25)
+#define SDVS18			(0x5<<9)
+#define SDVS30			(0x6<<9)
+#define SDVSCLR			0xFFFFF1FF
+#define SDVSDET			0x00000400
+#define AUTOIDLE		0x1
+#define SDBP			(1<<8)
+#define DTO			0xe
+#define ICE			0x1
+#define ICS			0x2
+#define CEN			(1<<2)
+#define CLKD_MASK		0x0000FFC0
+#define INT_EN_MASK		0x307F0033
+#define INIT_STREAM		(1<<1)
+#define DP_SELECT		(1<<21)
+#define DDIR			(1<<4)
+#define DMA_EN			0x1
+#define MSBS			1<<5
+#define BCE			1<<1
+#define FOUR_BIT		1 << 1
+#define CC			0x1
+#define TC			0x02
+#define OD			0x1
+#define ERR			(1 << 15)
+#define CMD_TIMEOUT		(1 << 16)
+#define DATA_TIMEOUT		(1 << 20)
+#define CMD_CRC			(1 << 17)
+#define DATA_CRC		(1 << 21)
+#define CARD_ERR		(1 << 28)
+#define STAT_CLEAR		0xFFFFFFFF
+#define INIT_STREAM_CMD		0x00000000
+#define DUAL_VOLT_OCR_BIT	7
+#define SRC			(1 << 25)
+#define SRD			(1 << 26)
+
+#define OMAP_MMC1_DEVID		1
+#define OMAP_MMC2_DEVID		2
+#define OMAP_MMC_DATADIR_NONE	0
+#define OMAP_MMC_DATADIR_READ	1
+#define OMAP_MMC_DATADIR_WRITE	2
+#define MMC_TIMEOUT_MS		20
+#define OMAP_MMC_MASTER_CLOCK	96000000
+#define DRIVER_NAME		"mmci-omap"
+/*
+ * slot_id is device id - 1, device id is a static value
+ * of 1 to represent device 1 etc..
+ */
+#define mmc_slot(host)		(host->pdata->slots[host->slot_id])
+
+/*
+ * MMC Host controller read/write API's
+ */
+#define OMAP_HSMMC_READ(base, reg)	\
+	__raw_readl((base) + OMAP_HSMMC_##reg)
+
+#define OMAP_HSMMC_WRITE(base, reg, val) \
+	__raw_writel((val), (base) + OMAP_HSMMC_##reg)
+
+struct mmc_omap_host {
+	struct	device		*dev;
+	struct	mmc_host	*mmc;
+	struct	mmc_request	*mrq;
+	struct	mmc_command	*cmd;
+	struct	mmc_data	*data;
+	struct	clk		*fclk;
+	struct	clk		*iclk;
+	struct	clk		*dbclk;
+	struct	semaphore	sem;
+	struct	work_struct	mmc_carddetect_work;
+	void	__iomem		*base;
+	resource_size_t		mapbase;
+	unsigned int		id;
+	unsigned int		dma_len;
+	unsigned int		dma_dir;
+	unsigned char		bus_mode;
+	unsigned char		datadir;
+	u32			*buffer;
+	u32			bytesleft;
+	int			suspended;
+	int			irq;
+	int			carddetect;
+	int			use_dma, dma_ch;
+	int			initstr;
+	int			slot_id;
+	int			dbclk_enabled;
+	struct	omap_mmc_platform_data	*pdata;
+};
+
+/*
+ * Stop clock to the card
+ */
+static void omap_mmc_stop_clock(struct mmc_omap_host *host)
+{
+	OMAP_HSMMC_WRITE(host->base, SYSCTL,
+		OMAP_HSMMC_READ(host->base, SYSCTL) & ~CEN);
+	if ((OMAP_HSMMC_READ(host->base, SYSCTL) & CEN) != 0x0)
+		dev_dbg(mmc_dev(host->mmc), "MMC Clock is not stoped\n");
+}
+
+/*
+ * Send init stream sequence to card
+ * before sending IDLE command
+ */
+static void send_init_stream(struct mmc_omap_host *host)
+{
+	int reg = 0;
+	unsigned long timeout;
+
+	disable_irq(host->irq);
+	OMAP_HSMMC_WRITE(host->base, CON,
+		OMAP_HSMMC_READ(host->base, CON) | INIT_STREAM);
+	OMAP_HSMMC_WRITE(host->base, CMD, INIT_STREAM_CMD);
+
+	timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+	while ((reg != CC) && time_before(jiffies, timeout))
+		reg = OMAP_HSMMC_READ(host->base, STAT) & CC;
+
+	OMAP_HSMMC_WRITE(host->base, CON,
+		OMAP_HSMMC_READ(host->base, CON) & ~INIT_STREAM);
+	enable_irq(host->irq);
+}
+
+/*
+ * Configure the response type and send the cmd.
+ */
+static void
+mmc_omap_start_command(struct mmc_omap_host *host, struct mmc_command *cmd,
+	struct mmc_data *data)
+{
+	int cmdreg = 0, resptype = 0, cmdtype = 0;
+
+	dev_dbg(mmc_dev(host->mmc), "%s: CMD%d, argument 0x%08x\n",
+		mmc_hostname(host->mmc), cmd->opcode, cmd->arg);
+	host->cmd = cmd;
+
+	/*
+	 * Clear status bits and enable interrupts
+	 */
+	OMAP_HSMMC_WRITE(host->base, STAT, STAT_CLEAR);
+	OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
+	OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		if (cmd->flags & MMC_RSP_136)
+			resptype = 1;
+		else
+			resptype = 2;
+	}
+
+	/*
+	 * Unlike OMAP1 controller, the cmdtype does not seem to be based on
+	 * ac, bc, adtc, bcr. Only CMD12 needs a val of 0x3, rest 0x0.
+	 */
+	if (cmd->opcode == 12)
+		cmdtype = 0x3;
+
+	cmdreg = (cmd->opcode << 24) | (resptype << 16) | (cmdtype << 22);
+
+	if (data) {
+		cmdreg |= DP_SELECT | MSBS | BCE;
+		if (data->flags & MMC_DATA_READ)
+			cmdreg |= DDIR;
+		else
+			cmdreg &= ~(DDIR);
+	}
+
+	if (host->use_dma)
+		cmdreg |= DMA_EN;
+
+	OMAP_HSMMC_WRITE(host->base, ARG, cmd->arg);
+	OMAP_HSMMC_WRITE(host->base, CMD, cmdreg);
+}
+
+/*
+ * Notify the transfer complete to MMC core
+ */
+static void
+mmc_omap_xfer_done(struct mmc_omap_host *host, struct mmc_data *data)
+{
+	host->data = NULL;
+
+	if (host->use_dma && host->dma_ch != -1)
+		dma_unmap_sg(mmc_dev(host->mmc), data->sg, host->dma_len,
+			host->dma_dir);
+
+	host->datadir = OMAP_MMC_DATADIR_NONE;
+
+	if (!data->error)
+		data->bytes_xfered += data->blocks * (data->blksz);
+	else
+		data->bytes_xfered = 0;
+
+	if (!data->stop) {
+		host->mrq = NULL;
+		mmc_request_done(host->mmc, data->mrq);
+		return;
+	}
+	mmc_omap_start_command(host, data->stop, NULL);
+}
+
+/*
+ * Notify the core about command completion
+ */
+static void
+mmc_omap_cmd_done(struct mmc_omap_host *host, struct mmc_command *cmd)
+{
+	host->cmd = NULL;
+
+	if (cmd->flags & MMC_RSP_PRESENT) {
+		if (cmd->flags & MMC_RSP_136) {
+			/* response type 2 */
+			cmd->resp[3] = OMAP_HSMMC_READ(host->base, RSP10);
+			cmd->resp[2] = OMAP_HSMMC_READ(host->base, RSP32);
+			cmd->resp[1] = OMAP_HSMMC_READ(host->base, RSP54);
+			cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP76);
+		} else {
+			/* response types 1, 1b, 3, 4, 5, 6 */
+			cmd->resp[0] = OMAP_HSMMC_READ(host->base, RSP10);
+		}
+	}
+	if (host->data == NULL || cmd->error) {
+		host->mrq = NULL;
+		mmc_request_done(host->mmc, cmd->mrq);
+	}
+}
+
+/*
+ * DMA clean up for command errors
+ */
+static void mmc_dma_cleanup(struct mmc_omap_host *host)
+{
+	host->data->error = -ETIMEDOUT;
+
+	if (host->use_dma && host->dma_ch != -1) {
+		dma_unmap_sg(mmc_dev(host->mmc), host->data->sg, host->dma_len,
+			host->dma_dir);
+		omap_free_dma(host->dma_ch);
+		host->dma_ch = -1;
+		up(&host->sem);
+	}
+	host->data = NULL;
+	host->datadir = OMAP_MMC_DATADIR_NONE;
+}
+
+/*
+ * MMC controller IRQ handler
+ */
+static irqreturn_t mmc_omap_irq(int irq, void *dev_id)
+{
+	struct mmc_omap_host *host = dev_id;
+	struct mmc_data *data;
+	int end_cmd = 0, end_trans = 0, status;
+
+	if (host->cmd == NULL && host->data == NULL) {
+		OMAP_HSMMC_WRITE(host->base, STAT,
+			OMAP_HSMMC_READ(host->base, STAT));
+		return IRQ_HANDLED;
+	}
+
+	data = host->data;
+	status = OMAP_HSMMC_READ(host->base, STAT);
+	dev_dbg(mmc_dev(host->mmc), "IRQ Status is %x\n", status);
+
+	if (status & ERR) {
+		if ((status & CMD_TIMEOUT) ||
+			(status & CMD_CRC)) {
+			if (host->cmd) {
+				if (status & CMD_TIMEOUT) {
+					OMAP_HSMMC_WRITE(host->base, SYSCTL,
+						OMAP_HSMMC_READ(host->base,
+								SYSCTL) | SRC);
+					while (OMAP_HSMMC_READ(host->base,
+								SYSCTL) & SRC) ;
+					host->cmd->error = -ETIMEDOUT;
+				} else {
+					host->cmd->error = -EILSEQ;
+				}
+				end_cmd = 1;
+			}
+			if (host->data)
+				mmc_dma_cleanup(host);
+		}
+		if ((status & DATA_TIMEOUT) ||
+			(status & DATA_CRC)) {
+			if (host->data) {
+				if (status & DATA_TIMEOUT)
+					mmc_dma_cleanup(host);
+				else
+					host->data->error = -EILSEQ;
+				end_trans = 1;
+			}
+		}
+		if (status & CARD_ERR) {
+			dev_dbg(mmc_dev(host->mmc),
+				"Ignoring card err CMD%d\n", host->cmd->opcode);
+			if (host->cmd)
+				end_cmd = 1;
+			if (host->data)
+				end_trans = 1;
+		}
+	}
+
+	OMAP_HSMMC_WRITE(host->base, STAT, status);
+
+	if (end_cmd || (status & CC))
+		mmc_omap_cmd_done(host, host->cmd);
+	if (end_trans || (status & TC))
+		mmc_omap_xfer_done(host, data);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Switch MMC operating voltage
+ */
+static int omap_mmc_switch_opcond(struct mmc_omap_host *host, int vdd)
+{
+	u32 reg_val = 0;
+	int ret;
+
+	/* Disable the clocks */
+	clk_disable(host->fclk);
+	clk_disable(host->iclk);
+	clk_disable(host->dbclk);
+
+	/* Turn the power off */
+	ret = mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+	if (ret != 0)
+		goto err;
+
+	/* Turn the power ON with given VDD 1.8 or 3.0v */
+	ret = mmc_slot(host).set_power(host->dev, host->slot_id, 1, vdd);
+	if (ret != 0)
+		goto err;
+
+	clk_enable(host->fclk);
+	clk_enable(host->iclk);
+	clk_enable(host->dbclk);
+
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+		OMAP_HSMMC_READ(host->base, HCTL) & SDVSCLR);
+	reg_val = OMAP_HSMMC_READ(host->base, HCTL);
+	/*
+	 * If a MMC dual voltage card is detected, the set_ios fn calls
+	 * this fn with VDD bit set for 1.8V. Upon card removal from the
+	 * slot, mmc_omap_detect fn sets the VDD back to 3V.
+	 *
+	 * Only MMC1 supports 3.0V.  MMC2 will not function if SDVS30 is
+	 * set in HCTL.
+	 */
+	if (host->id == OMAP_MMC1_DEVID && (((1 << vdd) == MMC_VDD_32_33) ||
+				((1 << vdd) == MMC_VDD_33_34)))
+		reg_val |= SDVS30;
+	if ((1 << vdd) == MMC_VDD_165_195)
+		reg_val |= SDVS18;
+
+	OMAP_HSMMC_WRITE(host->base, HCTL, reg_val);
+
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+		OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+	return 0;
+err:
+	dev_dbg(mmc_dev(host->mmc), "Unable to switch operating voltage\n");
+	return ret;
+}
+
+/*
+ * Work Item to notify the core about card insertion/removal
+ */
+static void mmc_omap_detect(struct work_struct *work)
+{
+	u16 vdd = 0;
+	struct mmc_omap_host *host = container_of(work, struct mmc_omap_host,
+						mmc_carddetect_work);
+
+	if (host->carddetect) {
+		if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
+			/*
+			 * Set the VDD back to 3V when the card is removed
+			 * before the set_ios fn turns off the power.
+			 */
+			vdd = fls(host->mmc->ocr_avail) - 1;
+			if (omap_mmc_switch_opcond(host, vdd) != 0)
+				host->mmc->ios.vdd = vdd;
+		}
+		mmc_detect_change(host->mmc, (HZ * 200) / 1000);
+	} else {
+		OMAP_HSMMC_WRITE(host->base, SYSCTL,
+			OMAP_HSMMC_READ(host->base, SYSCTL) | SRD);
+		while (OMAP_HSMMC_READ(host->base, SYSCTL) & SRD) ;
+		mmc_detect_change(host->mmc, (HZ * 50) / 1000);
+	}
+}
+
+/*
+ * ISR for handling card insertion and removal
+ */
+static irqreturn_t omap_mmc_cd_handler(int irq, void *dev_id)
+{
+	struct mmc_omap_host *host = (struct mmc_omap_host *)dev_id;
+
+	host->carddetect = mmc_slot(host).card_detect(irq);
+	schedule_work(&host->mmc_carddetect_work);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * DMA call back function
+ */
+static void mmc_omap_dma_cb(int lch, u16 ch_status, void *data)
+{
+	struct mmc_omap_host *host = data;
+
+	if (ch_status & OMAP2_DMA_MISALIGNED_ERR_IRQ)
+		dev_dbg(mmc_dev(host->mmc), "MISALIGNED_ADRS_ERR\n");
+
+	if (host->dma_ch < 0)
+		return;
+
+	omap_free_dma(host->dma_ch);
+	host->dma_ch = -1;
+	/*
+	 * DMA Callback: run in interrupt context.
+	 * mutex_unlock will through a kernel warning if used.
+	 */
+	up(&host->sem);
+}
+
+/*
+ * Configure dma src and destination parameters
+ */
+static int mmc_omap_config_dma_param(int sync_dir, struct mmc_omap_host *host,
+				struct mmc_data *data)
+{
+	if (sync_dir == 0) {
+		omap_set_dma_dest_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_CONSTANT,
+			(host->mapbase + OMAP_HSMMC_DATA), 0, 0);
+		omap_set_dma_src_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_POST_INC,
+			sg_dma_address(&data->sg[0]), 0, 0);
+	} else {
+		omap_set_dma_src_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_CONSTANT,
+			(host->mapbase + OMAP_HSMMC_DATA), 0, 0);
+		omap_set_dma_dest_params(host->dma_ch, 0,
+			OMAP_DMA_AMODE_POST_INC,
+			sg_dma_address(&data->sg[0]), 0, 0);
+	}
+	return 0;
+}
+/*
+ * Routine to configure and start DMA for the MMC card
+ */
+static int
+mmc_omap_start_dma_transfer(struct mmc_omap_host *host, struct mmc_request *req)
+{
+	int sync_dev, sync_dir = 0;
+	int dma_ch = 0, ret = 0, err = 1;
+	struct mmc_data *data = req->data;
+
+	/*
+	 * If for some reason the DMA transfer is still active,
+	 * we wait for timeout period and free the dma
+	 */
+	if (host->dma_ch != -1) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(100);
+		if (down_trylock(&host->sem)) {
+			omap_free_dma(host->dma_ch);
+			host->dma_ch = -1;
+			up(&host->sem);
+			return err;
+		}
+	} else {
+		if (down_trylock(&host->sem))
+			return err;
+	}
+
+	if (!(data->flags & MMC_DATA_WRITE)) {
+		host->dma_dir = DMA_FROM_DEVICE;
+		if (host->id == OMAP_MMC1_DEVID)
+			sync_dev = OMAP24XX_DMA_MMC1_RX;
+		else
+			sync_dev = OMAP24XX_DMA_MMC2_RX;
+	} else {
+		host->dma_dir = DMA_TO_DEVICE;
+		if (host->id == OMAP_MMC1_DEVID)
+			sync_dev = OMAP24XX_DMA_MMC1_TX;
+		else
+			sync_dev = OMAP24XX_DMA_MMC2_TX;
+	}
+
+	ret = omap_request_dma(sync_dev, "MMC/SD", mmc_omap_dma_cb,
+			host, &dma_ch);
+	if (ret != 0) {
+		dev_dbg(mmc_dev(host->mmc),
+			"%s: omap_request_dma() failed with %d\n",
+			mmc_hostname(host->mmc), ret);
+		return ret;
+	}
+
+	host->dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg,
+			data->sg_len, host->dma_dir);
+	host->dma_ch = dma_ch;
+
+	if (!(data->flags & MMC_DATA_WRITE))
+		mmc_omap_config_dma_param(1, host, data);
+	else
+		mmc_omap_config_dma_param(0, host, data);
+
+	if ((data->blksz % 4) == 0)
+		omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32,
+			(data->blksz / 4), data->blocks, OMAP_DMA_SYNC_FRAME,
+			sync_dev, sync_dir);
+	else
+		/* REVISIT: The MMC buffer increments only when MSB is written.
+		 * Return error for blksz which is non multiple of four.
+		 */
+		return -EINVAL;
+
+	omap_start_dma(dma_ch);
+	return 0;
+}
+
+/*
+ * Configure block length for MMC/SD cards and initiate the transfer.
+ */
+static int
+mmc_omap_prepare_data(struct mmc_omap_host *host, struct mmc_request *req)
+{
+	int ret;
+	host->data = req->data;
+
+	if (req->data == NULL) {
+		host->datadir = OMAP_MMC_DATADIR_NONE;
+		OMAP_HSMMC_WRITE(host->base, BLK, 0);
+		return 0;
+	}
+
+	OMAP_HSMMC_WRITE(host->base, BLK, (req->data->blksz)
+					| (req->data->blocks << 16));
+
+	host->datadir = (req->data->flags & MMC_DATA_WRITE) ?
+			OMAP_MMC_DATADIR_WRITE : OMAP_MMC_DATADIR_READ;
+
+	if (host->use_dma) {
+		ret = mmc_omap_start_dma_transfer(host, req);
+		if (ret != 0) {
+			dev_dbg(mmc_dev(host->mmc), "MMC start dma failure\n");
+			return ret;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Request function. for read/write operation
+ */
+static void omap_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{
+	struct mmc_omap_host *host = mmc_priv(mmc);
+
+	WARN_ON(host->mrq != NULL);
+	host->mrq = req;
+	mmc_omap_prepare_data(host, req);
+	mmc_omap_start_command(host, req->cmd, req->data);
+}
+
+
+/* Routine to configure clock values. Exposed API to core */
+static void omap_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	struct mmc_omap_host *host = mmc_priv(mmc);
+	u16 dsor = 0;
+	unsigned long regval;
+	unsigned long timeout;
+
+	switch (ios->power_mode) {
+	case MMC_POWER_OFF:
+		mmc_slot(host).set_power(host->dev, host->slot_id, 0, 0);
+		break;
+	case MMC_POWER_UP:
+		mmc_slot(host).set_power(host->dev, host->slot_id, 1, ios->vdd);
+		break;
+	}
+
+	switch (mmc->ios.bus_width) {
+	case MMC_BUS_WIDTH_4:
+		OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) | FOUR_BIT);
+		break;
+	case MMC_BUS_WIDTH_1:
+		OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) & ~FOUR_BIT);
+		break;
+	}
+
+	if (host->id == OMAP_MMC1_DEVID) {
+		/* Only MMC1 can operate at 3V/1.8V */
+		if ((OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET) &&
+			(ios->vdd == DUAL_VOLT_OCR_BIT)) {
+				/*
+				 * The mmc_select_voltage fn of the core does
+				 * not seem to set the power_mode to
+				 * MMC_POWER_UP upon recalculating the voltage.
+				 * vdd 1.8v.
+				 */
+				if (omap_mmc_switch_opcond(host, ios->vdd) != 0)
+					dev_dbg(mmc_dev(host->mmc),
+						"Switch operation failed\n");
+		}
+	}
+
+	if (ios->clock) {
+		dsor = OMAP_MMC_MASTER_CLOCK / ios->clock;
+		if (dsor < 1)
+			dsor = 1;
+
+		if (OMAP_MMC_MASTER_CLOCK / dsor > ios->clock)
+			dsor++;
+
+		if (dsor > 250)
+			dsor = 250;
+	}
+	omap_mmc_stop_clock(host);
+	regval = OMAP_HSMMC_READ(host->base, SYSCTL);
+	regval = regval & ~(CLKD_MASK);
+	regval = regval | (dsor << 6) | (DTO << 16);
+	OMAP_HSMMC_WRITE(host->base, SYSCTL, regval);
+	OMAP_HSMMC_WRITE(host->base, SYSCTL,
+		OMAP_HSMMC_READ(host->base, SYSCTL) | ICE);
+
+	/* Wait till the ICS bit is set */
+	timeout = jiffies + msecs_to_jiffies(MMC_TIMEOUT_MS);
+	while ((OMAP_HSMMC_READ(host->base, SYSCTL) & ICS) != 0x2
+		&& time_before(jiffies, timeout))
+		msleep(1);
+
+	OMAP_HSMMC_WRITE(host->base, SYSCTL,
+		OMAP_HSMMC_READ(host->base, SYSCTL) | CEN);
+
+	if (ios->power_mode == MMC_POWER_ON)
+		send_init_stream(host);
+
+	if (ios->bus_mode == MMC_BUSMODE_OPENDRAIN)
+		OMAP_HSMMC_WRITE(host->base, CON,
+				OMAP_HSMMC_READ(host->base, CON) | OD);
+}
+/* NOTE: Read only switch not supported yet */
+static struct mmc_host_ops mmc_omap_ops = {
+	.request = omap_mmc_request,
+	.set_ios = omap_mmc_set_ios,
+};
+
+static int __init omap_mmc_probe(struct platform_device *pdev)
+{
+	struct omap_mmc_platform_data *pdata = pdev->dev.platform_data;
+	struct mmc_host *mmc;
+	struct mmc_omap_host *host = NULL;
+	struct resource *res;
+	int ret = 0, irq;
+	u32 hctl, capa;
+
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "Platform Data is missing\n");
+		return -ENXIO;
+	}
+
+	if (pdata->nr_slots == 0) {
+		dev_err(&pdev->dev, "No Slots\n");
+		return -ENXIO;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	irq = platform_get_irq(pdev, 0);
+	if (res == NULL || irq < 0)
+		return -ENXIO;
+
+	res = request_mem_region(res->start, res->end - res->start + 1,
+							pdev->name);
+	if (res == NULL)
+		return -EBUSY;
+
+	mmc = mmc_alloc_host(sizeof(struct mmc_omap_host), &pdev->dev);
+	if (!mmc) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	host		= mmc_priv(mmc);
+	host->mmc	= mmc;
+	host->pdata	= pdata;
+	host->use_dma	= 1;
+	host->dma_ch	= -1;
+	host->irq	= irq;
+	host->id	= pdev->id;
+	host->slot_id	= 0;
+	host->mapbase	= res->start;
+	host->base	= ioremap(host->mapbase, SZ_4K);
+	mmc->ops	= &mmc_omap_ops;
+	mmc->f_min	= 400000;
+	mmc->f_max	= 52000000;
+
+	sema_init(&host->sem, 1);
+
+	host->iclk = clk_get(&pdev->dev, "mmchs_ick");
+	if (IS_ERR(host->iclk)) {
+		ret = PTR_ERR(host->iclk);
+		host->iclk = NULL;
+		goto err1;
+	}
+	host->fclk = clk_get(&pdev->dev, "mmchs_fck");
+	if (IS_ERR(host->fclk)) {
+		ret = PTR_ERR(host->fclk);
+		host->fclk = NULL;
+		clk_put(host->iclk);
+		goto err1;
+	}
+
+	if (clk_enable(host->fclk) != 0) {
+		clk_put(host->iclk);
+		clk_put(host->fclk);
+		goto err1;
+	}
+
+	if (clk_enable(host->iclk) != 0) {
+		clk_disable(host->fclk);
+		clk_put(host->iclk);
+		clk_put(host->fclk);
+		goto err1;
+	}
+
+	host->dbclk = clk_get(&pdev->dev, "mmchsdb_fck");
+	/*
+	 * MMC can still work without debounce clock.
+	 */
+	if (IS_ERR(host->dbclk))
+		dev_dbg(mmc_dev(host->mmc), "Failed to get debounce clock\n");
+	else
+		if (clk_enable(host->dbclk) != 0)
+			dev_dbg(mmc_dev(host->mmc), "Enabling debounce"
+							" clk failed\n");
+		else
+			host->dbclk_enabled = 1;
+
+#ifdef CONFIG_MMC_BLOCK_BOUNCE
+	mmc->max_phys_segs = 1;
+	mmc->max_hw_segs = 1;
+#endif
+	mmc->max_blk_size = 512;       /* Block Length at max can be 1024 */
+	mmc->max_blk_count = 0xFFFF;    /* No. of Blocks is 16 bits */
+	mmc->max_req_size = mmc->max_blk_size * mmc->max_blk_count;
+	mmc->max_seg_size = mmc->max_req_size;
+
+	mmc->ocr_avail = mmc_slot(host).ocr_mask;
+	mmc->caps |= MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
+
+	if (pdata->conf.wire4)
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+
+	/* Only MMC1 supports 3.0V */
+	if (host->id == OMAP_MMC1_DEVID) {
+		hctl = SDVS30;
+		capa = VS30 | VS18;
+	} else {
+		hctl = SDVS18;
+		capa = VS18;
+	}
+
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) | hctl);
+
+	OMAP_HSMMC_WRITE(host->base, CAPA,
+			OMAP_HSMMC_READ(host->base, CAPA) | capa);
+
+	/* Set the controller to AUTO IDLE mode */
+	OMAP_HSMMC_WRITE(host->base, SYSCONFIG,
+			OMAP_HSMMC_READ(host->base, SYSCONFIG) | AUTOIDLE);
+
+	/* Set SD bus power bit */
+	OMAP_HSMMC_WRITE(host->base, HCTL,
+			OMAP_HSMMC_READ(host->base, HCTL) | SDBP);
+
+	/* Request IRQ for MMC operations */
+	ret = request_irq(host->irq, mmc_omap_irq, IRQF_DISABLED, pdev->name,
+			 host);
+	if (ret) {
+		dev_dbg(mmc_dev(host->mmc), "Unable to grab HSMMC IRQ\n");
+		goto irq_err;
+	}
+
+	/* Request IRQ for card detect */
+	if ((mmc_slot(host).card_detect_irq) && (mmc_slot(host).card_detect)) {
+		ret = request_irq(mmc_slot(host).card_detect_irq,
+				  omap_mmc_cd_handler, IRQF_DISABLED, "MMC CD",
+				  host);
+		if (ret) {
+			dev_dbg(mmc_dev(host->mmc),
+				"Unable to grab MMC CD IRQ");
+			free_irq(host->irq, host);
+			goto irq_err;
+		}
+	}
+
+	INIT_WORK(&host->mmc_carddetect_work, mmc_omap_detect);
+	if (pdata->init != NULL) {
+		if (pdata->init(&pdev->dev) != 0) {
+			free_irq(mmc_slot(host).card_detect_irq, host);
+			free_irq(host->irq, host);
+			goto irq_err;
+		}
+	}
+
+	OMAP_HSMMC_WRITE(host->base, ISE, INT_EN_MASK);
+	OMAP_HSMMC_WRITE(host->base, IE, INT_EN_MASK);
+
+	platform_set_drvdata(pdev, host);
+	mmc_add_host(mmc);
+
+	return 0;
+
+irq_err:
+	dev_dbg(mmc_dev(host->mmc), "Unable to configure MMC IRQs\n");
+	clk_disable(host->fclk);
+	clk_disable(host->iclk);
+	clk_put(host->fclk);
+	clk_put(host->iclk);
+	if (host->dbclk_enabled) {
+		clk_disable(host->dbclk);
+		clk_put(host->dbclk);
+	}
+
+err1:
+	iounmap(host->base);
+err:
+	dev_dbg(mmc_dev(host->mmc), "Probe Failed\n");
+	release_mem_region(res->start, res->end - res->start + 1);
+	if (host)
+		mmc_free_host(mmc);
+	return ret;
+}
+
+static int omap_mmc_remove(struct platform_device *pdev)
+{
+	struct mmc_omap_host *host = platform_get_drvdata(pdev);
+	struct resource *res;
+	u16 vdd = 0;
+
+	if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
+	/*
+	 * Set the vdd back to 3V,
+	 * applicable for dual volt support.
+	 */
+		vdd = fls(host->mmc->ocr_avail) - 1;
+		if (omap_mmc_switch_opcond(host, vdd) != 0)
+			host->mmc->ios.vdd = vdd;
+	}
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res)
+		release_mem_region(res->start, res->end - res->start + 1);
+
+	platform_set_drvdata(pdev, NULL);
+	if (host) {
+		mmc_remove_host(host->mmc);
+		if (host->pdata->cleanup)
+			host->pdata->cleanup(&pdev->dev);
+		free_irq(host->irq, host);
+		if (mmc_slot(host).card_detect_irq)
+			free_irq(mmc_slot(host).card_detect_irq, host);
+		flush_scheduled_work();
+
+		clk_disable(host->fclk);
+		clk_disable(host->iclk);
+		clk_put(host->fclk);
+		clk_put(host->iclk);
+		if (host->dbclk_enabled) {
+			clk_disable(host->dbclk);
+			clk_put(host->dbclk);
+		}
+
+		mmc_free_host(host->mmc);
+		iounmap(host->base);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int omap_mmc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	int ret = 0;
+	struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+	if (host && host->suspended)
+		return 0;
+
+	if (host) {
+		ret = mmc_suspend_host(host->mmc, state);
+		if (ret == 0) {
+			host->suspended = 1;
+
+			OMAP_HSMMC_WRITE(host->base, ISE, 0);
+			OMAP_HSMMC_WRITE(host->base, IE, 0);
+
+			ret = host->pdata->suspend(&pdev->dev, host->slot_id);
+			if (ret)
+				dev_dbg(mmc_dev(host->mmc),
+					"Unable to handle MMC board"
+					" level suspend\n");
+
+			if (!(OMAP_HSMMC_READ(host->base, HCTL) & SDVSDET)) {
+				OMAP_HSMMC_WRITE(host->base, HCTL,
+					OMAP_HSMMC_READ(host->base, HCTL)
+					& SDVSCLR);
+				OMAP_HSMMC_WRITE(host->base, HCTL,
+					OMAP_HSMMC_READ(host->base, HCTL)
+					| SDVS30);
+				OMAP_HSMMC_WRITE(host->base, HCTL,
+					OMAP_HSMMC_READ(host->base, HCTL)
+					| SDBP);
+			}
+
+			clk_disable(host->fclk);
+			clk_disable(host->iclk);
+			clk_disable(host->dbclk);
+		}
+
+	}
+	return ret;
+}
+
+/* Routine to resume the MMC device */
+static int omap_mmc_resume(struct platform_device *pdev)
+{
+	int ret = 0;
+	struct mmc_omap_host *host = platform_get_drvdata(pdev);
+
+	if (host && !host->suspended)
+		return 0;
+
+	if (host) {
+
+		ret = clk_enable(host->fclk);
+		if (ret)
+			goto clk_en_err;
+
+		ret = clk_enable(host->iclk);
+		if (ret) {
+			clk_disable(host->fclk);
+			clk_put(host->fclk);
+			goto clk_en_err;
+		}
+
+		if (clk_enable(host->dbclk) != 0)
+			dev_dbg(mmc_dev(host->mmc),
+					"Enabling debounce clk failed\n");
+
+		ret = host->pdata->resume(&pdev->dev, host->slot_id);
+		if (ret)
+			dev_dbg(mmc_dev(host->mmc),
+					"Unmask interrupt failed\n");
+
+		/* Notify the core to resume the host */
+		ret = mmc_resume_host(host->mmc);
+		if (ret == 0)
+			host->suspended = 0;
+	}
+
+	return ret;
+
+clk_en_err:
+	dev_dbg(mmc_dev(host->mmc),
+		"Failed to enable MMC clocks during resume\n");
+	return ret;
+}
+
+#else
+#define omap_mmc_suspend	NULL
+#define omap_mmc_resume		NULL
+#endif
+
+static struct platform_driver omap_mmc_driver = {
+	.probe		= omap_mmc_probe,
+	.remove		= omap_mmc_remove,
+	.suspend	= omap_mmc_suspend,
+	.resume		= omap_mmc_resume,
+	.driver		= {
+		.name = DRIVER_NAME,
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init omap_mmc_init(void)
+{
+	/* Register the MMC driver */
+	return platform_driver_register(&omap_mmc_driver);
+}
+
+static void __exit omap_mmc_cleanup(void)
+{
+	/* Unregister MMC driver */
+	platform_driver_unregister(&omap_mmc_driver);
+}
+
+module_init(omap_mmc_init);
+module_exit(omap_mmc_cleanup);
+
+MODULE_DESCRIPTION("OMAP High Speed Multimedia Card driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRIVER_NAME);
+MODULE_AUTHOR("Texas Instruments Inc");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 22/33] add omap nand drivers
  2008-08-30 17:16                                         ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver Felipe Balbi
@ 2008-08-30 17:16                                           ` Felipe Balbi
  2008-08-30 17:16                                             ` [PATCH 23/33] add omap irda driver Felipe Balbi
  2008-08-31 21:08                                             ` [PATCH 22/33] add omap nand drivers David Brownell
  2008-08-31 21:06                                           ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver David Brownell
  2008-09-02 22:05                                           ` Russell King - ARM Linux
  2 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/mtd/nand/Kconfig           |   19 +
 drivers/mtd/nand/Makefile          |    3 +
 drivers/mtd/nand/omap-hw.c         |  860 ++++++++++++++++++++++++++++++++++++
 drivers/mtd/nand/omap-nand-flash.c |  186 ++++++++
 drivers/mtd/nand/omap2.c           |  757 +++++++++++++++++++++++++++++++
 5 files changed, 1825 insertions(+), 0 deletions(-)
 create mode 100644 drivers/mtd/nand/omap-hw.c
 create mode 100644 drivers/mtd/nand/omap-nand-flash.c
 create mode 100644 drivers/mtd/nand/omap2.c

diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig
index 41f361c..ed1a331 100644
--- a/drivers/mtd/nand/Kconfig
+++ b/drivers/mtd/nand/Kconfig
@@ -68,6 +68,25 @@ config MTD_NAND_AMS_DELTA
 	help
 	  Support for NAND flash on Amstrad E3 (Delta).
 
+config MTD_NAND_OMAP2
+	tristate "NAND Flash device on OMAP2 and OMAP3"
+	depends on ARM && MTD_NAND && (ARCH_OMAP2 || ARCH_OMAP3)
+	help
+          Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms.
+
+config MTD_NAND_OMAP
+	tristate "NAND Flash device on OMAP H3/H2/P2 boards"
+	depends on ARM && ARCH_OMAP1 && MTD_NAND && (MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_PERSEUS2)
+	help
+	  Support for NAND flash on Texas Instruments H3/H2/P2 platforms.
+
+config MTD_NAND_OMAP_HW
+	bool "OMAP HW NAND Flash controller support"
+        depends on ARM && ARCH_OMAP16XX && MTD_NAND
+
+	help
+	  Driver for TI OMAP16xx hardware NAND flash controller.
+
 config MTD_NAND_TOTO
 	tristate "NAND Flash device on TOTO board"
 	depends on ARCH_OMAP && BROKEN
diff --git a/drivers/mtd/nand/Makefile b/drivers/mtd/nand/Makefile
index b786c5d..ba42584 100644
--- a/drivers/mtd/nand/Makefile
+++ b/drivers/mtd/nand/Makefile
@@ -24,6 +24,9 @@ obj-$(CONFIG_MTD_NAND_NANDSIM)		+= nandsim.o
 obj-$(CONFIG_MTD_NAND_CS553X)		+= cs553x_nand.o
 obj-$(CONFIG_MTD_NAND_NDFC)		+= ndfc.o
 obj-$(CONFIG_MTD_NAND_ATMEL)		+= atmel_nand.o
+obj-$(CONFIG_MTD_NAND_OMAP) 		+= omap-nand-flash.o
+obj-$(CONFIG_MTD_NAND_OMAP2) 		+= omap2.o
+obj-$(CONFIG_MTD_NAND_OMAP_HW)		+= omap-hw.o
 obj-$(CONFIG_MTD_NAND_CM_X270)		+= cmx270_nand.o
 obj-$(CONFIG_MTD_NAND_BASLER_EXCITE)	+= excite_nandflash.o
 obj-$(CONFIG_MTD_NAND_PXA3xx)		+= pxa3xx_nand.o
diff --git a/drivers/mtd/nand/omap-hw.c b/drivers/mtd/nand/omap-hw.c
new file mode 100644
index 0000000..c598d9d
--- /dev/null
+++ b/drivers/mtd/nand/omap-hw.c
@@ -0,0 +1,860 @@
+/*
+ *  drivers/mtd/nand/omap-hw.c
+ *
+ *  This is the MTD driver for OMAP1710 internal HW NAND controller.
+ *
+ *  Copyright (C) 2004-2006 Nokia Corporation
+ *
+ *  Author: Jarkko Lavinen <jarkko.lavinen@nokia.com> and
+ *          Juha Yrjölä <juha.yrjola@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; see the file COPYING. If not, write to the Free Software
+ * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/types.h>
+#include <linux/wait.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/dma-mapping.h>
+#include <linux/clk.h>
+
+#include <asm/io.h>
+
+#include <mach/board.h>
+#include <mach/dma.h>
+
+#define NAND_BASE		0xfffbcc00
+#define NND_REVISION		0x00
+#define NND_ACCESS		0x04
+#define NND_ADDR_SRC		0x08
+#define NND_CTRL		0x10
+#define NND_MASK		0x14
+#define NND_STATUS		0x18
+#define NND_READY		0x1c
+#define NND_COMMAND		0x20
+#define NND_COMMAND_SEC		0x24
+#define NND_ECC_SELECT		0x28
+#define NND_ECC_START		0x2c
+#define NND_ECC_9		0x4c
+#define NND_RESET		0x50
+#define NND_FIFO		0x54
+#define NND_FIFOCTRL		0x58
+#define NND_PSC_CLK		0x5c
+#define NND_SYSTEST		0x60
+#define NND_SYSCFG		0x64
+#define NND_SYSSTATUS		0x68
+#define NND_FIFOTEST1		0x6c
+#define NND_FIFOTEST2		0x70
+#define NND_FIFOTEST3		0x74
+#define NND_FIFOTEST4		0x78
+#define NND_PSC1_CLK		0x8c
+#define NND_PSC2_CLK		0x90
+
+
+#define NND_CMD_READ1_LOWER	0x00
+#define NND_CMD_WRITE1_LOWER	0x00
+#define NND_CMD_READ1_UPPER	0x01
+#define NND_CMD_WRITE1_UPPER	0x01
+#define NND_CMD_PROGRAM_END	0x10
+#define NND_CMD_READ2_SPARE	0x50
+#define NND_CMD_WRITE2_SPARE	0x50
+#define NND_CMD_ERASE		0x60
+#define NND_CMD_STATUS		0x70
+#define NND_CMD_PROGRAM		0x80
+#define NND_CMD_READ_ID		0x90
+#define NND_CMD_ERASE_END	0xD0
+#define NND_CMD_RESET		0xFF
+
+
+#define NAND_Ecc_P1e		(1 << 0)
+#define NAND_Ecc_P2e		(1 << 1)
+#define NAND_Ecc_P4e		(1 << 2)
+#define NAND_Ecc_P8e		(1 << 3)
+#define NAND_Ecc_P16e		(1 << 4)
+#define NAND_Ecc_P32e		(1 << 5)
+#define NAND_Ecc_P64e		(1 << 6)
+#define NAND_Ecc_P128e		(1 << 7)
+#define NAND_Ecc_P256e		(1 << 8)
+#define NAND_Ecc_P512e		(1 << 9)
+#define NAND_Ecc_P1024e		(1 << 10)
+#define NAND_Ecc_P2048e		(1 << 11)
+
+#define NAND_Ecc_P1o		(1 << 16)
+#define NAND_Ecc_P2o		(1 << 17)
+#define NAND_Ecc_P4o		(1 << 18)
+#define NAND_Ecc_P8o		(1 << 19)
+#define NAND_Ecc_P16o		(1 << 20)
+#define NAND_Ecc_P32o		(1 << 21)
+#define NAND_Ecc_P64o		(1 << 22)
+#define NAND_Ecc_P128o		(1 << 23)
+#define NAND_Ecc_P256o		(1 << 24)
+#define NAND_Ecc_P512o		(1 << 25)
+#define NAND_Ecc_P1024o		(1 << 26)
+#define NAND_Ecc_P2048o		(1 << 27)
+
+#define TF(value)	(value ? 1 : 0)
+
+#define P2048e(a)	(TF(a & NAND_Ecc_P2048e)	<< 0 )
+#define P2048o(a)	(TF(a & NAND_Ecc_P2048o)	<< 1 )
+#define P1e(a)		(TF(a & NAND_Ecc_P1e)		<< 2 )
+#define P1o(a)		(TF(a & NAND_Ecc_P1o)		<< 3 )
+#define P2e(a)		(TF(a & NAND_Ecc_P2e)		<< 4 )
+#define P2o(a)		(TF(a & NAND_Ecc_P2o)		<< 5 )
+#define P4e(a)		(TF(a & NAND_Ecc_P4e)		<< 6 )
+#define P4o(a)		(TF(a & NAND_Ecc_P4o)		<< 7 )
+
+#define P8e(a)		(TF(a & NAND_Ecc_P8e)		<< 0 )
+#define P8o(a)		(TF(a & NAND_Ecc_P8o)		<< 1 )
+#define P16e(a)		(TF(a & NAND_Ecc_P16e)		<< 2 )
+#define P16o(a)		(TF(a & NAND_Ecc_P16o)		<< 3 )
+#define P32e(a)		(TF(a & NAND_Ecc_P32e)		<< 4 )
+#define P32o(a)		(TF(a & NAND_Ecc_P32o)		<< 5 )
+#define P64e(a)		(TF(a & NAND_Ecc_P64e)		<< 6 )
+#define P64o(a)		(TF(a & NAND_Ecc_P64o)		<< 7 )
+
+#define P128e(a)	(TF(a & NAND_Ecc_P128e)		<< 0 )
+#define P128o(a)	(TF(a & NAND_Ecc_P128o)		<< 1 )
+#define P256e(a)	(TF(a & NAND_Ecc_P256e)		<< 2 )
+#define P256o(a)	(TF(a & NAND_Ecc_P256o)		<< 3 )
+#define P512e(a)	(TF(a & NAND_Ecc_P512e)		<< 4 )
+#define P512o(a)	(TF(a & NAND_Ecc_P512o)		<< 5 )
+#define P1024e(a)	(TF(a & NAND_Ecc_P1024e)	<< 6 )
+#define P1024o(a)	(TF(a & NAND_Ecc_P1024o)	<< 7 )
+
+#define P8e_s(a)	(TF(a & NAND_Ecc_P8e)		<< 0 )
+#define P8o_s(a)	(TF(a & NAND_Ecc_P8o)		<< 1 )
+#define P16e_s(a)	(TF(a & NAND_Ecc_P16e)		<< 2 )
+#define P16o_s(a)	(TF(a & NAND_Ecc_P16o)		<< 3 )
+#define P1e_s(a)	(TF(a & NAND_Ecc_P1e)		<< 4 )
+#define P1o_s(a)	(TF(a & NAND_Ecc_P1o)		<< 5 )
+#define P2e_s(a)	(TF(a & NAND_Ecc_P2e)		<< 6 )
+#define P2o_s(a)	(TF(a & NAND_Ecc_P2o)		<< 7 )
+
+#define P4e_s(a)	(TF(a & NAND_Ecc_P4e)		<< 0 )
+#define P4o_s(a)	(TF(a & NAND_Ecc_P4o)		<< 1 )
+
+extern struct nand_oobinfo jffs2_oobinfo;
+
+/*
+ * MTD structure for OMAP board
+ */
+static struct mtd_info *omap_mtd;
+static struct clk *omap_nand_clk;
+static int omap_nand_dma_ch;
+static struct completion omap_nand_dma_comp;
+static unsigned long omap_nand_base = io_p2v(NAND_BASE);
+
+static inline u32 nand_read_reg(int idx)
+{
+	return __raw_readl(omap_nand_base + idx);
+}
+
+static inline void nand_write_reg(int idx, u32 val)
+{
+	__raw_writel(val, omap_nand_base + idx);
+}
+
+static inline u8 nand_read_reg8(int idx)
+{
+	return __raw_readb(omap_nand_base + idx);
+}
+
+static inline void nand_write_reg8(int idx, u8 val)
+{
+	__raw_writeb(val, omap_nand_base + idx);
+}
+
+static void omap_nand_select_chip(struct mtd_info *mtd, int chip)
+{
+	u32 l;
+
+	switch(chip) {
+	case -1:
+		l = nand_read_reg(NND_CTRL);
+		l |= (1 << 8) | (1 << 10) | (1 << 12) | (1 << 14);
+		nand_write_reg(NND_CTRL, l);
+		break;
+	case 0:
+		/* Also CS1, CS2, CS4 would be available */
+		l = nand_read_reg(NND_CTRL);
+		l &= ~(1 << 8);
+		nand_write_reg(NND_CTRL, l);
+		break;
+	default:
+		BUG();
+	}
+}
+
+static void nand_dma_cb(int lch, u16 ch_status, void *data)
+{
+	complete((struct completion *) data);
+}
+
+static void omap_nand_dma_transfer(struct mtd_info *mtd, void *addr,
+                                         unsigned int u32_count, int is_write)
+{
+	const int block_size = 16;
+	unsigned int block_count, len;
+	int dma_ch;
+	unsigned long fifo_reg, timeout, jiffies_before, jiffies_spent;
+	static unsigned long max_jiffies = 0;
+
+	dma_ch = omap_nand_dma_ch;
+	block_count = u32_count * 4 / block_size;
+	nand_write_reg(NND_STATUS, 0x0f);
+	nand_write_reg(NND_FIFOCTRL, (block_size << 24) | block_count);
+	fifo_reg = NAND_BASE + NND_FIFO;
+	if (is_write) {
+		omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_TIPB,
+					 OMAP_DMA_AMODE_CONSTANT, fifo_reg,
+					 0, 0);
+		omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_EMIFF,
+					OMAP_DMA_AMODE_POST_INC,
+					virt_to_phys(addr),
+					0, 0);
+//		omap_set_dma_src_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_4);
+		/* Set POSTWRITE bit */
+		nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 16));
+	} else {
+		omap_set_dma_src_params(dma_ch, OMAP_DMA_PORT_TIPB,
+					OMAP_DMA_AMODE_CONSTANT, fifo_reg,
+					0, 0);
+		omap_set_dma_dest_params(dma_ch, OMAP_DMA_PORT_EMIFF,
+					 OMAP_DMA_AMODE_POST_INC,
+					 virt_to_phys(addr),
+					 0, 0);
+//		omap_set_dma_dest_burst_mode(dma_ch, OMAP_DMA_DATA_BURST_8);
+		/* Set PREFETCH bit */
+		nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17));
+	}
+	omap_set_dma_transfer_params(dma_ch, OMAP_DMA_DATA_TYPE_S32, block_size / 4,
+				     block_count, OMAP_DMA_SYNC_FRAME,
+				     0, 0);
+	init_completion(&omap_nand_dma_comp);
+
+	len = u32_count << 2;
+	dma_cache_maint(addr, len, DMA_TO_DEVICE);
+	omap_start_dma(dma_ch);
+	jiffies_before = jiffies;
+	timeout = wait_for_completion_timeout(&omap_nand_dma_comp,
+					      msecs_to_jiffies(1000));
+	jiffies_spent = (unsigned long)((long)jiffies - (long)jiffies_before);
+	if (jiffies_spent > max_jiffies)
+		max_jiffies = jiffies_spent;
+
+	if (timeout == 0) {
+		printk(KERN_WARNING "omap-hw-nand: DMA timeout after %u ms, max. seen latency %u ms\n",
+		       jiffies_to_msecs(jiffies_spent),
+		       jiffies_to_msecs(max_jiffies));
+	}
+	if (!is_write)
+		dma_cache_maint(addr, len, DMA_FROM_DEVICE);
+
+	nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~((1 << 16) | (1 << 17)));
+}
+
+static void fifo_read(u32 *out, unsigned int len)
+{
+	const int block_size = 16;
+	unsigned long status_reg, fifo_reg;
+	int c;
+
+	status_reg = omap_nand_base + NND_STATUS;
+	fifo_reg = omap_nand_base + NND_FIFO;
+	len = len * 4 / block_size;
+	nand_write_reg(NND_FIFOCTRL, (block_size << 24) | len);
+	nand_write_reg(NND_STATUS, 0x0f);
+	nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) | (1 << 17));
+	c = block_size / 4;
+	while (len--) {
+		int i;
+
+		while ((__raw_readl(status_reg) & (1 << 2)) == 0);
+		__raw_writel(0x0f, status_reg);
+		for (i = 0; i < c; i++) {
+			u32 l = __raw_readl(fifo_reg);
+			*out++ = l;
+		}
+	}
+	nand_write_reg(NND_CTRL, nand_read_reg(NND_CTRL) & ~(1 << 17));
+	nand_write_reg(NND_STATUS, 0x0f);
+}
+
+static void omap_nand_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+	unsigned long access_reg;
+
+	if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) {
+		int u32_count = len >> 2;
+		u32 *dest = (u32 *) buf;
+		/* If the transfer is big enough and the length divisible by
+		 * 16, we try to use DMA transfer, or FIFO copy in case of
+		 * DMA failure (e.g. all channels busy) */
+		if (u32_count > 64 && (u32_count & 3) == 0) {
+			if (omap_nand_dma_ch >= 0) {
+				omap_nand_dma_transfer(mtd, buf, u32_count, 0);
+				return;
+			}
+			/* In case of an error, fallback to FIFO copy */
+			fifo_read((u32 *) buf, u32_count);
+			return;
+		}
+		access_reg = omap_nand_base + NND_ACCESS;
+		/* Small buffers we just read directly */
+		while (u32_count--)
+			*dest++ = __raw_readl(access_reg);
+	} else {
+		/* If we're not word-aligned, we use byte copy */
+		access_reg = omap_nand_base + NND_ACCESS;
+		while (len--)
+			*buf++ = __raw_readb(access_reg);
+	}
+}
+
+static void omap_nand_write_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+	if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) {
+		const u32 *src = (const u32 *) buf;
+
+		len >>= 2;
+#if 0
+		/* If the transfer is big enough and length divisible by 16,
+		 * we try to use DMA transfer. */
+		if (len > 256 / 4 && (len & 3) == 0) {
+			if (omap_nand_dma_transfer(mtd, (void *) buf, len, 1) == 0)
+				return;
+			/* In case of an error, fallback to CPU copy */
+		}
+#endif
+		while (len--)
+			nand_write_reg(NND_ACCESS, *src++);
+	} else {
+		while (len--)
+			nand_write_reg8(NND_ACCESS, *buf++);
+	}
+}
+
+static int omap_nand_verify_buf(struct mtd_info *mtd, const u_char *buf, int len)
+{
+	if (likely(((unsigned long) buf & 3) == 0 && (len & 3) == 0)) {
+		const u32 *dest = (const u32 *) buf;
+		len >>= 2;
+		while (len--)
+			if (*dest++ != nand_read_reg(NND_ACCESS))
+				return -EFAULT;
+	} else {
+		while (len--)
+			if (*buf++ != nand_read_reg8(NND_ACCESS))
+				return -EFAULT;
+	}
+	return 0;
+}
+
+static u_char omap_nand_read_byte(struct mtd_info *mtd)
+{
+	return nand_read_reg8(NND_ACCESS);
+}
+
+static int omap_nand_dev_ready(struct mtd_info *mtd)
+{
+	u32 l;
+
+	l = nand_read_reg(NND_READY);
+	return l & 0x01;
+}
+
+static int nand_write_command(u8 cmd, u32 addr, int addr_valid)
+{
+	if (addr_valid) {
+		nand_write_reg(NND_ADDR_SRC, addr);
+		nand_write_reg8(NND_COMMAND, cmd);
+	} else {
+		nand_write_reg(NND_ADDR_SRC, 0);
+		nand_write_reg8(NND_COMMAND_SEC, cmd);
+	}
+	while (!omap_nand_dev_ready(NULL));
+	return 0;
+}
+
+/*
+ * Send command to NAND device
+ */
+static void omap_nand_command(struct mtd_info *mtd, unsigned command, int column, int page_addr)
+{
+	struct nand_chip *this = mtd->priv;
+
+	/*
+	 * Write out the command to the device.
+	 */
+	if (command == NAND_CMD_SEQIN) {
+		int readcmd;
+
+		if (column >= mtd->writesize) {
+			/* OOB area */
+			column -= mtd->writesize;
+			readcmd = NAND_CMD_READOOB;
+		} else if (column < 256) {
+			/* First 256 bytes --> READ0 */
+			readcmd = NAND_CMD_READ0;
+		} else {
+			column -= 256;
+			readcmd = NAND_CMD_READ1;
+		}
+		nand_write_command(readcmd, 0, 0);
+	}
+	switch (command) {
+	case NAND_CMD_RESET:
+	case NAND_CMD_PAGEPROG:
+	case NAND_CMD_STATUS:
+	case NAND_CMD_ERASE2:
+		nand_write_command(command, 0, 0);
+		break;
+	case NAND_CMD_ERASE1:
+		nand_write_command(command, ((page_addr & 0xFFFFFF00) << 1) | (page_addr & 0XFF), 1);
+		break;
+	default:
+		nand_write_command(command, (page_addr << this->page_shift) | column, 1);
+	}
+}
+
+static void omap_nand_command_lp(struct mtd_info *mtd, unsigned command, int column, int page_addr)
+{
+	struct nand_chip *this = mtd->priv;
+
+	if (command == NAND_CMD_READOOB) {
+		column += mtd->writesize;
+		command = NAND_CMD_READ0;
+	}
+	switch (command) {
+	case NAND_CMD_RESET:
+	case NAND_CMD_PAGEPROG:
+	case NAND_CMD_STATUS:
+	case NAND_CMD_ERASE2:		
+		nand_write_command(command, 0, 0);
+		break;
+	case NAND_CMD_ERASE1:
+		nand_write_command(command, page_addr << this->page_shift >> 11, 1);
+		break;
+	default:
+		nand_write_command(command, (page_addr << 16) | column, 1);
+	}
+	if (command == NAND_CMD_READ0)
+		nand_write_command(NAND_CMD_READSTART, 0, 0);
+}
+
+/*
+ * Generate non-inverted ECC bytes.
+ *
+ * Using noninverted ECC can be considered ugly since writing a blank
+ * page ie. padding will clear the ECC bytes. This is no problem as long
+ * nobody is trying to write data on the seemingly unused page.
+ *
+ * Reading an erased page will produce an ECC mismatch between
+ * generated and read ECC bytes that has to be dealt with separately.
+ */
+static int omap_nand_calculate_ecc(struct mtd_info *mtd, const u_char *dat, u_char *ecc_code)
+{
+	u32 l;
+	int reg;
+	int n;
+	struct nand_chip *this = mtd->priv;
+
+	/* Ex NAND_ECC_HW12_2048 */
+	if ((this->ecc.mode == NAND_ECC_HW) && (this->ecc.size  == 2048))
+		n = 4;
+	else
+		n = 1;
+	reg = NND_ECC_START;
+	while (n--) {
+		l = nand_read_reg(reg);
+		*ecc_code++ = l;          // P128e, ..., P1e
+		*ecc_code++ = l >> 16;    // P128o, ..., P1o
+		// P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e
+		*ecc_code++ = ((l >> 8) & 0x0f) | ((l >> 20) & 0xf0);
+		reg += 4;
+	}
+	return 0;
+}
+
+/*
+ * This function will generate true ECC value, which can be used
+ * when correcting data read from NAND flash memory core
+ */
+static void gen_true_ecc(u8 *ecc_buf)
+{
+	u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) | ((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8);
+
+	ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) | P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp) );
+	ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) | P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp));
+	ecc_buf[2] = ~( P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) | P1e(tmp) | P2048o(tmp) | P2048e(tmp));
+}
+
+/*
+ * This function compares two ECC's and indicates if there is an error.
+ * If the error can be corrected it will be corrected to the buffer
+ */
+static int omap_nand_compare_ecc(u8 *ecc_data1,   /* read from NAND memory */
+				 u8 *ecc_data2,   /* read from register */
+				 u8 *page_data)
+{
+	uint   i;
+	u8     tmp0_bit[8], tmp1_bit[8], tmp2_bit[8];
+	u8     comp0_bit[8], comp1_bit[8], comp2_bit[8];
+	u8     ecc_bit[24];
+	u8     ecc_sum = 0;
+	u8     find_bit = 0;
+	uint   find_byte = 0;
+	int    isEccFF;
+
+	isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF);
+
+	gen_true_ecc(ecc_data1);
+	gen_true_ecc(ecc_data2);
+
+	for (i = 0; i <= 2; i++) {
+		*(ecc_data1 + i) = ~(*(ecc_data1 + i));
+		*(ecc_data2 + i) = ~(*(ecc_data2 + i));
+	}
+
+	for (i = 0; i < 8; i++) {
+		tmp0_bit[i]      = *ecc_data1 % 2;
+		*ecc_data1       = *ecc_data1 / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		tmp1_bit[i]      = *(ecc_data1 + 1) % 2;
+		*(ecc_data1 + 1) = *(ecc_data1 + 1) / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		tmp2_bit[i]      = *(ecc_data1 + 2) % 2;
+		*(ecc_data1 + 2) = *(ecc_data1 + 2) / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		comp0_bit[i]     = *ecc_data2 % 2;
+		*ecc_data2       = *ecc_data2 / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		comp1_bit[i]     = *(ecc_data2 + 1) % 2;
+		*(ecc_data2 + 1) = *(ecc_data2 + 1) / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		comp2_bit[i]     = *(ecc_data2 + 2) % 2;
+		*(ecc_data2 + 2) = *(ecc_data2 + 2) / 2;
+	}
+
+	for (i = 0; i< 6; i++ )
+		ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2];
+
+	for (i = 0; i < 8; i++)
+		ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i];
+
+	for (i = 0; i < 8; i++)
+		ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i];
+
+	ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0];
+	ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1];
+
+	for (i = 0; i < 24; i++)
+		ecc_sum += ecc_bit[i];
+
+	switch (ecc_sum) {
+	case 0:
+		/* Not reached because this function is not called if
+		   ECC values are equal */
+		return 0;
+
+	case 1:
+		/* Uncorrectable error */
+		DEBUG (MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n");
+		return -1;
+
+	case 12:
+		/* Correctable error */
+		find_byte = (ecc_bit[23] << 8) + 
+			    (ecc_bit[21] << 7) + 
+			    (ecc_bit[19] << 6) +
+			    (ecc_bit[17] << 5) +
+			    (ecc_bit[15] << 4) +
+			    (ecc_bit[13] << 3) +
+			    (ecc_bit[11] << 2) +
+			    (ecc_bit[9]  << 1) +
+			    ecc_bit[7];
+
+		find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1];
+
+		DEBUG (MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at offset: %d, bit: %d\n", find_byte, find_bit);
+
+		page_data[find_byte] ^= (1 << find_bit);
+
+		return 0;
+	default:
+		if (isEccFF) {
+			if (ecc_data2[0] == 0 && ecc_data2[1] == 0 && ecc_data2[2] == 0)
+				return 0;
+		} 
+		DEBUG (MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n");
+		return -1;
+	}
+}
+
+static int omap_nand_correct_data(struct mtd_info *mtd, u_char *dat, u_char *read_ecc, u_char *calc_ecc)
+{
+	struct nand_chip *this;
+	int block_count = 0, i, r;
+
+	this = mtd->priv;
+	/* Ex NAND_ECC_HW12_2048 */
+	if ((this->ecc.mode == NAND_ECC_HW) && (this->ecc.size  == 2048))
+		block_count = 4;
+	else
+		block_count = 1;
+	for (i = 0; i < block_count; i++) {
+		if (memcmp(read_ecc, calc_ecc, 3) != 0) {
+			r = omap_nand_compare_ecc(read_ecc, calc_ecc, dat);
+			if (r < 0)
+				return r;
+		}
+		read_ecc += 3;
+		calc_ecc += 3;
+		dat += 512;
+	}
+	return 0;
+}
+
+static void omap_nand_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+	nand_write_reg(NND_RESET, 0x01);
+}
+
+#ifdef CONFIG_MTD_CMDLINE_PARTS
+
+extern int mtdpart_setup(char *);
+
+static int __init add_dynamic_parts(struct mtd_info *mtd)
+{
+	static const char *part_parsers[] = { "cmdlinepart", NULL };
+	struct mtd_partition *parts;
+	const struct omap_flash_part_str_config *cfg;
+	char *part_str = NULL;
+	size_t part_str_len;
+	int c;
+
+	cfg = omap_get_var_config(OMAP_TAG_FLASH_PART_STR, &part_str_len);
+	if (cfg != NULL) {
+		part_str = kmalloc(part_str_len + 1, GFP_KERNEL);
+		if (part_str == NULL)
+			return -ENOMEM;
+		memcpy(part_str, cfg->part_table, part_str_len);
+		part_str[part_str_len] = '\0';
+		mtdpart_setup(part_str);
+	}
+	c = parse_mtd_partitions(omap_mtd, part_parsers, &parts, 0);
+	if (part_str != NULL) {
+		mtdpart_setup(NULL);
+		kfree(part_str);
+	}
+	if (c <= 0)
+		return -1;
+
+	add_mtd_partitions(mtd, parts, c);
+
+	return 0;
+}
+
+#else
+
+static inline int add_dynamic_parts(struct mtd_info *mtd)
+{
+	return -1;
+}
+
+#endif
+
+static inline int calc_psc(int ns, int cycle_ps)
+{
+	return (ns * 1000 + (cycle_ps - 1)) / cycle_ps;
+}
+
+static void set_psc_regs(int psc_ns, int psc1_ns, int psc2_ns)
+{
+	int psc[3], i;
+	unsigned long rate, ps;
+
+	rate = clk_get_rate(omap_nand_clk);
+	ps = 1000000000 / (rate / 1000);
+	psc[0] = calc_psc(psc_ns, ps);
+	psc[1] = calc_psc(psc1_ns, ps);
+	psc[2] = calc_psc(psc2_ns, ps);
+	for (i = 0; i < 3; i++) {
+		if (psc[i] < 2)
+			psc[i] = 2;
+		else if (psc[i] > 256)
+			psc[i] = 256;
+	}
+	nand_write_reg(NND_PSC_CLK, psc[0] - 1);
+	nand_write_reg(NND_PSC1_CLK, psc[1] - 1);
+	nand_write_reg(NND_PSC2_CLK, psc[2] - 1);
+	printk(KERN_INFO "omap-hw-nand: using PSC values %d, %d, %d\n", psc[0], psc[1], psc[2]);
+}
+
+/*
+ * Main initialization routine
+ */
+static int __init omap_nand_init(void)
+{
+	struct nand_chip *this;
+	int err = 0;
+	u32 l;
+
+	omap_nand_clk = clk_get(NULL, "armper_ck");
+	BUG_ON(omap_nand_clk == NULL);
+	clk_enable(omap_nand_clk);
+
+	l = nand_read_reg(NND_REVISION);	
+	printk(KERN_INFO "omap-hw-nand: OMAP NAND Controller rev. %d.%d\n", l>>4, l & 0xf);
+
+	/* Reset the NAND Controller */
+	nand_write_reg(NND_SYSCFG, 0x02);
+	while ((nand_read_reg(NND_SYSSTATUS) & 0x01) == 0);
+
+	/* No Prefetch, no postwrite, write prot & enable pairs disabled,
+	   addres counter set to send 4 byte addresses to flash,
+	   A8 is set not to be sent to flash (erase addre needs formatting),
+	   choose little endian, enable 512 byte ECC logic,	   
+	 */
+	nand_write_reg(NND_CTRL, 0xFF01);
+
+	/* Allocate memory for MTD device structure and private data */
+	omap_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL);
+	if (!omap_mtd) {
+		printk(KERN_WARNING "omap-hw-nand: Unable to allocate OMAP NAND MTD device structure.\n");
+		err = -ENOMEM;
+		goto free_clock;
+	}
+#if 1
+	err = omap_request_dma(OMAP_DMA_NAND, "NAND", nand_dma_cb,
+			       &omap_nand_dma_comp, &omap_nand_dma_ch);
+	if (err < 0) {
+		printk(KERN_WARNING "omap-hw-nand: Unable to reserve DMA channel\n");
+		omap_nand_dma_ch = -1;
+	}
+#else
+	omap_nand_dma_ch = -1;
+#endif
+	/* Get pointer to private data */
+	this = (struct nand_chip *) (&omap_mtd[1]);
+
+	/* Initialize structures */
+	memset((char *) omap_mtd, 0, sizeof(struct mtd_info));
+	memset((char *) this, 0, sizeof(struct nand_chip));
+
+	/* Link the private data with the MTD structure */
+	omap_mtd->priv = this;
+	omap_mtd->name = "omap-nand";
+
+	this->options = NAND_SKIP_BBTSCAN;
+
+	/* Used from chip select and nand_command() */
+	this->read_byte = omap_nand_read_byte;
+
+	this->select_chip   = omap_nand_select_chip;
+	this->dev_ready     = omap_nand_dev_ready;
+	this->chip_delay    = 0;
+	this->ecc.mode      = NAND_ECC_HW;
+	this->ecc.bytes     = 3;
+	this->ecc.size      = 512;
+	this->cmdfunc       = omap_nand_command;
+	this->write_buf     = omap_nand_write_buf;
+	this->read_buf      = omap_nand_read_buf;
+	this->verify_buf    = omap_nand_verify_buf;
+	this->ecc.calculate = omap_nand_calculate_ecc;
+	this->ecc.correct   = omap_nand_correct_data;
+	this->ecc.hwctl     = omap_nand_enable_hwecc;
+
+	nand_write_reg(NND_SYSCFG, 0x1); /* Enable auto idle */
+	nand_write_reg(NND_PSC_CLK, 10);
+	/* Scan to find existance of the device */
+	if (nand_scan(omap_mtd, 1)) {
+		err = -ENXIO;
+		goto out_mtd;
+	}
+
+	set_psc_regs(25, 15, 35);
+	if (this->page_shift == 11) {
+		this->cmdfunc = omap_nand_command_lp;
+		l = nand_read_reg(NND_CTRL);
+		l |= 1 << 4; /* Set the A8 bit in CTRL reg */
+		nand_write_reg(NND_CTRL, l);
+		this->ecc.mode = NAND_ECC_HW;
+		this->ecc.steps = 1;
+		this->ecc.size = 2048;
+		this->ecc.bytes = 12;
+		nand_write_reg(NND_ECC_SELECT, 6);
+	}
+
+	/* We have to do bbt scanning ourselves */
+	if (this->scan_bbt (omap_mtd)) {
+		err = -ENXIO;
+		goto out_mtd;
+	}
+
+	err = add_dynamic_parts(omap_mtd);
+	if (err < 0) {
+		printk(KERN_ERR "omap-hw-nand: no partitions defined\n");
+		err = -ENODEV;
+		nand_release(omap_mtd);
+		goto out_mtd;
+	}
+	/* init completed */
+	return 0;
+out_mtd:
+	if (omap_nand_dma_ch >= 0)
+		omap_free_dma(omap_nand_dma_ch);
+	kfree(omap_mtd);
+free_clock:
+	clk_put(omap_nand_clk);
+	return err;
+}
+
+module_init(omap_nand_init);
+
+/*
+ * Clean up routine
+ */
+static void __exit omap_nand_cleanup (void)
+{
+	clk_disable(omap_nand_clk);
+	clk_put(omap_nand_clk);
+	nand_release(omap_mtd);
+	kfree(omap_mtd);
+}
+
+module_exit(omap_nand_cleanup);
+
diff --git a/drivers/mtd/nand/omap-nand-flash.c b/drivers/mtd/nand/omap-nand-flash.c
new file mode 100644
index 0000000..63a7968
--- /dev/null
+++ b/drivers/mtd/nand/omap-nand-flash.c
@@ -0,0 +1,186 @@
+/*
+ * drivers/mtd/nand/omap-nand-flash.c
+ *
+ * Copyright (c) 2004 Texas Instruments, Jian Zhang <jzhang@ti.com>
+ * Copyright (c) 2004 David Brownell
+ *
+ * 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/init.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+
+#include <asm/io.h>
+#include <mach/hardware.h>
+#include <asm/mach-types.h>
+#include <asm/mach/flash.h>
+#include <mach/tc.h>
+
+#include <asm/io.h>
+#include <mach/hardware.h>
+#include <mach/nand.h>
+
+#define	DRIVER_NAME	"omapnand"
+
+#ifdef CONFIG_MTD_PARTITIONS
+static const char *part_probes[] = { "cmdlinepart", NULL };
+#endif
+
+struct omap_nand_info {
+	struct omap_nand_platform_data *pdata;
+	struct mtd_partition	*parts;
+	struct mtd_info		mtd;
+	struct nand_chip	nand;
+};
+
+/*
+ *	hardware specific access to control-lines
+ *	NOTE:  boards may use different bits for these!!
+ *
+ *	ctrl:
+ *	NAND_NCE: bit 0 - don't care
+ *	NAND_CLE: bit 1 -> bit 1  (0x0002)
+ *	NAND_ALE: bit 2 -> bit 2  (0x0004)
+ */
+
+static void omap_nand_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+	struct nand_chip *chip = mtd->priv;
+	unsigned long mask;
+
+	if (cmd == NAND_CMD_NONE)
+		return;
+
+	mask = (ctrl & NAND_CLE) ? 0x02 : 0;
+	if (ctrl & NAND_ALE)
+		mask |= 0x04;
+	writeb(cmd, (unsigned long)chip->IO_ADDR_W | mask);
+}
+
+static int omap_nand_dev_ready(struct mtd_info *mtd)
+{
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd);
+
+	return info->pdata->dev_ready(info->pdata);
+}
+
+static int __devinit omap_nand_probe(struct platform_device *pdev)
+{
+	struct omap_nand_info		*info;
+	struct omap_nand_platform_data	*pdata = pdev->dev.platform_data;
+	struct resource			*res = pdev->resource;
+	unsigned long			size = res->end - res->start + 1;
+	int				err;
+
+	info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	if (!request_mem_region(res->start, size, pdev->dev.driver->name)) {
+		err = -EBUSY;
+		goto out_free_info;
+	}
+
+	info->nand.IO_ADDR_R = ioremap(res->start, size);
+	if (!info->nand.IO_ADDR_R) {
+		err = -ENOMEM;
+		goto out_release_mem_region;
+	}
+	info->nand.IO_ADDR_W = info->nand.IO_ADDR_R;
+	info->nand.cmd_ctrl = omap_nand_hwcontrol;
+	info->nand.ecc.mode = NAND_ECC_SOFT;
+	info->nand.options = pdata->options;
+	if (pdata->dev_ready)
+		info->nand.dev_ready = omap_nand_dev_ready;
+	else
+		info->nand.chip_delay = 20;
+
+	info->mtd.name = pdev->dev.bus_id;
+	info->mtd.priv = &info->nand;
+
+	info->pdata = pdata;
+
+	/* DIP switches on H2 and some other boards change between 8 and 16 bit
+	 * bus widths for flash.  Try the other width if the first try fails.
+	 */
+	if (nand_scan(&info->mtd, 1)) {
+		info->nand.options ^= NAND_BUSWIDTH_16;
+		if (nand_scan(&info->mtd, 1)) {
+			err = -ENXIO;
+			goto out_iounmap;
+		}
+	}
+	info->mtd.owner = THIS_MODULE;
+
+#ifdef CONFIG_MTD_PARTITIONS
+	err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0);
+	if (err > 0)
+		add_mtd_partitions(&info->mtd, info->parts, err);
+	else if (err < 0 && pdata->parts)
+		add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts);
+	else
+#endif
+		add_mtd_device(&info->mtd);
+
+	platform_set_drvdata(pdev, info);
+
+	return 0;
+
+out_iounmap:
+	iounmap(info->nand.IO_ADDR_R);
+out_release_mem_region:
+	release_mem_region(res->start, size);
+out_free_info:
+	kfree(info);
+
+	return err;
+}
+
+static int omap_nand_remove(struct platform_device *pdev)
+{
+	struct omap_nand_info *info = platform_get_drvdata(pdev);
+
+	platform_set_drvdata(pdev, NULL);
+	/* Release NAND device, its internal structures and partitions */
+	nand_release(&info->mtd);
+	iounmap(info->nand.IO_ADDR_R);
+	kfree(info);
+	return 0;
+}
+
+static struct platform_driver omap_nand_driver = {
+	.probe		= omap_nand_probe,
+	.remove		= omap_nand_remove,
+	.driver		= {
+		.name	= DRIVER_NAME,
+	},
+};
+MODULE_ALIAS(DRIVER_NAME);
+
+static int __init omap_nand_init(void)
+{
+	return platform_driver_register(&omap_nand_driver);
+}
+
+static void __exit omap_nand_exit(void)
+{
+	platform_driver_unregister(&omap_nand_driver);
+}
+
+module_init(omap_nand_init);
+module_exit(omap_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jian Zhang <jzhang@ti.com> (and others)");
+MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards");
+
diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c
new file mode 100644
index 0000000..a33944c
--- /dev/null
+++ b/drivers/mtd/nand/omap2.c
@@ -0,0 +1,757 @@
+/*
+ * drivers/mtd/nand/omap2.c
+ *
+ * Copyright (c) 2004 Texas Instruments, Jian Zhang <jzhang@ti.com>
+ * Copyright (c) 2004 Micron Technology Inc.
+ * Copyright (c) 2004 David Brownell
+ *
+ * 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/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/nand.h>
+#include <linux/mtd/partitions.h>
+#include <linux/io.h>
+
+#include <asm/dma.h>
+
+#include <mach/gpmc.h>
+#include <mach/nand.h>
+
+#define GPMC_IRQ_STATUS		0x18
+#define GPMC_ECC_CONFIG		0x1F4
+#define GPMC_ECC_CONTROL	0x1F8
+#define GPMC_ECC_SIZE_CONFIG	0x1FC
+#define GPMC_ECC1_RESULT	0x200
+
+#define	DRIVER_NAME	"omap2-nand"
+#define	NAND_IO_SIZE	SZ_4K
+
+#define	NAND_WP_ON	1
+#define	NAND_WP_OFF	0
+#define NAND_WP_BIT	0x00000010
+#define WR_RD_PIN_MONITORING	0x00600000
+
+#define	GPMC_BUF_FULL	0x00000001
+#define	GPMC_BUF_EMPTY	0x00000000
+
+#define NAND_Ecc_P1e		(1 << 0)
+#define NAND_Ecc_P2e		(1 << 1)
+#define NAND_Ecc_P4e		(1 << 2)
+#define NAND_Ecc_P8e		(1 << 3)
+#define NAND_Ecc_P16e		(1 << 4)
+#define NAND_Ecc_P32e		(1 << 5)
+#define NAND_Ecc_P64e		(1 << 6)
+#define NAND_Ecc_P128e		(1 << 7)
+#define NAND_Ecc_P256e		(1 << 8)
+#define NAND_Ecc_P512e		(1 << 9)
+#define NAND_Ecc_P1024e		(1 << 10)
+#define NAND_Ecc_P2048e		(1 << 11)
+
+#define NAND_Ecc_P1o		(1 << 16)
+#define NAND_Ecc_P2o		(1 << 17)
+#define NAND_Ecc_P4o		(1 << 18)
+#define NAND_Ecc_P8o		(1 << 19)
+#define NAND_Ecc_P16o		(1 << 20)
+#define NAND_Ecc_P32o		(1 << 21)
+#define NAND_Ecc_P64o		(1 << 22)
+#define NAND_Ecc_P128o		(1 << 23)
+#define NAND_Ecc_P256o		(1 << 24)
+#define NAND_Ecc_P512o		(1 << 25)
+#define NAND_Ecc_P1024o		(1 << 26)
+#define NAND_Ecc_P2048o		(1 << 27)
+
+#define TF(value)	(value ? 1 : 0)
+
+#define P2048e(a)	(TF(a & NAND_Ecc_P2048e)	<< 0)
+#define P2048o(a)	(TF(a & NAND_Ecc_P2048o)	<< 1)
+#define P1e(a)		(TF(a & NAND_Ecc_P1e)		<< 2)
+#define P1o(a)		(TF(a & NAND_Ecc_P1o)		<< 3)
+#define P2e(a)		(TF(a & NAND_Ecc_P2e)		<< 4)
+#define P2o(a)		(TF(a & NAND_Ecc_P2o)		<< 5)
+#define P4e(a)		(TF(a & NAND_Ecc_P4e)		<< 6)
+#define P4o(a)		(TF(a & NAND_Ecc_P4o)		<< 7)
+
+#define P8e(a)		(TF(a & NAND_Ecc_P8e)		<< 0)
+#define P8o(a)		(TF(a & NAND_Ecc_P8o)		<< 1)
+#define P16e(a)		(TF(a & NAND_Ecc_P16e)		<< 2)
+#define P16o(a)		(TF(a & NAND_Ecc_P16o)		<< 3)
+#define P32e(a)		(TF(a & NAND_Ecc_P32e)		<< 4)
+#define P32o(a)		(TF(a & NAND_Ecc_P32o)		<< 5)
+#define P64e(a)		(TF(a & NAND_Ecc_P64e)		<< 6)
+#define P64o(a)		(TF(a & NAND_Ecc_P64o)		<< 7)
+
+#define P128e(a)	(TF(a & NAND_Ecc_P128e)		<< 0)
+#define P128o(a)	(TF(a & NAND_Ecc_P128o)		<< 1)
+#define P256e(a)	(TF(a & NAND_Ecc_P256e)		<< 2)
+#define P256o(a)	(TF(a & NAND_Ecc_P256o)		<< 3)
+#define P512e(a)	(TF(a & NAND_Ecc_P512e)		<< 4)
+#define P512o(a)	(TF(a & NAND_Ecc_P512o)		<< 5)
+#define P1024e(a)	(TF(a & NAND_Ecc_P1024e)	<< 6)
+#define P1024o(a)	(TF(a & NAND_Ecc_P1024o)	<< 7)
+
+#define P8e_s(a)	(TF(a & NAND_Ecc_P8e)		<< 0)
+#define P8o_s(a)	(TF(a & NAND_Ecc_P8o)		<< 1)
+#define P16e_s(a)	(TF(a & NAND_Ecc_P16e)		<< 2)
+#define P16o_s(a)	(TF(a & NAND_Ecc_P16o)		<< 3)
+#define P1e_s(a)	(TF(a & NAND_Ecc_P1e)		<< 4)
+#define P1o_s(a)	(TF(a & NAND_Ecc_P1o)		<< 5)
+#define P2e_s(a)	(TF(a & NAND_Ecc_P2e)		<< 6)
+#define P2o_s(a)	(TF(a & NAND_Ecc_P2o)		<< 7)
+
+#define P4e_s(a)	(TF(a & NAND_Ecc_P4e)		<< 0)
+#define P4o_s(a)	(TF(a & NAND_Ecc_P4o)		<< 1)
+
+#ifdef CONFIG_MTD_PARTITIONS
+static const char *part_probes[] = { "cmdlinepart", NULL };
+#endif
+
+struct omap_nand_info {
+	struct nand_hw_control		controller;
+	struct omap_nand_platform_data	*pdata;
+	struct mtd_info			mtd;
+	struct mtd_partition		*parts;
+	struct nand_chip		nand;
+	struct platform_device		*pdev;
+
+	int				gpmc_cs;
+	unsigned long			phys_base;
+	void __iomem			*gpmc_cs_baseaddr;
+	void __iomem			*gpmc_baseaddr;
+};
+
+/*
+ * omap_nand_wp - This function enable or disable the Write Protect feature on
+ * NAND device
+ * @mtd: MTD device structure
+ * @mode: WP ON/OFF
+ */
+static void omap_nand_wp(struct mtd_info *mtd, int mode)
+{
+	struct omap_nand_info *info = container_of(mtd,
+						struct omap_nand_info, mtd);
+
+	unsigned long config = __raw_readl(info->gpmc_baseaddr + GPMC_CONFIG);
+
+	if (mode)
+		config &= ~(NAND_WP_BIT);	/* WP is ON */
+	else
+		config |= (NAND_WP_BIT);	/* WP is OFF */
+
+	__raw_writel(config, (info->gpmc_baseaddr + GPMC_CONFIG));
+}
+
+/*
+ * hardware specific access to control-lines
+ * NOTE: boards may use different bits for these!!
+ *
+ * ctrl:
+ * NAND_NCE: bit 0 - don't care
+ * NAND_CLE: bit 1 -> Command Latch
+ * NAND_ALE: bit 2 -> Address Latch
+ */
+static void omap_hwcontrol(struct mtd_info *mtd, int cmd, unsigned int ctrl)
+{
+	struct omap_nand_info *info = container_of(mtd,
+					struct omap_nand_info, mtd);
+	switch (ctrl) {
+	case NAND_CTRL_CHANGE | NAND_CTRL_CLE:
+		info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr +
+						GPMC_CS_NAND_COMMAND;
+		info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr +
+						GPMC_CS_NAND_DATA;
+		break;
+
+	case NAND_CTRL_CHANGE | NAND_CTRL_ALE:
+		info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr +
+						GPMC_CS_NAND_ADDRESS;
+		info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr +
+						GPMC_CS_NAND_DATA;
+		break;
+
+	case NAND_CTRL_CHANGE | NAND_NCE:
+		info->nand.IO_ADDR_W = info->gpmc_cs_baseaddr +
+						GPMC_CS_NAND_DATA;
+		info->nand.IO_ADDR_R = info->gpmc_cs_baseaddr +
+						GPMC_CS_NAND_DATA;
+		break;
+	}
+
+	if (cmd != NAND_CMD_NONE)
+		__raw_writeb(cmd, info->nand.IO_ADDR_W);
+}
+
+/*
+ * omap_read_buf - read data from NAND controller into buffer
+ * @mtd: MTD device structure
+ * @buf: buffer to store date
+ * @len: number of bytes to read
+ */
+static void omap_read_buf(struct mtd_info *mtd, u_char *buf, int len)
+{
+	struct omap_nand_info *info = container_of(mtd,
+					struct omap_nand_info, mtd);
+	u16 *p = (u16 *) buf;
+
+	len >>= 1;
+
+	while (len--)
+		*p++ = cpu_to_le16(readw(info->nand.IO_ADDR_R));
+}
+
+/*
+ * omap_write_buf - write buffer to NAND controller
+ * @mtd: MTD device structure
+ * @buf: data buffer
+ * @len: number of bytes to write
+ */
+static void omap_write_buf(struct mtd_info *mtd, const u_char * buf, int len)
+{
+	struct omap_nand_info *info = container_of(mtd,
+						struct omap_nand_info, mtd);
+	u16 *p = (u16 *) buf;
+
+	len >>= 1;
+
+	while (len--) {
+		writew(cpu_to_le16(*p++), info->nand.IO_ADDR_W);
+
+		while (GPMC_BUF_EMPTY == (readl(info->gpmc_baseaddr +
+						GPMC_STATUS) & GPMC_BUF_FULL));
+	}
+}
+/*
+ * omap_verify_buf - Verify chip data against buffer
+ * @mtd: MTD device structure
+ * @buf: buffer containing the data to compare
+ * @len: number of bytes to compare
+ */
+static int omap_verify_buf(struct mtd_info *mtd, const u_char * buf, int len)
+{
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+							mtd);
+	u16 *p = (u16 *) buf;
+
+	len >>= 1;
+
+	while (len--) {
+
+		if (*p++ != cpu_to_le16(readw(info->nand.IO_ADDR_R)))
+			return -EFAULT;
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_MTD_NAND_OMAP_HWECC
+/*
+ * omap_hwecc_init-Initialize the Hardware ECC for NAND flash in GPMC controller
+ * @mtd: MTD device structure
+ */
+static void omap_hwecc_init(struct mtd_info *mtd)
+{
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+							mtd);
+	register struct nand_chip *chip = mtd->priv;
+	unsigned long val = 0x0;
+
+	/* Read from ECC Control Register */
+	val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_CONTROL);
+	/* Clear all ECC | Enable Reg1 */
+	val = ((0x00000001<<8) | 0x00000001);
+	__raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_CONTROL);
+
+	/* Read from ECC Size Config Register */
+	val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_SIZE_CONFIG);
+	/* ECCSIZE1=512 | Select eccResultsize[0-3] */
+	val = ((((chip->ecc.size >> 1) - 1) << 22) | (0x0000000F));
+	__raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_SIZE_CONFIG);
+}
+
+/*
+ * gen_true_ecc - This function will generate true ECC value, which can be used
+ * when correcting data read from NAND flash memory core
+ * @ecc_buf: buffer to store ecc code
+ */
+static void gen_true_ecc(u8 *ecc_buf)
+{
+	u32 tmp = ecc_buf[0] | (ecc_buf[1] << 16) |
+		((ecc_buf[2] & 0xF0) << 20) | ((ecc_buf[2] & 0x0F) << 8);
+
+	ecc_buf[0] = ~(P64o(tmp) | P64e(tmp) | P32o(tmp) | P32e(tmp) |
+			P16o(tmp) | P16e(tmp) | P8o(tmp) | P8e(tmp));
+	ecc_buf[1] = ~(P1024o(tmp) | P1024e(tmp) | P512o(tmp) | P512e(tmp) |
+			P256o(tmp) | P256e(tmp) | P128o(tmp) | P128e(tmp));
+	ecc_buf[2] = ~(P4o(tmp) | P4e(tmp) | P2o(tmp) | P2e(tmp) | P1o(tmp) |
+			P1e(tmp) | P2048o(tmp) | P2048e(tmp));
+}
+
+/*
+ * omap_compare_ecc - This function compares two ECC's and indicates if there
+ * is an error. If the error can be corrected it will be corrected to the
+ * buffer
+ * @ecc_data1:  ecc code from nand spare area
+ * @ecc_data2:  ecc code from hardware register obtained from hardware ecc
+ * @page_data:  page data
+ */
+static int omap_compare_ecc(u8 *ecc_data1,	/* read from NAND memory */
+			    u8 *ecc_data2,	/* read from register */
+			    u8 *page_data)
+{
+	uint	i;
+	u8	tmp0_bit[8], tmp1_bit[8], tmp2_bit[8];
+	u8	comp0_bit[8], comp1_bit[8], comp2_bit[8];
+	u8	ecc_bit[24];
+	u8	ecc_sum = 0;
+	u8	find_bit = 0;
+	uint	find_byte = 0;
+	int	isEccFF;
+
+	isEccFF = ((*(u32 *)ecc_data1 & 0xFFFFFF) == 0xFFFFFF);
+
+	gen_true_ecc(ecc_data1);
+	gen_true_ecc(ecc_data2);
+
+	for (i = 0; i <= 2; i++) {
+		*(ecc_data1 + i) = ~(*(ecc_data1 + i));
+		*(ecc_data2 + i) = ~(*(ecc_data2 + i));
+	}
+
+	for (i = 0; i < 8; i++) {
+		tmp0_bit[i]     = *ecc_data1 % 2;
+		*ecc_data1	= *ecc_data1 / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		tmp1_bit[i]	 = *(ecc_data1 + 1) % 2;
+		*(ecc_data1 + 1) = *(ecc_data1 + 1) / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		tmp2_bit[i]	 = *(ecc_data1 + 2) % 2;
+		*(ecc_data1 + 2) = *(ecc_data1 + 2) / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		comp0_bit[i]     = *ecc_data2 % 2;
+		*ecc_data2       = *ecc_data2 / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		comp1_bit[i]     = *(ecc_data2 + 1) % 2;
+		*(ecc_data2 + 1) = *(ecc_data2 + 1) / 2;
+	}
+
+	for (i = 0; i < 8; i++) {
+		comp2_bit[i]     = *(ecc_data2 + 2) % 2;
+		*(ecc_data2 + 2) = *(ecc_data2 + 2) / 2;
+	}
+
+	for (i = 0; i < 6; i++)
+		ecc_bit[i] = tmp2_bit[i + 2] ^ comp2_bit[i + 2];
+
+	for (i = 0; i < 8; i++)
+		ecc_bit[i + 6] = tmp0_bit[i] ^ comp0_bit[i];
+
+	for (i = 0; i < 8; i++)
+		ecc_bit[i + 14] = tmp1_bit[i] ^ comp1_bit[i];
+
+	ecc_bit[22] = tmp2_bit[0] ^ comp2_bit[0];
+	ecc_bit[23] = tmp2_bit[1] ^ comp2_bit[1];
+
+	for (i = 0; i < 24; i++)
+		ecc_sum += ecc_bit[i];
+
+	switch (ecc_sum) {
+	case 0:
+		/* Not reached because this function is not called if
+		 *  ECC values are equal
+		 */
+		return 0;
+
+	case 1:
+		/* Uncorrectable error */
+		DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR 1\n");
+		return -1;
+
+	case 11:
+		/* UN-Correctable error */
+		DEBUG(MTD_DEBUG_LEVEL0, "ECC UNCORRECTED_ERROR B\n");
+		return -1;
+
+	case 12:
+		/* Correctable error */
+		find_byte = (ecc_bit[23] << 8) +
+			    (ecc_bit[21] << 7) +
+			    (ecc_bit[19] << 6) +
+			    (ecc_bit[17] << 5) +
+			    (ecc_bit[15] << 4) +
+			    (ecc_bit[13] << 3) +
+			    (ecc_bit[11] << 2) +
+			    (ecc_bit[9]  << 1) +
+			    ecc_bit[7];
+
+		find_bit = (ecc_bit[5] << 2) + (ecc_bit[3] << 1) + ecc_bit[1];
+
+		DEBUG(MTD_DEBUG_LEVEL0, "Correcting single bit ECC error at "
+				"offset: %d, bit: %d\n", find_byte, find_bit);
+
+		page_data[find_byte] ^= (1 << find_bit);
+
+		return 0;
+	default:
+		if (isEccFF) {
+			if (ecc_data2[0] == 0 &&
+			    ecc_data2[1] == 0 &&
+			    ecc_data2[2] == 0)
+				return 0;
+		}
+		DEBUG(MTD_DEBUG_LEVEL0, "UNCORRECTED_ERROR default\n");
+		return -1;
+	}
+}
+
+/*
+ * omap_correct_data - Compares the ecc read from nand spare area with ECC
+ * registers values and corrects one bit error if it has occured
+ * @mtd: MTD device structure
+ * @dat: page data
+ * @read_ecc: ecc read from nand flash
+ * @calc_ecc: ecc read from ECC registers
+ */
+static int omap_correct_data(struct mtd_info *mtd, u_char * dat,
+				u_char * read_ecc, u_char * calc_ecc)
+{
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+							mtd);
+	int blockCnt = 0, i = 0, ret = 0;
+
+	/* Ex NAND_ECC_HW12_2048 */
+	if ((info->nand.ecc.mode == NAND_ECC_HW) &&
+			(info->nand.ecc.size  == 2048))
+		blockCnt = 4;
+	else
+		blockCnt = 1;
+
+	for (i = 0; i < blockCnt; i++) {
+		if (memcmp(read_ecc, calc_ecc, 3) != 0) {
+			ret = omap_compare_ecc(read_ecc, calc_ecc, dat);
+			if (ret < 0) return ret;
+		}
+		read_ecc += 3;
+		calc_ecc += 3;
+		dat      += 512;
+	}
+	return 0;
+}
+
+/*
+ * omap_calcuate_ecc - Generate non-inverted ECC bytes.
+ * Using noninverted ECC can be considered ugly since writing a blank
+ * page ie. padding will clear the ECC bytes. This is no problem as long
+ * nobody is trying to write data on the seemingly unused page. Reading
+ * an erased page will produce an ECC mismatch between generated and read
+ * ECC bytes that has to be dealt with separately.
+ * @mtd: MTD device structure
+ * @dat: The pointer to data on which ecc is computed
+ * @ecc_code: The ecc_code buffer
+ */
+static int omap_calculate_ecc(struct mtd_info *mtd, const u_char *dat,
+				u_char *ecc_code)
+{
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+							mtd);
+	unsigned long val = 0x0;
+	unsigned long reg;
+
+	/* Start Reading from HW ECC1_Result = 0x200 */
+	reg = (unsigned long)(info->gpmc_baseaddr + GPMC_ECC1_RESULT);
+	val = __raw_readl(reg);
+	*ecc_code++ = val;          /* P128e, ..., P1e */
+	*ecc_code++ = val >> 16;    /* P128o, ..., P1o */
+	/* P2048o, P1024o, P512o, P256o, P2048e, P1024e, P512e, P256e */
+	*ecc_code++ = ((val >> 8) & 0x0f) | ((val >> 20) & 0xf0);
+	reg += 4;
+
+	return 0;
+}
+
+/*
+ * omap_enable_hwecc - This function enables the hardware ecc functionality
+ * @mtd: MTD device structure
+ * @mode: Read/Write mode
+ */
+static void omap_enable_hwecc(struct mtd_info *mtd, int mode)
+{
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+							mtd);
+	register struct nand_chip *chip = mtd->priv;
+	unsigned int dev_width = (chip->options & NAND_BUSWIDTH_16) ? 1 : 0;
+	unsigned long val = __raw_readl(info->gpmc_baseaddr + GPMC_ECC_CONFIG);
+
+	switch (mode) {
+	case NAND_ECC_READ    :
+		__raw_writel(0x101, info->gpmc_baseaddr + GPMC_ECC_CONTROL);
+		/* (ECC 16 or 8 bit col) | ( CS  )  | ECC Enable */
+		val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1);
+		break;
+	case NAND_ECC_READSYN :
+		 __raw_writel(0x100, info->gpmc_baseaddr + GPMC_ECC_CONTROL);
+		/* (ECC 16 or 8 bit col) | ( CS  )  | ECC Enable */
+		val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1);
+		break;
+	case NAND_ECC_WRITE   :
+		__raw_writel(0x101, info->gpmc_baseaddr + GPMC_ECC_CONTROL);
+		/* (ECC 16 or 8 bit col) | ( CS  )  | ECC Enable */
+		val = (dev_width << 7) | (info->gpmc_cs << 1) | (0x1);
+		break;
+	default:
+		DEBUG(MTD_DEBUG_LEVEL0, "Error: Unrecognized Mode[%d]!\n",
+					mode);
+		break;
+	}
+
+	__raw_writel(val, info->gpmc_baseaddr + GPMC_ECC_CONFIG);
+}
+#endif
+
+/*
+ * omap_wait - Wait function is called during Program and erase
+ * operations and the way it is called from MTD layer, we should wait
+ * till the NAND chip is ready after the programming/erase operation
+ * has completed.
+ * @mtd: MTD device structure
+ * @chip: NAND Chip structure
+ */
+static int omap_wait(struct mtd_info *mtd, struct nand_chip *chip)
+{
+	register struct nand_chip *this = mtd->priv;
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+							mtd);
+	int status = 0;
+
+	this->IO_ADDR_W = (void *) info->gpmc_cs_baseaddr +
+						GPMC_CS_NAND_COMMAND;
+	this->IO_ADDR_R = (void *) info->gpmc_cs_baseaddr + GPMC_CS_NAND_DATA;
+
+	while (!(status & 0x40)) {
+		 __raw_writeb(NAND_CMD_STATUS & 0xFF, this->IO_ADDR_W);
+		status = __raw_readb(this->IO_ADDR_R);
+	}
+	return status;
+}
+
+/*
+ * omap_dev_ready - calls the platform specific dev_ready function
+ * @mtd: MTD device structure
+ */
+static int omap_dev_ready(struct mtd_info *mtd)
+{
+	struct omap_nand_info *info = container_of(mtd, struct omap_nand_info,
+							mtd);
+	unsigned int val = __raw_readl(info->gpmc_baseaddr + GPMC_IRQ_STATUS);
+
+	if ((val & 0x100) == 0x100) {
+		/* Clear IRQ Interrupt */
+		val |= 0x100;
+		val &= ~(0x0);
+		__raw_writel(val, info->gpmc_baseaddr + GPMC_IRQ_STATUS);
+	} else {
+		unsigned int cnt = 0;
+		while (cnt++ < 0x1FF) {
+			if  ((val & 0x100) == 0x100)
+				return 0;
+			val = __raw_readl(info->gpmc_baseaddr +
+							GPMC_IRQ_STATUS);
+		}
+	}
+
+	return 1;
+}
+
+static int __devinit omap_nand_probe(struct platform_device *pdev)
+{
+	struct omap_nand_info		*info;
+	struct omap_nand_platform_data	*pdata;
+	int				err;
+	unsigned long 			val;
+
+
+	pdata = pdev->dev.platform_data;
+	if (pdata == NULL) {
+		dev_err(&pdev->dev, "platform data missing\n");
+		return -ENODEV;
+	}
+
+	info = kzalloc(sizeof(struct omap_nand_info), GFP_KERNEL);
+	if (!info) return -ENOMEM;
+
+	platform_set_drvdata(pdev, info);
+
+	spin_lock_init(&info->controller.lock);
+	init_waitqueue_head(&info->controller.wq);
+
+	info->pdev = pdev;
+
+	info->gpmc_cs		= pdata->cs;
+	info->gpmc_baseaddr	= pdata->gpmc_baseaddr;
+	info->gpmc_cs_baseaddr	= pdata->gpmc_cs_baseaddr;
+
+	info->mtd.priv		= &info->nand;
+	info->mtd.name		= pdev->dev.bus_id;
+	info->mtd.owner		= THIS_MODULE;
+
+	err = gpmc_cs_request(info->gpmc_cs, NAND_IO_SIZE, &info->phys_base);
+	if (err < 0) {
+		dev_err(&pdev->dev, "Cannot request GPMC CS\n");
+		goto out_free_info;
+	}
+
+	/* Enable RD PIN Monitoring Reg */
+	if (pdata->dev_ready) {
+		val  = gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG1);
+		val |= WR_RD_PIN_MONITORING;
+		gpmc_cs_write_reg(info->gpmc_cs, GPMC_CS_CONFIG1, val);
+	}
+
+	val  = gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG7);
+	val &= ~(0xf << 8);
+	val |=  (0xc & 0xf) << 8;
+	gpmc_cs_write_reg(info->gpmc_cs, GPMC_CS_CONFIG7, val);
+
+	/* NAND write protect off */
+	omap_nand_wp(&info->mtd, NAND_WP_OFF);
+
+	if (!request_mem_region(info->phys_base, NAND_IO_SIZE,
+				pdev->dev.driver->name)) {
+		err = -EBUSY;
+		goto out_free_cs;
+	}
+
+	info->nand.IO_ADDR_R = ioremap(info->phys_base, NAND_IO_SIZE);
+	if (!info->nand.IO_ADDR_R) {
+		err = -ENOMEM;
+		goto out_release_mem_region;
+	}
+	info->nand.controller = &info->controller;
+
+	info->nand.IO_ADDR_W = info->nand.IO_ADDR_R;
+	info->nand.cmd_ctrl  = omap_hwcontrol;
+
+	info->nand.read_buf   = omap_read_buf;
+	info->nand.write_buf  = omap_write_buf;
+	info->nand.verify_buf = omap_verify_buf;
+
+	/*
+	* If RDY/BSY line is connected to OMAP then use the omap ready funcrtion
+	* and the generic nand_wait function which reads the status register
+	* after monitoring the RDY/BSY line.Otherwise use a standard chip delay
+	* which is slightly more than tR (AC Timing) of the NAND device and read
+	* status register until you get a failure or success
+	*/
+	if (pdata->dev_ready) {
+		info->nand.dev_ready = omap_dev_ready;
+		info->nand.chip_delay = 0;
+	} else {
+		info->nand.waitfunc = omap_wait;
+		info->nand.chip_delay = 50;
+	}
+
+	info->nand.options  |= NAND_SKIP_BBTSCAN;
+	if ((gpmc_cs_read_reg(info->gpmc_cs, GPMC_CS_CONFIG1) & 0x3000)
+								== 0x1000)
+		info->nand.options  |= NAND_BUSWIDTH_16;
+
+#ifdef CONFIG_MTD_NAND_OMAP_HWECC
+	info->nand.ecc.bytes		= 3;
+	info->nand.ecc.size		= 512;
+	info->nand.ecc.calculate	= omap_calculate_ecc;
+	info->nand.ecc.hwctl		= omap_enable_hwecc;
+	info->nand.ecc.correct		= omap_correct_data;
+	info->nand.ecc.mode		= NAND_ECC_HW;
+
+	/* init HW ECC */
+	omap_hwecc_init(&info->mtd);
+#else
+	info->nand.ecc.mode = NAND_ECC_SOFT;
+#endif
+
+	/* DIP switches on some boards change between 8 and 16 bit
+	 * bus widths for flash.  Try the other width if the first try fails.
+	 */
+	if (nand_scan(&info->mtd, 1)) {
+		info->nand.options ^= NAND_BUSWIDTH_16;
+		if (nand_scan(&info->mtd, 1)) {
+			err = -ENXIO;
+			goto out_release_mem_region;
+		}
+	}
+
+#ifdef CONFIG_MTD_PARTITIONS
+	err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0);
+	if (err > 0)
+		add_mtd_partitions(&info->mtd, info->parts, err);
+	else if (pdata->parts)
+		add_mtd_partitions(&info->mtd, pdata->parts, pdata->nr_parts);
+	else
+#endif
+		add_mtd_device(&info->mtd);
+
+	platform_set_drvdata(pdev, &info->mtd);
+
+	return 0;
+
+out_release_mem_region:
+	release_mem_region(info->phys_base, NAND_IO_SIZE);
+out_free_cs:
+	gpmc_cs_free(info->gpmc_cs);
+out_free_info:
+	kfree(info);
+
+	return err;
+}
+
+static int omap_nand_remove(struct platform_device *pdev)
+{
+	struct mtd_info *mtd = platform_get_drvdata(pdev);
+	struct omap_nand_info *info = mtd->priv;
+
+	platform_set_drvdata(pdev, NULL);
+	/* Release NAND device, its internal structures and partitions */
+	nand_release(&info->mtd);
+	iounmap(info->nand.IO_ADDR_R);
+	kfree(&info->mtd);
+	return 0;
+}
+
+static struct platform_driver omap_nand_driver = {
+	.probe		= omap_nand_probe,
+	.remove		= omap_nand_remove,
+	.driver		= {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+	},
+};
+MODULE_ALIAS(DRIVER_NAME);
+
+static int __init omap_nand_init(void)
+{
+	printk(KERN_INFO "%s driver initializing\n", DRIVER_NAME);
+	return platform_driver_register(&omap_nand_driver);
+}
+
+static void __exit omap_nand_exit(void)
+{
+	platform_driver_unregister(&omap_nand_driver);
+}
+
+module_init(omap_nand_init);
+module_exit(omap_nand_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Glue layer for NAND flash on TI OMAP boards");
-- 
1.6.0.1.141.g445ca

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 23/33] add omap irda driver
  2008-08-30 17:16                                           ` [PATCH 22/33] add omap nand drivers Felipe Balbi
@ 2008-08-30 17:16                                             ` Felipe Balbi
  2008-08-30 17:16                                               ` [PATCH 24/33] add bq27x00 battery driver Felipe Balbi
  2008-08-31 21:08                                             ` [PATCH 22/33] add omap nand drivers David Brownell
  1 sibling, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/net/irda/Kconfig   |   10 +
 drivers/net/irda/Makefile  |    1 +
 drivers/net/irda/omap-ir.c |  901 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 912 insertions(+), 0 deletions(-)
 create mode 100644 drivers/net/irda/omap-ir.c

diff --git a/drivers/net/irda/Kconfig b/drivers/net/irda/Kconfig
index e631755..b83e25b 100644
--- a/drivers/net/irda/Kconfig
+++ b/drivers/net/irda/Kconfig
@@ -342,5 +342,15 @@ config MCS_FIR
 	  To compile it as a module, choose M here: the module will be called
 	  mcs7780.
 
+config OMAP_IR
+	tristate "OMAP IrDA(SIR/MIR/FIR)"
+	depends on IRDA && ARCH_OMAP
+	select GPIOEXPANDER_OMAP if (MACH_OMAP_H3 || MACH_OMAP_H4)
+        help
+	  Say Y here if you want to build support for the Texas Instruments
+	  OMAP IrDA device driver, which supports SIR/MIR/FIR. This driver
+	  relies on platform specific helper routines so available capabilities
+	  may vary from one OMAP target to another.
+
 endmenu
 
diff --git a/drivers/net/irda/Makefile b/drivers/net/irda/Makefile
index 5d20fde..49e6f06 100644
--- a/drivers/net/irda/Makefile
+++ b/drivers/net/irda/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_VLSI_FIR)		+= vlsi_ir.o
 obj-$(CONFIG_VIA_FIR)		+= via-ircc.o
 obj-$(CONFIG_PXA_FICP)	        += pxaficp_ir.o
 obj-$(CONFIG_MCS_FIR)	        += mcs7780.o
+obj-$(CONFIG_OMAP_IR)		+= omap-ir.o
 obj-$(CONFIG_AU1000_FIR)	+= au1k_ir.o
 # SIR drivers
 obj-$(CONFIG_IRTTY_SIR)		+= irtty-sir.o	sir-dev.o
diff --git a/drivers/net/irda/omap-ir.c b/drivers/net/irda/omap-ir.c
new file mode 100644
index 0000000..b706253
--- /dev/null
+++ b/drivers/net/irda/omap-ir.c
@@ -0,0 +1,901 @@
+/*
+ * BRIEF MODULE DESCRIPTION
+ *
+ *	Infra-red driver for the OMAP1610-H2 and OMAP1710-H3 and H4 Platforms
+ *	  (SIR/MIR/FIR modes)
+ *	  (based on omap-sir.c)
+ *
+ * Copyright 2003 MontaVista Software Inc.
+ * Author: MontaVista Software, Inc.
+ *	   source@mvista.com
+ *
+ * Copyright 2004 Texas Instruments.
+ *
+ *  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.
+ *
+ *  THIS  SOFTWARE  IS PROVIDED	  ``AS	IS'' AND   ANY	EXPRESS OR IMPLIED
+ *  WARRANTIES,	  INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO	EVENT  SHALL   THE AUTHOR  BE	 LIABLE FOR ANY	  DIRECT, INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED	  TO, PROCUREMENT OF  SUBSTITUTE GOODS	OR SERVICES; LOSS OF
+ *  USE, DATA,	OR PROFITS; OR	BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN	 CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ *
+ Modifications:
+ Feb 2004, Texas Instruments
+ - Ported to 2.6 kernel (Feb 2004).
+ *
+ Apr 2004, Texas Instruments
+ - Added support for H3 (Apr 2004).
+ Nov 2004, Texas Instruments
+ - Added support for Power Management.
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/slab.h>
+#include <linux/rtnetlink.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/ioport.h>
+#include <linux/dma-mapping.h>
+#include <linux/platform_device.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+
+#include <net/irda/irda.h>
+#include <net/irda/irmod.h>
+#include <net/irda/wrapper.h>
+#include <net/irda/irda_device.h>
+
+#include <asm/irq.h>
+#include <asm/io.h>
+#include <mach/hardware.h>
+#include <asm/serial.h>
+#include <asm/mach-types.h>
+#include <asm/dma.h>
+#include <mach/mux.h>
+#include <mach/gpio.h>
+#include <mach/irda.h>
+
+#define UART3_EFR_EN			(1 << 4)
+#define UART3_MCR_EN_TCR_TLR		(1 << 6)
+
+#define UART3_LCR_WL_8			(3 << 0)
+#define UART3_LCR_SP2			(1 << 2)
+#define UART3_LCR_DIVEN			(1 << 7)
+
+#define UART3_FCR_FIFO_EN		(1 << 0)
+#define UART3_FCR_FIFO_RX		(1 << 1)
+#define UART3_FCR_FIFO_TX		(1 << 2)
+#define UART3_FCR_FIFO_DMA1		(1 << 3)
+#define UART3_FCR_FIFO_TX_TRIG16	(1 << 4)
+#define UART3_FCR_FIFO_RX_TRIG16	(1 << 6)
+#define UART3_FCR_CONFIG	(\
+		UART3_FCR_FIFO_EN | UART3_FCR_FIFO_RX	|\
+		UART3_FCR_FIFO_TX | UART3_FCR_FIFO_DMA1 |\
+		UART3_FCR_FIFO_TX_TRIG16		|\
+		UART3_FCR_FIFO_RX_TRIG16)
+
+#define UART3_SCR_TX_TRIG1		(1 << 6)
+#define UART3_SCR_RX_TRIG1		(1 << 7)
+
+#define UART3_MDR1_RESET		(0x07)
+#define UART3_MDR1_SIR			(1 << 0)
+#define UART3_MDR1_MIR			(4 << 0)
+#define UART3_MDR1_FIR			(5 << 0)
+#define UART3_MDR1_SIP_AUTO		(1 << 6)
+
+#define UART3_MDR2_TRIG1		(0 << 1)
+#define UART3_MDR2_IRTX_UNDERRUN	(1 << 0)
+
+#define UART3_ACERG_TX_UNDERRUN_DIS	(1 << 4)
+#define UART3_ACERG_SD_MODE_LOW		(1 << 6)
+#define UART3_ACERG_DIS_IR_RX		(1 << 5)
+
+#define UART3_IER_EOF			(1 << 5)
+#define UART3_IER_CTS			(1 << 7)
+
+#define UART3_IIR_TX_STATUS		(1 << 5)
+#define UART3_IIR_EOF			(0x80)
+
+#define IS_FIR(omap_ir)		((omap_ir)->speed >= 4000000)
+
+struct omap_irda {
+	unsigned char open;
+	int speed;		/* Current IrDA speed */
+	int newspeed;
+
+	struct net_device_stats stats;
+	struct irlap_cb *irlap;
+	struct qos_info qos;
+
+	int rx_dma_channel;
+	int tx_dma_channel;
+
+	dma_addr_t rx_buf_dma_phys;	/* Physical address of RX DMA buffer */
+	dma_addr_t tx_buf_dma_phys;	/* Physical address of TX DMA buffer */
+
+	void *rx_buf_dma_virt;		/* Virtual address of RX DMA buffer */
+	void *tx_buf_dma_virt;		/* Virtual address of TX DMA buffer */
+
+	struct device *dev;
+	struct omap_irda_config *pdata;
+};
+
+static void inline uart_reg_out(int idx, u8 val)
+{
+	omap_writeb(val, idx);
+}
+
+static u8 inline uart_reg_in(int idx)
+{
+	u8 b = omap_readb(idx);
+	return b;
+}
+
+/* forward declarations */
+extern void omap_stop_dma(int lch);
+static int omap_irda_set_speed(struct net_device *dev, int speed);
+
+static void omap_irda_start_rx_dma(struct omap_irda *omap_ir)
+{
+	/* Configure DMA */
+	omap_set_dma_src_params(omap_ir->rx_dma_channel, 0x3, 0x0,
+				omap_ir->pdata->src_start,
+				0, 0);
+
+	omap_enable_dma_irq(omap_ir->rx_dma_channel, 0x01);
+
+	omap_set_dma_dest_params(omap_ir->rx_dma_channel, 0x0, 0x1,
+				omap_ir->rx_buf_dma_phys,
+				0, 0);
+
+	omap_set_dma_transfer_params(omap_ir->rx_dma_channel, 0x0,
+				IRDA_SKB_MAX_MTU, 0x1,
+				0x0, omap_ir->pdata->rx_trigger, 0);
+
+	omap_start_dma(omap_ir->rx_dma_channel);
+}
+
+static void omap_start_tx_dma(struct omap_irda *omap_ir, int size)
+{
+	/* Configure DMA */
+	omap_set_dma_dest_params(omap_ir->tx_dma_channel, 0x03, 0x0,
+				omap_ir->pdata->dest_start, 0, 0);
+
+	omap_enable_dma_irq(omap_ir->tx_dma_channel, 0x01);
+
+	omap_set_dma_src_params(omap_ir->tx_dma_channel, 0x0, 0x1,
+				omap_ir->tx_buf_dma_phys,
+				0, 0);
+
+	omap_set_dma_transfer_params(omap_ir->tx_dma_channel, 0x0, size, 0x1,
+				0x0, omap_ir->pdata->tx_trigger, 0);
+
+	/* Start DMA */
+	omap_start_dma(omap_ir->tx_dma_channel);
+}
+
+/* DMA RX callback - normally, we should not go here,
+ * it calls only if something is going wrong
+ */
+static void omap_irda_rx_dma_callback(int lch, u16 ch_status, void *data)
+{
+	struct net_device *dev = data;
+	struct omap_irda *omap_ir = netdev_priv(dev);
+
+	printk(KERN_ERR "RX Transfer error or very big frame\n");
+
+	/* Clear interrupts */
+	uart_reg_in(UART3_IIR);
+
+	omap_ir->stats.rx_frame_errors++;
+
+	uart_reg_in(UART3_RESUME);
+
+	/* Re-init RX DMA */
+	omap_irda_start_rx_dma(omap_ir);
+}
+
+/* DMA TX callback - calling when frame transfer has been finished */
+static void omap_irda_tx_dma_callback(int lch, u16 ch_status, void *data)
+{
+	struct net_device *dev = data;
+	struct omap_irda *omap_ir = netdev_priv(dev);
+
+	/*Stop DMA controller */
+	omap_stop_dma(omap_ir->tx_dma_channel);
+}
+
+/*
+ * Set the IrDA communications speed.
+ * Interrupt have to be disabled here.
+ */
+static int omap_irda_startup(struct net_device *dev)
+{
+	struct omap_irda *omap_ir = netdev_priv(dev);
+
+	/* FIXME: use clk_* apis for UART3 clock*/
+	/* Enable UART3 clock and set UART3 to IrDA mode */
+	if (machine_is_omap_h2() || machine_is_omap_h3())
+		omap_writel(omap_readl(MOD_CONF_CTRL_0) | (1 << 31) | (1 << 15),
+				MOD_CONF_CTRL_0);
+
+	/* Only for H2?
+	 */
+	if (omap_ir->pdata->transceiver_mode && machine_is_omap_h2()) {
+		/* Is it select_irda on H2 ? */
+		omap_writel(omap_readl(FUNC_MUX_CTRL_A) | 7,
+					FUNC_MUX_CTRL_A);
+		omap_ir->pdata->transceiver_mode(omap_ir->dev, IR_SIRMODE);
+	}
+
+	uart_reg_out(UART3_MDR1, UART3_MDR1_RESET);	/* Reset mode */
+
+	/* Clear DLH and DLL */
+	uart_reg_out(UART3_LCR, UART3_LCR_DIVEN);
+
+	uart_reg_out(UART3_DLL, 0);
+	uart_reg_out(UART3_DLH, 0);
+	uart_reg_out(UART3_LCR, 0xbf);	/* FIXME: Add #define */
+
+	uart_reg_out(UART3_EFR, UART3_EFR_EN);
+	uart_reg_out(UART3_LCR, UART3_LCR_DIVEN);
+
+	/* Enable access to UART3_TLR and UART3_TCR registers */
+	uart_reg_out(UART3_MCR, UART3_MCR_EN_TCR_TLR);
+
+	uart_reg_out(UART3_SCR, 0);
+	/* Set Rx trigger to 1 and Tx trigger to 1 */
+	uart_reg_out(UART3_TLR, 0);
+
+	/* Set LCR to 8 bits and 1 stop bit */
+	uart_reg_out(UART3_LCR, 0x03);
+
+	/* Clear RX and TX FIFO and enable FIFO */
+	/* Use DMA Req for transfers */
+	uart_reg_out(UART3_FCR, UART3_FCR_CONFIG);
+
+	uart_reg_out(UART3_MCR, 0);
+
+	uart_reg_out(UART3_SCR, UART3_SCR_TX_TRIG1 |
+			UART3_SCR_RX_TRIG1);
+
+	/* Enable UART3 SIR Mode,(Frame-length method to end frames) */
+	uart_reg_out(UART3_MDR1, UART3_MDR1_SIR);
+
+	/* Set Status FIFO trig to 1 */
+	uart_reg_out(UART3_MDR2, 0);
+
+	/* Enables RXIR input */
+	/* and disable TX underrun */
+	/* SEND_SIP pulse */
+	uart_reg_out(UART3_ACREG, UART3_ACERG_SD_MODE_LOW |
+			UART3_ACERG_TX_UNDERRUN_DIS);
+
+	/* Enable EOF Interrupt only */
+	uart_reg_out(UART3_IER, UART3_IER_CTS | UART3_IER_EOF);
+
+	/* Set Maximum Received Frame size to 2048 bytes */
+	uart_reg_out(UART3_RXFLL, 0x00);
+	uart_reg_out(UART3_RXFLH, 0x08);
+
+	uart_reg_in(UART3_RESUME);
+
+	return 0;
+}
+
+static int omap_irda_shutdown(struct omap_irda *omap_ir)
+{
+	unsigned long flags;
+
+	local_irq_save(flags);
+
+	/* Disable all UART3 Interrupts */
+	uart_reg_out(UART3_IER, 0);
+
+	/* Disable UART3 and disable baud rate generator */
+	uart_reg_out(UART3_MDR1, UART3_MDR1_RESET);
+
+	/* set SD_MODE pin to high and Disable RX IR */
+	uart_reg_out(UART3_ACREG, (UART3_ACERG_DIS_IR_RX |
+			~(UART3_ACERG_SD_MODE_LOW)));
+
+	/* Clear DLH and DLL */
+	uart_reg_out(UART3_LCR, UART3_LCR_DIVEN);
+	uart_reg_out(UART3_DLL, 0);
+	uart_reg_out(UART3_DLH, 0);
+
+	local_irq_restore(flags);
+
+	return 0;
+}
+
+static irqreturn_t
+omap_irda_irq(int irq, void *dev_id)
+{
+	struct net_device *dev = dev_id;
+	struct omap_irda *omap_ir = netdev_priv(dev);
+	struct sk_buff *skb;
+
+	u8 status;
+	int w = 0;
+
+	/* Clear EOF interrupt */
+	status = uart_reg_in(UART3_IIR);
+
+	if (status & UART3_IIR_TX_STATUS) {
+		u8 mdr2 = uart_reg_in(UART3_MDR2);
+		if (mdr2 & UART3_MDR2_IRTX_UNDERRUN)
+			printk(KERN_ERR "IrDA Buffer underrun error\n");
+
+		omap_ir->stats.tx_packets++;
+
+		if (omap_ir->newspeed) {
+			omap_irda_set_speed(dev, omap_ir->newspeed);
+			omap_ir->newspeed = 0;
+		}
+
+		netif_wake_queue(dev);
+		if (!(status & UART3_IIR_EOF))
+			return IRQ_HANDLED;
+	}
+
+	/* Stop DMA and if there are no errors, send frame to upper layer */
+	omap_stop_dma(omap_ir->rx_dma_channel);
+
+	status = uart_reg_in(UART3_SFLSR);	/* Take a frame status */
+
+	if (status != 0) {	/* Bad frame? */
+		omap_ir->stats.rx_frame_errors++;
+		uart_reg_in(UART3_RESUME);
+	} else {
+		/* We got a frame! */
+		skb = dev_alloc_skb(IRDA_SKB_MAX_MTU);
+
+		if (!skb) {
+			printk(KERN_ERR "omap_sir: out of memory for RX SKB\n");
+			return IRQ_HANDLED;
+		}
+		/*
+		 * Align any IP headers that may be contained
+		 * within the frame.
+		 */
+
+		skb_reserve(skb, 1);
+
+		w = omap_get_dma_dst_pos(omap_ir->rx_dma_channel) -
+						omap_ir->rx_buf_dma_phys;
+
+		if (!IS_FIR(omap_ir))
+			/* Copy DMA buffer to skb */
+			memcpy(skb_put(skb, w - 2), omap_ir->rx_buf_dma_virt,
+					w - 2);
+		else
+			/* Copy DMA buffer to skb */
+			memcpy(skb_put(skb, w - 4), omap_ir->rx_buf_dma_virt,
+					w - 4);
+
+		skb->dev = dev;
+		skb_reset_mac_header(skb);
+		skb->protocol = htons(ETH_P_IRDA);
+		omap_ir->stats.rx_packets++;
+		omap_ir->stats.rx_bytes += skb->len;
+		netif_receive_skb(skb);	/* Send data to upper level */
+	}
+
+	/* Re-init RX DMA */
+	omap_irda_start_rx_dma(omap_ir);
+
+	dev->last_rx = jiffies;
+
+	return IRQ_HANDLED;
+}
+
+static int omap_irda_hard_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct omap_irda *omap_ir = netdev_priv(dev);
+	int speed = irda_get_next_speed(skb);
+	int mtt = irda_get_mtt(skb);
+	int xbofs = irda_get_next_xbofs(skb);
+
+
+	/*
+	 * Does this packet contain a request to change the interface
+	 * speed?  If so, remember it until we complete the transmission
+	 * of this frame.
+	 */
+	if (speed != omap_ir->speed && speed != -1)
+		omap_ir->newspeed = speed;
+
+	if (xbofs) /* Set number of addtional BOFS */
+		uart_reg_out(UART3_EBLR, xbofs + 1);
+
+	/*
+	 * If this is an empty frame, we can bypass a lot.
+	 */
+	if (skb->len == 0) {
+		if (omap_ir->newspeed) {
+			omap_ir->newspeed = 0;
+			omap_irda_set_speed(dev, speed);
+		}
+		dev_kfree_skb(skb);
+		return 0;
+	}
+
+	netif_stop_queue(dev);
+
+	/* Copy skb data to DMA buffer */
+	skb_copy_from_linear_data(skb, omap_ir->tx_buf_dma_virt, skb->len);
+
+	/* Copy skb data to DMA buffer */
+	omap_ir->stats.tx_bytes += skb->len;
+
+	/* Set frame length */
+	uart_reg_out(UART3_TXFLL, (skb->len & 0xff));
+	uart_reg_out(UART3_TXFLH, (skb->len >> 8));
+
+	if (mtt > 1000)
+		mdelay(mtt / 1000);
+	else
+		udelay(mtt);
+
+	/* Start TX DMA transfer */
+	omap_start_tx_dma(omap_ir, skb->len);
+
+	/* We can free skb now because it's already in DMA buffer */
+	dev_kfree_skb(skb);
+
+	dev->trans_start = jiffies;
+
+	return 0;
+}
+
+static int
+omap_irda_ioctl(struct net_device *dev, struct ifreq *ifreq, int cmd)
+{
+	struct if_irda_req *rq = (struct if_irda_req *)ifreq;
+	struct omap_irda *omap_ir = netdev_priv(dev);
+	int ret = -EOPNOTSUPP;
+
+
+	switch (cmd) {
+	case SIOCSBANDWIDTH:
+		if (capable(CAP_NET_ADMIN)) {
+			/*
+			 * We are unable to set the speed if the
+			 * device is not running.
+			 */
+			if (omap_ir->open)
+				ret = omap_irda_set_speed(dev,
+						rq->ifr_baudrate);
+			else {
+				printk(KERN_ERR "omap_ir: SIOCSBANDWIDTH:"
+						" !netif_running\n");
+				ret = 0;
+			}
+		}
+		break;
+
+	case SIOCSMEDIABUSY:
+		ret = -EPERM;
+		if (capable(CAP_NET_ADMIN)) {
+			irda_device_set_media_busy(dev, TRUE);
+			ret = 0;
+		}
+		break;
+
+	case SIOCGRECEIVING:
+		rq->ifr_receiving = 0;
+		break;
+
+	default:
+		break;
+	}
+
+	return ret;
+}
+
+static struct net_device_stats *omap_irda_stats(struct net_device *dev)
+{
+	struct omap_irda *omap_ir = netdev_priv(dev);
+	return &omap_ir->stats;
+}
+
+static int omap_irda_start(struct net_device *dev)
+{
+	struct omap_irda *omap_ir = netdev_priv(dev);
+	int err;
+
+	omap_ir->speed = 9600;
+
+	err = request_irq(dev->irq, omap_irda_irq, 0, dev->name, dev);
+	if (err)
+		goto err_irq;
+
+	/*
+	 * The interrupt must remain disabled for now.
+	 */
+	disable_irq(dev->irq);
+
+	/*  Request DMA channels for IrDA hardware */
+	if (omap_request_dma(omap_ir->pdata->rx_channel, "IrDA Rx DMA",
+			(void *)omap_irda_rx_dma_callback,
+			dev, &(omap_ir->rx_dma_channel))) {
+		printk(KERN_ERR "Failed to request IrDA Rx DMA\n");
+		goto err_irq;
+	}
+
+	if (omap_request_dma(omap_ir->pdata->tx_channel, "IrDA Tx DMA",
+			(void *)omap_irda_tx_dma_callback,
+			dev, &(omap_ir->tx_dma_channel))) {
+		printk(KERN_ERR "Failed to request IrDA Tx DMA\n");
+		goto err_irq;
+	}
+
+	/* Allocate TX and RX buffers for DMA channels */
+	omap_ir->rx_buf_dma_virt =
+		dma_alloc_coherent(NULL, IRDA_SKB_MAX_MTU,
+				&(omap_ir->rx_buf_dma_phys),
+				GFP_KERNEL);
+
+	if (!omap_ir->rx_buf_dma_virt) {
+		printk(KERN_ERR "Unable to allocate memory for rx_buf_dma\n");
+		goto err_irq;
+	}
+
+	omap_ir->tx_buf_dma_virt =
+		dma_alloc_coherent(NULL, IRDA_SIR_MAX_FRAME,
+				&(omap_ir->tx_buf_dma_phys),
+				GFP_KERNEL);
+
+	if (!omap_ir->tx_buf_dma_virt) {
+		printk(KERN_ERR "Unable to allocate memory for tx_buf_dma\n");
+		goto err_mem1;
+	}
+
+	/*
+	 * Setup the serial port for the specified config.
+	 */
+	if (omap_ir->pdata->select_irda)
+		omap_ir->pdata->select_irda(omap_ir->dev, IR_SEL);
+
+	err = omap_irda_startup(dev);
+
+	if (err)
+		goto err_startup;
+
+	omap_irda_set_speed(dev, omap_ir->speed = 9600);
+
+	/*
+	 * Open a new IrLAP layer instance.
+	 */
+	omap_ir->irlap = irlap_open(dev, &omap_ir->qos, "omap_sir");
+
+	err = -ENOMEM;
+	if (!omap_ir->irlap)
+		goto err_irlap;
+
+	/* Now enable the interrupt and start the queue */
+	omap_ir->open = 1;
+
+	/* Start RX DMA */
+	omap_irda_start_rx_dma(omap_ir);
+
+	enable_irq(dev->irq);
+	netif_start_queue(dev);
+
+	return 0;
+
+err_irlap:
+	omap_ir->open = 0;
+	omap_irda_shutdown(omap_ir);
+err_startup:
+	dma_free_coherent(NULL, IRDA_SIR_MAX_FRAME,
+			omap_ir->tx_buf_dma_virt, omap_ir->tx_buf_dma_phys);
+err_mem1:
+	dma_free_coherent(NULL, IRDA_SKB_MAX_MTU,
+			omap_ir->rx_buf_dma_virt, omap_ir->rx_buf_dma_phys);
+err_irq:
+	free_irq(dev->irq, dev);
+	return err;
+}
+
+static int omap_irda_stop(struct net_device *dev)
+{
+	struct omap_irda *omap_ir = netdev_priv(dev);
+
+	disable_irq(dev->irq);
+
+	netif_stop_queue(dev);
+
+	omap_free_dma(omap_ir->rx_dma_channel);
+	omap_free_dma(omap_ir->tx_dma_channel);
+
+	if (omap_ir->rx_buf_dma_virt)
+		dma_free_coherent(NULL, IRDA_SKB_MAX_MTU,
+				omap_ir->rx_buf_dma_virt,
+				omap_ir->rx_buf_dma_phys);
+	if (omap_ir->tx_buf_dma_virt)
+		dma_free_coherent(NULL, IRDA_SIR_MAX_FRAME,
+				omap_ir->tx_buf_dma_virt,
+				omap_ir->tx_buf_dma_phys);
+
+	omap_irda_shutdown(omap_ir);
+
+	/* Stop IrLAP */
+	if (omap_ir->irlap) {
+		irlap_close(omap_ir->irlap);
+		omap_ir->irlap = NULL;
+	}
+
+	omap_ir->open = 0;
+
+	/*
+	 * Free resources
+	 */
+	free_irq(dev->irq, dev);
+
+	return 0;
+}
+
+static int omap_irda_set_speed(struct net_device *dev, int speed)
+{
+	struct omap_irda *omap_ir = netdev_priv(dev);
+	int divisor;
+	unsigned long flags;
+
+	/* Set IrDA speed */
+	if (speed <= 115200) {
+
+		local_irq_save(flags);
+
+		/* SIR mode */
+		if (omap_ir->pdata->transceiver_mode)
+			omap_ir->pdata->transceiver_mode(omap_ir->dev,
+							IR_SIRMODE);
+
+		/* Set SIR mode */
+		uart_reg_out(UART3_MDR1, 1);
+		uart_reg_out(UART3_EBLR, 1);
+
+		divisor = 48000000 / (16 * speed);	/* Base clock 48 MHz */
+
+		uart_reg_out(UART3_LCR, UART3_LCR_DIVEN);
+		uart_reg_out(UART3_DLL, (divisor & 0xff));
+		uart_reg_out(UART3_DLH, (divisor >> 8));
+		uart_reg_out(UART3_LCR, 0x03);
+
+		uart_reg_out(UART3_MCR, 0);
+
+		local_irq_restore(flags);
+	} else if (speed <= 1152000) {
+
+		local_irq_save(flags);
+
+		/* Set MIR mode, auto SIP */
+		uart_reg_out(UART3_MDR1, UART3_MDR1_MIR |
+				UART3_MDR1_SIP_AUTO);
+
+		uart_reg_out(UART3_EBLR, 2);
+
+		divisor = 48000000 / (41 * speed);	/* Base clock 48 MHz */
+
+		uart_reg_out(UART3_LCR, UART3_LCR_DIVEN);
+		uart_reg_out(UART3_DLL, (divisor & 0xff));
+		uart_reg_out(UART3_DLH, (divisor >> 8));
+		uart_reg_out(UART3_LCR, 0x03);
+
+		if (omap_ir->pdata->transceiver_mode)
+			omap_ir->pdata->transceiver_mode(omap_ir->dev,
+							IR_MIRMODE);
+
+		local_irq_restore(flags);
+	} else {
+		local_irq_save(flags);
+
+		/* FIR mode */
+		uart_reg_out(UART3_MDR1, UART3_MDR1_FIR |
+				UART3_MDR1_SIP_AUTO);
+
+		if (omap_ir->pdata->transceiver_mode)
+			omap_ir->pdata->transceiver_mode(omap_ir->dev,
+							IR_FIRMODE);
+
+		local_irq_restore(flags);
+	}
+
+	omap_ir->speed = speed;
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * Suspend the IrDA interface.
+ */
+static int omap_irda_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct net_device *dev = platform_get_drvdata(pdev);
+	struct omap_irda *omap_ir = netdev_priv(dev);
+
+	if (!dev)
+		return 0;
+
+	if (omap_ir->open) {
+		/*
+		 * Stop the transmit queue
+		 */
+		netif_device_detach(dev);
+		disable_irq(dev->irq);
+		omap_irda_shutdown(omap_ir);
+	}
+	return 0;
+}
+
+/*
+ * Resume the IrDA interface.
+ */
+static int omap_irda_resume(struct platform_device *pdev)
+{
+	struct net_device *dev = platform_get_drvdata(pdev);
+	struct omap_irda *omap_ir= netdev_priv(dev);
+
+	if (!dev)
+		return 0;
+
+	if (omap_ir->open) {
+		/*
+		 * If we missed a speed change, initialise at the new speed
+		 * directly.  It is debatable whether this is actually
+		 * required, but in the interests of continuing from where
+		 * we left off it is desireable.  The converse argument is
+		 * that we should re-negotiate at 9600 baud again.
+		 */
+		if (omap_ir->newspeed) {
+			omap_ir->speed = omap_ir->newspeed;
+			omap_ir->newspeed = 0;
+		}
+
+		omap_irda_startup(dev);
+		omap_irda_set_speed(dev, omap_ir->speed);
+		enable_irq(dev->irq);
+
+		/*
+		 * This automatically wakes up the queue
+		 */
+		netif_device_attach(dev);
+	}
+
+	return 0;
+}
+#else
+#define omap_irda_suspend	NULL
+#define omap_irda_resume	NULL
+#endif
+
+static int omap_irda_probe(struct platform_device *pdev)
+{
+	struct net_device *dev;
+	struct omap_irda *omap_ir;
+	struct omap_irda_config *pdata = pdev->dev.platform_data;
+	unsigned int baudrate_mask;
+	int err = 0;
+	int irq = NO_IRQ;
+
+	if (!pdata) {
+		printk(KERN_ERR "IrDA Platform data not supplied\n");
+		return -ENOENT;
+	}
+
+	if (!pdata->rx_channel || !pdata->tx_channel) {
+		printk(KERN_ERR "IrDA invalid rx/tx channel value\n");
+		return -ENOENT;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq <= 0) {
+		printk(KERN_WARNING "no irq for IrDA\n");
+		return -ENOENT;
+	}
+
+	dev = alloc_irdadev(sizeof(struct omap_irda));
+	if (!dev)
+		goto err_mem_1;
+
+
+	omap_ir = netdev_priv(dev);
+	omap_ir->dev = &pdev->dev;
+	omap_ir->pdata = pdata;
+
+	dev->hard_start_xmit	= omap_irda_hard_xmit;
+	dev->open		= omap_irda_start;
+	dev->stop		= omap_irda_stop;
+	dev->do_ioctl		= omap_irda_ioctl;
+	dev->get_stats		= omap_irda_stats;
+	dev->irq		= irq;
+
+	irda_init_max_qos_capabilies(&omap_ir->qos);
+
+	baudrate_mask = 0;
+	if (omap_ir->pdata->transceiver_cap & IR_SIRMODE)
+		baudrate_mask |= IR_9600|IR_19200|IR_38400|IR_57600|IR_115200;
+	if (omap_ir->pdata->transceiver_cap & IR_MIRMODE)
+		baudrate_mask |= IR_57600 | IR_1152000;
+	if (omap_ir->pdata->transceiver_cap & IR_FIRMODE)
+		baudrate_mask |= IR_4000000 << 8;
+
+	omap_ir->qos.baud_rate.bits &= baudrate_mask;
+	omap_ir->qos.min_turn_time.bits = 7;
+
+	irda_qos_bits_to_value(&omap_ir->qos);
+
+	/* Any better way to avoid this? No. */
+	if (machine_is_omap_h3() || machine_is_omap_h4())
+		INIT_DELAYED_WORK(&omap_ir->pdata->gpio_expa, NULL);
+
+	err = register_netdev(dev);
+	if (!err)
+		platform_set_drvdata(pdev, dev);
+	else
+		free_netdev(dev);
+
+err_mem_1:
+	return err;
+}
+
+static int omap_irda_remove(struct platform_device *pdev)
+{
+	struct net_device *dev = platform_get_drvdata(pdev);
+
+	if (pdev) {
+		unregister_netdev(dev);
+		free_netdev(dev);
+	}
+	return 0;
+}
+
+static struct platform_driver omapir_driver = {
+	.probe		= omap_irda_probe,
+	.remove		= omap_irda_remove,
+	.suspend	= omap_irda_suspend,
+	.resume		= omap_irda_resume,
+	.driver		= {
+		.name	= "omapirda",
+	},
+};
+
+static char __initdata banner[] = KERN_INFO "OMAP IrDA driver initializing\n";
+
+static int __init omap_irda_init(void)
+{
+	printk(banner);
+	return platform_driver_register(&omapir_driver);
+}
+
+static void __exit omap_irda_exit(void)
+{
+	platform_driver_unregister(&omapir_driver);
+}
+
+module_init(omap_irda_init);
+module_exit(omap_irda_exit);
+
+MODULE_AUTHOR("MontaVista");
+MODULE_DESCRIPTION("OMAP IrDA Driver");
+MODULE_LICENSE("GPL");
+
-- 
1.6.0.1.141.g445ca


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

* [PATCH 24/33] add bq27x00 battery driver
  2008-08-30 17:16                                             ` [PATCH 23/33] add omap irda driver Felipe Balbi
@ 2008-08-30 17:16                                               ` Felipe Balbi
  2008-08-30 17:16                                                 ` [PATCH 25/33] add TWL4030 Battery Charger Interface driver Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/power/Kconfig           |   21 ++
 drivers/power/Makefile          |    1 +
 drivers/power/bq27x00_battery.c |  564 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 586 insertions(+), 0 deletions(-)
 create mode 100644 drivers/power/bq27x00_battery.c

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 9ce5585..18b70fa 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -49,6 +49,27 @@ config BATTERY_OLPC
 	help
 	  Say Y to enable support for the battery on the OLPC laptop.
 
+config BATTERY_BQ27x00
+	tristate "BQ27x00 battery driver"
+	help
+	  Say Y here to enable support for batteries with BQ27000 or BQ27200 chip.
+
+config BATTERY_BQ27000
+	bool "BQ27000 battery driver"
+	depends on BATTERY_BQ27x00
+	select W1
+	select W1_SLAVE_BQ27000
+	help
+	  Say Y here to enable support for batteries with BQ27000(HDQ) chip.
+
+config BATTERY_BQ27200
+	bool "BQ27200 battery driver"
+	depends on BATTERY_BQ27x00
+	select I2C
+	select I2C_OMAP
+	help
+	  Say Y here to enable support for batteries with BQ27200(I2C) chip.
+
 config BATTERY_TOSA
 	tristate "Sharp SL-6000 (tosa) battery"
 	depends on MACH_TOSA && MFD_TC6393XB
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index 4706bf8..dc6cfa3 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -20,5 +20,6 @@ obj-$(CONFIG_APM_POWER)		+= apm_power.o
 obj-$(CONFIG_BATTERY_DS2760)	+= ds2760_battery.o
 obj-$(CONFIG_BATTERY_PMU)	+= pmu_battery.o
 obj-$(CONFIG_BATTERY_OLPC)	+= olpc_battery.o
+obj-$(CONFIG_BATTERY_BQ27x00)   += bq27x00_battery.o
 obj-$(CONFIG_BATTERY_TOSA)	+= tosa_battery.o
 obj-$(CONFIG_BATTERY_PALMTX)	+= palmtx_battery.o
diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c
new file mode 100644
index 0000000..8b439e8
--- /dev/null
+++ b/drivers/power/bq27x00_battery.c
@@ -0,0 +1,564 @@
+/*
+ * linux/drivers/power/bq27x00_battery.c
+ *
+ * BQ27000/BQ27200 battery driver
+ *
+ * Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * Author: Texas Instruments
+ *
+ * This package 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#ifdef CONFIG_BATTERY_BQ27000
+#include "../w1/w1.h"
+#endif
+#ifdef CONFIG_BATTERY_BQ27200
+#include <linux/i2c.h>
+#endif
+
+#define BQ27x00_REG_TEMP		0x06
+#define BQ27x00_REG_VOLT		0x08
+#define BQ27x00_REG_RSOC		0x0B /* Relative State-of-Charge */
+#define BQ27x00_REG_AI			0x14
+#define BQ27x00_REG_FLAGS		0x0A
+#define HIGH_BYTE(A)			((A) << 8)
+
+#ifdef CONFIG_BATTERY_BQ27000
+extern int w1_bq27000_read(struct device *dev, u8 reg);
+#endif
+
+struct bq27x00_device_info;
+struct bq27x00_access_methods {
+	int (*read)(u8 reg, int *rt_value, int b_single,
+		struct bq27x00_device_info *di);
+};
+
+struct bq27x00_device_info {
+	struct device 		*dev;
+#ifdef CONFIG_BATTERY_BQ27000
+	struct device		*w1_dev;
+#endif
+#ifdef CONFIG_BATTERY_BQ27200
+	struct i2c_client *client;
+#endif
+	unsigned long		update_time;
+	int			voltage_uV;
+	int			current_uA;
+	int			temp_C;
+	int			charge_rsoc;
+	struct bq27x00_access_methods	*bus;
+	struct power_supply	bat;
+	struct delayed_work	monitor_work;
+};
+
+static unsigned int cache_time = 60000;
+module_param(cache_time, uint, 0644);
+MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
+
+static enum power_supply_property bq27x00_battery_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_TEMP,
+};
+
+static int bq27x00_read(u8 reg, int *rt_value, int b_single,
+	struct bq27x00_device_info *di);
+
+#ifdef CONFIG_BATTERY_BQ27000
+static int bq27000_battery_probe(struct platform_device *dev);
+static int bq27000_battery_remove(struct platform_device *dev);
+#ifdef CONFIG_PM
+static int bq27000_battery_suspend(struct platform_device *dev,
+	pm_message_t state);
+static int bq27000_battery_resume(struct platform_device *dev);
+#endif /* CONFIG_PM */
+
+static struct platform_driver bq27000_battery_driver = {
+	.probe = bq27000_battery_probe,
+	.remove = bq27000_battery_remove,
+#ifdef CONFIG_PM
+	.suspend = bq27000_battery_suspend,
+	.resume = bq27000_battery_resume,
+#endif /* CONFIG_PM */
+	.driver = {
+		.name = "bq27000-battery",
+	},
+};
+#endif /* CONFIG_BATTERY_BQ27000 */
+
+#ifdef CONFIG_BATTERY_BQ27200
+static int bq27200_battery_probe(struct i2c_client *client);
+static int bq27200_battery_remove(struct i2c_client *client);
+#ifdef CONFIG_PM
+static int bq27200_battery_suspend(struct i2c_client *client,
+		pm_message_t mesg);
+static int bq27200_battery_resume(struct i2c_client *client);
+#endif /* CONFIG_PM */
+static struct i2c_driver bq27200_battery_driver = {
+	.driver = {
+		.name   = "bq27200-bat",
+	},
+	.probe  = bq27200_battery_probe,
+	.remove = bq27200_battery_remove,
+#ifdef CONFIG_PM
+	.suspend = bq27200_battery_suspend,
+	.resume = bq27200_battery_resume,
+#endif /* CONFIG_PM */
+};
+#endif /* CONFIG_BATTERY_BQ27200 */
+
+/*
+ * Return the battery temperature in Celcius degrees
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_temperature(struct bq27x00_device_info *di)
+{
+	int ret, temp = 0;
+
+	ret = bq27x00_read(BQ27x00_REG_TEMP, &temp, 0, di);
+	if (ret) {
+		pr_err("BQ27x00 battery driver:"
+			"Error reading temperature from the battery\n");
+		return ret;
+	}
+
+	return (temp >> 2) - 273;
+}
+
+/*
+ * Return the battery Voltage in milivolts
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_voltage(struct bq27x00_device_info *di)
+{
+	int ret, volt = 0;
+
+	ret = bq27x00_read(BQ27x00_REG_VOLT, &volt, 0, di);
+	if (ret) {
+		pr_err("BQ27x00 battery driver:"
+			"Error reading battery voltage from the battery\n");
+		return ret;
+	}
+
+	return volt;
+}
+
+/*
+ * Return the battery average current
+ * Note that current can be negative signed as well
+ * Or 0 if something fails.
+ */
+static int bq27x00_battery_current(struct bq27x00_device_info *di)
+{
+	int ret, curr = 0, flags = 0;
+
+	ret = bq27x00_read(BQ27x00_REG_AI, &curr, 0, di);
+	if (ret) {
+		pr_err("BQ27x00 battery driver:"
+			"Error reading current from the battery\n");
+		return 0;
+	}
+	ret = bq27x00_read(BQ27x00_REG_FLAGS, &flags, 0, di);
+	if (ret < 0) {
+		pr_err("BQ27x00 battery driver:"
+			"Error reading battery flags\n");
+		return 0;
+	}
+	if ((flags & (1 << 7)) != 0) {
+		pr_debug("Negative current\n");
+		return -curr;
+	} else {
+		return curr;
+	}
+}
+
+/*
+ * Return the battery Relative State-of-Charge
+ * Or < 0 if something fails.
+ */
+static int bq27x00_battery_rsoc(struct bq27x00_device_info *di)
+{
+	int ret, rsoc = 0;
+
+	ret = bq27x00_read(BQ27x00_REG_RSOC, &rsoc, 1, di);
+	if (ret) {
+		pr_err("BQ27x00 battery driver:"
+			"Error reading battery Relative"
+			"State-of-Charge\n");
+		return ret;
+	}
+	return rsoc;
+}
+
+#ifdef CONFIG_BATTERY_BQ27000
+static inline int bq27000_read(u8 reg, int *rt_value, int b_single,
+	struct bq27x00_device_info *di)
+{
+	u8 val;
+
+	val = w1_bq27000_read(di->w1_dev, reg);
+	*rt_value = val;
+
+	if (!b_single) {
+		val = 0;
+		val = w1_bq27000_read(di->w1_dev, reg + 1);
+		*rt_value +=  HIGH_BYTE((int) val);
+	}
+
+	return 0;
+}
+#else
+static inline int bq27000_read(u8 reg, int *rt_value, int b_single,
+	struct bq27x00_device_info *di)
+{
+	return 0;
+}
+#endif
+
+#ifdef CONFIG_BATTERY_BQ27200
+static inline int bq27200_read(u8 reg, int *rt_value, int b_single,
+	struct bq27x00_device_info *di)
+{
+	struct i2c_client *client = di->client;
+	struct i2c_msg msg[1];
+	unsigned char data[2];
+	int err;
+
+	if (!client->adapter)
+		return -ENODEV;
+
+	msg->addr = client->addr;
+	msg->flags = 0;
+	msg->len = 1;
+	msg->buf = data;
+
+	data[0] = reg;
+	err = i2c_transfer(client->adapter, msg, 1);
+
+	if (err >= 0) {
+		if (!b_single)
+			msg->len = 2;
+		else
+			msg->len = 1;
+
+		msg->flags = I2C_M_RD;
+		err = i2c_transfer(client->adapter, msg, 1);
+		if (err >= 0) {
+			if (!b_single)
+				*rt_value = data[1] | HIGH_BYTE(data[0]);
+			else
+				*rt_value = data[0];
+
+			return 0;
+		} else {
+			pr_err("BQ27200 I2C read failed\n");
+			return err;
+		}
+	} else {
+		pr_err("BQ27200 I2C write failed\n");
+		return err;
+	}
+}
+#else
+static inline int bq27200_read(u8 reg, int *rt_value, int b_single,
+	struct bq27x00_device_info *di)
+{
+	return 0;
+}
+#endif
+
+static int bq27x00_read(u8 reg, int *rt_value, int b_single,
+	struct bq27x00_device_info *di)
+{
+	int ret;
+
+	ret = di->bus->read(reg, rt_value, b_single, di);
+	return ret;
+}
+
+/*
+ * Read the battery temp, voltage, current and relative state of charge.
+ */
+static void bq27x00_battery_read_status(struct bq27x00_device_info *di)
+{
+	if (di->update_time && time_before(jiffies, di->update_time +
+					msecs_to_jiffies(cache_time)))
+		return;
+
+	di->temp_C = bq27x00_battery_temperature(di);
+	di->voltage_uV = bq27x00_battery_voltage(di);
+	di->current_uA = bq27x00_battery_current(di);
+	di->charge_rsoc = bq27x00_battery_rsoc(di);
+
+	di->update_time = jiffies;
+
+	return;
+}
+
+static void bq27x00_battery_work(struct delayed_work *work)
+{
+	struct bq27x00_device_info *di = container_of(work,
+		struct bq27x00_device_info, monitor_work);
+
+	bq27x00_battery_read_status(di);
+	schedule_delayed_work(&di->monitor_work, 100);
+	return;
+}
+
+#define to_bq27x00_device_info(x) container_of((x), \
+				struct bq27x00_device_info, bat);
+
+static int bq27x00_battery_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct bq27x00_device_info *di = to_bq27x00_device_info(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = di->voltage_uV;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		val->intval = di->current_uA;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:
+		val->intval = di->charge_rsoc;
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		val->intval = di->temp_C;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = di->charge_rsoc;
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		if (di->voltage_uV == 0)
+			val->intval = 0;
+		else
+			val->intval = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void bq27x00_powersupply_init(struct bq27x00_device_info *di)
+{
+	di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+	di->bat.properties = bq27x00_battery_props;
+	di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props);
+	di->bat.get_property = bq27x00_battery_get_property;
+	di->bat.external_power_changed = NULL;
+	return;
+}
+
+#ifdef CONFIG_BATTERY_BQ27200
+static int bq27200_battery_probe(struct i2c_client *client)
+{
+	struct bq27x00_device_info *di;
+	struct bq27x00_access_methods *bus;
+	int retval = 0;
+
+	di = kzalloc(sizeof(*di), GFP_KERNEL);
+	if (!di) {
+		pr_err("BQ27000 battery driver:"
+			"Failed to allocate device info structure\n");
+		return -ENOMEM;
+	}
+
+	bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+	if (!bus) {
+		pr_err("BQ27000 battery driver:"
+			"Failed to allocate access method structure\n");
+		kfree(di);
+		return -ENOMEM;
+	}
+
+	i2c_set_clientdata(client, di);
+	di->dev = &client->dev;
+	di->bat.name = "bq27200";
+	bus->read = &bq27200_read;
+	di->bus = bus;
+	di->client = client;
+
+	bq27x00_powersupply_init(di);
+
+	retval = power_supply_register(&client->dev, &di->bat);
+	if (retval) {
+		pr_err("BQ27200 battery driver: Failed to register battery\n");
+		goto batt_failed;
+	}
+
+	INIT_DELAYED_WORK(&di->monitor_work, bq27x00_battery_work);
+	schedule_delayed_work(&di->monitor_work, 100);
+
+	return 0;
+
+batt_failed:
+	kfree(bus);
+	kfree(di);
+	return retval;
+}
+
+static int bq27200_battery_remove(struct i2c_client *client)
+{
+	struct bq27x00_device_info *di  = i2c_get_clientdata(client);
+
+	flush_scheduled_work();
+	power_supply_unregister(&di->bat);
+	kfree(di);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int bq27200_battery_suspend(struct i2c_client *client, pm_message_t mesg)
+{
+	struct bq27x00_device_info *di  = i2c_get_clientdata(client);
+
+	cancel_delayed_work(&di->monitor_work);
+	return 0;
+}
+
+static int bq27200_battery_resume(struct i2c_client *client)
+{
+	struct bq27x00_device_info *di  = i2c_get_clientdata(client);
+
+	schedule_delayed_work(&di->monitor_work, 0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+#endif /* CONFIG_BATTERY_BQ27200 */
+
+#ifdef CONFIG_BATTERY_BQ27000
+static int bq27000_battery_probe(struct  platform_device *pdev)
+{
+	struct bq27x00_device_info *di;
+	struct bq27x00_access_methods *bus;
+	int retval = 0;
+
+	di = kzalloc(sizeof(*di), GFP_KERNEL);
+	if (!di) {
+		pr_err("BQ27000 battery driver:"
+			"Failed to allocate device info structure\n");
+		return -ENOMEM;
+	}
+
+	bus = kzalloc(sizeof(*bus), GFP_KERNEL);
+	if (!bus) {
+		pr_err("BQ27000 battery driver:"
+			"Failed to allocate access method structure\n");
+		kfree(di);
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, di);
+
+	di->dev = &pdev->dev;
+	di->w1_dev = pdev->dev.parent;
+	di->bat.name = "bq27000";
+	bus->read = &bq27000_read;
+	di->bus = bus;
+
+	bq27x00_powersupply_init(di);
+
+	retval = power_supply_register(&pdev->dev, &di->bat);
+	if (retval) {
+		pr_err("BQ27000 battery driver: Failed to register battery\n");
+		goto batt_failed;
+	}
+
+	INIT_DELAYED_WORK(&di->monitor_work, bq27x00_battery_work);
+	schedule_delayed_work(&di->monitor_work, 50);
+
+	return 0;
+
+batt_failed:
+	kfree(bus);
+	kfree(di);
+	return retval;
+}
+
+static int bq27000_battery_remove(struct  platform_device *pdev)
+{
+	struct bq27x00_device_info *di = platform_get_drvdata(pdev);
+
+	flush_scheduled_work();
+	power_supply_unregister(&di->bat);
+	platform_set_drvdata(pdev, NULL);
+	kfree(di);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int bq27000_battery_suspend(struct platform_device *pdev,
+	pm_message_t state)
+{
+	struct bq27x00_device_info *di = platform_get_drvdata(pdev);
+
+	cancel_delayed_work(&di->monitor_work);
+	return 0;
+}
+
+static int bq27000_battery_resume(struct platform_device *pdev)
+{
+	struct bq27x00_device_info *di = platform_get_drvdata(pdev);
+
+	schedule_delayed_work(&di->monitor_work, 0);
+	return 0;
+}
+#endif /* CONFIG_PM */
+#endif /* CONFIG_BATTERY_BQ27000 */
+
+static int __init bq27x00_battery_init(void)
+{
+	int status = 0;
+
+#ifdef CONFIG_BATTERY_BQ27000
+	status = platform_driver_register(&bq27000_battery_driver);
+	if (status)
+		printk(KERN_ERR "Unable to register BQ27000 driver\n");
+#endif
+#ifdef CONFIG_BATTERY_BQ27200
+	status = i2c_add_driver(&bq27200_battery_driver);
+		printk(KERN_ERR "Unable to register BQ27200 driver\n");
+#endif
+	return status;
+}
+
+static void __exit bq27x00_battery_exit(void)
+{
+#ifdef CONFIG_BATTERY_BQ27000
+	platform_driver_unregister(&bq27000_battery_driver);
+#endif
+#ifdef CONFIG_BATTERY_BQ27200
+	i2c_del_driver(&bq27200_battery_driver);
+#endif
+}
+
+module_init(bq27x00_battery_init);
+module_exit(bq27x00_battery_exit);
+
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("BQ27x00 battery moniter driver");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 25/33] add TWL4030 Battery Charger Interface driver
  2008-08-30 17:16                                               ` [PATCH 24/33] add bq27x00 battery driver Felipe Balbi
@ 2008-08-30 17:16                                                 ` Felipe Balbi
  2008-08-30 17:16                                                   ` [PATCH 26/33] add TWL4030 RealTime Clock driver Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/power/Kconfig               |    8 +
 drivers/power/Makefile              |    1 +
 drivers/power/twl4030_bci_battery.c | 1080 +++++++++++++++++++++++++++++++++++
 3 files changed, 1089 insertions(+), 0 deletions(-)
 create mode 100644 drivers/power/twl4030_bci_battery.c

diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig
index 18b70fa..7bd8c2b 100644
--- a/drivers/power/Kconfig
+++ b/drivers/power/Kconfig
@@ -70,6 +70,14 @@ config BATTERY_BQ27200
 	help
 	  Say Y here to enable support for batteries with BQ27200(I2C) chip.
 
+config TWL4030_BCI_BATTERY
+	tristate "OMAP TWL4030 BCI Battery driver"
+	depends on (MACH_OMAP_2430SDP || MACH_OMAP_3430SDP) && TWL4030_MADC
+	default y
+	help
+	  Support for OMAP TWL4030 BCI Battery driver.
+	  This driver can give support for TWL4030 Battery Charge Interface.
+
 config BATTERY_TOSA
 	tristate "Sharp SL-6000 (tosa) battery"
 	depends on MACH_TOSA && MFD_TC6393XB
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index dc6cfa3..8da941a 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -21,5 +21,6 @@ obj-$(CONFIG_BATTERY_DS2760)	+= ds2760_battery.o
 obj-$(CONFIG_BATTERY_PMU)	+= pmu_battery.o
 obj-$(CONFIG_BATTERY_OLPC)	+= olpc_battery.o
 obj-$(CONFIG_BATTERY_BQ27x00)   += bq27x00_battery.o
+obj-$(CONFIG_TWL4030_BCI_BATTERY)	+= twl4030_bci_battery.o
 obj-$(CONFIG_BATTERY_TOSA)	+= tosa_battery.o
 obj-$(CONFIG_BATTERY_PALMTX)	+= palmtx_battery.o
diff --git a/drivers/power/twl4030_bci_battery.c b/drivers/power/twl4030_bci_battery.c
new file mode 100644
index 0000000..d30a0e4
--- /dev/null
+++ b/drivers/power/twl4030_bci_battery.c
@@ -0,0 +1,1080 @@
+/*
+ * linux/drivers/power/twl4030_bci_battery.c
+ *
+ * OMAP2430/3430 BCI battery driver for Linux
+ *
+ * Copyright (C) 2008 Texas Instruments, Inc.
+ * Author: Texas Instruments, Inc.
+ *
+ * This package 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+#include <linux/power_supply.h>
+#include <asm/arch/bci.h>
+#include <linux/i2c/twl4030-madc.h>
+
+#define T2_BATTERY_VOLT		0x04
+#define T2_BATTERY_TEMP		0x06
+#define T2_BATTERY_CUR		0x08
+
+/* charger constants */
+#define NO_PW_CONN		0
+#define AC_PW_CONN		0x01
+#define USB_PW_CONN		0x02
+
+/* TWL4030_MODULE_USB */
+#define REG_POWER_CTRL		0x0AC
+#define OTG_EN			0x020
+#define REG_PHY_CLK_CTRL	0x0FE
+#define REG_PHY_CLK_CTRL_STS	0x0FF
+#define PHY_DPLL_CLK		0x01
+
+#define REG_BCICTL1 		0x023
+#define REG_BCICTL2 		0x024
+#define CGAIN			0x020
+#define ITHEN			0x010
+#define ITHSENS			0x007
+
+/* Boot BCI flag bits */
+#define BCIAUTOWEN		0x020
+#define CONFIG_DONE		0x010
+#define BCIAUTOUSB		0x002
+#define BCIAUTOAC		0x001
+#define BCIMSTAT_MASK		0x03F
+
+/* Boot BCI register */
+#define REG_BOOT_BCI		0x007
+#define REG_CTRL1		0x00
+#define REG_SW1SELECT_MSB	0x07
+#define SW1_CH9_SEL		0x02
+#define REG_CTRL_SW1		0x012
+#define SW1_TRIGGER		0x020
+#define EOC_SW1			0x002
+#define REG_GPCH9		0x049
+#define REG_STS_HW_CONDITIONS	0x0F
+#define STS_VBUS		0x080
+#define STS_CHG			0x02
+#define REG_BCIMSTATEC		0x02
+#define REG_BCIMFSTS4		0x010
+#define REG_BCIMFSTS2		0x00E
+#define REG_BCIMFSTS3 		0x00F
+#define REG_BCIMFSTS1		0x001
+#define USBFASTMCHG		0x004
+#define BATSTSPCHG		0x004
+#define BATSTSMCHG		0x040
+#define VBATOV4			0x020
+#define VBATOV3			0x010
+#define VBATOV2			0x008
+#define VBATOV1			0x004
+#define REG_BB_CFG		0x012
+#define BBCHEN			0x010
+
+/* Power supply charge interrupt */
+#define REG_PWR_ISR1		0x00
+#define REG_PWR_IMR1		0x01
+#define REG_PWR_EDR1		0x05
+#define REG_PWR_SIH_CTRL	0x007
+
+#define USB_PRES		0x004
+#define CHG_PRES		0x002
+
+#define USB_PRES_RISING		0x020
+#define USB_PRES_FALLING	0x010
+#define CHG_PRES_RISING		0x008
+#define CHG_PRES_FALLING	0x004
+#define AC_STATEC		0x20
+#define COR			0x004
+
+/* interrupt status registers */
+#define REG_BCIISR1A 		0x0
+#define REG_BCIISR2A 		0x01
+
+/* Interrupt flags bits BCIISR1 */
+#define BATSTS_ISR1		0x080
+#define VBATLVL_ISR1		0x001
+
+/* Interrupt mask registers for int1*/
+#define REG_BCIIMR1A		0x002
+#define REG_BCIIMR2A		0x003
+
+ /* Interrupt masks for BCIIMR1 */
+#define BATSTS_IMR1		0x080
+#define VBATLVL_IMR1		0x001
+
+/* Interrupt edge detection register */
+#define REG_BCIEDR1		0x00A
+#define REG_BCIEDR2		0x00B
+#define REG_BCIEDR3		0x00C
+
+/* BCIEDR2 */
+#define	BATSTS_EDRRISIN		0x080
+#define BATSTS_EDRFALLING	0x040
+
+/* BCIEDR3 */
+#define	VBATLVL_EDRRISIN	0x02
+
+/* Step size and prescaler ratio */
+#define TEMP_STEP_SIZE		147
+#define TEMP_PSR_R		100
+
+#define VOLT_STEP_SIZE		588
+#define VOLT_PSR_R		100
+
+#define CURR_STEP_SIZE		147
+#define CURR_PSR_R1		44
+#define CURR_PSR_R2		80
+
+#define BK_VOLT_STEP_SIZE	441
+#define BK_VOLT_PSR_R		100
+
+#define ENABLE		1
+#define DISABLE		1
+
+/* Ptr to thermistor table */
+int *therm_tbl;
+
+static int twl4030_bci_battery_probe(struct platform_device *dev);
+static int twl4030_bci_battery_remove(struct platform_device *dev);
+#ifdef CONFIG_PM
+static int twl4030_bci_battery_suspend(struct platform_device *dev,
+					pm_message_t state);
+static int twl4030_bci_battery_resume(struct platform_device *dev);
+#endif
+
+struct twl4030_bci_device_info {
+	struct device		*dev;
+
+	unsigned long		update_time;
+	int			voltage_uV;
+	int			bk_voltage_uV;
+	int			current_uA;
+	int			temp_C;
+	int			charge_rsoc;
+	int			charge_status;
+
+	struct power_supply	bat;
+	struct power_supply	bk_bat;
+	struct delayed_work	twl4030_bci_monitor_work;
+	struct delayed_work	twl4030_bk_bci_monitor_work;
+};
+
+static struct platform_driver twl4030_bci_battery_driver = {
+	.probe =	twl4030_bci_battery_probe,
+	.remove =	twl4030_bci_battery_remove,
+#ifdef CONFIG_PM
+	.suspend =	twl4030_bci_battery_suspend,
+	.resume =	twl4030_bci_battery_resume,
+#endif
+	.driver = {
+		.name = "twl4030-bci-battery",
+	},
+};
+
+static int usb_charger_flag;
+static int LVL_1, LVL_2, LVL_3, LVL_4;
+
+static int read_bci_val(u8 reg_1);
+static inline int clear_n_set(u8 mod_no, u8 clear, u8 set, u8 reg);
+static int twl4030charger_presence(void);
+
+/*
+ * Report and clear the charger presence event.
+ */
+static inline int twl4030charger_presence_evt(void)
+{
+	int ret;
+	u8 chg_sts, set = 0, clear = 0;
+
+	/* read charger power supply status */
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &chg_sts,
+		REG_STS_HW_CONDITIONS);
+	if (ret)
+		return IRQ_NONE;
+
+	/* If the AC charger have been connected */
+	if (chg_sts & STS_CHG) {
+		/* configuring falling edge detection for CHG_PRES */
+		set = CHG_PRES_FALLING;
+		clear = CHG_PRES_RISING;
+	}
+	/* If the AC charger have been disconnected */
+	else {
+		/* configuring rising edge detection for CHG_PRES */
+		set = CHG_PRES_RISING;
+		clear = CHG_PRES_FALLING;
+	}
+
+	/* Update the interrupt edge detection register */
+	clear_n_set(TWL4030_MODULE_INT, clear, set, REG_PWR_EDR1);
+
+	return 0;
+}
+
+/*
+ * Interrupt service routine
+ *
+ * Attends to TWL 4030 power module interruptions events, specifically
+ * USB_PRES (USB charger presence) CHG_PRES (AC charger presence) events
+ *
+ */
+static irqreturn_t twl4030charger_interrupt(int irq, void *dev_id)
+{
+	struct twl4030_bci_device_info *di = dev_id;
+
+	twl4030charger_presence_evt();
+	power_supply_changed(&di->bat);
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * This function handles the twl4030 battery presence interrupt
+ */
+static int twl4030battery_presence_evt(void)
+{
+	int ret;
+	u8 batstsmchg, batstspchg;
+
+	/* check for the battery presence in main charge*/
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE,
+			&batstsmchg, REG_BCIMFSTS3);
+	if (ret)
+		return ret;
+
+	/* check for the battery presence in precharge */
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_PRECHARGE,
+			&batstspchg, REG_BCIMFSTS1);
+	if (ret)
+		return ret;
+
+	/*
+	 * REVISIT: Physically inserting/removing the batt
+	 * does not seem to generate an int on 3430ES2 SDP.
+	 */
+
+	/* In case of the battery insertion event */
+	if ((batstspchg & BATSTSPCHG) || (batstsmchg & BATSTSMCHG)) {
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_EDRRISIN,
+			BATSTS_EDRFALLING, REG_BCIEDR2);
+		if (ret)
+			return ret;
+	}
+
+	/* In case of the battery removal event */
+	else {
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_EDRFALLING,
+			BATSTS_EDRRISIN, REG_BCIEDR2);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * This function handles the twl4030 battery voltage level interrupt.
+ */
+static int twl4030battery_level_evt(void)
+{
+	int ret;
+	u8 mfst;
+
+	/* checking for threshold event */
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE,
+			&mfst, REG_BCIMFSTS2);
+	if (ret)
+		return ret;
+
+	if (mfst & VBATOV4) {
+		LVL_4 = 1;
+		LVL_3 = LVL_2 = LVL_1 = 0;
+	} else if (mfst & VBATOV3) {
+		LVL_3 = 1;
+		LVL_4 = LVL_2 = LVL_1 = 0;
+	} else if (mfst & VBATOV2) {
+		LVL_2 = 1;
+		LVL_4 = LVL_3 = LVL_1 = 0;
+	} else {
+		LVL_1 = 1;
+		LVL_4 = LVL_3 = LVL_2 = 0;
+	}
+
+	return 0;
+}
+
+/*
+ * Interrupt service routine
+ *
+ * Attends to BCI interruptions events,
+ * specifically BATSTS (battery connection and removal)
+ * VBATOV (main battery voltage threshold) events
+ *
+ */
+static irqreturn_t twl4030battery_interrupt(int irq, void *dev_id)
+{
+	int ret;
+	u8 isr1a_val, isr2a_val, clear_2a, clear_1a;
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &isr1a_val,
+				REG_BCIISR1A);
+	if (ret)
+		return IRQ_NONE;
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &isr2a_val,
+				REG_BCIISR2A);
+	if (ret)
+		return IRQ_NONE;
+
+	clear_2a = (isr2a_val & VBATLVL_ISR1)? (VBATLVL_ISR1): 0;
+	clear_1a = (isr1a_val & BATSTS_ISR1)? (BATSTS_ISR1): 0;
+
+	/* cleaning BCI interrupt status flags */
+	ret = twl4030_i2c_write_u8(TWL4030_MODULE_INTERRUPTS,
+			clear_1a , REG_BCIISR1A);
+	if (ret)
+		return IRQ_NONE;
+
+	ret = twl4030_i2c_write_u8(TWL4030_MODULE_INTERRUPTS,
+			clear_2a , REG_BCIISR2A);
+	if (ret)
+		return IRQ_NONE;
+
+	/* battery connetion or removal event */
+	if (isr1a_val & BATSTS_ISR1)
+		twl4030battery_presence_evt();
+	/* battery voltage threshold event*/
+	else if (isr2a_val & VBATLVL_ISR1)
+		twl4030battery_level_evt();
+	else
+		return IRQ_NONE;
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * Enable/Disable hardware battery level event notifications.
+ */
+static int twl4030battery_hw_level_en(int enable)
+{
+	int ret;
+
+	if (enable) {
+		/* unmask VBATOV interrupt for INT1 */
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, VBATLVL_IMR1,
+			0, REG_BCIIMR2A);
+		if (ret)
+			return ret;
+
+		/* configuring interrupt edge detection for VBATOv */
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0,
+			VBATLVL_EDRRISIN, REG_BCIEDR3);
+		if (ret)
+			return ret;
+	} else {
+		/* mask VBATOV interrupt for INT1 */
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0,
+			VBATLVL_IMR1, REG_BCIIMR2A);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Enable/disable hardware battery presence event notifications.
+ */
+static int twl4030battery_hw_presence_en(int enable)
+{
+	int ret;
+
+	if (enable) {
+		/* unmask BATSTS interrupt for INT1 */
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, BATSTS_IMR1,
+			0, REG_BCIIMR1A);
+		if (ret)
+			return ret;
+
+		/* configuring interrupt edge for BATSTS */
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0,
+			BATSTS_EDRRISIN | BATSTS_EDRFALLING, REG_BCIEDR2);
+		if (ret)
+			return ret;
+	} else {
+		/* mask BATSTS interrupt for INT1 */
+		ret = clear_n_set(TWL4030_MODULE_INTERRUPTS, 0,
+			BATSTS_IMR1, REG_BCIIMR1A);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Enable/Disable AC Charge funtionality.
+ */
+static int twl4030charger_ac_en(int enable)
+{
+	int ret;
+
+	if (enable) {
+		/* forcing the field BCIAUTOAC (BOOT_BCI[0) to 1 */
+		ret = clear_n_set(TWL4030_MODULE_PM_MASTER, 0,
+			(CONFIG_DONE | BCIAUTOWEN | BCIAUTOAC),
+			REG_BOOT_BCI);
+		if (ret)
+			return ret;
+	} else {
+		/* forcing the field BCIAUTOAC (BOOT_BCI[0) to 0*/
+		ret = clear_n_set(TWL4030_MODULE_PM_MASTER, BCIAUTOAC,
+			(CONFIG_DONE | BCIAUTOWEN),
+			REG_BOOT_BCI);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/*
+ * Enable/Disable USB Charge funtionality.
+ */
+int twl4030charger_usb_en(int enable)
+{
+	u8 value;
+	int ret;
+	unsigned long timeout;
+
+	if (enable) {
+		/* Check for USB charger conneted */
+		ret = twl4030charger_presence();
+		if (ret < 0)
+			return ret;
+
+		if (!(ret & USB_PW_CONN))
+			return -ENXIO;
+
+		/* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */
+		ret = clear_n_set(TWL4030_MODULE_PM_MASTER, 0,
+			(CONFIG_DONE | BCIAUTOWEN | BCIAUTOUSB),
+			REG_BOOT_BCI);
+		if (ret)
+			return ret;
+
+		ret = clear_n_set(TWL4030_MODULE_USB, 0, PHY_DPLL_CLK,
+			REG_PHY_CLK_CTRL);
+		if (ret)
+			return ret;
+
+		value = 0;
+		timeout = jiffies + msecs_to_jiffies(50);
+
+		while ((!(value & PHY_DPLL_CLK)) &&
+			time_before(jiffies, timeout)) {
+			udelay(10);
+			ret = twl4030_i2c_read_u8(TWL4030_MODULE_USB, &value,
+				REG_PHY_CLK_CTRL_STS);
+			if (ret)
+				return ret;
+		}
+
+		/* OTG_EN (POWER_CTRL[5]) to 1 */
+		ret = clear_n_set(TWL4030_MODULE_USB, 0, OTG_EN,
+			REG_POWER_CTRL);
+		if (ret)
+			return ret;
+
+		mdelay(50);
+
+		/* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */
+		ret = clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0,
+			USBFASTMCHG, REG_BCIMFSTS4);
+		if (ret)
+			return ret;
+	} else {
+		twl4030charger_presence();
+		ret = clear_n_set(TWL4030_MODULE_PM_MASTER, BCIAUTOUSB,
+			(CONFIG_DONE | BCIAUTOWEN), REG_BOOT_BCI);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Return battery temperature
+ * Or < 0 on failure.
+ */
+static int twl4030battery_temperature(void)
+{
+	u8 val;
+	int temp, curr, volt, res, ret;
+
+	/* Getting and calculating the thermistor voltage */
+	ret = read_bci_val(T2_BATTERY_TEMP);
+	if (ret < 0)
+		return ret;
+
+	volt = (ret * TEMP_STEP_SIZE) / TEMP_PSR_R;
+
+	/* Getting and calculating the supply current in micro ampers */
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val,
+		 REG_BCICTL2);
+	if (ret)
+		return 0;
+
+	curr = ((val & ITHSENS) + 1) * 10;
+
+	/* Getting and calculating the thermistor resistance in ohms*/
+	res = volt * 1000 / curr;
+
+	/*calculating temperature*/
+	for (temp = 58; temp >= 0; temp--) {
+		int actual = therm_tbl [temp];
+		if ((actual - res) >= 0)
+			break;
+	}
+
+	/* Negative temperature */
+	if (temp < 3) {
+		if (temp == 2)
+			temp = -1;
+		else if (temp == 1)
+			temp = -2;
+		else
+			temp = -3;
+	}
+
+	return temp + 1;
+}
+
+/*
+ * Return battery voltage
+ * Or < 0 on failure.
+ */
+static int twl4030battery_voltage(void)
+{
+	int volt = read_bci_val(T2_BATTERY_VOLT);
+
+	return (volt * VOLT_STEP_SIZE) / VOLT_PSR_R;
+}
+
+/*
+ * Return the battery current
+ * Or < 0 on failure.
+ */
+static int twl4030battery_current(void)
+{
+	int ret, curr = read_bci_val(T2_BATTERY_CUR);
+	u8 val;
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val,
+		REG_BCICTL1);
+	if (ret)
+		return ret;
+
+	if (val & CGAIN) /* slope of 0.44 mV/mA */
+		return (curr * CURR_STEP_SIZE) / CURR_PSR_R1;
+	else /* slope of 0.88 mV/mA */
+		return (curr * CURR_STEP_SIZE) / CURR_PSR_R2;
+}
+
+/*
+ * Return the battery backup voltage
+ * Or < 0 on failure.
+ */
+static int twl4030backupbatt_voltage(void)
+{
+	int ret, temp;
+	u8 volt;
+	struct twl4030_madc_request req;
+
+	req.channels = (1 << 9);
+	req.method = TWL4030_MADC_SW1;
+	twl4030_madc_conversion(&req);
+	temp = (u16)req.rbuf[9];
+
+	return  (temp * BK_VOLT_STEP_SIZE) / BK_VOLT_PSR_R;
+}
+
+/*
+ * Returns an integer value, that means,
+ * NO_PW_CONN  no power supply is connected
+ * AC_PW_CONN  if the AC power supply is connected
+ * USB_PW_CONN  if the USB power supply is connected
+ * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected
+ *
+ * Or < 0 on failure.
+ */
+static int twl4030charger_presence(void)
+{
+	int ret;
+	u8 hwsts;
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts,
+		REG_STS_HW_CONDITIONS);
+	if (ret) {
+		pr_err("BATTERY DRIVER: error reading STS_HW_CONDITIONS \n");
+		return ret;
+	}
+
+	ret = (hwsts & STS_CHG)? AC_PW_CONN: NO_PW_CONN;
+	ret += (hwsts & STS_VBUS)? USB_PW_CONN: NO_PW_CONN;
+
+	if (ret & USB_PW_CONN)
+		usb_charger_flag = 1;
+	else
+		usb_charger_flag = 0;
+
+	return ret;
+
+}
+
+/*
+ * Returns the main charge FSM status
+ * Or < 0 on failure.
+ */
+static int twl4030bci_status(void)
+{
+	int ret;
+	u8 status;
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE,
+		&status, REG_BCIMSTATEC);
+	if (ret) {
+		pr_err("BATTERY DRIVER: error reading BCIMSTATEC \n");
+		return ret;
+	}
+
+	return (int) (status & BCIMSTAT_MASK);
+}
+
+static int read_bci_val(u8 reg)
+{
+	int ret, temp;
+	u8 val;
+
+	/* reading MSB */
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val,
+		reg + 1);
+	if (ret)
+		return ret;
+
+	temp = ((int)(val & 0x03)) << 8;
+
+	/* reading LSB */
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, &val,
+		reg);
+	if (ret)
+		return ret;
+
+	return temp | val;
+}
+
+/*
+ * Settup the twl4030 BCI module to enable backup
+ * battery charging.
+ */
+static int twl4030backupbatt_voltage_setup(void)
+{
+	int ret;
+
+	/* Starting backup batery charge */
+	ret = clear_n_set(TWL4030_MODULE_PM_RECEIVER, 0, BBCHEN,
+		REG_BB_CFG);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Settup the twl4030 BCI module to measure battery
+ * temperature
+ */
+static int twl4030battery_temp_setup(void)
+{
+	int ret;
+
+	/* Enabling thermistor current */
+	ret = clear_n_set(TWL4030_MODULE_MAIN_CHARGE, 0, ITHEN,
+		REG_BCICTL1);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Sets and clears bits on an given register on a given module
+ */
+static inline int clear_n_set(u8 mod_no, u8 clear, u8 set, u8 reg)
+{
+	int ret;
+	u8 val = 0;
+
+	/* Gets the initial register value */
+	ret = twl4030_i2c_read_u8(mod_no, &val, reg);
+	if (ret)
+		return ret;
+
+	/* Clearing all those bits to clear */
+	val &= ~(clear);
+
+	/* Setting all those bits to set */
+	val |= set;
+
+	/* Update the register */
+	ret = twl4030_i2c_write_u8(mod_no, val, reg);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static enum power_supply_property twl4030_bci_battery_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_TEMP,
+};
+
+static enum power_supply_property twl4030_bk_bci_battery_props[] = {
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static void
+twl4030_bk_bci_battery_read_status(struct twl4030_bci_device_info *di)
+{
+	di->bk_voltage_uV = twl4030backupbatt_voltage();
+}
+
+static void twl4030_bk_bci_battery_work(struct work_struct *work)
+{
+	struct twl4030_bci_device_info *di = container_of(work,
+		struct twl4030_bci_device_info,
+		twl4030_bk_bci_monitor_work.work);
+
+	twl4030_bk_bci_battery_read_status(di);
+	schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500);
+}
+
+static void twl4030_bci_battery_read_status(struct twl4030_bci_device_info *di)
+{
+	di->temp_C = twl4030battery_temperature();
+	di->voltage_uV = twl4030battery_voltage();
+	di->current_uA = twl4030battery_current();
+}
+
+static void
+twl4030_bci_battery_update_status(struct twl4030_bci_device_info *di)
+{
+	twl4030_bci_battery_read_status(di);
+	di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+	if (power_supply_am_i_supplied(&di->bat))
+		di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+	else
+		di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static void twl4030_bci_battery_work(struct work_struct *work)
+{
+	struct twl4030_bci_device_info *di = container_of(work,
+		struct twl4030_bci_device_info, twl4030_bci_monitor_work.work);
+
+	twl4030_bci_battery_update_status(di);
+	schedule_delayed_work(&di->twl4030_bci_monitor_work, 100);
+}
+
+
+#define to_twl4030_bci_device_info(x) container_of((x), \
+			struct twl4030_bci_device_info, bat);
+
+static void twl4030_bci_battery_external_power_changed(struct power_supply *psy)
+{
+	struct twl4030_bci_device_info *di = to_twl4030_bci_device_info(psy);
+
+	cancel_delayed_work(&di->twl4030_bci_monitor_work);
+	schedule_delayed_work(&di->twl4030_bci_monitor_work, 0);
+}
+
+#define to_twl4030_bk_bci_device_info(x) container_of((x), \
+		struct twl4030_bci_device_info, bk_bat);
+
+static int twl4030_bk_bci_battery_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct twl4030_bci_device_info *di = to_twl4030_bk_bci_device_info(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = di->bk_voltage_uV;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int twl4030_bci_battery_get_property(struct power_supply *psy,
+					enum power_supply_property psp,
+					union power_supply_propval *val)
+{
+	struct twl4030_bci_device_info *di = to_twl4030_bci_device_info(psy);
+	int status = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = di->charge_status;
+		return 0;
+	default:
+		break;
+	}
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		val->intval = di->voltage_uV;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		val->intval = di->current_uA;
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		val->intval = di->temp_C;
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		status = twl4030bci_status();
+		if ((status & AC_STATEC) == AC_STATEC)
+			val->intval = POWER_SUPPLY_TYPE_MAINS;
+		else if (usb_charger_flag)
+			val->intval = POWER_SUPPLY_TYPE_USB;
+		else
+			val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		/*
+		 * need to get the correct percentage value per the
+		 * battery characteristics. Approx values for now.
+		 */
+		if (di->voltage_uV < 2894 || LVL_1) {
+			val->intval = 5;
+			LVL_1 = 0;
+		} else if ((di->voltage_uV < 3451 && di->voltage_uV > 2894)
+			|| LVL_2) {
+			val->intval = 20;
+			LVL_2 = 0;
+		} else if ((di->voltage_uV < 3902 && di->voltage_uV > 3451)
+			|| LVL_3) {
+			val->intval = 50;
+			LVL_3 = 0;
+		} else if ((di->voltage_uV < 3949 && di->voltage_uV > 3902)
+			|| LVL_4) {
+			val->intval = 75;
+			LVL_4 = 0;
+		} else if (di->voltage_uV > 3949)
+			val->intval = 90;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static char *twl4030_bci_supplied_to[] = {
+	"twl4030_bci_battery",
+};
+
+static int twl4030_bci_battery_probe(struct  platform_device *dev)
+{
+	struct twl4030_bci_device_info *di;
+	int ret;
+	struct twl4030_bci_platform_data *pdata = dev->dev.platform_data;
+
+	therm_tbl = pdata->battery_tmp_tbl;
+
+	di = kzalloc(sizeof(*di), GFP_KERNEL);
+	if (!di)
+		return -ENOMEM;
+
+	platform_set_drvdata(dev, di);
+
+	di->dev = &dev->dev;
+	di->bat.name = "twl4030_bci_battery";
+	di->bat.supplied_to = twl4030_bci_supplied_to;
+	di->bat.num_supplicants = ARRAY_SIZE(twl4030_bci_supplied_to);
+	di->bat.type = POWER_SUPPLY_TYPE_BATTERY;
+	di->bat.properties = twl4030_bci_battery_props;
+	di->bat.num_properties = ARRAY_SIZE(twl4030_bci_battery_props);
+	di->bat.get_property = twl4030_bci_battery_get_property;
+	di->bat.external_power_changed =
+			twl4030_bci_battery_external_power_changed;
+
+	di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+	di->bk_bat.name = "twl4030_bci_bk_battery";
+	di->bk_bat.type = POWER_SUPPLY_TYPE_BATTERY;
+	di->bk_bat.properties = twl4030_bk_bci_battery_props;
+	di->bk_bat.num_properties = ARRAY_SIZE(twl4030_bk_bci_battery_props);
+	di->bk_bat.get_property = twl4030_bk_bci_battery_get_property;
+	di->bk_bat.external_power_changed = NULL;
+
+	twl4030charger_ac_en(ENABLE);
+	twl4030charger_usb_en(ENABLE);
+	twl4030battery_hw_level_en(ENABLE);
+	twl4030battery_hw_presence_en(ENABLE);
+
+	/* settings for temperature sensing */
+	ret = twl4030battery_temp_setup();
+	if (ret)
+		goto temp_setup_fail;
+
+	/* enabling GPCH09 for read back battery voltage */
+	ret = twl4030backupbatt_voltage_setup();
+	if (ret)
+		goto voltage_setup_fail;
+
+	/* request BCI interruption */
+	ret = request_irq(TWL4030_MODIRQ_BCI, twl4030battery_interrupt,
+		IRQF_DISABLED, dev->name, NULL);
+	if (ret) {
+		pr_err("BATTERY DRIVER: (BCI) IRQ%d is not free.\n",
+			TWL4030_MODIRQ_PWR);
+		goto batt_irq_fail;
+	}
+
+	/* request Power interruption */
+	ret = request_irq(TWL4030_PWRIRQ_CHG_PRES, twl4030charger_interrupt,
+		0, dev->name, di);
+
+	if (ret) {
+		pr_err("BATTERY DRIVER: (POWER) IRQ%d is not free.\n",
+			TWL4030_MODIRQ_PWR);
+		goto chg_irq_fail;
+	}
+
+	ret = power_supply_register(&dev->dev, &di->bat);
+	if (ret) {
+		pr_err("BATTERY DRIVER: failed to register main battery\n");
+		goto batt_failed;
+	}
+
+	INIT_DELAYED_WORK_DEFERRABLE(&di->twl4030_bci_monitor_work,
+				twl4030_bci_battery_work);
+	schedule_delayed_work(&di->twl4030_bci_monitor_work, 0);
+
+	ret = power_supply_register(&dev->dev, &di->bk_bat);
+	if (ret) {
+		pr_err("BATTERY DRIVER: failed to register backup battery\n");
+		goto bk_batt_failed;
+	}
+
+	INIT_DELAYED_WORK_DEFERRABLE(&di->twl4030_bk_bci_monitor_work,
+				twl4030_bk_bci_battery_work);
+	schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 500);
+
+	return 0;
+
+bk_batt_failed:
+	power_supply_unregister(&di->bat);
+batt_failed:
+	free_irq(TWL4030_MODIRQ_PWR, di);
+chg_irq_fail:
+prev_setup_err:
+	free_irq(TWL4030_MODIRQ_BCI, NULL);
+batt_irq_fail:
+voltage_setup_fail:
+temp_setup_fail:
+	twl4030charger_ac_en(DISABLE);
+	twl4030charger_usb_en(DISABLE);
+	twl4030battery_hw_level_en(DISABLE);
+	twl4030battery_hw_presence_en(DISABLE);
+	kfree(di);
+
+	return ret;
+}
+
+static int twl4030_bci_battery_remove(struct  platform_device *dev)
+{
+	struct twl4030_bci_device_info *di = platform_get_drvdata(dev);
+
+	twl4030charger_ac_en(DISABLE);
+	twl4030charger_usb_en(DISABLE);
+	twl4030battery_hw_level_en(DISABLE);
+	twl4030battery_hw_presence_en(DISABLE);
+
+	free_irq(TWL4030_MODIRQ_BCI, NULL);
+	free_irq(TWL4030_MODIRQ_PWR, di);
+
+	flush_scheduled_work();
+	power_supply_unregister(&di->bat);
+	power_supply_unregister(&di->bk_bat);
+	platform_set_drvdata(dev, NULL);
+	kfree(di);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int twl4030_bci_battery_suspend(struct platform_device *dev,
+	pm_message_t state)
+{
+	struct twl4030_bci_device_info *di = platform_get_drvdata(dev);
+
+	di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+	cancel_delayed_work(&di->twl4030_bci_monitor_work);
+	cancel_delayed_work(&di->twl4030_bk_bci_monitor_work);
+	return 0;
+}
+
+static int twl4030_bci_battery_resume(struct platform_device *dev)
+{
+	struct twl4030_bci_device_info *di = platform_get_drvdata(dev);
+
+	schedule_delayed_work(&di->twl4030_bci_monitor_work, 0);
+	schedule_delayed_work(&di->twl4030_bk_bci_monitor_work, 50);
+	return 0;
+}
+#endif /* CONFIG_PM */
+
+/*
+ * Battery driver module initializer function
+ * registers battery driver structure
+ */
+static int __init twl4030_battery_init(void)
+{
+	return platform_driver_register(&twl4030_bci_battery_driver);
+
+}
+
+/*
+ * Battery driver module exit function
+ * unregister battery driver structure
+ */
+static void __exit twl4030_battery_exit(void)
+{
+	platform_driver_unregister(&twl4030_bci_battery_driver);
+}
+
+module_init(twl4030_battery_init);
+module_exit(twl4030_battery_exit);
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("twl4030_bci_battery");
+MODULE_AUTHOR("Texas Instruments Inc");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 26/33] add TWL4030 RealTime Clock driver
  2008-08-30 17:16                                                 ` [PATCH 25/33] add TWL4030 Battery Charger Interface driver Felipe Balbi
@ 2008-08-30 17:16                                                   ` Felipe Balbi
  2008-08-30 17:16                                                     ` [PATCH 27/33] add tsc210x driver Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/rtc/Kconfig             |   10 +
 drivers/rtc/Makefile            |    1 +
 drivers/rtc/rtc-twl4030.c       |  649 +++++++++++++++++++++++++++++++++++++++
 include/linux/i2c/twl4030-rtc.h |  230 ++++++++++++++
 4 files changed, 890 insertions(+), 0 deletions(-)
 create mode 100644 drivers/rtc/rtc-twl4030.c
 create mode 100644 include/linux/i2c/twl4030-rtc.h

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 9a9755c..9f597ac 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -246,6 +246,16 @@ config RTC_DRV_TWL92330
 	  platforms.  The support is integrated with the rest of
 	  the Menelaus driver; it's not separate module.
 
+config RTC_DRV_TWL4030
+	tristate "OMAP TWL4030 Real Time Clock"
+	depends on RTC_CLASS && TWL4030_CORE
+	help
+	  If you say yes here you get support for internal Real-Time
+	  Clock of TWL4030 chip.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called rtc-twl4030.
+
 config RTC_DRV_S35390A
 	tristate "Seiko Instruments S-35390A"
 	select BITREVERSE
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 18622ef..3ef1dfc 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -56,6 +56,7 @@ obj-$(CONFIG_RTC_DRV_SA1100)	+= rtc-sa1100.o
 obj-$(CONFIG_RTC_DRV_SH)	+= rtc-sh.o
 obj-$(CONFIG_RTC_DRV_STK17TA8)	+= rtc-stk17ta8.o
 obj-$(CONFIG_RTC_DRV_TEST)	+= rtc-test.o
+obj-$(CONFIG_RTC_DRV_TWL4030)	+= rtc-twl4030.o
 obj-$(CONFIG_RTC_DRV_V3020)	+= rtc-v3020.o
 obj-$(CONFIG_RTC_DRV_VR41XX)	+= rtc-vr41xx.o
 obj-$(CONFIG_RTC_DRV_X1205)	+= rtc-x1205.o
diff --git a/drivers/rtc/rtc-twl4030.c b/drivers/rtc/rtc-twl4030.c
new file mode 100644
index 0000000..e4a3658
--- /dev/null
+++ b/drivers/rtc/rtc-twl4030.c
@@ -0,0 +1,649 @@
+/*
+ * drivers/rtc/rtc-twl4030.c
+ *
+ * TWL4030 Real Time Clock interface
+ *
+ * Copyright (C) 2007 MontaVista Software, Inc
+ * Author: Alexandre Rusev <source@mvista.com>
+ *
+ * Based on original TI driver twl4030-rtc.c
+ *   Copyright (C) 2006 Texas Instruments, Inc.
+ *
+ * Based on rtc-omap.c
+ *   Copyright (C) 2003 MontaVista Software, Inc.
+ *   Author: George G. Davis <gdavis@mvista.com> or <source@mvista.com>
+ *
+ *   Copyright (C) 2006 David Brownell
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/ioport.h>
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/rtc.h>
+#include <linux/bcd.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/i2c/twl4030.h>
+#include <linux/i2c/twl4030-rtc.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+
+#include <asm/mach/time.h>
+#include <asm/system.h>
+#include <mach/hardware.h>
+
+#define ALL_TIME_REGS		6
+
+/*
+ * If this driver ever becomes modularised, it will be really nice
+ * to make the epoch retain its value across module reload...
+ */
+static int epoch = 1900;	/* year corresponding to 0x00   */
+
+/*
+ * Supports 1 byte read from TWL4030 RTC register.
+ */
+static int twl4030_rtc_read_u8(u8 *data, u8 reg)
+{
+	int ret;
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_RTC, data, reg);
+	if (ret < 0) {
+		printk(KERN_WARNING "twl4030_rtc: Could not read TWL4030"
+		       "register %X - returned %d[%x]\n", reg, ret, ret);
+	}
+	return ret;
+}
+
+/*
+ * Supports 1 byte write to TWL4030 RTC registers.
+ */
+static int twl4030_rtc_write_u8(u8 data, u8 reg)
+{
+	int ret;
+
+	ret = twl4030_i2c_write_u8(TWL4030_MODULE_RTC, data, reg);
+	if (ret < 0) {
+		printk(KERN_WARNING "twl4030_rtc: Could not write TWL4030"
+		       "register %X - returned %d[%x]\n", reg, ret, ret);
+	}
+	return ret;
+}
+
+/*
+ * Enables timer or alarm interrupts.
+ */
+static int set_rtc_irq_bit(unsigned char bit)
+{
+	unsigned char val;
+	int ret;
+
+	ret = twl4030_rtc_read_u8(&val, REG_RTC_INTERRUPTS_REG);
+	if (ret < 0)
+		goto set_irq_out;
+
+	val |= bit;
+	ret = twl4030_rtc_write_u8(val, REG_RTC_INTERRUPTS_REG);
+
+set_irq_out:
+	return ret;
+}
+
+#ifdef CONFIG_PM
+/*
+ * Read timer or alarm interrupts register.
+ */
+static int get_rtc_irq_bit(unsigned char *val)
+{
+	int ret;
+
+	ret = twl4030_rtc_read_u8(val, REG_RTC_INTERRUPTS_REG);
+	return ret;
+}
+#endif
+/*
+ * Disables timer or alarm interrupts.
+ */
+static int mask_rtc_irq_bit(unsigned char bit)
+{
+	unsigned char val;
+	int ret;
+
+	ret = twl4030_rtc_read_u8(&val, REG_RTC_INTERRUPTS_REG);
+	if (ret < 0)
+		goto mask_irq_out;
+
+	val &= ~bit;
+	ret = twl4030_rtc_write_u8(val, REG_RTC_INTERRUPTS_REG);
+
+mask_irq_out:
+	return ret;
+}
+
+static int twl4030_rtc_alarm_irq_set_state(struct device *dev, int enabled)
+{
+	int ret;
+
+	/* Allow ints for RTC ALARM updates.  */
+	if (enabled)
+		ret = set_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M);
+	else
+		ret = mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M);
+
+	return ret;
+}
+
+/*
+ * Gets current TWL4030 RTC time and date parameters.
+ */
+static int get_rtc_time(struct rtc_time *rtc_tm)
+{
+	unsigned char rtc_data[ALL_TIME_REGS + 1];
+	int ret;
+	u8 save_control;
+
+	ret = twl4030_rtc_read_u8(&save_control, REG_RTC_CTRL_REG);
+	if (ret < 0)
+		return ret;
+
+	save_control |= BIT_RTC_CTRL_REG_GET_TIME_M;
+
+	ret = twl4030_rtc_write_u8(save_control, REG_RTC_CTRL_REG);
+	if (ret < 0)
+		return ret;
+
+	ret = twl4030_i2c_read(TWL4030_MODULE_RTC, rtc_data,
+			       REG_SECONDS_REG, ALL_TIME_REGS);
+
+	if (ret < 0) {
+		printk(KERN_ERR "twl4030_rtc: twl4030_i2c_read error.\n");
+		return ret;
+	}
+
+	rtc_tm->tm_sec = BCD2BIN(rtc_data[0]);
+	rtc_tm->tm_min = BCD2BIN(rtc_data[1]);
+	rtc_tm->tm_hour = BCD2BIN(rtc_data[2]);
+	rtc_tm->tm_mday = BCD2BIN(rtc_data[3]);
+	rtc_tm->tm_mon = BCD2BIN(rtc_data[4]);
+	rtc_tm->tm_year = BCD2BIN(rtc_data[5]);
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+	rtc_tm->tm_year += (epoch - 1900);
+	if (rtc_tm->tm_year <= 69)
+		rtc_tm->tm_year += 100;
+
+	rtc_tm->tm_mon--;
+
+	return ret;
+}
+
+static int twl4030_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	unsigned char save_control;
+	unsigned char rtc_data[ALL_TIME_REGS + 1];
+	int ret;
+
+	/* Month range is 01..12 */
+	tm->tm_mon++;
+
+	rtc_data[1] = BIN2BCD(tm->tm_sec);
+	rtc_data[2] = BIN2BCD(tm->tm_min);
+	rtc_data[3] = BIN2BCD(tm->tm_hour);
+	rtc_data[4] = BIN2BCD(tm->tm_mday);
+	rtc_data[5] = BIN2BCD(tm->tm_mon);
+	rtc_data[6] = BIN2BCD(tm->tm_year);
+
+	/* Stop RTC while updating the TC registers */
+	ret = twl4030_rtc_read_u8(&save_control, REG_RTC_CTRL_REG);
+	if (ret < 0)
+		goto out;
+
+	save_control &= ~BIT_RTC_CTRL_REG_STOP_RTC_M;
+	twl4030_rtc_write_u8(save_control, REG_RTC_CTRL_REG);
+	if (ret < 0)
+		goto out;
+
+	/* update all the alarm registers in one shot */
+	ret = twl4030_i2c_write(TWL4030_MODULE_RTC, rtc_data,
+			REG_SECONDS_REG, ALL_TIME_REGS);
+	if (ret < 0) {
+		printk(KERN_ERR "twl4030: twl4030_i2c_write error.\n");
+		goto out;
+	}
+
+	/* Start back RTC */
+	save_control |= BIT_RTC_CTRL_REG_STOP_RTC_M;
+	ret = twl4030_rtc_write_u8(save_control, REG_RTC_CTRL_REG);
+
+out:
+	return ret;
+}
+
+/*
+ * Gets current TWL4030 RTC alarm time.
+ */
+static int get_rtc_alm_time(struct rtc_time *alm_tm)
+{
+	unsigned char rtc_data[ALL_TIME_REGS + 1];
+	int ret;
+
+	ret = twl4030_i2c_read(TWL4030_MODULE_RTC, rtc_data,
+			       REG_ALARM_SECONDS_REG, ALL_TIME_REGS);
+	if (ret < 0) {
+		printk(KERN_ERR "twl4030_rtc: twl4030_i2c_read error.\n");
+		return ret;
+	}
+
+	alm_tm->tm_sec = BCD2BIN(rtc_data[0]);
+	alm_tm->tm_min = BCD2BIN(rtc_data[1]);
+	alm_tm->tm_hour = BCD2BIN(rtc_data[2]);
+	alm_tm->tm_mday = BCD2BIN(rtc_data[3]);
+	alm_tm->tm_mon = BCD2BIN(rtc_data[4]);
+	alm_tm->tm_year = BCD2BIN(rtc_data[5]);
+
+	/*
+	 * Account for differences between how the RTC uses the values
+	 * and how they are defined in a struct rtc_time;
+	 */
+	alm_tm->tm_year += (epoch - 1900);
+	if (alm_tm->tm_year <= 69)
+		alm_tm->tm_year += 100;
+
+	alm_tm->tm_mon--;
+
+	return ret;
+}
+
+static int twl4030_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+	int ret;
+
+	memset(tm, 0, sizeof(struct rtc_time));
+	ret = get_rtc_time(tm);
+
+	return ret;
+}
+
+/*
+ * Gets current TWL4030 RTC alarm time.
+ */
+static int twl4030_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	int ret;
+	u8 rtc_interrupts_reg = 0;
+
+	/*
+	 * This returns a struct rtc_time. Reading >= 0xc0
+	 * means "don't care" or "match all". Only the tm_hour,
+	 * tm_min, and tm_sec values are filled in.
+	 */
+	memset(&alm->time, 0, sizeof(struct rtc_time));
+	ret = get_rtc_alm_time(&alm->time);
+
+	if (ret)
+		goto out;
+
+	/* Check alarm enabled flag state */
+	ret =
+	    ret | twl4030_i2c_read_u8(TWL4030_MODULE_RTC, &rtc_interrupts_reg,
+				      REG_RTC_INTERRUPTS_REG);
+
+	if (ret)
+		goto out;
+
+	if ((rtc_interrupts_reg & BIT_RTC_INTERRUPTS_REG_IT_ALARM_M) != 0)
+		alm->enabled = 1;
+	else
+		alm->enabled = 0;
+
+out:
+	return ret;
+}
+
+static int twl4030_rtc_irq_set_state(struct device *dev, int enabled)
+{
+	int ret;
+
+	/* Allow ints for RTC updates.  */
+	if (enabled)
+		ret = set_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M);
+	else
+		ret = mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M);
+
+	return ret;
+}
+
+static int twl4030_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+	unsigned char alarm_data[ALL_TIME_REGS + 1];
+	int ret;
+
+	/* Month range is 01..12 */
+	alm->time.tm_mon++;
+
+	alarm_data[1] = BIN2BCD(alm->time.tm_sec);
+	alarm_data[2] = BIN2BCD(alm->time.tm_min);
+	alarm_data[3] = BIN2BCD(alm->time.tm_hour);
+	alarm_data[4] = BIN2BCD(alm->time.tm_mday);
+	alarm_data[5] = BIN2BCD(alm->time.tm_mon);
+	alarm_data[6] = BIN2BCD(alm->time.tm_year);
+
+	/* update all the alarm registers in one shot */
+	ret = twl4030_i2c_write(TWL4030_MODULE_RTC, alarm_data,
+			REG_ALARM_SECONDS_REG, ALL_TIME_REGS);
+	if (ret) {
+		printk(KERN_ERR "twl4030: twl4030_i2c_write error.\n");
+		goto out;
+	}
+
+	ret = twl4030_rtc_alarm_irq_set_state(dev, alm->enabled);
+out:
+	return ret;
+}
+
+/*
+ * We will just handle setting the frequency and make use the framework for
+ * reading the periodic interupts.
+ * @freq: Current periodic IRQ freq
+ */
+static int twl4030_rtc_irq_set_freq(struct device *dev, int freq)
+{
+	struct rtc_device *rtc = dev_get_drvdata(dev);
+
+	if (freq < 0 || freq > 3)
+		return -EINVAL;
+
+	rtc->irq_freq = freq;
+
+	/* set rtc irq freq to user defined value */
+	set_rtc_irq_bit(freq);
+
+	return 0;
+}
+
+#ifdef	CONFIG_RTC_INTF_DEV
+
+static int twl4030_rtc_ioctl(struct device *dev, unsigned int cmd,
+			     unsigned long arg)
+{
+
+	switch (cmd) {
+	case RTC_AIE_OFF:
+		return twl4030_rtc_alarm_irq_set_state(dev, 0);
+	case RTC_AIE_ON:
+		return twl4030_rtc_alarm_irq_set_state(dev, 1);
+
+	case RTC_UIE_OFF:
+		/* Fall Through */
+	case RTC_PIE_OFF:
+		/* Mask ints from RTC updates.	*/
+		return twl4030_rtc_irq_set_state(dev, 0);
+	case RTC_UIE_ON:
+		/* Fall Through */
+	case RTC_PIE_ON:
+		/* Allow ints for RTC updates.	*/
+		return twl4030_rtc_irq_set_state(dev, 1);
+
+	case RTC_EPOCH_READ:
+		return put_user(epoch, (unsigned long *)arg);
+	case RTC_EPOCH_SET:
+		/*
+		 * There were no RTC clocks before 1900.
+		 */
+		if (arg < 1900)
+			return -EINVAL;
+
+		if (!capable(CAP_SYS_TIME))
+			return -EACCES;
+
+		epoch = arg;
+		return 0;
+	default:
+		return -ENOIOCTLCMD;
+	}
+}
+
+#else
+#define	omap_rtc_ioctl	NULL
+#endif
+
+static irqreturn_t twl4030_rtc_interrupt(int irq, void *rtc)
+{
+	unsigned long events = 0;
+	int ret = IRQ_NONE;
+	int res;
+	u8 rd_reg;
+
+	res = twl4030_rtc_read_u8(&rd_reg, REG_RTC_STATUS_REG);
+	if (res)
+		goto out;
+	/*
+	 * Figure out source of interrupt: ALARM or TIMER in RTC_STATUS_REG.
+	 * only one (ALARM or RTC) interrupt source may be enabled
+	 * at time, we also could check our results
+	 * by reading RTS_INTERRUPTS_REGISTER[IT_TIMER,IT_ALARM]
+	 */
+	if (rd_reg & BIT_RTC_STATUS_REG_ALARM_M)
+		events |= RTC_IRQF | RTC_AF;
+	else
+		events |= RTC_IRQF | RTC_UF;
+
+	res = twl4030_rtc_write_u8(rd_reg | BIT_RTC_STATUS_REG_ALARM_M,
+				   REG_RTC_STATUS_REG);
+	if (res)
+		goto out;
+	res = twl4030_i2c_write_u8(TWL4030_MODULE_INT,
+			PWR_RTC_INT_CLR, REG_PWR_ISR1);
+	if (res)
+		goto out;
+
+	/* Notify RTC core on event */
+	rtc_update_irq(rtc, 1, events);
+
+	ret = IRQ_HANDLED;
+out:
+	return ret;
+}
+
+static struct rtc_class_ops twl4030_rtc_ops = {
+	.ioctl		= twl4030_rtc_ioctl,
+	.read_time	= twl4030_rtc_read_time,
+	.set_time	= twl4030_rtc_set_time,
+	.read_alarm	= twl4030_rtc_read_alarm,
+	.set_alarm	= twl4030_rtc_set_alarm,
+	.irq_set_freq	= twl4030_rtc_irq_set_freq,
+};
+
+static int __devinit twl4030_rtc_probe(struct platform_device *pdev)
+{
+	struct twl4030rtc_platform_data *pdata = pdev->dev.platform_data;
+	struct rtc_device *rtc;
+	int ret = 0;
+	u8 rd_reg;
+
+	if (pdata != NULL && pdata->init != NULL) {
+		ret = pdata->init();
+		if (ret < 0)
+			goto out;
+	}
+
+	rtc = rtc_device_register(pdev->name,
+				  &pdev->dev, &twl4030_rtc_ops, THIS_MODULE);
+	if (IS_ERR(rtc)) {
+		ret = -EINVAL;
+		dev_err(&pdev->dev, "can't register RTC device, err %ld\n",
+			PTR_ERR(rtc));
+		goto out0;
+
+	}
+
+	/* Set the irq freq to every second */
+	rtc->irq_freq = 0;
+
+	platform_set_drvdata(pdev, rtc);
+
+	ret = twl4030_rtc_read_u8(&rd_reg, REG_RTC_STATUS_REG);
+
+	if (ret < 0)
+		goto out1;
+
+	if (rd_reg & BIT_RTC_STATUS_REG_POWER_UP_M)
+		dev_warn(&pdev->dev, "Power up reset detected.\n");
+
+	if (rd_reg & BIT_RTC_STATUS_REG_ALARM_M)
+		dev_warn(&pdev->dev, "Pending Alarm interrupt detected.\n");
+
+	/* Clear RTC Power up reset and pending alarm interrupts */
+	ret = twl4030_rtc_write_u8(rd_reg, REG_RTC_STATUS_REG);
+	if (ret < 0)
+		goto out1;
+
+	ret = request_irq(TWL4030_PWRIRQ_RTC, twl4030_rtc_interrupt,
+				0, rtc->dev.bus_id, rtc);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "IRQ is not free.\n");
+		goto out1;
+	}
+
+	/* Check RTC module status, Enable if it is off */
+	ret = twl4030_rtc_read_u8(&rd_reg, REG_RTC_CTRL_REG);
+	if (ret < 0)
+		goto out2;
+
+	if (!(rd_reg & BIT_RTC_CTRL_REG_STOP_RTC_M)) {
+		dev_info(&pdev->dev, "Enabling TWL4030-RTC.\n");
+		rd_reg = BIT_RTC_CTRL_REG_STOP_RTC_M;
+		ret = twl4030_rtc_write_u8(rd_reg, REG_RTC_CTRL_REG);
+		if (ret < 0)
+			goto out2;
+	}
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &rd_reg, REG_PWR_IMR1);
+	if (ret < 0)
+		goto out2;
+
+	rd_reg &= PWR_RTC_IT_UNMASK;
+	/* MASK PWR - we will need this */
+	ret = twl4030_i2c_write_u8(TWL4030_MODULE_INT, rd_reg, REG_PWR_IMR1);
+	if (ret < 0)
+		goto out2;
+
+	ret = twl4030_i2c_read_u8(TWL4030_MODULE_INT, &rd_reg, REG_PWR_EDR1);
+	if (ret < 0)
+		goto out2;
+
+	/* Rising edge detection enabled, needed for RTC alarm */
+	rd_reg |= 0x80;
+	ret = twl4030_i2c_write_u8(TWL4030_MODULE_INT, rd_reg, REG_PWR_EDR1);
+	if (ret < 0)
+		goto out2;
+
+	return ret;
+
+
+out2:
+	free_irq(TWL4030_MODIRQ_PWR, rtc);
+out1:
+	rtc_device_unregister(rtc);
+out0:
+	if (pdata != NULL && pdata->exit != NULL)
+		pdata->exit();
+out:
+	return ret;
+}
+
+/*
+ * Disable all TWL4030 RTC module interrupts.
+ * Sets status flag to free.
+ */
+static int __devexit twl4030_rtc_remove(struct platform_device *pdev)
+{
+	/* leave rtc running, but disable irqs */
+	struct twl4030rtc_platform_data *pdata = pdev->dev.platform_data;
+	struct rtc_device *rtc = platform_get_drvdata(pdev);
+
+	mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_ALARM_M);
+	mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M);
+
+	free_irq(TWL4030_MODIRQ_PWR, rtc);
+
+	if (pdata != NULL && pdata->exit != NULL)
+		pdata->exit();
+
+	rtc_device_unregister(rtc);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static void twl4030_rtc_shutdown(struct platform_device *pdev)
+{
+	twl4030_rtc_alarm_irq_set_state(&pdev->dev, 0);
+	twl4030_rtc_irq_set_state(&pdev->dev, 0);
+}
+
+#ifdef CONFIG_PM
+
+static unsigned char irqstat;
+
+static int twl4030_rtc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	get_rtc_irq_bit(&irqstat);
+
+	mask_rtc_irq_bit(BIT_RTC_INTERRUPTS_REG_IT_TIMER_M |
+			 BIT_RTC_INTERRUPTS_REG_IT_ALARM_M);
+	return 0;
+}
+
+static int twl4030_rtc_resume(struct platform_device *pdev)
+{
+	set_rtc_irq_bit(irqstat);
+	return 0;
+}
+#else
+#define twl4030_rtc_suspend NULL
+#define twl4030_rtc_resume  NULL
+#endif
+
+MODULE_ALIAS("platform:twl4030_rtc");
+static struct platform_driver twl4030rtc_driver = {
+	.probe 		= twl4030_rtc_probe,
+	.remove 	= __devexit_p(twl4030_rtc_remove),
+	.shutdown 	= twl4030_rtc_shutdown,
+	.suspend 	= twl4030_rtc_suspend,
+	.resume 	= twl4030_rtc_resume,
+	.driver 	= {
+		.owner	= THIS_MODULE,
+		.name	= "twl4030_rtc",
+	},
+};
+
+static int __init twl4030_rtc_init(void)
+{
+	return platform_driver_register(&twl4030rtc_driver);
+}
+
+static void __exit twl4030_rtc_exit(void)
+{
+	platform_driver_unregister(&twl4030rtc_driver);
+}
+
+MODULE_ALIAS("platform:twl4030_rtc");
+MODULE_AUTHOR("Texas Instruments, MontaVista Software");
+MODULE_LICENSE("GPL");;
+
+module_init(twl4030_rtc_init);
+module_exit(twl4030_rtc_exit);
diff --git a/include/linux/i2c/twl4030-rtc.h b/include/linux/i2c/twl4030-rtc.h
new file mode 100644
index 0000000..fa74613
--- /dev/null
+++ b/include/linux/i2c/twl4030-rtc.h
@@ -0,0 +1,230 @@
+/*
+ * include/asm-arm/arch-omap/twl4030-rtc.h
+ *
+ * Copyright (C) 2006 Texas Instruments, Inc.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+#ifndef __TWL4030_RTC_H__
+#define __TWL4030_RTC_H__
+
+#define REG_SECONDS_REG                          (0x0)
+#define REG_MINUTES_REG                          (0x1)
+#define REG_HOURS_REG                            (0x2)
+#define REG_DAYS_REG                             (0x3)
+#define REG_MONTHS_REG                           (0x4)
+#define REG_YEARS_REG                            (0x5)
+#define REG_WEEKS_REG                            (0x6)
+#define REG_ALARM_SECONDS_REG                    (0x7)
+#define REG_ALARM_MINUTES_REG                    (0x8)
+#define REG_ALARM_HOURS_REG                      (0x9)
+#define REG_ALARM_DAYS_REG                       (0xA)
+#define REG_ALARM_MONTHS_REG                     (0xB)
+#define REG_ALARM_YEARS_REG                      (0xC)
+#define REG_RTC_CTRL_REG                         (0xD)
+#define REG_RTC_STATUS_REG                       (0xE)
+#define REG_RTC_INTERRUPTS_REG                   (0xF)
+#define REG_RTC_COMP_LSB_REG                     (0x10)
+#define REG_RTC_COMP_MSB_REG                     (0x11)
+
+/* REVISIT: these TWL4030 power registers are only used
+ * by rtc-twl4030 driver, move to an appropriate header
+ * if other drivers need the registers
+ */
+/* Power registers */
+#define REG_PWR_ISR1		0x00
+#define REG_PWR_IMR1		0x01
+#define REG_PWR_EDR1		0x05
+
+#define PWR_RTC_IT_UNMASK	 ~(0x08)
+#define PWR_RTC_INT_CLR          0x08
+
+/**** BitField Definitions */
+/* SECONDS_REG Fields */
+#define BIT_SECONDS_REG_SEC0                     (0x000)
+#define BIT_SECONDS_REG_SEC0_M                   (0x0000000F)
+#define BIT_SECONDS_REG_SEC1                     (0x004)
+#define BIT_SECONDS_REG_SEC1_M                   (0x00000070)
+/* MINUTES_REG Fields */
+#define BIT_MINUTES_REG_MIN0                     (0x000)
+#define BIT_MINUTES_REG_MIN0_M                   (0x0000000F)
+#define BIT_MINUTES_REG_MIN1                     (0x004)
+#define BIT_MINUTES_REG_MIN1_M                   (0x00000070)
+/* HOURS_REG Fields */
+#define BIT_HOURS_REG_HOUR0                      (0x000)
+#define BIT_HOURS_REG_HOUR0_M                    (0x0000000F)
+#define BIT_HOURS_REG_HOUR1                      (0x004)
+#define BIT_HOURS_REG_HOUR1_M                    (0x00000030)
+#define BIT_HOURS_REG_PM_NAM                     (0x007)
+#define BIT_HOURS_REG_PM_NAM_M                   (0x00000080)
+/* DAYS_REG Fields */
+#define BIT_DAYS_REG_DAY0                        (0x000)
+#define BIT_DAYS_REG_DAY0_M                      (0x0000000F)
+#define BIT_DAYS_REG_DAY1                        (0x004)
+#define BIT_DAYS_REG_DAY1_M                      (0x00000030)
+/* MONTHS_REG Fields */
+#define BIT_MONTHS_REG_MONTH0                    (0x000)
+#define BIT_MONTHS_REG_MONTH0_M                  (0x0000000F)
+#define BIT_MONTHS_REG_MONTH1                    (0x004)
+#define BIT_MONTHS_REG_MONTH1_M                  (0x00000010)
+/* YEARS_REG Fields */
+#define BIT_YEARS_REG_YEAR0                      (0x000)
+#define BIT_YEARS_REG_YEAR0_M                    (0x0000000F)
+#define BIT_YEARS_REG_YEAR1                      (0x004)
+#define BIT_YEARS_REG_YEAR1_M                    (0x000000F0)
+/* WEEKS_REG Fields */
+#define BIT_WEEKS_REG_WEEK                       (0x000)
+#define BIT_WEEKS_REG_WEEK_M                     (0x00000007)
+/* ALARM_SECONDS_REG Fields */
+#define BIT_ALARM_SECONDS_REG_ALARM_SEC0         (0x000)
+#define BIT_ALARM_SECONDS_REG_ALARM_SEC0_M       (0x0000000F)
+#define BIT_ALARM_SECONDS_REG_ALARM_SEC1         (0x004)
+#define BIT_ALARM_SECONDS_REG_ALARM_SEC1_M       (0x00000070)
+/* ALARM_MINUTES_REG Fields */
+#define BIT_ALARM_MINUTES_REG_ALARM_MIN0         (0x000)
+#define BIT_ALARM_MINUTES_REG_ALARM_MIN0_M       (0x0000000F)
+#define BIT_ALARM_MINUTES_REG_ALARM_MIN1         (0x004)
+#define BIT_ALARM_MINUTES_REG_ALARM_MIN1_M       (0x00000070)
+/* ALARM_HOURS_REG Fields */
+#define BIT_ALARM_HOURS_REG_ALARM_HOUR0          (0x000)
+#define BIT_ALARM_HOURS_REG_ALARM_HOUR0_M        (0x0000000F)
+#define BIT_ALARM_HOURS_REG_ALARM_HOUR1          (0x004)
+#define BIT_ALARM_HOURS_REG_ALARM_HOUR1_M        (0x00000030)
+#define BIT_ALARM_HOURS_REG_ALARM_PM_NAM         (0x007)
+#define BIT_ALARM_HOURS_REG_ALARM_PM_NAM_M       (0x00000080)
+/* ALARM_DAYS_REG Fields */
+#define BIT_ALARM_DAYS_REG_ALARM_DAY0            (0x000)
+#define BIT_ALARM_DAYS_REG_ALARM_DAY0_M          (0x0000000F)
+#define BIT_ALARM_DAYS_REG_ALARM_DAY1            (0x004)
+#define BIT_ALARM_DAYS_REG_ALARM_DAY1_M          (0x00000030)
+/* ALARM_MONTHS_REG Fields */
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH0        (0x000)
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH0_M      (0x0000000F)
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH1        (0x004)
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH1_M      (0x00000010)
+/* ALARM_YEARS_REG Fields */
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR0          (0x000)
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR0_M        (0x0000000F)
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR1          (0x004)
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR1_M        (0x000000F0)
+/* RTC_CTRL_REG Fields */
+#define BIT_RTC_CTRL_REG_STOP_RTC                (0x000)
+#define BIT_RTC_CTRL_REG_STOP_RTC_M              (0x00000001)
+#define BIT_RTC_CTRL_REG_ROUND_30S               (0x001)
+#define BIT_RTC_CTRL_REG_ROUND_30S_M             (0x00000002)
+#define BIT_RTC_CTRL_REG_AUTO_COMP               (0x002)
+#define BIT_RTC_CTRL_REG_AUTO_COMP_M             (0x00000004)
+#define BIT_RTC_CTRL_REG_MODE_12_24              (0x003)
+#define BIT_RTC_CTRL_REG_MODE_12_24_M            (0x00000008)
+#define BIT_RTC_CTRL_REG_TEST_MODE               (0x004)
+#define BIT_RTC_CTRL_REG_TEST_MODE_M             (0x00000010)
+#define BIT_RTC_CTRL_REG_SET_32_COUNTER          (0x005)
+#define BIT_RTC_CTRL_REG_SET_32_COUNTER_M        (0x00000020)
+#define BIT_RTC_CTRL_REG_GET_TIME                (0x006)
+#define BIT_RTC_CTRL_REG_GET_TIME_M              (0x00000040)
+/* RTC_STATUS_REG Fields */
+#define BIT_RTC_STATUS_REG_RUN                   (0x001)
+#define BIT_RTC_STATUS_REG_RUN_M                 (0x00000002)
+#define BIT_RTC_STATUS_REG_1S_EVENT              (0x002)
+#define BIT_RTC_STATUS_REG_1S_EVENT_M            (0x00000004)
+#define BIT_RTC_STATUS_REG_1M_EVENT              (0x003)
+#define BIT_RTC_STATUS_REG_1M_EVENT_M            (0x00000008)
+#define BIT_RTC_STATUS_REG_1H_EVENT              (0x004)
+#define BIT_RTC_STATUS_REG_1H_EVENT_M            (0x00000010)
+#define BIT_RTC_STATUS_REG_1D_EVENT              (0x005)
+#define BIT_RTC_STATUS_REG_1D_EVENT_M            (0x00000020)
+#define BIT_RTC_STATUS_REG_ALARM                 (0x006)
+#define BIT_RTC_STATUS_REG_ALARM_M               (0x00000040)
+#define BIT_RTC_STATUS_REG_POWER_UP              (0x007)
+#define BIT_RTC_STATUS_REG_POWER_UP_M            (0x00000080)
+
+/* RTC_INTERRUPTS_REG Fields */
+#define BIT_RTC_INTERRUPTS_REG_EVERY             (0x000)
+#define BIT_RTC_INTERRUPTS_REG_EVERY_M           (0x00000003)
+#define BIT_RTC_INTERRUPTS_REG_IT_TIMER          (0x002)
+#define BIT_RTC_INTERRUPTS_REG_IT_TIMER_M        (0x00000004)
+#define BIT_RTC_INTERRUPTS_REG_IT_ALARM          (0x003)
+#define BIT_RTC_INTERRUPTS_REG_IT_ALARM_M        (0x00000008)
+/* RTC_COMP_LSB_REG Fields */
+#define BIT_RTC_COMP_LSB_REG_RTC_COMP_LSB        (0x000)
+#define BIT_RTC_COMP_LSB_REG_RTC_COMP_LSB_M      (0x000000FF)
+/* RTC_COMP_MSB_REG Fields */
+#define BIT_RTC_COMP_MSB_REG_RTC_COMP_MSB        (0x000)
+#define BIT_RTC_COMP_MSB_REG_RTC_COMP_MSB_M      (0x000000FF)
+
+/* ALARM_DAYS_REG Fields */
+#define BIT_ALARM_DAYS_REG_ALARM_DAY1            (0x004)
+#define BIT_ALARM_DAYS_REG_ALARM_DAY1_M          (0x00000030)
+/* ALARM_MONTHS_REG Fields */
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH0        (0x000)
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH0_M      (0x0000000F)
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH1        (0x004)
+#define BIT_ALARM_MONTHS_REG_ALARM_MONTH1_M      (0x00000010)
+/* ALARM_YEARS_REG Fields */
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR0          (0x000)
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR0_M        (0x0000000F)
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR1          (0x004)
+#define BIT_ALARM_YEARS_REG_ALARM_YEAR1_M        (0x000000F0)
+/* RTC_CTRL_REG Fields */
+#define BIT_RTC_CTRL_REG_STOP_RTC                (0x000)
+#define BIT_RTC_CTRL_REG_STOP_RTC_M              (0x00000001)
+#define BIT_RTC_CTRL_REG_ROUND_30S               (0x001)
+#define BIT_RTC_CTRL_REG_ROUND_30S_M             (0x00000002)
+#define BIT_RTC_CTRL_REG_AUTO_COMP               (0x002)
+#define BIT_RTC_CTRL_REG_AUTO_COMP_M             (0x00000004)
+#define BIT_RTC_CTRL_REG_MODE_12_24              (0x003)
+#define BIT_RTC_CTRL_REG_MODE_12_24_M            (0x00000008)
+#define BIT_RTC_CTRL_REG_TEST_MODE               (0x004)
+#define BIT_RTC_CTRL_REG_TEST_MODE_M             (0x00000010)
+#define BIT_RTC_CTRL_REG_SET_32_COUNTER          (0x005)
+#define BIT_RTC_CTRL_REG_SET_32_COUNTER_M        (0x00000020)
+#define BIT_RTC_CTRL_REG_GET_TIME                (0x006)
+#define BIT_RTC_CTRL_REG_GET_TIME_M              (0x00000040)
+/* RTC_STATUS_REG Fields */
+#define BIT_RTC_STATUS_REG_RUN                   (0x001)
+#define BIT_RTC_STATUS_REG_RUN_M                 (0x00000002)
+#define BIT_RTC_STATUS_REG_1S_EVENT              (0x002)
+#define BIT_RTC_STATUS_REG_1S_EVENT_M            (0x00000004)
+#define BIT_RTC_STATUS_REG_1M_EVENT              (0x003)
+#define BIT_RTC_STATUS_REG_1M_EVENT_M            (0x00000008)
+#define BIT_RTC_STATUS_REG_1H_EVENT              (0x004)
+#define BIT_RTC_STATUS_REG_1H_EVENT_M            (0x00000010)
+#define BIT_RTC_STATUS_REG_1D_EVENT              (0x005)
+#define BIT_RTC_STATUS_REG_1D_EVENT_M            (0x00000020)
+#define BIT_RTC_STATUS_REG_ALARM                 (0x006)
+#define BIT_RTC_STATUS_REG_ALARM_M               (0x00000040)
+#define BIT_RTC_STATUS_REG_POWER_UP              (0x007)
+#define BIT_RTC_STATUS_REG_POWER_UP_M            (0x00000080)
+/* RTC_INTERRUPTS_REG Fields */
+#define BIT_RTC_INTERRUPTS_REG_EVERY             (0x000)
+#define BIT_RTC_INTERRUPTS_REG_EVERY_M           (0x00000003)
+#define BIT_RTC_INTERRUPTS_REG_IT_TIMER          (0x002)
+#define BIT_RTC_INTERRUPTS_REG_IT_TIMER_M        (0x00000004)
+#define BIT_RTC_INTERRUPTS_REG_IT_ALARM          (0x003)
+#define BIT_RTC_INTERRUPTS_REG_IT_ALARM_M        (0x00000008)
+/* RTC_COMP_LSB_REG Fields */
+#define BIT_RTC_COMP_LSB_REG_RTC_COMP_LSB        (0x000)
+#define BIT_RTC_COMP_LSB_REG_RTC_COMP_LSB_M      (0x000000FF)
+/* RTC_COMP_MSB_REG Fields */
+#define BIT_RTC_COMP_MSB_REG_RTC_COMP_MSB        (0x000)
+#define BIT_RTC_COMP_MSB_REG_RTC_COMP_MSB_M      (0x000000FF)
+
+
+struct twl4030rtc_platform_data {
+	int (*init)(void);
+	void (*exit)(void);
+};
+
+#endif				/* End of __TWL4030_RTC_H__ */
-- 
1.6.0.1.141.g445ca


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

* [PATCH 27/33] add tsc210x driver
  2008-08-30 17:16                                                   ` [PATCH 26/33] add TWL4030 RealTime Clock driver Felipe Balbi
@ 2008-08-30 17:16                                                     ` Felipe Balbi
  2008-08-30 17:16                                                       ` [PATCH 28/33] add tsc2301 driver Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/spi/Kconfig   |   19 +
 drivers/spi/Makefile  |    1 +
 drivers/spi/tsc210x.c | 1262 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 1282 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/tsc210x.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index b9d0efb..2a200ff 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -225,6 +225,25 @@ config SPI_AT25
 	  This driver can also be built as a module.  If so, the module
 	  will be called at25.
 
+config SPI_TSC210X
+	depends on SPI_MASTER && EXPERIMENTAL
+	tristate "TI TSC210x (TSC2101/TSC2102) support"
+	help
+	  Say Y here if you want support for the TSC210x chips.  Some
+	  boards use these for touchscreen and audio support.
+
+	  These are members of a family of highly integrated PDA analog
+	  interface circuit.  They include a 12-bit ADC used for battery,
+	  temperature, touchscreen, and other sensors.  They also have
+	  an audio DAC and amplifier, and in some models an audio ADC.
+	  The audio support is highly chip-specific, but most of the
+	  sensor support works the same.
+
+	  Note that the device has to be present in the board's SPI
+	  devices table for this driver to load.  This driver doesn't
+	  automatically enable touchscreen, sensors or audio
+	  functionality - enable these in their respective menus.
+
 config SPI_SPIDEV
 	tristate "User mode SPI device driver support"
 	depends on EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index ccf18de..bf12723 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_SPI_SH_SCI)		+= spi_sh_sci.o
 obj-$(CONFIG_SPI_AT25)		+= at25.o
 obj-$(CONFIG_SPI_SPIDEV)	+= spidev.o
 obj-$(CONFIG_SPI_TLE62X0)	+= tle62x0.o
+obj-$(CONFIG_SPI_TSC210X)	+= tsc210x.o
 # 	... add above this line ...
 
 # SPI slave controller drivers (upstream link)
diff --git a/drivers/spi/tsc210x.c b/drivers/spi/tsc210x.c
new file mode 100644
index 0000000..1d2ac94
--- /dev/null
+++ b/drivers/spi/tsc210x.c
@@ -0,0 +1,1262 @@
+/*
+ * tsc210x.c - TSC2101/2102/... driver core
+ *
+ * Copyright (c) 2005-2007 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package 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.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/suspend.h>
+#include <linux/interrupt.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/completion.h>
+#include <linux/autoconf.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/tsc210x.h>
+
+
+/* NOTE:  It should be straightforward to make this driver framework handle
+ * tsc2100 and tsc2111 chips, and maybe others too.  The main differences
+ * are in the audio codec capabilities, but there are also some differences
+ * in how the various sensors (including touchscreen) are handled.
+ */
+
+/* Bit field definitions for chip registers */
+
+/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_TS_CONTROL		0x8bf4
+/* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_SCAN_CONTROL	0x2ff4
+/* Scan TEMP1, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_T1_CONTROL		0x2bf4
+/* Scan TEMP2, 12-bit, 16 samples, 500 usec */
+#define TSC210X_ADC_T2_CONTROL		0x33f4
+/* PINT/DAV acts as DAV */
+#define TSC210X_ADC_DAV			0x4000
+/* Internal reference, 100 usec delay, 1.25 V reference */
+#define TSC210X_ADC_INT_REF		0x0016
+/* External reference, 100 usec delay, 1.25 V reference */
+#define TSC210X_ADC_EXT_REF		0x0002
+/* 84 usec precharge time, 32 usec sense time */
+#define TSC210X_CONFIG_TIMES		0x0008
+/* The reset sequence */
+#define TSC210X_RESET			0xbb00
+/* Pen Status bit */
+#define TSC210X_ADC_PSTCM		(1 << 15)
+/* A/D Status bit */
+#define TSC210X_ADC_ADST		(1 << 14)
+/* (At least) One of X, Y, Z1, Z2 contains data */
+#define TSC210X_TS_DAV			0x0780
+/* (At least) One of BAT1, BAT2, AUX1, AUX2 contains data */
+#define TSC210X_PS_DAV			0x0078
+/* TEMP1 contains data */
+#define TSC210X_T1_DAV			0x0004
+/* TEMP2 contains data */
+#define TSC210X_T2_DAV			0x0002
+#define TSC2101_DAC_ON			0x0000
+#define TSC2101_DAC_OFF			0xe7fc
+#define TSC2102_DAC_ON			0x3ba0
+#define TSC2102_DAC_OFF			0xafa0
+#define TSC210X_FS44K			(1 << 13)
+#define TSC210X_PLL1_OFF		0x0000
+#define TSC210X_PLL1_44K		0x811c
+#define TSC210X_PLL1_48K		0x8120
+#define TSC210X_PLL2_44K		(5462 << 2)
+#define TSC210X_PLL2_48K		(1920 << 2)
+#define TSC210X_SLVMS			(1 << 11)
+#define TSC210X_DEEMPF			(1 << 0)
+#define TSC2102_BASSBC			(1 << 1)
+#define TSC210X_KEYCLICK_OFF		0x0000
+
+#define CS_CHANGE(val)			0
+
+struct tsc210x_spi_req {
+	struct spi_device *dev;
+	u16 command;
+	u16 data;
+	struct spi_message message;
+};
+
+struct tsc210x_dev {
+	enum tsc_type {
+		tsc2101,
+		tsc2102,
+	} kind;
+	struct tsc210x_config *pdata;
+	struct clk *bclk_ck;
+
+	struct workqueue_struct *queue;
+	struct delayed_work ts_worker;		/* Poll-wait for PEN UP */
+	struct delayed_work sensor_worker;	/* Scan the ADC inputs */
+	struct mutex queue_lock;
+	struct completion data_avail;
+
+	tsc210x_touch_t touch_cb;
+	void *touch_cb_ctx;
+
+	tsc210x_coords_t coords_cb;
+	void *coords_cb_ctx;
+
+	tsc210x_ports_t ports_cb;
+	void *ports_cb_ctx;
+
+	tsc210x_temp_t temp1_cb;
+	void *temp2_cb_ctx;
+
+	tsc210x_temp_t temp2_cb;
+	void *temp1_cb_ctx;
+
+	struct spi_device *spi;
+	struct spi_transfer *transfers;
+	struct tsc210x_spi_req req_adc;
+	struct tsc210x_spi_req req_status;
+	struct tsc210x_spi_req req_mode;
+	struct tsc210x_spi_req req_stop;
+
+	int pendown;
+	int flushing;			/* Queue flush in progress */
+	u16 status;
+	u16 adc_data[4];
+	int bat[2], aux[2], temp[2];
+};
+
+static struct {
+	unsigned int ts_msecs;		/* Interval for .ts_timer */
+	unsigned int mode_msecs;	/* Interval for .mode_timer */
+} settings;
+
+module_param_named(touch_check_msecs, settings.ts_msecs, uint, 0);
+MODULE_PARM_DESC(touch_check_msecs, "Pen-up polling interval in msecs");
+
+module_param_named(sensor_scan_msecs, settings.mode_msecs, uint, 0);
+MODULE_PARM_DESC(sensor_scan_msecs, "Temperature & battery scan interval");
+
+int tsc210x_write_sync(struct tsc210x_dev *dev,
+		int page, u8 address, u16 data)
+{
+	static struct tsc210x_spi_req req;
+	static struct spi_transfer transfer[2];
+	int ret;
+
+	spi_message_init(&req.message);
+
+	/* Address */
+	req.command = (page << 11) | (address << 5);
+	transfer[0].tx_buf = &req.command;
+	transfer[0].rx_buf = NULL;
+	transfer[0].len = 2;
+	spi_message_add_tail(&transfer[0], &req.message);
+
+	/* Data */
+	transfer[1].tx_buf = &data;
+	transfer[1].rx_buf = NULL;
+	transfer[1].len = 2;
+	transfer[1].cs_change = CS_CHANGE(1);
+	spi_message_add_tail(&transfer[1], &req.message);
+
+	ret = spi_sync(dev->spi, &req.message);
+	if (!ret && req.message.status)
+		ret = req.message.status;
+	if (ret)
+		dev_dbg(&dev->spi->dev, "write_sync --> %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(tsc210x_write_sync);
+
+int tsc210x_reads_sync(struct tsc210x_dev *dev,
+		int page, u8 startaddress, u16 *data, int numregs)
+{
+	static struct tsc210x_spi_req req;
+	static struct spi_transfer transfer[6];
+	int ret, i, j;
+
+	if (numregs + 1 > ARRAY_SIZE(transfer))
+		return -EINVAL;
+
+	spi_message_init(&req.message);
+	i = 0;
+	j = 0;
+
+	/* Address */
+	req.command = 0x8000 | (page << 11) | (startaddress << 5);
+	transfer[i].tx_buf = &req.command;
+	transfer[i].rx_buf = NULL;
+	transfer[i].len = 2;
+	spi_message_add_tail(&transfer[i ++], &req.message);
+
+	/* Data */
+	while (j < numregs) {
+		transfer[i].tx_buf = NULL;
+		transfer[i].rx_buf = &data[j ++];
+		transfer[i].len = 2;
+		transfer[i].cs_change = CS_CHANGE(j == numregs);
+		spi_message_add_tail(&transfer[i ++], &req.message);
+	}
+
+	ret = spi_sync(dev->spi, &req.message);
+	if (!ret && req.message.status)
+		ret = req.message.status;
+	if (ret)
+		dev_dbg(&dev->spi->dev, "reads_sync --> %d\n", ret);
+
+	return ret;
+}
+EXPORT_SYMBOL(tsc210x_reads_sync);
+
+int tsc210x_read_sync(struct tsc210x_dev *dev, int page, u8 address)
+{
+	u16 ret;
+	int status;
+
+	status = tsc210x_reads_sync(dev, page, address, &ret, 1);
+	return status ? : ret;
+}
+EXPORT_SYMBOL(tsc210x_read_sync);
+
+
+static void tsc210x_submit_async(struct tsc210x_spi_req *spi)
+{
+	int ret;
+
+	ret = spi_async(spi->dev, &spi->message);
+	if (ret)
+		dev_dbg(&spi->dev->dev, "%s: error %i in SPI request\n",
+				__FUNCTION__, ret);
+}
+
+static void tsc210x_request_alloc(struct tsc210x_dev *dev,
+		struct tsc210x_spi_req *spi, int direction,
+		int page, u8 startaddress, int numregs, u16 *data,
+		void (*complete)(struct tsc210x_dev *context),
+		struct spi_transfer **transfer)
+{
+	spi->dev = dev->spi;
+
+	if (direction == 1)	/* Write */
+		numregs = 2;
+	else			/* Read */
+		numregs += 1;
+
+	spi_message_init(&spi->message);
+	spi->message.complete = (void (*)(void *)) complete;
+	spi->message.context = dev;
+
+	/* Address */
+	spi->command = (page << 11) | (startaddress << 5);
+	if (direction != 1)
+		spi->command |= 1 << 15;
+
+	(*transfer)->tx_buf = &spi->command;
+	(*transfer)->rx_buf = NULL;
+	(*transfer)->len = 2;
+	spi_message_add_tail((*transfer) ++, &spi->message);
+
+	/* Data */
+	while (-- numregs) {
+		if (direction == 1)
+			(*transfer)->tx_buf = &spi->data;
+		else
+			(*transfer)->rx_buf = data++;
+		(*transfer)->len = 2;
+		(*transfer)->cs_change = CS_CHANGE(numregs != 1);
+		spi_message_add_tail((*transfer) ++, &spi->message);
+	}
+}
+
+#define tsc210x_cb_register_func(cb, cb_t)	\
+int tsc210x_ ## cb(struct device *dev, cb_t handler, void *context)	\
+{	\
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);	\
+	\
+	/* Lock the module */	\
+	if (handler && !tsc->cb)	\
+		if (!try_module_get(THIS_MODULE)) {	\
+			dev_err(dev, "Failed to get TSC module\n");	\
+		}	\
+	if (!handler && tsc->cb)	\
+		module_put(THIS_MODULE);	\
+	\
+	tsc->cb = handler;	\
+	tsc->cb ## _ctx = context;	\
+	return 0;	\
+} \
+EXPORT_SYMBOL(tsc210x_ ## cb);
+
+tsc210x_cb_register_func(touch_cb, tsc210x_touch_t)
+tsc210x_cb_register_func(coords_cb, tsc210x_coords_t)
+tsc210x_cb_register_func(ports_cb, tsc210x_ports_t)
+tsc210x_cb_register_func(temp1_cb, tsc210x_temp_t)
+tsc210x_cb_register_func(temp2_cb, tsc210x_temp_t)
+
+#ifdef DEBUG
+static void tsc210x_print_dav(struct tsc210x_dev *dev)
+{
+	int status = tsc210x_read_sync(dev, TSC210X_TS_STATUS_CTRL);
+
+	if (status < 0) {
+		dev_dbg(&dev->spi->dev, "status %d\n", status);
+		return;
+	}
+
+	if (!(status & 0x0fff))
+		return;
+
+	dev_dbg(&dev->spi->dev, "data in %s%s%s%s%s%s%s%s%s%s%s\n",
+		(status & 0x0400) ? " X" : "",
+		(status & 0x0200) ? " Y" : "",
+		(status & 0x0100) ? " Z1" : "",
+		(status & 0x0080) ? " Z2" : "",
+		(status & 0x0040) ? " BAT1" : "",
+		(status & 0x0020) ? " BAT2" : "",
+		(status & 0x0010) ? " AUX1" : "",
+		(status & 0x0008) ? " AUX2" : "",
+		(status & 0x0004) ? " TEMP1" : "",
+		(status & 0x0002) ? " TEMP2" : "",
+		(status & 0x0001) ? " KP" : "");
+}
+#endif
+
+static void tsc210x_complete_dummy(struct tsc210x_dev *dev)
+{
+}
+
+static inline void tsc210x_touchscreen_mode(struct tsc210x_dev *dev)
+{
+	/* Scan X, Y, Z1, Z2, chip controlled, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_TS_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+static inline void tsc210x_portscan_mode(struct tsc210x_dev *dev)
+{
+	/* Scan BAT1, BAT2, AUX1, AUX2, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_SCAN_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+static inline void tsc210x_temp1_mode(struct tsc210x_dev *dev)
+{
+	/* Scan TEMP1, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_T1_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+static inline void tsc210x_temp2_mode(struct tsc210x_dev *dev)
+{
+	/* Scan TEMP2, 12-bit, 16 samples, 500 usec */
+	dev->req_mode.data = TSC210X_ADC_T2_CONTROL;
+	tsc210x_submit_async(&dev->req_mode);
+}
+
+/* Abort current conversion if any */
+static void tsc210x_new_mode(struct tsc210x_dev *dev)
+{
+	dev->req_stop.data = TSC210X_ADC_ADST;
+	tsc210x_submit_async(&dev->req_stop);
+}
+
+static void tsc210x_queue_scan(struct tsc210x_dev *dev)
+{
+	if (dev->pdata->monitor)
+		if (!queue_delayed_work(dev->queue,
+				&dev->sensor_worker,
+				msecs_to_jiffies(settings.mode_msecs)))
+			dev_err(&dev->spi->dev,
+					"%s: can't queue measurements\n",
+					__FUNCTION__);
+}
+
+static void tsc210x_queue_penup(struct tsc210x_dev *dev)
+{
+	if (!queue_delayed_work(dev->queue,
+			&dev->ts_worker,
+			msecs_to_jiffies(settings.ts_msecs)))
+		dev_err(&dev->spi->dev,
+				"%s: can't queue pen-up poll\n",
+				__FUNCTION__);
+}
+
+static void tsc210x_status_report(struct tsc210x_dev *dev)
+{
+	/*
+	 * Read all converted data from corresponding registers
+	 * so that the ADC can move on to a new conversion.
+	 */
+	if (dev->status & TSC210X_TS_DAV) {
+		if (!dev->pendown && !dev->flushing) {
+			dev->pendown = 1;
+			if (dev->touch_cb)
+				dev->touch_cb(dev->touch_cb_ctx, 1);
+
+			tsc210x_queue_penup(dev);
+		}
+
+		tsc210x_submit_async(&dev->req_adc);
+	}
+
+	if (dev->status & (TSC210X_PS_DAV | TSC210X_T1_DAV | TSC210X_T2_DAV))
+		complete(&dev->data_avail);
+}
+
+static void tsc210x_data_report(struct tsc210x_dev *dev)
+{
+	u16 adc_data[4];
+
+	if (dev->status & TSC210X_PS_DAV) {
+		tsc210x_reads_sync(dev, TSC210X_TS_BAT1, adc_data, 4);
+		/* NOTE: reads_sync() could fail */
+
+		dev->bat[0] = adc_data[0];
+		dev->bat[1] = adc_data[1];
+		dev->aux[0] = adc_data[2];
+		dev->aux[1] = adc_data[3];
+		if (dev->ports_cb)
+			dev->ports_cb(dev->ports_cb_ctx, dev->bat, dev->aux);
+	}
+
+	if (dev->status & TSC210X_T1_DAV) {
+		dev->temp[0] = tsc210x_read_sync(dev, TSC210X_TS_TEMP1);
+
+		if (dev->temp[0] >= 0 && dev->temp1_cb)
+			dev->temp1_cb(dev->temp1_cb_ctx, dev->temp[0]);
+	}
+
+	if (dev->status & TSC210X_T2_DAV) {
+		dev->temp[1] = tsc210x_read_sync(dev, TSC210X_TS_TEMP2);
+
+		if (dev->temp[1] >= 0 && dev->temp2_cb)
+			dev->temp2_cb(dev->temp2_cb_ctx, dev->temp[1]);
+	}
+}
+
+static void tsc210x_coords_report(struct tsc210x_dev *dev)
+{
+	if (dev->coords_cb)
+		dev->coords_cb(dev->coords_cb_ctx,
+				dev->adc_data[0], dev->adc_data[1],
+				dev->adc_data[2], dev->adc_data[3]);
+}
+
+/*
+ * There are at least three ways to check for pen-up:
+ *	- the PINT/DAV pin state,
+ *	- reading PSTCM bit in ADC Control register (D15, offset 0x00),
+ *	- reading ADST bit in ADC Control register (D14, offset 0x00),
+ *		ADC idle would indicate no screen touch.
+ * Unfortunately none of them seems to be 100% accurate and you will
+ * find they are totally inconsistent, i.e. you get to see any arbitrary
+ * combination of values in these three bits.  So we will busy-wait
+ * for a moment when the latter two indicate a pen-up, using a queue,
+ * before we report a pen-up.
+ */
+static void tsc210x_pressure(struct work_struct *work)
+{
+	struct tsc210x_dev *dev =
+		container_of(work, struct tsc210x_dev, ts_worker.work);
+	int adc_status;
+
+	WARN_ON(!dev->pendown);
+
+	adc_status = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL);
+	if (adc_status < 0) {
+		dev_dbg(&dev->spi->dev, "pressure, err %d\n", adc_status);
+		return;
+	}
+
+	if ((adc_status & TSC210X_ADC_PSTCM) != 0
+			|| !(adc_status & TSC210X_ADC_ADST))
+		tsc210x_queue_penup(dev);
+	else {
+		dev->pendown = 0;
+		if (dev->touch_cb)
+			dev->touch_cb(dev->touch_cb_ctx, 0);
+	}
+}
+
+static void tsc210x_wait_data(struct tsc210x_dev *dev)
+{
+	wait_for_completion(&dev->data_avail);
+
+	tsc210x_data_report(dev);
+}
+
+static void tsc210x_input_scan(struct work_struct *work)
+{
+	struct tsc210x_dev *dev = (struct tsc210x_dev *)
+		container_of(work, struct tsc210x_dev, sensor_worker.work);
+
+	tsc210x_new_mode(dev);
+
+	if (dev->pdata->monitor &
+			(TSC_BAT1 | TSC_BAT2 | TSC_AUX1 | TSC_AUX2)) {
+		tsc210x_portscan_mode(dev);
+		tsc210x_wait_data(dev);
+	}
+
+	if (dev->pdata->monitor & TSC_TEMP) {
+		tsc210x_temp1_mode(dev);
+		tsc210x_wait_data(dev);
+
+		tsc210x_temp2_mode(dev);
+		tsc210x_wait_data(dev);
+	}
+
+	tsc210x_touchscreen_mode(dev);
+
+	mutex_lock(&dev->queue_lock);
+	if (!dev->flushing)
+		tsc210x_queue_scan(dev);
+	mutex_unlock(&dev->queue_lock);
+}
+
+/* ADC has finished a new conversion for us.  */
+static irqreturn_t tsc210x_handler(int irq, void *dev_id)
+{
+	struct tsc210x_dev *dev = (struct tsc210x_dev *) dev_id;
+
+	/* See what data became available.  */
+	tsc210x_submit_async(&dev->req_status);
+
+	return IRQ_HANDLED;
+}
+
+#if defined(CONFIG_SOUND) || defined(CONFIG_SOUND_MODULE)
+
+/*
+ * FIXME the audio support shouldn't be included in upstream patches
+ * until it's ready.  They might be better as utility functions linked
+ * with a chip-specific tsc21xx audio module ... e.g. chips with input
+ * channels need more, as will ones with multiple output channels and
+ * so on.  Each of these functions should probably return a fault code,
+ * and will need to be exported so the sound drier can be modular.
+ */
+
+/*
+ * Volume level values should be in the range [0, 127].
+ * Higher values mean lower volume.
+ */
+void tsc210x_set_dac_volume(struct device *dev, u8 left_ch, u8 right_ch)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
+	if (tsc->kind == tsc2102) {
+		/* All 0's or all 1's */
+		if (left_ch == 0x00 || left_ch == 0x7f)
+			left_ch ^= 0x7f;
+		if (right_ch == 0x00 || right_ch == 0x7f)
+			right_ch ^= 0x7f;
+	}
+
+	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
+
+	val &= 0x8080;	/* Preserve mute-bits */
+	val |= (left_ch << 8) | right_ch;
+
+	tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
+	/* NOTE: write_sync() could fail */
+}
+
+void tsc210x_set_dac_mute(struct device *dev, int left_ch, int right_ch)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
+
+	val &= 0x7f7f;	/* Preserve volume settings */
+	val |= (left_ch << 15) | (right_ch << 7);
+
+	tsc210x_write_sync(tsc, TSC210X_DAC_GAIN_CTRL, val);
+	/* NOTE: write_sync() could fail */
+}
+
+void tsc210x_get_dac_mute(struct device *dev, int *left_ch, int *right_ch)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_DAC_GAIN_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
+
+	*left_ch = !!(val & (1 << 15));
+	*right_ch = !!(val & (1 << 7));
+}
+
+void tsc210x_set_deemphasis(struct device *dev, int enable)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
+
+	if (enable)
+		val &= ~TSC210X_DEEMPF;
+	else
+		val |= TSC210X_DEEMPF;
+
+	tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
+	/* NOTE: write_sync() could fail */
+}
+
+void tsc2102_set_bassboost(struct device *dev, int enable)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_POWER_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
+
+	if (enable)
+		val &= ~TSC2102_BASSBC;
+	else
+		val |= TSC2102_BASSBC;
+
+	tsc210x_write_sync(tsc, TSC210X_POWER_CTRL, val);
+	/* NOTE: write_sync() could fail */
+}
+
+/*	{rate, dsor, fsref}	*/
+static const struct tsc210x_rate_info_s tsc2101_rates[] = {
+	/* Fsref / 6.0 */
+	{7350,	7,	1},
+	{8000,	7,	0},
+	/* Fsref / 5.5 */
+	{8018,	6,	1},
+	{8727,	6,	0},
+	/* Fsref / 5.0 */
+	{8820,	5,	1},
+	{9600,	5,	0},
+	/* Fsref / 4.0 */
+	{11025,	4,	1},
+	{12000,	4,	0},
+	/* Fsref / 3.0 */
+	{14700,	3,	1},
+	{16000,	3,	0},
+	/* Fsref / 2.0 */
+	{22050,	2,	1},
+	{24000,	2,	0},
+	/* Fsref / 1.5 */
+	{29400,	1,	1},
+	{32000,	1,	0},
+	/* Fsref */
+	{44100,	0,	1},
+	{48000,	0,	0},
+
+	{0,	0,	0},
+};
+
+/*	{rate, dsor, fsref}	*/
+static const struct tsc210x_rate_info_s tsc2102_rates[] = {
+	/* Fsref / 6.0 */
+	{7350,	63,	1},
+	{8000,	63,	0},
+	/* Fsref / 6.0 */
+	{7350,	54,	1},
+	{8000,	54,	0},
+	/* Fsref / 5.0 */
+	{8820,	45,	1},
+	{9600,	45,	0},
+	/* Fsref / 4.0 */
+	{11025,	36,	1},
+	{12000,	36,	0},
+	/* Fsref / 3.0 */
+	{14700,	27,	1},
+	{16000,	27,	0},
+	/* Fsref / 2.0 */
+	{22050,	18,	1},
+	{24000,	18,	0},
+	/* Fsref / 1.5 */
+	{29400,	9,	1},
+	{32000,	9,	0},
+	/* Fsref */
+	{44100,	0,	1},
+	{48000,	0,	0},
+
+	{0,	0,	0},
+};
+
+int tsc210x_set_rate(struct device *dev, int rate)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int i;
+	int val;
+	const struct tsc210x_rate_info_s *rates;
+
+	if (tsc->kind == tsc2101)
+		rates = tsc2101_rates;
+	else
+		rates = tsc2102_rates;
+
+	for (i = 0; rates[i].sample_rate; i ++)
+		if (rates[i].sample_rate == rate)
+			break;
+	if (rates[i].sample_rate == 0) {
+		dev_err(dev, "Unknown sampling rate %i.0 Hz\n", rate);
+		return -EINVAL;
+	}
+
+	if (tsc->kind == tsc2101) {
+		val = tsc210x_read_sync(tsc, TSC210X_AUDIO1_CTRL);
+		if (val < 0) {
+			dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+			return val;
+		}
+		val &= ~((7 << 3) | (7 << 0));
+		val |= rates[i].divisor << 3;
+		val |= rates[i].divisor << 0;
+	} else
+		val = rates[i].divisor;
+
+	tsc210x_write_sync(tsc, TSC210X_AUDIO1_CTRL, val);
+	/* NOTE: write_sync() could fail */
+
+	val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return val;
+	}
+
+	if (tsc2102_rates[i].fs_44k) {
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO3_CTRL, val | TSC210X_FS44K);
+		/* Enable Phase-locked-loop, set up clock dividers */
+		tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_44K);
+		tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_44K);
+	} else {
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO3_CTRL, val & ~TSC210X_FS44K);
+		/* Enable Phase-locked-loop, set up clock dividers */
+		tsc210x_write_sync(tsc, TSC210X_PLL1_CTRL, TSC210X_PLL1_48K);
+		tsc210x_write_sync(tsc, TSC210X_PLL2_CTRL, TSC210X_PLL2_48K);
+	}
+
+	return 0;
+}
+
+/*
+ * Perform basic set-up with default values and power the DAC/ADC on.
+ */
+void tsc210x_dac_power(struct device *dev, int on)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+
+	/* NOTE: write_sync() could fail */
+	if (on) {
+		/* 16-bit words, DSP mode, sample at Fsref */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO1_CTRL, 0x0100);
+		/* Keyclicks off, soft-stepping at normal rate */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF);
+		/* 44.1 kHz Fsref, continuous transfer mode, master DAC */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO3_CTRL, 0x2000);
+		/* Soft-stepping enabled, 1 dB MIX AGC hysteresis */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO4_CTRL, 0x0000);
+
+		/* PLL generates 44.1 kHz */
+		tsc210x_write_sync(tsc,
+				TSC210X_PLL1_CTRL, TSC210X_PLL1_44K);
+		tsc210x_write_sync(tsc,
+				TSC210X_PLL2_CTRL, TSC210X_PLL2_44K);
+
+		/* Codec & DAC power up, virtual ground disabled */
+		tsc210x_write_sync(tsc,
+				TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ?
+				TSC2101_DAC_ON : TSC2102_DAC_ON);
+	} else {
+		/* All off */
+		tsc210x_write_sync(tsc,
+				TSC210X_AUDIO2_CTRL, TSC210X_KEYCLICK_OFF);
+		tsc210x_write_sync(tsc,
+				TSC210X_PLL1_CTRL, TSC210X_PLL1_OFF);
+#if 0
+		tsc210x_write_sync(tsc,
+				TSC210X_POWER_CTRL, (tsc->kind == tsc2101) ?
+				TSC2102_DAC_OFF : TSC2102_DAC_OFF);
+#endif
+	}
+}
+
+void tsc210x_set_i2s_master(struct device *dev, int state)
+{
+	struct tsc210x_dev *tsc = dev_get_drvdata(dev);
+	int val;
+
+	val = tsc210x_read_sync(tsc, TSC210X_AUDIO3_CTRL);
+	if (val < 0) {
+		dev_dbg(dev, "%s, err %d\n", __FUNCTION__, val);
+		return;
+	}
+
+	/* NOTE: write_sync() could fail */
+	if (state)
+		tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL,
+				val | TSC210X_SLVMS);
+	else
+		tsc210x_write_sync(tsc, TSC210X_AUDIO3_CTRL,
+				val & ~TSC210X_SLVMS);
+}
+#endif	/* CONFIG_SOUND */
+
+static int tsc210x_configure(struct tsc210x_dev *dev)
+{
+	/* NOTE: write_sync() could fail */
+
+	/* Reset the chip */
+	tsc210x_write_sync(dev, TSC210X_TS_RESET_CTRL, TSC210X_RESET);
+
+	/* Reference mode */
+	if (dev->pdata->use_internal)
+		tsc210x_write_sync(dev,
+				TSC210X_TS_REF_CTRL, TSC210X_ADC_INT_REF);
+	else
+		tsc210x_write_sync(dev,
+				TSC210X_TS_REF_CTRL, TSC210X_ADC_EXT_REF);
+
+	/* Precharge and sense delays, pen touch detection on */
+	tsc210x_write_sync(dev, TSC210X_TS_CONFIG_CTRL, TSC210X_CONFIG_TIMES);
+
+	/* PINT/DAV acts as DAV */
+	tsc210x_write_sync(dev, TSC210X_TS_STATUS_CTRL, TSC210X_ADC_DAV);
+
+	tsc210x_queue_scan(dev);
+	return 0;
+}
+
+void tsc210x_keyclick(struct tsc210x_dev *dev,
+		int amplitude, int freq, int length)
+{
+	int val;
+
+	val = tsc210x_read_sync(dev, TSC210X_AUDIO2_CTRL);
+	if (val < 0) {
+		dev_dbg(&dev->spi->dev, "%s, err %d\n",
+				__FUNCTION__, val);
+		return;
+	}
+	val &= 0x800f;
+
+	/* Set amplitude */
+	switch (amplitude) {
+	case 1:
+		val |= 4 << 12;
+		break;
+	case 2:
+		val |= 7 << 12;
+		break;
+	default:
+		break;
+	}
+
+	/* Frequency */
+	val |= (freq & 0x7) << 8;
+
+	/* Round to nearest supported length */
+	if (dev->kind == tsc2101)
+		val = (min(length - 1, 31) >> 1) << 4;
+	else {
+		if (length > 20)
+			val |= 4 << 4;
+		else if (length > 6)
+			val |= 3 << 4;
+		else if (length > 4)
+			val |= 2 << 4;
+		else if (length > 2)
+			val |= 1 << 4;
+	}
+
+	/* Enable keyclick */
+	val |= 0x8000;
+
+	/* NOTE: write_sync() could fail */
+	tsc210x_write_sync(dev, TSC210X_AUDIO2_CTRL, val);
+}
+EXPORT_SYMBOL(tsc210x_keyclick);
+
+#ifdef CONFIG_PM
+/*
+ * Suspend the chip.
+ */
+static int
+tsc210x_suspend(struct spi_device *spi, pm_message_t state)
+{
+	struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
+
+	if (!dev)
+		return -ENODEV;
+
+	/* Stop the inputs scan loop */
+	mutex_lock(&dev->queue_lock);
+	dev->flushing = 1;
+	cancel_delayed_work(&dev->sensor_worker);
+	mutex_unlock(&dev->queue_lock);
+	flush_workqueue(dev->queue);
+
+	/* Wait until pen-up happens */
+	while (dev->pendown)
+		flush_workqueue(dev->queue);
+
+	/* Abort current conversion and power down the ADC */
+	tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
+	/* NOTE: write_sync() could fail */
+
+	return 0;
+}
+
+/*
+ * Resume chip operation.
+ */
+static int tsc210x_resume(struct spi_device *spi)
+{
+	struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
+	int err;
+
+	if (!dev)
+		return 0;
+
+	mutex_lock(&dev->queue_lock);
+	err = tsc210x_configure(dev);
+
+	dev->flushing = 0;
+	mutex_unlock(&dev->queue_lock);
+
+	return err;
+}
+#else
+#define tsc210x_suspend	NULL
+#define tsc210x_resume	NULL
+#endif
+
+/* REVISIT don't make these static */
+static struct platform_device tsc210x_ts_device = {
+	.name		= "tsc210x-ts",
+	.id		= -1,
+};
+
+static struct platform_device tsc210x_hwmon_device = {
+	.name		= "tsc210x-hwmon",
+	.id		= -1,
+};
+
+static struct platform_device tsc210x_alsa_device = {
+	.name		= "tsc210x-alsa",
+	.id		= -1,
+};
+
+static int tsc210x_probe(struct spi_device *spi, enum tsc_type type)
+{
+	struct tsc210x_config *pdata = spi->dev.platform_data;
+	struct spi_transfer *spi_buffer;
+	struct tsc210x_dev *dev;
+	int reg;
+	int err = 0;
+
+	if (!pdata) {
+		dev_dbg(&spi->dev, "Platform data not supplied\n");
+		return -ENOENT;
+	}
+
+	if (!spi->irq) {
+		dev_dbg(&spi->dev, "Invalid irq value\n");
+		return -EINVAL;
+	}
+
+	dev = (struct tsc210x_dev *)
+		kzalloc(sizeof(struct tsc210x_dev), GFP_KERNEL);
+	if (!dev) {
+		dev_dbg(&spi->dev, "No memory\n");
+		return -ENOMEM;
+	}
+
+	dev->pdata = pdata;
+	dev->pendown = 0;
+	dev->spi = spi;
+	dev->kind = type;
+	dev->queue = create_singlethread_workqueue(spi->dev.driver->name);
+	if (!dev->queue) {
+		dev_dbg(&spi->dev, "Can't make a workqueue\n");
+		err = -ENOMEM;
+		goto err_queue;
+	}
+
+	mutex_init(&dev->queue_lock);
+	init_completion(&dev->data_avail);
+
+	/* Allocate enough struct spi_transfer's for all requests */
+	spi_buffer = kzalloc(sizeof(struct spi_transfer) * 16, GFP_KERNEL);
+	if (!spi_buffer) {
+		dev_dbg(&spi->dev, "No memory for SPI buffers\n");
+		err = -ENOMEM;
+		goto err_buffers;
+	}
+
+	dev->transfers = spi_buffer;
+	tsc210x_request_alloc(dev, &dev->req_adc, 0,
+			TSC210X_TS_X, 4, dev->adc_data,
+			tsc210x_coords_report, &spi_buffer);
+	tsc210x_request_alloc(dev, &dev->req_status, 0,
+			TSC210X_TS_STATUS_CTRL, 1, &dev->status,
+			tsc210x_status_report, &spi_buffer);
+	tsc210x_request_alloc(dev, &dev->req_mode, 1,
+			TSC210X_TS_ADC_CTRL, 1, NULL,
+			tsc210x_complete_dummy, &spi_buffer);
+	tsc210x_request_alloc(dev, &dev->req_stop, 1,
+			TSC210X_TS_ADC_CTRL, 1, NULL,
+			tsc210x_complete_dummy, &spi_buffer);
+
+	if (pdata->bclk) {
+		/* Get the BCLK */
+		dev->bclk_ck = clk_get(&spi->dev, pdata->bclk);
+		if (IS_ERR(dev->bclk_ck)) {
+			err = PTR_ERR(dev->bclk_ck);
+			dev_dbg(&spi->dev, "Unable to get '%s': %i\n",
+					pdata->bclk, err);
+			goto err_clk;
+		}
+
+		clk_enable(dev->bclk_ck);
+	}
+
+	INIT_DELAYED_WORK(&dev->ts_worker, tsc210x_pressure);
+	INIT_DELAYED_WORK(&dev->sensor_worker, tsc210x_input_scan);
+
+	/* Setup the communication bus */
+	dev_set_drvdata(&spi->dev, dev);
+	spi->mode = SPI_MODE_1;
+	spi->bits_per_word = 16;
+	err = spi_setup(spi);
+	if (err)
+		goto err_spi;
+
+	/* Now try to detect the chip, make first contact.  These chips
+	 * don't self-identify, but we can expect that the status register
+	 * reports the ADC is idle and use that as a sanity check.  (It'd
+	 * be even better if we did a soft reset first...)
+	 */
+	reg = tsc210x_read_sync(dev, TSC210X_TS_ADC_CTRL);
+	if (reg < 0) {
+		err = reg;
+		dev_dbg(&dev->spi->dev, "adc_ctrl, err %d\n", err);
+		goto err_spi;
+	}
+	if (!(reg & (1 << 14))) {
+		err = -EIO;
+		dev_dbg(&dev->spi->dev, "adc_ctrl, busy? - %04x\n", reg);
+		goto err_spi;
+	}
+
+	reg = tsc210x_read_sync(dev, TSC210X_AUDIO3_CTRL);
+	if (reg < 0) {
+		err = reg;
+		dev_dbg(&dev->spi->dev, "revision, err %d\n", err);
+		goto err_spi;
+	}
+	if (reg == 0xffff) {
+		err = -ENODEV;
+		dev_dbg(&dev->spi->dev, "no device, err %d\n", err);
+		goto err_spi;
+	}
+	dev_info(&spi->dev, "rev %d, irq %d\n", reg & 0x0007, spi->irq);
+
+	err = tsc210x_configure(dev);
+	if (err)
+		goto err_spi;
+
+	/* We want no interrupts before configuration succeeds.  */
+	mutex_lock(&dev->queue_lock);
+	dev->flushing = 1;
+
+	if (request_irq(spi->irq, tsc210x_handler, IRQF_SAMPLE_RANDOM |
+				IRQF_TRIGGER_FALLING, spi->dev.driver->name,
+				dev)) {
+		dev_dbg(&spi->dev, "Could not allocate touchscreen IRQ!\n");
+		err = -EINVAL;
+		goto err_irq;
+	}
+
+	/* Register subdevices controlled by the TSC 2101/2102 */
+	tsc210x_ts_device.dev.platform_data = dev;
+	tsc210x_ts_device.dev.parent = &spi->dev;
+	err = platform_device_register(&tsc210x_ts_device);
+	if (err)
+		goto err_irq;
+
+	tsc210x_hwmon_device.dev.platform_data = pdata;
+	tsc210x_hwmon_device.dev.parent = &spi->dev;
+	err = platform_device_register(&tsc210x_hwmon_device);
+	if (err)
+		goto err_hwmon;
+
+	tsc210x_alsa_device.dev.platform_data = pdata->alsa_config;
+	tsc210x_alsa_device.dev.parent = &spi->dev;
+	err = platform_device_register(&tsc210x_alsa_device);
+	if (err)
+		goto err_alsa;
+
+	dev->flushing = 0;
+	mutex_unlock(&dev->queue_lock);
+	return 0;
+
+err_alsa:
+	platform_device_unregister(&tsc210x_hwmon_device);
+err_hwmon:
+	platform_device_unregister(&tsc210x_ts_device);
+err_irq:
+	mutex_unlock(&dev->queue_lock);
+err_spi:
+	dev_set_drvdata(&spi->dev, NULL);
+	clk_disable(dev->bclk_ck);
+	clk_put(dev->bclk_ck);
+err_clk:
+	kfree(dev->transfers);
+err_buffers:
+	destroy_workqueue(dev->queue);
+err_queue:
+	kfree(dev);
+	return err;
+}
+
+static int tsc2101_probe(struct spi_device *spi)
+{
+	return tsc210x_probe(spi, tsc2101);
+}
+
+static int tsc2102_probe(struct spi_device *spi)
+{
+	return tsc210x_probe(spi, tsc2102);
+}
+
+static int tsc210x_remove(struct spi_device *spi)
+{
+	struct tsc210x_dev *dev = dev_get_drvdata(&spi->dev);
+
+	/* Stop the inputs scan loop */
+	mutex_lock(&dev->queue_lock);
+	dev->flushing = 1;
+	cancel_delayed_work(&dev->sensor_worker);
+	mutex_unlock(&dev->queue_lock);
+	flush_workqueue(dev->queue);
+
+	/* Wait for pen-up */
+	while (dev->pendown)
+		flush_workqueue(dev->queue);
+
+	/* Abort current conversion and power down the ADC */
+	tsc210x_write_sync(dev, TSC210X_TS_ADC_CTRL, TSC210X_ADC_ADST);
+	/* NOTE: write_sync() could fail */
+
+	destroy_workqueue(dev->queue);
+
+	platform_device_unregister(&tsc210x_ts_device);
+	platform_device_unregister(&tsc210x_hwmon_device);
+	platform_device_unregister(&tsc210x_alsa_device);
+
+	dev_set_drvdata(&spi->dev, NULL);
+
+	/* Release the BCLK */
+	clk_disable(dev->bclk_ck);
+	clk_put(dev->bclk_ck);
+
+	kfree(dev->transfers);
+	kfree(dev);
+
+	return 0;
+}
+
+static struct spi_driver tsc2101_driver = {
+	.probe		= tsc2101_probe,
+	.remove		= tsc210x_remove,
+	.suspend	= tsc210x_suspend,
+	.resume		= tsc210x_resume,
+	.driver		= {
+		.name	= "tsc2101",
+		.owner	= THIS_MODULE,
+		.bus	= &spi_bus_type,
+	},
+};
+
+static struct spi_driver tsc2102_driver = {
+	.probe		= tsc2102_probe,
+	.remove		= tsc210x_remove,
+	.suspend	= tsc210x_suspend,
+	.resume		= tsc210x_resume,
+	.driver		= {
+		.name	= "tsc2102",
+		.owner	= THIS_MODULE,
+		.bus	= &spi_bus_type,
+	},
+};
+
+static char __initdata banner[] = KERN_INFO "TI TSC210x driver initializing\n";
+
+static int __init tsc210x_init(void)
+{
+	int err;
+	printk(banner);
+
+	settings.ts_msecs = 20;
+	settings.mode_msecs = 1000;
+
+	err = spi_register_driver(&tsc2101_driver);
+	if (err != 0)
+		return err;
+
+	err = spi_register_driver(&tsc2102_driver);
+	if (err != 0)
+		spi_unregister_driver(&tsc2101_driver);
+
+	return err;
+}
+module_init(tsc210x_init);
+
+static void __exit tsc210x_exit(void)
+{
+	spi_unregister_driver(&tsc2101_driver);
+	spi_unregister_driver(&tsc2102_driver);
+}
+module_exit(tsc210x_exit);
+
+MODULE_AUTHOR("Andrzej Zaborowski");
+MODULE_DESCRIPTION("Interface driver for TI TSC210x chips.");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 28/33] add tsc2301 driver
  2008-08-30 17:16                                                     ` [PATCH 27/33] add tsc210x driver Felipe Balbi
@ 2008-08-30 17:16                                                       ` Felipe Balbi
  2008-08-30 17:16                                                         ` [PATCH 29/33] add omap ehci bus glue Felipe Balbi
  2008-09-01  7:35                                                         ` [PATCH 28/33] add tsc2301 driver Jarkko Nikula
  0 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/spi/Kconfig         |   22 +
 drivers/spi/Makefile        |    3 +
 drivers/spi/tsc2301-core.c  |  301 +++++++++++++
 drivers/spi/tsc2301-mixer.c | 1004 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1330 insertions(+), 0 deletions(-)
 create mode 100644 drivers/spi/tsc2301-core.c
 create mode 100644 drivers/spi/tsc2301-mixer.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index 2a200ff..c891c1e 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -244,6 +244,28 @@ config SPI_TSC210X
 	  automatically enable touchscreen, sensors or audio
 	  functionality - enable these in their respective menus.
 
+config SPI_TSC2301
+	tristate "TSC2301 driver"
+	depends on SPI_MASTER
+	help
+	  Say Y here if you have a TSC2301 chip connected to an SPI
+	  bus on your board.
+
+	  The TSC2301 is a highly integrated PDA analog interface circuit.
+	  It contains a complete 12-bit A/D resistive touch screen
+	  converter (ADC) including drivers, touch pressure measurement
+	  capability, keypad controller, and 8-bit D/A converter (DAC) output
+	  for LCD contrast control.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tsc2301.
+
+config SPI_TSC2301_AUDIO
+	boolean "TSC2301 audio support"
+	depends on SPI_TSC2301 && SND
+	help
+	  Say Y here for if you are using the audio features of TSC2301.
+
 config SPI_SPIDEV
 	tristate "User mode SPI device driver support"
 	depends on EXPERIMENTAL
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index bf12723..f3841c8 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -36,6 +36,9 @@ obj-$(CONFIG_SPI_AT25)		+= at25.o
 obj-$(CONFIG_SPI_SPIDEV)	+= spidev.o
 obj-$(CONFIG_SPI_TLE62X0)	+= tle62x0.o
 obj-$(CONFIG_SPI_TSC210X)	+= tsc210x.o
+obj-$(CONFIG_SPI_TSC2301)	+= tsc2301.o
+tsc2301-objs			:= tsc2301-core.o
+tsc2301-$(CONFIG_SPI_TSC2301_AUDIO)	+= tsc2301-mixer.o
 # 	... add above this line ...
 
 # SPI slave controller drivers (upstream link)
diff --git a/drivers/spi/tsc2301-core.c b/drivers/spi/tsc2301-core.c
new file mode 100644
index 0000000..939bc48
--- /dev/null
+++ b/drivers/spi/tsc2301-core.c
@@ -0,0 +1,301 @@
+/*
+ * TSC2301 driver
+ *
+ * Copyright (C) 2005, 2006 Nokia Corporation
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/tsc2301.h>
+
+#ifdef CONFIG_ARCH_OMAP
+#include <mach/gpio.h>
+#endif
+
+u16 tsc2301_read_reg(struct tsc2301 *tsc, int reg)
+{
+	struct spi_transfer t[2];
+	struct spi_message m;
+	u16 data = 0, cmd;
+
+	cmd = reg;
+	cmd |= 0x8000;
+
+	memset(t, 0, sizeof(t));
+	spi_message_init(&m);
+	m.spi = tsc->spi;
+
+	t[0].tx_buf = &cmd;
+	t[0].rx_buf = NULL;
+	t[0].len = 2;
+	spi_message_add_tail(&t[0], &m);
+
+	t[1].tx_buf = NULL;
+	t[1].rx_buf = &data;
+	t[1].len = 2;
+	spi_message_add_tail(&t[1], &m);
+
+	spi_sync(m.spi, &m);
+
+	return data;
+}
+
+void tsc2301_write_reg(struct tsc2301 *tsc, int reg, u16 val)
+{
+	struct spi_transfer t;
+	struct spi_message m;
+	u16 data[2];
+
+	/* Now we prepare the command for transferring */
+	data[0] = reg;
+	data[1] = val;
+
+	spi_message_init(&m);
+	m.spi = tsc->spi;
+
+	memset(&t, 0, sizeof(t));
+	t.tx_buf = data;
+	t.rx_buf = NULL;
+	t.len = 4;
+	spi_message_add_tail(&t, &m);
+
+	spi_sync(m.spi, &m);
+}
+
+void tsc2301_write_kbc(struct tsc2301 *tsc, int val)
+{
+	u16 w;
+
+	w = tsc->config2_shadow;
+	w &= ~(0x03 << 14);
+	w |= (val & 0x03) << 14;
+	tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w);
+	tsc->config2_shadow = w;
+}
+
+void tsc2301_write_pll(struct tsc2301 *tsc,
+		       int pll_n, int pll_a, int pll_pdc, int pct_e, int pll_o)
+{
+	u16 w;
+
+	w = tsc->config2_shadow;
+	w &= ~0x3fff;
+	w |= (pll_n & 0x0f) | ((pll_a & 0x0f) << 4) | ((pll_pdc & 0x0f) << 8);
+	w |= pct_e ? (1 << 12) : 0;
+	w |= pll_o ? (1 << 13) : 0;
+	tsc2301_write_reg(tsc, TSC2301_REG_CONFIG2, w);
+	tsc->config2_shadow = w;
+}
+
+void tsc2301_read_buf(struct tsc2301 *tsc, int reg, u16 *rx_buf, int len)
+{
+	struct spi_transfer t[2];
+	struct spi_message m;
+	u16 cmd, i;
+
+	cmd = reg;
+	cmd |= 0x8000;
+
+	spi_message_init(&m);
+	m.spi = tsc->spi;
+
+	memset(t, 0, sizeof(t));
+	t[0].tx_buf = &cmd;
+	t[0].rx_buf = NULL;
+	t[0].len = 2;
+	spi_message_add_tail(&t[0], &m);
+
+	t[1].tx_buf = NULL;
+	t[1].rx_buf = rx_buf;
+	t[1].len = 2 * len;
+	spi_message_add_tail(&t[1], &m);
+
+	spi_sync(m.spi, &m);
+
+	for (i = 0; i < len; i++)
+		printk(KERN_DEBUG "rx_buf[%d]: %04x\n", i, rx_buf[i]);
+}
+
+static int __devinit tsc2301_probe(struct spi_device *spi)
+{
+	struct tsc2301			*tsc;
+	struct tsc2301_platform_data	*pdata = spi->dev.platform_data;
+	int r;
+	u16 w;
+
+	if (!pdata) {
+		dev_dbg(&spi->dev, "no platform data?\n");
+		return -ENODEV;
+	}
+
+	tsc = kzalloc(sizeof(*tsc), GFP_KERNEL);
+	if (tsc == NULL)
+		return -ENOMEM;
+
+	dev_set_drvdata(&spi->dev, tsc);
+	tsc->spi = spi;
+
+	tsc->enable_clock = pdata->enable_clock;
+	tsc->disable_clock = pdata->disable_clock;
+
+	if (pdata->reset_gpio >= 0) {
+		tsc->reset_gpio = pdata->reset_gpio;
+#ifdef CONFIG_ARCH_OMAP
+		r = omap_request_gpio(tsc->reset_gpio);
+		if (r < 0)
+			goto err1;
+		omap_set_gpio_dataout(tsc->reset_gpio, 1);
+		omap_set_gpio_direction(tsc->reset_gpio, 0);
+		mdelay(1);
+		omap_set_gpio_dataout(tsc->reset_gpio, 0);
+#endif
+	} else
+		tsc->reset_gpio = -1;
+
+	spi->mode = SPI_MODE_1;
+	spi->bits_per_word = 16;
+	/* The max speed might've been defined by the board-specific
+	 * struct */
+	if (!spi->max_speed_hz)
+		spi->max_speed_hz = TSC2301_HZ;
+	spi_setup(spi);
+
+	/* Soft reset */
+	tsc2301_write_reg(tsc, TSC2301_REG_RESET, 0xbb00);
+	msleep(1);
+
+	w = tsc2301_read_reg(tsc, TSC2301_REG_ADC);
+	if (!(w & (1 << 14))) {
+		dev_err(&spi->dev, "invalid ADC reg value: %04x\n", w);
+		r = -ENODEV;
+		goto err1;
+	}
+
+	w = tsc2301_read_reg(tsc, TSC2301_REG_DAC);
+	if (!(w & (1 << 15))) {
+		dev_err(&spi->dev, "invalid DAC reg value: %04x\n", w);
+		r = -ENODEV;
+		goto err1;
+	}
+
+	/* Stop keypad scanning */
+	tsc2301_write_reg(tsc, TSC2301_REG_KEY, 0x4000);
+
+	/* We have to cache this for read-modify-write, since we can't
+	 * read back BIT15 */
+	w = tsc2301_read_reg(tsc, TSC2301_REG_CONFIG2);
+	/* By default BIT15 is set */
+	w |= 1 << 15;
+	tsc->config2_shadow = w;
+
+	r = tsc2301_kp_init(tsc, pdata);
+	if (r)
+		goto err1;
+	r = tsc2301_ts_init(tsc, pdata);
+	if (r)
+		goto err2;
+	r = tsc2301_mixer_init(tsc, pdata);
+	if (r)
+		goto err3;
+	return 0;
+
+err3:
+	tsc2301_ts_exit(tsc);
+err2:
+	tsc2301_kp_exit(tsc);
+err1:
+	kfree(tsc);
+	return r;
+}
+
+static int __devexit tsc2301_remove(struct spi_device *spi)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(&spi->dev);
+
+	tsc2301_mixer_exit(tsc);
+        tsc2301_ts_exit(tsc);
+        tsc2301_kp_exit(tsc);
+	kfree(tsc);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int tsc2301_suspend(struct spi_device *spi, pm_message_t mesg)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(&spi->dev);
+	int r;
+
+	if ((r = tsc2301_mixer_suspend(tsc)) < 0)
+		return r;
+	if ((r = tsc2301_kp_suspend(tsc)) < 0)
+		goto err1;
+	if ((r = tsc2301_ts_suspend(tsc)) < 0)
+		goto err2;
+
+	return 0;
+err2:
+	tsc2301_kp_resume(tsc);
+err1:
+	tsc2301_mixer_resume(tsc);
+	return r;
+}
+
+static int tsc2301_resume(struct spi_device *spi)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(&spi->dev);
+
+	tsc2301_ts_resume(tsc);
+	tsc2301_kp_resume(tsc);
+	tsc2301_mixer_resume(tsc);
+	return 0;
+}
+#endif
+
+static struct spi_driver tsc2301_driver = {
+	.driver = {
+		   .name = "tsc2301",
+		   .bus = &spi_bus_type,
+		   .owner = THIS_MODULE,
+	},
+#ifdef CONFIG_PM
+	.suspend = tsc2301_suspend,
+	.resume = tsc2301_resume,
+#endif
+	.probe = tsc2301_probe,
+	.remove = __devexit_p(tsc2301_remove),
+};
+
+static int __init tsc2301_init(void)
+{
+	printk("TSC2301 driver initializing\n");
+
+	return spi_register_driver(&tsc2301_driver);
+}
+module_init(tsc2301_init);
+
+static void __exit tsc2301_exit(void)
+{
+	spi_unregister_driver(&tsc2301_driver);
+}
+module_exit(tsc2301_exit);
+
+MODULE_AUTHOR("Juha Yrjölä <juha.yrjola@nokia.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/spi/tsc2301-mixer.c b/drivers/spi/tsc2301-mixer.c
new file mode 100644
index 0000000..31ec87c
--- /dev/null
+++ b/drivers/spi/tsc2301-mixer.c
@@ -0,0 +1,1004 @@
+/*
+ * ALSA Mixer implementation for TSC2301
+ *
+ * Copyright (C) 2006 Nokia Corporation.
+ *
+ * Contact: Jarkko Nikula <jarkko.nikula@nokia.com>
+ *          Juha Yrjola
+ *
+ * Some notes about TSC2301:
+ * - PLL will stop when DAC and ADC's are powered down.
+ * - Touchscreen will stop working when audio part is powered up and if audio
+ *   MCLK is stopped. Problem is avoided if audio is powered down before
+ *   stopping MCLK.
+ * - Audio DAC or audio outputs will activate only after 100 msec from the
+ *   chip power-up. Reason seems to be VCM since there is no this delay if the
+ *   chip and VCM (bit AVPD on PD/MISC) were not powered down. The chip will
+ *   consume about 1 mA if all other audio blocks are powered down except the
+ *   chip itself and VCM. Full power down consumes only about few uA.
+ * - Power-down transition could happen earliest about 100 msec after the chip
+ *   power-up. Otherwise power-down will fail if there is no that 100 msec
+ *   on time before it. It's not obvious why is that since chip reports
+ *   power-up to be completed and also PLL output on GPIO_0 is active in few
+ *   milliseconds.
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/spi/spi.h>
+#include <linux/spi/tsc2301.h>
+
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/control.h>
+
+/* shadow register indexes */
+enum {
+	/* audio control and volume registers */
+	AUDCNTL_INDEX,
+	ADCVOL_INDEX,
+	DACVOL_INDEX,
+	BPVOL_INDEX,
+	/* keyclick control register (not needed here) */
+	/* audio power control register */
+	PD_MISC_INDEX,
+	/* TSC2301 GPIO control register */
+	GPIO_INDEX,
+
+	SHADOW_REG_COUNT,
+};
+
+/* structure for driver private data */
+struct tsc2301_mixer {
+	struct tsc2301 *tsc;
+	struct mutex mutex;
+
+	/* shadow registers holding TSC2301 audio registers. Used to hold
+	 * their states during the sleep and also to reduce communication with
+	 * the chip since get callback functions could get register values
+	 * directly from these shadow registers without needing to read them
+	 * from the chip */
+	u16 shadow_regs[SHADOW_REG_COUNT];
+
+	/* audio controller driver usage of the ADC and DAC */
+	unsigned adc_enabled:1, dac_enabled:1;
+	unsigned pll_output:1;
+	unsigned mclk_enabled;
+
+	/* latest audio power-up timestamp */
+	unsigned long pu_jiffies;
+
+	/* these are used when upper layer(s) are going to power-down TSC2301
+	 * before 100 msec is passed from power-up */
+	struct delayed_work delayed_power_down;
+	unsigned delayed_pd_active:1;
+
+	int (* platform_init)(struct device *);
+	void (* platform_cleanup)(struct device *);
+
+	struct tsc2301_mixer_gpio *mixer_gpios;
+	int n_mixer_gpios;
+};
+
+#define TSC2301_DAC_DELAY		msecs_to_jiffies(100)
+#define TSC2301_MIN_PU_PERIOD		msecs_to_jiffies(100)
+
+#define TSC2301_REG_TO_PVAL(reg)	\
+	(TSC2301_REG_TO_PAGE(reg) << 6 | TSC2301_REG_TO_ADDR(reg))
+#define  TSC2301_PVAL_TO_REG(v)		\
+	(TSC2301_REG((((v) >> 6) & 3),((v) & 0x1f)))
+
+#define TSC2301_VOLUME_MASK		0x7f
+#define TSC2301_MIN_ADCVOL		6
+#define TSC2301_MIN_DACVOL		0
+#define TSC2301_MIN_BPVOL		31
+#define TSC2301_MUTE_LEFT_SHIFT		15
+#define TSC2301_VOL_LEFT_SHIFT		8
+#define TSC2301_MUTE_RIGHT_SHIFT	7
+#define TSC2301_VOL_RIGHT_SHIFT		0
+
+#define TSC2301_INM_MASK		3
+#define TSC2301_INML_SHIFT		12
+#define TSC2301_INMR_SHIFT		10
+
+#define TSC2301_MICG_MASK		3
+#define TSC2301_MICG_MIN		1 /* values 0 & 1 both mean 0 dB */
+#define TSC2301_MICG_SHIFT		8
+
+#define TSC2301_REG_AUDCNTL_MCLK(v)	(((v) & 3) << 6)
+#define TSC2301_REG_AUDCNTL_I2SFS(v)	(((v) & 0xf) << 2)
+#define TSC2301_REG_AUDCNTL_I2SFM(v)	(((v) & 3) << 0)
+
+#define TSC2301_SINGLE(xname, xindex, reg, shadow_index, shift, mask, min) \
+{\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.index = xindex, \
+	.info = snd_tsc2301_info_single, \
+	.get = snd_tsc2301_get_single, \
+	.put = snd_tsc2301_put_single, \
+	.private_value = TSC2301_REG_TO_PVAL(reg) | \
+		(shadow_index << 8) | (shift << 16) | (mask << 24) | \
+		(min << 28) \
+}
+#define TSC2301_SINGLE_MINVAL(v)	(((v) >> 28) & 15)
+#define TSC2301_SINGLE_SHIFT(v)		(((v) >> 16) & 15)
+#define TSC2301_SINGLE_MASK(v)		(((v) >> 24) & 15)
+
+#define TSC2301_DOUBLE(xname, xindex, reg, shadow_index, ls, rs, mask, min) \
+{\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.index = xindex, \
+	.info = snd_tsc2301_info_double, \
+	.get = snd_tsc2301_get_double, \
+	.put = snd_tsc2301_put_double, \
+	.private_value = TSC2301_REG_TO_PVAL(reg) | \
+		(shadow_index << 8) | (min << 11) | \
+		(ls << 16) | (rs << 20) | (mask << 24) \
+}
+#define TSC2301_DOUBLE_MINVAL(v)	(((v) >> 11) & 0x1f)
+#define TSC2301_DOUBLE_LEFT_SHIFT(v)	(((v) >> 16) & 15)
+#define TSC2301_DOUBLE_RIGHT_SHIFT(v)	(((v) >> 20) & 15)
+#define TSC2301_DOUBLE_MASK(v)		(((v) >> 24) & TSC2301_VOLUME_MASK)
+
+#define TSC2301_MUX(xname, xindex, reg, shadow_index, ls, rs, mask) \
+{\
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.index = xindex, \
+	.info = snd_tsc2301_info_mux, \
+	.get = snd_tsc2301_get_mux, \
+	.put = snd_tsc2301_put_mux, \
+	.private_value = TSC2301_REG_TO_PVAL(reg) | \
+		(shadow_index << 8) | (ls << 16) | (rs << 20) | (mask << 24) \
+}
+#define TSC2301_MUX_LEFT_SHIFT(v)	(((v) >> 16) & 15)
+#define TSC2301_MUX_RIGHT_SHIFT(v)	(((v) >> 20) & 15)
+#define TSC2301_MUX_MASK(v)		(((v) >> 24) & TSC2301_VOLUME_MASK)
+
+#define TSC2301_BOOL(xname, xindex, reg, shadow_index, shift, invert, state) \
+{ \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.index = xindex, \
+	.info = snd_tsc2301_info_bool, \
+	.get = snd_tsc2301_get_bool, \
+	.put = snd_tsc2301_put_bool, \
+	.private_value = TSC2301_REG_TO_PVAL(reg) | \
+		(shadow_index << 8) | (shift << 16) | \
+		(invert << 24) | (state << 25) \
+}
+#define TSC2301_BOOL_SHIFT(v)		(((v) >> 16) & 7)
+#define TSC2301_BOOL_INVERT(v)		(((v) >> 24) & 1)
+#define TSC2301_BOOL_STATE(v)		(((v) >> 25) & 1)
+
+#define TSC2301_SHADOW_INDEX(v)		(((v) >> 8) & 7)
+
+/*
+ * Power-down handler for additional GPIO mixer controls. GPIO state of GPIO
+ * controls whose power-down flag is enabled are set to their false/deactivate
+ * state
+ *
+ * Must be called tsc->mixer->mutex locked
+ */
+static void tsc2301_gpio_power_down(struct tsc2301 *tsc)
+{
+	struct tsc2301_mixer *mix = tsc->mixer;
+	u16 temp;
+	int i;
+
+	temp = mix->shadow_regs[GPIO_INDEX];
+	for (i = 0; i < mix->n_mixer_gpios; i++) {
+		const struct tsc2301_mixer_gpio *mg;
+
+		mg = mix->mixer_gpios + i;
+		if (mg->deactivate_on_pd) {
+			int gpio = mg->gpio;
+
+			temp &= ~(1 << gpio);
+			temp |= mg->inverted << gpio;
+		}
+	}
+	tsc2301_write_reg(tsc, TSC2301_REG_GPIO, temp);
+}
+
+/*
+ * Powers down/up audio blocks which are muted or become unused.
+ * shadow_index >= 0, changes power state of single audio block
+ * shadow_index < 0, changes power state of all blocks
+ *
+ * Must be called tsc->mixer->mutex locked
+ */
+#define TSC2301_MUTE_MASK \
+	((1 << TSC2301_MUTE_LEFT_SHIFT) | (1 << TSC2301_MUTE_RIGHT_SHIFT))
+static void tsc2301_power_ctrl(struct tsc2301 *tsc, int shadow_index,
+			       int poll_pdsts)
+{
+	struct tsc2301_mixer *mix = tsc->mixer;
+	u16 pd_ctrl, pd_ctrl_old, w;
+	unsigned long timeout;
+	int power_up = 0;
+
+	if (mix->delayed_pd_active) {
+		mix->delayed_pd_active = 0;
+		mix->mclk_enabled--;
+		cancel_delayed_work(&mix->delayed_power_down);
+	}
+
+	pd_ctrl = pd_ctrl_old = mix->shadow_regs[PD_MISC_INDEX];
+	/* power control helper based on used space mixer selections. See
+	 * actual power control decisions below */
+	if (shadow_index < 0 || shadow_index == ADCVOL_INDEX) {
+		/* ADC left and right power down control */
+		if (mix->shadow_regs[ADCVOL_INDEX] &
+		    (1 << TSC2301_MUTE_LEFT_SHIFT))
+			/* left ADC muted. Power down the left ADC */
+			pd_ctrl |= TSC2301_REG_PD_MISC_ADPDL;
+		else
+			pd_ctrl &= ~TSC2301_REG_PD_MISC_ADPDL;
+		if (mix->shadow_regs[ADCVOL_INDEX] &
+		    (1 << TSC2301_MUTE_LEFT_SHIFT))
+			/* right ADC muted. Power down the right ADC */
+			pd_ctrl |= TSC2301_REG_PD_MISC_ADPDR;
+		else
+			pd_ctrl &= ~TSC2301_REG_PD_MISC_ADPDR;
+	}
+	if (shadow_index < 0 || shadow_index == DACVOL_INDEX) {
+		/* DAC power down control */
+		if ((mix->shadow_regs[DACVOL_INDEX] &
+		     TSC2301_MUTE_MASK) == TSC2301_MUTE_MASK)
+			/* both DACs muted. Power down the DAC */
+			pd_ctrl |= TSC2301_REG_PD_MISC_DAPD;
+		else
+			pd_ctrl &= ~TSC2301_REG_PD_MISC_DAPD;
+	}
+	if (shadow_index < 0 || shadow_index == BPVOL_INDEX) {
+		/* line bypass power down control */
+		if ((mix->shadow_regs[BPVOL_INDEX] &
+		     TSC2301_MUTE_MASK) == TSC2301_MUTE_MASK)
+			/* both line bypasses muted. Power down the bypass
+			 * path */
+			pd_ctrl |= TSC2301_REG_PD_MISC_ABPD;
+		else
+			pd_ctrl &= ~TSC2301_REG_PD_MISC_ABPD;
+	}
+	if (shadow_index < 0 || shadow_index == AUDCNTL_INDEX) {
+		/* mic bias power down control */
+		if ((mix->shadow_regs[AUDCNTL_INDEX] &
+		     (3 << TSC2301_INML_SHIFT)) &&
+		    (mix->shadow_regs[AUDCNTL_INDEX] &
+		     (3 << TSC2301_INMR_SHIFT)))
+			/* both ADC channels use other than mic input. Power
+			 * down the mic bias output */
+			pd_ctrl |= TSC2301_REG_PD_MISC_MIBPD;
+		else
+			pd_ctrl &= ~TSC2301_REG_PD_MISC_MIBPD;
+	}
+
+	/* power control decisions based on codec usage and user space mixer
+	 * selections detected above */
+	pd_ctrl &= ~TSC2301_REG_PD_MISC_APD; /* audio not powered down */
+	if (mix->mclk_enabled) {
+		if (!mix->adc_enabled) {
+			/* ADC not used, power down both ADC's and mic bias
+			 * output independently of user space mixer
+			 * selections */
+			pd_ctrl |= TSC2301_REG_PD_MISC_ADPDL;
+			pd_ctrl |= TSC2301_REG_PD_MISC_ADPDR;
+			pd_ctrl |= TSC2301_REG_PD_MISC_MIBPD;
+		}
+		if (!mix->dac_enabled) {
+			/* DAC not used, power down DAC independently of user
+			 * space mixer selections */
+			pd_ctrl |= TSC2301_REG_PD_MISC_DAPD;
+		}
+
+		if (mix->pll_output) {
+			/* GPIO_0 is configured as PLL output so audio
+			 * controller is expecting clock from TSC2301. Either
+			 * ADC or DAC must be active in order to keep PLL on */
+			if ((pd_ctrl & TSC2301_REG_PD_MISC_ADPDL) &&
+			    (pd_ctrl & TSC2301_REG_PD_MISC_ADPDR) &&
+			    (pd_ctrl & TSC2301_REG_PD_MISC_DAPD)) {
+				/* neither ADC or DAC used. Force ADC on in
+				 * order to keep PLL active */
+				pd_ctrl &= ~(TSC2301_REG_PD_MISC_ADPDL |
+					     TSC2301_REG_PD_MISC_ADPDR);
+			}
+		}
+	} else {
+		/* audio input clock is not enabled so power down DAC and ADC
+		 * in order to shutdown PLL and to keep touchscreen and keypad
+		 * parts working. Touchscreen and keypad use audio clock when
+		 * PLL is on and internal clock otherwise */
+		pd_ctrl |= TSC2301_REG_PD_MISC_DAPD |
+			   TSC2301_REG_PD_MISC_ADPDL |
+			   TSC2301_REG_PD_MISC_ADPDR;
+	}
+
+	if ((pd_ctrl & TSC2301_REG_PD_MISC_ADPDL) &&
+	    (pd_ctrl & TSC2301_REG_PD_MISC_ADPDR) &&
+	    (pd_ctrl & TSC2301_REG_PD_MISC_DAPD) &&
+	    (pd_ctrl & TSC2301_REG_PD_MISC_ABPD)) {
+		/* all ADC, DAC and line bypass path unused. Power down the
+		 * whole audio part of the TSC2301 */
+		pd_ctrl |= TSC2301_REG_PD_MISC_APD;
+	}
+
+	if (pd_ctrl == pd_ctrl_old)
+		return;
+
+	/* power down control changed. Update into TSC2301 */
+	if ((pd_ctrl ^ pd_ctrl_old) & TSC2301_REG_PD_MISC_APD) {
+		/* whole audio power state changed. Update GPIO states */
+		if (pd_ctrl & TSC2301_REG_PD_MISC_APD) {
+			/* power down GPIO controls before powering down
+			 * the codec */
+			tsc2301_gpio_power_down(tsc);
+			/* we must really ensure that codec has been on no less
+			 * than 100 msec before doing power-down */
+			timeout = mix->pu_jiffies + TSC2301_MIN_PU_PERIOD -
+				  jiffies;
+			if (timeout <= TSC2301_MIN_PU_PERIOD) {
+				mix->delayed_pd_active = 1;
+				mix->mclk_enabled++;
+				schedule_delayed_work(&mix->delayed_power_down,
+						      timeout + 1);
+				return;
+			}
+		} else
+			/* restore GPIOs after codec is powered up */
+			power_up = 1;
+	}
+	mix->shadow_regs[PD_MISC_INDEX] = pd_ctrl;
+	tsc2301_write_reg(tsc, TSC2301_REG_PD_MISC, pd_ctrl);
+	if (power_up)
+		mix->pu_jiffies = jiffies;
+	if (!poll_pdsts) {
+		if (power_up)
+			tsc2301_write_reg(tsc, TSC2301_REG_GPIO,
+					  mix->shadow_regs[GPIO_INDEX]);
+		return;
+	}
+
+	/* wait until power-up/-down is completed */
+	timeout = jiffies + msecs_to_jiffies(100);
+	w = 0;
+	do {
+		if (time_after(jiffies, timeout)) {
+			/* Print a warning only if the I2S clock is not
+			 * present / out of sync. This can happen during
+			 * init time, when that clock will be turned on
+			 * by another driver like in the OMAP EAC with
+			 * external clock case.
+			 */
+			if (w & TSC2301_REG_PD_MISC_OTSYN) {
+				dev_warn(&tsc->spi->dev,
+				   "I2S clock not in sync or off.\n");
+			} else {
+				dev_err(&tsc->spi->dev,
+				   "power-up/-down timed out "
+				   "(0x%04x, 0x%04x -> 0x%04x)\n",
+				   w, pd_ctrl_old, pd_ctrl);
+			}
+			goto out;
+		}
+		w = tsc2301_read_reg(tsc, TSC2301_REG_PD_MISC);
+	} while (!(w & TSC2301_REG_PD_MISC_PDSTS));
+
+out:
+	if (((pd_ctrl ^ pd_ctrl_old) & TSC2301_REG_PD_MISC_DAPD) &&
+	    !(pd_ctrl & TSC2301_REG_PD_MISC_DAPD)) {
+		/* DAC powered up now. Ensure that DAC and audio outputs are
+		 * activated. They are up 100 msec after the chip power-up
+		 * command */
+		timeout = mix->pu_jiffies + TSC2301_DAC_DELAY - jiffies;
+		if (timeout <= TSC2301_DAC_DELAY)
+			schedule_timeout_interruptible(timeout);
+		/* FIXME: This is lazy. We restore GPIOs only after activating
+		 * the DAC. It would be better to do some kind of delayed GPIO
+		 * restore. That ensures that we restore them also if only ADC
+		 * path is activated. But this is required only if there is
+		 * some input amplifier, bias control, etc. and their power
+		 * state is under TSC GPIO control */
+		tsc2301_write_reg(tsc, TSC2301_REG_GPIO,
+				  mix->shadow_regs[GPIO_INDEX]);
+	}
+}
+
+static int snd_tsc2301_info_single(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	int mask = TSC2301_SINGLE_MASK(kcontrol->private_value);
+	int minval = TSC2301_SINGLE_MINVAL(kcontrol->private_value);
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = minval;
+	uinfo->value.integer.max = mask;
+
+	return 0;
+}
+
+static int snd_tsc2301_get_single(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	int mask = TSC2301_SINGLE_MASK(priv);
+	int shift = TSC2301_SINGLE_SHIFT(priv);
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	u16 shadow_reg;
+
+	shadow_reg = tsc->mixer->shadow_regs[shadow_index];
+
+	ucontrol->value.integer.value[0] = (shadow_reg >> shift) & mask;
+
+	return 0;
+}
+
+static int snd_tsc2301_put_single(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	int mask = TSC2301_SINGLE_MASK(priv);
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	u16 shadow_reg, shadow_reg_old;
+	int shift = TSC2301_SINGLE_SHIFT(priv);
+	int reg = TSC2301_PVAL_TO_REG(priv);
+	int changed;
+
+	mutex_lock(&tsc->mixer->mutex);
+	shadow_reg = shadow_reg_old = tsc->mixer->shadow_regs[shadow_index];
+
+	/* zero bits to be modified */
+	shadow_reg &= ~(mask << shift);
+	/* modify with new value */
+	shadow_reg |= ((ucontrol->value.integer.value[0] & mask) << shift);
+
+	changed = (shadow_reg != shadow_reg_old);
+	tsc->mixer->shadow_regs[shadow_index] = shadow_reg;
+
+	/* update into TSC2301 if necessary */
+	if (changed)
+		tsc2301_write_reg(tsc, reg, shadow_reg);
+	mutex_unlock(&tsc->mixer->mutex);
+
+	return changed;
+}
+
+static int snd_tsc2301_info_double(struct snd_kcontrol *kcontrol,
+				   struct snd_ctl_elem_info *uinfo)
+{
+	/* mask == 1 : Switch
+	 * mask > 1 : Max volume */
+	int mask = TSC2301_DOUBLE_MASK(kcontrol->private_value);
+	int minval = TSC2301_DOUBLE_MINVAL(kcontrol->private_value);
+
+	uinfo->type = mask == 1 ? SNDRV_CTL_ELEM_TYPE_BOOLEAN :
+		SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = minval;
+	uinfo->value.integer.max = mask;
+
+	return 0;
+}
+
+static int snd_tsc2301_get_double(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	/* mask == 1 : Switch
+	 * mask > 1 : Volume */
+	int mask = TSC2301_DOUBLE_MASK(priv);
+	int ls = TSC2301_DOUBLE_LEFT_SHIFT(priv);
+	int rs = TSC2301_DOUBLE_RIGHT_SHIFT(priv);
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	u16 shadow_reg;
+
+	shadow_reg = tsc->mixer->shadow_regs[shadow_index];
+
+	/* invert mute bits for the switches */
+	if (mask == 1)
+		shadow_reg = ~shadow_reg;
+
+	ucontrol->value.integer.value[0] = (shadow_reg >> ls) & mask;
+	ucontrol->value.integer.value[1] = (shadow_reg >> rs) & mask;
+
+	return 0;
+}
+
+static int snd_tsc2301_put_double(struct snd_kcontrol *kcontrol,
+				  struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	/* mask == 1 : Switch
+	 * mask > 1 : Volume */
+	int mask = TSC2301_DOUBLE_MASK(priv);
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	u16 shadow_reg, shadow_reg_old;
+	int ls = TSC2301_DOUBLE_LEFT_SHIFT(priv);
+	int rs = TSC2301_DOUBLE_RIGHT_SHIFT(priv);
+	int reg = TSC2301_PVAL_TO_REG(priv);
+	int changed;
+
+	mutex_lock(&tsc->mixer->mutex);
+	shadow_reg = shadow_reg_old = tsc->mixer->shadow_regs[shadow_index];
+
+	/* zero bits to be modified */
+	shadow_reg &= ~((mask << ls) | (mask << rs));
+	/* modify with new value */
+	if (mask == 1) {
+		/* switch. Invert switch values for the mute bits */
+		shadow_reg |=
+			((~ucontrol->value.integer.value[0] & mask) << ls) |
+			((~ucontrol->value.integer.value[1] & mask) << rs);
+	} else {
+		/* volume */
+		shadow_reg |=
+			(ucontrol->value.integer.value[0] << ls) |
+			(ucontrol->value.integer.value[1] << rs);
+	}
+
+	changed = (shadow_reg != shadow_reg_old);
+	tsc->mixer->shadow_regs[shadow_index] = shadow_reg;
+
+	/* update into TSC2301 if necessary */
+	if (changed)
+		tsc2301_write_reg(tsc, reg, shadow_reg);
+
+	if (mask == 1)
+		/* check is need to power down/up audio blocks in case of
+		 * muted state change */
+		tsc2301_power_ctrl(tsc, shadow_index, 0);
+	mutex_unlock(&tsc->mixer->mutex);
+
+	return changed;
+}
+
+static int snd_tsc2301_info_mux(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_info *uinfo)
+{
+	static char *texts[4] = {"Mic", "Line", "Line swapped", "Line mono"};
+
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 2;
+	uinfo->value.enumerated.items = 4;
+	if (uinfo->value.enumerated.item > 3)
+		uinfo->value.enumerated.item = 3;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+
+	return 0;
+}
+
+static int snd_tsc2301_get_mux(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	int mask = TSC2301_MUX_MASK(priv);
+	int ls = TSC2301_MUX_LEFT_SHIFT(priv);
+	int rs = TSC2301_MUX_RIGHT_SHIFT(priv);
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	u16 shadow_reg;
+
+	shadow_reg = tsc->mixer->shadow_regs[shadow_index];
+	ucontrol->value.enumerated.item[0] = (shadow_reg >> ls) & mask;
+	ucontrol->value.enumerated.item[1] = (shadow_reg >> rs) & mask;
+
+	return 0;
+}
+
+static int snd_tsc2301_put_mux(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	int mask = TSC2301_MUX_MASK(priv);
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	u16 shadow_reg, shadow_reg_old;
+	int ls = TSC2301_MUX_LEFT_SHIFT(priv);
+	int rs = TSC2301_MUX_RIGHT_SHIFT(priv);
+	int reg = TSC2301_PVAL_TO_REG(priv);
+	int changed;
+
+	mutex_lock(&tsc->mixer->mutex);
+	shadow_reg = shadow_reg_old = tsc->mixer->shadow_regs[shadow_index];
+
+	/* zero bits to be modified */
+	shadow_reg &= ~((mask << ls) | (mask << rs));
+	/* modify with new value */
+	shadow_reg |= (ucontrol->value.enumerated.item[0] << ls);
+	shadow_reg |= (ucontrol->value.enumerated.item[1] << rs);
+
+	changed = (shadow_reg != shadow_reg_old);
+
+	/* update into TSC2301 if necessary */
+	if (changed) {
+		tsc->mixer->shadow_regs[shadow_index] = shadow_reg;
+		tsc2301_write_reg(tsc, reg, shadow_reg);
+	}
+
+	/* check is need to power up/down audio blocks in case of ADC input
+	 * change */
+	tsc2301_power_ctrl(tsc, shadow_index, 0);
+	mutex_unlock(&tsc->mixer->mutex);
+
+	return changed;
+}
+
+static int snd_tsc2301_info_bool(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+
+	return 0;
+}
+
+static int snd_tsc2301_get_bool(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	int shift = TSC2301_BOOL_SHIFT(priv);
+	int invert = TSC2301_BOOL_INVERT(priv);
+	u16 shadow_reg;
+
+	shadow_reg = tsc->mixer->shadow_regs[shadow_index];
+	ucontrol->value.integer.value[0] =
+		invert ^ ((shadow_reg >> shift) & 1);
+
+	return 0;
+}
+
+static int snd_tsc2301_put_bool(struct snd_kcontrol *kcontrol,
+				struct snd_ctl_elem_value *ucontrol)
+{
+	struct tsc2301 *tsc = kcontrol->private_data;
+	unsigned long priv = kcontrol->private_value;
+	int shadow_index = TSC2301_SHADOW_INDEX(priv);
+	int shift = TSC2301_BOOL_SHIFT(priv);
+	int invert = TSC2301_BOOL_INVERT(priv);
+	int reg = TSC2301_PVAL_TO_REG(priv);
+	u16 shadow_reg, shadow_reg_old;
+	int changed;
+
+	mutex_lock(&tsc->mixer->mutex);
+	shadow_reg = shadow_reg_old = tsc->mixer->shadow_regs[shadow_index];
+
+	/* zero bit to be modified */
+	shadow_reg &= ~(1 << shift);
+	/* modify with new value */
+	shadow_reg |=
+		(invert ^ (ucontrol->value.integer.value[0] & 1)) << shift;
+
+	changed = (shadow_reg != shadow_reg_old);
+
+	/* update into TSC2301 if necessary */
+	if (changed) {
+		tsc->mixer->shadow_regs[shadow_index] = shadow_reg;
+		if ((shadow_index == GPIO_INDEX) &&
+		    (tsc->mixer->shadow_regs[PD_MISC_INDEX] &
+		     TSC2301_REG_PD_MISC_APD)) {
+			/* changing GPIO control and audio is powered down.
+			 * Update GPIO states according to their power-down
+			 * flag */
+			tsc2301_gpio_power_down(tsc);
+		} else
+			tsc2301_write_reg(tsc, reg, shadow_reg);
+	}
+	mutex_unlock(&tsc->mixer->mutex);
+
+	return changed;
+}
+
+/* TSC2301 internal mixer controls */
+static struct snd_kcontrol_new snd_tsc2301_controls[] = {
+	/* stereo ADC input switches and volumes */
+	TSC2301_DOUBLE("Capture Switch", 0,
+		TSC2301_REG_ADCVOL, ADCVOL_INDEX,
+		TSC2301_MUTE_LEFT_SHIFT, TSC2301_MUTE_RIGHT_SHIFT,
+		1, 0),
+	TSC2301_DOUBLE("Capture Volume", 0,
+		TSC2301_REG_ADCVOL, ADCVOL_INDEX,
+		TSC2301_VOL_LEFT_SHIFT, TSC2301_VOL_RIGHT_SHIFT,
+		TSC2301_VOLUME_MASK, TSC2301_MIN_ADCVOL),
+
+	/* stereo DAC output switches and volumes */
+	TSC2301_DOUBLE("PCM Playback Switch", 0,
+		TSC2301_REG_DACVOL, DACVOL_INDEX,
+		TSC2301_MUTE_LEFT_SHIFT, TSC2301_MUTE_RIGHT_SHIFT,
+		1, 0),
+	TSC2301_DOUBLE("PCM Playback Volume", 0,
+		TSC2301_REG_DACVOL, DACVOL_INDEX,
+		TSC2301_VOL_LEFT_SHIFT, TSC2301_VOL_RIGHT_SHIFT,
+		TSC2301_VOLUME_MASK, TSC2301_MIN_DACVOL),
+
+	/* stereo line input bypass switches and volumes */
+	TSC2301_DOUBLE("Line Playback Switch", 0,
+		TSC2301_REG_BPVOL, BPVOL_INDEX,
+		TSC2301_MUTE_LEFT_SHIFT, TSC2301_MUTE_RIGHT_SHIFT,
+		1, 0),
+	TSC2301_DOUBLE("Line Playback Volume", 0,
+		TSC2301_REG_BPVOL, BPVOL_INDEX,
+		TSC2301_VOL_LEFT_SHIFT, TSC2301_VOL_RIGHT_SHIFT,
+		TSC2301_VOLUME_MASK, TSC2301_MIN_BPVOL),
+
+	/* mono microphone input gain */
+	TSC2301_SINGLE("Mic Boost", 0,
+		TSC2301_REG_AUDCNTL, AUDCNTL_INDEX,
+		TSC2301_MICG_SHIFT,
+		TSC2301_MICG_MASK, TSC2301_MICG_MIN),
+
+	/* ADC input sources. Both channels could be selected separately */
+	TSC2301_MUX("Capture Source", 0,
+		TSC2301_REG_AUDCNTL, AUDCNTL_INDEX,
+		TSC2301_INML_SHIFT, TSC2301_INMR_SHIFT,
+		TSC2301_INM_MASK),
+};
+
+/* must be called tsc->mixer->mutex locked */
+static void tsc2301_flush_shadow_regs(struct tsc2301 *tsc)
+{
+	int i, page, addr;
+	u16 temp;
+
+	page = TSC2301_REG_TO_PAGE(TSC2301_REG_AUDCNTL);
+	addr = TSC2301_REG_TO_ADDR(TSC2301_REG_AUDCNTL);
+
+	for (i = 0; i < 4; i++) {
+		temp = tsc->mixer->shadow_regs[i];
+		tsc2301_write_reg(tsc, TSC2301_REG(page, addr + i), temp);
+	}
+	temp = tsc->mixer->shadow_regs[GPIO_INDEX];
+	tsc2301_write_reg(tsc, TSC2301_REG_GPIO, temp);
+
+	/* Update power state of all audio blocks depending are they
+	 * muted or unused. */
+	tsc2301_power_ctrl(tsc, -1, 0);
+}
+
+#ifdef CONFIG_PM
+int tsc2301_mixer_suspend(struct tsc2301 *tsc)
+{
+	/* power down entire audio section inside TSC2301 in case the
+	 * chip is still powered during the system sleep. However this driver
+	 * doesn't require that chip is powered because registers are restored
+	 * in function tsc2301_mixer_resume */
+	mutex_lock(&tsc->mixer->mutex);
+	tsc2301_gpio_power_down(tsc);
+	tsc->mixer->shadow_regs[PD_MISC_INDEX] |= TSC2301_REG_PD_MISC_APD;
+	tsc2301_write_reg(tsc, TSC2301_REG_PD_MISC,
+			  tsc->mixer->shadow_regs[PD_MISC_INDEX]);
+	mutex_unlock(&tsc->mixer->mutex);
+	return 0;
+}
+
+void tsc2301_mixer_resume(struct tsc2301 *tsc)
+{
+	/* power up the TSC2301 audio section and restore registers */
+	mutex_lock(&tsc->mixer->mutex);
+	tsc->mixer->shadow_regs[PD_MISC_INDEX] &= ~TSC2301_REG_PD_MISC_APD;
+	tsc2301_flush_shadow_regs(tsc);
+	mutex_unlock(&tsc->mixer->mutex);
+}
+#endif
+
+void tsc2301_mixer_enable_mclk(struct device *dev)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(dev);
+	struct tsc2301_mixer *mix = tsc->mixer;
+
+	mutex_lock(&mix->mutex);
+	if (!mix->mclk_enabled++ && tsc->enable_clock != NULL) {
+		tsc->enable_clock(dev);
+	}
+	tsc2301_power_ctrl(tsc, -1, 1);
+	mutex_unlock(&mix->mutex);
+}
+
+void tsc2301_mixer_disable_mclk(struct device *dev)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(dev);
+	struct tsc2301_mixer *mix = tsc->mixer;
+
+	mutex_lock(&mix->mutex);
+	mix->mclk_enabled--;
+	tsc2301_power_ctrl(tsc, -1, 1);
+	if (!mix->mclk_enabled && tsc->disable_clock != NULL) {
+		tsc->disable_clock(dev);
+	}
+	mutex_unlock(&mix->mutex);
+}
+
+static void tsc2301_mixer_delayed_power_down(struct work_struct *work)
+{
+	struct tsc2301_mixer *mix = container_of(work, struct tsc2301_mixer,
+						 delayed_power_down.work);
+	struct tsc2301 *tsc = mix->tsc;
+
+	mutex_lock(&mix->mutex);
+	if (!mix->delayed_pd_active) {
+		mutex_unlock(&mix->mutex);
+		return;
+	}
+	mix->delayed_pd_active = 0;
+	mutex_unlock(&mix->mutex);
+	tsc2301_mixer_disable_mclk(&tsc->spi->dev);
+}
+
+/*
+ * Allows audio controller driver to notify its usage of ADC and DAC
+ */
+void tsc2301_mixer_set_power(struct device *dev, int dac, int adc)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(dev);
+
+	mutex_lock(&tsc->mixer->mutex);
+	tsc->mixer->adc_enabled = adc;
+	tsc->mixer->dac_enabled = dac;
+
+	/* update power state of all audio blocks */
+	tsc2301_power_ctrl(tsc, -1, 1);
+	mutex_unlock(&tsc->mixer->mutex);
+}
+
+/*
+ * Registers TSC2301 ALSA Mixer controls for the given sound card
+ */
+int tsc2301_mixer_register_controls(struct device *dev, struct snd_card *card)
+{
+	struct tsc2301 *tsc = dev_get_drvdata(dev);
+	struct tsc2301_mixer *mix = tsc->mixer;
+	int i, err;
+
+	/* Register ALSA mixer controls */
+	for (i = 0; i < ARRAY_SIZE(snd_tsc2301_controls); i++) {
+		err = snd_ctl_add(card,
+				  snd_ctl_new1(&snd_tsc2301_controls[i], tsc));
+		if (err < 0)
+			return err;
+	}
+
+	if (!mix->n_mixer_gpios)
+		return 0;
+
+	/* Register additional GPIO controls if defined */
+	for (i = 0; i < mix->n_mixer_gpios; i++) {
+		const struct tsc2301_mixer_gpio *mg = mix->mixer_gpios + i;
+		struct snd_kcontrol *ctrlp;
+		struct snd_kcontrol_new ctrl =
+			TSC2301_BOOL((char *)mg->name, 0,
+				     TSC2301_REG_GPIO, GPIO_INDEX,
+				     mg->gpio, mg->inverted, mg->def_enable);
+
+		ctrlp = snd_ctl_new1(&ctrl, tsc);
+		err = snd_ctl_add(card, ctrlp);
+		if (err < 0)
+			return err;
+	}
+
+	return 0;
+}
+
+int tsc2301_mixer_init(struct tsc2301 *tsc,
+		       struct tsc2301_platform_data *pdata)
+{
+	struct tsc2301_mixer *mix;
+	int err = 0;
+	u16 w;
+
+	mix = kzalloc(sizeof(*mix), GFP_KERNEL);
+	if (mix == NULL)
+		return -ENOMEM;
+	tsc->mixer = mix;
+
+	mix->tsc = tsc;
+	mutex_init(&mix->mutex);
+	mix->platform_init = pdata->codec_init;
+	mix->platform_cleanup = pdata->codec_cleanup;
+	mix->pll_output = pdata->pll_output;
+
+	INIT_DELAYED_WORK(&mix->delayed_power_down,
+			  tsc2301_mixer_delayed_power_down);
+
+	/* initialize shadow register default values */
+	w = 0xc000;
+	w |= (pdata->mclk_ratio << 6) | (pdata->i2s_sample_rate << 2);
+	w |= pdata->i2s_format;
+	mix->shadow_regs[AUDCNTL_INDEX] = w;
+	mix->shadow_regs[ADCVOL_INDEX] = 0xd7d7;
+	mix->shadow_regs[DACVOL_INDEX] = 0xffff;
+	mix->shadow_regs[BPVOL_INDEX] = 0xe7e7;
+	mix->shadow_regs[PD_MISC_INDEX] = pdata->power_down_blocks;
+
+	/* if extra mixer controls configured, then configure associated
+	 * GPIOs as output and drive their default state */
+	if (pdata->n_mixer_gpios) {
+		int i;
+
+		w = 0;
+		for (i = 0; i < pdata->n_mixer_gpios; i++) {
+			const struct tsc2301_mixer_gpio *mg;
+			int gpio;
+
+			mg = pdata->mixer_gpios + i;
+			gpio = mg->gpio;
+			w |= (1 << gpio) << 8;
+			w |= (mg->inverted ^ mg->def_enable) << gpio;
+		}
+		mix->shadow_regs[GPIO_INDEX] = w;
+
+		mix->mixer_gpios = kmalloc(sizeof(*pdata->mixer_gpios) *
+					   pdata->n_mixer_gpios,
+					   GFP_KERNEL);
+		if (mix->mixer_gpios == NULL) {
+			err = -ENOMEM;
+			goto err1;
+		}
+		memcpy(mix->mixer_gpios, pdata->mixer_gpios,
+		       sizeof(*pdata->mixer_gpios) * pdata->n_mixer_gpios);
+		mix->n_mixer_gpios = pdata->n_mixer_gpios;
+	}
+
+	/* PLL control */
+	tsc2301_write_pll(tsc, pdata->pll_n, pdata->pll_a, pdata->pll_pdc,
+			  0, mix->pll_output ? 0 : 1);
+
+	tsc2301_flush_shadow_regs(tsc);
+
+	if (mix->platform_init != NULL) {
+		err = mix->platform_init(&tsc->spi->dev);
+		if (err < 0)
+			goto err2;
+	}
+
+	return 0;
+err2:
+	if (mix->mixer_gpios != NULL)
+		kfree(mix->mixer_gpios);
+err1:
+	kfree(mix);
+	return err;
+}
+
+void tsc2301_mixer_exit(struct tsc2301 *tsc)
+{
+	struct tsc2301_mixer *mixer = tsc->mixer;
+
+	if (mixer->platform_cleanup != NULL)
+		mixer->platform_cleanup(&tsc->spi->dev);
+
+	if (mixer->mixer_gpios != NULL)
+		kfree(mixer->mixer_gpios);
+}
+
+MODULE_AUTHOR("Jarkko Nikula <jarkko.nikula@nokia.com>");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* [PATCH 29/33] add omap ehci bus glue
  2008-08-30 17:16                                                       ` [PATCH 28/33] add tsc2301 driver Felipe Balbi
@ 2008-08-30 17:16                                                         ` Felipe Balbi
  2008-08-30 17:16                                                           ` [PATCH 30/33] add omap backlight support Felipe Balbi
                                                                             ` (2 more replies)
  2008-09-01  7:35                                                         ` [PATCH 28/33] add tsc2301 driver Jarkko Nikula
  1 sibling, 3 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/usb/host/Kconfig     |   19 ++
 drivers/usb/host/ehci-hcd.c  |    5 +
 drivers/usb/host/ehci-omap.c |  562 ++++++++++++++++++++++++++++++++++++++++++
 drivers/usb/host/ehci-omap.h |  125 ++++++++++
 4 files changed, 711 insertions(+), 0 deletions(-)
 create mode 100644 drivers/usb/host/ehci-omap.c
 create mode 100644 drivers/usb/host/ehci-omap.h

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 228797e..9807d13 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -41,6 +41,25 @@ config USB_EHCI_HCD
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called ehci-hcd.
+choice
+	prompt "PHY/TLL mode"
+	depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
+	---help---
+	Choose PHY or TLL mode of operation
+
+config OMAP_EHCI_PHY_MODE
+	bool "PHY mode: ISP1504 on Port1/2 (NEW 3430ES2.0)"
+	depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
+	---help---
+	  EHCI PHY mode. Port1 and Port2 are connected to ISP1504 transcievers
+
+config OMAP_EHCI_TLL_MODE
+	bool "TLL mode: (EXPERIMENTAL)"
+	depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
+	---help---
+	OMAP EHCI controller has TLL mode of operation for all 3 ports.
+	Use this mode when no transciever is present
+endchoice
 
 config USB_EHCI_ROOT_HUB_TT
 	bool "Root Hub Transaction Translators"
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index d9d53f2..b0a5d10 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -1009,6 +1009,11 @@ MODULE_LICENSE ("GPL");
 #define	PLATFORM_DRIVER		ehci_hcd_au1xxx_driver
 #endif
 
+#ifdef CONFIG_ARCH_OMAP34XX
+#include "ehci-omap.c"
+#define	PLATFORM_DRIVER		ehci_hcd_omap_driver
+#endif
+
 #ifdef CONFIG_PPC_PS3
 #include "ehci-ps3.c"
 #define	PS3_SYSTEM_BUS_DRIVER	ps3_ehci_driver
diff --git a/drivers/usb/host/ehci-omap.c b/drivers/usb/host/ehci-omap.c
new file mode 100644
index 0000000..8f122e5
--- /dev/null
+++ b/drivers/usb/host/ehci-omap.c
@@ -0,0 +1,562 @@
+/*
+ * ehci-omap.c - driver for USBHOST on OMAP 34xx processor
+ *
+ * Bus Glue for OMAP34xx USBHOST 3 port EHCI controller
+ * Tested on OMAP3430 ES2.0 SDP
+ *
+ * Copyright (C) 2007-2008 Texas Instruments, Inc.
+ * 	Author: Vikram Pandita <vikram.pandita@ti.com>
+ *
+ * Based on "ehci-fsl.c" and "ehci-au1xxx.c" ehci glue layers
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <mach/gpio.h>
+
+#include "ehci-omap.h"
+
+
+#ifdef CONFIG_OMAP_EHCI_PHY_MODE
+/* EHCI connected to External PHY */
+
+/* External USB connectivity board: 750-2083-001
+ * Connected to OMAP3430 SDP
+ * The board has Port1 and Port2 connected to ISP1504 in 12-pin ULPI mode
+ */
+
+/* ISSUE1:
+ *      ISP1504 for input clocking mode needs special reset handling
+ * 	Hold the PHY in reset by asserting RESET_N signal
+ * 	Then start the 60Mhz clock input to PHY
+ * 	Release the reset after a delay -
+ * 		to get the PHY state machine in working state
+ */
+#define EXTERNAL_PHY_RESET
+#define	EXT_PHY_RESET_GPIO_PORT1	(57)
+#define	EXT_PHY_RESET_GPIO_PORT2	(61)
+#define	EXT_PHY_RESET_DELAY		(10)
+
+/* ISSUE2:
+ * USBHOST supports External charge pump PHYs only
+ * Use the VBUS from Port1 to power VBUS of Port2 externally
+ * So use Port2 as the working ULPI port
+ */
+#define VBUS_INTERNAL_CHARGEPUMP_HACK
+
+#endif /* CONFIG_OMAP_EHCI_PHY_MODE */
+
+/*-------------------------------------------------------------------------*/
+
+/* Define USBHOST clocks for clock management */
+struct ehci_omap_clock_defs {
+	struct clk	*usbhost_ick_clk;
+	struct clk	*usbhost2_120m_fck_clk;
+	struct clk	*usbhost1_48m_fck_clk;
+	struct clk	*usbtll_fck_clk;
+	struct clk	*usbtll_ick_clk;
+};
+
+/* Clock names as per clock framework: May change so keep as #defs */
+#define USBHOST_ICKL		"usbhost_ick"
+#define USBHOST_120M_FCLK	"usbhost_120m_fck"
+#define USBHOST_48M_FCLK	"usbhost_48m_fck"
+#define USBHOST_TLL_ICKL	"usbtll_ick"
+#define USBHOST_TLL_FCLK	"usbtll_fck"
+/*-------------------------------------------------------------------------*/
+
+
+#ifndef CONFIG_OMAP_EHCI_PHY_MODE
+
+static void omap_usb_utmi_init(struct usb_hcd *hcd, u8 tll_channel_mask)
+{
+	int i;
+
+	/* Use UTMI Ports of TLL */
+	omap_writel((1 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)|
+			(1<<OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN_SHIFT)|
+			(1<<OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN_SHIFT)|
+			(1<<OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN_SHIFT)|
+			(0<<OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN_SHIFT),
+						OMAP_UHH_HOSTCONFIG);
+	/* Enusre bit is set */
+	while (!(omap_readl(OMAP_UHH_HOSTCONFIG) &
+		(1 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)));
+
+	dev_dbg(hcd->self.controller, "\nEntered UTMI MODE: success\n");
+
+	/* Program the 3 TLL channels upfront */
+
+	for (i = 0; i < OMAP_TLL_CHANNEL_COUNT; i++) {
+
+		/* Disable AutoIdle */
+		omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) &
+			    ~(1<<OMAP_TLL_CHANNEL_CONF_UTMIAUTOIDLE_SHIFT),
+			    OMAP_TLL_CHANNEL_CONF(i));
+		/* Disable BitStuffing */
+		omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) &
+			    ~(1<<OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF_SHIFT),
+			    OMAP_TLL_CHANNEL_CONF(i));
+		/* SDR Mode */
+		omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) &
+			    ~(1<<OMAP_TLL_CHANNEL_CONF_ULPIDDRMODE_SHIFT),
+			    OMAP_TLL_CHANNEL_CONF(i));
+
+	}
+
+	/* Program Common TLL register */
+	omap_writel((1 << OMAP_TLL_SHARED_CONF_FCLK_IS_ON_SHIFT) |
+			(1 << OMAP_TLL_SHARED_CONF_USB_DIVRATION_SHIFT) |
+			(0 << OMAP_TLL_SHARED_CONF_USB_180D_SDR_EN_SHIFT) |
+			(0 << OMAP_TLL_SHARED_CONF_USB_90D_DDR_EN_SHFT),
+				OMAP_TLL_SHARED_CONF);
+
+	/* Enable channels now */
+	for (i = 0; i < OMAP_TLL_CHANNEL_COUNT; i++) {
+
+		/* Enable only the channel that is needed */
+		if (!(tll_channel_mask & 1<<i))
+			continue;
+
+		omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) |
+			    (1<<OMAP_TLL_CHANNEL_CONF_CHANEN_SHIFT),
+			    OMAP_TLL_CHANNEL_CONF(i));
+
+		omap_writeb(0xBE, OMAP_TLL_ULPI_SCRATCH_REGISTER(i));
+		dev_dbg(hcd->self.controller, "\nULPI_SCRATCH_REG[ch=%d]"
+			"= 0x%02x\n",
+			i+1, omap_readb(OMAP_TLL_ULPI_SCRATCH_REGISTER(i)));
+	}
+}
+
+#else
+# define omap_usb_utmi_init(x, y)	0
+#endif
+
+
+/* omap_start_ehc
+ * 	- Start the TI USBHOST controller
+ */
+static int omap_start_ehc(struct platform_device *dev, struct usb_hcd *hcd)
+{
+	struct ehci_omap_clock_defs *ehci_clocks;
+
+	dev_dbg(hcd->self.controller, ": starting TI EHCI USB Controller\n");
+
+	ehci_clocks = (struct ehci_omap_clock_defs *)(
+				((char *)hcd_to_ehci(hcd)) +
+					sizeof(struct ehci_hcd));
+
+	/* Start DPLL5 Programming:
+	 * Clock Framework is not doing this now:
+	 * This will be done in clock framework later
+	 */
+	/* Enable DPLL 5 : Based on Input of 13Mhz*/
+	cm_write_mod_reg((12 << OMAP3430ES2_PERIPH2_DPLL_DIV_SHIFT)|
+			(120 << OMAP3430ES2_PERIPH2_DPLL_MULT_SHIFT),
+			PLL_MOD, OMAP3430ES2_CM_CLKSEL4);
+
+	cm_write_mod_reg(1 << OMAP3430ES2_DIV_120M_SHIFT,
+			PLL_MOD, OMAP3430ES2_CM_CLKSEL5);
+
+	cm_write_mod_reg((7 << OMAP3430ES2_PERIPH2_DPLL_FREQSEL_SHIFT) |
+			(7 << OMAP3430ES2_EN_PERIPH2_DPLL_SHIFT),
+			PLL_MOD, OMAP3430ES2_CM_CLKEN2);
+
+	while (!(cm_read_mod_reg(PLL_MOD, CM_IDLEST2) &
+				OMAP3430ES2_ST_PERIPH2_CLK_MASK))
+		dev_dbg(hcd->self.controller,
+			"idlest2 = 0x%x\n",
+			cm_read_mod_reg(PLL_MOD, CM_IDLEST2));
+	/* End DPLL5 programming */
+
+
+	/* PRCM settings for USBHOST:
+	 * Interface clk un-related to domain transition
+	 */
+	cm_write_mod_reg(0 << OMAP3430ES2_AUTO_USBHOST_SHIFT,
+				OMAP3430ES2_USBHOST_MOD, CM_AUTOIDLE);
+
+	/* Disable sleep dependency with MPU and IVA */
+	cm_write_mod_reg((0 << OMAP3430ES2_EN_MPU_SHIFT) |
+				(0 << OMAP3430ES2_EN_IVA2_SHIFT),
+				OMAP3430ES2_USBHOST_MOD, OMAP3430_CM_SLEEPDEP);
+
+	/* Disable Automatic transition of clock */
+	cm_write_mod_reg(0 << OMAP3430ES2_CLKTRCTRL_USBHOST_SHIFT,
+				OMAP3430ES2_USBHOST_MOD, CM_CLKSTCTRL);
+
+	/* Enable Clocks for USBHOST */
+	ehci_clocks->usbhost_ick_clk = clk_get(&dev->dev,
+						USBHOST_ICKL);
+	if (IS_ERR(ehci_clocks->usbhost_ick_clk))
+		return PTR_ERR(ehci_clocks->usbhost_ick_clk);
+	clk_enable(ehci_clocks->usbhost_ick_clk);
+
+
+	ehci_clocks->usbhost2_120m_fck_clk = clk_get(&dev->dev,
+							USBHOST_120M_FCLK);
+	if (IS_ERR(ehci_clocks->usbhost2_120m_fck_clk))
+		return PTR_ERR(ehci_clocks->usbhost2_120m_fck_clk);
+	clk_enable(ehci_clocks->usbhost2_120m_fck_clk);
+
+	ehci_clocks->usbhost1_48m_fck_clk = clk_get(&dev->dev,
+						USBHOST_48M_FCLK);
+	if (IS_ERR(ehci_clocks->usbhost1_48m_fck_clk))
+		return PTR_ERR(ehci_clocks->usbhost1_48m_fck_clk);
+	clk_enable(ehci_clocks->usbhost1_48m_fck_clk);
+
+
+#ifdef EXTERNAL_PHY_RESET
+	/* Refer: ISSUE1 */
+	omap_request_gpio(EXT_PHY_RESET_GPIO_PORT1);
+	omap_set_gpio_direction(EXT_PHY_RESET_GPIO_PORT1, 0);
+	omap_request_gpio(EXT_PHY_RESET_GPIO_PORT2);
+	omap_set_gpio_direction(EXT_PHY_RESET_GPIO_PORT2, 0);
+	omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT1, 0);
+	omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT2, 0);
+	/* Hold the PHY in RESET for enough time till DIR is high */
+	udelay(EXT_PHY_RESET_DELAY);
+#endif
+
+	/* Configure TLL for 60Mhz clk for ULPI */
+	ehci_clocks->usbtll_fck_clk = clk_get(&dev->dev, USBHOST_TLL_FCLK);
+	if (IS_ERR(ehci_clocks->usbtll_fck_clk))
+		return PTR_ERR(ehci_clocks->usbtll_fck_clk);
+	clk_enable(ehci_clocks->usbtll_fck_clk);
+
+	ehci_clocks->usbtll_ick_clk = clk_get(&dev->dev, USBHOST_TLL_ICKL);
+	if (IS_ERR(ehci_clocks->usbtll_ick_clk))
+		return PTR_ERR(ehci_clocks->usbtll_ick_clk);
+	clk_enable(ehci_clocks->usbtll_ick_clk);
+
+	/* Disable Auto Idle of USBTLL */
+	cm_write_mod_reg((0 << OMAP3430ES2_AUTO_USBTLL_SHIFT),
+				CORE_MOD, CM_AUTOIDLE3);
+
+	/* Wait for TLL to be Active */
+	while ((cm_read_mod_reg(CORE_MOD, OMAP2430_CM_IDLEST3) &
+		(1 << OMAP3430ES2_ST_USBTLL_SHIFT)));
+
+	/* perform TLL soft reset, and wait until reset is complete */
+	omap_writel(1 << OMAP_USBTLL_SYSCONFIG_SOFTRESET_SHIFT,
+			OMAP_USBTLL_SYSCONFIG);
+	/* Wait for TLL reset to complete */
+	while (!(omap_readl(OMAP_USBTLL_SYSSTATUS) &
+		(1 << OMAP_USBTLL_SYSSTATUS_RESETDONE_SHIFT)));
+
+	dev_dbg(hcd->self.controller, "\n TLL RESET DONE\n");
+
+	/* (1<<3) = no idle mode only for initial debugging */
+	omap_writel((1 << OMAP_USBTLL_SYSCONFIG_ENAWAKEUP_SHIFT) |
+			(1 << OMAP_USBTLL_SYSCONFIG_SIDLEMODE_SHIFT) |
+			(1 << OMAP_USBTLL_SYSCONFIG_CACTIVITY_SHIFT),
+			OMAP_USBTLL_SYSCONFIG);
+
+
+	/* Put UHH in NoIdle/NoStandby mode */
+	omap_writel((0 << OMAP_UHH_SYSCONFIG_AUTOIDLE_SHIFT) |
+			(1 << OMAP_UHH_SYSCONFIG_ENAWAKEUP_SHIFT) |
+			(1 << OMAP_UHH_SYSCONFIG_SIDLEMODE_SHIFT) |
+			(1 << OMAP_UHH_SYSCONFIG_CACTIVITY_SHIFT) |
+			(1 << OMAP_UHH_SYSCONFIG_MIDLEMODE_SHIFT),
+			OMAP_UHH_SYSCONFIG);
+
+#ifdef CONFIG_OMAP_EHCI_PHY_MODE
+	/* Bypass the TLL module for PHY mode operation */
+	omap_writel((0 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)|
+			(1<<OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN_SHIFT)|
+			(1<<OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN_SHIFT)|
+			(1<<OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN_SHIFT)|
+			(0<<OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN_SHIFT),
+						OMAP_UHH_HOSTCONFIG);
+	/* Ensure that BYPASS is set */
+	while (omap_readl(OMAP_UHH_HOSTCONFIG) &
+		(1 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT));
+
+	dev_dbg(hcd->self.controller, "Entered ULPI PHY MODE: success");
+
+#else
+	/* Enable UTMI mode for all 3 TLL channels */
+	omap_usb_utmi_init(hcd,
+		OMAP_TLL_CHANNEL_1_EN_MASK |
+		OMAP_TLL_CHANNEL_2_EN_MASK |
+		OMAP_TLL_CHANNEL_3_EN_MASK
+		);
+#endif
+
+#ifdef EXTERNAL_PHY_RESET
+	/* Refer ISSUE1:
+	 * Hold the PHY in RESET for enough time till PHY is settled and ready
+	 */
+	udelay(EXT_PHY_RESET_DELAY);
+	omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT1, 1);
+	omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT2, 1);
+#endif
+
+#ifdef VBUS_INTERNAL_CHARGEPUMP_HACK
+	/* Refer ISSUE2: LINK assumes external charge pump */
+
+	/* use Port1 VBUS to charge externally Port2:
+	 * 	So for PHY mode operation use Port2 only
+	 */
+	omap_writel((0xA << EHCI_INSNREG05_ULPI_REGADD_SHIFT) |/* OTG ctrl reg*/
+			(2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) |/*   Write */
+			(1 << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) |/* Port1 */
+			(1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT) |/* Start */
+			(0x26),
+			EHCI_INSNREG05_ULPI);
+
+	while (!(omap_readl(EHCI_INSNREG05_ULPI) &
+		(1<<EHCI_INSNREG05_ULPI_CONTROL_SHIFT)));
+
+#endif
+
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static void omap_stop_ehc(struct platform_device *dev, struct usb_hcd *hcd)
+{
+	struct ehci_omap_clock_defs *ehci_clocks;
+
+	ehci_clocks = (struct ehci_omap_clock_defs *)
+			(((char *)hcd_to_ehci(hcd)) + sizeof(struct ehci_hcd));
+
+	dev_dbg(hcd->self.controller, ": stopping TI EHCI USB Controller\n");
+
+	/* Reset OMAP modules for insmod/rmmod to work */
+	omap_writel((1<<1), OMAP_UHH_SYSCONFIG);
+	while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<0)));
+	while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<1)));
+	while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<2)));
+	dev_dbg(hcd->self.controller,
+		"UHH RESET DONE OMAP_UHH_SYSSTATUS %x !!\n",
+			omap_readl(OMAP_UHH_SYSSTATUS));
+
+	omap_writel((1<<1), OMAP_USBTLL_SYSCONFIG);
+	while (!(omap_readl(OMAP_USBTLL_SYSSTATUS) & (1<<0)));
+	dev_dbg(hcd->self.controller, ":TLL RESEET DONE");
+
+	if (ehci_clocks->usbtll_fck_clk != NULL) {
+		clk_disable(ehci_clocks->usbtll_fck_clk);
+		clk_put(ehci_clocks->usbtll_fck_clk);
+		ehci_clocks->usbtll_fck_clk = NULL;
+	}
+
+	if (ehci_clocks->usbhost_ick_clk != NULL) {
+		clk_disable(ehci_clocks->usbhost_ick_clk);
+		clk_put(ehci_clocks->usbhost_ick_clk);
+		ehci_clocks->usbhost_ick_clk = NULL;
+	}
+
+	if (ehci_clocks->usbhost1_48m_fck_clk != NULL) {
+		clk_disable(ehci_clocks->usbhost1_48m_fck_clk);
+		clk_put(ehci_clocks->usbhost1_48m_fck_clk);
+		ehci_clocks->usbhost1_48m_fck_clk = NULL;
+	}
+
+	if (ehci_clocks->usbhost2_120m_fck_clk != NULL) {
+		clk_disable(ehci_clocks->usbhost2_120m_fck_clk);
+		clk_put(ehci_clocks->usbhost2_120m_fck_clk);
+		ehci_clocks->usbhost2_120m_fck_clk = NULL;
+	}
+
+	if (ehci_clocks->usbtll_ick_clk != NULL) {
+		clk_disable(ehci_clocks->usbtll_ick_clk);
+		clk_put(ehci_clocks->usbtll_ick_clk);
+		ehci_clocks->usbtll_ick_clk = NULL;
+	}
+
+
+#ifdef EXTERNAL_PHY_RESET
+	omap_free_gpio(EXT_PHY_RESET_GPIO_PORT1);
+	omap_free_gpio(EXT_PHY_RESET_GPIO_PORT2);
+#endif
+
+	dev_dbg(hcd->self.controller,
+		": Clock to USB host has been disabled\n");
+}
+
+static const struct hc_driver ehci_omap_hc_driver;
+
+/*-------------------------------------------------------------------------*/
+/* configure so an HC device and id are always provided */
+/* always called with process context; sleeping is OK */
+
+/**
+ * ehci_hcd_omap_drv_probe - initialize TI-based HCDs
+ * Context: !in_interrupt()
+ *
+ * Allocates basic resources for this USB host controller, and
+ * then invokes the start() method for the HCD associated with it
+ * through the hotplug entry's driver_data.
+ *
+ */
+static int ehci_hcd_omap_drv_probe(struct platform_device *dev)
+{
+	int retval = 0;
+	struct usb_hcd *hcd;
+	struct ehci_hcd *ehci;
+
+	dev_dbg(&dev->dev, "ehci_hcd_omap_drv_probe()");
+
+	if (usb_disabled())
+		return -ENODEV;
+
+	if (dev->resource[1].flags != IORESOURCE_IRQ) {
+		dev_dbg(&dev->dev, "resource[1] is not IORESOURCE_IRQ");
+		retval = -ENOMEM;
+	}
+
+	hcd = usb_create_hcd(&ehci_omap_hc_driver, &dev->dev, dev->dev.bus_id);
+	if (!hcd)
+		return -ENOMEM;
+
+	retval = omap_start_ehc(dev, hcd);
+	if (retval)
+		return retval;
+
+	hcd->rsrc_start = 0;
+	hcd->rsrc_len = 0;
+	hcd->rsrc_start = dev->resource[0].start;
+	hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
+
+	hcd->regs = (void __iomem *) (int) IO_ADDRESS(hcd->rsrc_start);
+
+	ehci = hcd_to_ehci(hcd);
+	ehci->caps = hcd->regs;
+
+	ehci->regs = hcd->regs + HC_LENGTH(readl(&ehci->caps->hc_capbase));
+	/* cache this readonly data; minimize chip reads */
+	ehci->hcs_params = readl(&ehci->caps->hcs_params);
+
+	/* SET 1 micro-frame Interrupt interval */
+	writel(readl(&ehci->regs->command) | (1<<16), &ehci->regs->command);
+
+	retval = usb_add_hcd(hcd, dev->resource[1].start,
+				IRQF_DISABLED | IRQF_SHARED);
+	if (retval == 0)
+		return retval;
+
+	dev_dbg(hcd->self.controller, "ERR: add_hcd");
+	omap_stop_ehc(dev, hcd);
+
+	usb_put_hcd(hcd);
+	return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* may be called without controller electrically present */
+/* may be called with controller, bus, and devices active */
+
+/**
+ * ehci_hcd_omap_drv_remove - shutdown processing for EHCI HCDs
+ * @dev: USB Host Controller being removed
+ * Context: !in_interrupt()
+ *
+ * Reverses the effect of usb_ehci_hcd_omap_probe(), first invoking
+ * the HCD's stop() method.  It is always called from a thread
+ * context, normally "rmmod", "apmd", or something similar.
+ *
+ */
+static int ehci_hcd_omap_drv_remove(struct platform_device *dev)
+{
+	struct usb_hcd *hcd = platform_get_drvdata(dev);
+
+	dev_dbg(&dev->dev, "ehci_hcd_omap_drv_remove()");
+
+	usb_remove_hcd(hcd);
+	usb_put_hcd(hcd);
+	omap_stop_ehc(dev, hcd);
+
+	return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_PM
+static int omap_ehci_bus_suspend(struct usb_hcd *hcd)
+{
+	return ehci_bus_suspend(hcd);
+}
+
+static int omap_ehci_bus_resume(struct usb_hcd *hcd)
+{
+	return ehci_bus_resume(hcd);
+}
+#endif
+/*-------------------------------------------------------------------------*/
+
+static const struct hc_driver ehci_omap_hc_driver = {
+	.description = hcd_name,
+	.product_desc = "OMAP-EHCI Host Controller",
+	.hcd_priv_size = sizeof(struct ehci_hcd)
+				+ sizeof(struct ehci_omap_clock_defs),
+
+	/*
+	 * generic hardware linkage
+	 */
+	.irq = ehci_irq,
+	.flags = HCD_MEMORY | HCD_USB2,
+
+	/*
+	 * basic lifecycle operations
+	 */
+	.reset = ehci_init,
+	.start = ehci_run,
+	.stop = ehci_stop,
+	.shutdown = ehci_shutdown,
+
+	/*
+	 * managing i/o requests and associated device resources
+	 */
+	.urb_enqueue = ehci_urb_enqueue,
+	.urb_dequeue = ehci_urb_dequeue,
+	.endpoint_disable = ehci_endpoint_disable,
+
+	/*
+	 * scheduling support
+	 */
+	.get_frame_number = ehci_get_frame,
+
+	/*
+	 * root hub support
+	 */
+	.hub_status_data = ehci_hub_status_data,
+	.hub_control = ehci_hub_control,
+#ifdef	CONFIG_PM
+	.bus_suspend = omap_ehci_bus_suspend,
+	.bus_resume = omap_ehci_bus_resume,
+#endif
+};
+
+/*-------------------------------------------------------------------------*/
+MODULE_ALIAS("omap-ehci");
+static struct platform_driver ehci_hcd_omap_driver = {
+	.probe = ehci_hcd_omap_drv_probe,
+	.remove = ehci_hcd_omap_drv_remove,
+	.shutdown = usb_hcd_platform_shutdown,
+	/*.suspend      = ehci_hcd_omap_drv_suspend, */
+	/*.resume       = ehci_hcd_omap_drv_resume, */
+	.driver = {
+		.name = "ehci-omap",
+		.bus = &platform_bus_type
+	}
+};
diff --git a/drivers/usb/host/ehci-omap.h b/drivers/usb/host/ehci-omap.h
new file mode 100644
index 0000000..9e4378f
--- /dev/null
+++ b/drivers/usb/host/ehci-omap.h
@@ -0,0 +1,125 @@
+/*
+ * ehci-omap.h - register definitions for USBHOST in OMAP 34xx
+ *
+ * Copyright (C) 2007-2008 Texas Instruments, Inc.
+ * 	Author: Vikram Pandita <vikram.pandita@ti.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.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+#ifndef __EHCI_OMAP_H
+#define __EHCI_OMAP_H
+
+#include <mach/hardware.h>
+#include "../../../arch/arm/mach-omap2/cm.h"
+#include "../../../arch/arm/mach-omap2/cm-regbits-34xx.h"
+
+/*
+ * OMAP USBHOST Register addresses: PHYSICAL ADDRESSES
+ * 	Use omap_readl()/omap_writel() functions
+ */
+
+/* USBHOST: TLL, UUH, OHCI, EHCI */
+#define	OMAP_USBHOST_BASE	(L4_34XX_BASE + 0x60000)
+
+/* TLL Register Set */
+#define	OMAP_USBHOST_TLL_BASE	(OMAP_USBHOST_BASE + 0x2000)
+#define	OMAP_USBTLL_REVISION	(OMAP_USBHOST_TLL_BASE + 0x00)
+#define	OMAP_USBTLL_SYSCONFIG	(OMAP_USBHOST_TLL_BASE + 0x10)
+	#define	OMAP_USBTLL_SYSCONFIG_CACTIVITY_SHIFT	8
+	#define	OMAP_USBTLL_SYSCONFIG_SIDLEMODE_SHIFT	3
+	#define	OMAP_USBTLL_SYSCONFIG_ENAWAKEUP_SHIFT	2
+	#define	OMAP_USBTLL_SYSCONFIG_SOFTRESET_SHIFT	1
+	#define	OMAP_USBTLL_SYSCONFIG_AUTOIDLE_SHIFT	0
+#define	OMAP_USBTLL_SYSSTATUS	(OMAP_USBHOST_TLL_BASE + 0x14)
+	#define	OMAP_USBTLL_SYSSTATUS_RESETDONE_SHIFT	0
+#define	OMAP_USBTLL_IRQSTATUS	(OMAP_USBHOST_TLL_BASE + 0x18)
+#define	OMAP_USBTLL_IRQENABLE	(OMAP_USBHOST_TLL_BASE + 0x1C)
+
+#define	OMAP_TLL_SHARED_CONF	(OMAP_USBHOST_TLL_BASE + 0x30)
+	#define	OMAP_TLL_SHARED_CONF_USB_90D_DDR_EN_SHFT	6
+	#define	OMAP_TLL_SHARED_CONF_USB_180D_SDR_EN_SHIFT	5
+	#define	OMAP_TLL_SHARED_CONF_USB_DIVRATION_SHIFT	2
+	#define	OMAP_TLL_SHARED_CONF_FCLK_REQ_SHIFT		1
+	#define	OMAP_TLL_SHARED_CONF_FCLK_IS_ON_SHIFT		0
+
+#define	OMAP_TLL_CHANNEL_CONF(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x040 + 0x004 * num))
+	#define	OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF_SHIFT	11
+	#define	OMAP_TLL_CHANNEL_CONF_ULPI_ULPIAUTOIDLE_SHIFT	10
+	#define	OMAP_TLL_CHANNEL_CONF_UTMIAUTOIDLE_SHIFT	9
+	#define	OMAP_TLL_CHANNEL_CONF_ULPIDDRMODE_SHIFT		8
+	#define	OMAP_TLL_CHANNEL_CONF_CHANEN_SHIFT		0
+
+#define	OMAP_TLL_ULPI_FUNCTION_CTRL(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x804 + 0x100 * num))
+#define	OMAP_TLL_ULPI_INTERFACE_CTRL(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x807 + 0x100 * num))
+#define	OMAP_TLL_ULPI_OTG_CTRL(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x80A + 0x100 * num))
+#define	OMAP_TLL_ULPI_INT_EN_RISE(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x80D + 0x100 * num))
+#define	OMAP_TLL_ULPI_INT_EN_FALL(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x810 + 0x100 * num))
+#define	OMAP_TLL_ULPI_INT_STATUS(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x813 + 0x100 * num))
+#define	OMAP_TLL_ULPI_INT_LATCH(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x814 + 0x100 * num))
+#define	OMAP_TLL_ULPI_DEBUG(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x815 + 0x100 * num))
+#define	OMAP_TLL_ULPI_SCRATCH_REGISTER(num)\
+			(OMAP_USBHOST_TLL_BASE + (0x816 + 0x100 * num))
+
+#define OMAP_TLL_CHANNEL_COUNT		3
+	#define OMAP_TLL_CHANNEL_1_EN_MASK	1
+	#define OMAP_TLL_CHANNEL_2_EN_MASK	2
+	#define OMAP_TLL_CHANNEL_3_EN_MASK	4
+
+/* UHH Register Set */
+#define	OMAP_USBHOST_UHH_BASE	(OMAP_USBHOST_BASE + 0x4000)
+#define	OMAP_UHH_REVISION	(OMAP_USBHOST_UHH_BASE + 0x00)
+#define	OMAP_UHH_SYSCONFIG	(OMAP_USBHOST_UHH_BASE + 0x10)
+	#define	OMAP_UHH_SYSCONFIG_MIDLEMODE_SHIFT	12
+	#define	OMAP_UHH_SYSCONFIG_CACTIVITY_SHIFT	8
+	#define	OMAP_UHH_SYSCONFIG_SIDLEMODE_SHIFT	3
+	#define	OMAP_UHH_SYSCONFIG_ENAWAKEUP_SHIFT	2
+	#define	OMAP_UHH_SYSCONFIG_SOFTRESET_SHIFT	1
+	#define	OMAP_UHH_SYSCONFIG_AUTOIDLE_SHIFT	0
+
+#define	OMAP_UHH_SYSSTATUS	(OMAP_USBHOST_UHH_BASE + 0x14)
+#define	OMAP_UHH_HOSTCONFIG	(OMAP_USBHOST_UHH_BASE + 0x40)
+	#define	OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT	0
+	#define OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN_SHIFT	2
+	#define OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN_SHIFT	3
+	#define OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN_SHIFT	4
+	#define OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN_SHIFT	5
+
+#define	OMAP_UHH_DEBUG_CSR	(OMAP_USBHOST_UHH_BASE + 0x44)
+
+/* EHCI Register Set */
+#define	OMAP_USBHOST_EHCI_BASE	(OMAP_USBHOST_BASE + 0x4800)
+#define	EHCI_INSNREG05_ULPI		(OMAP_USBHOST_EHCI_BASE + 0xA4)
+	#define	EHCI_INSNREG05_ULPI_CONTROL_SHIFT	31
+	#define	EHCI_INSNREG05_ULPI_PORTSEL_SHIFT	24
+	#define	EHCI_INSNREG05_ULPI_OPSEL_SHIFT		22
+	#define	EHCI_INSNREG05_ULPI_REGADD_SHIFT	16
+	#define	EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT	8
+	#define	EHCI_INSNREG05_ULPI_WRDATA_SHIFT	0
+
+/* OHCI Register Set */
+#define	OMAP_USBHOST_OHCI_BASE	(OMAP_USBHOST_BASE + 0x4400)
+
+#endif/* __EHCI_OMAP_H*/
-- 
1.6.0.1.141.g445ca


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

* [PATCH 30/33] add omap backlight support
  2008-08-30 17:16                                                         ` [PATCH 29/33] add omap ehci bus glue Felipe Balbi
@ 2008-08-30 17:16                                                           ` Felipe Balbi
  2008-08-30 17:16                                                             ` [PATCH 31/33] add several omap lcd drivers Felipe Balbi
  2008-08-31 21:12                                                             ` [PATCH 30/33] add omap backlight support David Brownell
  2008-08-31 21:09                                                           ` [PATCH 29/33] add omap ehci bus glue David Brownell
  2008-09-01  5:55                                                           ` Gadiyar, Anand
  2 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/video/backlight/Kconfig   |    9 ++
 drivers/video/backlight/Makefile  |    1 +
 drivers/video/backlight/omap_bl.c |  218 +++++++++++++++++++++++++++++++++++++
 3 files changed, 228 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/backlight/omap_bl.c

diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig
index 452b770..6b8cb23 100644
--- a/drivers/video/backlight/Kconfig
+++ b/drivers/video/backlight/Kconfig
@@ -133,6 +133,15 @@ config BACKLIGHT_HP680
 	  If you have a HP Jornada 680, say y to enable the
 	  backlight driver.
 
+config BACKLIGHT_OMAP
+	tristate "OMAP LCD Backlight"
+	depends on BACKLIGHT_CLASS_DEVICE && (ARCH_OMAP1 || ARCH_OMAP2)
+	default y
+	help
+	  This driver controls the LCD backlight level and power
+	  for the PWL module of OMAP processors.  Say Y if you plan
+	  to use power saving.
+
 config BACKLIGHT_PROGEAR
 	tristate "Frontpath ProGear Backlight Driver"
 	depends on BACKLIGHT_CLASS_DEVICE && PCI && X86
diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile
index b405aac..a317041 100644
--- a/drivers/video/backlight/Makefile
+++ b/drivers/video/backlight/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_BACKLIGHT_CORGI)	+= corgi_bl.o
 obj-$(CONFIG_BACKLIGHT_HP680)	+= hp680_bl.o
 obj-$(CONFIG_BACKLIGHT_LOCOMO)	+= locomolcd.o
 obj-$(CONFIG_BACKLIGHT_OMAP1)	+= omap1_bl.o
+obj-$(CONFIG_BACKLIGHT_OMAP)	+= omap_bl.o
 obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o
 obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o
 obj-$(CONFIG_BACKLIGHT_PWM)	+= pwm_bl.o
diff --git a/drivers/video/backlight/omap_bl.c b/drivers/video/backlight/omap_bl.c
new file mode 100644
index 0000000..eb9b5ba
--- /dev/null
+++ b/drivers/video/backlight/omap_bl.c
@@ -0,0 +1,218 @@
+/*
+ * drivers/video/backlight/omap_bl.c
+ *
+ * Backlight driver for OMAP based boards.
+ *
+ * Copyright (c) 2006 Andrzej Zaborowski  <balrog@zabor.org>
+ *
+ * This package 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.
+ *
+ * This package is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this package; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/fb.h>
+#include <linux/backlight.h>
+
+#include <mach/hardware.h>
+#include <mach/board.h>
+#include <mach/mux.h>
+
+#define OMAPBL_MAX_INTENSITY		0xff
+
+struct omap_backlight {
+	int powermode;
+	int current_intensity;
+
+	struct device *dev;
+	struct omap_backlight_config *pdata;
+};
+
+static void inline omapbl_send_intensity(int intensity)
+{
+	omap_writeb(intensity, OMAP_PWL_ENABLE);
+}
+
+static void inline omapbl_send_enable(int enable)
+{
+	omap_writeb(enable, OMAP_PWL_CLK_ENABLE);
+}
+
+static void omapbl_blank(struct omap_backlight *bl, int mode)
+{
+	if (bl->pdata->set_power)
+		bl->pdata->set_power(bl->dev, mode);
+
+	switch (mode) {
+	case FB_BLANK_NORMAL:
+	case FB_BLANK_VSYNC_SUSPEND:
+	case FB_BLANK_HSYNC_SUSPEND:
+	case FB_BLANK_POWERDOWN:
+		omapbl_send_intensity(0);
+		omapbl_send_enable(0);
+		break;
+
+	case FB_BLANK_UNBLANK:
+		omapbl_send_intensity(bl->current_intensity);
+		omapbl_send_enable(1);
+		break;
+	}
+}
+
+#ifdef CONFIG_PM
+static int omapbl_suspend(struct platform_device *pdev, pm_message_t state)
+{
+	struct backlight_device *dev = platform_get_drvdata(pdev);
+	struct omap_backlight *bl = dev_get_drvdata(&dev->dev);
+
+	omapbl_blank(bl, FB_BLANK_POWERDOWN);
+	return 0;
+}
+
+static int omapbl_resume(struct platform_device *pdev)
+{
+	struct backlight_device *dev = platform_get_drvdata(pdev);
+	struct omap_backlight *bl = dev_get_drvdata(&dev->dev);
+
+	omapbl_blank(bl, bl->powermode);
+	return 0;
+}
+#else
+#define omapbl_suspend	NULL
+#define omapbl_resume	NULL
+#endif
+
+static int omapbl_set_power(struct backlight_device *dev, int state)
+{
+	struct omap_backlight *bl = dev_get_drvdata(&dev->dev);
+
+	omapbl_blank(bl, state);
+	bl->powermode = state;
+
+	return 0;
+}
+
+static int omapbl_update_status(struct backlight_device *dev)
+{
+	struct omap_backlight *bl = dev_get_drvdata(&dev->dev);
+
+	if (bl->current_intensity != dev->props.brightness) {
+		if (dev->props.brightness < 0)
+			return -EPERM;	/* Leave current_intensity untouched */
+
+		if (bl->powermode == FB_BLANK_UNBLANK)
+			omapbl_send_intensity(dev->props.brightness);
+		bl->current_intensity = dev->props.brightness;
+	}
+
+	if (dev->props.fb_blank != bl->powermode)
+		omapbl_set_power(dev, dev->props.fb_blank);
+
+	return 0;
+}
+
+
+static int omapbl_get_intensity(struct backlight_device *dev)
+{
+	struct omap_backlight *bl = dev_get_drvdata(&dev->dev);
+	return bl->current_intensity;
+}
+
+static struct backlight_ops omapbl_ops = {
+	.get_brightness = omapbl_get_intensity,
+	.update_status  = omapbl_update_status,
+};
+
+
+static int omapbl_probe(struct platform_device *pdev)
+{
+	struct backlight_device *dev;
+	struct omap_backlight *bl;
+	struct omap_backlight_config *pdata = pdev->dev.platform_data;
+
+	if (!pdata)
+		return -ENXIO;
+
+	omapbl_ops.check_fb = pdata->check_fb;
+
+	bl = kzalloc(sizeof(struct omap_backlight), GFP_KERNEL);
+	if (unlikely(!bl))
+		return -ENOMEM;
+
+	dev = backlight_device_register("omap-bl", &pdev->dev,
+			bl, &omapbl_ops);
+	if (IS_ERR(dev)) {
+		kfree(bl);
+		return PTR_ERR(dev);
+	}
+
+	bl->powermode = FB_BLANK_POWERDOWN;
+	bl->current_intensity = 0;
+
+	bl->pdata = pdata;
+	bl->dev = &pdev->dev;
+
+	platform_set_drvdata(pdev, dev);
+
+	omap_cfg_reg(PWL);	/* Conflicts with UART3 */
+
+	dev->props.fb_blank = FB_BLANK_UNBLANK;
+	dev->props.max_brightness = OMAPBL_MAX_INTENSITY;
+	dev->props.brightness = pdata->default_intensity;
+	omapbl_update_status(dev);
+
+	printk(KERN_INFO "OMAP LCD backlight initialised\n");
+
+	return 0;
+}
+
+static int omapbl_remove(struct platform_device *pdev)
+{
+	struct backlight_device *dev = platform_get_drvdata(pdev);
+	struct omap_backlight *bl = dev_get_drvdata(&dev->dev);
+
+	backlight_device_unregister(dev);
+	kfree(bl);
+
+	return 0;
+}
+
+static struct platform_driver omapbl_driver = {
+	.probe		= omapbl_probe,
+	.remove		= omapbl_remove,
+	.suspend	= omapbl_suspend,
+	.resume		= omapbl_resume,
+	.driver		= {
+		.name	= "omap-bl",
+	},
+};
+
+static int __init omapbl_init(void)
+{
+	return platform_driver_register(&omapbl_driver);
+}
+
+static void __exit omapbl_exit(void)
+{
+	platform_driver_unregister(&omapbl_driver);
+}
+
+module_init(omapbl_init);
+module_exit(omapbl_exit);
+
+MODULE_AUTHOR("Andrzej Zaborowski <balrog@zabor.org>");
+MODULE_DESCRIPTION("OMAP LCD Backlight driver");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 31/33] add several omap lcd drivers
  2008-08-30 17:16                                                           ` [PATCH 30/33] add omap backlight support Felipe Balbi
@ 2008-08-30 17:16                                                             ` Felipe Balbi
  2008-08-30 17:16                                                               ` [PATCH 32/33] add omap 1-wire interface driver Felipe Balbi
  2008-08-31 21:12                                                             ` [PATCH 30/33] add omap backlight support David Brownell
  1 sibling, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/video/omap/Kconfig           |   18 +
 drivers/video/omap/Makefile          |   11 +
 drivers/video/omap/lcd_2430sdp.c     |  182 ++++++++++
 drivers/video/omap/lcd_ams_delta.c   |  140 ++++++++
 drivers/video/omap/lcd_apollon.c     |  137 ++++++++
 drivers/video/omap/lcd_h2.c          |  155 +++++++++
 drivers/video/omap/lcd_mipid.c       |  617 ++++++++++++++++++++++++++++++++++
 drivers/video/omap/lcd_omap2evm.c    |  195 +++++++++++
 drivers/video/omap/lcd_omap3beagle.c |  133 ++++++++
 drivers/video/omap/lcd_omap3evm.c    |  197 +++++++++++
 drivers/video/omap/lcd_p2.c          |  342 +++++++++++++++++++
 11 files changed, 2127 insertions(+), 0 deletions(-)
 create mode 100644 drivers/video/omap/lcd_2430sdp.c
 create mode 100644 drivers/video/omap/lcd_ams_delta.c
 create mode 100644 drivers/video/omap/lcd_apollon.c
 create mode 100644 drivers/video/omap/lcd_h2.c
 create mode 100644 drivers/video/omap/lcd_mipid.c
 create mode 100644 drivers/video/omap/lcd_omap2evm.c
 create mode 100644 drivers/video/omap/lcd_omap3beagle.c
 create mode 100644 drivers/video/omap/lcd_omap3evm.c
 create mode 100644 drivers/video/omap/lcd_p2.c

diff --git a/drivers/video/omap/Kconfig b/drivers/video/omap/Kconfig
index 4440885..8f4c27b 100644
--- a/drivers/video/omap/Kconfig
+++ b/drivers/video/omap/Kconfig
@@ -56,3 +56,21 @@ config FB_OMAP_LCDC_BLIZZARD
 	help
 	  Say Y here if you want to have support for the external
 	  Epson Blizzard LCD controller.
+
+config FB_OMAP_MANUAL_UPDATE
+	bool "Default to manual update mode"
+	depends on FB_OMAP && FB_OMAP_LCDC_EXTERNAL
+	help
+	  Say Y here, if your user-space applications are capable of
+	  notifying the frame buffer driver when a change has occured in
+          the frame buffer content and thus a reload of the image data to
+	  the external frame buffer is required. If unsure, say N.
+
+config FB_OMAP_LCD_MIPID
+	bool "MIPI DBI-C/DCS compatible LCD support"
+	depends on FB_OMAP && SPI_MASTER && CBUS_TAHVO
+	help
+	  Say Y here if you want to have support for LCDs compatible with
+	  the Mobile Industry Processor Interface DBI-C/DCS
+	  specification. (Supported LCDs: Philips LPH8923, Sharp LS041Y3)
+
diff --git a/drivers/video/omap/Makefile b/drivers/video/omap/Makefile
index 99da8b6..f9b5cd4 100644
--- a/drivers/video/omap/Makefile
+++ b/drivers/video/omap/Makefile
@@ -8,6 +8,7 @@ objs-yy := omapfb_main.o
 
 objs-y$(CONFIG_ARCH_OMAP1) += lcdc.o
 objs-y$(CONFIG_ARCH_OMAP2) += dispc.o
+objs-y$(CONFIG_ARCH_OMAP3) += dispc.o
 
 objs-$(CONFIG_ARCH_OMAP1)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += sossi.o
 objs-$(CONFIG_ARCH_OMAP2)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += rfbi.o
@@ -15,8 +16,10 @@ objs-$(CONFIG_ARCH_OMAP2)$(CONFIG_FB_OMAP_LCDC_EXTERNAL) += rfbi.o
 objs-y$(CONFIG_FB_OMAP_LCDC_HWA742) += hwa742.o
 objs-y$(CONFIG_FB_OMAP_LCDC_BLIZZARD) += blizzard.o
 
+objs-y$(CONFIG_MACH_AMS_DELTA) += lcd_ams_delta.o
 objs-y$(CONFIG_MACH_OMAP_H4) += lcd_h4.o
 objs-y$(CONFIG_MACH_OMAP_H3) += lcd_h3.o
+objs-y$(CONFIG_MACH_OMAP_H2) += lcd_h2.o
 objs-y$(CONFIG_MACH_OMAP_PALMTE) += lcd_palmte.o
 objs-y$(CONFIG_MACH_OMAP_PALMTT) += lcd_palmtt.o
 objs-y$(CONFIG_MACH_OMAP_PALMZ71) += lcd_palmz71.o
@@ -25,5 +28,13 @@ objs-$(CONFIG_ARCH_OMAP15XX)$(CONFIG_MACH_OMAP_INNOVATOR) += lcd_inn1510.o
 objs-y$(CONFIG_MACH_OMAP_OSK) += lcd_osk.o
 objs-y$(CONFIG_MACH_SX1) += lcd_sx1.o
 
+objs-y$(CONFIG_MACH_OMAP_APOLLON) += lcd_apollon.o
+objs-y$(CONFIG_MACH_OMAP_2430SDP) += lcd_2430sdp.o
+objs-y$(CONFIG_MACH_OMAP_3430SDP) += lcd_2430sdp.o
+objs-y$(CONFIG_MACH_OMAP2EVM) += lcd_omap2evm.o
+objs-y$(CONFIG_MACH_OMAP3EVM) += lcd_omap3evm.o
+objs-y$(CONFIG_MACH_OMAP3_BEAGLE) += lcd_omap3beagle.o
+objs-y$(CONFIG_FB_OMAP_LCD_MIPID) += lcd_mipid.o
+
 omapfb-objs := $(objs-yy)
 
diff --git a/drivers/video/omap/lcd_2430sdp.c b/drivers/video/omap/lcd_2430sdp.c
new file mode 100644
index 0000000..9af6cd0
--- /dev/null
+++ b/drivers/video/omap/lcd_2430sdp.c
@@ -0,0 +1,182 @@
+/*
+ * LCD panel support for the TI 2430SDP board
+ *
+ * Copyright (C) 2007 MontaVista
+ * Author: Hunyue Yau <hyau@mvista.com>
+ *
+ * Derived from drivers/video/omap/lcd-apollon.c
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+
+#include <mach/gpio.h>
+#include <mach/mux.h>
+#include <mach/omapfb.h>
+#include <asm/mach-types.h>
+
+#define SDP2430_LCD_PANEL_BACKLIGHT_GPIO	91
+#define SDP2430_LCD_PANEL_ENABLE_GPIO		154
+#define SDP3430_LCD_PANEL_BACKLIGHT_GPIO 	24
+#define SDP3430_LCD_PANEL_ENABLE_GPIO 		28
+
+static unsigned backlight_gpio;
+static unsigned enable_gpio;
+
+#define LCD_PIXCLOCK_MAX		5400 /* freq 5.4 MHz */
+#define PM_RECEIVER             TWL4030_MODULE_PM_RECEIVER
+#define ENABLE_VAUX2_DEDICATED  0x09
+#define ENABLE_VAUX2_DEV_GRP    0x20
+#define ENABLE_VAUX3_DEDICATED	0x03
+#define ENABLE_VAUX3_DEV_GRP	0x20
+
+
+#define t2_out(c, r, v) twl4030_i2c_write_u8(c, r, v)
+
+
+static int sdp2430_panel_init(struct lcd_panel *panel,
+				struct omapfb_device *fbdev)
+{
+	if (machine_is_omap_3430sdp()) {
+		enable_gpio    = SDP3430_LCD_PANEL_ENABLE_GPIO;
+		backlight_gpio = SDP3430_LCD_PANEL_BACKLIGHT_GPIO;
+	} else {
+		enable_gpio    = SDP2430_LCD_PANEL_ENABLE_GPIO;
+		backlight_gpio = SDP2430_LCD_PANEL_BACKLIGHT_GPIO;
+	}
+
+	omap_request_gpio(enable_gpio);			/* LCD panel */
+	omap_request_gpio(backlight_gpio);		/* LCD backlight */
+	omap_set_gpio_direction(enable_gpio, 0);	/* output */
+	omap_set_gpio_direction(backlight_gpio, 0);	/* output */
+
+	return 0;
+}
+
+static void sdp2430_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int sdp2430_panel_enable(struct lcd_panel *panel)
+{
+	u8 ded_val, ded_reg;
+	u8 grp_val, grp_reg;
+
+	if (machine_is_omap_3430sdp()) {
+		ded_reg = TWL4030_VAUX3_DEDICATED;
+		ded_val = ENABLE_VAUX3_DEDICATED;
+		grp_reg = TWL4030_VAUX3_DEV_GRP;
+		grp_val = ENABLE_VAUX3_DEV_GRP;
+	} else {
+		ded_reg = TWL4030_VAUX2_DEDICATED;
+		ded_val = ENABLE_VAUX2_DEDICATED;
+		grp_reg = TWL4030_VAUX2_DEV_GRP;
+		grp_val = ENABLE_VAUX2_DEV_GRP;
+	}
+		
+	omap_set_gpio_dataout(enable_gpio, 1);
+	omap_set_gpio_dataout(backlight_gpio, 1);
+
+	if (0 != t2_out(PM_RECEIVER, ded_val, ded_reg))
+		return -EIO;
+	if (0 != t2_out(PM_RECEIVER, grp_val, grp_reg))
+		return -EIO;
+
+	return 0;
+}
+
+static void sdp2430_panel_disable(struct lcd_panel *panel)
+{
+	omap_set_gpio_dataout(enable_gpio, 0);
+	omap_set_gpio_dataout(backlight_gpio, 0);
+}
+
+static unsigned long sdp2430_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+struct lcd_panel sdp2430_panel = {
+	.name		= "sdp2430",
+	.config		= OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
+			  OMAP_LCDC_INV_HSYNC,
+
+	.bpp		= 16,
+	.data_lines	= 16,
+	.x_res		= 240,
+	.y_res		= 320,
+	.hsw		= 3,		/* hsync_len (4) - 1 */
+	.hfp		= 3,		/* right_margin (4) - 1 */
+	.hbp		= 39,		/* left_margin (40) - 1 */
+	.vsw		= 1,		/* vsync_len (2) - 1 */
+	.vfp		= 2,		/* lower_margin */
+	.vbp		= 7,		/* upper_margin (8) - 1 */
+
+	.pixel_clock	= LCD_PIXCLOCK_MAX,
+
+	.init		= sdp2430_panel_init,
+	.cleanup	= sdp2430_panel_cleanup,
+	.enable		= sdp2430_panel_enable,
+	.disable	= sdp2430_panel_disable,
+	.get_caps	= sdp2430_panel_get_caps,
+};
+
+static int sdp2430_panel_probe(struct platform_device *pdev)
+{
+	omapfb_register_panel(&sdp2430_panel);
+	return 0;
+}
+
+static int sdp2430_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int sdp2430_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+	return 0;
+}
+
+static int sdp2430_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver sdp2430_panel_driver = {
+	.probe		= sdp2430_panel_probe,
+	.remove		= sdp2430_panel_remove,
+	.suspend	= sdp2430_panel_suspend,
+	.resume		= sdp2430_panel_resume,
+	.driver		= {
+		.name	= "sdp2430_lcd",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init sdp2430_panel_drv_init(void)
+{
+	return platform_driver_register(&sdp2430_panel_driver);
+}
+
+static void __exit sdp2430_panel_drv_exit(void)
+{
+	platform_driver_unregister(&sdp2430_panel_driver);
+}
+
+module_init(sdp2430_panel_drv_init);
+module_exit(sdp2430_panel_drv_exit);
diff --git a/drivers/video/omap/lcd_ams_delta.c b/drivers/video/omap/lcd_ams_delta.c
new file mode 100644
index 0000000..3fd5342
--- /dev/null
+++ b/drivers/video/omap/lcd_ams_delta.c
@@ -0,0 +1,140 @@
+/*
+ * File: drivers/video/omap/lcd_ams_delta.c
+ *
+ * Based on drivers/video/omap/lcd_inn1510.c
+ *
+ * LCD panel support for the Amstrad E3 (Delta) videophone.
+ *
+ * Copyright (C) 2006 Jonathan McDowell <noodles@earth.li>
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <asm/delay.h>
+#include <asm/io.h>
+
+#include <mach/board-ams-delta.h>
+#include <mach/hardware.h>
+#include <mach/omapfb.h>
+
+#define AMS_DELTA_DEFAULT_CONTRAST	112
+
+static int ams_delta_panel_init(struct lcd_panel *panel,
+		struct omapfb_device *fbdev)
+{
+	return 0;
+}
+
+static void ams_delta_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int ams_delta_panel_enable(struct lcd_panel *panel)
+{
+	ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP,
+			AMS_DELTA_LATCH2_LCD_NDISP);
+	ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN,
+			AMS_DELTA_LATCH2_LCD_VBLEN);
+
+	omap_writeb(1, OMAP_PWL_CLK_ENABLE);
+	omap_writeb(AMS_DELTA_DEFAULT_CONTRAST, OMAP_PWL_ENABLE);
+
+	return 0;
+}
+
+static void ams_delta_panel_disable(struct lcd_panel *panel)
+{
+	ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_VBLEN, 0);
+	ams_delta_latch2_write(AMS_DELTA_LATCH2_LCD_NDISP, 0);
+}
+
+static unsigned long ams_delta_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+static struct lcd_panel ams_delta_panel = {
+	.name		= "ams-delta",
+	.config		= 0,
+
+	.bpp		= 12,
+	.data_lines	= 16,
+	.x_res		= 480,
+	.y_res		= 320,
+	.pixel_clock	= 4687,
+	.hsw		= 3,
+	.hfp		= 1,
+	.hbp		= 1,
+	.vsw		= 1,
+	.vfp		= 0,
+	.vbp		= 0,
+	.pcd		= 0,
+	.acb		= 37,
+
+	.init		= ams_delta_panel_init,
+	.cleanup	= ams_delta_panel_cleanup,
+	.enable		= ams_delta_panel_enable,
+	.disable	= ams_delta_panel_disable,
+	.get_caps	= ams_delta_panel_get_caps,
+};
+
+static int ams_delta_panel_probe(struct platform_device *pdev)
+{
+	omapfb_register_panel(&ams_delta_panel);
+	return 0;
+}
+
+static int ams_delta_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int ams_delta_panel_suspend(struct platform_device *pdev,
+		pm_message_t mesg)
+{
+	return 0;
+}
+
+static int ams_delta_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver ams_delta_panel_driver = {
+	.probe		= ams_delta_panel_probe,
+	.remove		= ams_delta_panel_remove,
+	.suspend	= ams_delta_panel_suspend,
+	.resume		= ams_delta_panel_resume,
+	.driver		= {
+		.name	= "lcd_ams_delta",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int ams_delta_panel_drv_init(void)
+{
+	return platform_driver_register(&ams_delta_panel_driver);
+}
+
+static void ams_delta_panel_drv_cleanup(void)
+{
+	platform_driver_unregister(&ams_delta_panel_driver);
+}
+
+module_init(ams_delta_panel_drv_init);
+module_exit(ams_delta_panel_drv_cleanup);
diff --git a/drivers/video/omap/lcd_apollon.c b/drivers/video/omap/lcd_apollon.c
new file mode 100644
index 0000000..beae5d9
--- /dev/null
+++ b/drivers/video/omap/lcd_apollon.c
@@ -0,0 +1,137 @@
+/*
+ * LCD panel support for the Samsung OMAP2 Apollon board
+ *
+ * Copyright (C) 2005,2006 Samsung Electronics
+ * Author: Kyungmin Park <kyungmin.park@samsung.com>
+ *
+ * Derived from drivers/video/omap/lcd-h4.c
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#include <mach/gpio.h>
+#include <mach/mux.h>
+#include <mach/omapfb.h>
+
+/* #define USE_35INCH_LCD 1 */
+
+static int apollon_panel_init(struct lcd_panel *panel,
+				struct omapfb_device *fbdev)
+{
+	/* configure LCD PWR_EN */
+	omap_cfg_reg(M21_242X_GPIO11);
+	return 0;
+}
+
+static void apollon_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int apollon_panel_enable(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+static void apollon_panel_disable(struct lcd_panel *panel)
+{
+}
+
+static unsigned long apollon_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+struct lcd_panel apollon_panel = {
+	.name		= "apollon",
+	.config		= OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
+			  OMAP_LCDC_INV_HSYNC,
+
+	.bpp		= 16,
+	.data_lines	= 18,
+#ifdef USE_35INCH_LCD
+	.x_res		= 240,
+	.y_res		= 320,
+	.hsw		= 2,
+	.hfp		= 3,
+	.hbp		= 9,
+	.vsw		= 4,
+	.vfp		= 3,
+	.vbp		= 5,
+#else
+	.x_res		= 480,
+	.y_res		= 272,
+	.hsw		= 41,
+	.hfp		= 2,
+	.hbp		= 2,
+	.vsw		= 10,
+	.vfp		= 2,
+	.vbp		= 2,
+#endif
+	.pixel_clock	= 6250,
+
+	.init		= apollon_panel_init,
+	.cleanup	= apollon_panel_cleanup,
+	.enable		= apollon_panel_enable,
+	.disable	= apollon_panel_disable,
+	.get_caps	= apollon_panel_get_caps,
+};
+
+static int apollon_panel_probe(struct platform_device *pdev)
+{
+	omapfb_register_panel(&apollon_panel);
+	return 0;
+}
+
+static int apollon_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int apollon_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+	return 0;
+}
+
+static int apollon_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver apollon_panel_driver = {
+	.probe		= apollon_panel_probe,
+	.remove		= apollon_panel_remove,
+	.suspend	= apollon_panel_suspend,
+	.resume		= apollon_panel_resume,
+	.driver		= {
+		.name	= "apollon_lcd",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init apollon_panel_drv_init(void)
+{
+	return platform_driver_register(&apollon_panel_driver);
+}
+
+static void __exit apollon_panel_drv_exit(void)
+{
+	platform_driver_unregister(&apollon_panel_driver);
+}
+
+module_init(apollon_panel_drv_init);
+module_exit(apollon_panel_drv_exit);
diff --git a/drivers/video/omap/lcd_h2.c b/drivers/video/omap/lcd_h2.c
new file mode 100644
index 0000000..96b4816
--- /dev/null
+++ b/drivers/video/omap/lcd_h2.c
@@ -0,0 +1,155 @@
+/*
+ * LCD panel support for the TI OMAP H2 board
+ *
+ * Copyright (C) 2004 Nokia Corporation
+ * Author: Imre Deak <imre.deak@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/spi/tsc2101.h>
+
+#include <mach/mux.h>
+#include <mach/omapfb.h>
+
+static struct {
+	struct platform_device	*lcd_dev;
+	struct spi_device	*tsc2101_dev;
+} h2_panel_dev;
+
+static int h2_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
+{
+	return 0;
+}
+
+static void h2_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int h2_panel_enable(struct lcd_panel *panel)
+{
+	int r;
+
+	/*
+	 * Assert LCD_EN, BKLIGHT_EN pins on LCD panel
+	 * page2, GPIO config reg, GPIO(0,1) to out and asserted
+	 */
+	r = tsc2101_write_sync(h2_panel_dev.tsc2101_dev, 2, 0x23, 0xcc00);
+	if (r < 0)
+		dev_err(&h2_panel_dev.lcd_dev->dev,
+			"failed to enable LCD panel\n");
+
+	return r;
+}
+
+static void h2_panel_disable(struct lcd_panel *panel)
+{
+	/*
+	 * Deassert LCD_EN and BKLIGHT_EN pins on LCD panel
+	 * page2, GPIO config reg, GPIO(0,1) to out and deasserted
+	 */
+	if (tsc2101_write_sync(h2_panel_dev.tsc2101_dev, 2, 0x23, 0x8800))
+		dev_err(&h2_panel_dev.lcd_dev->dev,
+			"failed to disable LCD panel\n");
+}
+
+static unsigned long h2_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+struct lcd_panel h2_panel = {
+	.name		= "h2",
+	.config		= OMAP_LCDC_PANEL_TFT,
+
+	.bpp		= 16,
+	.data_lines	= 16,
+	.x_res		= 240,
+	.y_res		= 320,
+	.pixel_clock	= 5000,
+	.hsw		= 12,
+	.hfp		= 12,
+	.hbp		= 46,
+	.vsw		= 1,
+	.vfp		= 1,
+	.vbp		= 0,
+
+	.init		= h2_panel_init,
+	.cleanup	= h2_panel_cleanup,
+	.enable		= h2_panel_enable,
+	.disable	= h2_panel_disable,
+	.get_caps	= h2_panel_get_caps,
+};
+
+static int h2_panel_probe(struct platform_device *pdev)
+{
+	struct spi_device *tsc2101;
+
+	tsc2101 = pdev->dev.platform_data;
+	if (tsc2101 == NULL) {
+		dev_err(&pdev->dev, "no platform data\n");
+		return -ENODEV;
+	}
+	if (strncmp(tsc2101->modalias, "tsc2101", 8) != 0) {
+		dev_err(&pdev->dev, "tsc2101 not found\n");
+		return -EINVAL;
+	}
+	h2_panel_dev.lcd_dev = pdev;
+	h2_panel_dev.tsc2101_dev = tsc2101;
+	omapfb_register_panel(&h2_panel);
+	return 0;
+}
+
+static int h2_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int h2_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+	return 0;
+}
+
+static int h2_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver h2_panel_driver = {
+	.probe		= h2_panel_probe,
+	.remove		= h2_panel_remove,
+	.suspend	= h2_panel_suspend,
+	.resume		= h2_panel_resume,
+	.driver		= {
+		.name	= "lcd_h2",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int h2_panel_drv_init(void)
+{
+	return platform_driver_register(&h2_panel_driver);
+}
+
+static void h2_panel_drv_cleanup(void)
+{
+	platform_driver_unregister(&h2_panel_driver);
+}
+
+module_init(h2_panel_drv_init);
+module_exit(h2_panel_drv_cleanup);
+
diff --git a/drivers/video/omap/lcd_mipid.c b/drivers/video/omap/lcd_mipid.c
new file mode 100644
index 0000000..1895997
--- /dev/null
+++ b/drivers/video/omap/lcd_mipid.c
@@ -0,0 +1,617 @@
+/*
+ * LCD driver for MIPI DBI-C / DCS compatible LCDs
+ *
+ * Copyright (C) 2006 Nokia Corporation
+ * Author: Imre Deak <imre.deak@nokia.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/spi/spi.h>
+
+#include <mach/omapfb.h>
+#include <mach/lcd_mipid.h>
+
+#include "../../cbus/tahvo.h"
+
+#define MIPID_MODULE_NAME		"lcd_mipid"
+
+#define MIPID_CMD_READ_DISP_ID		0x04
+#define MIPID_CMD_READ_RED		0x06
+#define MIPID_CMD_READ_GREEN		0x07
+#define MIPID_CMD_READ_BLUE		0x08
+#define MIPID_CMD_READ_DISP_STATUS	0x09
+#define MIPID_CMD_RDDSDR		0x0F
+#define MIPID_CMD_SLEEP_IN		0x10
+#define MIPID_CMD_SLEEP_OUT		0x11
+#define MIPID_CMD_DISP_OFF		0x28
+#define MIPID_CMD_DISP_ON		0x29
+
+#define MIPID_VER_LPH8923		3
+#define MIPID_VER_LS041Y3		4
+
+#define MIPID_ESD_CHECK_PERIOD		msecs_to_jiffies(5000)
+
+#define to_mipid_device(p)		container_of(p, struct mipid_device, \
+						panel)
+struct mipid_device {
+	int		enabled;
+	int		model;
+	int		revision;
+	u8		display_id[3];
+	unsigned int	saved_bklight_level;
+	unsigned long	hw_guard_end;		/* next value of jiffies
+						   when we can issue the
+						   next sleep in/out command */
+	unsigned long	hw_guard_wait;		/* max guard time in jiffies */
+
+	struct omapfb_device	*fbdev;
+	struct spi_device	*spi;
+	struct mutex		mutex;
+	struct lcd_panel	panel;
+
+	struct workqueue_struct	*esd_wq;
+	struct delayed_work	esd_work;
+	void			(*esd_check)(struct mipid_device *m);
+};
+
+static void mipid_transfer(struct mipid_device *md, int cmd, const u8 *wbuf,
+			   int wlen, u8 *rbuf, int rlen)
+{
+	struct spi_message	m;
+	struct spi_transfer	*x, xfer[4];
+	u16			w;
+	int			r;
+
+	BUG_ON(md->spi == NULL);
+
+	spi_message_init(&m);
+
+	memset(xfer, 0, sizeof(xfer));
+	x = &xfer[0];
+
+	cmd &=  0xff;
+	x->tx_buf	= &cmd;
+	x->bits_per_word= 9;
+	x->len		= 2;
+	spi_message_add_tail(x, &m);
+
+	if (wlen) {
+		x++;
+		x->tx_buf	= wbuf;
+		x->len		= wlen;
+		x->bits_per_word= 9;
+		spi_message_add_tail(x, &m);
+	}
+
+	if (rlen) {
+		x++;
+		x->rx_buf	= &w;
+		x->len		= 1;
+		spi_message_add_tail(x, &m);
+
+		if (rlen > 1) {
+			/* Arrange for the extra clock before the first
+			 * data bit.
+			 */
+			x->bits_per_word = 9;
+			x->len		 = 2;
+
+			x++;
+			x->rx_buf	 = &rbuf[1];
+			x->len		 = rlen - 1;
+			spi_message_add_tail(x, &m);
+		}
+	}
+
+	r = spi_sync(md->spi, &m);
+	if (r < 0)
+		dev_dbg(&md->spi->dev, "spi_sync %d\n", r);
+
+	if (rlen)
+		rbuf[0] = w & 0xff;
+}
+
+static inline void mipid_cmd(struct mipid_device *md, int cmd)
+{
+	mipid_transfer(md, cmd, NULL, 0, NULL, 0);
+}
+
+static inline void mipid_write(struct mipid_device *md,
+			       int reg, const u8 *buf, int len)
+{
+	mipid_transfer(md, reg, buf, len, NULL, 0);
+}
+
+static inline void mipid_read(struct mipid_device *md,
+			      int reg, u8 *buf, int len)
+{
+	mipid_transfer(md, reg, NULL, 0, buf, len);
+}
+
+static void set_data_lines(struct mipid_device *md, int data_lines)
+{
+	u16 par;
+
+	switch (data_lines) {
+	case 16:
+		par = 0x150;
+		break;
+	case 18:
+		par = 0x160;
+		break;
+	case 24:
+		par = 0x170;
+		break;
+	}
+	mipid_write(md, 0x3a, (u8 *)&par, 2);
+}
+
+static void send_init_string(struct mipid_device *md)
+{
+	u16 initpar[] = { 0x0102, 0x0100, 0x0100 };
+
+	mipid_write(md, 0xc2, (u8 *)initpar, sizeof(initpar));
+	set_data_lines(md, md->panel.data_lines);
+}
+
+static void hw_guard_start(struct mipid_device *md, int guard_msec)
+{
+	md->hw_guard_wait = msecs_to_jiffies(guard_msec);
+	md->hw_guard_end = jiffies + md->hw_guard_wait;
+}
+
+static void hw_guard_wait(struct mipid_device *md)
+{
+	unsigned long wait = md->hw_guard_end - jiffies;
+
+	if ((long)wait > 0 && wait <= md->hw_guard_wait) {
+		set_current_state(TASK_UNINTERRUPTIBLE);
+		schedule_timeout(wait);
+	}
+}
+
+static void set_sleep_mode(struct mipid_device *md, int on)
+{
+	int cmd, sleep_time = 50;
+
+	if (on)
+		cmd = MIPID_CMD_SLEEP_IN;
+	else
+		cmd = MIPID_CMD_SLEEP_OUT;
+	hw_guard_wait(md);
+	mipid_cmd(md, cmd);
+	hw_guard_start(md, 120);
+	/*
+	 * When we enable the panel, it seems we _have_ to sleep
+	 * 120 ms before sending the init string. When disabling the
+	 * panel we'll sleep for the duration of 2 frames, so that the
+	 * controller can still provide the PCLK,HS,VS signals. */
+	if (!on)
+		sleep_time = 120;
+	msleep(sleep_time);
+}
+
+static void set_display_state(struct mipid_device *md, int enabled)
+{
+	int cmd = enabled ? MIPID_CMD_DISP_ON : MIPID_CMD_DISP_OFF;
+
+	mipid_cmd(md, cmd);
+}
+
+static int mipid_set_bklight_level(struct lcd_panel *panel, unsigned int level)
+{
+	struct mipid_device *md = to_mipid_device(panel);
+
+	if (level > tahvo_get_max_backlight_level())
+		return -EINVAL;
+	if (!md->enabled) {
+		md->saved_bklight_level = level;
+		return 0;
+	}
+	tahvo_set_backlight_level(level);
+
+	return 0;
+}
+
+static unsigned int mipid_get_bklight_level(struct lcd_panel *panel)
+{
+	return tahvo_get_backlight_level();
+}
+
+static unsigned int mipid_get_bklight_max(struct lcd_panel *panel)
+{
+	return tahvo_get_max_backlight_level();
+}
+
+
+static unsigned long mipid_get_caps(struct lcd_panel *panel)
+{
+	return OMAPFB_CAPS_SET_BACKLIGHT;
+}
+
+static u16 read_first_pixel(struct mipid_device *md)
+{
+	u16 pixel;
+	u8 red, green, blue;
+
+	mutex_lock(&md->mutex);
+	mipid_read(md, MIPID_CMD_READ_RED, &red, 1);
+	mipid_read(md, MIPID_CMD_READ_GREEN, &green, 1);
+	mipid_read(md, MIPID_CMD_READ_BLUE, &blue, 1);
+	mutex_unlock(&md->mutex);
+
+	switch (md->panel.data_lines) {
+	case 16:
+		pixel = ((red >> 1) << 11) | (green << 5) | (blue >> 1);
+		break;
+	case 24:
+		/* 24 bit -> 16 bit */
+		pixel = ((red >> 3) << 11) | ((green >> 2) << 5) |
+			(blue >> 3);
+		break;
+	default:
+		BUG();
+	}
+
+	return pixel;
+}
+
+static int mipid_run_test(struct lcd_panel *panel, int test_num)
+{
+	struct mipid_device *md = to_mipid_device(panel);
+	static const u16 test_values[4] = {
+		0x0000, 0xffff, 0xaaaa, 0x5555,
+	};
+	int i;
+
+	if (test_num != MIPID_TEST_RGB_LINES)
+		return MIPID_TEST_INVALID;
+
+	for (i = 0; i < ARRAY_SIZE(test_values); i++) {
+		int delay;
+		unsigned long tmo;
+
+		omapfb_write_first_pixel(md->fbdev, test_values[i]);
+		tmo = jiffies + msecs_to_jiffies(100);
+		delay = 25;
+		while (1) {
+			u16 pixel;
+
+			msleep(delay);
+			pixel = read_first_pixel(md);
+			if (pixel == test_values[i])
+				break;
+			if (time_after(jiffies, tmo)) {
+				dev_err(&md->spi->dev,
+					"MIPI LCD RGB I/F test failed: "
+					"expecting %04x, got %04x\n",
+					test_values[i], pixel);
+				return MIPID_TEST_FAILED;
+			}
+			delay = 10;
+		}
+	}
+
+	return 0;
+}
+
+static void ls041y3_esd_recover(struct mipid_device *md)
+{
+	dev_err(&md->spi->dev, "performing LCD ESD recovery\n");
+	set_sleep_mode(md, 1);
+	set_sleep_mode(md, 0);
+}
+
+static void ls041y3_esd_check_mode1(struct mipid_device *md)
+{
+	u8 state1, state2;
+
+	mipid_read(md, MIPID_CMD_RDDSDR, &state1, 1);
+	set_sleep_mode(md, 0);
+	mipid_read(md, MIPID_CMD_RDDSDR, &state2, 1);
+	dev_dbg(&md->spi->dev, "ESD mode 1 state1 %02x state2 %02x\n",
+		state1, state2);
+	/* Each sleep out command will trigger a self diagnostic and flip
+	* Bit6 if the test passes.
+	*/
+	if (!((state1 ^ state2) & (1 << 6)))
+		ls041y3_esd_recover(md);
+}
+
+static void ls041y3_esd_check_mode2(struct mipid_device *md)
+{
+	int i;
+	u8 rbuf[2];
+	static const struct {
+		int	cmd;
+		int	wlen;
+		u16	wbuf[3];
+	} *rd, rd_ctrl[7] = {
+		{ 0xb0, 4, { 0x0101, 0x01fe, } },
+		{ 0xb1, 4, { 0x01de, 0x0121, } },
+		{ 0xc2, 4, { 0x0100, 0x0100, } },
+		{ 0xbd, 2, { 0x0100, } },
+		{ 0xc2, 4, { 0x01fc, 0x0103, } },
+		{ 0xb4, 0, },
+		{ 0x00, 0, },
+	};
+
+	rd = rd_ctrl;
+	for (i = 0; i < 3; i++, rd++)
+		mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen);
+
+	udelay(10);
+	mipid_read(md, rd->cmd, rbuf, 2);
+	rd++;
+
+	for (i = 0; i < 3; i++, rd++) {
+		udelay(10);
+		mipid_write(md, rd->cmd, (u8 *)rd->wbuf, rd->wlen);
+	}
+
+	dev_dbg(&md->spi->dev, "ESD mode 2 state %02x\n", rbuf[1]);
+	if (rbuf[1] == 0x00)
+		ls041y3_esd_recover(md);
+}
+
+static void ls041y3_esd_check(struct mipid_device *md)
+{
+	ls041y3_esd_check_mode1(md);
+	if (md->revision >= 0x88)
+		ls041y3_esd_check_mode2(md);
+}
+
+static void mipid_esd_start_check(struct mipid_device *md)
+{
+	if (md->esd_check != NULL)
+		queue_delayed_work(md->esd_wq, &md->esd_work,
+				   MIPID_ESD_CHECK_PERIOD);
+}
+
+static void mipid_esd_stop_check(struct mipid_device *md)
+{
+	if (md->esd_check != NULL)
+		cancel_rearming_delayed_workqueue(md->esd_wq, &md->esd_work);
+}
+
+static void mipid_esd_work(struct work_struct *work)
+{
+	struct mipid_device *md = container_of(work, struct mipid_device, esd_work.work);
+
+	mutex_lock(&md->mutex);
+	md->esd_check(md);
+	mutex_unlock(&md->mutex);
+	mipid_esd_start_check(md);
+}
+
+static int mipid_enable(struct lcd_panel *panel)
+{
+	struct mipid_device *md = to_mipid_device(panel);
+
+	mutex_lock(&md->mutex);
+
+	if (md->enabled) {
+		mutex_unlock(&md->mutex);
+		return 0;
+	}
+	set_sleep_mode(md, 0);
+	md->enabled = 1;
+	send_init_string(md);
+	set_display_state(md, 1);
+	mipid_set_bklight_level(panel, md->saved_bklight_level);
+	mipid_esd_start_check(md);
+
+	mutex_unlock(&md->mutex);
+	return 0;
+}
+
+static void mipid_disable(struct lcd_panel *panel)
+{
+	struct mipid_device *md = to_mipid_device(panel);
+
+	/*
+	 * A final ESD work might be called before returning,
+	 * so do this without holding the lock.
+	 */
+	mipid_esd_stop_check(md);
+	mutex_lock(&md->mutex);
+
+	if (!md->enabled) {
+		mutex_unlock(&md->mutex);
+		return;
+	}
+	md->saved_bklight_level = mipid_get_bklight_level(panel);
+	mipid_set_bklight_level(panel, 0);
+	set_display_state(md, 0);
+	set_sleep_mode(md, 1);
+	md->enabled = 0;
+
+	mutex_unlock(&md->mutex);
+}
+
+static int panel_enabled(struct mipid_device *md)
+{
+	u32 disp_status;
+	int enabled;
+
+	mipid_read(md, MIPID_CMD_READ_DISP_STATUS, (u8 *)&disp_status, 4);
+	disp_status = __be32_to_cpu(disp_status);
+	enabled = (disp_status & (1 << 17)) && (disp_status & (1 << 10));
+	dev_dbg(&md->spi->dev,
+		"LCD panel %senabled by bootloader (status 0x%04x)\n",
+		enabled ? "" : "not ", disp_status);
+	return enabled;
+}
+
+static int mipid_init(struct lcd_panel *panel,
+			    struct omapfb_device *fbdev)
+{
+	struct mipid_device *md = to_mipid_device(panel);
+
+	md->fbdev = fbdev;
+	md->esd_wq = create_singlethread_workqueue("mipid_esd");
+	if (md->esd_wq == NULL) {
+		dev_err(&md->spi->dev, "can't create ESD workqueue\n");
+		return -ENOMEM;
+	}
+	INIT_DELAYED_WORK(&md->esd_work, mipid_esd_work);
+	mutex_init(&md->mutex);
+
+	md->enabled = panel_enabled(md);
+
+	if (md->enabled)
+		mipid_esd_start_check(md);
+	else
+		md->saved_bklight_level = mipid_get_bklight_level(panel);
+
+	return 0;
+}
+
+static void mipid_cleanup(struct lcd_panel *panel)
+{
+	struct mipid_device *md = to_mipid_device(panel);
+
+	if (md->enabled)
+		mipid_esd_stop_check(md);
+	destroy_workqueue(md->esd_wq);
+}
+
+static struct lcd_panel mipid_panel = {
+	.config		= OMAP_LCDC_PANEL_TFT,
+
+	.bpp		= 16,
+	.x_res		= 800,
+	.y_res		= 480,
+	.pixel_clock	= 21940,
+	.hsw		= 50,
+	.hfp		= 20,
+	.hbp		= 15,
+	.vsw		= 2,
+	.vfp		= 1,
+	.vbp		= 3,
+
+	.init		= mipid_init,
+	.cleanup	= mipid_cleanup,
+	.enable		= mipid_enable,
+	.disable	= mipid_disable,
+	.get_caps	= mipid_get_caps,
+	.set_bklight_level= mipid_set_bklight_level,
+	.get_bklight_level= mipid_get_bklight_level,
+	.get_bklight_max= mipid_get_bklight_max,
+	.run_test	= mipid_run_test,
+};
+
+static int mipid_detect(struct mipid_device *md)
+{
+	struct mipid_platform_data *pdata;
+
+	pdata = md->spi->dev.platform_data;
+	if (pdata == NULL) {
+		dev_err(&md->spi->dev, "missing platform data\n");
+		return -ENOENT;
+	}
+
+	mipid_read(md, MIPID_CMD_READ_DISP_ID, md->display_id, 3);
+	dev_dbg(&md->spi->dev, "MIPI display ID: %02x%02x%02x\n",
+		md->display_id[0], md->display_id[1], md->display_id[2]);
+
+	switch (md->display_id[0]) {
+	case 0x45:
+		md->model = MIPID_VER_LPH8923;
+		md->panel.name = "lph8923";
+		break;
+	case 0x83:
+		md->model = MIPID_VER_LS041Y3;
+		md->panel.name = "ls041y3";
+		md->esd_check = ls041y3_esd_check;
+		break;
+	default:
+		md->panel.name = "unknown";
+		dev_err(&md->spi->dev, "invalid display ID\n");
+		return -ENODEV;
+	}
+
+	md->revision = md->display_id[1];
+	md->panel.data_lines = pdata->data_lines;
+	pr_info("omapfb: %s rev %02x LCD detected\n",
+			md->panel.name, md->revision);
+
+	return 0;
+}
+
+static int mipid_spi_probe(struct spi_device *spi)
+{
+	struct mipid_device *md;
+	int r;
+
+	md = kzalloc(sizeof(*md), GFP_KERNEL);
+	if (md == NULL) {
+		dev_err(&spi->dev, "out of memory\n");
+		return -ENOMEM;
+	}
+
+	spi->mode = SPI_MODE_0;
+	md->spi = spi;
+	dev_set_drvdata(&spi->dev, md);
+	md->panel = mipid_panel;
+
+	r = mipid_detect(md);
+	if (r < 0)
+		return r;
+
+	omapfb_register_panel(&md->panel);
+
+	return 0;
+}
+
+static int mipid_spi_remove(struct spi_device *spi)
+{
+	struct mipid_device *md = dev_get_drvdata(&spi->dev);
+
+	mipid_disable(&md->panel);
+	kfree(md);
+
+	return 0;
+}
+
+static struct spi_driver mipid_spi_driver = {
+	.driver = {
+		.name	= MIPID_MODULE_NAME,
+		.bus	= &spi_bus_type,
+		.owner	= THIS_MODULE,
+	},
+	.probe	= mipid_spi_probe,
+	.remove	= __devexit_p(mipid_spi_remove),
+};
+
+static int mipid_drv_init(void)
+{
+	spi_register_driver(&mipid_spi_driver);
+
+	return 0;
+}
+module_init(mipid_drv_init);
+
+static void mipid_drv_cleanup(void)
+{
+	spi_unregister_driver(&mipid_spi_driver);
+}
+module_exit(mipid_drv_cleanup);
+
+MODULE_DESCRIPTION("MIPI display driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/video/omap/lcd_omap2evm.c b/drivers/video/omap/lcd_omap2evm.c
new file mode 100644
index 0000000..8a0105e
--- /dev/null
+++ b/drivers/video/omap/lcd_omap2evm.c
@@ -0,0 +1,195 @@
+/*
+ * LCD panel support for the MISTRAL OMAP2EVM board
+ *
+ * Author: Arun C <arunedarath@mistralsolutions.com>
+ *
+ * Derived from drivers/video/omap/lcd_omap3evm.c
+ * Derived from drivers/video/omap/lcd-apollon.c
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+
+#include <mach/gpio.h>
+#include <mach/mux.h>
+#include <mach/omapfb.h>
+#include <asm/mach-types.h>
+
+#define LCD_PANEL_ENABLE_GPIO	154
+#define LCD_PANEL_LR		128
+#define LCD_PANEL_UD		129
+#define LCD_PANEL_INI		152
+#define LCD_PANEL_QVGA		148
+#define LCD_PANEL_RESB		153
+
+#define LCD_XRES		480
+#define LCD_YRES		640
+#define LCD_PIXCLOCK_MAX	20000 /* in kHz */
+
+#define TWL_LED_LEDEN		0x00
+#define TWL_PWMA_PWMAON		0x00
+#define TWL_PWMA_PWMAOFF	0x01
+
+static unsigned int bklight_level;
+
+static int omap2evm_panel_init(struct lcd_panel *panel,
+				struct omapfb_device *fbdev)
+{
+	omap_request_gpio(LCD_PANEL_ENABLE_GPIO);
+	omap_request_gpio(LCD_PANEL_LR);
+	omap_request_gpio(LCD_PANEL_UD);
+	omap_request_gpio(LCD_PANEL_INI);
+	omap_request_gpio(LCD_PANEL_QVGA);
+	omap_request_gpio(LCD_PANEL_RESB);
+
+	omap_set_gpio_direction(LCD_PANEL_ENABLE_GPIO, 0);
+	omap_set_gpio_direction(LCD_PANEL_LR, 0);
+	omap_set_gpio_direction(LCD_PANEL_UD, 0);
+	omap_set_gpio_direction(LCD_PANEL_INI, 0);
+	omap_set_gpio_direction(LCD_PANEL_QVGA, 0);
+	omap_set_gpio_direction(LCD_PANEL_RESB, 0);
+
+	omap_set_gpio_dataout(LCD_PANEL_RESB, 1);
+	omap_set_gpio_dataout(LCD_PANEL_INI, 1);
+	omap_set_gpio_dataout(LCD_PANEL_QVGA, 0);
+	omap_set_gpio_dataout(LCD_PANEL_LR, 1);
+	omap_set_gpio_dataout(LCD_PANEL_UD, 1);
+
+	twl4030_i2c_write_u8(TWL4030_MODULE_LED, 0x11, TWL_LED_LEDEN);
+	twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x01, TWL_PWMA_PWMAON);
+	twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x02, TWL_PWMA_PWMAOFF);
+	bklight_level = 100;
+
+	return 0;
+}
+
+static void omap2evm_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int omap2evm_panel_enable(struct lcd_panel *panel)
+{
+	omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 0);
+	return 0;
+}
+
+static void omap2evm_panel_disable(struct lcd_panel *panel)
+{
+	omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 1);
+}
+
+static unsigned long omap2evm_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+static int omap2evm_bklight_setlevel(struct lcd_panel *panel,
+						unsigned int level)
+{
+	u8 c;
+	if ((level >= 0) && (level <= 100)) {
+		c = (125 * (100 - level)) / 100 + 2;
+		twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, c, TWL_PWMA_PWMAOFF);
+		bklight_level = level;
+	}
+	return 0;
+}
+
+static unsigned int omap2evm_bklight_getlevel(struct lcd_panel *panel)
+{
+	return bklight_level;
+}
+
+static unsigned int omap2evm_bklight_getmaxlevel(struct lcd_panel *panel)
+{
+	return 100;
+}
+
+struct lcd_panel omap2evm_panel = {
+	.name		= "omap2evm",
+	.config		= OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
+			  OMAP_LCDC_INV_HSYNC,
+
+	.bpp		= 16,
+	.data_lines	= 18,
+	.x_res		= LCD_XRES,
+	.y_res		= LCD_YRES,
+	.hsw		= 3,
+	.hfp		= 0,
+	.hbp		= 28,
+	.vsw		= 2,
+	.vfp		= 1,
+	.vbp		= 0,
+
+	.pixel_clock	= LCD_PIXCLOCK_MAX,
+
+	.init		= omap2evm_panel_init,
+	.cleanup	= omap2evm_panel_cleanup,
+	.enable		= omap2evm_panel_enable,
+	.disable	= omap2evm_panel_disable,
+	.get_caps	= omap2evm_panel_get_caps,
+	.set_bklight_level      = omap2evm_bklight_setlevel,
+	.get_bklight_level      = omap2evm_bklight_getlevel,
+	.get_bklight_max        = omap2evm_bklight_getmaxlevel,
+};
+
+static int omap2evm_panel_probe(struct platform_device *pdev)
+{
+	omapfb_register_panel(&omap2evm_panel);
+	return 0;
+}
+
+static int omap2evm_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int omap2evm_panel_suspend(struct platform_device *pdev,
+				   pm_message_t mesg)
+{
+	return 0;
+}
+
+static int omap2evm_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver omap2evm_panel_driver = {
+	.probe		= omap2evm_panel_probe,
+	.remove		= omap2evm_panel_remove,
+	.suspend	= omap2evm_panel_suspend,
+	.resume		= omap2evm_panel_resume,
+	.driver		= {
+		.name	= "omap2evm_lcd",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init omap2evm_panel_drv_init(void)
+{
+	return platform_driver_register(&omap2evm_panel_driver);
+}
+
+static void __exit omap2evm_panel_drv_exit(void)
+{
+	platform_driver_unregister(&omap2evm_panel_driver);
+}
+
+module_init(omap2evm_panel_drv_init);
+module_exit(omap2evm_panel_drv_exit);
diff --git a/drivers/video/omap/lcd_omap3beagle.c b/drivers/video/omap/lcd_omap3beagle.c
new file mode 100644
index 0000000..9637735
--- /dev/null
+++ b/drivers/video/omap/lcd_omap3beagle.c
@@ -0,0 +1,133 @@
+/*
+ * LCD panel support for the TI OMAP3 Beagle board
+ *
+ * Author: Koen Kooi <koen@openembedded.org>
+ *
+ * Derived from drivers/video/omap/lcd-omap3evm.c
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+
+#include <mach/gpio.h>
+#include <mach/mux.h>
+#include <mach/omapfb.h>
+#include <asm/mach-types.h>
+
+#define LCD_PANEL_ENABLE_GPIO       170
+
+#define LCD_XRES		1024	
+#define LCD_YRES 		768
+#define LCD_PIXCLOCK		64000 /* in kHz */
+
+static int omap3beagle_panel_init(struct lcd_panel *panel,
+				struct omapfb_device *fbdev)
+{
+	omap_request_gpio(LCD_PANEL_ENABLE_GPIO);
+	return 0;
+}
+
+static void omap3beagle_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int omap3beagle_panel_enable(struct lcd_panel *panel)
+{
+	omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 1);
+	return 0;
+}
+
+static void omap3beagle_panel_disable(struct lcd_panel *panel)
+{
+	omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 0);
+}
+
+static unsigned long omap3beagle_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+struct lcd_panel omap3beagle_panel = {
+	.name		= "omap3beagle",
+	.config		= OMAP_LCDC_PANEL_TFT,
+
+	.bpp		= 24,
+	.data_lines	= 24,
+	.x_res		= LCD_XRES,
+	.y_res		= LCD_YRES,
+	.hsw		= 3,		/* hsync_len (4) - 1 */
+	.hfp		= 3,		/* right_margin (4) - 1 */
+	.hbp		= 39,		/* left_margin (40) - 1 */
+	.vsw		= 1,		/* vsync_len (2) - 1 */
+	.vfp		= 2,		/* lower_margin */
+	.vbp		= 7,		/* upper_margin (8) - 1 */
+
+	.pixel_clock	= LCD_PIXCLOCK,
+
+	.init		= omap3beagle_panel_init,
+	.cleanup	= omap3beagle_panel_cleanup,
+	.enable		= omap3beagle_panel_enable,
+	.disable	= omap3beagle_panel_disable,
+	.get_caps	= omap3beagle_panel_get_caps,
+};
+
+static int omap3beagle_panel_probe(struct platform_device *pdev)
+{
+	omapfb_register_panel(&omap3beagle_panel);
+	return 0;
+}
+
+static int omap3beagle_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int omap3beagle_panel_suspend(struct platform_device *pdev,
+				   pm_message_t mesg)
+{
+	return 0;
+}
+
+static int omap3beagle_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver omap3beagle_panel_driver = {
+	.probe		= omap3beagle_panel_probe,
+	.remove		= omap3beagle_panel_remove,
+	.suspend	= omap3beagle_panel_suspend,
+	.resume		= omap3beagle_panel_resume,
+	.driver		= {
+		.name	= "omap3beagle_lcd",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init omap3beagle_panel_drv_init(void)
+{
+	return platform_driver_register(&omap3beagle_panel_driver);
+}
+
+static void __exit omap3beagle_panel_drv_exit(void)
+{
+	platform_driver_unregister(&omap3beagle_panel_driver);
+}
+
+module_init(omap3beagle_panel_drv_init);
+module_exit(omap3beagle_panel_drv_exit);
diff --git a/drivers/video/omap/lcd_omap3evm.c b/drivers/video/omap/lcd_omap3evm.c
new file mode 100644
index 0000000..a564ca5
--- /dev/null
+++ b/drivers/video/omap/lcd_omap3evm.c
@@ -0,0 +1,197 @@
+/*
+ * LCD panel support for the TI OMAP3 EVM board
+ *
+ * Author: Steve Sakoman <steve@sakoman.com>
+ *
+ * Derived from drivers/video/omap/lcd-apollon.c
+ *
+ * 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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/i2c/twl4030.h>
+
+#include <mach/gpio.h>
+#include <mach/mux.h>
+#include <mach/omapfb.h>
+#include <asm/mach-types.h>
+
+#define LCD_PANEL_ENABLE_GPIO       153
+#define LCD_PANEL_LR                2
+#define LCD_PANEL_UD                3
+#define LCD_PANEL_INI               152
+#define LCD_PANEL_QVGA              154
+#define LCD_PANEL_RESB              155
+
+#define LCD_XRES	 	480
+#define LCD_YRES 		640
+#define LCD_PIXCLOCK		26000 /* in kHz  */
+
+#define ENABLE_VDAC_DEDICATED	0x03
+#define ENABLE_VDAC_DEV_GRP	0x20
+#define ENABLE_VPLL2_DEDICATED	0x05
+#define ENABLE_VPLL2_DEV_GRP	0xE0
+
+#define TWL_LED_LEDEN		0x00
+#define TWL_PWMA_PWMAON		0x00
+#define TWL_PWMA_PWMAOFF	0x01
+
+static unsigned int bklight_level;
+
+static int omap3evm_panel_init(struct lcd_panel *panel,
+				struct omapfb_device *fbdev)
+{
+	omap_request_gpio(LCD_PANEL_LR);
+	omap_request_gpio(LCD_PANEL_UD);
+	omap_request_gpio(LCD_PANEL_INI);
+	omap_request_gpio(LCD_PANEL_RESB);
+	omap_request_gpio(LCD_PANEL_QVGA);
+
+	omap_set_gpio_direction(LCD_PANEL_LR, 0);
+	omap_set_gpio_direction(LCD_PANEL_UD, 0);
+	omap_set_gpio_direction(LCD_PANEL_INI, 0);
+	omap_set_gpio_direction(LCD_PANEL_RESB, 0);
+	omap_set_gpio_direction(LCD_PANEL_QVGA, 0);
+
+	twl4030_i2c_write_u8(TWL4030_MODULE_LED, 0x11, TWL_LED_LEDEN);
+	twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x01, TWL_PWMA_PWMAON);
+	twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, 0x02, TWL_PWMA_PWMAOFF);
+	bklight_level = 100;
+
+	omap_set_gpio_dataout(LCD_PANEL_RESB, 1);
+	omap_set_gpio_dataout(LCD_PANEL_INI, 1);
+	omap_set_gpio_dataout(LCD_PANEL_QVGA, 0);
+	omap_set_gpio_dataout(LCD_PANEL_LR, 1);
+	omap_set_gpio_dataout(LCD_PANEL_UD, 1);
+
+	return 0;
+}
+
+static void omap3evm_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int omap3evm_panel_enable(struct lcd_panel *panel)
+{
+	omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 0);
+	return 0;
+}
+
+static void omap3evm_panel_disable(struct lcd_panel *panel)
+{
+	omap_set_gpio_dataout(LCD_PANEL_ENABLE_GPIO, 1);
+}
+
+static unsigned long omap3evm_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+static int omap3evm_bklight_setlevel(struct lcd_panel *panel,
+						unsigned int level)
+{
+	u8 c;
+	if ((level >= 0) && (level <= 100)) {
+		c = (125 * (100 - level)) / 100 + 2;
+		twl4030_i2c_write_u8(TWL4030_MODULE_PWMA, c, TWL_PWMA_PWMAOFF);
+		bklight_level = level;
+	}
+	return 0;
+}
+
+static unsigned int omap3evm_bklight_getlevel(struct lcd_panel *panel)
+{
+	return bklight_level;
+}
+
+static unsigned int omap3evm_bklight_getmaxlevel(struct lcd_panel *panel)
+{
+	return 100;
+}
+
+struct lcd_panel omap3evm_panel = {
+	.name		= "omap3evm",
+	.config		= OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_VSYNC |
+			  OMAP_LCDC_INV_HSYNC,
+
+	.bpp		= 16,
+	.data_lines	= 18,
+	.x_res		= LCD_XRES,
+	.y_res		= LCD_YRES,
+	.hsw		= 3,		/* hsync_len (4) - 1 */
+	.hfp		= 3,		/* right_margin (4) - 1 */
+	.hbp		= 39,		/* left_margin (40) - 1 */
+	.vsw		= 1,		/* vsync_len (2) - 1 */
+	.vfp		= 2,		/* lower_margin */
+	.vbp		= 7,		/* upper_margin (8) - 1 */
+
+	.pixel_clock	= LCD_PIXCLOCK,
+
+	.init		= omap3evm_panel_init,
+	.cleanup	= omap3evm_panel_cleanup,
+	.enable		= omap3evm_panel_enable,
+	.disable	= omap3evm_panel_disable,
+	.get_caps	= omap3evm_panel_get_caps,
+	.set_bklight_level      = omap3evm_bklight_setlevel,
+	.get_bklight_level      = omap3evm_bklight_getlevel,
+	.get_bklight_max        = omap3evm_bklight_getmaxlevel,
+};
+
+static int omap3evm_panel_probe(struct platform_device *pdev)
+{
+	omapfb_register_panel(&omap3evm_panel);
+	return 0;
+}
+
+static int omap3evm_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int omap3evm_panel_suspend(struct platform_device *pdev,
+				   pm_message_t mesg)
+{
+	return 0;
+}
+
+static int omap3evm_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver omap3evm_panel_driver = {
+	.probe		= omap3evm_panel_probe,
+	.remove		= omap3evm_panel_remove,
+	.suspend	= omap3evm_panel_suspend,
+	.resume		= omap3evm_panel_resume,
+	.driver		= {
+		.name	= "omap3evm_lcd",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init omap3evm_panel_drv_init(void)
+{
+	return platform_driver_register(&omap3evm_panel_driver);
+}
+
+static void __exit omap3evm_panel_drv_exit(void)
+{
+	platform_driver_unregister(&omap3evm_panel_driver);
+}
+
+module_init(omap3evm_panel_drv_init);
+module_exit(omap3evm_panel_drv_exit);
diff --git a/drivers/video/omap/lcd_p2.c b/drivers/video/omap/lcd_p2.c
new file mode 100644
index 0000000..8c66bf6
--- /dev/null
+++ b/drivers/video/omap/lcd_p2.c
@@ -0,0 +1,342 @@
+/*
+ * LCD panel support for the TI OMAP P2 board
+ *
+ * Authors:
+ *   jekyll <jekyll@mail.jekyll.idv.tw>
+ *   B Jp <lastjp_fr@yahoo.fr>
+ *   Brian Swetland <swetland@android.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.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * 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.,
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+
+#include <mach/mux.h>
+#include <mach/gpio.h>
+#include <mach/omapfb.h>
+
+/*
+ * File: epson-md-tft.h
+ *
+ * This file contains definitions for Epsons MD-TF LCD Module
+ *
+ * Copyright (C) 2004 MPC-Data Limited  (http://www.mpc-data.co.uk)
+ * Author: Dave Peverley <dpeverley at mpc-data.co.uk>
+ *
+ *  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.
+ *
+ *  THIS  SOFTWARE  IS  PROVIDED  ``AS  IS''  AND   ANY  EXPRESS  OR IMPLIED
+ *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
+ *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
+ *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT,  INDIRECT,
+ *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
+ *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
+ *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ *  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.
+ *
+ * Please report all bugs and problems to the author.
+ *
+ */
+
+/* LCD uWire commands & params
+ * All values from Epson
+ */
+#define LCD_DISON 0xAF
+#define LCD_DISOFF 0xAE
+#define LCD_DISNOR 0xA6
+#define LCD_DISINV 0xA7
+#define LCD_DISCTL 0xCA
+#define LCD_GCP64 0xCB
+#define LCD_GCP16 0xCC
+#define LCD_GSSET 0xCD
+#define LCD_SLPIN 0x95
+#define LCD_SLPOUT 0x94
+#define LCD_SD_PSET 0x75
+#define LCD_MD_PSET 0x76
+#define LCD_SD_CSET 0x15
+#define LCD_MD_CSET 0x16
+#define LCD_DATCTL 0xBC
+#define LCD_RAMWR 0x5C
+#define LCD_RAMRD 0x5D
+#define LCD_PTLIN 0xA8
+#define LCD_PTLOUT 0xA9
+#define LCD_ASCSET 0xAA
+#define LCD_SCSTART 0xAB
+#define LCD_VOLCTL 0xC6
+#define LCD_NOP 0x25
+#define LCD_OSCISEL 0x7
+#define LCD_3500KSET 0xD1
+#define LCD_3500KEND 0xD2
+#define LCD_14MSET 0xD3
+#define LCD_14MEND 0xD4
+
+#define INIT_3500KSET 0x45
+#define INIT_14MSET 0x4B
+#define INIT_DATCTL 0x08 /* 6.6.6 bits for D-Sample */
+
+#define INIT_OSCISEL 0x05
+
+#define INIT_VOLCTL 0x77 /* Nominel "volume" */
+
+#define INIT_VOLCTL_Ton 0x98 /* Activate power-IC timer */
+#define INIT_GSSET 0x00
+
+const unsigned short INIT_DISCTL[11] =
+{
+	0xDE, 0x01, 0x64, 0x00, 0x1B, 0xF4, 0x00, 0xDC, 0x00, 0x02, 0x00
+};
+
+const unsigned short INIT_GCP64[126] =
+{
+	0x3B,0x00,0x42,0x00,0x4A,0x00,0x51,0x00,
+	0x58,0x00,0x5F,0x00,0x66,0x00,0x6E,0x00,
+	0x75,0x00,0x7C,0x00,0x83,0x00,0x8A,0x00,
+	0x92,0x00,0x99,0x00,0xA0,0x00,0xA7,0x00,
+	0xAE,0x00,0xB6,0x00,0xBD,0x00,0xC4,0x00,
+	0xCB,0x00,0xD2,0x00,0xDA,0x00,0xE1,0x00,
+	0xE8,0x00,0xEF,0x00,0xF6,0x00,0xFE,0x00,
+	0x05,0x01,0x0C,0x01,0x13,0x01,0x1A,0x01,
+	0x22,0x01,0x29,0x01,0x30,0x01,0x37,0x01,
+	0x3E,0x01,0x46,0x01,0x4D,0x01,0x54,0x01,
+	0x5B,0x01,0x62,0x01,0x6A,0x01,0x71,0x01,
+	0x78,0x01,0x7F,0x01,0x86,0x01,0x8E,0x01,
+	0x95,0x01,0x9C,0x01,0xA3,0x01,0xAA,0x01,
+	0xB2,0x01,0xB9,0x01,0xC0,0x01,0xC7,0x01,
+	0xCE,0x01,0xD6,0x01,0xDD,0x01,0xE4,0x01,
+	0xEB,0x01,0xF2,0x01,0xFA,0x01
+};
+
+const unsigned short INIT_GCP16[15] =
+{
+	0x1A,0x31,0x48,0x54,0x5F,0x67,0x70,0x76,0x7C,0x80,0x83,0x84,0x85,0x87,0x96
+};
+
+const unsigned short INIT_MD_PSET[4] = { 0, 0, 219, 0 };
+const unsigned short INIT_MD_CSET[4] = { 2, 0, 177, 0 };
+
+const unsigned short INIT_SD_PSET[4] = { 0x00, 0x01, 0x00, 0x01 };
+const unsigned short INIT_SD_CSET[4] = { 0x00, 0x02, 0x00, 0x02 };
+
+const unsigned short INIT_ASCSET[7] = { 0x00, 0x00, 0xDB, 0x00, 0xDC, 0x00, 0x01 };
+const unsigned short INIT_SCSTART[2] = { 0x00, 0x00 };
+
+/* ----- end of epson_md_tft.h ----- */
+
+
+#include "../drivers/ssi/omap-uwire.h"
+
+#define LCD_UWIRE_CS 0
+
+static int p2_panel_init(struct lcd_panel *panel, struct omapfb_device *fbdev)
+{
+	return 0;
+}
+
+static void p2_panel_cleanup(struct lcd_panel *panel)
+{
+}
+
+static int p2_panel_enable(struct lcd_panel *panel)
+{
+	int i;
+	unsigned long value;
+
+		/* thwack the reset line */
+	omap_set_gpio_direction(19, 0);
+	omap_set_gpio_dataout(19, 0);
+	mdelay(2);
+	omap_set_gpio_dataout(19, 1);
+
+		/* bits 31:28 -> 0  LCD_PXL_15 .. 12 */
+	value = omap_readl(OMAP730_IO_CONF_3) & 0x0FFFFFFF;
+	omap_writel(value, OMAP730_IO_CONF_3);
+
+		/* bits 19:0 -> 0  LCD_VSYNC, AC, PXL_0, PCLK, HSYNC,
+		**                 PXL_9..1, PXL_10, PXL_11
+		*/
+	value = omap_readl(OMAP730_IO_CONF_4) & 0xFFF00000;
+	omap_writel(value, OMAP730_IO_CONF_4);
+
+	omap_uwire_configure_mode(0,16);
+
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISOFF, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPIN, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISNOR, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GSSET, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GSSET | 0x100), 9, 0,NULL,1);
+
+	/* DISCTL */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISCTL, 9, 0,NULL,1);
+	for (i = 0; i < (sizeof(INIT_DISCTL)/sizeof(unsigned short)); i++)
+		omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DISCTL[i] | 0x100), 9, 0,NULL,1);
+
+	/* GCP64 */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP64, 9, 0,NULL,1);
+	for (i = 0; i < (sizeof(INIT_GCP64)/sizeof(unsigned short)); i++)
+		omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP64[i] | 0x100), 9, 0,NULL,1);
+
+	/* GCP16 */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_GCP16, 9, 0,NULL,1);
+	for (i = 0; i < (sizeof(INIT_GCP16)/sizeof(unsigned short)); i++)
+		omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_GCP16[i] | 0x100), 9, 0,NULL,1);
+
+	/* MD_CSET */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_CSET, 9, 0,NULL,1);
+	for (i = 0; i < (sizeof(INIT_MD_CSET)/sizeof(unsigned short)); i++)
+		omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_CSET[i] | 0x100), 9, 0,NULL,1);
+
+	/* MD_PSET */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_MD_PSET, 9, 0,NULL,1);
+	for (i = 0; i < (sizeof(INIT_MD_PSET)/sizeof(unsigned short)); i++)
+		omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_MD_PSET[i] | 0x100), 9, 0,NULL,1);
+
+	/* SD_CSET */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_CSET, 9, 0,NULL,1);
+	for (i = 0; i < (sizeof(INIT_SD_CSET)/sizeof(unsigned short)); i++)
+		omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_CSET[i] | 0x100), 9, 0,NULL,1);
+
+	/* SD_PSET */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SD_PSET, 9, 0,NULL,1);
+	for (i = 0; i < (sizeof(INIT_SD_PSET)/sizeof(unsigned short)); i++)
+		omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_SD_PSET[i] | 0x100), 9, 0,NULL,1);
+
+	/* DATCTL */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DATCTL, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_DATCTL | 0x100), 9, 0,NULL,1);
+
+	/* OSSISEL = d'5 */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_OSCISEL, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_OSCISEL | 0x100), 9, 0,NULL,1);
+
+	/* 14MSET = d'74 */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MSET, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_14MSET | 0x100), 9, 0,NULL,1);
+
+	/* 14MEND */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_14MEND, 9, 0,NULL,1);
+
+	/* 3500KSET = d'69 */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KSET, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_3500KSET | 0x100), 9, 0,NULL,1);
+
+	/* 3500KEND */
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_3500KEND, 9, 0,NULL,1);
+
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_SLPOUT, 9, 0,NULL,1);
+
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1);
+	omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL_Ton | 0x100), 9, 0,NULL,1);
+
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_VOLCTL, 9, 0,NULL,1);
+
+	omap_uwire_data_transfer(LCD_UWIRE_CS, (INIT_VOLCTL | 0x100), 9, 0,NULL,1);
+
+	omap_uwire_data_transfer(LCD_UWIRE_CS, LCD_DISON, 9, 0,NULL,1);
+
+	/* enable backlight */
+	omap_set_gpio_direction(134, 0);
+	omap_set_gpio_dataout(134, 1);
+
+	return 0;
+}
+
+static void p2_panel_disable(struct lcd_panel *panel)
+{
+}
+
+static unsigned long p2_panel_get_caps(struct lcd_panel *panel)
+{
+	return 0;
+}
+
+struct lcd_panel p2_panel = {
+	.name		= "p2",
+	.config		= OMAP_LCDC_PANEL_TFT | OMAP_LCDC_INV_PIX_CLOCK,
+
+	.bpp		= 16,
+	.data_lines	= 16,
+	.x_res		= 176,
+	.y_res		= 220,
+	.pixel_clock	= 12500,
+	.hsw		= 5,
+	.hfp		= 1,
+	.hbp		= 1,
+	.vsw		= 2,
+	.vfp		= 12,
+	.vbp		= 1,
+
+	.init		= p2_panel_init,
+	.cleanup	= p2_panel_cleanup,
+	.enable		= p2_panel_enable,
+	.disable	= p2_panel_disable,
+	.get_caps	= p2_panel_get_caps,
+};
+
+static int p2_panel_probe(struct platform_device *pdev)
+{
+	omapfb_register_panel(&p2_panel);
+	return 0;
+}
+
+static int p2_panel_remove(struct platform_device *pdev)
+{
+	return 0;
+}
+
+static int p2_panel_suspend(struct platform_device *pdev, pm_message_t mesg)
+{
+	return 0;
+}
+
+static int p2_panel_resume(struct platform_device *pdev)
+{
+	return 0;
+}
+
+struct platform_driver p2_panel_driver = {
+	.probe		= p2_panel_probe,
+	.remove		= p2_panel_remove,
+	.suspend	= p2_panel_suspend,
+	.resume		= p2_panel_resume,
+	.driver		= {
+		.name	= "lcd_p2",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int p2_panel_drv_init(void)
+{
+	return platform_driver_register(&p2_panel_driver);
+}
+
+static void p2_panel_drv_cleanup(void)
+{
+	platform_driver_unregister(&p2_panel_driver);
+}
+
+module_init(p2_panel_drv_init);
+module_exit(p2_panel_drv_cleanup);
+
-- 
1.6.0.1.141.g445ca


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

* [PATCH 32/33] add omap 1-wire interface driver
  2008-08-30 17:16                                                             ` [PATCH 31/33] add several omap lcd drivers Felipe Balbi
@ 2008-08-30 17:16                                                               ` Felipe Balbi
  2008-08-30 17:16                                                                 ` [PATCH 33/33] add bq27000 1-wire slave driver Felipe Balbi
  2008-09-01 13:16                                                                 ` [PATCH 32/33] add omap 1-wire interface driver Madhusudhan Chikkature
  0 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 arch/arm/plat-omap/include/mach/irqs.h |    2 +
 drivers/w1/masters/Kconfig             |    7 +
 drivers/w1/masters/Makefile            |    1 +
 drivers/w1/masters/omap_hdq.c          |  704 ++++++++++++++++++++++++++++++++
 4 files changed, 714 insertions(+), 0 deletions(-)
 create mode 100644 drivers/w1/masters/omap_hdq.c

diff --git a/arch/arm/plat-omap/include/mach/irqs.h b/arch/arm/plat-omap/include/mach/irqs.h
index 17248bb..c9e95a6 100644
--- a/arch/arm/plat-omap/include/mach/irqs.h
+++ b/arch/arm/plat-omap/include/mach/irqs.h
@@ -54,6 +54,8 @@
 #define INT_TIMER2		30
 #define INT_LCD_CTRL		31
 
+#define INT_24XX_HDQ_IRQ	58 /* Temporarily here for driver to build */
+
 /*
  * OMAP-1510 specific IRQ numbers for interrupt handler 1
  */
diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig
index c449309..636d4f7 100644
--- a/drivers/w1/masters/Kconfig
+++ b/drivers/w1/masters/Kconfig
@@ -42,6 +42,13 @@ config W1_MASTER_DS1WM
 	  in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
 	  hx4700.
 
+config HDQ_MASTER_OMAP
+	tristate "OMAP HDQ driver"
+	depends on ARCH_OMAP2430 || ARCH_OMAP34XX
+	help
+	  Say Y here if you want support for the 1-wire or HDQ Interface
+	  on an OMAP processor.
+
 config W1_MASTER_GPIO
 	tristate "GPIO 1-wire busmaster"
 	depends on GENERIC_GPIO
diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile
index 1420b5b..1daeb6e 100644
--- a/drivers/w1/masters/Makefile
+++ b/drivers/w1/masters/Makefile
@@ -6,4 +6,5 @@ obj-$(CONFIG_W1_MASTER_MATROX)		+= matrox_w1.o
 obj-$(CONFIG_W1_MASTER_DS2490)		+= ds2490.o
 obj-$(CONFIG_W1_MASTER_DS2482)		+= ds2482.o
 obj-$(CONFIG_W1_MASTER_DS1WM)		+= ds1wm.o
+obj-$(CONFIG_HDQ_MASTER_OMAP)		+= omap_hdq.o
 obj-$(CONFIG_W1_MASTER_GPIO)		+= w1-gpio.o
diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c
new file mode 100644
index 0000000..880e282
--- /dev/null
+++ b/drivers/w1/masters/omap_hdq.c
@@ -0,0 +1,704 @@
+/*
+ * drivers/w1/masters/omap_hdq.c
+ *
+ * Copyright (C) 2007 Texas Instruments, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <asm/irq.h>
+#include <mach/hardware.h>
+
+#include "../w1.h"
+#include "../w1_int.h"
+
+#define	MOD_NAME	"OMAP_HDQ:"
+
+#define OMAP_HDQ_REVISION			0x00
+#define OMAP_HDQ_TX_DATA			0x04
+#define OMAP_HDQ_RX_DATA			0x08
+#define OMAP_HDQ_CTRL_STATUS			0x0c
+#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK	(1<<6)
+#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE	(1<<5)
+#define OMAP_HDQ_CTRL_STATUS_GO			(1<<4)
+#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION	(1<<2)
+#define OMAP_HDQ_CTRL_STATUS_DIR		(1<<1)
+#define OMAP_HDQ_CTRL_STATUS_MODE		(1<<0)
+#define OMAP_HDQ_INT_STATUS			0x10
+#define OMAP_HDQ_INT_STATUS_TXCOMPLETE		(1<<2)
+#define OMAP_HDQ_INT_STATUS_RXCOMPLETE		(1<<1)
+#define OMAP_HDQ_INT_STATUS_TIMEOUT		(1<<0)
+#define OMAP_HDQ_SYSCONFIG			0x14
+#define OMAP_HDQ_SYSCONFIG_SOFTRESET		(1<<1)
+#define OMAP_HDQ_SYSCONFIG_AUTOIDLE		(1<<0)
+#define OMAP_HDQ_SYSSTATUS			0x18
+#define OMAP_HDQ_SYSSTATUS_RESETDONE		(1<<0)
+
+#define OMAP_HDQ_FLAG_CLEAR			0
+#define OMAP_HDQ_FLAG_SET			1
+#define OMAP_HDQ_TIMEOUT			(HZ/5)
+
+#define OMAP_HDQ_MAX_USER			4
+
+DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue);
+int W1_ID;
+
+struct hdq_data {
+	resource_size_t		hdq_base;
+	struct	semaphore	hdq_semlock;
+	int			hdq_usecount;
+	struct	clk		*hdq_ick;
+	struct	clk		*hdq_fck;
+	u8			hdq_irqstatus;
+	spinlock_t		hdq_spinlock;
+};
+
+static struct hdq_data *hdq_data;
+
+static int omap_hdq_get(void);
+static int omap_hdq_put(void);
+static int omap_hdq_break(void);
+
+static int __init omap_hdq_probe(struct platform_device *pdev);
+static int omap_hdq_remove(struct platform_device *pdev);
+
+static struct platform_driver omap_hdq_driver = {
+	.probe = omap_hdq_probe,
+	.remove = omap_hdq_remove,
+	.suspend = NULL,
+	.resume = NULL,
+	.driver = {
+		.name = "omap_hdq",
+	},
+};
+
+static u8 omap_w1_read_byte(void *data);
+static void omap_w1_write_byte(void *data, u8 byte);
+static u8 omap_w1_reset_bus(void *data);
+static void omap_w1_search_bus(void *data, u8 search_type,
+	w1_slave_found_callback slave_found);
+
+static struct w1_bus_master omap_w1_master = {
+	.read_byte	= omap_w1_read_byte,
+	.write_byte	= omap_w1_write_byte,
+	.reset_bus	= omap_w1_reset_bus,
+	.search		= omap_w1_search_bus,
+};
+
+/*
+ * HDQ register I/O routines
+ */
+static inline u8
+hdq_reg_in(u32 offset)
+{
+	return omap_readb(hdq_data->hdq_base + offset);
+}
+
+static inline u8
+hdq_reg_out(u32 offset, u8 val)
+{
+	omap_writeb(val, hdq_data->hdq_base + offset);
+	return val;
+}
+
+static inline u8
+hdq_reg_merge(u32 offset, u8 val, u8 mask)
+{
+	u8 new_val = (omap_readb(hdq_data->hdq_base + offset) & ~mask)
+			| (val & mask);
+	omap_writeb(new_val, hdq_data->hdq_base + offset);
+	return new_val;
+}
+
+/*
+ * Wait for one or more bits in flag change.
+ * HDQ_FLAG_SET: wait until any bit in the flag is set.
+ * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared.
+ * return 0 on success and -ETIMEDOUT in the case of timeout.
+ */
+static int
+hdq_wait_for_flag(u32 offset, u8 flag, u8 flag_set, u8 *status)
+{
+	int ret = 0;
+	unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
+
+	if (flag_set == OMAP_HDQ_FLAG_CLEAR) {
+		/* wait for the flag clear */
+		while (((*status = hdq_reg_in(offset)) & flag)
+			&& time_before(jiffies, timeout)) {
+			set_current_state(TASK_UNINTERRUPTIBLE);
+			schedule_timeout(1);
+		}
+		if (unlikely(*status & flag))
+			ret = -ETIMEDOUT;
+	} else if (flag_set == OMAP_HDQ_FLAG_SET) {
+		/* wait for the flag set */
+		while (!((*status = hdq_reg_in(offset)) & flag)
+			&& time_before(jiffies, timeout)) {
+			set_current_state(TASK_UNINTERRUPTIBLE);
+			schedule_timeout(1);
+		}
+		if (unlikely(!(*status & flag)))
+			ret = -ETIMEDOUT;
+	} else
+		return -EINVAL;
+
+	return ret;
+}
+
+/*
+ * write out a byte and fill *status with HDQ_INT_STATUS
+ */
+static int
+hdq_write_byte(u8 val, u8 *status)
+{
+	int ret;
+	u8 tmp_status;
+	unsigned long irqflags;
+
+	*status = 0;
+
+	spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
+	/* clear interrupt flags via a dummy read */
+	hdq_reg_in(OMAP_HDQ_INT_STATUS);
+	/* ISR loads it with new INT_STATUS */
+	hdq_data->hdq_irqstatus = 0;
+	spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
+
+	hdq_reg_out(OMAP_HDQ_TX_DATA, val);
+
+	/* set the GO bit */
+	hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
+		OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
+	/* wait for the TXCOMPLETE bit */
+	ret = wait_event_interruptible_timeout(hdq_wait_queue,
+		hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
+	if (unlikely(ret < 0)) {
+		pr_debug("wait interrupted");
+		return -EINTR;
+	}
+
+	spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
+	*status = hdq_data->hdq_irqstatus;
+	spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
+	/* check irqstatus */
+	if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) {
+		pr_debug("timeout waiting for TXCOMPLETE/RXCOMPLETE, %x",
+			*status);
+		return -ETIMEDOUT;
+	}
+
+	/* wait for the GO bit return to zero */
+	ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
+		OMAP_HDQ_FLAG_CLEAR, &tmp_status);
+	if (ret) {
+		pr_debug("timeout waiting GO bit return to zero, %x",
+			tmp_status);
+		return ret;
+	}
+
+	return ret;
+}
+
+/*
+ * HDQ Interrupt service routine.
+ */
+static irqreturn_t
+hdq_isr(int irq, void *arg)
+{
+	unsigned long irqflags;
+
+	spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
+	hdq_data->hdq_irqstatus = hdq_reg_in(OMAP_HDQ_INT_STATUS);
+	spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
+	pr_debug("hdq_isr: %x", hdq_data->hdq_irqstatus);
+
+	if (hdq_data->hdq_irqstatus &
+		(OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE
+		| OMAP_HDQ_INT_STATUS_TIMEOUT)) {
+		/* wake up sleeping process */
+		wake_up_interruptible(&hdq_wait_queue);
+	}
+
+	return IRQ_HANDLED;
+}
+
+/*
+ * HDQ Mode: always return success.
+ */
+static u8 omap_w1_reset_bus(void *data)
+{
+	return 0;
+}
+
+/*
+ * W1 search callback function.
+ */
+static void omap_w1_search_bus(void *data, u8 search_type,
+	w1_slave_found_callback slave_found)
+{
+	u64 module_id, rn_le, cs, id;
+
+	if (W1_ID)
+		module_id = W1_ID;
+	else
+		module_id = 0x1;
+
+	rn_le = cpu_to_le64(module_id);
+	/*
+	 * HDQ might not obey truly the 1-wire spec.
+	 * So calculate CRC based on module parameter.
+	 */
+	cs = w1_calc_crc8((u8 *)&rn_le, 7);
+	id = (cs << 56) | module_id;
+
+	slave_found(data, id);
+}
+
+static int
+_omap_hdq_reset(void)
+{
+	int ret;
+	u8 tmp_status;
+
+	hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_SOFTRESET);
+	/*
+	 * Select HDQ mode & enable clocks.
+	 * It is observed that INT flags can't be cleared via a read and GO/INIT
+	 * won't return to zero if interrupt is disabled. So we always enable
+	 * interrupt.
+	 */
+	hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
+		OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
+		OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
+
+	/* wait for reset to complete */
+	ret = hdq_wait_for_flag(OMAP_HDQ_SYSSTATUS,
+		OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status);
+	if (ret)
+		pr_debug("timeout waiting HDQ reset, %x", tmp_status);
+	else {
+		hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
+			OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
+			OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
+		hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_AUTOIDLE);
+	}
+
+	return ret;
+}
+
+/*
+ * Issue break pulse to the device.
+ */
+static int
+omap_hdq_break()
+{
+	int ret;
+	u8 tmp_status;
+	unsigned long irqflags;
+
+	ret = down_interruptible(&hdq_data->hdq_semlock);
+	if (ret < 0)
+		return -EINTR;
+
+	if (!hdq_data->hdq_usecount) {
+		up(&hdq_data->hdq_semlock);
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
+	/* clear interrupt flags via a dummy read */
+	hdq_reg_in(OMAP_HDQ_INT_STATUS);
+	/* ISR loads it with new INT_STATUS */
+	hdq_data->hdq_irqstatus = 0;
+	spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
+
+	/* set the INIT and GO bit */
+	hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
+		OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO,
+		OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
+		OMAP_HDQ_CTRL_STATUS_GO);
+
+	/* wait for the TIMEOUT bit */
+	ret = wait_event_interruptible_timeout(hdq_wait_queue,
+		hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
+	if (unlikely(ret < 0)) {
+		pr_debug("wait interrupted");
+		up(&hdq_data->hdq_semlock);
+		return -EINTR;
+	}
+
+	spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
+	tmp_status = hdq_data->hdq_irqstatus;
+	spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
+	/* check irqstatus */
+	if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) {
+		pr_debug("timeout waiting for TIMEOUT, %x", tmp_status);
+		up(&hdq_data->hdq_semlock);
+		return -ETIMEDOUT;
+	}
+	/*
+	 * wait for both INIT and GO bits rerurn to zero.
+	 * zero wait time expected for interrupt mode.
+	 */
+	ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS,
+			OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
+			OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR,
+			&tmp_status);
+	if (ret)
+		pr_debug("timeout waiting INIT&GO bits return to zero, %x",
+			tmp_status);
+
+	up(&hdq_data->hdq_semlock);
+	return ret;
+}
+
+static int hdq_read_byte(u8 *val)
+{
+	int ret;
+	u8 status;
+	unsigned long irqflags;
+
+	ret = down_interruptible(&hdq_data->hdq_semlock);
+	if (ret < 0)
+		return -EINTR;
+
+	if (!hdq_data->hdq_usecount) {
+		up(&hdq_data->hdq_semlock);
+		return -EINVAL;
+	}
+
+	if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
+		hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
+			OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO,
+			OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
+		/*
+		 * The RX comes immediately after TX. It
+		 * triggers another interrupt before we
+		 * sleep. So we have to wait for RXCOMPLETE bit.
+		 */
+		{
+			unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
+			while (!(hdq_data->hdq_irqstatus
+				& OMAP_HDQ_INT_STATUS_RXCOMPLETE)
+				&& time_before(jiffies, timeout)) {
+				set_current_state(TASK_UNINTERRUPTIBLE);
+				schedule_timeout(1);
+			}
+		}
+		hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, 0,
+			OMAP_HDQ_CTRL_STATUS_DIR);
+		spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
+		status = hdq_data->hdq_irqstatus;
+		spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
+		/* check irqstatus */
+		if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
+			pr_debug("timeout waiting for RXCOMPLETE, %x", status);
+			up(&hdq_data->hdq_semlock);
+			return -ETIMEDOUT;
+		}
+	}
+	/* the data is ready. Read it in! */
+	*val = hdq_reg_in(OMAP_HDQ_RX_DATA);
+	up(&hdq_data->hdq_semlock);
+
+	return 0;
+
+}
+
+/*
+ * Enable clocks and set the controller to HDQ mode.
+ */
+static int
+omap_hdq_get()
+{
+	int ret = 0;
+
+	ret = down_interruptible(&hdq_data->hdq_semlock);
+	if (ret < 0)
+		return -EINTR;
+
+	if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) {
+		pr_debug("attempt to exceed the max use count");
+		up(&hdq_data->hdq_semlock);
+		ret = -EINVAL;
+	} else {
+		hdq_data->hdq_usecount++;
+		try_module_get(THIS_MODULE);
+		if (1 == hdq_data->hdq_usecount) {
+			if (clk_enable(hdq_data->hdq_ick)) {
+				pr_debug("Can not enable ick\n");
+				clk_put(hdq_data->hdq_ick);
+				clk_put(hdq_data->hdq_fck);
+				up(&hdq_data->hdq_semlock);
+				return -ENODEV;
+			}
+			if (clk_enable(hdq_data->hdq_fck)) {
+				pr_debug("Can not enable fck\n");
+				clk_put(hdq_data->hdq_ick);
+				clk_put(hdq_data->hdq_fck);
+				up(&hdq_data->hdq_semlock);
+				return -ENODEV;
+			}
+
+			/* make sure HDQ is out of reset */
+			if (!(hdq_reg_in(OMAP_HDQ_SYSSTATUS) &
+				OMAP_HDQ_SYSSTATUS_RESETDONE)) {
+				ret = _omap_hdq_reset();
+				if (ret)
+					/* back up the count */
+					hdq_data->hdq_usecount--;
+			} else {
+				/* select HDQ mode & enable clocks */
+				hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
+					OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
+					OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
+				hdq_reg_out(OMAP_HDQ_SYSCONFIG,
+					OMAP_HDQ_SYSCONFIG_AUTOIDLE);
+				hdq_reg_in(OMAP_HDQ_INT_STATUS);
+			}
+		}
+	}
+	up(&hdq_data->hdq_semlock);
+	return ret;
+}
+
+/*
+ * Disable clocks to the module.
+ */
+static int
+omap_hdq_put()
+{
+	int ret = 0;
+
+	ret = down_interruptible(&hdq_data->hdq_semlock);
+	if (ret < 0)
+		return -EINTR;
+
+	if (0 == hdq_data->hdq_usecount) {
+		pr_debug("attempt to decrement use count when it is zero");
+		ret = -EINVAL;
+	} else {
+		hdq_data->hdq_usecount--;
+		module_put(THIS_MODULE);
+		if (0 == hdq_data->hdq_usecount) {
+			clk_disable(hdq_data->hdq_ick);
+			clk_disable(hdq_data->hdq_fck);
+		}
+	}
+	up(&hdq_data->hdq_semlock);
+	return ret;
+}
+
+/*
+ * Used to control the call to omap_hdq_get and omap_hdq_put.
+ * HDQ Protocol: Write the CMD|REG_address first, followed by
+ * the data wrire or read.
+ */
+static int init_trans;
+
+/*
+ * Read a byte of data from the device.
+ */
+static u8 omap_w1_read_byte(void *data)
+{
+	u8 val;
+	int ret;
+
+	ret = hdq_read_byte(&val);
+	if (ret) {
+		init_trans = 0;
+		omap_hdq_put();
+		return -1;
+	}
+
+	/* Write followed by a read, release the module */
+	if (init_trans) {
+		init_trans = 0;
+		omap_hdq_put();
+	}
+
+	return val;
+}
+
+/*
+ * Write a byte of data to the device.
+ */
+static void omap_w1_write_byte(void *data, u8 byte)
+{
+	u8 status;
+
+	/* First write to initialize the transfer */
+	if (init_trans == 0)
+		omap_hdq_get();
+
+	init_trans++;
+
+	hdq_write_byte(byte, &status);
+	pr_debug("Ctrl status %x\n", status);
+
+	/* Second write, data transfered. Release the module */
+	if (init_trans > 1) {
+		omap_hdq_put();
+		init_trans = 0;
+	}
+
+	return;
+}
+
+static int __init omap_hdq_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int ret, irq;
+	u8 rev;
+
+	if (!pdev)
+		return -ENODEV;
+
+	hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL);
+	if (!hdq_data)
+		return -ENODEV;
+
+	platform_set_drvdata(pdev, hdq_data);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (res == NULL) {
+		platform_set_drvdata(pdev, NULL);
+		kfree(hdq_data);
+		return -ENXIO;
+	}
+
+	hdq_data->hdq_base = res->start;
+
+	/* get interface & functional clock objects */
+	hdq_data->hdq_ick = clk_get(&pdev->dev, "hdq_ick");
+	hdq_data->hdq_fck = clk_get(&pdev->dev, "hdq_fck");
+
+	if (IS_ERR(hdq_data->hdq_ick) || IS_ERR(hdq_data->hdq_fck)) {
+		pr_debug("Can't get HDQ clock objects\n");
+		if (IS_ERR(hdq_data->hdq_ick)) {
+			ret = PTR_ERR(hdq_data->hdq_ick);
+			platform_set_drvdata(pdev, NULL);
+			kfree(hdq_data);
+			return ret;
+		}
+		if (IS_ERR(hdq_data->hdq_fck)) {
+			ret = PTR_ERR(hdq_data->hdq_fck);
+			platform_set_drvdata(pdev, NULL);
+			kfree(hdq_data);
+			return ret;
+		}
+	}
+
+	hdq_data->hdq_usecount = 0;
+	sema_init(&hdq_data->hdq_semlock, 1);
+
+	if (clk_enable(hdq_data->hdq_ick)) {
+		pr_debug("Can not enable ick\n");
+		clk_put(hdq_data->hdq_ick);
+		clk_put(hdq_data->hdq_fck);
+		platform_set_drvdata(pdev, NULL);
+		kfree(hdq_data);
+		return -ENODEV;
+	}
+
+	if (clk_enable(hdq_data->hdq_fck)) {
+		pr_debug("Can not enable fck\n");
+		clk_disable(hdq_data->hdq_ick);
+		clk_put(hdq_data->hdq_ick);
+		clk_put(hdq_data->hdq_fck);
+		platform_set_drvdata(pdev, NULL);
+		kfree(hdq_data);
+		return -ENODEV;
+	}
+
+	rev = hdq_reg_in(OMAP_HDQ_REVISION);
+	pr_info("OMAP HDQ Hardware Revision %c.%c. Driver in %s mode.\n",
+		(rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt");
+
+	spin_lock_init(&hdq_data->hdq_spinlock);
+	omap_hdq_break();
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq	< 0) {
+		platform_set_drvdata(pdev, NULL);
+		kfree(hdq_data);
+		return -ENXIO;
+	}
+
+	if (request_irq(irq, hdq_isr, IRQF_DISABLED, "OMAP HDQ",
+		&hdq_data->hdq_semlock)) {
+		pr_debug("request_irq failed\n");
+		clk_disable(hdq_data->hdq_ick);
+		clk_put(hdq_data->hdq_ick);
+		clk_put(hdq_data->hdq_fck);
+		platform_set_drvdata(pdev, NULL);
+		kfree(hdq_data);
+		return -ENODEV;
+	}
+
+	/* don't clock the HDQ until it is needed */
+	clk_disable(hdq_data->hdq_ick);
+	clk_disable(hdq_data->hdq_fck);
+
+	ret = w1_add_master_device(&omap_w1_master);
+	if (ret) {
+		pr_debug("Failure in registering w1 master\n");
+		clk_put(hdq_data->hdq_ick);
+		clk_put(hdq_data->hdq_fck);
+		platform_set_drvdata(pdev, NULL);
+		kfree(hdq_data);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int omap_hdq_remove(struct platform_device *pdev)
+{
+	down_interruptible(&hdq_data->hdq_semlock);
+	if (0 != hdq_data->hdq_usecount) {
+		pr_debug("removed when use count is not zero\n");
+		return -EBUSY;
+	}
+	up(&hdq_data->hdq_semlock);
+
+	/* remove module dependency */
+	clk_put(hdq_data->hdq_ick);
+	clk_put(hdq_data->hdq_fck);
+	free_irq(INT_24XX_HDQ_IRQ, &hdq_data->hdq_semlock);
+	platform_set_drvdata(pdev, NULL);
+	kfree(hdq_data);
+
+	return 0;
+}
+
+static int __init
+omap_hdq_init(void)
+{
+	return platform_driver_register(&omap_hdq_driver);
+}
+
+static void __exit
+omap_hdq_exit(void)
+{
+	platform_driver_unregister(&omap_hdq_driver);
+}
+
+module_init(omap_hdq_init);
+module_exit(omap_hdq_exit);
+
+module_param(W1_ID, int, S_IRUSR);
+
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("HDQ driver Library");
+MODULE_LICENSE("GPL");
-- 
1.6.0.1.141.g445ca


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

* [PATCH 33/33] add bq27000 1-wire slave driver
  2008-08-30 17:16                                                               ` [PATCH 32/33] add omap 1-wire interface driver Felipe Balbi
@ 2008-08-30 17:16                                                                 ` Felipe Balbi
  2008-09-01 13:16                                                                 ` [PATCH 32/33] add omap 1-wire interface driver Madhusudhan Chikkature
  1 sibling, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-30 17:16 UTC (permalink / raw)
  To: linux-omap; +Cc: Felipe Balbi

From: Felipe Balbi <felipe.balbi@nokia.com>

Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
---
 drivers/w1/slaves/Kconfig      |    8 +++
 drivers/w1/slaves/Makefile     |    2 +-
 drivers/w1/slaves/w1_bq27000.c |  120 ++++++++++++++++++++++++++++++++++++++++
 drivers/w1/w1.h                |    1 +
 drivers/w1/w1_io.c             |    3 +-
 5 files changed, 132 insertions(+), 2 deletions(-)
 create mode 100644 drivers/w1/slaves/w1_bq27000.c

diff --git a/drivers/w1/slaves/Kconfig b/drivers/w1/slaves/Kconfig
index 3df29a1..8960fe4 100644
--- a/drivers/w1/slaves/Kconfig
+++ b/drivers/w1/slaves/Kconfig
@@ -44,4 +44,12 @@ config W1_SLAVE_DS2760
 
 	  If you are unsure, say N.
 
+config W1_SLAVE_BQ27000
+	tristate "BQ27000 slave support"
+	depends on W1
+	help
+	  Say Y here if you want to use a hdq
+	  bq27000 slave support.
+
+
 endmenu
diff --git a/drivers/w1/slaves/Makefile b/drivers/w1/slaves/Makefile
index a8eb752..990f400 100644
--- a/drivers/w1/slaves/Makefile
+++ b/drivers/w1/slaves/Makefile
@@ -6,4 +6,4 @@ obj-$(CONFIG_W1_SLAVE_THERM)	+= w1_therm.o
 obj-$(CONFIG_W1_SLAVE_SMEM)	+= w1_smem.o
 obj-$(CONFIG_W1_SLAVE_DS2433)	+= w1_ds2433.o
 obj-$(CONFIG_W1_SLAVE_DS2760)	+= w1_ds2760.o
-
+obj-$(CONFIG_W1_SLAVE_BQ27000)	+= w1_bq27000.o
diff --git a/drivers/w1/slaves/w1_bq27000.c b/drivers/w1/slaves/w1_bq27000.c
new file mode 100644
index 0000000..0e8a3f3
--- /dev/null
+++ b/drivers/w1/slaves/w1_bq27000.c
@@ -0,0 +1,120 @@
+/*
+ * drivers/w1/slaves/w1_bq27000.c
+ *
+ * Copyright (C) 2007 Texas Instruments, Inc.
+ *
+ * This file is licensed under the terms of the GNU General Public License
+ * version 2. This program is licensed "as is" without any warranty of any
+ * kind, whether express or implied.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+
+#include "../w1.h"
+#include "../w1_int.h"
+#include "../w1_family.h"
+
+#define HDQ_CMD_READ	(0)
+#define HDQ_CMD_WRITE	(1<<7)
+
+int F_ID;
+int family_id;
+
+void w1_bq27000_write(struct device *dev, u8 buf, u8 reg)
+{
+	struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
+	if (!dev) {
+		pr_info("Could not obtain slave dev ptr\n");
+		return;
+	}
+
+	w1_write_8(sl->master, HDQ_CMD_WRITE | reg);
+	w1_write_8(sl->master, buf);
+}
+EXPORT_SYMBOL(w1_bq27000_write);
+
+int w1_bq27000_read(struct device *dev, u8 reg)
+{
+	u8 val;
+	struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
+	if (!dev)
+		return 0;
+
+	w1_write_8(sl->master, HDQ_CMD_READ | reg);
+	val = w1_read_8(sl->master);
+
+	return val;
+}
+EXPORT_SYMBOL(w1_bq27000_read);
+
+static int w1_bq27000_add_slave(struct w1_slave *sl)
+{
+	int ret;
+	int id = 1;
+	struct platform_device *pdev;
+
+	pdev = platform_device_alloc("bq27000-battery", id);
+	if (!pdev) {
+		ret = -ENOMEM;
+		return ret;
+	}
+	pdev->dev.parent = &sl->dev;
+
+	ret = platform_device_add(pdev);
+	if (ret)
+		goto pdev_add_failed;
+
+	dev_set_drvdata(&sl->dev, pdev);
+
+	goto success;
+
+pdev_add_failed:
+	platform_device_unregister(pdev);
+success:
+	return ret;
+}
+
+static void w1_bq27000_remove_slave(struct w1_slave *sl)
+{
+	struct platform_device *pdev = dev_get_drvdata(&sl->dev);
+	platform_device_unregister(pdev);
+}
+
+static struct w1_family_ops w1_bq27000_fops = {
+	.add_slave	= w1_bq27000_add_slave,
+	.remove_slave	= w1_bq27000_remove_slave,
+};
+
+static struct w1_family w1_bq27000_family = {
+	.fid = 1,
+	.fops = &w1_bq27000_fops,
+};
+
+static int __init w1_bq27000_init(void)
+{
+	if (F_ID)
+		w1_bq27000_family.fid = F_ID;
+
+	return w1_register_family(&w1_bq27000_family);
+}
+
+static void __exit w1_bq27000_exit(void)
+{
+	w1_unregister_family(&w1_bq27000_family);
+}
+
+
+module_init(w1_bq27000_init);
+module_exit(w1_bq27000_exit);
+
+module_param(F_ID, int, S_IRUSR);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Texas Instruments Ltd");
+MODULE_DESCRIPTION("HDQ/1-wire slave driver bq27000 battery monitor chip");
diff --git a/drivers/w1/w1.h b/drivers/w1/w1.h
index f1df534..b8189e8 100644
--- a/drivers/w1/w1.h
+++ b/drivers/w1/w1.h
@@ -189,6 +189,7 @@ struct w1_master *w1_search_master_id(u32 id);
 
 u8 w1_triplet(struct w1_master *dev, int bdir);
 void w1_write_8(struct w1_master *, u8);
+u8 w1_read_8(struct w1_master *);
 int w1_reset_bus(struct w1_master *);
 u8 w1_calc_crc8(u8 *, int);
 void w1_write_block(struct w1_master *, const u8 *, int);
diff --git a/drivers/w1/w1_io.c b/drivers/w1/w1_io.c
index 30b6fbf..da08537 100644
--- a/drivers/w1/w1_io.c
+++ b/drivers/w1/w1_io.c
@@ -177,7 +177,7 @@ u8 w1_triplet(struct w1_master *dev, int bdir)
  * @param dev     the master device
  * @return        the byte read
  */
-static u8 w1_read_8(struct w1_master * dev)
+u8 w1_read_8(struct w1_master *dev)
 {
 	int i;
 	u8 res = 0;
@@ -190,6 +190,7 @@ static u8 w1_read_8(struct w1_master * dev)
 
 	return res;
 }
+EXPORT_SYMBOL_GPL(w1_read_8);
 
 /**
  * Writes a series of bytes.
-- 
1.6.0.1.141.g445ca


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

* Re: [PATCH 01/33] add lp5521 driver
  2008-08-30 17:16 ` [PATCH 01/33] add lp5521 driver Felipe Balbi
       [not found]   ` <1220116593-862-3-git-send-email-me@felipebalbi.com>
@ 2008-08-31 20:39   ` David Brownell
  2008-08-31 21:41     ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 20:39 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Saturday 30 August 2008, Felipe Balbi wrote:
> +static int lp5521_configure(struct i2c_client *client)
> +{
> +       int ret = 0;
> +
> +       /* Enable chip and set light to logarithmic mode*/
> +       ret |= lp5521_write(client, LP5521_REG_ENABLE, 0xc0);
> +
> +       /* setting all color pwms to direct control mode */
> +       ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3f);
> +
> +       /* setting current to 4.7 mA for all channels */
> +       ret |= lp5521_write(client, LP5521_REG_R_CNTRL, LP5521_CURRENT_4m7);
> +       ret |= lp5521_write(client, LP5521_REG_G_CNTRL, LP5521_CURRENT_4m7);
> +       ret |= lp5521_write(client, LP5521_REG_B_CNTRL, LP5521_CURRENT_4m7);
> +
> +       /* Enable auto-powersave, set charge pump to auto, red to battery */
> +       ret |= lp5521_write(client, LP5521_REG_MISC, 0x3c);
> +

This highlights one reason many of these drivers haven't gone
upstream: embedding non-general assumptions.  In this case it
should be simple enough to define platform data to represent
various options like these ...

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 04/33] add omap gpio expander driver
  2008-08-30 17:16       ` [PATCH 04/33] add omap gpio expander driver Felipe Balbi
  2008-08-30 17:16         ` [PATCH 05/33] add tlv320aic23 driver Felipe Balbi
@ 2008-08-31 20:51         ` David Brownell
  2008-08-31 21:36           ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 20:51 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

NAK on this one..

On Saturday 30 August 2008, Felipe Balbi wrote:
> --- a/drivers/i2c/chips/Kconfig
> +++ b/drivers/i2c/chips/Kconfig
> @@ -137,6 +137,13 @@ config TPS65010
>           This driver can also be built as a module.  If so, the module
>           will be called tps65010.
>  
> +config GPIOEXPANDER_OMAP
> +       bool "GPIO Expander PCF8574PWR for OMAP"
> +       depends on I2C && (ARCH_OMAP16XX || ARCH_OMAP24XX)
> +       help
> +         If you say yes here you get support for I/O expander calls
> +         to configure IrDA, Camera and audio devices.
> +
>  config SENSORS_MAX6875
>         tristate "Maxim MAX6875 Power supply supervisor"
>         depends on EXPERIMENTAL

But drivers/gpio/pcf857x.c does just fine for this chip.  Rather
than push this upstream, switch over all users and delete this code.

ISTR having an example patch doing this for H4 boards.  I'll
dig that up and post it when I have time to refresh the code.

The only other user of this hack is the H3 board support.

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 12/33] Add tsc2005 touchscreen driver
  2008-08-30 17:16                       ` [PATCH 12/33] Add tsc2005 touchscreen driver Felipe Balbi
  2008-08-30 17:16                         ` [PATCH 13/33] Add omap " Felipe Balbi
@ 2008-08-31 20:53                         ` David Brownell
  2008-08-31 21:35                           ` Felipe Balbi
  2008-09-01  1:01                         ` andrzej zaborowski
  2008-09-02 21:33                         ` Russell King - ARM Linux
  3 siblings, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 20:53 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Saturday 30 August 2008, Felipe Balbi wrote:
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -217,6 +217,11 @@ config TOUCHSCREEN_ATMEL_TSADCC
>           To compile this driver as a module, choose M here: the
>           module will be called atmel_tsadcc.
>  
> +config TOUCHSCREEN_TSC2005
> +       tristate "TSC2005 touchscreen support"
> +       help
> +         Say Y here for if you are using the touchscreen features of TSC2301

TSC 2301 or 2005?
And shouldn't there be a TSC 2301 or 2005 driver to depend on?

> +
>  config TOUCHSCREEN_UCB1400
>         tristate "Philips UCB1400 touchscreen"
>         select AC97_BUS


--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 16/33] add omap led drivers
  2008-08-30 17:16                               ` [PATCH 16/33] add omap led drivers Felipe Balbi
  2008-08-30 17:16                                 ` [PATCH 17/33] add tea5761 radio driver Felipe Balbi
@ 2008-08-31 21:01                                 ` David Brownell
  2008-08-31 21:33                                   ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 21:01 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Saturday 30 August 2008, Felipe Balbi wrote:
> +config LEDS_OMAP_DEBUG
> +       boolean "LED Support for OMAP debug board LEDs"
> +       depends on LEDS_CLASS=y && ARCH_OMAP
> +       help
> +         Enables support for the LEDs on the debug board used with OMAP
> +         reference boards like H2/H3/H4 and Perseus2.  Up to six of these
> +         may be claimed by the original ARM debug LED API.

This stuff is ancient and should not be pushed upstream.
Anything not using arch/arm/plat-omap/debug-leds.c should
be converted immediately.  Ditto anything using the legacy
ARM led framework.


> +config LEDS_OMAP
> +       tristate "LED Support for OMAP GPIO LEDs"
> +       depends on LEDS_CLASS && ARCH_OMAP
> +       help
> +         This option enables support for the LEDs on OMAP processors.

Similarly obsolete.  Use drivers/leds/leds-gpio.c ...
Best to just remove this driver and convert its clients.


> +config LEDS_OMAP_PWM
> +       tristate "LED Support for OMAP PWM-controlled LEDs"
> +       depends on LEDS_CLASS && ARCH_OMAP && OMAP_DM_TIMER
> +       help
> +         This options enables support for LEDs connected to GPIO lines
> +         controlled by a PWM timer on OMAP CPUs.

This is more plausible.  But looking -- briefly! -- at the code, I
don't see why it needs a work_struct at all, or why it's not using
the blink_set() method to control the hardware blink rate.

- Dave

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-30 17:16                                         ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver Felipe Balbi
  2008-08-30 17:16                                           ` [PATCH 22/33] add omap nand drivers Felipe Balbi
@ 2008-08-31 21:06                                           ` David Brownell
  2008-08-31 21:31                                             ` Felipe Balbi
  2008-09-04 13:26                                             ` Madhusudhan Chikkature
  2008-09-02 22:05                                           ` Russell King - ARM Linux
  2 siblings, 2 replies; 86+ messages in thread
From: David Brownell @ 2008-08-31 21:06 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Saturday 30 August 2008, Felipe Balbi wrote:
> From: Felipe Balbi <felipe.balbi@nokia.com>
> 
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> ---
>  drivers/mmc/host/Kconfig      |   13 +-
>  drivers/mmc/host/Makefile     |    1 +
>  drivers/mmc/host/omap_hsmmc.c | 1069 +++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 1082 insertions(+), 1 deletions(-)
>  create mode 100644 drivers/mmc/host/omap_hsmmc.c

OK, this one may need cleanup ... but I don't disagree that it
should go upstream.

Now the process for getting it there involves posting to LKML
and probably CCing the MMC maintainer...

One reason I'd like to see this particular driver upstream is
so that folk using www.beagleboard.org hardware can stand a
real chance of using mainline kernels for development.  That's
a small set of drivers, so that's a very achievable goal.



--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 22/33] add omap nand drivers
  2008-08-30 17:16                                           ` [PATCH 22/33] add omap nand drivers Felipe Balbi
  2008-08-30 17:16                                             ` [PATCH 23/33] add omap irda driver Felipe Balbi
@ 2008-08-31 21:08                                             ` David Brownell
  2008-08-31 21:28                                               ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 21:08 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

Hmm, this seems like three distinct drivers to me.
Which should mean three separate patches ... going
go the MTD list!


On Saturday 30 August 2008, Felipe Balbi wrote:
> 
> +config MTD_NAND_OMAP2
> +       tristate "NAND Flash device on OMAP2 and OMAP3"
> +       depends on ARM && MTD_NAND && (ARCH_OMAP2 || ARCH_OMAP3)
> +       help
> +          Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms.

I've got a small patch for this one which I'll post.


> +config MTD_NAND_OMAP
> +       tristate "NAND Flash device on OMAP H3/H2/P2 boards"
> +       depends on ARM && ARCH_OMAP1 && MTD_NAND && (MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_PERSEUS2)
> +       help
> +         Support for NAND flash on Texas Instruments H3/H2/P2 platforms.
> +
> +config MTD_NAND_OMAP_HW
> +       bool "OMAP HW NAND Flash controller support"
> +        depends on ARM && ARCH_OMAP16XX && MTD_NAND
> +
> +       help
> +         Driver for TI OMAP16xx hardware NAND flash controller.

This one raises the question:  why are there *TWO* drivers for
the same hardware?  Which one is the failed experiment?

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 29/33] add omap ehci bus glue
  2008-08-30 17:16                                                         ` [PATCH 29/33] add omap ehci bus glue Felipe Balbi
  2008-08-30 17:16                                                           ` [PATCH 30/33] add omap backlight support Felipe Balbi
@ 2008-08-31 21:09                                                           ` David Brownell
  2008-08-31 21:25                                                             ` Felipe Balbi
  2008-09-01  5:55                                                           ` Gadiyar, Anand
  2 siblings, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 21:09 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Saturday 30 August 2008, Felipe Balbi wrote:
> +choice
> +       prompt "PHY/TLL mode"
> +       depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> +       ---help---
> +       Choose PHY or TLL mode of operation
> +
> +config OMAP_EHCI_PHY_MODE
> +       bool "PHY mode: ISP1504 on Port1/2 (NEW 3430ES2.0)"
> +       depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> +       ---help---
> +         EHCI PHY mode. Port1 and Port2 are connected to ISP1504 transcievers
> +
> +config OMAP_EHCI_TLL_MODE
> +       bool "TLL mode: (EXPERIMENTAL)"
> +       depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> +       ---help---
> +       OMAP EHCI controller has TLL mode of operation for all 3 ports.
> +       Use this mode when no transciever is present

All that stuff should be platform data, not Kconfig options.

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 30/33] add omap backlight support
  2008-08-30 17:16                                                           ` [PATCH 30/33] add omap backlight support Felipe Balbi
  2008-08-30 17:16                                                             ` [PATCH 31/33] add several omap lcd drivers Felipe Balbi
@ 2008-08-31 21:12                                                             ` David Brownell
  2008-08-31 21:23                                                               ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 21:12 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Saturday 30 August 2008, Felipe Balbi wrote:
> ---
>  drivers/video/backlight/Kconfig   |    9 ++
>  drivers/video/backlight/Makefile  |    1 +
>  drivers/video/backlight/omap_bl.c |  218 +++++++++++++++++++++++++++++++++++++
>  3 files changed, 228 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/video/backlight/omap_bl.c

How is this different from drivers/video/backlight/omap1_bl.c
which is already upstream?

Looks to me like this is an old version of the same code, and
this driver should have been removed ... double check to make
sure no bugfixes are missing upstream.

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 30/33] add omap backlight support
  2008-08-31 21:12                                                             ` [PATCH 30/33] add omap backlight support David Brownell
@ 2008-08-31 21:23                                                               ` Felipe Balbi
  2008-08-31 21:44                                                                 ` Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:23 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 02:12:47PM -0700, David Brownell wrote:
> On Saturday 30 August 2008, Felipe Balbi wrote:
> > ---
> >  drivers/video/backlight/Kconfig   |    9 ++
> >  drivers/video/backlight/Makefile  |    1 +
> >  drivers/video/backlight/omap_bl.c |  218 +++++++++++++++++++++++++++++++++++++
> >  3 files changed, 228 insertions(+), 0 deletions(-)
> >  create mode 100644 drivers/video/backlight/omap_bl.c
> 
> How is this different from drivers/video/backlight/omap1_bl.c
> which is already upstream?
> 
> Looks to me like this is an old version of the same code, and
> this driver should have been removed ... double check to make
> sure no bugfixes are missing upstream.

Weird that git marked this one as new file :-s

I'm dropping from the series.

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 29/33] add omap ehci bus glue
  2008-08-31 21:09                                                           ` [PATCH 29/33] add omap ehci bus glue David Brownell
@ 2008-08-31 21:25                                                             ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:25 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 02:09:42PM -0700, David Brownell wrote:
> On Saturday 30 August 2008, Felipe Balbi wrote:
> > +choice
> > +       prompt "PHY/TLL mode"
> > +       depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> > +       ---help---
> > +       Choose PHY or TLL mode of operation
> > +
> > +config OMAP_EHCI_PHY_MODE
> > +       bool "PHY mode: ISP1504 on Port1/2 (NEW 3430ES2.0)"
> > +       depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> > +       ---help---
> > +         EHCI PHY mode. Port1 and Port2 are connected to ISP1504 transcievers
> > +
> > +config OMAP_EHCI_TLL_MODE
> > +       bool "TLL mode: (EXPERIMENTAL)"
> > +       depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> > +       ---help---
> > +       OMAP EHCI controller has TLL mode of operation for all 3 ports.
> > +       Use this mode when no transciever is present
> 
> All that stuff should be platform data, not Kconfig options.

Hmm... someone else will have to fix that. Will drop from the series
from now as well.

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 22/33] add omap nand drivers
  2008-08-31 21:08                                             ` [PATCH 22/33] add omap nand drivers David Brownell
@ 2008-08-31 21:28                                               ` Felipe Balbi
  2008-09-05 18:07                                                 ` Tony Lindgren
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:28 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 02:08:18PM -0700, David Brownell wrote:
> Hmm, this seems like three distinct drivers to me.
> Which should mean three separate patches ... going
> go the MTD list!
> 
> 
> On Saturday 30 August 2008, Felipe Balbi wrote:
> > 
> > +config MTD_NAND_OMAP2
> > +       tristate "NAND Flash device on OMAP2 and OMAP3"
> > +       depends on ARM && MTD_NAND && (ARCH_OMAP2 || ARCH_OMAP3)
> > +       help
> > +          Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms.
> 
> I've got a small patch for this one which I'll post.

Great, I can meld it on the final version of the driver. Thanks

> > +config MTD_NAND_OMAP
> > +       tristate "NAND Flash device on OMAP H3/H2/P2 boards"
> > +       depends on ARM && ARCH_OMAP1 && MTD_NAND && (MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_PERSEUS2)
> > +       help
> > +         Support for NAND flash on Texas Instruments H3/H2/P2 platforms.
> > +
> > +config MTD_NAND_OMAP_HW
> > +       bool "OMAP HW NAND Flash controller support"
> > +        depends on ARM && ARCH_OMAP16XX && MTD_NAND
> > +
> > +       help
> > +         Driver for TI OMAP16xx hardware NAND flash controller.
> 
> This one raises the question:  why are there *TWO* drivers for
> the same hardware?  Which one is the failed experiment?

It's even worse. If you look at the Makefile itself, there are three:

obj-$(CONFIG_MTD_NAND_OMAP) 		+= omap-nand-flash.o
obj-$(CONFIG_MTD_NAND_OMAP2) 		+= omap2.o
obj-$(CONFIG_MTD_NAND_OMAP_HW)		+= omap-hw.o

Looks like this series was good for finding some weird stuff on
linux-omap ;-)

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-31 21:06                                           ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver David Brownell
@ 2008-08-31 21:31                                             ` Felipe Balbi
  2008-08-31 21:51                                               ` David Brownell
  2008-09-04 13:26                                             ` Madhusudhan Chikkature
  1 sibling, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:31 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 02:06:13PM -0700, David Brownell wrote:
> On Saturday 30 August 2008, Felipe Balbi wrote:
> > From: Felipe Balbi <felipe.balbi@nokia.com>
> > 
> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> > ---
> >  drivers/mmc/host/Kconfig      |   13 +-
> >  drivers/mmc/host/Makefile     |    1 +
> >  drivers/mmc/host/omap_hsmmc.c | 1069 +++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 1082 insertions(+), 1 deletions(-)
> >  create mode 100644 drivers/mmc/host/omap_hsmmc.c
> 
> OK, this one may need cleanup ... but I don't disagree that it
> should go upstream.
> 
> Now the process for getting it there involves posting to LKML
> and probably CCing the MMC maintainer...
> 
> One reason I'd like to see this particular driver upstream is
> so that folk using www.beagleboard.org hardware can stand a
> real chance of using mainline kernels for development.  That's
> a small set of drivers, so that's a very achievable goal.

Agreed. But we're missing omap3 stuff on mainline still, so even if we
have all the drivers there. Mainline has support up to omap2430, if i'm
not wrong. So looks like beagleboard users will have to wait a bit
longer.

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 16/33] add omap led drivers
  2008-08-31 21:01                                 ` [PATCH 16/33] add omap led drivers David Brownell
@ 2008-08-31 21:33                                   ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:33 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 02:01:44PM -0700, David Brownell wrote:
> On Saturday 30 August 2008, Felipe Balbi wrote:
> > +config LEDS_OMAP_DEBUG
> > +       boolean "LED Support for OMAP debug board LEDs"
> > +       depends on LEDS_CLASS=y && ARCH_OMAP
> > +       help
> > +         Enables support for the LEDs on the debug board used with OMAP
> > +         reference boards like H2/H3/H4 and Perseus2.  Up to six of these
> > +         may be claimed by the original ARM debug LED API.
> 
> This stuff is ancient and should not be pushed upstream.
> Anything not using arch/arm/plat-omap/debug-leds.c should
> be converted immediately.  Ditto anything using the legacy
> ARM led framework.
> 
> 
> > +config LEDS_OMAP
> > +       tristate "LED Support for OMAP GPIO LEDs"
> > +       depends on LEDS_CLASS && ARCH_OMAP
> > +       help
> > +         This option enables support for the LEDs on OMAP processors.
> 
> Similarly obsolete.  Use drivers/leds/leds-gpio.c ...
> Best to just remove this driver and convert its clients.
> 
> 
> > +config LEDS_OMAP_PWM
> > +       tristate "LED Support for OMAP PWM-controlled LEDs"
> > +       depends on LEDS_CLASS && ARCH_OMAP && OMAP_DM_TIMER
> > +       help
> > +         This options enables support for LEDs connected to GPIO lines
> > +         controlled by a PWM timer on OMAP CPUs.
> 
> This is more plausible.  But looking -- briefly! -- at the code, I
> don't see why it needs a work_struct at all, or why it's not using
> the blink_set() method to control the hardware blink rate.

One more to drop from the series. Someone will have to take care of
these drivers.

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 12/33] Add tsc2005 touchscreen driver
  2008-08-31 20:53                         ` [PATCH 12/33] Add tsc2005 touchscreen driver David Brownell
@ 2008-08-31 21:35                           ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:35 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 01:53:37PM -0700, David Brownell wrote:
> On Saturday 30 August 2008, Felipe Balbi wrote:
> > --- a/drivers/input/touchscreen/Kconfig
> > +++ b/drivers/input/touchscreen/Kconfig
> > @@ -217,6 +217,11 @@ config TOUCHSCREEN_ATMEL_TSADCC
> >           To compile this driver as a module, choose M here: the
> >           module will be called atmel_tsadcc.
> >  
> > +config TOUCHSCREEN_TSC2005
> > +       tristate "TSC2005 touchscreen support"
> > +       help
> > +         Say Y here for if you are using the touchscreen features of TSC2301
> 
> TSC 2301 or 2005?
> And shouldn't there be a TSC 2301 or 2005 driver to depend on?

My bad, didn't see the typo on linux-omap. I'll fix on both trees and
send patch to l-o soonish.

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 04/33] add omap gpio expander driver
  2008-08-31 20:51         ` [PATCH 04/33] add omap gpio expander driver David Brownell
@ 2008-08-31 21:36           ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:36 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 01:51:52PM -0700, David Brownell wrote:
> NAK on this one..

Cool, patch dropped.

-- 
balbi

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

* Re: [PATCH 01/33] add lp5521 driver
  2008-08-31 20:39   ` [PATCH 01/33] add lp5521 driver David Brownell
@ 2008-08-31 21:41     ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:41 UTC (permalink / raw)
  To: David Brownell; +Cc: Felipe Balbi, linux-omap, Felipe Balbi, Mathias Nyman

On Sun, Aug 31, 2008 at 01:39:21PM -0700, David Brownell wrote:
> On Saturday 30 August 2008, Felipe Balbi wrote:
> > +static int lp5521_configure(struct i2c_client *client)
> > +{
> > +       int ret = 0;
> > +
> > +       /* Enable chip and set light to logarithmic mode*/
> > +       ret |= lp5521_write(client, LP5521_REG_ENABLE, 0xc0);
> > +
> > +       /* setting all color pwms to direct control mode */
> > +       ret |= lp5521_write(client, LP5521_REG_OP_MODE, 0x3f);
> > +
> > +       /* setting current to 4.7 mA for all channels */
> > +       ret |= lp5521_write(client, LP5521_REG_R_CNTRL, LP5521_CURRENT_4m7);
> > +       ret |= lp5521_write(client, LP5521_REG_G_CNTRL, LP5521_CURRENT_4m7);
> > +       ret |= lp5521_write(client, LP5521_REG_B_CNTRL, LP5521_CURRENT_4m7);
> > +
> > +       /* Enable auto-powersave, set charge pump to auto, red to battery */
> > +       ret |= lp5521_write(client, LP5521_REG_MISC, 0x3c);
> > +
> 
> This highlights one reason many of these drivers haven't gone
> upstream: embedding non-general assumptions.  In this case it
> should be simple enough to define platform data to represent
> various options like these ...

Also dropped. The author should take a look at this. I'm putting him in
the loop.

Mathias, any comments on this ??

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 30/33] add omap backlight support
  2008-08-31 21:23                                                               ` Felipe Balbi
@ 2008-08-31 21:44                                                                 ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 21:44 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: David Brownell, linux-omap, Felipe Balbi

On Mon, Sep 01, 2008 at 12:23:20AM +0300, Felipe Balbi wrote:
> On Sun, Aug 31, 2008 at 02:12:47PM -0700, David Brownell wrote:
> > On Saturday 30 August 2008, Felipe Balbi wrote:
> > > ---
> > >  drivers/video/backlight/Kconfig   |    9 ++
> > >  drivers/video/backlight/Makefile  |    1 +
> > >  drivers/video/backlight/omap_bl.c |  218 +++++++++++++++++++++++++++++++++++++
> > >  3 files changed, 228 insertions(+), 0 deletions(-)
> > >  create mode 100644 drivers/video/backlight/omap_bl.c
> > 
> > How is this different from drivers/video/backlight/omap1_bl.c
> > which is already upstream?
> > 
> > Looks to me like this is an old version of the same code, and
> > this driver should have been removed ... double check to make
> > sure no bugfixes are missing upstream.
> 
> Weird that git marked this one as new file :-s
> 
> I'm dropping from the series.

The problem is on linux-omap. It has a file without Makefile and Kconfig
entries. I'm sending a patch soonish.

-- 
balbi
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-31 21:31                                             ` Felipe Balbi
@ 2008-08-31 21:51                                               ` David Brownell
  2008-08-31 22:02                                                 ` Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: David Brownell @ 2008-08-31 21:51 UTC (permalink / raw)
  To: me; +Cc: linux-omap, Felipe Balbi

On Sunday 31 August 2008, Felipe Balbi wrote:
> 
> > One reason I'd like to see this particular driver upstream is
> > so that folk using www.beagleboard.org hardware can stand a
> > real chance of using mainline kernels for development.  That's
> > a small set of drivers, so that's a very achievable goal.
> 
> Agreed. But we're missing omap3 stuff on mainline still,

Fixable, I'd expect.  :)


> so even if we 
> have all the drivers there. Mainline has support up to omap2430, if i'm
> not wrong. So looks like beagleboard users will have to wait a bit
> longer.

Well, it's not a 2.6.27 goal.  But I'd think it should be very
possible for 2.6.28 merging.

However, I'll re-emphasize that doing that means submitting the
patches for real, not to the OMAP list.  And since mainline is
now at RC5, there's not a lot of time yet to get this new code
reviewed and entered into the MMC queue...

- Dave
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-31 21:51                                               ` David Brownell
@ 2008-08-31 22:02                                                 ` Felipe Balbi
  2008-09-04  1:02                                                   ` Tony Lindgren
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 22:02 UTC (permalink / raw)
  To: David Brownell; +Cc: me, linux-omap, Felipe Balbi

On Sun, Aug 31, 2008 at 02:51:18PM -0700, David Brownell wrote:
> Fixable, I'd expect.  :)

I'm sure it is :-)

> > so even if we 
> > have all the drivers there. Mainline has support up to omap2430, if i'm
> > not wrong. So looks like beagleboard users will have to wait a bit
> > longer.
> 
> Well, it's not a 2.6.27 goal.  But I'd think it should be very
> possible for 2.6.28 merging.

Should be possible, but Tony can say for sure.

> However, I'll re-emphasize that doing that means submitting the
> patches for real, not to the OMAP list.  And since mainline is
> now at RC5, there's not a lot of time yet to get this new code
> reviewed and entered into the MMC queue...

I think it's not a must to merge it for 2.6.27 time, but 2.6.28 would be
good enough. As long as we merge them upstream and avoid merge conflicts
on l-o, I'd be happy. Besides the fact that drivers should be discussed
on the proper mainling list for facts that you already said in another
comment, right ? :-)

-- 
balbi

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

* Re: [PATCH 00/34] omap drivers going upstream
  2008-08-30 17:16 [PATCH 00/34] omap drivers going upstream Felipe Balbi
  2008-08-30 17:16 ` [PATCH 01/33] add lp5521 driver Felipe Balbi
@ 2008-08-31 22:37 ` Felipe Balbi
  2008-08-31 22:40   ` Felipe Balbi
  2008-09-01  1:22   ` andrzej zaborowski
  2008-09-01  1:46 ` andrzej zaborowski
  2 siblings, 2 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 22:37 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Sat, Aug 30, 2008 at 08:16:00PM +0300, Felipe Balbi wrote:
> From: Felipe Balbi <felipe.balbi@nokia.com>
> 
> The following drivers are going upstream for integration.
> They have been sitting on linux-omap for quite a while just
> increasing the diff against mainline and probability of
> merge conflicts.

Just one comment to this. I had to left bluetooth driver out of the
series because it's using omap2_block/allow_sleep in the driver code.
That should be fixed and the set_clock function should come via
platform_data to the driver.

  53 static void hci_h4p_set_clk(struct hci_h4p_info *info, int *clock, int enable)
  54 {
  55         unsigned long flags;
  56 
  57         spin_lock_irqsave(&info->clocks_lock, flags);
  58         if (enable && !*clock) {
  59                 NBT_DBG_POWER("Enabling %p\n", clock);
  60                 clk_enable(info->uart_fclk);
  61 #ifdef CONFIG_ARCH_OMAP2
  62                 if (cpu_is_omap24xx()) {
  63                         clk_enable(info->uart_iclk);
  64                         omap2_block_sleep();
  65                 }
  66 #endif
  67         }
  68         if (!enable && *clock) {
  69                 NBT_DBG_POWER("Disabling %p\n", clock);
  70                 clk_disable(info->uart_fclk);
  71 #ifdef CONFIG_ARCH_OMAP2
  72                 if (cpu_is_omap24xx()) {
  73                         clk_disable(info->uart_iclk);
  74                         omap2_allow_sleep();
  75                 }
  76 #endif
  77         }
  78 
  79         *clock = enable;
  80         spin_unlock_irqrestore(&info->clocks_lock, flags);
  81 }

That driver is full of arch specific code and should be cleaned up ASAP.

A few things I could get by briefly looking at the driver (actualy only
drivers/bluetooth/hci_h4p/core.c):

* set_clock should come via platform_data
* gpio handling should use gpiolib instead of omap-specific ones
* irq should come via platform_data to avoid 'if (cpu_is_xxx())'
* unnecessary casts should be removed
* one line ifs do not need {}
* there's a memory leak on probe if no platform_data is found
* use of io_p2v() should be avoided on drivers, refer to Russel King's
  patch
* OMAP_GPIO_IRQ() should not be used on the driver.

-- 
balbi

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

* Re: [PATCH 00/34] omap drivers going upstream
  2008-08-31 22:37 ` [PATCH 00/34] omap drivers going upstream Felipe Balbi
@ 2008-08-31 22:40   ` Felipe Balbi
  2008-09-01  1:22   ` andrzej zaborowski
  1 sibling, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-08-31 22:40 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Mon, Sep 01, 2008 at 01:37:37AM +0300, Felipe Balbi wrote:
> On Sat, Aug 30, 2008 at 08:16:00PM +0300, Felipe Balbi wrote:
> > From: Felipe Balbi <felipe.balbi@nokia.com>
> > 
> > The following drivers are going upstream for integration.
> > They have been sitting on linux-omap for quite a while just
> > increasing the diff against mainline and probability of
> > merge conflicts.
> 
> Just one comment to this. I had to left bluetooth driver out of the

typo, 'I had to leave'

-- 
balbi

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

* Re: [PATCH 12/33] Add tsc2005 touchscreen driver
  2008-08-30 17:16                       ` [PATCH 12/33] Add tsc2005 touchscreen driver Felipe Balbi
  2008-08-30 17:16                         ` [PATCH 13/33] Add omap " Felipe Balbi
  2008-08-31 20:53                         ` [PATCH 12/33] Add tsc2005 touchscreen driver David Brownell
@ 2008-09-01  1:01                         ` andrzej zaborowski
  2008-09-01  7:34                           ` Daniel Stone
  2008-09-02 21:33                         ` Russell King - ARM Linux
  3 siblings, 1 reply; 86+ messages in thread
From: andrzej zaborowski @ 2008-09-01  1:01 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

2008/8/30 Felipe Balbi <me@felipebalbi.com>:
> From: Felipe Balbi <felipe.balbi@nokia.com>
>
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> ---
>  drivers/input/touchscreen/Kconfig   |    5 +
>  drivers/input/touchscreen/Makefile  |    1 +
>  drivers/input/touchscreen/tsc2005.c |  736 +++++++++++++++++++++++++++++++++++
>  include/linux/spi/tsc2005.h         |   29 ++
>  4 files changed, 771 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/input/touchscreen/tsc2005.c
>  create mode 100644 include/linux/spi/tsc2005.h
>
> diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
> index 25287e8..a115f38 100644
> --- a/drivers/input/touchscreen/Kconfig
> +++ b/drivers/input/touchscreen/Kconfig
> @@ -217,6 +217,11 @@ config TOUCHSCREEN_ATMEL_TSADCC
>          To compile this driver as a module, choose M here: the
>          module will be called atmel_tsadcc.
>
> +config TOUCHSCREEN_TSC2005
> +       tristate "TSC2005 touchscreen support"
> +       help
> +         Say Y here for if you are using the touchscreen features of TSC2301.
> +
>  config TOUCHSCREEN_UCB1400
>        tristate "Philips UCB1400 touchscreen"
>        select AC97_BUS
> diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
> index 15cf290..0342389 100644
> --- a/drivers/input/touchscreen/Makefile
> +++ b/drivers/input/touchscreen/Makefile
> @@ -26,6 +26,7 @@ obj-$(CONFIG_TOUCHSCREEN_TOUCHIT213)  += touchit213.o
>  obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT)   += touchright.o
>  obj-$(CONFIG_TOUCHSCREEN_TOUCHWIN)     += touchwin.o
>  obj-$(CONFIG_TOUCHSCREEN_UCB1400)      += ucb1400_ts.o
> +obj-$(CONFIG_TOUCHSCREEN_TSC2005)      += tsc2005.o
>  obj-$(CONFIG_TOUCHSCREEN_WM97XX)       += wm97xx-ts.o
>  wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9705) += wm9705.o
>  wm97xx-ts-$(CONFIG_TOUCHSCREEN_WM9712) += wm9712.o
> diff --git a/drivers/input/touchscreen/tsc2005.c b/drivers/input/touchscreen/tsc2005.c
> new file mode 100644
> index 0000000..7fb107e
> --- /dev/null
> +++ b/drivers/input/touchscreen/tsc2005.c
> @@ -0,0 +1,736 @@
> +/*
> + * TSC2005 touchscreen driver
> + *
> + * Copyright (C) 2006-2008 Nokia Corporation
> + *
> + * Author: Lauri Leukkunen <lauri.leukkunen@nokia.com>
> + * based on TSC2301 driver by Klaus K. Pedersen <klaus.k.pedersen@nokia.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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
> + *
> + */
> +
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/spi/spi.h>
> +
> +#ifdef CONFIG_ARCH_OMAP
> +#include <mach/gpio.h>
> +#endif
> +
> +#include <linux/spi/tsc2005.h>
> +
> +/**
> + * The touchscreen interface operates as follows:
> + *
> + * Initialize:
> + *    Request access to GPIO103 (DAV)
> + *    tsc2005_dav_irq_handler will trigger when DAV line goes down
> + *
> + *  1) Pen is pressed against touchscreeen
> + *  2) TSC2005 performs AD conversion
> + *  3) After the conversion is done TSC2005 drives DAV line down
> + *  4) GPIO IRQ is received and tsc2005_dav_irq_handler is called
> + *  5) tsc2005_ts_irq_handler queues up an spi transfer to fetch
> + *     the x, y, z1, z2 values
> + *  6) tsc2005_ts_rx() reports coordinates to input layer and
> + *     sets up tsc2005_ts_timer() to be called after TSC2005_TS_SCAN_TIME
> + *  7)  When the penup_timer expires, there have not been DAV interrupts
> + *     during the last 20ms which means the pen has been lifted.
> + */
> +
> +#define TSC2005_VDD_LOWER_27
> +
> +#ifdef TSC2005_VDD_LOWER_27
> +#define TSC2005_HZ     (10000000)
> +#else
> +#define TSC2005_HZ     (25000000)
> +#endif
> +
> +#define TSC2005_CMD    (0x80)
> +#define TSC2005_REG    (0x00)
> +
> +#define TSC2005_CMD_STOP       (1)
> +#define TSC2005_CMD_10BIT      (0 << 2)
> +#define TSC2005_CMD_12BIT      (1 << 2)
> +
> +#define TSC2005_CMD_SCAN_XYZZ  (0 << 3)
> +#define TSC2005_CMD_SCAN_XY    (1 << 3)
> +#define TSC2005_CMD_SCAN_X     (2 << 3)
> +#define TSC2005_CMD_SCAN_Y     (3 << 3)
> +#define TSC2005_CMD_SCAN_ZZ    (4 << 3)
> +#define TSC2005_CMD_AUX_SINGLE (5 << 3)
> +#define TSC2005_CMD_TEMP1      (6 << 3)
> +#define TSC2005_CMD_TEMP2      (7 << 3)
> +#define TSC2005_CMD_AUX_CONT   (8 << 3)
> +#define TSC2005_CMD_TEST_X_CONN        (9 << 3)
> +#define TSC2005_CMD_TEST_Y_CONN        (10 << 3)
> +/* command 11 reserved */
> +#define TSC2005_CMD_TEST_SHORT (12 << 3)
> +#define TSC2005_CMD_DRIVE_XX   (13 << 3)
> +#define TSC2005_CMD_DRIVE_YY   (14 << 3)
> +#define TSC2005_CMD_DRIVE_YX   (15 << 3)
> +
> +#define TSC2005_REG_X          (0 << 3)
> +#define TSC2005_REG_Y          (1 << 3)
> +#define TSC2005_REG_Z1         (2 << 3)
> +#define TSC2005_REG_Z2         (3 << 3)
> +#define TSC2005_REG_AUX                (4 << 3)
> +#define TSC2005_REG_TEMP1      (5 << 3)
> +#define TSC2005_REG_TEMP2      (6 << 3)
> +#define TSC2005_REG_STATUS     (7 << 3)
> +#define TSC2005_REG_AUX_HIGH   (8 << 3)
> +#define TSC2005_REG_AUX_LOW    (9 << 3)
> +#define TSC2005_REG_TEMP_HIGH  (10 << 3)
> +#define TSC2005_REG_TEMP_LOW   (11 << 3)
> +#define TSC2005_REG_CFR0       (12 << 3)
> +#define TSC2005_REG_CFR1       (13 << 3)
> +#define TSC2005_REG_CFR2       (14 << 3)
> +#define TSC2005_REG_FUNCTION   (15 << 3)
> +
> +#define TSC2005_REG_PND0       (1 << 1)
> +#define TSC2005_REG_READ       (0x01)
> +#define TSC2005_REG_WRITE      (0x00)
> +
> +
> +#define TSC2005_CFR0_LONGSAMPLING      (1)
> +#define TSC2005_CFR0_DETECTINWAIT      (1 << 1)
> +#define TSC2005_CFR0_SENSETIME_32US    (0)
> +#define TSC2005_CFR0_SENSETIME_96US    (1 << 2)
> +#define TSC2005_CFR0_SENSETIME_544US   (1 << 3)
> +#define TSC2005_CFR0_SENSETIME_2080US  (1 << 4)
> +#define TSC2005_CFR0_SENSETIME_2656US  (0x001C)
> +#define TSC2005_CFR0_PRECHARGE_20US    (0x0000)
> +#define TSC2005_CFR0_PRECHARGE_84US    (0x0020)
> +#define TSC2005_CFR0_PRECHARGE_276US   (0x0040)
> +#define TSC2005_CFR0_PRECHARGE_1044US  (0x0080)
> +#define TSC2005_CFR0_PRECHARGE_1364US  (0x00E0)
> +#define TSC2005_CFR0_STABTIME_0US      (0x0000)
> +#define TSC2005_CFR0_STABTIME_100US    (0x0100)
> +#define TSC2005_CFR0_STABTIME_500US    (0x0200)
> +#define TSC2005_CFR0_STABTIME_1MS      (0x0300)
> +#define TSC2005_CFR0_STABTIME_5MS      (0x0400)
> +#define TSC2005_CFR0_STABTIME_100MS    (0x0700)
> +#define TSC2005_CFR0_CLOCK_4MHZ                (0x0000)
> +#define TSC2005_CFR0_CLOCK_2MHZ                (0x0800)
> +#define TSC2005_CFR0_CLOCK_1MHZ                (0x1000)
> +#define TSC2005_CFR0_RESOLUTION12      (0x2000)
> +#define TSC2005_CFR0_STATUS            (0x4000)
> +#define TSC2005_CFR0_PENMODE           (0x8000)
> +
> +#define TSC2005_CFR0_INITVALUE (TSC2005_CFR0_STABTIME_1MS  |   \
> +                                TSC2005_CFR0_CLOCK_1MHZ    |   \
> +                                TSC2005_CFR0_RESOLUTION12  |   \
> +                                TSC2005_CFR0_PRECHARGE_276US | \
> +                                TSC2005_CFR0_PENMODE)
> +
> +#define TSC2005_CFR1_BATCHDELAY_0MS    (0x0000)
> +#define TSC2005_CFR1_BATCHDELAY_1MS    (0x0001)
> +#define TSC2005_CFR1_BATCHDELAY_2MS    (0x0002)
> +#define TSC2005_CFR1_BATCHDELAY_4MS    (0x0003)
> +#define TSC2005_CFR1_BATCHDELAY_10MS   (0x0004)
> +#define TSC2005_CFR1_BATCHDELAY_20MS   (0x0005)
> +#define TSC2005_CFR1_BATCHDELAY_40MS   (0x0006)
> +#define TSC2005_CFR1_BATCHDELAY_100MS  (0x0007)
> +
> +#define TSC2005_CFR1_INITVALUE (TSC2005_CFR1_BATCHDELAY_2MS)
> +
> +#define TSC2005_CFR2_MAVE_TEMP (0x0001)
> +#define TSC2005_CFR2_MAVE_AUX  (0x0002)
> +#define TSC2005_CFR2_MAVE_Z    (0x0004)
> +#define TSC2005_CFR2_MAVE_Y    (0x0008)
> +#define TSC2005_CFR2_MAVE_X    (0x0010)
> +#define TSC2005_CFR2_AVG_1     (0x0000)
> +#define TSC2005_CFR2_AVG_3     (0x0400)
> +#define TSC2005_CFR2_AVG_7     (0x0800)
> +#define TSC2005_CFR2_MEDIUM_1  (0x0000)
> +#define TSC2005_CFR2_MEDIUM_3  (0x1000)
> +#define TSC2005_CFR2_MEDIUM_7  (0x2000)
> +#define TSC2005_CFR2_MEDIUM_15 (0x3000)
> +
> +#define TSC2005_CFR2_IRQ_DAV   (0x4000)
> +#define TSC2005_CFR2_IRQ_PEN   (0x8000)
> +#define TSC2005_CFR2_IRQ_PENDAV        (0x0000)
> +
> +#define TSC2005_CFR2_INITVALUE (TSC2005_CFR2_IRQ_DAV   |       \
> +                                TSC2005_CFR2_MAVE_X    |       \
> +                                TSC2005_CFR2_MAVE_Y    |       \
> +                                TSC2005_CFR2_MAVE_Z    |       \
> +                                TSC2005_CFR2_MEDIUM_15 |       \
> +                                TSC2005_CFR2_AVG_7)
> +
> +#define MAX_12BIT                                      ((1 << 12) - 1)
> +#define TS_SAMPLES                                     4
> +#define TS_RECT_SIZE                                   8
> +#define TSC2005_TS_PENUP_TIME                          20
> +
> +static const u32 tsc2005_read_reg[] = {
> +       (TSC2005_REG | TSC2005_REG_X | TSC2005_REG_READ) << 16,
> +       (TSC2005_REG | TSC2005_REG_Y | TSC2005_REG_READ) << 16,
> +       (TSC2005_REG | TSC2005_REG_Z1 | TSC2005_REG_READ) << 16,
> +       (TSC2005_REG | TSC2005_REG_Z2 | TSC2005_REG_READ) << 16,
> +};
> +#define NUM_READ_REGS  (sizeof(tsc2005_read_reg)/sizeof(tsc2005_read_reg[0]))
> +
> +struct tsc2005 {
> +       struct spi_device       *spi;
> +
> +       struct input_dev        *idev;
> +       char                    phys[32];
> +       struct timer_list       penup_timer;
> +       spinlock_t              lock;
> +       struct mutex            mutex;
> +
> +       struct spi_message      read_msg;
> +       struct spi_transfer     read_xfer[NUM_READ_REGS];
> +       u32                     data[NUM_READ_REGS];
> +
> +       /* previous x,y,z */
> +       int                     x;
> +       int                     y;
> +       int                     p;
> +       /* average accumulators for each component */
> +       int                     sample_cnt;
> +       int                     avg_x;
> +       int                     avg_y;
> +       int                     avg_z1;
> +       int                     avg_z2;
> +       /* configuration */
> +       int                     x_plate_ohm;
> +       int                     hw_avg_max;
> +       int                     stab_time;
> +       int                     p_max;
> +       int                     touch_pressure;
> +       int                     irq;
> +       s16                     dav_gpio;
> +       /* status */
> +       u8                      sample_sent;
> +       u8                      pen_down;
> +       u8                      disabled;
> +       u8                      disable_depth;
> +       u8                      spi_active;
> +};
> +
> +static void tsc2005_cmd(struct tsc2005 *ts, u8 cmd)
> +{
> +       u16 data = TSC2005_CMD | TSC2005_CMD_12BIT | cmd;
> +       struct spi_message msg;
> +       struct spi_transfer xfer = { 0 };
> +
> +       xfer.tx_buf = &data;
> +       xfer.rx_buf = NULL;
> +       xfer.len = 1;
> +       xfer.bits_per_word = 8;
> +
> +       spi_message_init(&msg);
> +       spi_message_add_tail(&xfer, &msg);
> +       spi_sync(ts->spi, &msg);
> +}
> +
> +static void tsc2005_write(struct tsc2005 *ts, u8 reg, u16 value)
> +{
> +       u32 tx;
> +       struct spi_message msg;
> +       struct spi_transfer xfer = { 0 };
> +
> +       tx = (TSC2005_REG | reg | TSC2005_REG_PND0 |
> +              TSC2005_REG_WRITE) << 16;
> +       tx |= value;
> +
> +       xfer.tx_buf = &tx;
> +       xfer.rx_buf = NULL;
> +       xfer.len = 4;
> +       xfer.bits_per_word = 24;
> +
> +       spi_message_init(&msg);
> +       spi_message_add_tail(&xfer, &msg);
> +       spi_sync(ts->spi, &msg);
> +}
> +
> +static void tsc2005_ts_update_pen_state(struct tsc2005 *ts,
> +                                       int x, int y, int pressure)
> +{
> +       if (pressure) {
> +               input_report_abs(ts->idev, ABS_X, x);
> +               input_report_abs(ts->idev, ABS_Y, y);
> +               input_report_abs(ts->idev, ABS_PRESSURE, pressure);
> +               if (!ts->pen_down) {
> +                       input_report_key(ts->idev, BTN_TOUCH, 1);
> +                       ts->pen_down = 1;
> +               }
> +       } else {
> +               input_report_abs(ts->idev, ABS_PRESSURE, 0);
> +               if (ts->pen_down) {
> +                       input_report_key(ts->idev, BTN_TOUCH, 0);
> +                       ts->pen_down = 0;
> +               }
> +       }
> +
> +       input_sync(ts->idev);
> +}
> +
> +/*
> + * This function is called by the SPI framework after the coordinates
> + * have been read from TSC2005
> + */
> +static void tsc2005_ts_rx(void *arg)
> +{
> +       struct tsc2005 *ts = arg;
> +       unsigned long flags;
> +       int inside_rect, pressure_limit;
> +       int x, y, z1, z2, pressure;
> +
> +       spin_lock_irqsave(&ts->lock, flags);
> +
> +       x = ts->data[0];
> +       y = ts->data[1];
> +       z1 = ts->data[2];
> +       z2 = ts->data[3];
> +
> +       /* validate pressure and position */
> +       if (x > MAX_12BIT || y > MAX_12BIT)
> +               goto out;
> +
> +       /* skip coords if the pressure-components are out of range */
> +       if (z1 < 100 || z2 > 4000)
> +               goto out;
> +
> +       /* don't run average on the "pen down" event */
> +       if (ts->sample_sent) {
> +               ts->avg_x += x;
> +               ts->avg_y += y;
> +               ts->avg_z1 += z1;
> +               ts->avg_z2 += z2;
> +
> +               if (++ts->sample_cnt < TS_SAMPLES)
> +                       goto out;
> +
> +               x = ts->avg_x / TS_SAMPLES;
> +               y = ts->avg_y / TS_SAMPLES;
> +               z1 = ts->avg_z1 / TS_SAMPLES;
> +               z2 = ts->avg_z2 / TS_SAMPLES;
> +       }
> +
> +       ts->sample_cnt = 0;
> +       ts->avg_x = 0;
> +       ts->avg_y = 0;
> +       ts->avg_z1 = 0;
> +       ts->avg_z2 = 0;
> +
> +       if (z1) {
> +               pressure = x * (z2 - z1) / z1;
> +               pressure = pressure * ts->x_plate_ohm / 4096;
> +       } else
> +               goto out;
> +
> +       pressure_limit = ts->sample_sent? ts->p_max: ts->touch_pressure;
> +       if (pressure > pressure_limit)
> +               goto out;
> +
> +       /* discard the event if it still is within the previous rect - unless
> +        * if the pressure is harder, but then use previous x,y position */
> +       inside_rect = (ts->sample_sent &&
> +               x > (int)ts->x - TS_RECT_SIZE &&
> +               x < (int)ts->x + TS_RECT_SIZE &&
> +               y > (int)ts->y - TS_RECT_SIZE &&
> +               y < (int)ts->y + TS_RECT_SIZE);
> +       if (inside_rect)
> +               x = ts->x, y = ts->y;
> +
> +       if (!inside_rect || pressure < ts->p) {
> +               tsc2005_ts_update_pen_state(ts, x, y, pressure);
> +               ts->sample_sent = 1;
> +               ts->x = x;
> +               ts->y = y;
> +               ts->p = pressure;
> +       }

Minor nit: will this not break ts_calibrate from tslib? ts_calibrate
tries to read 5 samples for every touch, so the user will need to be
moving the pen. I hit this in emulators where noise needs to be added
artifically because userspace (tslib) relies on it.
Also curious why report if pressure becomes harder but not when softer.

Regards

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

* Re: [PATCH 00/34] omap drivers going upstream
  2008-08-31 22:37 ` [PATCH 00/34] omap drivers going upstream Felipe Balbi
  2008-08-31 22:40   ` Felipe Balbi
@ 2008-09-01  1:22   ` andrzej zaborowski
  2008-09-01  8:04     ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: andrzej zaborowski @ 2008-09-01  1:22 UTC (permalink / raw)
  To: me; +Cc: linux-omap, Felipe Balbi

2008/9/1 Felipe Balbi <me@felipebalbi.com>:
> On Sat, Aug 30, 2008 at 08:16:00PM +0300, Felipe Balbi wrote:
>> From: Felipe Balbi <felipe.balbi@nokia.com>
>>
>> The following drivers are going upstream for integration.
>> They have been sitting on linux-omap for quite a while just
>> increasing the diff against mainline and probability of
>> merge conflicts.
>
> Just one comment to this. I had to left bluetooth driver out of the
> series because it's using omap2_block/allow_sleep in the driver code.
> That should be fixed and the set_clock function should come via
> platform_data to the driver.
>
>  53 static void hci_h4p_set_clk(struct hci_h4p_info *info, int *clock, int enable)
>  54 {
>  55         unsigned long flags;
>  56
>  57         spin_lock_irqsave(&info->clocks_lock, flags);
>  58         if (enable && !*clock) {
>  59                 NBT_DBG_POWER("Enabling %p\n", clock);
>  60                 clk_enable(info->uart_fclk);
>  61 #ifdef CONFIG_ARCH_OMAP2
>  62                 if (cpu_is_omap24xx()) {
>  63                         clk_enable(info->uart_iclk);
>  64                         omap2_block_sleep();
>  65                 }
>  66 #endif
>  67         }
>  68         if (!enable && *clock) {
>  69                 NBT_DBG_POWER("Disabling %p\n", clock);
>  70                 clk_disable(info->uart_fclk);
>  71 #ifdef CONFIG_ARCH_OMAP2
>  72                 if (cpu_is_omap24xx()) {
>  73                         clk_disable(info->uart_iclk);
>  74                         omap2_allow_sleep();
>  75                 }
>  76 #endif
>  77         }
>  78
>  79         *clock = enable;
>  80         spin_unlock_irqrestore(&info->clocks_lock, flags);
>  81 }
>
> That driver is full of arch specific code and should be cleaned up ASAP.
>
> A few things I could get by briefly looking at the driver (actualy only
> drivers/bluetooth/hci_h4p/core.c):

There's also a curious issue in hci_h4p_interrupt I hit recently but
after looking at the rest of the driver thought it was beating a dead
horse..., but just in case it isn't:
the driver assumes the OMAP UART, but then it uses UART_IIR_ID which
is only valid for standard UARTs, causing OMAP-specific Rx errors to
be ignored silently.  Turns out that on my N810 there are actually Rx
errors reported during firmware upload, but I didn't find a better way
to handle them than to ignore them:

--- a/drivers/bluetooth/hci_h4p/core.c
+++ b/drivers/bluetooth/hci_h4p/core.c
@@ -482,7 +491,14 @@ static irqreturn_t hci_h4p_interrupt(int irq, void *data)

        NBT_DBG("In interrupt handler iir 0x%.2x\n", iir);

-       iir &= UART_IIR_ID;
+       iir &= 0x1e; /* OMAP UART has wider INT than UART_IIR_ID */
+
+       /*
+        * Often Rx errors are reported but reading the receive buffer
+        * gives the correct data, so treat it as an Rx interrupt.
+        */
+       if (iir == 0xc)
+               iir = 0x4;

        if (iir == UART_IIR_MSI) {
                msr = hci_h4p_inb(info, UART_MSR);

The tsc210x drivers should be upstreamable with the exception of ALSA
code which needs to be converted to ASoC.  Maruk Vasut found a leak in
one error path, but I can't charge the device that has the tsc2102
that I used for testing.

I have some improvements to drivers/net/irda/omap-ir.c (clean-up and
removing OMAP16xx specific bits to support OMAP1) but again, have no
charger for the device.

Thanks

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

* Re: [PATCH 00/34] omap drivers going upstream
  2008-08-30 17:16 [PATCH 00/34] omap drivers going upstream Felipe Balbi
  2008-08-30 17:16 ` [PATCH 01/33] add lp5521 driver Felipe Balbi
  2008-08-31 22:37 ` [PATCH 00/34] omap drivers going upstream Felipe Balbi
@ 2008-09-01  1:46 ` andrzej zaborowski
  2 siblings, 0 replies; 86+ messages in thread
From: andrzej zaborowski @ 2008-09-01  1:46 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

2008/8/30 Felipe Balbi <me@felipebalbi.com>:
<snim>
>  Add omap touchscreen driver

This is just a tsc2101 touchscreen + pin multiplexing, I'm not really
sure it should be upstream (but don't drop it without someone who
knows commenting). I think someone even experimented with using a
tsc2101 driver on the Hx board, but I may be wrong.
Regards

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

* RE: [PATCH 29/33] add omap ehci bus glue
  2008-08-30 17:16                                                         ` [PATCH 29/33] add omap ehci bus glue Felipe Balbi
  2008-08-30 17:16                                                           ` [PATCH 30/33] add omap backlight support Felipe Balbi
  2008-08-31 21:09                                                           ` [PATCH 29/33] add omap ehci bus glue David Brownell
@ 2008-09-01  5:55                                                           ` Gadiyar, Anand
  2008-09-01  7:56                                                             ` Felipe Balbi
  2 siblings, 1 reply; 86+ messages in thread
From: Gadiyar, Anand @ 2008-09-01  5:55 UTC (permalink / raw)
  To: Felipe Balbi, linux-omap; +Cc: Felipe Balbi

> -----Original Message-----
> From: linux-omap-owner@vger.kernel.org
> [mailto:linux-omap-owner@vger.kernel.org] On Behalf Of Felipe Balbi
> Sent: Saturday, August 30, 2008 10:46 PM
> To: linux-omap@vger.kernel.org
> Cc: Felipe Balbi
> Subject: [PATCH 29/33] add omap ehci bus glue
>
> From: Felipe Balbi <felipe.balbi@nokia.com>
>
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>


Hold off on this one. I'll send a better version to linux-usb
with a CC to linux-omap.

Regards,
Anand

> ---
>  drivers/usb/host/Kconfig     |   19 ++
>  drivers/usb/host/ehci-hcd.c  |    5 +
>  drivers/usb/host/ehci-omap.c |  562
> ++++++++++++++++++++++++++++++++++++++++++
>  drivers/usb/host/ehci-omap.h |  125 ++++++++++
>  4 files changed, 711 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/usb/host/ehci-omap.c
>  create mode 100644 drivers/usb/host/ehci-omap.h
>
> diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
> index 228797e..9807d13 100644
> --- a/drivers/usb/host/Kconfig
> +++ b/drivers/usb/host/Kconfig
> @@ -41,6 +41,25 @@ config USB_EHCI_HCD
>
>         To compile this driver as a module, choose M here: the
>         module will be called ehci-hcd.
> +choice
> +     prompt "PHY/TLL mode"
> +     depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> +     ---help---
> +     Choose PHY or TLL mode of operation
> +
> +config OMAP_EHCI_PHY_MODE
> +     bool "PHY mode: ISP1504 on Port1/2 (NEW 3430ES2.0)"
> +     depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> +     ---help---
> +       EHCI PHY mode. Port1 and Port2 are connected to
> ISP1504 transcievers
> +
> +config OMAP_EHCI_TLL_MODE
> +     bool "TLL mode: (EXPERIMENTAL)"
> +     depends on USB_EHCI_HCD && EXPERIMENTAL && ARCH_OMAP34XX
> +     ---help---
> +     OMAP EHCI controller has TLL mode of operation for all 3 ports.
> +     Use this mode when no transciever is present
> +endchoice
>
>  config USB_EHCI_ROOT_HUB_TT
>       bool "Root Hub Transaction Translators"
> diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
> index d9d53f2..b0a5d10 100644
> --- a/drivers/usb/host/ehci-hcd.c
> +++ b/drivers/usb/host/ehci-hcd.c
> @@ -1009,6 +1009,11 @@ MODULE_LICENSE ("GPL");
>  #define      PLATFORM_DRIVER         ehci_hcd_au1xxx_driver
>  #endif
>
> +#ifdef CONFIG_ARCH_OMAP34XX
> +#include "ehci-omap.c"
> +#define      PLATFORM_DRIVER         ehci_hcd_omap_driver
> +#endif
> +
>  #ifdef CONFIG_PPC_PS3
>  #include "ehci-ps3.c"
>  #define      PS3_SYSTEM_BUS_DRIVER   ps3_ehci_driver
> diff --git a/drivers/usb/host/ehci-omap.c
> b/drivers/usb/host/ehci-omap.c
> new file mode 100644
> index 0000000..8f122e5
> --- /dev/null
> +++ b/drivers/usb/host/ehci-omap.c
> @@ -0,0 +1,562 @@
> +/*
> + * ehci-omap.c - driver for USBHOST on OMAP 34xx processor
> + *
> + * Bus Glue for OMAP34xx USBHOST 3 port EHCI controller
> + * Tested on OMAP3430 ES2.0 SDP
> + *
> + * Copyright (C) 2007-2008 Texas Instruments, Inc.
> + *   Author: Vikram Pandita <vikram.pandita@ti.com>
> + *
> + * Based on "ehci-fsl.c" and "ehci-au1xxx.c" ehci glue layers
> + *
> + * 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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
> + *
> + */
> +
> +#include <linux/platform_device.h>
> +#include <linux/clk.h>
> +#include <mach/gpio.h>
> +
> +#include "ehci-omap.h"
> +
> +
> +#ifdef CONFIG_OMAP_EHCI_PHY_MODE
> +/* EHCI connected to External PHY */
> +
> +/* External USB connectivity board: 750-2083-001
> + * Connected to OMAP3430 SDP
> + * The board has Port1 and Port2 connected to ISP1504 in 12-pin ULPI mode
> + */
> +
> +/* ISSUE1:
> + *      ISP1504 for input clocking mode needs special reset handling
> + *   Hold the PHY in reset by asserting RESET_N signal
> + *   Then start the 60Mhz clock input to PHY
> + *   Release the reset after a delay -
> + *           to get the PHY state machine in working state
> + */
> +#define EXTERNAL_PHY_RESET
> +#define      EXT_PHY_RESET_GPIO_PORT1        (57)
> +#define      EXT_PHY_RESET_GPIO_PORT2        (61)
> +#define      EXT_PHY_RESET_DELAY             (10)
> +
> +/* ISSUE2:
> + * USBHOST supports External charge pump PHYs only
> + * Use the VBUS from Port1 to power VBUS of Port2 externally
> + * So use Port2 as the working ULPI port
> + */
> +#define VBUS_INTERNAL_CHARGEPUMP_HACK
> +
> +#endif /* CONFIG_OMAP_EHCI_PHY_MODE */
> +
> +/*-------------------------------------------------------------------------*/
> +
> +/* Define USBHOST clocks for clock management */
> +struct ehci_omap_clock_defs {
> +     struct clk      *usbhost_ick_clk;
> +     struct clk      *usbhost2_120m_fck_clk;
> +     struct clk      *usbhost1_48m_fck_clk;
> +     struct clk      *usbtll_fck_clk;
> +     struct clk      *usbtll_ick_clk;
> +};
> +
> +/* Clock names as per clock framework: May change so keep as #defs */
> +#define USBHOST_ICKL         "usbhost_ick"
> +#define USBHOST_120M_FCLK    "usbhost_120m_fck"
> +#define USBHOST_48M_FCLK     "usbhost_48m_fck"
> +#define USBHOST_TLL_ICKL     "usbtll_ick"
> +#define USBHOST_TLL_FCLK     "usbtll_fck"
> +/*-------------------------------------------------------------------------*/
> +
> +
> +#ifndef CONFIG_OMAP_EHCI_PHY_MODE
> +
> +static void omap_usb_utmi_init(struct usb_hcd *hcd, u8 tll_channel_mask)
> +{
> +     int i;
> +
> +     /* Use UTMI Ports of TLL */
> +     omap_writel((1 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)|
> +                     (1<<OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN_SHIFT)|
> +                     (1<<OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN_SHIFT)|
> +                     (1<<OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN_SHIFT)|
> +                     (0<<OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN_SHIFT),
> +                                             OMAP_UHH_HOSTCONFIG);
> +     /* Enusre bit is set */
> +     while (!(omap_readl(OMAP_UHH_HOSTCONFIG) &
> +             (1 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)));
> +
> +     dev_dbg(hcd->self.controller, "\nEntered UTMI MODE: success\n");
> +
> +     /* Program the 3 TLL channels upfront */
> +
> +     for (i = 0; i < OMAP_TLL_CHANNEL_COUNT; i++) {
> +
> +             /* Disable AutoIdle */
> +             omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) &
> +                         ~(1<<OMAP_TLL_CHANNEL_CONF_UTMIAUTOIDLE_SHIFT),
> +                         OMAP_TLL_CHANNEL_CONF(i));
> +             /* Disable BitStuffing */
> +             omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) &
> +                         ~(1<<OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF_SHIFT),
> +                         OMAP_TLL_CHANNEL_CONF(i));
> +             /* SDR Mode */
> +             omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) &
> +                         ~(1<<OMAP_TLL_CHANNEL_CONF_ULPIDDRMODE_SHIFT),
> +                         OMAP_TLL_CHANNEL_CONF(i));
> +
> +     }
> +
> +     /* Program Common TLL register */
> +     omap_writel((1 << OMAP_TLL_SHARED_CONF_FCLK_IS_ON_SHIFT) |
> +                     (1 << OMAP_TLL_SHARED_CONF_USB_DIVRATION_SHIFT) |
> +                     (0 << OMAP_TLL_SHARED_CONF_USB_180D_SDR_EN_SHIFT) |
> +                     (0 << OMAP_TLL_SHARED_CONF_USB_90D_DDR_EN_SHFT),
> +                             OMAP_TLL_SHARED_CONF);
> +
> +     /* Enable channels now */
> +     for (i = 0; i < OMAP_TLL_CHANNEL_COUNT; i++) {
> +
> +             /* Enable only the channel that is needed */
> +             if (!(tll_channel_mask & 1<<i))
> +                     continue;
> +
> +             omap_writel(omap_readl(OMAP_TLL_CHANNEL_CONF(i)) |
> +                         (1<<OMAP_TLL_CHANNEL_CONF_CHANEN_SHIFT),
> +                         OMAP_TLL_CHANNEL_CONF(i));
> +
> +             omap_writeb(0xBE, OMAP_TLL_ULPI_SCRATCH_REGISTER(i));
> +             dev_dbg(hcd->self.controller, "\nULPI_SCRATCH_REG[ch=%d]"
> +                     "= 0x%02x\n",
> +                     i+1, omap_readb(OMAP_TLL_ULPI_SCRATCH_REGISTER(i)));
> +     }
> +}
> +
> +#else
> +# define omap_usb_utmi_init(x, y)    0
> +#endif
> +
> +
> +/* omap_start_ehc
> + *   - Start the TI USBHOST controller
> + */
> +static int omap_start_ehc(struct platform_device *dev, struct usb_hcd *hcd)
> +{
> +     struct ehci_omap_clock_defs *ehci_clocks;
> +
> +     dev_dbg(hcd->self.controller, ": starting TI EHCI USB Controller\n");
> +
> +     ehci_clocks = (struct ehci_omap_clock_defs *)(
> +                             ((char *)hcd_to_ehci(hcd)) +
> +                                     sizeof(struct ehci_hcd));
> +
> +     /* Start DPLL5 Programming:
> +      * Clock Framework is not doing this now:
> +      * This will be done in clock framework later
> +      */
> +     /* Enable DPLL 5 : Based on Input of 13Mhz*/
> +     cm_write_mod_reg((12 << OMAP3430ES2_PERIPH2_DPLL_DIV_SHIFT)|
> +                     (120 << OMAP3430ES2_PERIPH2_DPLL_MULT_SHIFT),
> +                     PLL_MOD, OMAP3430ES2_CM_CLKSEL4);
> +
> +     cm_write_mod_reg(1 << OMAP3430ES2_DIV_120M_SHIFT,
> +                     PLL_MOD, OMAP3430ES2_CM_CLKSEL5);
> +
> +     cm_write_mod_reg((7 << OMAP3430ES2_PERIPH2_DPLL_FREQSEL_SHIFT) |
> +                     (7 << OMAP3430ES2_EN_PERIPH2_DPLL_SHIFT),
> +                     PLL_MOD, OMAP3430ES2_CM_CLKEN2);
> +
> +     while (!(cm_read_mod_reg(PLL_MOD, CM_IDLEST2) &
> +                             OMAP3430ES2_ST_PERIPH2_CLK_MASK))
> +             dev_dbg(hcd->self.controller,
> +                     "idlest2 = 0x%x\n",
> +                     cm_read_mod_reg(PLL_MOD, CM_IDLEST2));
> +     /* End DPLL5 programming */
> +
> +
> +     /* PRCM settings for USBHOST:
> +      * Interface clk un-related to domain transition
> +      */
> +     cm_write_mod_reg(0 << OMAP3430ES2_AUTO_USBHOST_SHIFT,
> +                             OMAP3430ES2_USBHOST_MOD, CM_AUTOIDLE);
> +
> +     /* Disable sleep dependency with MPU and IVA */
> +     cm_write_mod_reg((0 << OMAP3430ES2_EN_MPU_SHIFT) |
> +                             (0 << OMAP3430ES2_EN_IVA2_SHIFT),
> +                             OMAP3430ES2_USBHOST_MOD, OMAP3430_CM_SLEEPDEP);
> +
> +     /* Disable Automatic transition of clock */
> +     cm_write_mod_reg(0 << OMAP3430ES2_CLKTRCTRL_USBHOST_SHIFT,
> +                             OMAP3430ES2_USBHOST_MOD, CM_CLKSTCTRL);
> +
> +     /* Enable Clocks for USBHOST */
> +     ehci_clocks->usbhost_ick_clk = clk_get(&dev->dev,
> +                                             USBHOST_ICKL);
> +     if (IS_ERR(ehci_clocks->usbhost_ick_clk))
> +             return PTR_ERR(ehci_clocks->usbhost_ick_clk);
> +     clk_enable(ehci_clocks->usbhost_ick_clk);
> +
> +
> +     ehci_clocks->usbhost2_120m_fck_clk = clk_get(&dev->dev,
> +                                                     USBHOST_120M_FCLK);
> +     if (IS_ERR(ehci_clocks->usbhost2_120m_fck_clk))
> +             return PTR_ERR(ehci_clocks->usbhost2_120m_fck_clk);
> +     clk_enable(ehci_clocks->usbhost2_120m_fck_clk);
> +
> +     ehci_clocks->usbhost1_48m_fck_clk = clk_get(&dev->dev,
> +                                             USBHOST_48M_FCLK);
> +     if (IS_ERR(ehci_clocks->usbhost1_48m_fck_clk))
> +             return PTR_ERR(ehci_clocks->usbhost1_48m_fck_clk);
> +     clk_enable(ehci_clocks->usbhost1_48m_fck_clk);
> +
> +
> +#ifdef EXTERNAL_PHY_RESET
> +     /* Refer: ISSUE1 */
> +     omap_request_gpio(EXT_PHY_RESET_GPIO_PORT1);
> +     omap_set_gpio_direction(EXT_PHY_RESET_GPIO_PORT1, 0);
> +     omap_request_gpio(EXT_PHY_RESET_GPIO_PORT2);
> +     omap_set_gpio_direction(EXT_PHY_RESET_GPIO_PORT2, 0);
> +     omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT1, 0);
> +     omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT2, 0);
> +     /* Hold the PHY in RESET for enough time till DIR is high */
> +     udelay(EXT_PHY_RESET_DELAY);
> +#endif
> +
> +     /* Configure TLL for 60Mhz clk for ULPI */
> +     ehci_clocks->usbtll_fck_clk = clk_get(&dev->dev, USBHOST_TLL_FCLK);
> +     if (IS_ERR(ehci_clocks->usbtll_fck_clk))
> +             return PTR_ERR(ehci_clocks->usbtll_fck_clk);
> +     clk_enable(ehci_clocks->usbtll_fck_clk);
> +
> +     ehci_clocks->usbtll_ick_clk = clk_get(&dev->dev, USBHOST_TLL_ICKL);
> +     if (IS_ERR(ehci_clocks->usbtll_ick_clk))
> +             return PTR_ERR(ehci_clocks->usbtll_ick_clk);
> +     clk_enable(ehci_clocks->usbtll_ick_clk);
> +
> +     /* Disable Auto Idle of USBTLL */
> +     cm_write_mod_reg((0 << OMAP3430ES2_AUTO_USBTLL_SHIFT),
> +                             CORE_MOD, CM_AUTOIDLE3);
> +
> +     /* Wait for TLL to be Active */
> +     while ((cm_read_mod_reg(CORE_MOD, OMAP2430_CM_IDLEST3) &
> +             (1 << OMAP3430ES2_ST_USBTLL_SHIFT)));
> +
> +     /* perform TLL soft reset, and wait until reset is complete */
> +     omap_writel(1 << OMAP_USBTLL_SYSCONFIG_SOFTRESET_SHIFT,
> +                     OMAP_USBTLL_SYSCONFIG);
> +     /* Wait for TLL reset to complete */
> +     while (!(omap_readl(OMAP_USBTLL_SYSSTATUS) &
> +             (1 << OMAP_USBTLL_SYSSTATUS_RESETDONE_SHIFT)));
> +
> +     dev_dbg(hcd->self.controller, "\n TLL RESET DONE\n");
> +
> +     /* (1<<3) = no idle mode only for initial debugging */
> +     omap_writel((1 << OMAP_USBTLL_SYSCONFIG_ENAWAKEUP_SHIFT) |
> +                     (1 << OMAP_USBTLL_SYSCONFIG_SIDLEMODE_SHIFT) |
> +                     (1 << OMAP_USBTLL_SYSCONFIG_CACTIVITY_SHIFT),
> +                     OMAP_USBTLL_SYSCONFIG);
> +
> +
> +     /* Put UHH in NoIdle/NoStandby mode */
> +     omap_writel((0 << OMAP_UHH_SYSCONFIG_AUTOIDLE_SHIFT) |
> +                     (1 << OMAP_UHH_SYSCONFIG_ENAWAKEUP_SHIFT) |
> +                     (1 << OMAP_UHH_SYSCONFIG_SIDLEMODE_SHIFT) |
> +                     (1 << OMAP_UHH_SYSCONFIG_CACTIVITY_SHIFT) |
> +                     (1 << OMAP_UHH_SYSCONFIG_MIDLEMODE_SHIFT),
> +                     OMAP_UHH_SYSCONFIG);
> +
> +#ifdef CONFIG_OMAP_EHCI_PHY_MODE
> +     /* Bypass the TLL module for PHY mode operation */
> +     omap_writel((0 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT)|
> +                     (1<<OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN_SHIFT)|
> +                     (1<<OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN_SHIFT)|
> +                     (1<<OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN_SHIFT)|
> +                     (0<<OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN_SHIFT),
> +                                             OMAP_UHH_HOSTCONFIG);
> +     /* Ensure that BYPASS is set */
> +     while (omap_readl(OMAP_UHH_HOSTCONFIG) &
> +             (1 << OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT));
> +
> +     dev_dbg(hcd->self.controller, "Entered ULPI PHY MODE: success");
> +
> +#else
> +     /* Enable UTMI mode for all 3 TLL channels */
> +     omap_usb_utmi_init(hcd,
> +             OMAP_TLL_CHANNEL_1_EN_MASK |
> +             OMAP_TLL_CHANNEL_2_EN_MASK |
> +             OMAP_TLL_CHANNEL_3_EN_MASK
> +             );
> +#endif
> +
> +#ifdef EXTERNAL_PHY_RESET
> +     /* Refer ISSUE1:
> +      * Hold the PHY in RESET for enough time till PHY is
> settled and ready
> +      */
> +     udelay(EXT_PHY_RESET_DELAY);
> +     omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT1, 1);
> +     omap_set_gpio_dataout(EXT_PHY_RESET_GPIO_PORT2, 1);
> +#endif
> +
> +#ifdef VBUS_INTERNAL_CHARGEPUMP_HACK
> +     /* Refer ISSUE2: LINK assumes external charge pump */
> +
> +     /* use Port1 VBUS to charge externally Port2:
> +      *      So for PHY mode operation use Port2 only
> +      */
> +     omap_writel((0xA << EHCI_INSNREG05_ULPI_REGADD_SHIFT) |/* OTG ctrl reg*/
> +                     (2 << EHCI_INSNREG05_ULPI_OPSEL_SHIFT) |/*   Write */
> +                     (1 << EHCI_INSNREG05_ULPI_PORTSEL_SHIFT) |/* Port1 */
> +                     (1 << EHCI_INSNREG05_ULPI_CONTROL_SHIFT) |/* Start */
> +                     (0x26),
> +                     EHCI_INSNREG05_ULPI);
> +
> +     while (!(omap_readl(EHCI_INSNREG05_ULPI) &
> +             (1<<EHCI_INSNREG05_ULPI_CONTROL_SHIFT)));
> +
> +#endif
> +
> +     return 0;
> +}
> +
> +/*-------------------------------------------------------------------------*/
> +
> +static void omap_stop_ehc(struct platform_device *dev, struct usb_hcd *hcd)
> +{
> +     struct ehci_omap_clock_defs *ehci_clocks;
> +
> +     ehci_clocks = (struct ehci_omap_clock_defs *)
> +                     (((char *)hcd_to_ehci(hcd)) +
> sizeof(struct ehci_hcd));
> +
> +     dev_dbg(hcd->self.controller, ": stopping TI EHCI USB Controller\n");
> +
> +     /* Reset OMAP modules for insmod/rmmod to work */
> +     omap_writel((1<<1), OMAP_UHH_SYSCONFIG);
> +     while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<0)));
> +     while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<1)));
> +     while (!(omap_readl(OMAP_UHH_SYSSTATUS) & (1<<2)));
> +     dev_dbg(hcd->self.controller,
> +             "UHH RESET DONE OMAP_UHH_SYSSTATUS %x !!\n",
> +                     omap_readl(OMAP_UHH_SYSSTATUS));
> +
> +     omap_writel((1<<1), OMAP_USBTLL_SYSCONFIG);
> +     while (!(omap_readl(OMAP_USBTLL_SYSSTATUS) & (1<<0)));
> +     dev_dbg(hcd->self.controller, ":TLL RESEET DONE");
> +
> +     if (ehci_clocks->usbtll_fck_clk != NULL) {
> +             clk_disable(ehci_clocks->usbtll_fck_clk);
> +             clk_put(ehci_clocks->usbtll_fck_clk);
> +             ehci_clocks->usbtll_fck_clk = NULL;
> +     }
> +
> +     if (ehci_clocks->usbhost_ick_clk != NULL) {
> +             clk_disable(ehci_clocks->usbhost_ick_clk);
> +             clk_put(ehci_clocks->usbhost_ick_clk);
> +             ehci_clocks->usbhost_ick_clk = NULL;
> +     }
> +
> +     if (ehci_clocks->usbhost1_48m_fck_clk != NULL) {
> +             clk_disable(ehci_clocks->usbhost1_48m_fck_clk);
> +             clk_put(ehci_clocks->usbhost1_48m_fck_clk);
> +             ehci_clocks->usbhost1_48m_fck_clk = NULL;
> +     }
> +
> +     if (ehci_clocks->usbhost2_120m_fck_clk != NULL) {
> +             clk_disable(ehci_clocks->usbhost2_120m_fck_clk);
> +             clk_put(ehci_clocks->usbhost2_120m_fck_clk);
> +             ehci_clocks->usbhost2_120m_fck_clk = NULL;
> +     }
> +
> +     if (ehci_clocks->usbtll_ick_clk != NULL) {
> +             clk_disable(ehci_clocks->usbtll_ick_clk);
> +             clk_put(ehci_clocks->usbtll_ick_clk);
> +             ehci_clocks->usbtll_ick_clk = NULL;
> +     }
> +
> +
> +#ifdef EXTERNAL_PHY_RESET
> +     omap_free_gpio(EXT_PHY_RESET_GPIO_PORT1);
> +     omap_free_gpio(EXT_PHY_RESET_GPIO_PORT2);
> +#endif
> +
> +     dev_dbg(hcd->self.controller,
> +             ": Clock to USB host has been disabled\n");
> +}
> +
> +static const struct hc_driver ehci_omap_hc_driver;
> +
> +/*-------------------------------------------------------------------------*/
> +/* configure so an HC device and id are always provided */
> +/* always called with process context; sleeping is OK */
> +
> +/**
> + * ehci_hcd_omap_drv_probe - initialize TI-based HCDs
> + * Context: !in_interrupt()
> + *
> + * Allocates basic resources for this USB host controller, and
> + * then invokes the start() method for the HCD associated with it
> + * through the hotplug entry's driver_data.
> + *
> + */
> +static int ehci_hcd_omap_drv_probe(struct platform_device *dev)
> +{
> +     int retval = 0;
> +     struct usb_hcd *hcd;
> +     struct ehci_hcd *ehci;
> +
> +     dev_dbg(&dev->dev, "ehci_hcd_omap_drv_probe()");
> +
> +     if (usb_disabled())
> +             return -ENODEV;
> +
> +     if (dev->resource[1].flags != IORESOURCE_IRQ) {
> +             dev_dbg(&dev->dev, "resource[1] is not IORESOURCE_IRQ");
> +             retval = -ENOMEM;
> +     }
> +
> +     hcd = usb_create_hcd(&ehci_omap_hc_driver, &dev->dev, dev->dev.bus_id);
> +     if (!hcd)
> +             return -ENOMEM;
> +
> +     retval = omap_start_ehc(dev, hcd);
> +     if (retval)
> +             return retval;
> +
> +     hcd->rsrc_start = 0;
> +     hcd->rsrc_len = 0;
> +     hcd->rsrc_start = dev->resource[0].start;
> +     hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1;
> +
> +     hcd->regs = (void __iomem *) (int) IO_ADDRESS(hcd->rsrc_start);
> +
> +     ehci = hcd_to_ehci(hcd);
> +     ehci->caps = hcd->regs;
> +
> +     ehci->regs = hcd->regs + HC_LENGTH(readl(&ehci->caps->hc_capbase));
> +     /* cache this readonly data; minimize chip reads */
> +     ehci->hcs_params = readl(&ehci->caps->hcs_params);
> +
> +     /* SET 1 micro-frame Interrupt interval */
> +     writel(readl(&ehci->regs->command) | (1<<16), &ehci->regs->command);
> +
> +     retval = usb_add_hcd(hcd, dev->resource[1].start,
> +                             IRQF_DISABLED | IRQF_SHARED);
> +     if (retval == 0)
> +             return retval;
> +
> +     dev_dbg(hcd->self.controller, "ERR: add_hcd");
> +     omap_stop_ehc(dev, hcd);
> +
> +     usb_put_hcd(hcd);
> +     return retval;
> +}
> +
> +/*-------------------------------------------------------------------------*/
> +
> +/* may be called without controller electrically present */
> +/* may be called with controller, bus, and devices active */
> +
> +/**
> + * ehci_hcd_omap_drv_remove - shutdown processing for EHCI HCDs
> + * @dev: USB Host Controller being removed
> + * Context: !in_interrupt()
> + *
> + * Reverses the effect of usb_ehci_hcd_omap_probe(), first invoking
> + * the HCD's stop() method.  It is always called from a thread
> + * context, normally "rmmod", "apmd", or something similar.
> + *
> + */
> +static int ehci_hcd_omap_drv_remove(struct platform_device *dev)
> +{
> +     struct usb_hcd *hcd = platform_get_drvdata(dev);
> +
> +     dev_dbg(&dev->dev, "ehci_hcd_omap_drv_remove()");
> +
> +     usb_remove_hcd(hcd);
> +     usb_put_hcd(hcd);
> +     omap_stop_ehc(dev, hcd);
> +
> +     return 0;
> +}
> +
> +/*-------------------------------------------------------------------------*/
> +#ifdef CONFIG_PM
> +static int omap_ehci_bus_suspend(struct usb_hcd *hcd)
> +{
> +     return ehci_bus_suspend(hcd);
> +}
> +
> +static int omap_ehci_bus_resume(struct usb_hcd *hcd)
> +{
> +     return ehci_bus_resume(hcd);
> +}
> +#endif
> +/*-------------------------------------------------------------------------*/
> +
> +static const struct hc_driver ehci_omap_hc_driver = {
> +     .description = hcd_name,
> +     .product_desc = "OMAP-EHCI Host Controller",
> +     .hcd_priv_size = sizeof(struct ehci_hcd)
> +                             + sizeof(struct ehci_omap_clock_defs),
> +
> +     /*
> +      * generic hardware linkage
> +      */
> +     .irq = ehci_irq,
> +     .flags = HCD_MEMORY | HCD_USB2,
> +
> +     /*
> +      * basic lifecycle operations
> +      */
> +     .reset = ehci_init,
> +     .start = ehci_run,
> +     .stop = ehci_stop,
> +     .shutdown = ehci_shutdown,
> +
> +     /*
> +      * managing i/o requests and associated device resources
> +      */
> +     .urb_enqueue = ehci_urb_enqueue,
> +     .urb_dequeue = ehci_urb_dequeue,
> +     .endpoint_disable = ehci_endpoint_disable,
> +
> +     /*
> +      * scheduling support
> +      */
> +     .get_frame_number = ehci_get_frame,
> +
> +     /*
> +      * root hub support
> +      */
> +     .hub_status_data = ehci_hub_status_data,
> +     .hub_control = ehci_hub_control,
> +#ifdef       CONFIG_PM
> +     .bus_suspend = omap_ehci_bus_suspend,
> +     .bus_resume = omap_ehci_bus_resume,
> +#endif
> +};
> +
> +/*-------------------------------------------------------------------------*/
> +MODULE_ALIAS("omap-ehci");
> +static struct platform_driver ehci_hcd_omap_driver = {
> +     .probe = ehci_hcd_omap_drv_probe,
> +     .remove = ehci_hcd_omap_drv_remove,
> +     .shutdown = usb_hcd_platform_shutdown,
> +     /*.suspend      = ehci_hcd_omap_drv_suspend, */
> +     /*.resume       = ehci_hcd_omap_drv_resume, */
> +     .driver = {
> +             .name = "ehci-omap",
> +             .bus = &platform_bus_type
> +     }
> +};
> diff --git a/drivers/usb/host/ehci-omap.h
> b/drivers/usb/host/ehci-omap.h
> new file mode 100644
> index 0000000..9e4378f
> --- /dev/null
> +++ b/drivers/usb/host/ehci-omap.h
> @@ -0,0 +1,125 @@
> +/*
> + * ehci-omap.h - register definitions for USBHOST in OMAP 34xx
> + *
> + * Copyright (C) 2007-2008 Texas Instruments, Inc.
> + *   Author: Vikram Pandita <vikram.pandita@ti.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.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
> + *
> + */
> +
> +#ifndef __EHCI_OMAP_H
> +#define __EHCI_OMAP_H
> +
> +#include <mach/hardware.h>
> +#include "../../../arch/arm/mach-omap2/cm.h"
> +#include "../../../arch/arm/mach-omap2/cm-regbits-34xx.h"
> +
> +/*
> + * OMAP USBHOST Register addresses: PHYSICAL ADDRESSES
> + *   Use omap_readl()/omap_writel() functions
> + */
> +
> +/* USBHOST: TLL, UUH, OHCI, EHCI */
> +#define      OMAP_USBHOST_BASE       (L4_34XX_BASE + 0x60000)
> +
> +/* TLL Register Set */
> +#define      OMAP_USBHOST_TLL_BASE   (OMAP_USBHOST_BASE + 0x2000)
> +#define      OMAP_USBTLL_REVISION    (OMAP_USBHOST_TLL_BASE + 0x00)
> +#define      OMAP_USBTLL_SYSCONFIG   (OMAP_USBHOST_TLL_BASE + 0x10)
> +     #define OMAP_USBTLL_SYSCONFIG_CACTIVITY_SHIFT   8
> +     #define OMAP_USBTLL_SYSCONFIG_SIDLEMODE_SHIFT   3
> +     #define OMAP_USBTLL_SYSCONFIG_ENAWAKEUP_SHIFT   2
> +     #define OMAP_USBTLL_SYSCONFIG_SOFTRESET_SHIFT   1
> +     #define OMAP_USBTLL_SYSCONFIG_AUTOIDLE_SHIFT    0
> +#define      OMAP_USBTLL_SYSSTATUS   (OMAP_USBHOST_TLL_BASE + 0x14)
> +     #define OMAP_USBTLL_SYSSTATUS_RESETDONE_SHIFT   0
> +#define      OMAP_USBTLL_IRQSTATUS   (OMAP_USBHOST_TLL_BASE + 0x18)
> +#define      OMAP_USBTLL_IRQENABLE   (OMAP_USBHOST_TLL_BASE + 0x1C)
> +
> +#define      OMAP_TLL_SHARED_CONF    (OMAP_USBHOST_TLL_BASE + 0x30)
> +     #define OMAP_TLL_SHARED_CONF_USB_90D_DDR_EN_SHFT        6
> +     #define OMAP_TLL_SHARED_CONF_USB_180D_SDR_EN_SHIFT      5
> +     #define OMAP_TLL_SHARED_CONF_USB_DIVRATION_SHIFT        2
> +     #define OMAP_TLL_SHARED_CONF_FCLK_REQ_SHIFT             1
> +     #define OMAP_TLL_SHARED_CONF_FCLK_IS_ON_SHIFT           0
> +
> +#define      OMAP_TLL_CHANNEL_CONF(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x040 + 0x004 * num))
> +     #define OMAP_TLL_CHANNEL_CONF_ULPINOBITSTUFF_SHIFT      11
> +     #define OMAP_TLL_CHANNEL_CONF_ULPI_ULPIAUTOIDLE_SHIFT   10
> +     #define OMAP_TLL_CHANNEL_CONF_UTMIAUTOIDLE_SHIFT        9
> +     #define OMAP_TLL_CHANNEL_CONF_ULPIDDRMODE_SHIFT         8
> +     #define OMAP_TLL_CHANNEL_CONF_CHANEN_SHIFT              0
> +
> +#define      OMAP_TLL_ULPI_FUNCTION_CTRL(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x804 + 0x100 * num))
> +#define      OMAP_TLL_ULPI_INTERFACE_CTRL(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x807 + 0x100 * num))
> +#define      OMAP_TLL_ULPI_OTG_CTRL(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x80A + 0x100 * num))
> +#define      OMAP_TLL_ULPI_INT_EN_RISE(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x80D + 0x100 * num))
> +#define      OMAP_TLL_ULPI_INT_EN_FALL(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x810 + 0x100 * num))
> +#define      OMAP_TLL_ULPI_INT_STATUS(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x813 + 0x100 * num))
> +#define      OMAP_TLL_ULPI_INT_LATCH(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x814 + 0x100 * num))
> +#define      OMAP_TLL_ULPI_DEBUG(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x815 + 0x100 * num))
> +#define      OMAP_TLL_ULPI_SCRATCH_REGISTER(num)\
> +                     (OMAP_USBHOST_TLL_BASE + (0x816 + 0x100 * num))
> +
> +#define OMAP_TLL_CHANNEL_COUNT               3
> +     #define OMAP_TLL_CHANNEL_1_EN_MASK      1
> +     #define OMAP_TLL_CHANNEL_2_EN_MASK      2
> +     #define OMAP_TLL_CHANNEL_3_EN_MASK      4
> +
> +/* UHH Register Set */
> +#define      OMAP_USBHOST_UHH_BASE   (OMAP_USBHOST_BASE + 0x4000)
> +#define      OMAP_UHH_REVISION       (OMAP_USBHOST_UHH_BASE + 0x00)
> +#define      OMAP_UHH_SYSCONFIG      (OMAP_USBHOST_UHH_BASE + 0x10)
> +     #define OMAP_UHH_SYSCONFIG_MIDLEMODE_SHIFT      12
> +     #define OMAP_UHH_SYSCONFIG_CACTIVITY_SHIFT      8
> +     #define OMAP_UHH_SYSCONFIG_SIDLEMODE_SHIFT      3
> +     #define OMAP_UHH_SYSCONFIG_ENAWAKEUP_SHIFT      2
> +     #define OMAP_UHH_SYSCONFIG_SOFTRESET_SHIFT      1
> +     #define OMAP_UHH_SYSCONFIG_AUTOIDLE_SHIFT       0
> +
> +#define      OMAP_UHH_SYSSTATUS      (OMAP_USBHOST_UHH_BASE + 0x14)
> +#define      OMAP_UHH_HOSTCONFIG     (OMAP_USBHOST_UHH_BASE + 0x40)
> +     #define OMAP_UHH_HOSTCONFIG_ULPI_BYPASS_SHIFT   0
> +     #define OMAP_UHH_HOSTCONFIG_INCR4_BURST_EN_SHIFT        2
> +     #define OMAP_UHH_HOSTCONFIG_INCR8_BURST_EN_SHIFT        3
> +     #define OMAP_UHH_HOSTCONFIG_INCR16_BURST_EN_SHIFT       4
> +     #define OMAP_UHH_HOSTCONFIG_INCRX_ALIGN_EN_SHIFT        5
> +
> +#define      OMAP_UHH_DEBUG_CSR      (OMAP_USBHOST_UHH_BASE + 0x44)
> +
> +/* EHCI Register Set */
> +#define      OMAP_USBHOST_EHCI_BASE  (OMAP_USBHOST_BASE + 0x4800)
> +#define      EHCI_INSNREG05_ULPI
> (OMAP_USBHOST_EHCI_BASE + 0xA4)
> +     #define EHCI_INSNREG05_ULPI_CONTROL_SHIFT       31
> +     #define EHCI_INSNREG05_ULPI_PORTSEL_SHIFT       24
> +     #define EHCI_INSNREG05_ULPI_OPSEL_SHIFT         22
> +     #define EHCI_INSNREG05_ULPI_REGADD_SHIFT        16
> +     #define EHCI_INSNREG05_ULPI_EXTREGADD_SHIFT     8
> +     #define EHCI_INSNREG05_ULPI_WRDATA_SHIFT        0
> +
> +/* OHCI Register Set */
> +#define      OMAP_USBHOST_OHCI_BASE  (OMAP_USBHOST_BASE + 0x4400)
> +
> +#endif/* __EHCI_OMAP_H*/
> --
> 1.6.0.1.141.g445ca
>
> --
> To unsubscribe from this list: send the line "unsubscribe
> linux-omap" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
>

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

* Re: [PATCH 05/33] add tlv320aic23 driver
  2008-08-30 17:16         ` [PATCH 05/33] add tlv320aic23 driver Felipe Balbi
  2008-08-30 17:16           ` [PATCH 06/33] add tsl2563 driver Felipe Balbi
@ 2008-09-01  7:22           ` Jarkko Nikula
  2008-09-01  8:01             ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: Jarkko Nikula @ 2008-09-01  7:22 UTC (permalink / raw)
  To: ext Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Sat, 30 Aug 2008 20:16:05 +0300
"ext Felipe Balbi" <me@felipebalbi.com> wrote:

> From: Felipe Balbi <felipe.balbi@nokia.com>
> 
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> ---
>  drivers/i2c/chips/Kconfig       |    7 +
>  drivers/i2c/chips/Makefile      |    1 +
>  drivers/i2c/chips/tlv320aic23.c |  675 ++++++++++++++++++++++++++++++
> +++++++++ 3 files changed, 683 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/i2c/chips/tlv320aic23.c
> 
This driver is obsolete audio codec driver and should disappear
eventually than going upstream. At least there is no place for it.

Right path is to have ASoC codec driver for aic23 and develope ASoC
machine driver for affected boards like Nokia 770 and OSK.


Jarkko

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

* Re: [PATCH 17/33] add tea5761 radio driver
  2008-08-30 17:16                                 ` [PATCH 17/33] add tea5761 radio driver Felipe Balbi
  2008-08-30 17:16                                   ` [PATCH 18/33] add ov9640 sensor driver Felipe Balbi
@ 2008-09-01  7:24                                   ` Jarkko Nikula
  2008-09-01  7:59                                     ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: Jarkko Nikula @ 2008-09-01  7:24 UTC (permalink / raw)
  To: ext Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Sat, 30 Aug 2008 20:16:17 +0300
"ext Felipe Balbi" <me@felipebalbi.com> wrote:

> From: Felipe Balbi <felipe.balbi@nokia.com>
> 
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> ---
>  drivers/media/radio/Kconfig         |   13 +
>  drivers/media/radio/Makefile        |    1 +
>  drivers/media/radio/radio-tea5761.c |  517 ++++++++++++++++++++++++++
> +++++++++ 3 files changed, 531 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/media/radio/radio-tea5761.c
> 
I have the impression that Eduardo Valentin had work in progress to
push this via v4l list. However, don't know the current status.


Jarkko

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

* Re: [PATCH 12/33] Add tsc2005 touchscreen driver
  2008-09-01  1:01                         ` andrzej zaborowski
@ 2008-09-01  7:34                           ` Daniel Stone
  0 siblings, 0 replies; 86+ messages in thread
From: Daniel Stone @ 2008-09-01  7:34 UTC (permalink / raw)
  To: ext andrzej zaborowski; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

[-- Attachment #1: Type: text/plain, Size: 1640 bytes --]

On Mon, Sep 01, 2008 at 03:01:43AM +0200, ext andrzej zaborowski wrote:
> 2008/8/30 Felipe Balbi <me@felipebalbi.com>:
> > [roughly 470 irrelevant lines discarded]
> > +       /* discard the event if it still is within the previous rect - unless
> > +        * if the pressure is harder, but then use previous x,y position */
> > +       inside_rect = (ts->sample_sent &&
> > +               x > (int)ts->x - TS_RECT_SIZE &&
> > +               x < (int)ts->x + TS_RECT_SIZE &&
> > +               y > (int)ts->y - TS_RECT_SIZE &&
> > +               y < (int)ts->y + TS_RECT_SIZE);
> > +       if (inside_rect)
> > +               x = ts->x, y = ts->y;
> > +
> > +       if (!inside_rect || pressure < ts->p) {
> > +               tsc2005_ts_update_pen_state(ts, x, y, pressure);
> > +               ts->sample_sent = 1;
> > +               ts->x = x;
> > +               ts->y = y;
> > +               ts->p = pressure;
> > +       }
> 
> Minor nit: will this not break ts_calibrate from tslib? ts_calibrate
> tries to read 5 samples for every touch, so the user will need to be
> moving the pen. I hit this in emulators where noise needs to be added
> artifically because userspace (tslib) relies on it.
> Also curious why report if pressure becomes harder but not when softer.

Works fine for us, but of course that depends on your tslib
configuration.  We decided to remove the filtering and whathaveyou from
tslib and in a novel move, actually receive useful and correct events
from evdev, instead of something that needs to be subjected to further
arbitrary filtering to be usable.

Cheers,
Daniel

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

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

* Re: [PATCH 28/33] add tsc2301 driver
  2008-08-30 17:16                                                       ` [PATCH 28/33] add tsc2301 driver Felipe Balbi
  2008-08-30 17:16                                                         ` [PATCH 29/33] add omap ehci bus glue Felipe Balbi
@ 2008-09-01  7:35                                                         ` Jarkko Nikula
  2008-09-01  7:58                                                           ` Felipe Balbi
  1 sibling, 1 reply; 86+ messages in thread
From: Jarkko Nikula @ 2008-09-01  7:35 UTC (permalink / raw)
  To: ext Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Sat, 30 Aug 2008 20:16:28 +0300
"ext Felipe Balbi" <me@felipebalbi.com> wrote:

> From: Felipe Balbi <felipe.balbi@nokia.com>
> 
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> ---
>  drivers/spi/Kconfig         |   22 +
>  drivers/spi/Makefile        |    3 +
>  drivers/spi/tsc2301-core.c  |  301 +++++++++++++
>  drivers/spi/tsc2301-mixer.c | 1004 ++++++++++++++++++++++++++++++++++
> +++++++++ 4 files changed, 1330 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/spi/tsc2301-core.c
>  create mode 100644 drivers/spi/tsc2301-mixer.c
> 
Mixer driver here is also obsolete and used by N800 only. I think
TSC2003 driver is easier to handle by trying to push only core,
keyboard and ts part first. There is no place for mixer driver until
it's ported to ASoC codec driver.


Jarkko

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

* Re: [PATCH 29/33] add omap ehci bus glue
  2008-09-01  5:55                                                           ` Gadiyar, Anand
@ 2008-09-01  7:56                                                             ` Felipe Balbi
  2008-09-01  8:29                                                               ` Gadiyar, Anand
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-09-01  7:56 UTC (permalink / raw)
  To: Gadiyar, Anand; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Mon, Sep 01, 2008 at 11:25:12AM +0530, Gadiyar, Anand wrote:
> > -----Original Message-----
> > From: linux-omap-owner@vger.kernel.org
> > [mailto:linux-omap-owner@vger.kernel.org] On Behalf Of Felipe Balbi
> > Sent: Saturday, August 30, 2008 10:46 PM
> > To: linux-omap@vger.kernel.org
> > Cc: Felipe Balbi
> > Subject: [PATCH 29/33] add omap ehci bus glue
> >
> > From: Felipe Balbi <felipe.balbi@nokia.com>
> >
> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> 
> 
> Hold off on this one. I'll send a better version to linux-usb
> with a CC to linux-omap.

Great. It's dropped. Care to update the MAINTAINERS file as well :-)

-- 
balbi

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

* Re: [PATCH 28/33] add tsc2301 driver
  2008-09-01  7:35                                                         ` [PATCH 28/33] add tsc2301 driver Jarkko Nikula
@ 2008-09-01  7:58                                                           ` Felipe Balbi
  2008-09-01  8:11                                                             ` Jarkko Nikula
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-09-01  7:58 UTC (permalink / raw)
  To: Jarkko Nikula; +Cc: ext Felipe Balbi, linux-omap, Felipe Balbi

On Mon, Sep 01, 2008 at 10:35:12AM +0300, Jarkko Nikula wrote:
> On Sat, 30 Aug 2008 20:16:28 +0300
> "ext Felipe Balbi" <me@felipebalbi.com> wrote:
> 
> > From: Felipe Balbi <felipe.balbi@nokia.com>
> > 
> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> > ---
> >  drivers/spi/Kconfig         |   22 +
> >  drivers/spi/Makefile        |    3 +
> >  drivers/spi/tsc2301-core.c  |  301 +++++++++++++
> >  drivers/spi/tsc2301-mixer.c | 1004 ++++++++++++++++++++++++++++++++++
> > +++++++++ 4 files changed, 1330 insertions(+), 0 deletions(-)
> >  create mode 100644 drivers/spi/tsc2301-core.c
> >  create mode 100644 drivers/spi/tsc2301-mixer.c
> > 
> Mixer driver here is also obsolete and used by N800 only. I think
> TSC2003 driver is easier to handle by trying to push only core,
> keyboard and ts part first. There is no place for mixer driver until
> it's ported to ASoC codec driver.

Cool, I'll drop this one as well. Will you take care of moving it to
ASoC ?

-- 
balbi

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

* Re: [PATCH 17/33] add tea5761 radio driver
  2008-09-01  7:24                                   ` [PATCH 17/33] add tea5761 radio driver Jarkko Nikula
@ 2008-09-01  7:59                                     ` Felipe Balbi
  2008-09-03 15:01                                       ` Eduardo Valentin
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-09-01  7:59 UTC (permalink / raw)
  To: Jarkko Nikula
  Cc: ext Felipe Balbi, linux-omap, Felipe Balbi, Eduardo Valentin

On Mon, Sep 01, 2008 at 10:24:31AM +0300, Jarkko Nikula wrote:
> On Sat, 30 Aug 2008 20:16:17 +0300
> "ext Felipe Balbi" <me@felipebalbi.com> wrote:
> 
> > From: Felipe Balbi <felipe.balbi@nokia.com>
> > 
> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> > ---
> >  drivers/media/radio/Kconfig         |   13 +
> >  drivers/media/radio/Makefile        |    1 +
> >  drivers/media/radio/radio-tea5761.c |  517 ++++++++++++++++++++++++++
> > +++++++++ 3 files changed, 531 insertions(+), 0 deletions(-)
> >  create mode 100644 drivers/media/radio/radio-tea5761.c
> > 
> I have the impression that Eduardo Valentin had work in progress to
> push this via v4l list. However, don't know the current status.

Better asking. Eduardo, were you planning to push this one upstream ?

-- 
balbi

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

* Re: [PATCH 05/33] add tlv320aic23 driver
  2008-09-01  7:22           ` [PATCH 05/33] add tlv320aic23 driver Jarkko Nikula
@ 2008-09-01  8:01             ` Felipe Balbi
  2008-09-01  8:13               ` Jarkko Nikula
  0 siblings, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-09-01  8:01 UTC (permalink / raw)
  To: Jarkko Nikula; +Cc: ext Felipe Balbi, linux-omap, Felipe Balbi

On Mon, Sep 01, 2008 at 10:22:28AM +0300, Jarkko Nikula wrote:
> On Sat, 30 Aug 2008 20:16:05 +0300
> "ext Felipe Balbi" <me@felipebalbi.com> wrote:
> 
> > From: Felipe Balbi <felipe.balbi@nokia.com>
> > 
> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> > ---
> >  drivers/i2c/chips/Kconfig       |    7 +
> >  drivers/i2c/chips/Makefile      |    1 +
> >  drivers/i2c/chips/tlv320aic23.c |  675 ++++++++++++++++++++++++++++++
> > +++++++++ 3 files changed, 683 insertions(+), 0 deletions(-)
> >  create mode 100644 drivers/i2c/chips/tlv320aic23.c
> > 
> This driver is obsolete audio codec driver and should disappear
> eventually than going upstream. At least there is no place for it.
> 
> Right path is to have ASoC codec driver for aic23 and develope ASoC
> machine driver for affected boards like Nokia 770 and OSK.

Do we still have anything using on l-o ? Could you send a patch getting
rid of it ?

-- 
balbi

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

* Re: [PATCH 00/34] omap drivers going upstream
  2008-09-01  1:22   ` andrzej zaborowski
@ 2008-09-01  8:04     ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-09-01  8:04 UTC (permalink / raw)
  To: andrzej zaborowski; +Cc: me, linux-omap, Felipe Balbi, Ville Tervo

On Mon, Sep 01, 2008 at 03:22:23AM +0200, andrzej zaborowski wrote:
> 2008/9/1 Felipe Balbi <me@felipebalbi.com>:
> > On Sat, Aug 30, 2008 at 08:16:00PM +0300, Felipe Balbi wrote:
> >> From: Felipe Balbi <felipe.balbi@nokia.com>
> >>
> >> The following drivers are going upstream for integration.
> >> They have been sitting on linux-omap for quite a while just
> >> increasing the diff against mainline and probability of
> >> merge conflicts.
> >
> > Just one comment to this. I had to left bluetooth driver out of the
> > series because it's using omap2_block/allow_sleep in the driver code.
> > That should be fixed and the set_clock function should come via
> > platform_data to the driver.
> >
> >  53 static void hci_h4p_set_clk(struct hci_h4p_info *info, int *clock, int enable)
> >  54 {
> >  55         unsigned long flags;
> >  56
> >  57         spin_lock_irqsave(&info->clocks_lock, flags);
> >  58         if (enable && !*clock) {
> >  59                 NBT_DBG_POWER("Enabling %p\n", clock);
> >  60                 clk_enable(info->uart_fclk);
> >  61 #ifdef CONFIG_ARCH_OMAP2
> >  62                 if (cpu_is_omap24xx()) {
> >  63                         clk_enable(info->uart_iclk);
> >  64                         omap2_block_sleep();
> >  65                 }
> >  66 #endif
> >  67         }
> >  68         if (!enable && *clock) {
> >  69                 NBT_DBG_POWER("Disabling %p\n", clock);
> >  70                 clk_disable(info->uart_fclk);
> >  71 #ifdef CONFIG_ARCH_OMAP2
> >  72                 if (cpu_is_omap24xx()) {
> >  73                         clk_disable(info->uart_iclk);
> >  74                         omap2_allow_sleep();
> >  75                 }
> >  76 #endif
> >  77         }
> >  78
> >  79         *clock = enable;
> >  80         spin_unlock_irqrestore(&info->clocks_lock, flags);
> >  81 }
> >
> > That driver is full of arch specific code and should be cleaned up ASAP.
> >
> > A few things I could get by briefly looking at the driver (actualy only
> > drivers/bluetooth/hci_h4p/core.c):
> 
> There's also a curious issue in hci_h4p_interrupt I hit recently but
> after looking at the rest of the driver thought it was beating a dead
> horse..., but just in case it isn't:
> the driver assumes the OMAP UART, but then it uses UART_IIR_ID which
> is only valid for standard UARTs, causing OMAP-specific Rx errors to
> be ignored silently.  Turns out that on my N810 there are actually Rx
> errors reported during firmware upload, but I didn't find a better way
> to handle them than to ignore them:
> 
> --- a/drivers/bluetooth/hci_h4p/core.c
> +++ b/drivers/bluetooth/hci_h4p/core.c
> @@ -482,7 +491,14 @@ static irqreturn_t hci_h4p_interrupt(int irq, void *data)
> 
>         NBT_DBG("In interrupt handler iir 0x%.2x\n", iir);
> 
> -       iir &= UART_IIR_ID;
> +       iir &= 0x1e; /* OMAP UART has wider INT than UART_IIR_ID */
> +
> +       /*
> +        * Often Rx errors are reported but reading the receive buffer
> +        * gives the correct data, so treat it as an Rx interrupt.
> +        */
> +       if (iir == 0xc)
> +               iir = 0x4;
> 
>         if (iir == UART_IIR_MSI) {
>                 msr = hci_h4p_inb(info, UART_MSR);
> 
> The tsc210x drivers should be upstreamable with the exception of ALSA
> code which needs to be converted to ASoC.  Maruk Vasut found a leak in
> one error path, but I can't charge the device that has the tsc2102
> that I used for testing.
> 
> I have some improvements to drivers/net/irda/omap-ir.c (clean-up and
> removing OMAP16xx specific bits to support OMAP1) but again, have no
> charger for the device.

Let's put Ville on the loop as he might be interested in it. Ville, any
comments ?

-- 
balbi

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

* Re: [PATCH 28/33] add tsc2301 driver
  2008-09-01  7:58                                                           ` Felipe Balbi
@ 2008-09-01  8:11                                                             ` Jarkko Nikula
  2008-09-01  8:37                                                               ` Felipe Balbi
  0 siblings, 1 reply; 86+ messages in thread
From: Jarkko Nikula @ 2008-09-01  8:11 UTC (permalink / raw)
  To: me; +Cc: linux-omap, Felipe Balbi

On Mon, 1 Sep 2008 10:58:08 +0300
"ext Felipe Balbi" <me@felipebalbi.com> wrote:

> > Mixer driver here is also obsolete and used by N800 only. I think
> > TSC2003 driver is easier to handle by trying to push only core,
> > keyboard and ts part first. There is no place for mixer driver until
> > it's ported to ASoC codec driver.
> 
> Cool, I'll drop this one as well. Will you take care of moving it to
> ASoC ?
> 
Unfortunately I have very low priority for it :-(


Jarkko

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

* Re: [PATCH 05/33] add tlv320aic23 driver
  2008-09-01  8:01             ` Felipe Balbi
@ 2008-09-01  8:13               ` Jarkko Nikula
       [not found]                 ` <65e025470809010802m4c12a6eeh9cf5d3a3643ff5b8@mail.gmail.com>
  0 siblings, 1 reply; 86+ messages in thread
From: Jarkko Nikula @ 2008-09-01  8:13 UTC (permalink / raw)
  To: me; +Cc: linux-omap, Felipe Balbi

On Mon, 1 Sep 2008 11:01:58 +0300
"ext Felipe Balbi" <me@felipebalbi.com> wrote:

> > Right path is to have ASoC codec driver for aic23 and develope ASoC
> > machine driver for affected boards like Nokia 770 and OSK.
> 
> Do we still have anything using on l-o ? Could you send a patch
> getting rid of it ?
> 
At least OSK is still using. I sent some time ago a patch removing 770
related hacks from the driver since they didn't compile anymore.


Jarkko

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

* RE: [PATCH 29/33] add omap ehci bus glue
  2008-09-01  7:56                                                             ` Felipe Balbi
@ 2008-09-01  8:29                                                               ` Gadiyar, Anand
  0 siblings, 0 replies; 86+ messages in thread
From: Gadiyar, Anand @ 2008-09-01  8:29 UTC (permalink / raw)
  To: me; +Cc: linux-omap, Felipe Balbi

> > Hold off on this one. I'll send a better version to linux-usb
> > with a CC to linux-omap.
> 
> Great. It's dropped. Care to update the MAINTAINERS file as well :-)
> 

Will do. I need to fold in one more patch sent by Vikram and clean up
stuff a little. Then I'll send it across.

- Anand

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

* Re: [PATCH 28/33] add tsc2301 driver
  2008-09-01  8:11                                                             ` Jarkko Nikula
@ 2008-09-01  8:37                                                               ` Felipe Balbi
  0 siblings, 0 replies; 86+ messages in thread
From: Felipe Balbi @ 2008-09-01  8:37 UTC (permalink / raw)
  To: Jarkko Nikula; +Cc: me, linux-omap, Felipe Balbi

On Mon, Sep 01, 2008 at 11:11:07AM +0300, Jarkko Nikula wrote:
> On Mon, 1 Sep 2008 10:58:08 +0300
> "ext Felipe Balbi" <me@felipebalbi.com> wrote:
> 
> > > Mixer driver here is also obsolete and used by N800 only. I think
> > > TSC2003 driver is easier to handle by trying to push only core,
> > > keyboard and ts part first. There is no place for mixer driver until
> > > it's ported to ASoC codec driver.
> > 
> > Cool, I'll drop this one as well. Will you take care of moving it to
> > ASoC ?
> > 
> Unfortunately I have very low priority for it :-(

damn it... someone else interested could take a look at it. Let's hope
someone really does ;-)

It'd be great to get rid of all these drivers from linux-omap and get in
sync with mainline, then upcoming drivers could be discussed on proper
mailing lists :-)

-- 
balbi

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

* Re: [PATCH 32/33] add omap 1-wire interface driver
  2008-08-30 17:16                                                               ` [PATCH 32/33] add omap 1-wire interface driver Felipe Balbi
  2008-08-30 17:16                                                                 ` [PATCH 33/33] add bq27000 1-wire slave driver Felipe Balbi
@ 2008-09-01 13:16                                                                 ` Madhusudhan Chikkature
  2008-09-05 17:59                                                                   ` Tony Lindgren
  1 sibling, 1 reply; 86+ messages in thread
From: Madhusudhan Chikkature @ 2008-09-01 13:16 UTC (permalink / raw)
  To: Felipe Balbi, linux-omap; +Cc: Felipe Balbi

Hi,

The HDQ patchset that I posted to Tony were given ACK by Evgeniy Polyakov on the omap list. I hope that helps.

Regards,
Madhu

----- Original Message ----- 
From: "Felipe Balbi" <me@felipebalbi.com>
To: <linux-omap@vger.kernel.org>
Cc: "Felipe Balbi" <felipe.balbi@nokia.com>
Sent: Saturday, August 30, 2008 10:46 PM
Subject: [PATCH 32/33] add omap 1-wire interface driver


> From: Felipe Balbi <felipe.balbi@nokia.com>
> 
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> ---
> arch/arm/plat-omap/include/mach/irqs.h |    2 +
> drivers/w1/masters/Kconfig             |    7 +
> drivers/w1/masters/Makefile            |    1 +
> drivers/w1/masters/omap_hdq.c          |  704 ++++++++++++++++++++++++++++++++
> 4 files changed, 714 insertions(+), 0 deletions(-)
> create mode 100644 drivers/w1/masters/omap_hdq.c
> 
> diff --git a/arch/arm/plat-omap/include/mach/irqs.h b/arch/arm/plat-omap/include/mach/irqs.h
> index 17248bb..c9e95a6 100644
> --- a/arch/arm/plat-omap/include/mach/irqs.h
> +++ b/arch/arm/plat-omap/include/mach/irqs.h
> @@ -54,6 +54,8 @@
> #define INT_TIMER2 30
> #define INT_LCD_CTRL 31
> 
> +#define INT_24XX_HDQ_IRQ 58 /* Temporarily here for driver to build */
> +
> /*
>  * OMAP-1510 specific IRQ numbers for interrupt handler 1
>  */
> diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig
> index c449309..636d4f7 100644
> --- a/drivers/w1/masters/Kconfig
> +++ b/drivers/w1/masters/Kconfig
> @@ -42,6 +42,13 @@ config W1_MASTER_DS1WM
>    in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
>    hx4700.
> 
> +config HDQ_MASTER_OMAP
> + tristate "OMAP HDQ driver"
> + depends on ARCH_OMAP2430 || ARCH_OMAP34XX
> + help
> +   Say Y here if you want support for the 1-wire or HDQ Interface
> +   on an OMAP processor.
> +
> config W1_MASTER_GPIO
>  tristate "GPIO 1-wire busmaster"
>  depends on GENERIC_GPIO
> diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile
> index 1420b5b..1daeb6e 100644
> --- a/drivers/w1/masters/Makefile
> +++ b/drivers/w1/masters/Makefile
> @@ -6,4 +6,5 @@ obj-$(CONFIG_W1_MASTER_MATROX) += matrox_w1.o
> obj-$(CONFIG_W1_MASTER_DS2490) += ds2490.o
> obj-$(CONFIG_W1_MASTER_DS2482) += ds2482.o
> obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o
> +obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o
> obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o
> diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c
> new file mode 100644
> index 0000000..880e282
> --- /dev/null
> +++ b/drivers/w1/masters/omap_hdq.c
> @@ -0,0 +1,704 @@
> +/*
> + * drivers/w1/masters/omap_hdq.c
> + *
> + * Copyright (C) 2007 Texas Instruments, Inc.
> + *
> + * This file is licensed under the terms of the GNU General Public License
> + * version 2. This program is licensed "as is" without any warranty of any
> + * kind, whether express or implied.
> + *
> + */
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/interrupt.h>
> +#include <linux/err.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <asm/irq.h>
> +#include <mach/hardware.h>
> +
> +#include "../w1.h"
> +#include "../w1_int.h"
> +
> +#define MOD_NAME "OMAP_HDQ:"
> +
> +#define OMAP_HDQ_REVISION 0x00
> +#define OMAP_HDQ_TX_DATA 0x04
> +#define OMAP_HDQ_RX_DATA 0x08
> +#define OMAP_HDQ_CTRL_STATUS 0x0c
> +#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK (1<<6)
> +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE (1<<5)
> +#define OMAP_HDQ_CTRL_STATUS_GO (1<<4)
> +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION (1<<2)
> +#define OMAP_HDQ_CTRL_STATUS_DIR (1<<1)
> +#define OMAP_HDQ_CTRL_STATUS_MODE (1<<0)
> +#define OMAP_HDQ_INT_STATUS 0x10
> +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE (1<<2)
> +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE (1<<1)
> +#define OMAP_HDQ_INT_STATUS_TIMEOUT (1<<0)
> +#define OMAP_HDQ_SYSCONFIG 0x14
> +#define OMAP_HDQ_SYSCONFIG_SOFTRESET (1<<1)
> +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE (1<<0)
> +#define OMAP_HDQ_SYSSTATUS 0x18
> +#define OMAP_HDQ_SYSSTATUS_RESETDONE (1<<0)
> +
> +#define OMAP_HDQ_FLAG_CLEAR 0
> +#define OMAP_HDQ_FLAG_SET 1
> +#define OMAP_HDQ_TIMEOUT (HZ/5)
> +
> +#define OMAP_HDQ_MAX_USER 4
> +
> +DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue);
> +int W1_ID;
> +
> +struct hdq_data {
> + resource_size_t hdq_base;
> + struct semaphore hdq_semlock;
> + int hdq_usecount;
> + struct clk *hdq_ick;
> + struct clk *hdq_fck;
> + u8 hdq_irqstatus;
> + spinlock_t hdq_spinlock;
> +};
> +
> +static struct hdq_data *hdq_data;
> +
> +static int omap_hdq_get(void);
> +static int omap_hdq_put(void);
> +static int omap_hdq_break(void);
> +
> +static int __init omap_hdq_probe(struct platform_device *pdev);
> +static int omap_hdq_remove(struct platform_device *pdev);
> +
> +static struct platform_driver omap_hdq_driver = {
> + .probe = omap_hdq_probe,
> + .remove = omap_hdq_remove,
> + .suspend = NULL,
> + .resume = NULL,
> + .driver = {
> + .name = "omap_hdq",
> + },
> +};
> +
> +static u8 omap_w1_read_byte(void *data);
> +static void omap_w1_write_byte(void *data, u8 byte);
> +static u8 omap_w1_reset_bus(void *data);
> +static void omap_w1_search_bus(void *data, u8 search_type,
> + w1_slave_found_callback slave_found);
> +
> +static struct w1_bus_master omap_w1_master = {
> + .read_byte = omap_w1_read_byte,
> + .write_byte = omap_w1_write_byte,
> + .reset_bus = omap_w1_reset_bus,
> + .search = omap_w1_search_bus,
> +};
> +
> +/*
> + * HDQ register I/O routines
> + */
> +static inline u8
> +hdq_reg_in(u32 offset)
> +{
> + return omap_readb(hdq_data->hdq_base + offset);
> +}
> +
> +static inline u8
> +hdq_reg_out(u32 offset, u8 val)
> +{
> + omap_writeb(val, hdq_data->hdq_base + offset);
> + return val;
> +}
> +
> +static inline u8
> +hdq_reg_merge(u32 offset, u8 val, u8 mask)
> +{
> + u8 new_val = (omap_readb(hdq_data->hdq_base + offset) & ~mask)
> + | (val & mask);
> + omap_writeb(new_val, hdq_data->hdq_base + offset);
> + return new_val;
> +}
> +
> +/*
> + * Wait for one or more bits in flag change.
> + * HDQ_FLAG_SET: wait until any bit in the flag is set.
> + * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared.
> + * return 0 on success and -ETIMEDOUT in the case of timeout.
> + */
> +static int
> +hdq_wait_for_flag(u32 offset, u8 flag, u8 flag_set, u8 *status)
> +{
> + int ret = 0;
> + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
> +
> + if (flag_set == OMAP_HDQ_FLAG_CLEAR) {
> + /* wait for the flag clear */
> + while (((*status = hdq_reg_in(offset)) & flag)
> + && time_before(jiffies, timeout)) {
> + set_current_state(TASK_UNINTERRUPTIBLE);
> + schedule_timeout(1);
> + }
> + if (unlikely(*status & flag))
> + ret = -ETIMEDOUT;
> + } else if (flag_set == OMAP_HDQ_FLAG_SET) {
> + /* wait for the flag set */
> + while (!((*status = hdq_reg_in(offset)) & flag)
> + && time_before(jiffies, timeout)) {
> + set_current_state(TASK_UNINTERRUPTIBLE);
> + schedule_timeout(1);
> + }
> + if (unlikely(!(*status & flag)))
> + ret = -ETIMEDOUT;
> + } else
> + return -EINVAL;
> +
> + return ret;
> +}
> +
> +/*
> + * write out a byte and fill *status with HDQ_INT_STATUS
> + */
> +static int
> +hdq_write_byte(u8 val, u8 *status)
> +{
> + int ret;
> + u8 tmp_status;
> + unsigned long irqflags;
> +
> + *status = 0;
> +
> + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> + /* clear interrupt flags via a dummy read */
> + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> + /* ISR loads it with new INT_STATUS */
> + hdq_data->hdq_irqstatus = 0;
> + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> +
> + hdq_reg_out(OMAP_HDQ_TX_DATA, val);
> +
> + /* set the GO bit */
> + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
> + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
> + /* wait for the TXCOMPLETE bit */
> + ret = wait_event_interruptible_timeout(hdq_wait_queue,
> + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
> + if (unlikely(ret < 0)) {
> + pr_debug("wait interrupted");
> + return -EINTR;
> + }
> +
> + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> + *status = hdq_data->hdq_irqstatus;
> + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> + /* check irqstatus */
> + if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) {
> + pr_debug("timeout waiting for TXCOMPLETE/RXCOMPLETE, %x",
> + *status);
> + return -ETIMEDOUT;
> + }
> +
> + /* wait for the GO bit return to zero */
> + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
> + OMAP_HDQ_FLAG_CLEAR, &tmp_status);
> + if (ret) {
> + pr_debug("timeout waiting GO bit return to zero, %x",
> + tmp_status);
> + return ret;
> + }
> +
> + return ret;
> +}
> +
> +/*
> + * HDQ Interrupt service routine.
> + */
> +static irqreturn_t
> +hdq_isr(int irq, void *arg)
> +{
> + unsigned long irqflags;
> +
> + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> + hdq_data->hdq_irqstatus = hdq_reg_in(OMAP_HDQ_INT_STATUS);
> + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> + pr_debug("hdq_isr: %x", hdq_data->hdq_irqstatus);
> +
> + if (hdq_data->hdq_irqstatus &
> + (OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE
> + | OMAP_HDQ_INT_STATUS_TIMEOUT)) {
> + /* wake up sleeping process */
> + wake_up_interruptible(&hdq_wait_queue);
> + }
> +
> + return IRQ_HANDLED;
> +}
> +
> +/*
> + * HDQ Mode: always return success.
> + */
> +static u8 omap_w1_reset_bus(void *data)
> +{
> + return 0;
> +}
> +
> +/*
> + * W1 search callback function.
> + */
> +static void omap_w1_search_bus(void *data, u8 search_type,
> + w1_slave_found_callback slave_found)
> +{
> + u64 module_id, rn_le, cs, id;
> +
> + if (W1_ID)
> + module_id = W1_ID;
> + else
> + module_id = 0x1;
> +
> + rn_le = cpu_to_le64(module_id);
> + /*
> + * HDQ might not obey truly the 1-wire spec.
> + * So calculate CRC based on module parameter.
> + */
> + cs = w1_calc_crc8((u8 *)&rn_le, 7);
> + id = (cs << 56) | module_id;
> +
> + slave_found(data, id);
> +}
> +
> +static int
> +_omap_hdq_reset(void)
> +{
> + int ret;
> + u8 tmp_status;
> +
> + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_SOFTRESET);
> + /*
> + * Select HDQ mode & enable clocks.
> + * It is observed that INT flags can't be cleared via a read and GO/INIT
> + * won't return to zero if interrupt is disabled. So we always enable
> + * interrupt.
> + */
> + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> +
> + /* wait for reset to complete */
> + ret = hdq_wait_for_flag(OMAP_HDQ_SYSSTATUS,
> + OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status);
> + if (ret)
> + pr_debug("timeout waiting HDQ reset, %x", tmp_status);
> + else {
> + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_AUTOIDLE);
> + }
> +
> + return ret;
> +}
> +
> +/*
> + * Issue break pulse to the device.
> + */
> +static int
> +omap_hdq_break()
> +{
> + int ret;
> + u8 tmp_status;
> + unsigned long irqflags;
> +
> + ret = down_interruptible(&hdq_data->hdq_semlock);
> + if (ret < 0)
> + return -EINTR;
> +
> + if (!hdq_data->hdq_usecount) {
> + up(&hdq_data->hdq_semlock);
> + return -EINVAL;
> + }
> +
> + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> + /* clear interrupt flags via a dummy read */
> + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> + /* ISR loads it with new INT_STATUS */
> + hdq_data->hdq_irqstatus = 0;
> + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> +
> + /* set the INIT and GO bit */
> + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
> + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO,
> + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
> + OMAP_HDQ_CTRL_STATUS_GO);
> +
> + /* wait for the TIMEOUT bit */
> + ret = wait_event_interruptible_timeout(hdq_wait_queue,
> + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
> + if (unlikely(ret < 0)) {
> + pr_debug("wait interrupted");
> + up(&hdq_data->hdq_semlock);
> + return -EINTR;
> + }
> +
> + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> + tmp_status = hdq_data->hdq_irqstatus;
> + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> + /* check irqstatus */
> + if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) {
> + pr_debug("timeout waiting for TIMEOUT, %x", tmp_status);
> + up(&hdq_data->hdq_semlock);
> + return -ETIMEDOUT;
> + }
> + /*
> + * wait for both INIT and GO bits rerurn to zero.
> + * zero wait time expected for interrupt mode.
> + */
> + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS,
> + OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
> + OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR,
> + &tmp_status);
> + if (ret)
> + pr_debug("timeout waiting INIT&GO bits return to zero, %x",
> + tmp_status);
> +
> + up(&hdq_data->hdq_semlock);
> + return ret;
> +}
> +
> +static int hdq_read_byte(u8 *val)
> +{
> + int ret;
> + u8 status;
> + unsigned long irqflags;
> +
> + ret = down_interruptible(&hdq_data->hdq_semlock);
> + if (ret < 0)
> + return -EINTR;
> +
> + if (!hdq_data->hdq_usecount) {
> + up(&hdq_data->hdq_semlock);
> + return -EINVAL;
> + }
> +
> + if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
> + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
> + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO,
> + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
> + /*
> + * The RX comes immediately after TX. It
> + * triggers another interrupt before we
> + * sleep. So we have to wait for RXCOMPLETE bit.
> + */
> + {
> + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
> + while (!(hdq_data->hdq_irqstatus
> + & OMAP_HDQ_INT_STATUS_RXCOMPLETE)
> + && time_before(jiffies, timeout)) {
> + set_current_state(TASK_UNINTERRUPTIBLE);
> + schedule_timeout(1);
> + }
> + }
> + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, 0,
> + OMAP_HDQ_CTRL_STATUS_DIR);
> + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> + status = hdq_data->hdq_irqstatus;
> + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> + /* check irqstatus */
> + if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
> + pr_debug("timeout waiting for RXCOMPLETE, %x", status);
> + up(&hdq_data->hdq_semlock);
> + return -ETIMEDOUT;
> + }
> + }
> + /* the data is ready. Read it in! */
> + *val = hdq_reg_in(OMAP_HDQ_RX_DATA);
> + up(&hdq_data->hdq_semlock);
> +
> + return 0;
> +
> +}
> +
> +/*
> + * Enable clocks and set the controller to HDQ mode.
> + */
> +static int
> +omap_hdq_get()
> +{
> + int ret = 0;
> +
> + ret = down_interruptible(&hdq_data->hdq_semlock);
> + if (ret < 0)
> + return -EINTR;
> +
> + if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) {
> + pr_debug("attempt to exceed the max use count");
> + up(&hdq_data->hdq_semlock);
> + ret = -EINVAL;
> + } else {
> + hdq_data->hdq_usecount++;
> + try_module_get(THIS_MODULE);
> + if (1 == hdq_data->hdq_usecount) {
> + if (clk_enable(hdq_data->hdq_ick)) {
> + pr_debug("Can not enable ick\n");
> + clk_put(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_fck);
> + up(&hdq_data->hdq_semlock);
> + return -ENODEV;
> + }
> + if (clk_enable(hdq_data->hdq_fck)) {
> + pr_debug("Can not enable fck\n");
> + clk_put(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_fck);
> + up(&hdq_data->hdq_semlock);
> + return -ENODEV;
> + }
> +
> + /* make sure HDQ is out of reset */
> + if (!(hdq_reg_in(OMAP_HDQ_SYSSTATUS) &
> + OMAP_HDQ_SYSSTATUS_RESETDONE)) {
> + ret = _omap_hdq_reset();
> + if (ret)
> + /* back up the count */
> + hdq_data->hdq_usecount--;
> + } else {
> + /* select HDQ mode & enable clocks */
> + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> + hdq_reg_out(OMAP_HDQ_SYSCONFIG,
> + OMAP_HDQ_SYSCONFIG_AUTOIDLE);
> + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> + }
> + }
> + }
> + up(&hdq_data->hdq_semlock);
> + return ret;
> +}
> +
> +/*
> + * Disable clocks to the module.
> + */
> +static int
> +omap_hdq_put()
> +{
> + int ret = 0;
> +
> + ret = down_interruptible(&hdq_data->hdq_semlock);
> + if (ret < 0)
> + return -EINTR;
> +
> + if (0 == hdq_data->hdq_usecount) {
> + pr_debug("attempt to decrement use count when it is zero");
> + ret = -EINVAL;
> + } else {
> + hdq_data->hdq_usecount--;
> + module_put(THIS_MODULE);
> + if (0 == hdq_data->hdq_usecount) {
> + clk_disable(hdq_data->hdq_ick);
> + clk_disable(hdq_data->hdq_fck);
> + }
> + }
> + up(&hdq_data->hdq_semlock);
> + return ret;
> +}
> +
> +/*
> + * Used to control the call to omap_hdq_get and omap_hdq_put.
> + * HDQ Protocol: Write the CMD|REG_address first, followed by
> + * the data wrire or read.
> + */
> +static int init_trans;
> +
> +/*
> + * Read a byte of data from the device.
> + */
> +static u8 omap_w1_read_byte(void *data)
> +{
> + u8 val;
> + int ret;
> +
> + ret = hdq_read_byte(&val);
> + if (ret) {
> + init_trans = 0;
> + omap_hdq_put();
> + return -1;
> + }
> +
> + /* Write followed by a read, release the module */
> + if (init_trans) {
> + init_trans = 0;
> + omap_hdq_put();
> + }
> +
> + return val;
> +}
> +
> +/*
> + * Write a byte of data to the device.
> + */
> +static void omap_w1_write_byte(void *data, u8 byte)
> +{
> + u8 status;
> +
> + /* First write to initialize the transfer */
> + if (init_trans == 0)
> + omap_hdq_get();
> +
> + init_trans++;
> +
> + hdq_write_byte(byte, &status);
> + pr_debug("Ctrl status %x\n", status);
> +
> + /* Second write, data transfered. Release the module */
> + if (init_trans > 1) {
> + omap_hdq_put();
> + init_trans = 0;
> + }
> +
> + return;
> +}
> +
> +static int __init omap_hdq_probe(struct platform_device *pdev)
> +{
> + struct resource *res;
> + int ret, irq;
> + u8 rev;
> +
> + if (!pdev)
> + return -ENODEV;
> +
> + hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL);
> + if (!hdq_data)
> + return -ENODEV;
> +
> + platform_set_drvdata(pdev, hdq_data);
> +
> + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + if (res == NULL) {
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return -ENXIO;
> + }
> +
> + hdq_data->hdq_base = res->start;
> +
> + /* get interface & functional clock objects */
> + hdq_data->hdq_ick = clk_get(&pdev->dev, "hdq_ick");
> + hdq_data->hdq_fck = clk_get(&pdev->dev, "hdq_fck");
> +
> + if (IS_ERR(hdq_data->hdq_ick) || IS_ERR(hdq_data->hdq_fck)) {
> + pr_debug("Can't get HDQ clock objects\n");
> + if (IS_ERR(hdq_data->hdq_ick)) {
> + ret = PTR_ERR(hdq_data->hdq_ick);
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return ret;
> + }
> + if (IS_ERR(hdq_data->hdq_fck)) {
> + ret = PTR_ERR(hdq_data->hdq_fck);
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return ret;
> + }
> + }
> +
> + hdq_data->hdq_usecount = 0;
> + sema_init(&hdq_data->hdq_semlock, 1);
> +
> + if (clk_enable(hdq_data->hdq_ick)) {
> + pr_debug("Can not enable ick\n");
> + clk_put(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_fck);
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return -ENODEV;
> + }
> +
> + if (clk_enable(hdq_data->hdq_fck)) {
> + pr_debug("Can not enable fck\n");
> + clk_disable(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_fck);
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return -ENODEV;
> + }
> +
> + rev = hdq_reg_in(OMAP_HDQ_REVISION);
> + pr_info("OMAP HDQ Hardware Revision %c.%c. Driver in %s mode.\n",
> + (rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt");
> +
> + spin_lock_init(&hdq_data->hdq_spinlock);
> + omap_hdq_break();
> +
> + irq = platform_get_irq(pdev, 0);
> + if (irq < 0) {
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return -ENXIO;
> + }
> +
> + if (request_irq(irq, hdq_isr, IRQF_DISABLED, "OMAP HDQ",
> + &hdq_data->hdq_semlock)) {
> + pr_debug("request_irq failed\n");
> + clk_disable(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_fck);
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return -ENODEV;
> + }
> +
> + /* don't clock the HDQ until it is needed */
> + clk_disable(hdq_data->hdq_ick);
> + clk_disable(hdq_data->hdq_fck);
> +
> + ret = w1_add_master_device(&omap_w1_master);
> + if (ret) {
> + pr_debug("Failure in registering w1 master\n");
> + clk_put(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_fck);
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int omap_hdq_remove(struct platform_device *pdev)
> +{
> + down_interruptible(&hdq_data->hdq_semlock);
> + if (0 != hdq_data->hdq_usecount) {
> + pr_debug("removed when use count is not zero\n");
> + return -EBUSY;
> + }
> + up(&hdq_data->hdq_semlock);
> +
> + /* remove module dependency */
> + clk_put(hdq_data->hdq_ick);
> + clk_put(hdq_data->hdq_fck);
> + free_irq(INT_24XX_HDQ_IRQ, &hdq_data->hdq_semlock);
> + platform_set_drvdata(pdev, NULL);
> + kfree(hdq_data);
> +
> + return 0;
> +}
> +
> +static int __init
> +omap_hdq_init(void)
> +{
> + return platform_driver_register(&omap_hdq_driver);
> +}
> +
> +static void __exit
> +omap_hdq_exit(void)
> +{
> + platform_driver_unregister(&omap_hdq_driver);
> +}
> +
> +module_init(omap_hdq_init);
> +module_exit(omap_hdq_exit);
> +
> +module_param(W1_ID, int, S_IRUSR);
> +
> +MODULE_AUTHOR("Texas Instruments");
> +MODULE_DESCRIPTION("HDQ driver Library");
> +MODULE_LICENSE("GPL");
> -- 
> 1.6.0.1.141.g445ca
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-omap" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
>

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

* [PATCH 05/33] add tlv320aic23 driver
       [not found]                 ` <65e025470809010802m4c12a6eeh9cf5d3a3643ff5b8@mail.gmail.com>
@ 2008-09-01 15:03                   ` Yuri Jaeger Monti
  2008-09-01 15:19                   ` Felipe Balbi
  1 sibling, 0 replies; 86+ messages in thread
From: Yuri Jaeger Monti @ 2008-09-01 15:03 UTC (permalink / raw)
  To: linux-omap

We're still using OSK here. The patch compiled fine, although I'm
still having some troubles with the audio in the board.
Unfortunately I still don't have the knowledge to develop the ASoC
driver, but I would be glad to help.

Cheers
---
Yuri Jaeger Monti

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

* Re: [PATCH 05/33] add tlv320aic23 driver
       [not found]                 ` <65e025470809010802m4c12a6eeh9cf5d3a3643ff5b8@mail.gmail.com>
  2008-09-01 15:03                   ` Yuri Jaeger Monti
@ 2008-09-01 15:19                   ` Felipe Balbi
  2008-09-04  1:40                     ` Tony Lindgren
  1 sibling, 1 reply; 86+ messages in thread
From: Felipe Balbi @ 2008-09-01 15:19 UTC (permalink / raw)
  To: Yuri Jaeger Monti; +Cc: Jarkko Nikula, me, linux-omap, Felipe Balbi

On Mon, Sep 01, 2008 at 12:02:44PM -0300, Yuri Jaeger Monti wrote:
> We're still using OSK here. The patch compiled fine, although I'm still having
> some troubles with the audio in the board.
> Unfortunately I still don't have the knowledge to develop the ASoC driver, but
> I would be glad to help.

Please do, take any of other ASoC driver as an example and try to make
it work with tlv ;-)

-- 
balbi

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

* Re: [PATCH 12/33] Add tsc2005 touchscreen driver
  2008-08-30 17:16                       ` [PATCH 12/33] Add tsc2005 touchscreen driver Felipe Balbi
                                           ` (2 preceding siblings ...)
  2008-09-01  1:01                         ` andrzej zaborowski
@ 2008-09-02 21:33                         ` Russell King - ARM Linux
  2008-09-07 18:47                           ` David Brownell
  3 siblings, 1 reply; 86+ messages in thread
From: Russell King - ARM Linux @ 2008-09-02 21:33 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Sat, Aug 30, 2008 at 08:16:12PM +0300, Felipe Balbi wrote:
> +#include <linux/kernel.h>
> +#include <linux/module.h>
> +#include <linux/input.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/spi/spi.h>
> +
> +#ifdef CONFIG_ARCH_OMAP
> +#include <mach/gpio.h>
> +#endif

#include <linux/gpio.h> ?

If this driver depends on having gpio, shouldn't that be reflected in the
Kconfig entry?  ...

> +static int __devinit tsc2005_ts_init(struct tsc2005 *ts,
> +				     struct tsc2005_platform_data *pdata)
> +{
> +	struct input_dev *idev;
> +	int dav_gpio, r;
> +	int x_max, y_max;
> +	int x_fudge, y_fudge, p_fudge;
> +
> +	if (pdata->dav_gpio < 0) {
> +		dev_err(&ts->spi->dev, "need DAV GPIO");
> +		return -EINVAL;
> +	}
> +	dav_gpio = pdata->dav_gpio;
> +	ts->dav_gpio = dav_gpio;
> +	dev_dbg(&ts->spi->dev, "TSC2005: DAV GPIO = %d\n", dav_gpio);
> +
> +#ifdef CONFIG_ARCH_OMAP
> +	r = omap_request_gpio(dav_gpio);
> +	if (r < 0) {
> +		dev_err(&ts->spi->dev, "unable to get DAV GPIO");
> +		goto err1;
> +	}
> +	omap_set_gpio_direction(dav_gpio, 1);
> +	ts->irq = OMAP_GPIO_IRQ(dav_gpio);

In fact, this driver requires ARCH_OMAP to set ts->irq, otherwise
it's uninitialized.

> +	dev_dbg(&ts->spi->dev, "TSC2005: DAV IRQ = %d\n", ts->irq);
> +#endif

Can this be converted to the generic gpio stuff?

> +	init_timer(&ts->penup_timer);
> +	setup_timer(&ts->penup_timer, tsc2005_ts_penup_timer_handler,
> +			(unsigned long)ts);

setup_timer() does init_timer() so the first line is redundant.

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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-30 17:16                                         ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver Felipe Balbi
  2008-08-30 17:16                                           ` [PATCH 22/33] add omap nand drivers Felipe Balbi
  2008-08-31 21:06                                           ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver David Brownell
@ 2008-09-02 22:05                                           ` Russell King - ARM Linux
  2 siblings, 0 replies; 86+ messages in thread
From: Russell King - ARM Linux @ 2008-09-02 22:05 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: linux-omap, Felipe Balbi

On Sat, Aug 30, 2008 at 08:16:21PM +0300, Felipe Balbi wrote:
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/delay.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/platform_device.h>
> +#include <linux/workqueue.h>
> +#include <linux/timer.h>
> +#include <linux/clk.h>
> +#include <linux/mmc/host.h>
> +#include <linux/io.h>
> +#include <linux/semaphore.h>
> +#include <asm/dma.h>
> +#include <mach/hardware.h>
> +#include <mach/board.h>

Is there really anything in here which is board specific?  If yes,
shouldn't that be passed through platform data?


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

* Re: [PATCH 17/33] add tea5761 radio driver
  2008-09-01  7:59                                     ` Felipe Balbi
@ 2008-09-03 15:01                                       ` Eduardo Valentin
  0 siblings, 0 replies; 86+ messages in thread
From: Eduardo Valentin @ 2008-09-03 15:01 UTC (permalink / raw)
  To: ext Felipe Balbi
  Cc: Jarkko Nikula, linux-omap, Felipe Balbi, Eduardo Valentin

Hi guys,

Sorry for the late answer.

On Mon, Sep 01, 2008 at 10:59:38AM +0300, ext Felipe Balbi wrote:
> On Mon, Sep 01, 2008 at 10:24:31AM +0300, Jarkko Nikula wrote:
> > On Sat, 30 Aug 2008 20:16:17 +0300
> > "ext Felipe Balbi" <me@felipebalbi.com> wrote:
> > 
> > > From: Felipe Balbi <felipe.balbi@nokia.com>
> > > 
> > > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> > > ---
> > >  drivers/media/radio/Kconfig         |   13 +
> > >  drivers/media/radio/Makefile        |    1 +
> > >  drivers/media/radio/radio-tea5761.c |  517 ++++++++++++++++++++++++++
> > > +++++++++ 3 files changed, 531 insertions(+), 0 deletions(-)
> > >  create mode 100644 drivers/media/radio/radio-tea5761.c
> > > 
> > I have the impression that Eduardo Valentin had work in progress to
> > push this via v4l list. However, don't know the current status.

Yes, that's true. I tried pushing this through v4l list.
But it got blocked. It seams there is duplicated code here.
v4l already has a radio interface for tea. However, it is
based on radio tuner api.

The work here is to update our driver to use current code
already in v4l2. Just a simple patch to remove duplicated code
and adapt to use correct API.

> 
> Better asking. Eduardo, were you planning to push this one upstream ?
> 
> -- 
> balbi

-- 

BR,

---
Eduardo Bezerra Valentin
+55 92 21261118

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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-31 22:02                                                 ` Felipe Balbi
@ 2008-09-04  1:02                                                   ` Tony Lindgren
  0 siblings, 0 replies; 86+ messages in thread
From: Tony Lindgren @ 2008-09-04  1:02 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: David Brownell, linux-omap, Felipe Balbi

* Felipe Balbi <me@felipebalbi.com> [080831 15:03]:
> On Sun, Aug 31, 2008 at 02:51:18PM -0700, David Brownell wrote:
> > Fixable, I'd expect.  :)
> 
> I'm sure it is :-)
> 
> > > so even if we 
> > > have all the drivers there. Mainline has support up to omap2430, if i'm
> > > not wrong. So looks like beagleboard users will have to wait a bit
> > > longer.
> > 
> > Well, it's not a 2.6.27 goal.  But I'd think it should be very
> > possible for 2.6.28 merging.
> 
> Should be possible, but Tony can say for sure.

Sorry I'm still behind with my emails.. I think we have a fair chance of
getting minimal omap3 board configurations booting with mainline kernel.
Most core stuff is starting to be somewhat in sync for mach-omap2, and
then we just need the board-*.c files. PM stuff is still under construction
of course.

> > However, I'll re-emphasize that doing that means submitting the
> > patches for real, not to the OMAP list.  And since mainline is
> > now at RC5, there's not a lot of time yet to get this new code
> > reviewed and entered into the MMC queue...
> 
> I think it's not a must to merge it for 2.6.27 time, but 2.6.28 would be
> good enough. As long as we merge them upstream and avoid merge conflicts
> on l-o, I'd be happy. Besides the fact that drivers should be discussed
> on the proper mainling list for facts that you already said in another
> comment, right ? :-)

Yes, drivers should be discussed on the appropriate mailing lists.

Tony

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

* Re: [PATCH 05/33] add tlv320aic23 driver
  2008-09-01 15:19                   ` Felipe Balbi
@ 2008-09-04  1:40                     ` Tony Lindgren
  0 siblings, 0 replies; 86+ messages in thread
From: Tony Lindgren @ 2008-09-04  1:40 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: Yuri Jaeger Monti, Jarkko Nikula, linux-omap, Felipe Balbi

* Felipe Balbi <me@felipebalbi.com> [080901 08:20]:
> On Mon, Sep 01, 2008 at 12:02:44PM -0300, Yuri Jaeger Monti wrote:
> > We're still using OSK here. The patch compiled fine, although I'm still having
> > some troubles with the audio in the board.
> > Unfortunately I still don't have the knowledge to develop the ASoC driver, but
> > I would be glad to help.
> 
> Please do, take any of other ASoC driver as an example and try to make
> it work with tlv ;-)

Let's get rid of all the old audio drivers once osk5912 ASoC driver
works. I'll export the old audio code into a patch on my website for
references in case anybody needs it.

Tony

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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-08-31 21:06                                           ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver David Brownell
  2008-08-31 21:31                                             ` Felipe Balbi
@ 2008-09-04 13:26                                             ` Madhusudhan Chikkature
  2008-09-05  8:16                                               ` David Brownell
  1 sibling, 1 reply; 86+ messages in thread
From: Madhusudhan Chikkature @ 2008-09-04 13:26 UTC (permalink / raw)
  To: David Brownell, Felipe Balbi; +Cc: linux-omap, Felipe Balbi

Hi Dave,

>OK, this one may need cleanup ... but I don't disagree that it
>should go upstream.
I had fixed several comments provided by Pierre and Russell before Tony merged this patch. But then we have further added several fixes on top of the driver. Do you have any specific comments when you mean cleanup?

Regards,
Madhu
----- Original Message ----- 
From: "David Brownell" <david-b@pacbell.net>
To: "Felipe Balbi" <me@felipebalbi.com>
Cc: <linux-omap@vger.kernel.org>; "Felipe Balbi" <felipe.balbi@nokia.com>
Sent: Monday, September 01, 2008 2:36 AM
Subject: Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver


On Saturday 30 August 2008, Felipe Balbi wrote:
> From: Felipe Balbi <felipe.balbi@nokia.com>
> 
> Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> ---
> drivers/mmc/host/Kconfig | 13 +-
> drivers/mmc/host/Makefile | 1 +
> drivers/mmc/host/omap_hsmmc.c | 1069 +++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 1082 insertions(+), 1 deletions(-)
> create mode 100644 drivers/mmc/host/omap_hsmmc.c

OK, this one may need cleanup ... but I don't disagree that it
should go upstream.

Now the process for getting it there involves posting to LKML
and probably CCing the MMC maintainer...

One reason I'd like to see this particular driver upstream is
so that folk using www.beagleboard.org hardware can stand a
real chance of using mainline kernels for development.  That's
a small set of drivers, so that's a very achievable goal.



--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html



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

* Re: [PATCH 21/33] add OMAP HighSpeed mmc controller driver
  2008-09-04 13:26                                             ` Madhusudhan Chikkature
@ 2008-09-05  8:16                                               ` David Brownell
  0 siblings, 0 replies; 86+ messages in thread
From: David Brownell @ 2008-09-05  8:16 UTC (permalink / raw)
  To: Madhusudhan Chikkature; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

On Thursday 04 September 2008, Madhusudhan Chikkature wrote:
> > OK, this one may need cleanup ... but I don't disagree that it
> > should go upstream.
> 
> I had fixed several comments provided by Pierre and Russell
> before Tony merged this patch. But then we have further added
> several fixes on top of the driver. Do you have any specific
> comments when you mean cleanup   

My main concerns relate to board-specific setup, which might
not end up impacting the driver code as much as the code in
arch/arm/mach-omap2/hsmmc.c (and its callers).

Basically, if you look at the hsmmc.c file it's got a LOT of
board-specific stuff hard-wired.  The way it *should* work is
the board-XYZ.c file passes that into setup code ... and that
info can include things like "are all 8 data lines available?",
as well as card detect triggering and writeprotect switch
access.

There might be other issues ... but it was rather easy to
notice those problems when for example the HSMMC on a Beagle
comes up with truncated functionality.

- Dave


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

* Re: [PATCH 32/33] add omap 1-wire interface driver
  2008-09-01 13:16                                                                 ` [PATCH 32/33] add omap 1-wire interface driver Madhusudhan Chikkature
@ 2008-09-05 17:59                                                                   ` Tony Lindgren
  2008-09-08 12:15                                                                     ` Madhusudhan Chikkature
  0 siblings, 1 reply; 86+ messages in thread
From: Tony Lindgren @ 2008-09-05 17:59 UTC (permalink / raw)
  To: Madhusudhan Chikkature; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

* Madhusudhan Chikkature <madhu.cr@ti.com> [080901 06:16]:
> Hi,
> 
> The HDQ patchset that I posted to Tony were given ACK by Evgeniy Polyakov on the omap list. I hope that helps.

Madhu, can you post your 1-wire driver to LKML and Cc Evgeniy Polyakov
so we can get that integrated? Also Cc l-o list.

Thanks,

Tony

> 
> Regards,
> Madhu
> 
> ----- Original Message ----- 
> From: "Felipe Balbi" <me@felipebalbi.com>
> To: <linux-omap@vger.kernel.org>
> Cc: "Felipe Balbi" <felipe.balbi@nokia.com>
> Sent: Saturday, August 30, 2008 10:46 PM
> Subject: [PATCH 32/33] add omap 1-wire interface driver
> 
> 
> > From: Felipe Balbi <felipe.balbi@nokia.com>
> > 
> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> > ---
> > arch/arm/plat-omap/include/mach/irqs.h |    2 +
> > drivers/w1/masters/Kconfig             |    7 +
> > drivers/w1/masters/Makefile            |    1 +
> > drivers/w1/masters/omap_hdq.c          |  704 ++++++++++++++++++++++++++++++++
> > 4 files changed, 714 insertions(+), 0 deletions(-)
> > create mode 100644 drivers/w1/masters/omap_hdq.c
> > 
> > diff --git a/arch/arm/plat-omap/include/mach/irqs.h b/arch/arm/plat-omap/include/mach/irqs.h
> > index 17248bb..c9e95a6 100644
> > --- a/arch/arm/plat-omap/include/mach/irqs.h
> > +++ b/arch/arm/plat-omap/include/mach/irqs.h
> > @@ -54,6 +54,8 @@
> > #define INT_TIMER2 30
> > #define INT_LCD_CTRL 31
> > 
> > +#define INT_24XX_HDQ_IRQ 58 /* Temporarily here for driver to build */
> > +
> > /*
> >  * OMAP-1510 specific IRQ numbers for interrupt handler 1
> >  */
> > diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig
> > index c449309..636d4f7 100644
> > --- a/drivers/w1/masters/Kconfig
> > +++ b/drivers/w1/masters/Kconfig
> > @@ -42,6 +42,13 @@ config W1_MASTER_DS1WM
> >    in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
> >    hx4700.
> > 
> > +config HDQ_MASTER_OMAP
> > + tristate "OMAP HDQ driver"
> > + depends on ARCH_OMAP2430 || ARCH_OMAP34XX
> > + help
> > +   Say Y here if you want support for the 1-wire or HDQ Interface
> > +   on an OMAP processor.
> > +
> > config W1_MASTER_GPIO
> >  tristate "GPIO 1-wire busmaster"
> >  depends on GENERIC_GPIO
> > diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile
> > index 1420b5b..1daeb6e 100644
> > --- a/drivers/w1/masters/Makefile
> > +++ b/drivers/w1/masters/Makefile
> > @@ -6,4 +6,5 @@ obj-$(CONFIG_W1_MASTER_MATROX) += matrox_w1.o
> > obj-$(CONFIG_W1_MASTER_DS2490) += ds2490.o
> > obj-$(CONFIG_W1_MASTER_DS2482) += ds2482.o
> > obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o
> > +obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o
> > obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o
> > diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c
> > new file mode 100644
> > index 0000000..880e282
> > --- /dev/null
> > +++ b/drivers/w1/masters/omap_hdq.c
> > @@ -0,0 +1,704 @@
> > +/*
> > + * drivers/w1/masters/omap_hdq.c
> > + *
> > + * Copyright (C) 2007 Texas Instruments, Inc.
> > + *
> > + * This file is licensed under the terms of the GNU General Public License
> > + * version 2. This program is licensed "as is" without any warranty of any
> > + * kind, whether express or implied.
> > + *
> > + */
> > +#include <linux/kernel.h>
> > +#include <linux/module.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/err.h>
> > +#include <linux/clk.h>
> > +#include <linux/io.h>
> > +#include <asm/irq.h>
> > +#include <mach/hardware.h>
> > +
> > +#include "../w1.h"
> > +#include "../w1_int.h"
> > +
> > +#define MOD_NAME "OMAP_HDQ:"
> > +
> > +#define OMAP_HDQ_REVISION 0x00
> > +#define OMAP_HDQ_TX_DATA 0x04
> > +#define OMAP_HDQ_RX_DATA 0x08
> > +#define OMAP_HDQ_CTRL_STATUS 0x0c
> > +#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK (1<<6)
> > +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE (1<<5)
> > +#define OMAP_HDQ_CTRL_STATUS_GO (1<<4)
> > +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION (1<<2)
> > +#define OMAP_HDQ_CTRL_STATUS_DIR (1<<1)
> > +#define OMAP_HDQ_CTRL_STATUS_MODE (1<<0)
> > +#define OMAP_HDQ_INT_STATUS 0x10
> > +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE (1<<2)
> > +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE (1<<1)
> > +#define OMAP_HDQ_INT_STATUS_TIMEOUT (1<<0)
> > +#define OMAP_HDQ_SYSCONFIG 0x14
> > +#define OMAP_HDQ_SYSCONFIG_SOFTRESET (1<<1)
> > +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE (1<<0)
> > +#define OMAP_HDQ_SYSSTATUS 0x18
> > +#define OMAP_HDQ_SYSSTATUS_RESETDONE (1<<0)
> > +
> > +#define OMAP_HDQ_FLAG_CLEAR 0
> > +#define OMAP_HDQ_FLAG_SET 1
> > +#define OMAP_HDQ_TIMEOUT (HZ/5)
> > +
> > +#define OMAP_HDQ_MAX_USER 4
> > +
> > +DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue);
> > +int W1_ID;
> > +
> > +struct hdq_data {
> > + resource_size_t hdq_base;
> > + struct semaphore hdq_semlock;
> > + int hdq_usecount;
> > + struct clk *hdq_ick;
> > + struct clk *hdq_fck;
> > + u8 hdq_irqstatus;
> > + spinlock_t hdq_spinlock;
> > +};
> > +
> > +static struct hdq_data *hdq_data;
> > +
> > +static int omap_hdq_get(void);
> > +static int omap_hdq_put(void);
> > +static int omap_hdq_break(void);
> > +
> > +static int __init omap_hdq_probe(struct platform_device *pdev);
> > +static int omap_hdq_remove(struct platform_device *pdev);
> > +
> > +static struct platform_driver omap_hdq_driver = {
> > + .probe = omap_hdq_probe,
> > + .remove = omap_hdq_remove,
> > + .suspend = NULL,
> > + .resume = NULL,
> > + .driver = {
> > + .name = "omap_hdq",
> > + },
> > +};
> > +
> > +static u8 omap_w1_read_byte(void *data);
> > +static void omap_w1_write_byte(void *data, u8 byte);
> > +static u8 omap_w1_reset_bus(void *data);
> > +static void omap_w1_search_bus(void *data, u8 search_type,
> > + w1_slave_found_callback slave_found);
> > +
> > +static struct w1_bus_master omap_w1_master = {
> > + .read_byte = omap_w1_read_byte,
> > + .write_byte = omap_w1_write_byte,
> > + .reset_bus = omap_w1_reset_bus,
> > + .search = omap_w1_search_bus,
> > +};
> > +
> > +/*
> > + * HDQ register I/O routines
> > + */
> > +static inline u8
> > +hdq_reg_in(u32 offset)
> > +{
> > + return omap_readb(hdq_data->hdq_base + offset);
> > +}
> > +
> > +static inline u8
> > +hdq_reg_out(u32 offset, u8 val)
> > +{
> > + omap_writeb(val, hdq_data->hdq_base + offset);
> > + return val;
> > +}
> > +
> > +static inline u8
> > +hdq_reg_merge(u32 offset, u8 val, u8 mask)
> > +{
> > + u8 new_val = (omap_readb(hdq_data->hdq_base + offset) & ~mask)
> > + | (val & mask);
> > + omap_writeb(new_val, hdq_data->hdq_base + offset);
> > + return new_val;
> > +}
> > +
> > +/*
> > + * Wait for one or more bits in flag change.
> > + * HDQ_FLAG_SET: wait until any bit in the flag is set.
> > + * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared.
> > + * return 0 on success and -ETIMEDOUT in the case of timeout.
> > + */
> > +static int
> > +hdq_wait_for_flag(u32 offset, u8 flag, u8 flag_set, u8 *status)
> > +{
> > + int ret = 0;
> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
> > +
> > + if (flag_set == OMAP_HDQ_FLAG_CLEAR) {
> > + /* wait for the flag clear */
> > + while (((*status = hdq_reg_in(offset)) & flag)
> > + && time_before(jiffies, timeout)) {
> > + set_current_state(TASK_UNINTERRUPTIBLE);
> > + schedule_timeout(1);
> > + }
> > + if (unlikely(*status & flag))
> > + ret = -ETIMEDOUT;
> > + } else if (flag_set == OMAP_HDQ_FLAG_SET) {
> > + /* wait for the flag set */
> > + while (!((*status = hdq_reg_in(offset)) & flag)
> > + && time_before(jiffies, timeout)) {
> > + set_current_state(TASK_UNINTERRUPTIBLE);
> > + schedule_timeout(1);
> > + }
> > + if (unlikely(!(*status & flag)))
> > + ret = -ETIMEDOUT;
> > + } else
> > + return -EINVAL;
> > +
> > + return ret;
> > +}
> > +
> > +/*
> > + * write out a byte and fill *status with HDQ_INT_STATUS
> > + */
> > +static int
> > +hdq_write_byte(u8 val, u8 *status)
> > +{
> > + int ret;
> > + u8 tmp_status;
> > + unsigned long irqflags;
> > +
> > + *status = 0;
> > +
> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> > + /* clear interrupt flags via a dummy read */
> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> > + /* ISR loads it with new INT_STATUS */
> > + hdq_data->hdq_irqstatus = 0;
> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> > +
> > + hdq_reg_out(OMAP_HDQ_TX_DATA, val);
> > +
> > + /* set the GO bit */
> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
> > + /* wait for the TXCOMPLETE bit */
> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
> > + if (unlikely(ret < 0)) {
> > + pr_debug("wait interrupted");
> > + return -EINTR;
> > + }
> > +
> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> > + *status = hdq_data->hdq_irqstatus;
> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> > + /* check irqstatus */
> > + if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) {
> > + pr_debug("timeout waiting for TXCOMPLETE/RXCOMPLETE, %x",
> > + *status);
> > + return -ETIMEDOUT;
> > + }
> > +
> > + /* wait for the GO bit return to zero */
> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
> > + OMAP_HDQ_FLAG_CLEAR, &tmp_status);
> > + if (ret) {
> > + pr_debug("timeout waiting GO bit return to zero, %x",
> > + tmp_status);
> > + return ret;
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/*
> > + * HDQ Interrupt service routine.
> > + */
> > +static irqreturn_t
> > +hdq_isr(int irq, void *arg)
> > +{
> > + unsigned long irqflags;
> > +
> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> > + hdq_data->hdq_irqstatus = hdq_reg_in(OMAP_HDQ_INT_STATUS);
> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> > + pr_debug("hdq_isr: %x", hdq_data->hdq_irqstatus);
> > +
> > + if (hdq_data->hdq_irqstatus &
> > + (OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE
> > + | OMAP_HDQ_INT_STATUS_TIMEOUT)) {
> > + /* wake up sleeping process */
> > + wake_up_interruptible(&hdq_wait_queue);
> > + }
> > +
> > + return IRQ_HANDLED;
> > +}
> > +
> > +/*
> > + * HDQ Mode: always return success.
> > + */
> > +static u8 omap_w1_reset_bus(void *data)
> > +{
> > + return 0;
> > +}
> > +
> > +/*
> > + * W1 search callback function.
> > + */
> > +static void omap_w1_search_bus(void *data, u8 search_type,
> > + w1_slave_found_callback slave_found)
> > +{
> > + u64 module_id, rn_le, cs, id;
> > +
> > + if (W1_ID)
> > + module_id = W1_ID;
> > + else
> > + module_id = 0x1;
> > +
> > + rn_le = cpu_to_le64(module_id);
> > + /*
> > + * HDQ might not obey truly the 1-wire spec.
> > + * So calculate CRC based on module parameter.
> > + */
> > + cs = w1_calc_crc8((u8 *)&rn_le, 7);
> > + id = (cs << 56) | module_id;
> > +
> > + slave_found(data, id);
> > +}
> > +
> > +static int
> > +_omap_hdq_reset(void)
> > +{
> > + int ret;
> > + u8 tmp_status;
> > +
> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_SOFTRESET);
> > + /*
> > + * Select HDQ mode & enable clocks.
> > + * It is observed that INT flags can't be cleared via a read and GO/INIT
> > + * won't return to zero if interrupt is disabled. So we always enable
> > + * interrupt.
> > + */
> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> > +
> > + /* wait for reset to complete */
> > + ret = hdq_wait_for_flag(OMAP_HDQ_SYSSTATUS,
> > + OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status);
> > + if (ret)
> > + pr_debug("timeout waiting HDQ reset, %x", tmp_status);
> > + else {
> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_AUTOIDLE);
> > + }
> > +
> > + return ret;
> > +}
> > +
> > +/*
> > + * Issue break pulse to the device.
> > + */
> > +static int
> > +omap_hdq_break()
> > +{
> > + int ret;
> > + u8 tmp_status;
> > + unsigned long irqflags;
> > +
> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> > + if (ret < 0)
> > + return -EINTR;
> > +
> > + if (!hdq_data->hdq_usecount) {
> > + up(&hdq_data->hdq_semlock);
> > + return -EINVAL;
> > + }
> > +
> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> > + /* clear interrupt flags via a dummy read */
> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> > + /* ISR loads it with new INT_STATUS */
> > + hdq_data->hdq_irqstatus = 0;
> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> > +
> > + /* set the INIT and GO bit */
> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO,
> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
> > + OMAP_HDQ_CTRL_STATUS_GO);
> > +
> > + /* wait for the TIMEOUT bit */
> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
> > + if (unlikely(ret < 0)) {
> > + pr_debug("wait interrupted");
> > + up(&hdq_data->hdq_semlock);
> > + return -EINTR;
> > + }
> > +
> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> > + tmp_status = hdq_data->hdq_irqstatus;
> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> > + /* check irqstatus */
> > + if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) {
> > + pr_debug("timeout waiting for TIMEOUT, %x", tmp_status);
> > + up(&hdq_data->hdq_semlock);
> > + return -ETIMEDOUT;
> > + }
> > + /*
> > + * wait for both INIT and GO bits rerurn to zero.
> > + * zero wait time expected for interrupt mode.
> > + */
> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS,
> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
> > + OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR,
> > + &tmp_status);
> > + if (ret)
> > + pr_debug("timeout waiting INIT&GO bits return to zero, %x",
> > + tmp_status);
> > +
> > + up(&hdq_data->hdq_semlock);
> > + return ret;
> > +}
> > +
> > +static int hdq_read_byte(u8 *val)
> > +{
> > + int ret;
> > + u8 status;
> > + unsigned long irqflags;
> > +
> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> > + if (ret < 0)
> > + return -EINTR;
> > +
> > + if (!hdq_data->hdq_usecount) {
> > + up(&hdq_data->hdq_semlock);
> > + return -EINVAL;
> > + }
> > +
> > + if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO,
> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
> > + /*
> > + * The RX comes immediately after TX. It
> > + * triggers another interrupt before we
> > + * sleep. So we have to wait for RXCOMPLETE bit.
> > + */
> > + {
> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
> > + while (!(hdq_data->hdq_irqstatus
> > + & OMAP_HDQ_INT_STATUS_RXCOMPLETE)
> > + && time_before(jiffies, timeout)) {
> > + set_current_state(TASK_UNINTERRUPTIBLE);
> > + schedule_timeout(1);
> > + }
> > + }
> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, 0,
> > + OMAP_HDQ_CTRL_STATUS_DIR);
> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> > + status = hdq_data->hdq_irqstatus;
> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> > + /* check irqstatus */
> > + if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
> > + pr_debug("timeout waiting for RXCOMPLETE, %x", status);
> > + up(&hdq_data->hdq_semlock);
> > + return -ETIMEDOUT;
> > + }
> > + }
> > + /* the data is ready. Read it in! */
> > + *val = hdq_reg_in(OMAP_HDQ_RX_DATA);
> > + up(&hdq_data->hdq_semlock);
> > +
> > + return 0;
> > +
> > +}
> > +
> > +/*
> > + * Enable clocks and set the controller to HDQ mode.
> > + */
> > +static int
> > +omap_hdq_get()
> > +{
> > + int ret = 0;
> > +
> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> > + if (ret < 0)
> > + return -EINTR;
> > +
> > + if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) {
> > + pr_debug("attempt to exceed the max use count");
> > + up(&hdq_data->hdq_semlock);
> > + ret = -EINVAL;
> > + } else {
> > + hdq_data->hdq_usecount++;
> > + try_module_get(THIS_MODULE);
> > + if (1 == hdq_data->hdq_usecount) {
> > + if (clk_enable(hdq_data->hdq_ick)) {
> > + pr_debug("Can not enable ick\n");
> > + clk_put(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_fck);
> > + up(&hdq_data->hdq_semlock);
> > + return -ENODEV;
> > + }
> > + if (clk_enable(hdq_data->hdq_fck)) {
> > + pr_debug("Can not enable fck\n");
> > + clk_put(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_fck);
> > + up(&hdq_data->hdq_semlock);
> > + return -ENODEV;
> > + }
> > +
> > + /* make sure HDQ is out of reset */
> > + if (!(hdq_reg_in(OMAP_HDQ_SYSSTATUS) &
> > + OMAP_HDQ_SYSSTATUS_RESETDONE)) {
> > + ret = _omap_hdq_reset();
> > + if (ret)
> > + /* back up the count */
> > + hdq_data->hdq_usecount--;
> > + } else {
> > + /* select HDQ mode & enable clocks */
> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG,
> > + OMAP_HDQ_SYSCONFIG_AUTOIDLE);
> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> > + }
> > + }
> > + }
> > + up(&hdq_data->hdq_semlock);
> > + return ret;
> > +}
> > +
> > +/*
> > + * Disable clocks to the module.
> > + */
> > +static int
> > +omap_hdq_put()
> > +{
> > + int ret = 0;
> > +
> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> > + if (ret < 0)
> > + return -EINTR;
> > +
> > + if (0 == hdq_data->hdq_usecount) {
> > + pr_debug("attempt to decrement use count when it is zero");
> > + ret = -EINVAL;
> > + } else {
> > + hdq_data->hdq_usecount--;
> > + module_put(THIS_MODULE);
> > + if (0 == hdq_data->hdq_usecount) {
> > + clk_disable(hdq_data->hdq_ick);
> > + clk_disable(hdq_data->hdq_fck);
> > + }
> > + }
> > + up(&hdq_data->hdq_semlock);
> > + return ret;
> > +}
> > +
> > +/*
> > + * Used to control the call to omap_hdq_get and omap_hdq_put.
> > + * HDQ Protocol: Write the CMD|REG_address first, followed by
> > + * the data wrire or read.
> > + */
> > +static int init_trans;
> > +
> > +/*
> > + * Read a byte of data from the device.
> > + */
> > +static u8 omap_w1_read_byte(void *data)
> > +{
> > + u8 val;
> > + int ret;
> > +
> > + ret = hdq_read_byte(&val);
> > + if (ret) {
> > + init_trans = 0;
> > + omap_hdq_put();
> > + return -1;
> > + }
> > +
> > + /* Write followed by a read, release the module */
> > + if (init_trans) {
> > + init_trans = 0;
> > + omap_hdq_put();
> > + }
> > +
> > + return val;
> > +}
> > +
> > +/*
> > + * Write a byte of data to the device.
> > + */
> > +static void omap_w1_write_byte(void *data, u8 byte)
> > +{
> > + u8 status;
> > +
> > + /* First write to initialize the transfer */
> > + if (init_trans == 0)
> > + omap_hdq_get();
> > +
> > + init_trans++;
> > +
> > + hdq_write_byte(byte, &status);
> > + pr_debug("Ctrl status %x\n", status);
> > +
> > + /* Second write, data transfered. Release the module */
> > + if (init_trans > 1) {
> > + omap_hdq_put();
> > + init_trans = 0;
> > + }
> > +
> > + return;
> > +}
> > +
> > +static int __init omap_hdq_probe(struct platform_device *pdev)
> > +{
> > + struct resource *res;
> > + int ret, irq;
> > + u8 rev;
> > +
> > + if (!pdev)
> > + return -ENODEV;
> > +
> > + hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL);
> > + if (!hdq_data)
> > + return -ENODEV;
> > +
> > + platform_set_drvdata(pdev, hdq_data);
> > +
> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > + if (res == NULL) {
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return -ENXIO;
> > + }
> > +
> > + hdq_data->hdq_base = res->start;
> > +
> > + /* get interface & functional clock objects */
> > + hdq_data->hdq_ick = clk_get(&pdev->dev, "hdq_ick");
> > + hdq_data->hdq_fck = clk_get(&pdev->dev, "hdq_fck");
> > +
> > + if (IS_ERR(hdq_data->hdq_ick) || IS_ERR(hdq_data->hdq_fck)) {
> > + pr_debug("Can't get HDQ clock objects\n");
> > + if (IS_ERR(hdq_data->hdq_ick)) {
> > + ret = PTR_ERR(hdq_data->hdq_ick);
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return ret;
> > + }
> > + if (IS_ERR(hdq_data->hdq_fck)) {
> > + ret = PTR_ERR(hdq_data->hdq_fck);
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return ret;
> > + }
> > + }
> > +
> > + hdq_data->hdq_usecount = 0;
> > + sema_init(&hdq_data->hdq_semlock, 1);
> > +
> > + if (clk_enable(hdq_data->hdq_ick)) {
> > + pr_debug("Can not enable ick\n");
> > + clk_put(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_fck);
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return -ENODEV;
> > + }
> > +
> > + if (clk_enable(hdq_data->hdq_fck)) {
> > + pr_debug("Can not enable fck\n");
> > + clk_disable(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_fck);
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return -ENODEV;
> > + }
> > +
> > + rev = hdq_reg_in(OMAP_HDQ_REVISION);
> > + pr_info("OMAP HDQ Hardware Revision %c.%c. Driver in %s mode.\n",
> > + (rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt");
> > +
> > + spin_lock_init(&hdq_data->hdq_spinlock);
> > + omap_hdq_break();
> > +
> > + irq = platform_get_irq(pdev, 0);
> > + if (irq < 0) {
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return -ENXIO;
> > + }
> > +
> > + if (request_irq(irq, hdq_isr, IRQF_DISABLED, "OMAP HDQ",
> > + &hdq_data->hdq_semlock)) {
> > + pr_debug("request_irq failed\n");
> > + clk_disable(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_fck);
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return -ENODEV;
> > + }
> > +
> > + /* don't clock the HDQ until it is needed */
> > + clk_disable(hdq_data->hdq_ick);
> > + clk_disable(hdq_data->hdq_fck);
> > +
> > + ret = w1_add_master_device(&omap_w1_master);
> > + if (ret) {
> > + pr_debug("Failure in registering w1 master\n");
> > + clk_put(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_fck);
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int omap_hdq_remove(struct platform_device *pdev)
> > +{
> > + down_interruptible(&hdq_data->hdq_semlock);
> > + if (0 != hdq_data->hdq_usecount) {
> > + pr_debug("removed when use count is not zero\n");
> > + return -EBUSY;
> > + }
> > + up(&hdq_data->hdq_semlock);
> > +
> > + /* remove module dependency */
> > + clk_put(hdq_data->hdq_ick);
> > + clk_put(hdq_data->hdq_fck);
> > + free_irq(INT_24XX_HDQ_IRQ, &hdq_data->hdq_semlock);
> > + platform_set_drvdata(pdev, NULL);
> > + kfree(hdq_data);
> > +
> > + return 0;
> > +}
> > +
> > +static int __init
> > +omap_hdq_init(void)
> > +{
> > + return platform_driver_register(&omap_hdq_driver);
> > +}
> > +
> > +static void __exit
> > +omap_hdq_exit(void)
> > +{
> > + platform_driver_unregister(&omap_hdq_driver);
> > +}
> > +
> > +module_init(omap_hdq_init);
> > +module_exit(omap_hdq_exit);
> > +
> > +module_param(W1_ID, int, S_IRUSR);
> > +
> > +MODULE_AUTHOR("Texas Instruments");
> > +MODULE_DESCRIPTION("HDQ driver Library");
> > +MODULE_LICENSE("GPL");
> > -- 
> > 1.6.0.1.141.g445ca
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-omap" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > 
> >
> --
> To unsubscribe from this list: send the line "unsubscribe linux-omap" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 22/33] add omap nand drivers
  2008-08-31 21:28                                               ` Felipe Balbi
@ 2008-09-05 18:07                                                 ` Tony Lindgren
  0 siblings, 0 replies; 86+ messages in thread
From: Tony Lindgren @ 2008-09-05 18:07 UTC (permalink / raw)
  To: Felipe Balbi; +Cc: David Brownell, linux-omap, Felipe Balbi

* Felipe Balbi <me@felipebalbi.com> [080831 14:29]:
> On Sun, Aug 31, 2008 at 02:08:18PM -0700, David Brownell wrote:
> > Hmm, this seems like three distinct drivers to me.
> > Which should mean three separate patches ... going
> > go the MTD list!
> > 
> > 
> > On Saturday 30 August 2008, Felipe Balbi wrote:
> > > 
> > > +config MTD_NAND_OMAP2
> > > +       tristate "NAND Flash device on OMAP2 and OMAP3"
> > > +       depends on ARM && MTD_NAND && (ARCH_OMAP2 || ARCH_OMAP3)
> > > +       help
> > > +          Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms.
> > 
> > I've got a small patch for this one which I'll post.
> 
> Great, I can meld it on the final version of the driver. Thanks
> 
> > > +config MTD_NAND_OMAP
> > > +       tristate "NAND Flash device on OMAP H3/H2/P2 boards"
> > > +       depends on ARM && ARCH_OMAP1 && MTD_NAND && (MACH_OMAP_H2 || MACH_OMAP_H3 || MACH_OMAP_PERSEUS2)
> > > +       help
> > > +         Support for NAND flash on Texas Instruments H3/H2/P2 platforms.
> > > +
> > > +config MTD_NAND_OMAP_HW
> > > +       bool "OMAP HW NAND Flash controller support"
> > > +        depends on ARM && ARCH_OMAP16XX && MTD_NAND
> > > +
> > > +       help
> > > +         Driver for TI OMAP16xx hardware NAND flash controller.
> > 
> > This one raises the question:  why are there *TWO* drivers for
> > the same hardware?  Which one is the failed experiment?
> 
> It's even worse. If you look at the Makefile itself, there are three:
> 
> obj-$(CONFIG_MTD_NAND_OMAP) 		+= omap-nand-flash.o
> obj-$(CONFIG_MTD_NAND_OMAP2) 		+= omap2.o
> obj-$(CONFIG_MTD_NAND_OMAP_HW)		+= omap-hw.o
> 
> Looks like this series was good for finding some weird stuff on
> linux-omap ;-)

Then there's also the MTD_NAND_TOTO.. Sure there were various NAND
controllers, but if some of them are no longer maintained, we should
just export them into patches and remove from kernel.

Tony
--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 12/33] Add tsc2005 touchscreen driver
  2008-09-02 21:33                         ` Russell King - ARM Linux
@ 2008-09-07 18:47                           ` David Brownell
  0 siblings, 0 replies; 86+ messages in thread
From: David Brownell @ 2008-09-07 18:47 UTC (permalink / raw)
  To: Russell King - ARM Linux; +Cc: linux-omap, Felipe Balbi

On Tuesday 02 September 2008, Russell King - ARM Linux wrote:
> > +     ts->irq = OMAP_GPIO_IRQ(dav_gpio);
> 
> In fact, this driver requires ARCH_OMAP to set ts->irq, otherwise
> it's uninitialized.

Or  ts->irq = gpio_to_irq(dav_gpio) ...

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

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

* Re: [PATCH 32/33] add omap 1-wire interface driver
  2008-09-05 17:59                                                                   ` Tony Lindgren
@ 2008-09-08 12:15                                                                     ` Madhusudhan Chikkature
  2008-09-08 18:05                                                                       ` Tony Lindgren
  0 siblings, 1 reply; 86+ messages in thread
From: Madhusudhan Chikkature @ 2008-09-08 12:15 UTC (permalink / raw)
  To: Tony Lindgren; +Cc: Felipe Balbi, linux-omap, Felipe Balbi


----- Original Message ----- 
From: "Tony Lindgren" <tony@atomide.com>
To: "Madhusudhan Chikkature" <madhu.cr@ti.com>
Cc: "Felipe Balbi" <me@felipebalbi.com>; <linux-omap@vger.kernel.org>; "Felipe Balbi" <felipe.balbi@nokia.com>
Sent: Friday, September 05, 2008 11:29 PM
Subject: Re: [PATCH 32/33] add omap 1-wire interface driver


>* Madhusudhan Chikkature <madhu.cr@ti.com> [080901 06:16]:
>> Hi,
>> 
>> The HDQ patchset that I posted to Tony were given ACK by Evgeniy Polyakov on the omap list. I hope that helps.
> 
> Madhu, can you post your 1-wire driver to LKML and Cc Evgeniy Polyakov
> so we can get that integrated? Also Cc l-o list.
I have a question regarding this. In fact it can be a generic question which might apply for other drivers as well. How do we post the OMAP3 related driver patches to LKML or any other driver specific lists if OMAP3 base support is not already  present there?

Regards,
Madhu 
> 
> Thanks,
> 
> Tony
> 
>> 
>> Regards,
>> Madhu
>> 
>> ----- Original Message ----- 
>> From: "Felipe Balbi" <me@felipebalbi.com>
>> To: <linux-omap@vger.kernel.org>
>> Cc: "Felipe Balbi" <felipe.balbi@nokia.com>
>> Sent: Saturday, August 30, 2008 10:46 PM
>> Subject: [PATCH 32/33] add omap 1-wire interface driver
>> 
>> 
>> > From: Felipe Balbi <felipe.balbi@nokia.com>
>> > 
>> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
>> > ---
>> > arch/arm/plat-omap/include/mach/irqs.h |    2 +
>> > drivers/w1/masters/Kconfig             |    7 +
>> > drivers/w1/masters/Makefile            |    1 +
>> > drivers/w1/masters/omap_hdq.c          |  704 ++++++++++++++++++++++++++++++++
>> > 4 files changed, 714 insertions(+), 0 deletions(-)
>> > create mode 100644 drivers/w1/masters/omap_hdq.c
>> > 
>> > diff --git a/arch/arm/plat-omap/include/mach/irqs.h b/arch/arm/plat-omap/include/mach/irqs.h
>> > index 17248bb..c9e95a6 100644
>> > --- a/arch/arm/plat-omap/include/mach/irqs.h
>> > +++ b/arch/arm/plat-omap/include/mach/irqs.h
>> > @@ -54,6 +54,8 @@
>> > #define INT_TIMER2 30
>> > #define INT_LCD_CTRL 31
>> > 
>> > +#define INT_24XX_HDQ_IRQ 58 /* Temporarily here for driver to build */
>> > +
>> > /*
>> >  * OMAP-1510 specific IRQ numbers for interrupt handler 1
>> >  */
>> > diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig
>> > index c449309..636d4f7 100644
>> > --- a/drivers/w1/masters/Kconfig
>> > +++ b/drivers/w1/masters/Kconfig
>> > @@ -42,6 +42,13 @@ config W1_MASTER_DS1WM
>> >    in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
>> >    hx4700.
>> > 
>> > +config HDQ_MASTER_OMAP
>> > + tristate "OMAP HDQ driver"
>> > + depends on ARCH_OMAP2430 || ARCH_OMAP34XX
>> > + help
>> > +   Say Y here if you want support for the 1-wire or HDQ Interface
>> > +   on an OMAP processor.
>> > +
>> > config W1_MASTER_GPIO
>> >  tristate "GPIO 1-wire busmaster"
>> >  depends on GENERIC_GPIO
>> > diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile
>> > index 1420b5b..1daeb6e 100644
>> > --- a/drivers/w1/masters/Makefile
>> > +++ b/drivers/w1/masters/Makefile
>> > @@ -6,4 +6,5 @@ obj-$(CONFIG_W1_MASTER_MATROX) += matrox_w1.o
>> > obj-$(CONFIG_W1_MASTER_DS2490) += ds2490.o
>> > obj-$(CONFIG_W1_MASTER_DS2482) += ds2482.o
>> > obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o
>> > +obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o
>> > obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o
>> > diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c
>> > new file mode 100644
>> > index 0000000..880e282
>> > --- /dev/null
>> > +++ b/drivers/w1/masters/omap_hdq.c
>> > @@ -0,0 +1,704 @@
>> > +/*
>> > + * drivers/w1/masters/omap_hdq.c
>> > + *
>> > + * Copyright (C) 2007 Texas Instruments, Inc.
>> > + *
>> > + * This file is licensed under the terms of the GNU General Public License
>> > + * version 2. This program is licensed "as is" without any warranty of any
>> > + * kind, whether express or implied.
>> > + *
>> > + */
>> > +#include <linux/kernel.h>
>> > +#include <linux/module.h>
>> > +#include <linux/platform_device.h>
>> > +#include <linux/interrupt.h>
>> > +#include <linux/err.h>
>> > +#include <linux/clk.h>
>> > +#include <linux/io.h>
>> > +#include <asm/irq.h>
>> > +#include <mach/hardware.h>
>> > +
>> > +#include "../w1.h"
>> > +#include "../w1_int.h"
>> > +
>> > +#define MOD_NAME "OMAP_HDQ:"
>> > +
>> > +#define OMAP_HDQ_REVISION 0x00
>> > +#define OMAP_HDQ_TX_DATA 0x04
>> > +#define OMAP_HDQ_RX_DATA 0x08
>> > +#define OMAP_HDQ_CTRL_STATUS 0x0c
>> > +#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK (1<<6)
>> > +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE (1<<5)
>> > +#define OMAP_HDQ_CTRL_STATUS_GO (1<<4)
>> > +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION (1<<2)
>> > +#define OMAP_HDQ_CTRL_STATUS_DIR (1<<1)
>> > +#define OMAP_HDQ_CTRL_STATUS_MODE (1<<0)
>> > +#define OMAP_HDQ_INT_STATUS 0x10
>> > +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE (1<<2)
>> > +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE (1<<1)
>> > +#define OMAP_HDQ_INT_STATUS_TIMEOUT (1<<0)
>> > +#define OMAP_HDQ_SYSCONFIG 0x14
>> > +#define OMAP_HDQ_SYSCONFIG_SOFTRESET (1<<1)
>> > +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE (1<<0)
>> > +#define OMAP_HDQ_SYSSTATUS 0x18
>> > +#define OMAP_HDQ_SYSSTATUS_RESETDONE (1<<0)
>> > +
>> > +#define OMAP_HDQ_FLAG_CLEAR 0
>> > +#define OMAP_HDQ_FLAG_SET 1
>> > +#define OMAP_HDQ_TIMEOUT (HZ/5)
>> > +
>> > +#define OMAP_HDQ_MAX_USER 4
>> > +
>> > +DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue);
>> > +int W1_ID;
>> > +
>> > +struct hdq_data {
>> > + resource_size_t hdq_base;
>> > + struct semaphore hdq_semlock;
>> > + int hdq_usecount;
>> > + struct clk *hdq_ick;
>> > + struct clk *hdq_fck;
>> > + u8 hdq_irqstatus;
>> > + spinlock_t hdq_spinlock;
>> > +};
>> > +
>> > +static struct hdq_data *hdq_data;
>> > +
>> > +static int omap_hdq_get(void);
>> > +static int omap_hdq_put(void);
>> > +static int omap_hdq_break(void);
>> > +
>> > +static int __init omap_hdq_probe(struct platform_device *pdev);
>> > +static int omap_hdq_remove(struct platform_device *pdev);
>> > +
>> > +static struct platform_driver omap_hdq_driver = {
>> > + .probe = omap_hdq_probe,
>> > + .remove = omap_hdq_remove,
>> > + .suspend = NULL,
>> > + .resume = NULL,
>> > + .driver = {
>> > + .name = "omap_hdq",
>> > + },
>> > +};
>> > +
>> > +static u8 omap_w1_read_byte(void *data);
>> > +static void omap_w1_write_byte(void *data, u8 byte);
>> > +static u8 omap_w1_reset_bus(void *data);
>> > +static void omap_w1_search_bus(void *data, u8 search_type,
>> > + w1_slave_found_callback slave_found);
>> > +
>> > +static struct w1_bus_master omap_w1_master = {
>> > + .read_byte = omap_w1_read_byte,
>> > + .write_byte = omap_w1_write_byte,
>> > + .reset_bus = omap_w1_reset_bus,
>> > + .search = omap_w1_search_bus,
>> > +};
>> > +
>> > +/*
>> > + * HDQ register I/O routines
>> > + */
>> > +static inline u8
>> > +hdq_reg_in(u32 offset)
>> > +{
>> > + return omap_readb(hdq_data->hdq_base + offset);
>> > +}
>> > +
>> > +static inline u8
>> > +hdq_reg_out(u32 offset, u8 val)
>> > +{
>> > + omap_writeb(val, hdq_data->hdq_base + offset);
>> > + return val;
>> > +}
>> > +
>> > +static inline u8
>> > +hdq_reg_merge(u32 offset, u8 val, u8 mask)
>> > +{
>> > + u8 new_val = (omap_readb(hdq_data->hdq_base + offset) & ~mask)
>> > + | (val & mask);
>> > + omap_writeb(new_val, hdq_data->hdq_base + offset);
>> > + return new_val;
>> > +}
>> > +
>> > +/*
>> > + * Wait for one or more bits in flag change.
>> > + * HDQ_FLAG_SET: wait until any bit in the flag is set.
>> > + * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared.
>> > + * return 0 on success and -ETIMEDOUT in the case of timeout.
>> > + */
>> > +static int
>> > +hdq_wait_for_flag(u32 offset, u8 flag, u8 flag_set, u8 *status)
>> > +{
>> > + int ret = 0;
>> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
>> > +
>> > + if (flag_set == OMAP_HDQ_FLAG_CLEAR) {
>> > + /* wait for the flag clear */
>> > + while (((*status = hdq_reg_in(offset)) & flag)
>> > + && time_before(jiffies, timeout)) {
>> > + set_current_state(TASK_UNINTERRUPTIBLE);
>> > + schedule_timeout(1);
>> > + }
>> > + if (unlikely(*status & flag))
>> > + ret = -ETIMEDOUT;
>> > + } else if (flag_set == OMAP_HDQ_FLAG_SET) {
>> > + /* wait for the flag set */
>> > + while (!((*status = hdq_reg_in(offset)) & flag)
>> > + && time_before(jiffies, timeout)) {
>> > + set_current_state(TASK_UNINTERRUPTIBLE);
>> > + schedule_timeout(1);
>> > + }
>> > + if (unlikely(!(*status & flag)))
>> > + ret = -ETIMEDOUT;
>> > + } else
>> > + return -EINVAL;
>> > +
>> > + return ret;
>> > +}
>> > +
>> > +/*
>> > + * write out a byte and fill *status with HDQ_INT_STATUS
>> > + */
>> > +static int
>> > +hdq_write_byte(u8 val, u8 *status)
>> > +{
>> > + int ret;
>> > + u8 tmp_status;
>> > + unsigned long irqflags;
>> > +
>> > + *status = 0;
>> > +
>> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> > + /* clear interrupt flags via a dummy read */
>> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> > + /* ISR loads it with new INT_STATUS */
>> > + hdq_data->hdq_irqstatus = 0;
>> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> > +
>> > + hdq_reg_out(OMAP_HDQ_TX_DATA, val);
>> > +
>> > + /* set the GO bit */
>> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
>> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
>> > + /* wait for the TXCOMPLETE bit */
>> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
>> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
>> > + if (unlikely(ret < 0)) {
>> > + pr_debug("wait interrupted");
>> > + return -EINTR;
>> > + }
>> > +
>> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> > + *status = hdq_data->hdq_irqstatus;
>> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> > + /* check irqstatus */
>> > + if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) {
>> > + pr_debug("timeout waiting for TXCOMPLETE/RXCOMPLETE, %x",
>> > + *status);
>> > + return -ETIMEDOUT;
>> > + }
>> > +
>> > + /* wait for the GO bit return to zero */
>> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
>> > + OMAP_HDQ_FLAG_CLEAR, &tmp_status);
>> > + if (ret) {
>> > + pr_debug("timeout waiting GO bit return to zero, %x",
>> > + tmp_status);
>> > + return ret;
>> > + }
>> > +
>> > + return ret;
>> > +}
>> > +
>> > +/*
>> > + * HDQ Interrupt service routine.
>> > + */
>> > +static irqreturn_t
>> > +hdq_isr(int irq, void *arg)
>> > +{
>> > + unsigned long irqflags;
>> > +
>> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> > + hdq_data->hdq_irqstatus = hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> > + pr_debug("hdq_isr: %x", hdq_data->hdq_irqstatus);
>> > +
>> > + if (hdq_data->hdq_irqstatus &
>> > + (OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE
>> > + | OMAP_HDQ_INT_STATUS_TIMEOUT)) {
>> > + /* wake up sleeping process */
>> > + wake_up_interruptible(&hdq_wait_queue);
>> > + }
>> > +
>> > + return IRQ_HANDLED;
>> > +}
>> > +
>> > +/*
>> > + * HDQ Mode: always return success.
>> > + */
>> > +static u8 omap_w1_reset_bus(void *data)
>> > +{
>> > + return 0;
>> > +}
>> > +
>> > +/*
>> > + * W1 search callback function.
>> > + */
>> > +static void omap_w1_search_bus(void *data, u8 search_type,
>> > + w1_slave_found_callback slave_found)
>> > +{
>> > + u64 module_id, rn_le, cs, id;
>> > +
>> > + if (W1_ID)
>> > + module_id = W1_ID;
>> > + else
>> > + module_id = 0x1;
>> > +
>> > + rn_le = cpu_to_le64(module_id);
>> > + /*
>> > + * HDQ might not obey truly the 1-wire spec.
>> > + * So calculate CRC based on module parameter.
>> > + */
>> > + cs = w1_calc_crc8((u8 *)&rn_le, 7);
>> > + id = (cs << 56) | module_id;
>> > +
>> > + slave_found(data, id);
>> > +}
>> > +
>> > +static int
>> > +_omap_hdq_reset(void)
>> > +{
>> > + int ret;
>> > + u8 tmp_status;
>> > +
>> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_SOFTRESET);
>> > + /*
>> > + * Select HDQ mode & enable clocks.
>> > + * It is observed that INT flags can't be cleared via a read and GO/INIT
>> > + * won't return to zero if interrupt is disabled. So we always enable
>> > + * interrupt.
>> > + */
>> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
>> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
>> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
>> > +
>> > + /* wait for reset to complete */
>> > + ret = hdq_wait_for_flag(OMAP_HDQ_SYSSTATUS,
>> > + OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status);
>> > + if (ret)
>> > + pr_debug("timeout waiting HDQ reset, %x", tmp_status);
>> > + else {
>> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
>> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
>> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
>> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_AUTOIDLE);
>> > + }
>> > +
>> > + return ret;
>> > +}
>> > +
>> > +/*
>> > + * Issue break pulse to the device.
>> > + */
>> > +static int
>> > +omap_hdq_break()
>> > +{
>> > + int ret;
>> > + u8 tmp_status;
>> > + unsigned long irqflags;
>> > +
>> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> > + if (ret < 0)
>> > + return -EINTR;
>> > +
>> > + if (!hdq_data->hdq_usecount) {
>> > + up(&hdq_data->hdq_semlock);
>> > + return -EINVAL;
>> > + }
>> > +
>> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> > + /* clear interrupt flags via a dummy read */
>> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> > + /* ISR loads it with new INT_STATUS */
>> > + hdq_data->hdq_irqstatus = 0;
>> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> > +
>> > + /* set the INIT and GO bit */
>> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
>> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO,
>> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
>> > + OMAP_HDQ_CTRL_STATUS_GO);
>> > +
>> > + /* wait for the TIMEOUT bit */
>> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
>> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
>> > + if (unlikely(ret < 0)) {
>> > + pr_debug("wait interrupted");
>> > + up(&hdq_data->hdq_semlock);
>> > + return -EINTR;
>> > + }
>> > +
>> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> > + tmp_status = hdq_data->hdq_irqstatus;
>> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> > + /* check irqstatus */
>> > + if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) {
>> > + pr_debug("timeout waiting for TIMEOUT, %x", tmp_status);
>> > + up(&hdq_data->hdq_semlock);
>> > + return -ETIMEDOUT;
>> > + }
>> > + /*
>> > + * wait for both INIT and GO bits rerurn to zero.
>> > + * zero wait time expected for interrupt mode.
>> > + */
>> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS,
>> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
>> > + OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR,
>> > + &tmp_status);
>> > + if (ret)
>> > + pr_debug("timeout waiting INIT&GO bits return to zero, %x",
>> > + tmp_status);
>> > +
>> > + up(&hdq_data->hdq_semlock);
>> > + return ret;
>> > +}
>> > +
>> > +static int hdq_read_byte(u8 *val)
>> > +{
>> > + int ret;
>> > + u8 status;
>> > + unsigned long irqflags;
>> > +
>> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> > + if (ret < 0)
>> > + return -EINTR;
>> > +
>> > + if (!hdq_data->hdq_usecount) {
>> > + up(&hdq_data->hdq_semlock);
>> > + return -EINVAL;
>> > + }
>> > +
>> > + if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
>> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
>> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO,
>> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
>> > + /*
>> > + * The RX comes immediately after TX. It
>> > + * triggers another interrupt before we
>> > + * sleep. So we have to wait for RXCOMPLETE bit.
>> > + */
>> > + {
>> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
>> > + while (!(hdq_data->hdq_irqstatus
>> > + & OMAP_HDQ_INT_STATUS_RXCOMPLETE)
>> > + && time_before(jiffies, timeout)) {
>> > + set_current_state(TASK_UNINTERRUPTIBLE);
>> > + schedule_timeout(1);
>> > + }
>> > + }
>> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, 0,
>> > + OMAP_HDQ_CTRL_STATUS_DIR);
>> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> > + status = hdq_data->hdq_irqstatus;
>> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> > + /* check irqstatus */
>> > + if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
>> > + pr_debug("timeout waiting for RXCOMPLETE, %x", status);
>> > + up(&hdq_data->hdq_semlock);
>> > + return -ETIMEDOUT;
>> > + }
>> > + }
>> > + /* the data is ready. Read it in! */
>> > + *val = hdq_reg_in(OMAP_HDQ_RX_DATA);
>> > + up(&hdq_data->hdq_semlock);
>> > +
>> > + return 0;
>> > +
>> > +}
>> > +
>> > +/*
>> > + * Enable clocks and set the controller to HDQ mode.
>> > + */
>> > +static int
>> > +omap_hdq_get()
>> > +{
>> > + int ret = 0;
>> > +
>> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> > + if (ret < 0)
>> > + return -EINTR;
>> > +
>> > + if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) {
>> > + pr_debug("attempt to exceed the max use count");
>> > + up(&hdq_data->hdq_semlock);
>> > + ret = -EINVAL;
>> > + } else {
>> > + hdq_data->hdq_usecount++;
>> > + try_module_get(THIS_MODULE);
>> > + if (1 == hdq_data->hdq_usecount) {
>> > + if (clk_enable(hdq_data->hdq_ick)) {
>> > + pr_debug("Can not enable ick\n");
>> > + clk_put(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_fck);
>> > + up(&hdq_data->hdq_semlock);
>> > + return -ENODEV;
>> > + }
>> > + if (clk_enable(hdq_data->hdq_fck)) {
>> > + pr_debug("Can not enable fck\n");
>> > + clk_put(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_fck);
>> > + up(&hdq_data->hdq_semlock);
>> > + return -ENODEV;
>> > + }
>> > +
>> > + /* make sure HDQ is out of reset */
>> > + if (!(hdq_reg_in(OMAP_HDQ_SYSSTATUS) &
>> > + OMAP_HDQ_SYSSTATUS_RESETDONE)) {
>> > + ret = _omap_hdq_reset();
>> > + if (ret)
>> > + /* back up the count */
>> > + hdq_data->hdq_usecount--;
>> > + } else {
>> > + /* select HDQ mode & enable clocks */
>> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
>> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
>> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
>> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG,
>> > + OMAP_HDQ_SYSCONFIG_AUTOIDLE);
>> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> > + }
>> > + }
>> > + }
>> > + up(&hdq_data->hdq_semlock);
>> > + return ret;
>> > +}
>> > +
>> > +/*
>> > + * Disable clocks to the module.
>> > + */
>> > +static int
>> > +omap_hdq_put()
>> > +{
>> > + int ret = 0;
>> > +
>> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> > + if (ret < 0)
>> > + return -EINTR;
>> > +
>> > + if (0 == hdq_data->hdq_usecount) {
>> > + pr_debug("attempt to decrement use count when it is zero");
>> > + ret = -EINVAL;
>> > + } else {
>> > + hdq_data->hdq_usecount--;
>> > + module_put(THIS_MODULE);
>> > + if (0 == hdq_data->hdq_usecount) {
>> > + clk_disable(hdq_data->hdq_ick);
>> > + clk_disable(hdq_data->hdq_fck);
>> > + }
>> > + }
>> > + up(&hdq_data->hdq_semlock);
>> > + return ret;
>> > +}
>> > +
>> > +/*
>> > + * Used to control the call to omap_hdq_get and omap_hdq_put.
>> > + * HDQ Protocol: Write the CMD|REG_address first, followed by
>> > + * the data wrire or read.
>> > + */
>> > +static int init_trans;
>> > +
>> > +/*
>> > + * Read a byte of data from the device.
>> > + */
>> > +static u8 omap_w1_read_byte(void *data)
>> > +{
>> > + u8 val;
>> > + int ret;
>> > +
>> > + ret = hdq_read_byte(&val);
>> > + if (ret) {
>> > + init_trans = 0;
>> > + omap_hdq_put();
>> > + return -1;
>> > + }
>> > +
>> > + /* Write followed by a read, release the module */
>> > + if (init_trans) {
>> > + init_trans = 0;
>> > + omap_hdq_put();
>> > + }
>> > +
>> > + return val;
>> > +}
>> > +
>> > +/*
>> > + * Write a byte of data to the device.
>> > + */
>> > +static void omap_w1_write_byte(void *data, u8 byte)
>> > +{
>> > + u8 status;
>> > +
>> > + /* First write to initialize the transfer */
>> > + if (init_trans == 0)
>> > + omap_hdq_get();
>> > +
>> > + init_trans++;
>> > +
>> > + hdq_write_byte(byte, &status);
>> > + pr_debug("Ctrl status %x\n", status);
>> > +
>> > + /* Second write, data transfered. Release the module */
>> > + if (init_trans > 1) {
>> > + omap_hdq_put();
>> > + init_trans = 0;
>> > + }
>> > +
>> > + return;
>> > +}
>> > +
>> > +static int __init omap_hdq_probe(struct platform_device *pdev)
>> > +{
>> > + struct resource *res;
>> > + int ret, irq;
>> > + u8 rev;
>> > +
>> > + if (!pdev)
>> > + return -ENODEV;
>> > +
>> > + hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL);
>> > + if (!hdq_data)
>> > + return -ENODEV;
>> > +
>> > + platform_set_drvdata(pdev, hdq_data);
>> > +
>> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> > + if (res == NULL) {
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return -ENXIO;
>> > + }
>> > +
>> > + hdq_data->hdq_base = res->start;
>> > +
>> > + /* get interface & functional clock objects */
>> > + hdq_data->hdq_ick = clk_get(&pdev->dev, "hdq_ick");
>> > + hdq_data->hdq_fck = clk_get(&pdev->dev, "hdq_fck");
>> > +
>> > + if (IS_ERR(hdq_data->hdq_ick) || IS_ERR(hdq_data->hdq_fck)) {
>> > + pr_debug("Can't get HDQ clock objects\n");
>> > + if (IS_ERR(hdq_data->hdq_ick)) {
>> > + ret = PTR_ERR(hdq_data->hdq_ick);
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return ret;
>> > + }
>> > + if (IS_ERR(hdq_data->hdq_fck)) {
>> > + ret = PTR_ERR(hdq_data->hdq_fck);
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return ret;
>> > + }
>> > + }
>> > +
>> > + hdq_data->hdq_usecount = 0;
>> > + sema_init(&hdq_data->hdq_semlock, 1);
>> > +
>> > + if (clk_enable(hdq_data->hdq_ick)) {
>> > + pr_debug("Can not enable ick\n");
>> > + clk_put(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_fck);
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return -ENODEV;
>> > + }
>> > +
>> > + if (clk_enable(hdq_data->hdq_fck)) {
>> > + pr_debug("Can not enable fck\n");
>> > + clk_disable(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_fck);
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return -ENODEV;
>> > + }
>> > +
>> > + rev = hdq_reg_in(OMAP_HDQ_REVISION);
>> > + pr_info("OMAP HDQ Hardware Revision %c.%c. Driver in %s mode.\n",
>> > + (rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt");
>> > +
>> > + spin_lock_init(&hdq_data->hdq_spinlock);
>> > + omap_hdq_break();
>> > +
>> > + irq = platform_get_irq(pdev, 0);
>> > + if (irq < 0) {
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return -ENXIO;
>> > + }
>> > +
>> > + if (request_irq(irq, hdq_isr, IRQF_DISABLED, "OMAP HDQ",
>> > + &hdq_data->hdq_semlock)) {
>> > + pr_debug("request_irq failed\n");
>> > + clk_disable(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_fck);
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return -ENODEV;
>> > + }
>> > +
>> > + /* don't clock the HDQ until it is needed */
>> > + clk_disable(hdq_data->hdq_ick);
>> > + clk_disable(hdq_data->hdq_fck);
>> > +
>> > + ret = w1_add_master_device(&omap_w1_master);
>> > + if (ret) {
>> > + pr_debug("Failure in registering w1 master\n");
>> > + clk_put(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_fck);
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > + return ret;
>> > + }
>> > +
>> > + return 0;
>> > +}
>> > +
>> > +static int omap_hdq_remove(struct platform_device *pdev)
>> > +{
>> > + down_interruptible(&hdq_data->hdq_semlock);
>> > + if (0 != hdq_data->hdq_usecount) {
>> > + pr_debug("removed when use count is not zero\n");
>> > + return -EBUSY;
>> > + }
>> > + up(&hdq_data->hdq_semlock);
>> > +
>> > + /* remove module dependency */
>> > + clk_put(hdq_data->hdq_ick);
>> > + clk_put(hdq_data->hdq_fck);
>> > + free_irq(INT_24XX_HDQ_IRQ, &hdq_data->hdq_semlock);
>> > + platform_set_drvdata(pdev, NULL);
>> > + kfree(hdq_data);
>> > +
>> > + return 0;
>> > +}
>> > +
>> > +static int __init
>> > +omap_hdq_init(void)
>> > +{
>> > + return platform_driver_register(&omap_hdq_driver);
>> > +}
>> > +
>> > +static void __exit
>> > +omap_hdq_exit(void)
>> > +{
>> > + platform_driver_unregister(&omap_hdq_driver);
>> > +}
>> > +
>> > +module_init(omap_hdq_init);
>> > +module_exit(omap_hdq_exit);
>> > +
>> > +module_param(W1_ID, int, S_IRUSR);
>> > +
>> > +MODULE_AUTHOR("Texas Instruments");
>> > +MODULE_DESCRIPTION("HDQ driver Library");
>> > +MODULE_LICENSE("GPL");
>> > -- 
>> > 1.6.0.1.141.g445ca
>> > 
>> > --
>> > To unsubscribe from this list: send the line "unsubscribe linux-omap" in
>> > the body of a message to majordomo@vger.kernel.org
>> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> > 
>> >
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-omap" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
>

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

* Re: [PATCH 32/33] add omap 1-wire interface driver
  2008-09-08 12:15                                                                     ` Madhusudhan Chikkature
@ 2008-09-08 18:05                                                                       ` Tony Lindgren
  2008-09-08 20:07                                                                         ` Russell King - ARM Linux
  2008-09-09 13:38                                                                         ` Madhusudhan Chikkature
  0 siblings, 2 replies; 86+ messages in thread
From: Tony Lindgren @ 2008-09-08 18:05 UTC (permalink / raw)
  To: Madhusudhan Chikkature; +Cc: Felipe Balbi, linux-omap, Felipe Balbi

* Madhusudhan Chikkature <madhu.cr@ti.com> [080908 05:15]:
> 
> ----- Original Message ----- 
> From: "Tony Lindgren" <tony@atomide.com>
> To: "Madhusudhan Chikkature" <madhu.cr@ti.com>
> Cc: "Felipe Balbi" <me@felipebalbi.com>; <linux-omap@vger.kernel.org>; "Felipe Balbi" <felipe.balbi@nokia.com>
> Sent: Friday, September 05, 2008 11:29 PM
> Subject: Re: [PATCH 32/33] add omap 1-wire interface driver
> 
> 
> >* Madhusudhan Chikkature <madhu.cr@ti.com> [080901 06:16]:
> >> Hi,
> >> 
> >> The HDQ patchset that I posted to Tony were given ACK by Evgeniy Polyakov on the omap list. I hope that helps.
> > 
> > Madhu, can you post your 1-wire driver to LKML and Cc Evgeniy Polyakov
> > so we can get that integrated? Also Cc l-o list.
> I have a question regarding this. In fact it can be a generic question which might apply for other drivers as well. How do we post the OMAP3 related driver patches to LKML or any other driver specific lists if OMAP3 base support is not already  present there?

Well the drivers should not have dependencies to omap3 headers.
And hopefully we'll have the minimal omap3 support integrated when
2.6.28 opens.

Also, you're already getting IORESOURCE_MEM as rerource. And isn't
this driver also on 24XX chips too?

BTW, how about change it to use __raw_read/write instead of
omap_read/write? Just ioremap the IORESOURCE_MEM, see
the recent ioremap changes.

Regards,

Tony

> 
> Regards,
> Madhu 
> > 
> > Thanks,
> > 
> > Tony
> > 
> >> 
> >> Regards,
> >> Madhu
> >> 
> >> ----- Original Message ----- 
> >> From: "Felipe Balbi" <me@felipebalbi.com>
> >> To: <linux-omap@vger.kernel.org>
> >> Cc: "Felipe Balbi" <felipe.balbi@nokia.com>
> >> Sent: Saturday, August 30, 2008 10:46 PM
> >> Subject: [PATCH 32/33] add omap 1-wire interface driver
> >> 
> >> 
> >> > From: Felipe Balbi <felipe.balbi@nokia.com>
> >> > 
> >> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
> >> > ---
> >> > arch/arm/plat-omap/include/mach/irqs.h |    2 +
> >> > drivers/w1/masters/Kconfig             |    7 +
> >> > drivers/w1/masters/Makefile            |    1 +
> >> > drivers/w1/masters/omap_hdq.c          |  704 ++++++++++++++++++++++++++++++++
> >> > 4 files changed, 714 insertions(+), 0 deletions(-)
> >> > create mode 100644 drivers/w1/masters/omap_hdq.c
> >> > 
> >> > diff --git a/arch/arm/plat-omap/include/mach/irqs.h b/arch/arm/plat-omap/include/mach/irqs.h
> >> > index 17248bb..c9e95a6 100644
> >> > --- a/arch/arm/plat-omap/include/mach/irqs.h
> >> > +++ b/arch/arm/plat-omap/include/mach/irqs.h
> >> > @@ -54,6 +54,8 @@
> >> > #define INT_TIMER2 30
> >> > #define INT_LCD_CTRL 31
> >> > 
> >> > +#define INT_24XX_HDQ_IRQ 58 /* Temporarily here for driver to build */
> >> > +
> >> > /*
> >> >  * OMAP-1510 specific IRQ numbers for interrupt handler 1
> >> >  */
> >> > diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig
> >> > index c449309..636d4f7 100644
> >> > --- a/drivers/w1/masters/Kconfig
> >> > +++ b/drivers/w1/masters/Kconfig
> >> > @@ -42,6 +42,13 @@ config W1_MASTER_DS1WM
> >> >    in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
> >> >    hx4700.
> >> > 
> >> > +config HDQ_MASTER_OMAP
> >> > + tristate "OMAP HDQ driver"
> >> > + depends on ARCH_OMAP2430 || ARCH_OMAP34XX
> >> > + help
> >> > +   Say Y here if you want support for the 1-wire or HDQ Interface
> >> > +   on an OMAP processor.
> >> > +
> >> > config W1_MASTER_GPIO
> >> >  tristate "GPIO 1-wire busmaster"
> >> >  depends on GENERIC_GPIO
> >> > diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile
> >> > index 1420b5b..1daeb6e 100644
> >> > --- a/drivers/w1/masters/Makefile
> >> > +++ b/drivers/w1/masters/Makefile
> >> > @@ -6,4 +6,5 @@ obj-$(CONFIG_W1_MASTER_MATROX) += matrox_w1.o
> >> > obj-$(CONFIG_W1_MASTER_DS2490) += ds2490.o
> >> > obj-$(CONFIG_W1_MASTER_DS2482) += ds2482.o
> >> > obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o
> >> > +obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o
> >> > obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o
> >> > diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c
> >> > new file mode 100644
> >> > index 0000000..880e282
> >> > --- /dev/null
> >> > +++ b/drivers/w1/masters/omap_hdq.c
> >> > @@ -0,0 +1,704 @@
> >> > +/*
> >> > + * drivers/w1/masters/omap_hdq.c
> >> > + *
> >> > + * Copyright (C) 2007 Texas Instruments, Inc.
> >> > + *
> >> > + * This file is licensed under the terms of the GNU General Public License
> >> > + * version 2. This program is licensed "as is" without any warranty of any
> >> > + * kind, whether express or implied.
> >> > + *
> >> > + */
> >> > +#include <linux/kernel.h>
> >> > +#include <linux/module.h>
> >> > +#include <linux/platform_device.h>
> >> > +#include <linux/interrupt.h>
> >> > +#include <linux/err.h>
> >> > +#include <linux/clk.h>
> >> > +#include <linux/io.h>
> >> > +#include <asm/irq.h>
> >> > +#include <mach/hardware.h>
> >> > +
> >> > +#include "../w1.h"
> >> > +#include "../w1_int.h"
> >> > +
> >> > +#define MOD_NAME "OMAP_HDQ:"
> >> > +
> >> > +#define OMAP_HDQ_REVISION 0x00
> >> > +#define OMAP_HDQ_TX_DATA 0x04
> >> > +#define OMAP_HDQ_RX_DATA 0x08
> >> > +#define OMAP_HDQ_CTRL_STATUS 0x0c
> >> > +#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK (1<<6)
> >> > +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE (1<<5)
> >> > +#define OMAP_HDQ_CTRL_STATUS_GO (1<<4)
> >> > +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION (1<<2)
> >> > +#define OMAP_HDQ_CTRL_STATUS_DIR (1<<1)
> >> > +#define OMAP_HDQ_CTRL_STATUS_MODE (1<<0)
> >> > +#define OMAP_HDQ_INT_STATUS 0x10
> >> > +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE (1<<2)
> >> > +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE (1<<1)
> >> > +#define OMAP_HDQ_INT_STATUS_TIMEOUT (1<<0)
> >> > +#define OMAP_HDQ_SYSCONFIG 0x14
> >> > +#define OMAP_HDQ_SYSCONFIG_SOFTRESET (1<<1)
> >> > +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE (1<<0)
> >> > +#define OMAP_HDQ_SYSSTATUS 0x18
> >> > +#define OMAP_HDQ_SYSSTATUS_RESETDONE (1<<0)
> >> > +
> >> > +#define OMAP_HDQ_FLAG_CLEAR 0
> >> > +#define OMAP_HDQ_FLAG_SET 1
> >> > +#define OMAP_HDQ_TIMEOUT (HZ/5)
> >> > +
> >> > +#define OMAP_HDQ_MAX_USER 4
> >> > +
> >> > +DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue);
> >> > +int W1_ID;
> >> > +
> >> > +struct hdq_data {
> >> > + resource_size_t hdq_base;
> >> > + struct semaphore hdq_semlock;
> >> > + int hdq_usecount;
> >> > + struct clk *hdq_ick;
> >> > + struct clk *hdq_fck;
> >> > + u8 hdq_irqstatus;
> >> > + spinlock_t hdq_spinlock;
> >> > +};
> >> > +
> >> > +static struct hdq_data *hdq_data;
> >> > +
> >> > +static int omap_hdq_get(void);
> >> > +static int omap_hdq_put(void);
> >> > +static int omap_hdq_break(void);
> >> > +
> >> > +static int __init omap_hdq_probe(struct platform_device *pdev);
> >> > +static int omap_hdq_remove(struct platform_device *pdev);
> >> > +
> >> > +static struct platform_driver omap_hdq_driver = {
> >> > + .probe = omap_hdq_probe,
> >> > + .remove = omap_hdq_remove,
> >> > + .suspend = NULL,
> >> > + .resume = NULL,
> >> > + .driver = {
> >> > + .name = "omap_hdq",
> >> > + },
> >> > +};
> >> > +
> >> > +static u8 omap_w1_read_byte(void *data);
> >> > +static void omap_w1_write_byte(void *data, u8 byte);
> >> > +static u8 omap_w1_reset_bus(void *data);
> >> > +static void omap_w1_search_bus(void *data, u8 search_type,
> >> > + w1_slave_found_callback slave_found);
> >> > +
> >> > +static struct w1_bus_master omap_w1_master = {
> >> > + .read_byte = omap_w1_read_byte,
> >> > + .write_byte = omap_w1_write_byte,
> >> > + .reset_bus = omap_w1_reset_bus,
> >> > + .search = omap_w1_search_bus,
> >> > +};
> >> > +
> >> > +/*
> >> > + * HDQ register I/O routines
> >> > + */
> >> > +static inline u8
> >> > +hdq_reg_in(u32 offset)
> >> > +{
> >> > + return omap_readb(hdq_data->hdq_base + offset);
> >> > +}
> >> > +
> >> > +static inline u8
> >> > +hdq_reg_out(u32 offset, u8 val)
> >> > +{
> >> > + omap_writeb(val, hdq_data->hdq_base + offset);
> >> > + return val;
> >> > +}
> >> > +
> >> > +static inline u8
> >> > +hdq_reg_merge(u32 offset, u8 val, u8 mask)
> >> > +{
> >> > + u8 new_val = (omap_readb(hdq_data->hdq_base + offset) & ~mask)
> >> > + | (val & mask);
> >> > + omap_writeb(new_val, hdq_data->hdq_base + offset);
> >> > + return new_val;
> >> > +}
> >> > +
> >> > +/*
> >> > + * Wait for one or more bits in flag change.
> >> > + * HDQ_FLAG_SET: wait until any bit in the flag is set.
> >> > + * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared.
> >> > + * return 0 on success and -ETIMEDOUT in the case of timeout.
> >> > + */
> >> > +static int
> >> > +hdq_wait_for_flag(u32 offset, u8 flag, u8 flag_set, u8 *status)
> >> > +{
> >> > + int ret = 0;
> >> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
> >> > +
> >> > + if (flag_set == OMAP_HDQ_FLAG_CLEAR) {
> >> > + /* wait for the flag clear */
> >> > + while (((*status = hdq_reg_in(offset)) & flag)
> >> > + && time_before(jiffies, timeout)) {
> >> > + set_current_state(TASK_UNINTERRUPTIBLE);
> >> > + schedule_timeout(1);
> >> > + }
> >> > + if (unlikely(*status & flag))
> >> > + ret = -ETIMEDOUT;
> >> > + } else if (flag_set == OMAP_HDQ_FLAG_SET) {
> >> > + /* wait for the flag set */
> >> > + while (!((*status = hdq_reg_in(offset)) & flag)
> >> > + && time_before(jiffies, timeout)) {
> >> > + set_current_state(TASK_UNINTERRUPTIBLE);
> >> > + schedule_timeout(1);
> >> > + }
> >> > + if (unlikely(!(*status & flag)))
> >> > + ret = -ETIMEDOUT;
> >> > + } else
> >> > + return -EINVAL;
> >> > +
> >> > + return ret;
> >> > +}
> >> > +
> >> > +/*
> >> > + * write out a byte and fill *status with HDQ_INT_STATUS
> >> > + */
> >> > +static int
> >> > +hdq_write_byte(u8 val, u8 *status)
> >> > +{
> >> > + int ret;
> >> > + u8 tmp_status;
> >> > + unsigned long irqflags;
> >> > +
> >> > + *status = 0;
> >> > +
> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> >> > + /* clear interrupt flags via a dummy read */
> >> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> >> > + /* ISR loads it with new INT_STATUS */
> >> > + hdq_data->hdq_irqstatus = 0;
> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> >> > +
> >> > + hdq_reg_out(OMAP_HDQ_TX_DATA, val);
> >> > +
> >> > + /* set the GO bit */
> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
> >> > + /* wait for the TXCOMPLETE bit */
> >> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
> >> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
> >> > + if (unlikely(ret < 0)) {
> >> > + pr_debug("wait interrupted");
> >> > + return -EINTR;
> >> > + }
> >> > +
> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> >> > + *status = hdq_data->hdq_irqstatus;
> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> >> > + /* check irqstatus */
> >> > + if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) {
> >> > + pr_debug("timeout waiting for TXCOMPLETE/RXCOMPLETE, %x",
> >> > + *status);
> >> > + return -ETIMEDOUT;
> >> > + }
> >> > +
> >> > + /* wait for the GO bit return to zero */
> >> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
> >> > + OMAP_HDQ_FLAG_CLEAR, &tmp_status);
> >> > + if (ret) {
> >> > + pr_debug("timeout waiting GO bit return to zero, %x",
> >> > + tmp_status);
> >> > + return ret;
> >> > + }
> >> > +
> >> > + return ret;
> >> > +}
> >> > +
> >> > +/*
> >> > + * HDQ Interrupt service routine.
> >> > + */
> >> > +static irqreturn_t
> >> > +hdq_isr(int irq, void *arg)
> >> > +{
> >> > + unsigned long irqflags;
> >> > +
> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> >> > + hdq_data->hdq_irqstatus = hdq_reg_in(OMAP_HDQ_INT_STATUS);
> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> >> > + pr_debug("hdq_isr: %x", hdq_data->hdq_irqstatus);
> >> > +
> >> > + if (hdq_data->hdq_irqstatus &
> >> > + (OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE
> >> > + | OMAP_HDQ_INT_STATUS_TIMEOUT)) {
> >> > + /* wake up sleeping process */
> >> > + wake_up_interruptible(&hdq_wait_queue);
> >> > + }
> >> > +
> >> > + return IRQ_HANDLED;
> >> > +}
> >> > +
> >> > +/*
> >> > + * HDQ Mode: always return success.
> >> > + */
> >> > +static u8 omap_w1_reset_bus(void *data)
> >> > +{
> >> > + return 0;
> >> > +}
> >> > +
> >> > +/*
> >> > + * W1 search callback function.
> >> > + */
> >> > +static void omap_w1_search_bus(void *data, u8 search_type,
> >> > + w1_slave_found_callback slave_found)
> >> > +{
> >> > + u64 module_id, rn_le, cs, id;
> >> > +
> >> > + if (W1_ID)
> >> > + module_id = W1_ID;
> >> > + else
> >> > + module_id = 0x1;
> >> > +
> >> > + rn_le = cpu_to_le64(module_id);
> >> > + /*
> >> > + * HDQ might not obey truly the 1-wire spec.
> >> > + * So calculate CRC based on module parameter.
> >> > + */
> >> > + cs = w1_calc_crc8((u8 *)&rn_le, 7);
> >> > + id = (cs << 56) | module_id;
> >> > +
> >> > + slave_found(data, id);
> >> > +}
> >> > +
> >> > +static int
> >> > +_omap_hdq_reset(void)
> >> > +{
> >> > + int ret;
> >> > + u8 tmp_status;
> >> > +
> >> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_SOFTRESET);
> >> > + /*
> >> > + * Select HDQ mode & enable clocks.
> >> > + * It is observed that INT flags can't be cleared via a read and GO/INIT
> >> > + * won't return to zero if interrupt is disabled. So we always enable
> >> > + * interrupt.
> >> > + */
> >> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> >> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> >> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> >> > +
> >> > + /* wait for reset to complete */
> >> > + ret = hdq_wait_for_flag(OMAP_HDQ_SYSSTATUS,
> >> > + OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status);
> >> > + if (ret)
> >> > + pr_debug("timeout waiting HDQ reset, %x", tmp_status);
> >> > + else {
> >> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> >> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> >> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> >> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_AUTOIDLE);
> >> > + }
> >> > +
> >> > + return ret;
> >> > +}
> >> > +
> >> > +/*
> >> > + * Issue break pulse to the device.
> >> > + */
> >> > +static int
> >> > +omap_hdq_break()
> >> > +{
> >> > + int ret;
> >> > + u8 tmp_status;
> >> > + unsigned long irqflags;
> >> > +
> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> >> > + if (ret < 0)
> >> > + return -EINTR;
> >> > +
> >> > + if (!hdq_data->hdq_usecount) {
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return -EINVAL;
> >> > + }
> >> > +
> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> >> > + /* clear interrupt flags via a dummy read */
> >> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> >> > + /* ISR loads it with new INT_STATUS */
> >> > + hdq_data->hdq_irqstatus = 0;
> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> >> > +
> >> > + /* set the INIT and GO bit */
> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
> >> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO,
> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
> >> > + OMAP_HDQ_CTRL_STATUS_GO);
> >> > +
> >> > + /* wait for the TIMEOUT bit */
> >> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
> >> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
> >> > + if (unlikely(ret < 0)) {
> >> > + pr_debug("wait interrupted");
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return -EINTR;
> >> > + }
> >> > +
> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> >> > + tmp_status = hdq_data->hdq_irqstatus;
> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> >> > + /* check irqstatus */
> >> > + if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) {
> >> > + pr_debug("timeout waiting for TIMEOUT, %x", tmp_status);
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return -ETIMEDOUT;
> >> > + }
> >> > + /*
> >> > + * wait for both INIT and GO bits rerurn to zero.
> >> > + * zero wait time expected for interrupt mode.
> >> > + */
> >> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS,
> >> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
> >> > + OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR,
> >> > + &tmp_status);
> >> > + if (ret)
> >> > + pr_debug("timeout waiting INIT&GO bits return to zero, %x",
> >> > + tmp_status);
> >> > +
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return ret;
> >> > +}
> >> > +
> >> > +static int hdq_read_byte(u8 *val)
> >> > +{
> >> > + int ret;
> >> > + u8 status;
> >> > + unsigned long irqflags;
> >> > +
> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> >> > + if (ret < 0)
> >> > + return -EINTR;
> >> > +
> >> > + if (!hdq_data->hdq_usecount) {
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return -EINVAL;
> >> > + }
> >> > +
> >> > + if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO,
> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
> >> > + /*
> >> > + * The RX comes immediately after TX. It
> >> > + * triggers another interrupt before we
> >> > + * sleep. So we have to wait for RXCOMPLETE bit.
> >> > + */
> >> > + {
> >> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
> >> > + while (!(hdq_data->hdq_irqstatus
> >> > + & OMAP_HDQ_INT_STATUS_RXCOMPLETE)
> >> > + && time_before(jiffies, timeout)) {
> >> > + set_current_state(TASK_UNINTERRUPTIBLE);
> >> > + schedule_timeout(1);
> >> > + }
> >> > + }
> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, 0,
> >> > + OMAP_HDQ_CTRL_STATUS_DIR);
> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
> >> > + status = hdq_data->hdq_irqstatus;
> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
> >> > + /* check irqstatus */
> >> > + if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
> >> > + pr_debug("timeout waiting for RXCOMPLETE, %x", status);
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return -ETIMEDOUT;
> >> > + }
> >> > + }
> >> > + /* the data is ready. Read it in! */
> >> > + *val = hdq_reg_in(OMAP_HDQ_RX_DATA);
> >> > + up(&hdq_data->hdq_semlock);
> >> > +
> >> > + return 0;
> >> > +
> >> > +}
> >> > +
> >> > +/*
> >> > + * Enable clocks and set the controller to HDQ mode.
> >> > + */
> >> > +static int
> >> > +omap_hdq_get()
> >> > +{
> >> > + int ret = 0;
> >> > +
> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> >> > + if (ret < 0)
> >> > + return -EINTR;
> >> > +
> >> > + if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) {
> >> > + pr_debug("attempt to exceed the max use count");
> >> > + up(&hdq_data->hdq_semlock);
> >> > + ret = -EINVAL;
> >> > + } else {
> >> > + hdq_data->hdq_usecount++;
> >> > + try_module_get(THIS_MODULE);
> >> > + if (1 == hdq_data->hdq_usecount) {
> >> > + if (clk_enable(hdq_data->hdq_ick)) {
> >> > + pr_debug("Can not enable ick\n");
> >> > + clk_put(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_fck);
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return -ENODEV;
> >> > + }
> >> > + if (clk_enable(hdq_data->hdq_fck)) {
> >> > + pr_debug("Can not enable fck\n");
> >> > + clk_put(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_fck);
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return -ENODEV;
> >> > + }
> >> > +
> >> > + /* make sure HDQ is out of reset */
> >> > + if (!(hdq_reg_in(OMAP_HDQ_SYSSTATUS) &
> >> > + OMAP_HDQ_SYSSTATUS_RESETDONE)) {
> >> > + ret = _omap_hdq_reset();
> >> > + if (ret)
> >> > + /* back up the count */
> >> > + hdq_data->hdq_usecount--;
> >> > + } else {
> >> > + /* select HDQ mode & enable clocks */
> >> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
> >> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
> >> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
> >> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG,
> >> > + OMAP_HDQ_SYSCONFIG_AUTOIDLE);
> >> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
> >> > + }
> >> > + }
> >> > + }
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return ret;
> >> > +}
> >> > +
> >> > +/*
> >> > + * Disable clocks to the module.
> >> > + */
> >> > +static int
> >> > +omap_hdq_put()
> >> > +{
> >> > + int ret = 0;
> >> > +
> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
> >> > + if (ret < 0)
> >> > + return -EINTR;
> >> > +
> >> > + if (0 == hdq_data->hdq_usecount) {
> >> > + pr_debug("attempt to decrement use count when it is zero");
> >> > + ret = -EINVAL;
> >> > + } else {
> >> > + hdq_data->hdq_usecount--;
> >> > + module_put(THIS_MODULE);
> >> > + if (0 == hdq_data->hdq_usecount) {
> >> > + clk_disable(hdq_data->hdq_ick);
> >> > + clk_disable(hdq_data->hdq_fck);
> >> > + }
> >> > + }
> >> > + up(&hdq_data->hdq_semlock);
> >> > + return ret;
> >> > +}
> >> > +
> >> > +/*
> >> > + * Used to control the call to omap_hdq_get and omap_hdq_put.
> >> > + * HDQ Protocol: Write the CMD|REG_address first, followed by
> >> > + * the data wrire or read.
> >> > + */
> >> > +static int init_trans;
> >> > +
> >> > +/*
> >> > + * Read a byte of data from the device.
> >> > + */
> >> > +static u8 omap_w1_read_byte(void *data)
> >> > +{
> >> > + u8 val;
> >> > + int ret;
> >> > +
> >> > + ret = hdq_read_byte(&val);
> >> > + if (ret) {
> >> > + init_trans = 0;
> >> > + omap_hdq_put();
> >> > + return -1;
> >> > + }
> >> > +
> >> > + /* Write followed by a read, release the module */
> >> > + if (init_trans) {
> >> > + init_trans = 0;
> >> > + omap_hdq_put();
> >> > + }
> >> > +
> >> > + return val;
> >> > +}
> >> > +
> >> > +/*
> >> > + * Write a byte of data to the device.
> >> > + */
> >> > +static void omap_w1_write_byte(void *data, u8 byte)
> >> > +{
> >> > + u8 status;
> >> > +
> >> > + /* First write to initialize the transfer */
> >> > + if (init_trans == 0)
> >> > + omap_hdq_get();
> >> > +
> >> > + init_trans++;
> >> > +
> >> > + hdq_write_byte(byte, &status);
> >> > + pr_debug("Ctrl status %x\n", status);
> >> > +
> >> > + /* Second write, data transfered. Release the module */
> >> > + if (init_trans > 1) {
> >> > + omap_hdq_put();
> >> > + init_trans = 0;
> >> > + }
> >> > +
> >> > + return;
> >> > +}
> >> > +
> >> > +static int __init omap_hdq_probe(struct platform_device *pdev)
> >> > +{
> >> > + struct resource *res;
> >> > + int ret, irq;
> >> > + u8 rev;
> >> > +
> >> > + if (!pdev)
> >> > + return -ENODEV;
> >> > +
> >> > + hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL);
> >> > + if (!hdq_data)
> >> > + return -ENODEV;
> >> > +
> >> > + platform_set_drvdata(pdev, hdq_data);
> >> > +
> >> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> >> > + if (res == NULL) {
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return -ENXIO;
> >> > + }
> >> > +
> >> > + hdq_data->hdq_base = res->start;
> >> > +
> >> > + /* get interface & functional clock objects */
> >> > + hdq_data->hdq_ick = clk_get(&pdev->dev, "hdq_ick");
> >> > + hdq_data->hdq_fck = clk_get(&pdev->dev, "hdq_fck");
> >> > +
> >> > + if (IS_ERR(hdq_data->hdq_ick) || IS_ERR(hdq_data->hdq_fck)) {
> >> > + pr_debug("Can't get HDQ clock objects\n");
> >> > + if (IS_ERR(hdq_data->hdq_ick)) {
> >> > + ret = PTR_ERR(hdq_data->hdq_ick);
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return ret;
> >> > + }
> >> > + if (IS_ERR(hdq_data->hdq_fck)) {
> >> > + ret = PTR_ERR(hdq_data->hdq_fck);
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return ret;
> >> > + }
> >> > + }
> >> > +
> >> > + hdq_data->hdq_usecount = 0;
> >> > + sema_init(&hdq_data->hdq_semlock, 1);
> >> > +
> >> > + if (clk_enable(hdq_data->hdq_ick)) {
> >> > + pr_debug("Can not enable ick\n");
> >> > + clk_put(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_fck);
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return -ENODEV;
> >> > + }
> >> > +
> >> > + if (clk_enable(hdq_data->hdq_fck)) {
> >> > + pr_debug("Can not enable fck\n");
> >> > + clk_disable(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_fck);
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return -ENODEV;
> >> > + }
> >> > +
> >> > + rev = hdq_reg_in(OMAP_HDQ_REVISION);
> >> > + pr_info("OMAP HDQ Hardware Revision %c.%c. Driver in %s mode.\n",
> >> > + (rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt");
> >> > +
> >> > + spin_lock_init(&hdq_data->hdq_spinlock);
> >> > + omap_hdq_break();
> >> > +
> >> > + irq = platform_get_irq(pdev, 0);
> >> > + if (irq < 0) {
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return -ENXIO;
> >> > + }
> >> > +
> >> > + if (request_irq(irq, hdq_isr, IRQF_DISABLED, "OMAP HDQ",
> >> > + &hdq_data->hdq_semlock)) {
> >> > + pr_debug("request_irq failed\n");
> >> > + clk_disable(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_fck);
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return -ENODEV;
> >> > + }
> >> > +
> >> > + /* don't clock the HDQ until it is needed */
> >> > + clk_disable(hdq_data->hdq_ick);
> >> > + clk_disable(hdq_data->hdq_fck);
> >> > +
> >> > + ret = w1_add_master_device(&omap_w1_master);
> >> > + if (ret) {
> >> > + pr_debug("Failure in registering w1 master\n");
> >> > + clk_put(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_fck);
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > + return ret;
> >> > + }
> >> > +
> >> > + return 0;
> >> > +}
> >> > +
> >> > +static int omap_hdq_remove(struct platform_device *pdev)
> >> > +{
> >> > + down_interruptible(&hdq_data->hdq_semlock);
> >> > + if (0 != hdq_data->hdq_usecount) {
> >> > + pr_debug("removed when use count is not zero\n");
> >> > + return -EBUSY;
> >> > + }
> >> > + up(&hdq_data->hdq_semlock);
> >> > +
> >> > + /* remove module dependency */
> >> > + clk_put(hdq_data->hdq_ick);
> >> > + clk_put(hdq_data->hdq_fck);
> >> > + free_irq(INT_24XX_HDQ_IRQ, &hdq_data->hdq_semlock);
> >> > + platform_set_drvdata(pdev, NULL);
> >> > + kfree(hdq_data);
> >> > +
> >> > + return 0;
> >> > +}
> >> > +
> >> > +static int __init
> >> > +omap_hdq_init(void)
> >> > +{
> >> > + return platform_driver_register(&omap_hdq_driver);
> >> > +}
> >> > +
> >> > +static void __exit
> >> > +omap_hdq_exit(void)
> >> > +{
> >> > + platform_driver_unregister(&omap_hdq_driver);
> >> > +}
> >> > +
> >> > +module_init(omap_hdq_init);
> >> > +module_exit(omap_hdq_exit);
> >> > +
> >> > +module_param(W1_ID, int, S_IRUSR);
> >> > +
> >> > +MODULE_AUTHOR("Texas Instruments");
> >> > +MODULE_DESCRIPTION("HDQ driver Library");
> >> > +MODULE_LICENSE("GPL");
> >> > -- 
> >> > 1.6.0.1.141.g445ca
> >> > 
> >> > --
> >> > To unsubscribe from this list: send the line "unsubscribe linux-omap" in
> >> > the body of a message to majordomo@vger.kernel.org
> >> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> >> > 
> >> >
> >> --
> >> To unsubscribe from this list: send the line "unsubscribe linux-omap" in
> >> the body of a message to majordomo@vger.kernel.org
> >> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > 
> >

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

* Re: [PATCH 32/33] add omap 1-wire interface driver
  2008-09-08 18:05                                                                       ` Tony Lindgren
@ 2008-09-08 20:07                                                                         ` Russell King - ARM Linux
  2008-09-09 13:38                                                                         ` Madhusudhan Chikkature
  1 sibling, 0 replies; 86+ messages in thread
From: Russell King - ARM Linux @ 2008-09-08 20:07 UTC (permalink / raw)
  To: Tony Lindgren
  Cc: Madhusudhan Chikkature, Felipe Balbi, linux-omap, Felipe Balbi

On Mon, Sep 08, 2008 at 11:05:21AM -0700, Tony Lindgren wrote:
> * Madhusudhan Chikkature <madhu.cr@ti.com> [080908 05:15]:
> > 
> > ----- Original Message ----- 
> > From: "Tony Lindgren" <tony@atomide.com>
> > To: "Madhusudhan Chikkature" <madhu.cr@ti.com>
> > Cc: "Felipe Balbi" <me@felipebalbi.com>; <linux-omap@vger.kernel.org>; "Felipe Balbi" <felipe.balbi@nokia.com>
> > Sent: Friday, September 05, 2008 11:29 PM
> > Subject: Re: [PATCH 32/33] add omap 1-wire interface driver
> > 
> > 
> > >* Madhusudhan Chikkature <madhu.cr@ti.com> [080901 06:16]:
> > >> Hi,
> > >> 
> > >> The HDQ patchset that I posted to Tony were given ACK by Evgeniy Polyakov on the omap list. I hope that helps.
> > > 
> > > Madhu, can you post your 1-wire driver to LKML and Cc Evgeniy Polyakov
> > > so we can get that integrated? Also Cc l-o list.
> > I have a question regarding this. In fact it can be a generic question which might apply for other drivers as well. How do we post the OMAP3 related driver patches to LKML or any other driver specific lists if OMAP3 base support is not already  present there?
> 
> Well the drivers should not have dependencies to omap3 headers.
> And hopefully we'll have the minimal omap3 support integrated when
> 2.6.28 opens.
> 
> Also, you're already getting IORESOURCE_MEM as rerource. And isn't
> this driver also on 24XX chips too?
> 
> BTW, how about change it to use __raw_read/write instead of
> omap_read/write? Just ioremap the IORESOURCE_MEM, see
> the recent ioremap changes.

Last point - shouldn't it be converted to using mutexes instead of
semaphores?

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

* Re: [PATCH 32/33] add omap 1-wire interface driver
  2008-09-08 18:05                                                                       ` Tony Lindgren
  2008-09-08 20:07                                                                         ` Russell King - ARM Linux
@ 2008-09-09 13:38                                                                         ` Madhusudhan Chikkature
  1 sibling, 0 replies; 86+ messages in thread
From: Madhusudhan Chikkature @ 2008-09-09 13:38 UTC (permalink / raw)
  To: Tony Lindgren; +Cc: Felipe Balbi, linux-omap, Felipe Balbi


----- Original Message ----- 
From: "Tony Lindgren" <tony@atomide.com>
To: "Madhusudhan Chikkature" <madhu.cr@ti.com>
Cc: "Felipe Balbi" <me@felipebalbi.com>; <linux-omap@vger.kernel.org>; "Felipe Balbi" <felipe.balbi@nokia.com>
Sent: Monday, September 08, 2008 11:35 PM
Subject: Re: [PATCH 32/33] add omap 1-wire interface driver


>* Madhusudhan Chikkature <madhu.cr@ti.com> [080908 05:15]:
>> 
>> ----- Original Message ----- 
>> From: "Tony Lindgren" <tony@atomide.com>
>> To: "Madhusudhan Chikkature" <madhu.cr@ti.com>
>> Cc: "Felipe Balbi" <me@felipebalbi.com>; <linux-omap@vger.kernel.org>; "Felipe Balbi" <felipe.balbi@nokia.com>
>> Sent: Friday, September 05, 2008 11:29 PM
>> Subject: Re: [PATCH 32/33] add omap 1-wire interface driver
>> 
>> 
>> >* Madhusudhan Chikkature <madhu.cr@ti.com> [080901 06:16]:
>> >> Hi,
>> >> 
>> >> The HDQ patchset that I posted to Tony were given ACK by Evgeniy Polyakov on the omap list. I hope that helps.
>> > 
>> > Madhu, can you post your 1-wire driver to LKML and Cc Evgeniy Polyakov
>> > so we can get that integrated? Also Cc l-o list.
>> I have a question regarding this. In fact it can be a generic question which might apply for other drivers as well. How do we post the OMAP3 related driver patches to LKML or any other driver specific lists if OMAP3 base support is not already  present there?
> 
> Well the drivers should not have dependencies to omap3 headers.
> And hopefully we'll have the minimal omap3 support integrated when
> 2.6.28 opens.
> 
> Also, you're already getting IORESOURCE_MEM as rerource. And isn't
> this driver also on 24XX chips too?
Yes.  This driver should work on 2430 as well.
> 
> BTW, how about change it to use __raw_read/write instead of
> omap_read/write? Just ioremap the IORESOURCE_MEM, see
> the recent ioremap changes.
Okay. I will look at those changes.

Regards,
Madhu
> 
> Regards,
> 
> Tony
> 
>> 
>> Regards,
>> Madhu 
>> > 
>> > Thanks,
>> > 
>> > Tony
>> > 
>> >> 
>> >> Regards,
>> >> Madhu
>> >> 
>> >> ----- Original Message ----- 
>> >> From: "Felipe Balbi" <me@felipebalbi.com>
>> >> To: <linux-omap@vger.kernel.org>
>> >> Cc: "Felipe Balbi" <felipe.balbi@nokia.com>
>> >> Sent: Saturday, August 30, 2008 10:46 PM
>> >> Subject: [PATCH 32/33] add omap 1-wire interface driver
>> >> 
>> >> 
>> >> > From: Felipe Balbi <felipe.balbi@nokia.com>
>> >> > 
>> >> > Signed-off-by: Felipe Balbi <felipe.balbi@nokia.com>
>> >> > ---
>> >> > arch/arm/plat-omap/include/mach/irqs.h |    2 +
>> >> > drivers/w1/masters/Kconfig             |    7 +
>> >> > drivers/w1/masters/Makefile            |    1 +
>> >> > drivers/w1/masters/omap_hdq.c          |  704 ++++++++++++++++++++++++++++++++
>> >> > 4 files changed, 714 insertions(+), 0 deletions(-)
>> >> > create mode 100644 drivers/w1/masters/omap_hdq.c
>> >> > 
>> >> > diff --git a/arch/arm/plat-omap/include/mach/irqs.h b/arch/arm/plat-omap/include/mach/irqs.h
>> >> > index 17248bb..c9e95a6 100644
>> >> > --- a/arch/arm/plat-omap/include/mach/irqs.h
>> >> > +++ b/arch/arm/plat-omap/include/mach/irqs.h
>> >> > @@ -54,6 +54,8 @@
>> >> > #define INT_TIMER2 30
>> >> > #define INT_LCD_CTRL 31
>> >> > 
>> >> > +#define INT_24XX_HDQ_IRQ 58 /* Temporarily here for driver to build */
>> >> > +
>> >> > /*
>> >> >  * OMAP-1510 specific IRQ numbers for interrupt handler 1
>> >> >  */
>> >> > diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig
>> >> > index c449309..636d4f7 100644
>> >> > --- a/drivers/w1/masters/Kconfig
>> >> > +++ b/drivers/w1/masters/Kconfig
>> >> > @@ -42,6 +42,13 @@ config W1_MASTER_DS1WM
>> >> >    in HP iPAQ devices like h5xxx, h2200, and ASIC3-based like
>> >> >    hx4700.
>> >> > 
>> >> > +config HDQ_MASTER_OMAP
>> >> > + tristate "OMAP HDQ driver"
>> >> > + depends on ARCH_OMAP2430 || ARCH_OMAP34XX
>> >> > + help
>> >> > +   Say Y here if you want support for the 1-wire or HDQ Interface
>> >> > +   on an OMAP processor.
>> >> > +
>> >> > config W1_MASTER_GPIO
>> >> >  tristate "GPIO 1-wire busmaster"
>> >> >  depends on GENERIC_GPIO
>> >> > diff --git a/drivers/w1/masters/Makefile b/drivers/w1/masters/Makefile
>> >> > index 1420b5b..1daeb6e 100644
>> >> > --- a/drivers/w1/masters/Makefile
>> >> > +++ b/drivers/w1/masters/Makefile
>> >> > @@ -6,4 +6,5 @@ obj-$(CONFIG_W1_MASTER_MATROX) += matrox_w1.o
>> >> > obj-$(CONFIG_W1_MASTER_DS2490) += ds2490.o
>> >> > obj-$(CONFIG_W1_MASTER_DS2482) += ds2482.o
>> >> > obj-$(CONFIG_W1_MASTER_DS1WM) += ds1wm.o
>> >> > +obj-$(CONFIG_HDQ_MASTER_OMAP) += omap_hdq.o
>> >> > obj-$(CONFIG_W1_MASTER_GPIO) += w1-gpio.o
>> >> > diff --git a/drivers/w1/masters/omap_hdq.c b/drivers/w1/masters/omap_hdq.c
>> >> > new file mode 100644
>> >> > index 0000000..880e282
>> >> > --- /dev/null
>> >> > +++ b/drivers/w1/masters/omap_hdq.c
>> >> > @@ -0,0 +1,704 @@
>> >> > +/*
>> >> > + * drivers/w1/masters/omap_hdq.c
>> >> > + *
>> >> > + * Copyright (C) 2007 Texas Instruments, Inc.
>> >> > + *
>> >> > + * This file is licensed under the terms of the GNU General Public License
>> >> > + * version 2. This program is licensed "as is" without any warranty of any
>> >> > + * kind, whether express or implied.
>> >> > + *
>> >> > + */
>> >> > +#include <linux/kernel.h>
>> >> > +#include <linux/module.h>
>> >> > +#include <linux/platform_device.h>
>> >> > +#include <linux/interrupt.h>
>> >> > +#include <linux/err.h>
>> >> > +#include <linux/clk.h>
>> >> > +#include <linux/io.h>
>> >> > +#include <asm/irq.h>
>> >> > +#include <mach/hardware.h>
>> >> > +
>> >> > +#include "../w1.h"
>> >> > +#include "../w1_int.h"
>> >> > +
>> >> > +#define MOD_NAME "OMAP_HDQ:"
>> >> > +
>> >> > +#define OMAP_HDQ_REVISION 0x00
>> >> > +#define OMAP_HDQ_TX_DATA 0x04
>> >> > +#define OMAP_HDQ_RX_DATA 0x08
>> >> > +#define OMAP_HDQ_CTRL_STATUS 0x0c
>> >> > +#define OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK (1<<6)
>> >> > +#define OMAP_HDQ_CTRL_STATUS_CLOCKENABLE (1<<5)
>> >> > +#define OMAP_HDQ_CTRL_STATUS_GO (1<<4)
>> >> > +#define OMAP_HDQ_CTRL_STATUS_INITIALIZATION (1<<2)
>> >> > +#define OMAP_HDQ_CTRL_STATUS_DIR (1<<1)
>> >> > +#define OMAP_HDQ_CTRL_STATUS_MODE (1<<0)
>> >> > +#define OMAP_HDQ_INT_STATUS 0x10
>> >> > +#define OMAP_HDQ_INT_STATUS_TXCOMPLETE (1<<2)
>> >> > +#define OMAP_HDQ_INT_STATUS_RXCOMPLETE (1<<1)
>> >> > +#define OMAP_HDQ_INT_STATUS_TIMEOUT (1<<0)
>> >> > +#define OMAP_HDQ_SYSCONFIG 0x14
>> >> > +#define OMAP_HDQ_SYSCONFIG_SOFTRESET (1<<1)
>> >> > +#define OMAP_HDQ_SYSCONFIG_AUTOIDLE (1<<0)
>> >> > +#define OMAP_HDQ_SYSSTATUS 0x18
>> >> > +#define OMAP_HDQ_SYSSTATUS_RESETDONE (1<<0)
>> >> > +
>> >> > +#define OMAP_HDQ_FLAG_CLEAR 0
>> >> > +#define OMAP_HDQ_FLAG_SET 1
>> >> > +#define OMAP_HDQ_TIMEOUT (HZ/5)
>> >> > +
>> >> > +#define OMAP_HDQ_MAX_USER 4
>> >> > +
>> >> > +DECLARE_WAIT_QUEUE_HEAD(hdq_wait_queue);
>> >> > +int W1_ID;
>> >> > +
>> >> > +struct hdq_data {
>> >> > + resource_size_t hdq_base;
>> >> > + struct semaphore hdq_semlock;
>> >> > + int hdq_usecount;
>> >> > + struct clk *hdq_ick;
>> >> > + struct clk *hdq_fck;
>> >> > + u8 hdq_irqstatus;
>> >> > + spinlock_t hdq_spinlock;
>> >> > +};
>> >> > +
>> >> > +static struct hdq_data *hdq_data;
>> >> > +
>> >> > +static int omap_hdq_get(void);
>> >> > +static int omap_hdq_put(void);
>> >> > +static int omap_hdq_break(void);
>> >> > +
>> >> > +static int __init omap_hdq_probe(struct platform_device *pdev);
>> >> > +static int omap_hdq_remove(struct platform_device *pdev);
>> >> > +
>> >> > +static struct platform_driver omap_hdq_driver = {
>> >> > + .probe = omap_hdq_probe,
>> >> > + .remove = omap_hdq_remove,
>> >> > + .suspend = NULL,
>> >> > + .resume = NULL,
>> >> > + .driver = {
>> >> > + .name = "omap_hdq",
>> >> > + },
>> >> > +};
>> >> > +
>> >> > +static u8 omap_w1_read_byte(void *data);
>> >> > +static void omap_w1_write_byte(void *data, u8 byte);
>> >> > +static u8 omap_w1_reset_bus(void *data);
>> >> > +static void omap_w1_search_bus(void *data, u8 search_type,
>> >> > + w1_slave_found_callback slave_found);
>> >> > +
>> >> > +static struct w1_bus_master omap_w1_master = {
>> >> > + .read_byte = omap_w1_read_byte,
>> >> > + .write_byte = omap_w1_write_byte,
>> >> > + .reset_bus = omap_w1_reset_bus,
>> >> > + .search = omap_w1_search_bus,
>> >> > +};
>> >> > +
>> >> > +/*
>> >> > + * HDQ register I/O routines
>> >> > + */
>> >> > +static inline u8
>> >> > +hdq_reg_in(u32 offset)
>> >> > +{
>> >> > + return omap_readb(hdq_data->hdq_base + offset);
>> >> > +}
>> >> > +
>> >> > +static inline u8
>> >> > +hdq_reg_out(u32 offset, u8 val)
>> >> > +{
>> >> > + omap_writeb(val, hdq_data->hdq_base + offset);
>> >> > + return val;
>> >> > +}
>> >> > +
>> >> > +static inline u8
>> >> > +hdq_reg_merge(u32 offset, u8 val, u8 mask)
>> >> > +{
>> >> > + u8 new_val = (omap_readb(hdq_data->hdq_base + offset) & ~mask)
>> >> > + | (val & mask);
>> >> > + omap_writeb(new_val, hdq_data->hdq_base + offset);
>> >> > + return new_val;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * Wait for one or more bits in flag change.
>> >> > + * HDQ_FLAG_SET: wait until any bit in the flag is set.
>> >> > + * HDQ_FLAG_CLEAR: wait until all bits in the flag are cleared.
>> >> > + * return 0 on success and -ETIMEDOUT in the case of timeout.
>> >> > + */
>> >> > +static int
>> >> > +hdq_wait_for_flag(u32 offset, u8 flag, u8 flag_set, u8 *status)
>> >> > +{
>> >> > + int ret = 0;
>> >> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
>> >> > +
>> >> > + if (flag_set == OMAP_HDQ_FLAG_CLEAR) {
>> >> > + /* wait for the flag clear */
>> >> > + while (((*status = hdq_reg_in(offset)) & flag)
>> >> > + && time_before(jiffies, timeout)) {
>> >> > + set_current_state(TASK_UNINTERRUPTIBLE);
>> >> > + schedule_timeout(1);
>> >> > + }
>> >> > + if (unlikely(*status & flag))
>> >> > + ret = -ETIMEDOUT;
>> >> > + } else if (flag_set == OMAP_HDQ_FLAG_SET) {
>> >> > + /* wait for the flag set */
>> >> > + while (!((*status = hdq_reg_in(offset)) & flag)
>> >> > + && time_before(jiffies, timeout)) {
>> >> > + set_current_state(TASK_UNINTERRUPTIBLE);
>> >> > + schedule_timeout(1);
>> >> > + }
>> >> > + if (unlikely(!(*status & flag)))
>> >> > + ret = -ETIMEDOUT;
>> >> > + } else
>> >> > + return -EINVAL;
>> >> > +
>> >> > + return ret;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * write out a byte and fill *status with HDQ_INT_STATUS
>> >> > + */
>> >> > +static int
>> >> > +hdq_write_byte(u8 val, u8 *status)
>> >> > +{
>> >> > + int ret;
>> >> > + u8 tmp_status;
>> >> > + unsigned long irqflags;
>> >> > +
>> >> > + *status = 0;
>> >> > +
>> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> >> > + /* clear interrupt flags via a dummy read */
>> >> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> >> > + /* ISR loads it with new INT_STATUS */
>> >> > + hdq_data->hdq_irqstatus = 0;
>> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> >> > +
>> >> > + hdq_reg_out(OMAP_HDQ_TX_DATA, val);
>> >> > +
>> >> > + /* set the GO bit */
>> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
>> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
>> >> > + /* wait for the TXCOMPLETE bit */
>> >> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
>> >> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
>> >> > + if (unlikely(ret < 0)) {
>> >> > + pr_debug("wait interrupted");
>> >> > + return -EINTR;
>> >> > + }
>> >> > +
>> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> >> > + *status = hdq_data->hdq_irqstatus;
>> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> >> > + /* check irqstatus */
>> >> > + if (!(*status & OMAP_HDQ_INT_STATUS_TXCOMPLETE)) {
>> >> > + pr_debug("timeout waiting for TXCOMPLETE/RXCOMPLETE, %x",
>> >> > + *status);
>> >> > + return -ETIMEDOUT;
>> >> > + }
>> >> > +
>> >> > + /* wait for the GO bit return to zero */
>> >> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS, OMAP_HDQ_CTRL_STATUS_GO,
>> >> > + OMAP_HDQ_FLAG_CLEAR, &tmp_status);
>> >> > + if (ret) {
>> >> > + pr_debug("timeout waiting GO bit return to zero, %x",
>> >> > + tmp_status);
>> >> > + return ret;
>> >> > + }
>> >> > +
>> >> > + return ret;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * HDQ Interrupt service routine.
>> >> > + */
>> >> > +static irqreturn_t
>> >> > +hdq_isr(int irq, void *arg)
>> >> > +{
>> >> > + unsigned long irqflags;
>> >> > +
>> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> >> > + hdq_data->hdq_irqstatus = hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> >> > + pr_debug("hdq_isr: %x", hdq_data->hdq_irqstatus);
>> >> > +
>> >> > + if (hdq_data->hdq_irqstatus &
>> >> > + (OMAP_HDQ_INT_STATUS_TXCOMPLETE | OMAP_HDQ_INT_STATUS_RXCOMPLETE
>> >> > + | OMAP_HDQ_INT_STATUS_TIMEOUT)) {
>> >> > + /* wake up sleeping process */
>> >> > + wake_up_interruptible(&hdq_wait_queue);
>> >> > + }
>> >> > +
>> >> > + return IRQ_HANDLED;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * HDQ Mode: always return success.
>> >> > + */
>> >> > +static u8 omap_w1_reset_bus(void *data)
>> >> > +{
>> >> > + return 0;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * W1 search callback function.
>> >> > + */
>> >> > +static void omap_w1_search_bus(void *data, u8 search_type,
>> >> > + w1_slave_found_callback slave_found)
>> >> > +{
>> >> > + u64 module_id, rn_le, cs, id;
>> >> > +
>> >> > + if (W1_ID)
>> >> > + module_id = W1_ID;
>> >> > + else
>> >> > + module_id = 0x1;
>> >> > +
>> >> > + rn_le = cpu_to_le64(module_id);
>> >> > + /*
>> >> > + * HDQ might not obey truly the 1-wire spec.
>> >> > + * So calculate CRC based on module parameter.
>> >> > + */
>> >> > + cs = w1_calc_crc8((u8 *)&rn_le, 7);
>> >> > + id = (cs << 56) | module_id;
>> >> > +
>> >> > + slave_found(data, id);
>> >> > +}
>> >> > +
>> >> > +static int
>> >> > +_omap_hdq_reset(void)
>> >> > +{
>> >> > + int ret;
>> >> > + u8 tmp_status;
>> >> > +
>> >> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_SOFTRESET);
>> >> > + /*
>> >> > + * Select HDQ mode & enable clocks.
>> >> > + * It is observed that INT flags can't be cleared via a read and GO/INIT
>> >> > + * won't return to zero if interrupt is disabled. So we always enable
>> >> > + * interrupt.
>> >> > + */
>> >> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
>> >> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
>> >> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
>> >> > +
>> >> > + /* wait for reset to complete */
>> >> > + ret = hdq_wait_for_flag(OMAP_HDQ_SYSSTATUS,
>> >> > + OMAP_HDQ_SYSSTATUS_RESETDONE, OMAP_HDQ_FLAG_SET, &tmp_status);
>> >> > + if (ret)
>> >> > + pr_debug("timeout waiting HDQ reset, %x", tmp_status);
>> >> > + else {
>> >> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
>> >> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
>> >> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
>> >> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG, OMAP_HDQ_SYSCONFIG_AUTOIDLE);
>> >> > + }
>> >> > +
>> >> > + return ret;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * Issue break pulse to the device.
>> >> > + */
>> >> > +static int
>> >> > +omap_hdq_break()
>> >> > +{
>> >> > + int ret;
>> >> > + u8 tmp_status;
>> >> > + unsigned long irqflags;
>> >> > +
>> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> >> > + if (ret < 0)
>> >> > + return -EINTR;
>> >> > +
>> >> > + if (!hdq_data->hdq_usecount) {
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return -EINVAL;
>> >> > + }
>> >> > +
>> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> >> > + /* clear interrupt flags via a dummy read */
>> >> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> >> > + /* ISR loads it with new INT_STATUS */
>> >> > + hdq_data->hdq_irqstatus = 0;
>> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> >> > +
>> >> > + /* set the INIT and GO bit */
>> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
>> >> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION | OMAP_HDQ_CTRL_STATUS_GO,
>> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
>> >> > + OMAP_HDQ_CTRL_STATUS_GO);
>> >> > +
>> >> > + /* wait for the TIMEOUT bit */
>> >> > + ret = wait_event_interruptible_timeout(hdq_wait_queue,
>> >> > + hdq_data->hdq_irqstatus, OMAP_HDQ_TIMEOUT);
>> >> > + if (unlikely(ret < 0)) {
>> >> > + pr_debug("wait interrupted");
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return -EINTR;
>> >> > + }
>> >> > +
>> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> >> > + tmp_status = hdq_data->hdq_irqstatus;
>> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> >> > + /* check irqstatus */
>> >> > + if (!(tmp_status & OMAP_HDQ_INT_STATUS_TIMEOUT)) {
>> >> > + pr_debug("timeout waiting for TIMEOUT, %x", tmp_status);
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return -ETIMEDOUT;
>> >> > + }
>> >> > + /*
>> >> > + * wait for both INIT and GO bits rerurn to zero.
>> >> > + * zero wait time expected for interrupt mode.
>> >> > + */
>> >> > + ret = hdq_wait_for_flag(OMAP_HDQ_CTRL_STATUS,
>> >> > + OMAP_HDQ_CTRL_STATUS_INITIALIZATION |
>> >> > + OMAP_HDQ_CTRL_STATUS_GO, OMAP_HDQ_FLAG_CLEAR,
>> >> > + &tmp_status);
>> >> > + if (ret)
>> >> > + pr_debug("timeout waiting INIT&GO bits return to zero, %x",
>> >> > + tmp_status);
>> >> > +
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return ret;
>> >> > +}
>> >> > +
>> >> > +static int hdq_read_byte(u8 *val)
>> >> > +{
>> >> > + int ret;
>> >> > + u8 status;
>> >> > + unsigned long irqflags;
>> >> > +
>> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> >> > + if (ret < 0)
>> >> > + return -EINTR;
>> >> > +
>> >> > + if (!hdq_data->hdq_usecount) {
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return -EINVAL;
>> >> > + }
>> >> > +
>> >> > + if (!(hdq_data->hdq_irqstatus & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
>> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS,
>> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO,
>> >> > + OMAP_HDQ_CTRL_STATUS_DIR | OMAP_HDQ_CTRL_STATUS_GO);
>> >> > + /*
>> >> > + * The RX comes immediately after TX. It
>> >> > + * triggers another interrupt before we
>> >> > + * sleep. So we have to wait for RXCOMPLETE bit.
>> >> > + */
>> >> > + {
>> >> > + unsigned long timeout = jiffies + OMAP_HDQ_TIMEOUT;
>> >> > + while (!(hdq_data->hdq_irqstatus
>> >> > + & OMAP_HDQ_INT_STATUS_RXCOMPLETE)
>> >> > + && time_before(jiffies, timeout)) {
>> >> > + set_current_state(TASK_UNINTERRUPTIBLE);
>> >> > + schedule_timeout(1);
>> >> > + }
>> >> > + }
>> >> > + hdq_reg_merge(OMAP_HDQ_CTRL_STATUS, 0,
>> >> > + OMAP_HDQ_CTRL_STATUS_DIR);
>> >> > + spin_lock_irqsave(&hdq_data->hdq_spinlock, irqflags);
>> >> > + status = hdq_data->hdq_irqstatus;
>> >> > + spin_unlock_irqrestore(&hdq_data->hdq_spinlock, irqflags);
>> >> > + /* check irqstatus */
>> >> > + if (!(status & OMAP_HDQ_INT_STATUS_RXCOMPLETE)) {
>> >> > + pr_debug("timeout waiting for RXCOMPLETE, %x", status);
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return -ETIMEDOUT;
>> >> > + }
>> >> > + }
>> >> > + /* the data is ready. Read it in! */
>> >> > + *val = hdq_reg_in(OMAP_HDQ_RX_DATA);
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > +
>> >> > + return 0;
>> >> > +
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * Enable clocks and set the controller to HDQ mode.
>> >> > + */
>> >> > +static int
>> >> > +omap_hdq_get()
>> >> > +{
>> >> > + int ret = 0;
>> >> > +
>> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> >> > + if (ret < 0)
>> >> > + return -EINTR;
>> >> > +
>> >> > + if (OMAP_HDQ_MAX_USER == hdq_data->hdq_usecount) {
>> >> > + pr_debug("attempt to exceed the max use count");
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + ret = -EINVAL;
>> >> > + } else {
>> >> > + hdq_data->hdq_usecount++;
>> >> > + try_module_get(THIS_MODULE);
>> >> > + if (1 == hdq_data->hdq_usecount) {
>> >> > + if (clk_enable(hdq_data->hdq_ick)) {
>> >> > + pr_debug("Can not enable ick\n");
>> >> > + clk_put(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_fck);
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return -ENODEV;
>> >> > + }
>> >> > + if (clk_enable(hdq_data->hdq_fck)) {
>> >> > + pr_debug("Can not enable fck\n");
>> >> > + clk_put(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_fck);
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return -ENODEV;
>> >> > + }
>> >> > +
>> >> > + /* make sure HDQ is out of reset */
>> >> > + if (!(hdq_reg_in(OMAP_HDQ_SYSSTATUS) &
>> >> > + OMAP_HDQ_SYSSTATUS_RESETDONE)) {
>> >> > + ret = _omap_hdq_reset();
>> >> > + if (ret)
>> >> > + /* back up the count */
>> >> > + hdq_data->hdq_usecount--;
>> >> > + } else {
>> >> > + /* select HDQ mode & enable clocks */
>> >> > + hdq_reg_out(OMAP_HDQ_CTRL_STATUS,
>> >> > + OMAP_HDQ_CTRL_STATUS_CLOCKENABLE |
>> >> > + OMAP_HDQ_CTRL_STATUS_INTERRUPTMASK);
>> >> > + hdq_reg_out(OMAP_HDQ_SYSCONFIG,
>> >> > + OMAP_HDQ_SYSCONFIG_AUTOIDLE);
>> >> > + hdq_reg_in(OMAP_HDQ_INT_STATUS);
>> >> > + }
>> >> > + }
>> >> > + }
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return ret;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * Disable clocks to the module.
>> >> > + */
>> >> > +static int
>> >> > +omap_hdq_put()
>> >> > +{
>> >> > + int ret = 0;
>> >> > +
>> >> > + ret = down_interruptible(&hdq_data->hdq_semlock);
>> >> > + if (ret < 0)
>> >> > + return -EINTR;
>> >> > +
>> >> > + if (0 == hdq_data->hdq_usecount) {
>> >> > + pr_debug("attempt to decrement use count when it is zero");
>> >> > + ret = -EINVAL;
>> >> > + } else {
>> >> > + hdq_data->hdq_usecount--;
>> >> > + module_put(THIS_MODULE);
>> >> > + if (0 == hdq_data->hdq_usecount) {
>> >> > + clk_disable(hdq_data->hdq_ick);
>> >> > + clk_disable(hdq_data->hdq_fck);
>> >> > + }
>> >> > + }
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > + return ret;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * Used to control the call to omap_hdq_get and omap_hdq_put.
>> >> > + * HDQ Protocol: Write the CMD|REG_address first, followed by
>> >> > + * the data wrire or read.
>> >> > + */
>> >> > +static int init_trans;
>> >> > +
>> >> > +/*
>> >> > + * Read a byte of data from the device.
>> >> > + */
>> >> > +static u8 omap_w1_read_byte(void *data)
>> >> > +{
>> >> > + u8 val;
>> >> > + int ret;
>> >> > +
>> >> > + ret = hdq_read_byte(&val);
>> >> > + if (ret) {
>> >> > + init_trans = 0;
>> >> > + omap_hdq_put();
>> >> > + return -1;
>> >> > + }
>> >> > +
>> >> > + /* Write followed by a read, release the module */
>> >> > + if (init_trans) {
>> >> > + init_trans = 0;
>> >> > + omap_hdq_put();
>> >> > + }
>> >> > +
>> >> > + return val;
>> >> > +}
>> >> > +
>> >> > +/*
>> >> > + * Write a byte of data to the device.
>> >> > + */
>> >> > +static void omap_w1_write_byte(void *data, u8 byte)
>> >> > +{
>> >> > + u8 status;
>> >> > +
>> >> > + /* First write to initialize the transfer */
>> >> > + if (init_trans == 0)
>> >> > + omap_hdq_get();
>> >> > +
>> >> > + init_trans++;
>> >> > +
>> >> > + hdq_write_byte(byte, &status);
>> >> > + pr_debug("Ctrl status %x\n", status);
>> >> > +
>> >> > + /* Second write, data transfered. Release the module */
>> >> > + if (init_trans > 1) {
>> >> > + omap_hdq_put();
>> >> > + init_trans = 0;
>> >> > + }
>> >> > +
>> >> > + return;
>> >> > +}
>> >> > +
>> >> > +static int __init omap_hdq_probe(struct platform_device *pdev)
>> >> > +{
>> >> > + struct resource *res;
>> >> > + int ret, irq;
>> >> > + u8 rev;
>> >> > +
>> >> > + if (!pdev)
>> >> > + return -ENODEV;
>> >> > +
>> >> > + hdq_data = kmalloc(sizeof(*hdq_data), GFP_KERNEL);
>> >> > + if (!hdq_data)
>> >> > + return -ENODEV;
>> >> > +
>> >> > + platform_set_drvdata(pdev, hdq_data);
>> >> > +
>> >> > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> >> > + if (res == NULL) {
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return -ENXIO;
>> >> > + }
>> >> > +
>> >> > + hdq_data->hdq_base = res->start;
>> >> > +
>> >> > + /* get interface & functional clock objects */
>> >> > + hdq_data->hdq_ick = clk_get(&pdev->dev, "hdq_ick");
>> >> > + hdq_data->hdq_fck = clk_get(&pdev->dev, "hdq_fck");
>> >> > +
>> >> > + if (IS_ERR(hdq_data->hdq_ick) || IS_ERR(hdq_data->hdq_fck)) {
>> >> > + pr_debug("Can't get HDQ clock objects\n");
>> >> > + if (IS_ERR(hdq_data->hdq_ick)) {
>> >> > + ret = PTR_ERR(hdq_data->hdq_ick);
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return ret;
>> >> > + }
>> >> > + if (IS_ERR(hdq_data->hdq_fck)) {
>> >> > + ret = PTR_ERR(hdq_data->hdq_fck);
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return ret;
>> >> > + }
>> >> > + }
>> >> > +
>> >> > + hdq_data->hdq_usecount = 0;
>> >> > + sema_init(&hdq_data->hdq_semlock, 1);
>> >> > +
>> >> > + if (clk_enable(hdq_data->hdq_ick)) {
>> >> > + pr_debug("Can not enable ick\n");
>> >> > + clk_put(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_fck);
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return -ENODEV;
>> >> > + }
>> >> > +
>> >> > + if (clk_enable(hdq_data->hdq_fck)) {
>> >> > + pr_debug("Can not enable fck\n");
>> >> > + clk_disable(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_fck);
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return -ENODEV;
>> >> > + }
>> >> > +
>> >> > + rev = hdq_reg_in(OMAP_HDQ_REVISION);
>> >> > + pr_info("OMAP HDQ Hardware Revision %c.%c. Driver in %s mode.\n",
>> >> > + (rev >> 4) + '0', (rev & 0x0f) + '0', "Interrupt");
>> >> > +
>> >> > + spin_lock_init(&hdq_data->hdq_spinlock);
>> >> > + omap_hdq_break();
>> >> > +
>> >> > + irq = platform_get_irq(pdev, 0);
>> >> > + if (irq < 0) {
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return -ENXIO;
>> >> > + }
>> >> > +
>> >> > + if (request_irq(irq, hdq_isr, IRQF_DISABLED, "OMAP HDQ",
>> >> > + &hdq_data->hdq_semlock)) {
>> >> > + pr_debug("request_irq failed\n");
>> >> > + clk_disable(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_fck);
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return -ENODEV;
>> >> > + }
>> >> > +
>> >> > + /* don't clock the HDQ until it is needed */
>> >> > + clk_disable(hdq_data->hdq_ick);
>> >> > + clk_disable(hdq_data->hdq_fck);
>> >> > +
>> >> > + ret = w1_add_master_device(&omap_w1_master);
>> >> > + if (ret) {
>> >> > + pr_debug("Failure in registering w1 master\n");
>> >> > + clk_put(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_fck);
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > + return ret;
>> >> > + }
>> >> > +
>> >> > + return 0;
>> >> > +}
>> >> > +
>> >> > +static int omap_hdq_remove(struct platform_device *pdev)
>> >> > +{
>> >> > + down_interruptible(&hdq_data->hdq_semlock);
>> >> > + if (0 != hdq_data->hdq_usecount) {
>> >> > + pr_debug("removed when use count is not zero\n");
>> >> > + return -EBUSY;
>> >> > + }
>> >> > + up(&hdq_data->hdq_semlock);
>> >> > +
>> >> > + /* remove module dependency */
>> >> > + clk_put(hdq_data->hdq_ick);
>> >> > + clk_put(hdq_data->hdq_fck);
>> >> > + free_irq(INT_24XX_HDQ_IRQ, &hdq_data->hdq_semlock);
>> >> > + platform_set_drvdata(pdev, NULL);
>> >> > + kfree(hdq_data);
>> >> > +
>> >> > + return 0;
>> >> > +}
>> >> > +
>> >> > +static int __init
>> >> > +omap_hdq_init(void)
>> >> > +{
>> >> > + return platform_driver_register(&omap_hdq_driver);
>> >> > +}
>> >> > +
>> >> > +static void __exit
>> >> > +omap_hdq_exit(void)
>> >> > +{
>> >> > + platform_driver_unregister(&omap_hdq_driver);
>> >> > +}
>> >> > +
>> >> > +module_init(omap_hdq_init);
>> >> > +module_exit(omap_hdq_exit);
>> >> > +
>> >> > +module_param(W1_ID, int, S_IRUSR);
>> >> > +
>> >> > +MODULE_AUTHOR("Texas Instruments");
>> >> > +MODULE_DESCRIPTION("HDQ driver Library");
>> >> > +MODULE_LICENSE("GPL");
>> >> > -- 
>> >> > 1.6.0.1.141.g445ca
>> >> > 
>> >> > --
>> >> > To unsubscribe from this list: send the line "unsubscribe linux-omap" in
>> >> > the body of a message to majordomo@vger.kernel.org
>> >> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> >> > 
>> >> >
>> >> --
>> >> To unsubscribe from this list: send the line "unsubscribe linux-omap" in
>> >> the body of a message to majordomo@vger.kernel.org
>> >> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> > 
>> >
> 
>

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

end of thread, other threads:[~2008-09-09 13:39 UTC | newest]

Thread overview: 86+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2008-08-30 17:16 [PATCH 00/34] omap drivers going upstream Felipe Balbi
2008-08-30 17:16 ` [PATCH 01/33] add lp5521 driver Felipe Balbi
     [not found]   ` <1220116593-862-3-git-send-email-me@felipebalbi.com>
2008-08-30 17:16     ` [PATCH 03/33] add omap-sha1 driver Felipe Balbi
2008-08-30 17:16       ` [PATCH 04/33] add omap gpio expander driver Felipe Balbi
2008-08-30 17:16         ` [PATCH 05/33] add tlv320aic23 driver Felipe Balbi
2008-08-30 17:16           ` [PATCH 06/33] add tsl2563 driver Felipe Balbi
     [not found]             ` <1220116593-862-8-git-send-email-me@felipebalbi.com>
2008-08-30 17:16               ` [PATCH 08/33] add innovator ps2 keypad driver Felipe Balbi
2008-08-30 17:16                 ` [PATCH 09/33] add lm8323 " Felipe Balbi
2008-08-30 17:16                   ` [PATCH 10/33] Add twl4030 " Felipe Balbi
2008-08-30 17:16                     ` [PATCH 11/33] add tsc2301 " Felipe Balbi
2008-08-30 17:16                       ` [PATCH 12/33] Add tsc2005 touchscreen driver Felipe Balbi
2008-08-30 17:16                         ` [PATCH 13/33] Add omap " Felipe Balbi
2008-08-30 17:16                           ` [PATCH 14/33] add tsc210x " Felipe Balbi
2008-08-30 17:16                             ` [PATCH 15/33] add tsc2301 " Felipe Balbi
2008-08-30 17:16                               ` [PATCH 16/33] add omap led drivers Felipe Balbi
2008-08-30 17:16                                 ` [PATCH 17/33] add tea5761 radio driver Felipe Balbi
2008-08-30 17:16                                   ` [PATCH 18/33] add ov9640 sensor driver Felipe Balbi
     [not found]                                     ` <1220116593-862-20-git-send-email-me@felipebalbi.com>
2008-08-30 17:16                                       ` [PATCH 20/33] add omap Serial Trace Interface driver Felipe Balbi
2008-08-30 17:16                                         ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver Felipe Balbi
2008-08-30 17:16                                           ` [PATCH 22/33] add omap nand drivers Felipe Balbi
2008-08-30 17:16                                             ` [PATCH 23/33] add omap irda driver Felipe Balbi
2008-08-30 17:16                                               ` [PATCH 24/33] add bq27x00 battery driver Felipe Balbi
2008-08-30 17:16                                                 ` [PATCH 25/33] add TWL4030 Battery Charger Interface driver Felipe Balbi
2008-08-30 17:16                                                   ` [PATCH 26/33] add TWL4030 RealTime Clock driver Felipe Balbi
2008-08-30 17:16                                                     ` [PATCH 27/33] add tsc210x driver Felipe Balbi
2008-08-30 17:16                                                       ` [PATCH 28/33] add tsc2301 driver Felipe Balbi
2008-08-30 17:16                                                         ` [PATCH 29/33] add omap ehci bus glue Felipe Balbi
2008-08-30 17:16                                                           ` [PATCH 30/33] add omap backlight support Felipe Balbi
2008-08-30 17:16                                                             ` [PATCH 31/33] add several omap lcd drivers Felipe Balbi
2008-08-30 17:16                                                               ` [PATCH 32/33] add omap 1-wire interface driver Felipe Balbi
2008-08-30 17:16                                                                 ` [PATCH 33/33] add bq27000 1-wire slave driver Felipe Balbi
2008-09-01 13:16                                                                 ` [PATCH 32/33] add omap 1-wire interface driver Madhusudhan Chikkature
2008-09-05 17:59                                                                   ` Tony Lindgren
2008-09-08 12:15                                                                     ` Madhusudhan Chikkature
2008-09-08 18:05                                                                       ` Tony Lindgren
2008-09-08 20:07                                                                         ` Russell King - ARM Linux
2008-09-09 13:38                                                                         ` Madhusudhan Chikkature
2008-08-31 21:12                                                             ` [PATCH 30/33] add omap backlight support David Brownell
2008-08-31 21:23                                                               ` Felipe Balbi
2008-08-31 21:44                                                                 ` Felipe Balbi
2008-08-31 21:09                                                           ` [PATCH 29/33] add omap ehci bus glue David Brownell
2008-08-31 21:25                                                             ` Felipe Balbi
2008-09-01  5:55                                                           ` Gadiyar, Anand
2008-09-01  7:56                                                             ` Felipe Balbi
2008-09-01  8:29                                                               ` Gadiyar, Anand
2008-09-01  7:35                                                         ` [PATCH 28/33] add tsc2301 driver Jarkko Nikula
2008-09-01  7:58                                                           ` Felipe Balbi
2008-09-01  8:11                                                             ` Jarkko Nikula
2008-09-01  8:37                                                               ` Felipe Balbi
2008-08-31 21:08                                             ` [PATCH 22/33] add omap nand drivers David Brownell
2008-08-31 21:28                                               ` Felipe Balbi
2008-09-05 18:07                                                 ` Tony Lindgren
2008-08-31 21:06                                           ` [PATCH 21/33] add OMAP HighSpeed mmc controller driver David Brownell
2008-08-31 21:31                                             ` Felipe Balbi
2008-08-31 21:51                                               ` David Brownell
2008-08-31 22:02                                                 ` Felipe Balbi
2008-09-04  1:02                                                   ` Tony Lindgren
2008-09-04 13:26                                             ` Madhusudhan Chikkature
2008-09-05  8:16                                               ` David Brownell
2008-09-02 22:05                                           ` Russell King - ARM Linux
2008-09-01  7:24                                   ` [PATCH 17/33] add tea5761 radio driver Jarkko Nikula
2008-09-01  7:59                                     ` Felipe Balbi
2008-09-03 15:01                                       ` Eduardo Valentin
2008-08-31 21:01                                 ` [PATCH 16/33] add omap led drivers David Brownell
2008-08-31 21:33                                   ` Felipe Balbi
2008-08-31 20:53                         ` [PATCH 12/33] Add tsc2005 touchscreen driver David Brownell
2008-08-31 21:35                           ` Felipe Balbi
2008-09-01  1:01                         ` andrzej zaborowski
2008-09-01  7:34                           ` Daniel Stone
2008-09-02 21:33                         ` Russell King - ARM Linux
2008-09-07 18:47                           ` David Brownell
2008-09-01  7:22           ` [PATCH 05/33] add tlv320aic23 driver Jarkko Nikula
2008-09-01  8:01             ` Felipe Balbi
2008-09-01  8:13               ` Jarkko Nikula
     [not found]                 ` <65e025470809010802m4c12a6eeh9cf5d3a3643ff5b8@mail.gmail.com>
2008-09-01 15:03                   ` Yuri Jaeger Monti
2008-09-01 15:19                   ` Felipe Balbi
2008-09-04  1:40                     ` Tony Lindgren
2008-08-31 20:51         ` [PATCH 04/33] add omap gpio expander driver David Brownell
2008-08-31 21:36           ` Felipe Balbi
2008-08-31 20:39   ` [PATCH 01/33] add lp5521 driver David Brownell
2008-08-31 21:41     ` Felipe Balbi
2008-08-31 22:37 ` [PATCH 00/34] omap drivers going upstream Felipe Balbi
2008-08-31 22:40   ` Felipe Balbi
2008-09-01  1:22   ` andrzej zaborowski
2008-09-01  8:04     ` Felipe Balbi
2008-09-01  1:46 ` andrzej zaborowski

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.