# Base git commit: 98f7e32f20d2
# (Linux 6.11)
# 
# Author:    Russell King (Wed 28 Sep 09:55:58 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:50 BST 2024)
# 
#   ipaq sleeve updates
# 
#   Signed-off-by: Russell King
# 
#   902f6f96dcfeb766ce95121552950506c7935102
#    arch/arm/mach-sa1100/h3xxx-sleeve.c    | 189 +++++++++++++++++++++++++++++----
#    arch/arm/mach-sa1100/ipaq-sleeve-bus.c |  27 ++++-
#    include/linux/ipaq-sleeve.h            |   4 +-
#    3 files changed, 192 insertions(+), 28 deletions(-)
# 
# Author:    Russell King (Tue 27 Sep 19:52:38 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:49 BST 2024)
# 
#   update
# 
#   530058a486c92f7b4513aed57e3c6bfe62c88878
#    sound/soc/sa11x0/assabet.c | 28 +++++++++++++++-------------
#    1 file changed, 15 insertions(+), 13 deletions(-)
# 
# Author:    Russell King (Mon 26 Sep 15:13:40 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:47 BST 2024)
# 
#   asoc: sa11x0/h3xxx: add audio support
# 
#   Add support for the UDA1341 on the iPAQ H3600 and H3100 devices.
# 
#   Signed-off-by: Russell King
# 
#   815b29e746e9e424e466d81c3f4f56bc442d2cf8
#    sound/soc/sa11x0/Kconfig  |  10 ++
#    sound/soc/sa11x0/Makefile |   3 +
#    sound/soc/sa11x0/h3xxx.c  | 317 ++++++++++++++++++++++++++++++++++++++++++++++
#    3 files changed, 330 insertions(+)
#    create mode 100644 sound/soc/sa11x0/h3xxx.c
# 
# Author:    Russell King (Mon 26 Sep 15:13:04 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:46 BST 2024)
# 
#   h3600 audio gpios
# 
#   8348f3990a51c4db3f69bb021633a70d93de8187
#    arch/arm/mach-sa1100/h3600.c | 20 ++++++++++++++++++++
#    1 file changed, 20 insertions(+)
# 
# Author:    Russell King (Mon 26 Sep 18:53:39 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:44 BST 2024)
# 
#   clk: add iPAQ audio sysclock driver
# 
#   Add clock driver for iPAQ audio sysclock.
# 
#   Signed-off-by: Russell King
# 
#   935994d6e9b6b46eec83fe7dd154b281b483047f
#    arch/arm/mach-sa1100/h3600.c |  18 ++++++
#    drivers/clk/Makefile         |   1 +
#    drivers/clk/clk-ipaq.c       | 135 +++++++++++++++++++++++++++++++++++++++++++
#    3 files changed, 154 insertions(+)
#    create mode 100644 drivers/clk/clk-ipaq.c
# 
# Author:    Russell King (Thu 15 Sep 16:42:09 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:43 BST 2024)
# 
#   net: 3c589_cs: convert to NAPI
# 
#   Convert the 3c589_cs driver to NAPI to ease the interrupt workload.
# 
#   Signed-off-by: Russell King
# 
#   3181cc64fbbe42fbe7c2ef25fdae66d9f992fe86
#    drivers/net/ethernet/3com/3c589_cs.c | 117 ++++++++++++++++++++++++++---------
#    1 file changed, 88 insertions(+), 29 deletions(-)
# 
# Author:    Russell King (Thu 15 Sep 16:41:40 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:41 BST 2024)
# 
#   pcmcia: convert sa1100_h3600 to support dual-slot sleeve only
# 
#   cfa11aa0451a0489df322d07122aa06b68867342
#    drivers/pcmcia/Makefile       |   2 +-
#    drivers/pcmcia/l1110.c        | 109 ++++++++++++++++++++++++++++++++++++++++++
#    drivers/pcmcia/l1110.h        |  15 ++++++
#    drivers/pcmcia/sa1100_h3600.c |  93 ++++++++++++-----------------------
#    4 files changed, 156 insertions(+), 63 deletions(-)
#    create mode 100644 drivers/pcmcia/l1110.c
#    create mode 100644 drivers/pcmcia/l1110.h
# 
# Author:    Russell King (Thu 15 Sep 01:38:27 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:40 BST 2024)
# 
#   ARM: sa1100/h3xxx: sleeve support
# 
#   Signed-off-by: Russell King
# 
#   eb836906624c23ef94ff627f31011c7e3c191505
#    arch/arm/mach-sa1100/Makefile           |   3 +
#    arch/arm/mach-sa1100/h3600.c            |  14 ++
#    arch/arm/mach-sa1100/h3xxx-sleeve.c     | 341 ++++++++++++++++++++++++++++++++
#    arch/arm/mach-sa1100/h3xxx.c            |   6 +
#    arch/arm/mach-sa1100/ipaq-sleeve-bus.c  | 124 ++++++++++++
#    arch/arm/mach-sa1100/ipaq-sleeve-cf.c   |  70 +++++++
#    arch/arm/mach-sa1100/ipaq-sleeve-dual.c |  68 +++++++
#    drivers/pcmcia/sa1100_h3600.c           |  60 +-----
#    include/linux/ipaq-sleeve.h             |  34 ++++
#    9 files changed, 661 insertions(+), 59 deletions(-)
#    create mode 100644 arch/arm/mach-sa1100/h3xxx-sleeve.c
#    create mode 100644 arch/arm/mach-sa1100/ipaq-sleeve-bus.c
#    create mode 100644 arch/arm/mach-sa1100/ipaq-sleeve-cf.c
#    create mode 100644 arch/arm/mach-sa1100/ipaq-sleeve-dual.c
#    create mode 100644 include/linux/ipaq-sleeve.h
# 
# Author:    Russell King (Sat 24 Sep 23:38:59 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:38 BST 2024)
# 
#   module: add iPAQ sleeve device table and module alias support
# 
#   Add support for iPAQ sleeve device table and module alias support, so
#   that sleeve drivers can be automatically modprobed into the kernel at
#   run time.
# 
#   Signed-off-by: Russell King
# 
#   319f1652ef5f76844b247686827db3da79b71045
#    include/linux/mod_devicetable.h   | 10 ++++++++++
#    scripts/mod/devicetable-offsets.c |  4 ++++
#    scripts/mod/file2alias.c          | 11 +++++++++++
#    3 files changed, 25 insertions(+)
# 
# Author:    Russell King (Tue 20 Sep 00:41:32 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:37 BST 2024)
# 
#   h3xxx: add socket0
# 
#   1c9a8c647f8283a128f9f1ae0098fb66298cf489
#    arch/arm/mach-sa1100/h3xxx.c | 13 +++++++++++++
#    1 file changed, 13 insertions(+)
# 
# Author:    Russell King (Thu 15 Sep 10:21:31 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:35 BST 2024)
# 
#   mfd: ipaq-micro: allow SPI reads to work
# 
#   1f3b0b9bfe0b4c7727788ae1c861883b4a97701f
#    drivers/mfd/ipaq-micro.c | 1 +
#    1 file changed, 1 insertion(+)
# 
# Author:    Russell King (Tue 13 Sep 18:51:55 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:34 BST 2024)
# 
#   add sa11x0 uart clocks
# 
#   Signed-off-by: Russell King
# 
#   50dc3be3b0b68bf4a1d3fc78d81e5822cff88a7d
#    arch/arm/mach-sa1100/clock.c |  3 +++
#    drivers/tty/serial/sa1100.c  | 16 +++++++++++++++-
#    2 files changed, 18 insertions(+), 1 deletion(-)
# 
# Author:    Russell King (Tue 13 Sep 16:52:07 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:32 BST 2024)
# 
#   ARM: sa1111: use parent clock rate
# 
#   Signed-off-by: Russell King
# 
#   edb0b24b3eaf1cb1e008e21cfb05cd11882fad59
#    arch/arm/common/sa1111.c | 3 ++-
#    1 file changed, 2 insertions(+), 1 deletion(-)
# 
# Author:    Russell King (Tue 13 Sep 14:41:36 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:31 BST 2024)
# 
#   ARM: sa1100/assabet: remove PWER initialisation
# 
#   Remove the explicit PWER initialisation: gpio-keys already enables
#   wake-up for its keys, and so this setting is redundant.  In any case,
#   the conversion of the SA1100 GPIOs to gpiolib has made this setting
#   ineffectual.
# 
#   Signed-off-by: Russell King
# 
#   098c167a222e1a1f4004f89c655cd8c13d2dc706
#    arch/arm/mach-sa1100/assabet.c | 1 -
#    1 file changed, 1 deletion(-)
# 
# Author:    Russell King (Tue 13 Sep 13:22:14 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:29 BST 2024)
# 
#   ARM: sa1100/h3xxx: convert to xcvr-enable
# 
#   Signed-off-by: Russell King
# 
#   f355920d41e58d3e713517d245f2cf46b6ab9b05
#    arch/arm/mach-sa1100/h3xxx.c | 15 +--------------
#    1 file changed, 1 insertion(+), 14 deletions(-)
# 
# Author:    Russell King (Tue 13 Sep 13:18:18 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:28 BST 2024)
# 
#   ARM: sa1100/assabet: convert to xcvr-enable
# 
#   Signed-off-by: Russell King
# 
#   f46ed630a7edf443ee6d1c5abfe9a7cfda128e62
#    arch/arm/mach-sa1100/assabet.c | 18 +-----------------
#    1 file changed, 1 insertion(+), 17 deletions(-)
# 
# Author:    Russell King (Tue 13 Sep 13:02:23 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:26 BST 2024)
# 
#   ARM: sa1100: const-ify struct sa1100_port_fns
# 
#   Constify the sa1100_port_fns structures in all boards.
# 
#   Signed-off-by: Russell King
# 
#   6b45a98b80f2dec6ac5c9ae348eaf0e57ce46fbe
#    arch/arm/mach-sa1100/assabet.c | 2 +-
#    arch/arm/mach-sa1100/collie.c  | 2 +-
#    arch/arm/mach-sa1100/h3xxx.c   | 2 +-
#    3 files changed, 3 insertions(+), 3 deletions(-)
# 
# Author:    Russell King (Tue 13 Sep 12:10:29 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:25 BST 2024)
# 
#   serial: sa1100: constify struct sa1100_port_fns
# 
#   Signed-off-by: Russell King
# 
#   839f9a9624d0e3a6e605bfacbb9c14baeb9adc1e
#    drivers/tty/serial/sa1100.c                 | 2 +-
#    include/linux/platform_data/sa11x0-serial.h | 4 ++--
#    2 files changed, 3 insertions(+), 3 deletions(-)
# 
# Author:    Russell King (Tue 13 Sep 13:15:31 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:23 BST 2024)
# 
#   serial: sa1100: add support for transceiver enable GPIO
# 
#   Add support for a transceiver enable GPIO signal, which allows the
#   external RS232 transceiver on several platforms to be force-enabled
#   or force-disabled according to the system state and the hardware
#   capabilities.
# 
#   Signed-off-by: Russell King
# 
#   180e69613addd481413526eb5fdc7f6939733777
#    drivers/tty/serial/sa1100.c | 27 +++++++++++++++++++++++++++
#    1 file changed, 27 insertions(+)
# 
# Author:    Russell King (Tue 13 Sep 12:09:18 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:22 BST 2024)
# 
#   tty: serial: sa1100: fix iPAQ serial port wakeup
# 
#   The iPAQ H3xxx platforms used to wake up on DCD or DSR transitions.
#   However, at some point this got broken and fails to work today.  Fix
#   this by using the newly added modem control gpio layer's wakeup
#   controls.
# 
#   Signed-off-by: Russell King
# 
#   bb63435b09117a77ffcf75f6dae5620df7a3b1d0
#    arch/arm/mach-sa1100/h3xxx.c                | 22 ++-------
#    drivers/tty/serial/sa1100.c                 | 76 +++++++++++++++++++++++++++--
#    include/linux/platform_data/sa11x0-serial.h |  9 +++-
#    3 files changed, 83 insertions(+), 24 deletions(-)
# 
# Author:    Russell King (Sat 24 Sep 13:31:21 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:21:18 BST 2024)
# 
#   tty: serial_mctrl_gpio: add wakeup support for serial control gpios
# 
#   Allow serial control GPIOs to be used as a wakeup source for the system.
# 
#   Signed-off-by: Russell King
# 
#   c197c43807b11e752a8dcb2a55d59855e060b6a5
#    drivers/tty/serial/serial_mctrl_gpio.c | 19 +++++++++++++++++++
#    drivers/tty/serial/serial_mctrl_gpio.h | 22 ++++++++++++++++++++++
#    2 files changed, 41 insertions(+)
# 
# Author:    Russell King (Sun 11 Sep 10:10:30 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:15 BST 2024)
# 
#   3c589_cs 16-bit pcmcia fix
# 
#   Signed-off-by: Russell King
# 
#   bda31a2380f72f66a548df64556be76d49109448
#    drivers/net/ethernet/3com/3c589_cs.c | 9 +++++++++
#    1 file changed, 9 insertions(+)
# 
# Author:    Russell King (Thu  8 Sep 17:35:29 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:13 BST 2024)
# 
#   ARM: sa1100/assabet: switch to using gpiod APIs for LCD control
# 
#   Switch to using gpiod APIs for controlling the LCD depth and power.
# 
#   Signed-off-by: Russell King
# 
#   0dd71844fa5f17af63ae4d773e6fd580fcc921b3
#    arch/arm/mach-sa1100/assabet.c | 59 ++++++++++++++++++++++++------------------
#    1 file changed, 34 insertions(+), 25 deletions(-)
# 
# Author:    Russell King (Tue 23 Aug 14:50:04 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:11 BST 2024)
# 
#   asoc: sa11x0/assabet: use gpiod APIs for some gpios
# 
#   Signed-off-by: Russell King
# 
#   390e958a32408f5c8ffc11a07923d81042722452
#    arch/arm/mach-sa1100/assabet.c | 19 +++++++++
#    arch/arm/mach-sa1100/clock.c   |  2 +
#    sound/soc/sa11x0/assabet.c     | 92 +++++++++++++++++++++++++-----------------
#    3 files changed, 75 insertions(+), 38 deletions(-)
# 
# Author:    Russell King (Tue 27 Sep 00:37:43 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:10 BST 2024)
# 
#   asoc: uda134x: add support for UDA134x QMUTE via gpiolib
# 
#   Add support for the UDA134x QMUTE signal via the gpiolib layer.  We
#   assert QMUTE when powering up the device, keeping it muted until we
#   are starting up the playback stream.  The reverse happens when the
#   playback stream is shut down.
# 
#   Signed-off-by: Russell King
# 
#   494e024776d688ea6a38807a4d1ec225b2ddabf0
#    sound/soc/codecs/uda134x.c | 17 ++++++++++++++++-
#    1 file changed, 16 insertions(+), 1 deletion(-)
# 
# Author:    Russell King (Oracle) (Mon 30 Oct 12:52:01 GMT 2023)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:08 BST 2024)
# 
#   Revert "ASoC: Delete UDA134x/L3 audio codec"
# 
#   This reverts commit 6dd11b945951315ba4986844f20e83a0c27c1d38.
# 
#   516a31a8d664ed2a0c2984d0146e816c6842108a
#    include/sound/l3.h         |  28 +++
#    include/sound/uda134x.h    |  24 ++
#    sound/soc/codecs/Kconfig   |   8 +
#    sound/soc/codecs/Makefile  |   5 +-
#    sound/soc/codecs/l3.c      | 132 ++++++++++
#    sound/soc/codecs/uda134x.c | 587 +++++++++++++++++++++++++++++++++++++++++++++
#    sound/soc/codecs/uda134x.h |  33 +++
#    7 files changed, 816 insertions(+), 1 deletion(-)
#    create mode 100644 include/sound/l3.h
#    create mode 100644 include/sound/uda134x.h
#    create mode 100644 sound/soc/codecs/l3.c
#    create mode 100644 sound/soc/codecs/uda134x.c
#    create mode 100644 sound/soc/codecs/uda134x.h
# 
# Author:    Russell King (Wed 24 Aug 13:28:09 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:07 BST 2024)
# 
#   assabet fix/hack
# 
#   be27ebf589105e6d15a9c0b431cfdad07dd3b1b0
#    arch/arm/mach-sa1100/assabet.c | 7 +++++--
#    1 file changed, 5 insertions(+), 2 deletions(-)
# 
# Author:    Russell King (Sun 21 Aug 00:09:10 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:05 BST 2024)
# 
#   assabet hack
# 
#   Signed-off-by: Russell King
# 
#   1792737afe8c236199e3682812dbab4389a989e4
#    arch/arm/mach-sa1100/assabet.c | 20 ++++++++++++++------
#    1 file changed, 14 insertions(+), 6 deletions(-)
# 
# Author:    Russell King (Tue 13 Sep 20:57:22 BST 2016)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:20:04 BST 2024)
# 
#   ARM: sa1100: switch to accurate MPLL calculation
# 
#   Switch SA1100 to the more accurate MPLL calculation.  Table 8-1 in the
#   SA1110 developers manual gives two sets of frequencies for two different
#   crystals - 3.6864MHz and 3.5795MHz.  Calculating the multiplication
#   factor for each entry in the table shows that the clock rates given are
#   are equal to the crystal frequency multiplied by 4(4 + PPCR), rounded to
#   100kHz.  This equation holds for the higher frequencies up to 280.2MHz
#   which have been omitted from the later manuals.
# 
#   Signed-off-by: Russell King
# 
#   5882b7a60f177b283cd58a9c426379901bd6ec2e
#    arch/arm/mach-sa1100/clock.c | 7 ++++---
#    1 file changed, 4 insertions(+), 3 deletions(-)
# 
# Author:    Russell King (Oracle) (Thu 10 Oct 18:19:53 BST 2024)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:19:53 BST 2024)
# 
#   Merge branches 'sa1100-for-next' and 'unstable/sa11x0-asoc' into sa1100
# 
# Author:    Russell King (Thu 11 Jul 10:25:16 BST 2013)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:19:48 BST 2024)
# 
#   ALSA: ASoC: convert sa11x0 assabet driver to use generic ASoC dmaengine backend
# 
#   Signed-off-by: Russell King
# 
#   3332f3bb645dbadcd4f1539012803aee18988044
#    sound/soc/sa11x0/assabet.c | 2 +-
#    1 file changed, 1 insertion(+), 1 deletion(-)
# 
# Author:    Russell King (Thu 11 Jul 10:24:17 BST 2013)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:19:46 BST 2024)
# 
#   ALSA: ASoC: convert sa11x0 ssp driver to generic ASoC dmaengine support
# 
#   Signed-off-by: Russell King
# 
#   ef7c0a43a0b098f1bbcca985d18b3b4bb7470671
#    sound/soc/sa11x0/Kconfig |   2 +-
#    sound/soc/sa11x0/ssp.c   | 106 ++++++++++++++++++++++++++++++++++-------------
#    2 files changed, 79 insertions(+), 29 deletions(-)
# 
# Author:    Russell King (Wed 18 Jan 13:25:02 GMT 2012)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:19:45 BST 2024)
# 
#   ALSA: ASoC: add SA-11x0 Assabet platform support
# 
#   Add the support code for the UDA1341 on the Assabet platform, which
#   uses the DMA engine and SA-11x0 SSP drivers.
# 
#   Signed-off-by: Russell King
# 
#   cd4b48f2177a1bbdd40ba40f140b5d5eab2fc0ec
#    sound/soc/sa11x0/Kconfig   |  10 +
#    sound/soc/sa11x0/Makefile  |   3 +
#    sound/soc/sa11x0/assabet.c | 522 +++++++++++++++++++++++++++++++++++++++++++++
#    3 files changed, 535 insertions(+)
#    create mode 100644 sound/soc/sa11x0/assabet.c
# 
# Author:    Russell King (Wed 18 Jan 13:22:40 GMT 2012)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:19:43 BST 2024)
# 
#   ALSA: ASoC: add SA-11x0 SSP cpu_dai driver
# 
#   Add a SA11x0 SSP SoC audio interface driver, which contains the SoC
#   specifics for handing audio data via the SSP interface.
# 
#   Signed-off-by: Russell King
# 
#   7966b39f130da79939571363e741512d17fb1897
#    sound/soc/Kconfig         |   1 +
#    sound/soc/Makefile        |   1 +
#    sound/soc/sa11x0/Kconfig  |   4 +
#    sound/soc/sa11x0/Makefile |   2 +
#    sound/soc/sa11x0/ssp.c    | 361 ++++++++++++++++++++++++++++++++++++++++++++++
#    sound/soc/sa11x0/ssp.h    |  11 ++
#    6 files changed, 380 insertions(+)
#    create mode 100644 sound/soc/sa11x0/Kconfig
#    create mode 100644 sound/soc/sa11x0/Makefile
#    create mode 100644 sound/soc/sa11x0/ssp.c
#    create mode 100644 sound/soc/sa11x0/ssp.h
# 
# Author:    Russell King (Wed 18 Jan 13:01:26 GMT 2012)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:19:42 BST 2024)
# 
#   ALSA: ASoC: add generic slave and cyclic DMA engine support
# 
#   Add a generic DMA engine platform driver to ASoC.  This allows a range
#   of DMA engines to be used to transfer data to a CPUs audio interface
#   without having to rewrite this code many times.
# 
#   This driver supports both DMA_SLAVE and DMA_CYCLIC channels, preferring
#   DMA_CYCLIC channels over DMA_SLAVE channels.
# 
#   Signed-off-by: Russell King
# 
#   292abf1eb66724e426bd17e6a7cc8147fb1c03e7
#    include/sound/soc-dmaengine.h |  26 ++
#    sound/soc/Kconfig             |   6 +
#    sound/soc/Makefile            |   2 +
#    sound/soc/soc-dmaengine.c     | 591 ++++++++++++++++++++++++++++++++++++++++++
#    4 files changed, 625 insertions(+)
#    create mode 100644 include/sound/soc-dmaengine.h
#    create mode 100644 sound/soc/soc-dmaengine.c
# 
# Author:    Russell King (Thu 11 Jul 10:21:56 BST 2013)
# Committer: Russell King (Oracle) (Thu 10 Oct 18:19:41 BST 2024)
# 
#   ALSA: ASoC: fix generic dmaengine channel requesting
# 
#   The ASoC generic dmaengine support takes a filter data parameter via
#   struct snd_dmaengine_dai_dma_data.  However, rather than passing the
#   filter data to the DMA engine request function, it passed the
#   struct snd_dmaengine_dai_dma_data itself.  Fix this.
# 
#   Signed-off-by: Russell King
# 
#   4dc8d98c200d4465d9d9b5b4799c68f05a0ac3f4
#    sound/soc/soc-generic-dmaengine-pcm.c | 4 ++--
#    1 file changed, 2 insertions(+), 2 deletions(-)
# 
# Author:    Russell King (Fri  2 Sep 10:50:25 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:41:10 BST 2024)
# 
#   ARM: sa1100/assabet: use assabet GPIO definitions
# 
#   Signed-off-by: Russell King
# 
#   30e30506a712ab653382c5e34b9607f41dcbc8b1
#    arch/arm/mach-sa1100/assabet.c | 14 +++++++-------
#    1 file changed, 7 insertions(+), 7 deletions(-)
# 
# Author:    Russell King (Fri  2 Sep 10:45:14 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:41:07 BST 2024)
# 
#   ARM: sa1100/assabet: move GPIO definitions to assabet.c
# 
#   Move GPIO board definitions to assabet.c
# 
#   Signed-off-by: Russell King
# 
#   d886daadadd2b83c911a85bb254fc937c9f2e89d
#    arch/arm/mach-sa1100/assabet.c              | 8 ++++++++
#    arch/arm/mach-sa1100/include/mach/assabet.h | 8 --------
#    2 files changed, 8 insertions(+), 8 deletions(-)
# 
# Author:    Russell King (Wed 31 Aug 11:38:54 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:41:03 BST 2024)
# 
#   ARM: sa1100/assabet: move SCR definitions and machine_has_neponset()
# 
#   Nothing outside of assabet.c needs the board SCR register definitions,
#   nor does it need machine_has_neponset().  Remove these definitions.
# 
#   Signed-off-by: Russell King
# 
#   72eed378a4f031e939a960a6c00fcce477de6996
#    arch/arm/mach-sa1100/assabet.c              | 19 +++++++++++++++++--
#    arch/arm/mach-sa1100/include/mach/assabet.h | 20 --------------------
#    2 files changed, 17 insertions(+), 22 deletions(-)
# 
# Author:    Russell King (Wed 31 Aug 11:40:39 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:41:00 BST 2024)
# 
#   ARM: sa1100/assabet: remove ASSABET_BSR* definitions
# 
#   The assabet BSR register is treated as a GPIO register now, so the
#   register address and bit definitions are redundant.  Remove these
#   definitions.
# 
#   Signed-off-by: Russell King
# 
#   f990f7f38aa1a6bbd66fd4a20f3fdc90acd9de97
#    arch/arm/mach-sa1100/include/mach/assabet.h | 13 -------------
#    1 file changed, 13 deletions(-)
# 
# Author:    Russell King (Wed 31 Aug 11:34:19 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:57 BST 2024)
# 
#   ARM: sa1100/assabet: move BCR register address definition
# 
#   Move the Assabet BCR register address definition out of the header file
#   and into the board code, converting it to a iomem pointer rather than a
#   volatile accessor.  Also update the early IO maps to use the appropriate
#   definition.
# 
#   Signed-off-by: Russell King
# 
#   4de88ce940b71d4900f7420b6c306e50f74037df
#    arch/arm/mach-sa1100/assabet.c              | 7 +++++--
#    arch/arm/mach-sa1100/include/mach/assabet.h | 3 ---
#    2 files changed, 5 insertions(+), 5 deletions(-)
# 
# Author:    Russell King (Tue 30 Aug 22:48:32 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:53 BST 2024)
# 
#   ARM: sa1100/neponset: convert to devm_kzalloc()
# 
#   Use the device managed devm_kzalloc() to allocate the neponset device
#   private memory rather than manually allocating and freeing the data.
#   This simplifies the cleanup handling a little.
# 
#   Signed-off-by: Russell King
# 
#   3182f04c8cd2e0bf5134f71ec9ff84d838744542
#    arch/arm/mach-sa1100/neponset.c | 15 ++++++---------
#    1 file changed, 6 insertions(+), 9 deletions(-)
# 
# Author:    Russell King (Fri 26 Aug 23:52:42 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:50 BST 2024)
# 
#   ARM: sa1100/neponset: convert to irq domain
# 
#   Convert Neponset to use an irq domain rather than statically allocated
#   interrupt numbers, and switch to using the IRQ domain to lookup the
#   Linux interrupt number when decoding an interrupt.  Since interrupts
#   remain disabled across an interrupt handler, we can simplify the
#   handler code somewhat.
# 
#   Signed-off-by: Russell King
# 
#   7fb2c4ba44b2442609dfb30a4ef525d53bfbedb8
#    arch/arm/mach-sa1100/neponset.c | 113 ++++++++++++++++++++++------------------
#    1 file changed, 62 insertions(+), 51 deletions(-)
# 
# Author:    Russell King (Wed 31 Aug 08:49:55 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:47 BST 2024)
# 
#   ARM: sa1100/neponset: remove neponset_ncr_* interfaces and header
# 
#   Remove the legacy neponset_ncr_*() interfaces and definitions, which
#   leaves the header empty apart from the header and footer.  Remove the
#   header as well.
# 
#   Signed-off-by: Russell King
# 
#   21253c7f8c91e3810f6ef1f516689ffea8d377fd
#    arch/arm/mach-sa1100/include/mach/neponset.h | 31 ----------------------------
#    arch/arm/mach-sa1100/neponset.c              | 14 +------------
#    2 files changed, 1 insertion(+), 44 deletions(-)
#    delete mode 100644 arch/arm/mach-sa1100/include/mach/neponset.h
# 
# Author:    Russell King (Wed 31 Aug 08:49:53 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:43 BST 2024)
# 
#   net: smc91x: remove neponset specific oscillator hack
# 
#   Remove the neponset specific oscillator hack from the smc91x driver,
#   relying instead on the gpio controls provided.
# 
#   Acked-by: Nicolas Pitre
#   Acked-by: David S. Miller
#   Signed-off-by: Russell King
# 
#   54371a31ed026990f9c54a65c1e8c675670608eb
#    drivers/net/ethernet/smsc/smc91x.c | 9 ---------
#    1 file changed, 9 deletions(-)
# 
# Author:    Russell King (Wed 31 Aug 08:49:53 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:40 BST 2024)
# 
#   net: smc91x: add external clock support
# 
#   The smc91x can be operated with either a directly attached crystal, or
#   an externally supplied clock.  When used with an externally supplied
#   clock, this clock may require some configuration or control.  Add calls
#   to the driver to lookup, enable and disable this optional clock.
# 
#   Acked-by: Nicolas Pitre
#   Acked-by: David S. Miller
#   Signed-off-by: Russell King
# 
#   5852be7fc5c625ccd9afea8a2a38198af19f4107
#    drivers/net/ethernet/smsc/smc91x.c | 35 +++++++++++++++++++++++++++++++++--
#    drivers/net/ethernet/smsc/smc91x.h |  1 +
#    2 files changed, 34 insertions(+), 2 deletions(-)
# 
# Author:    Russell King (Wed 31 Aug 08:49:53 BST 2016)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:36 BST 2024)
# 
#   ARM: sa1100/neponset: add ethernet oscillator
# 
#   Add the clock structure for the external SMC91x ethernet oscillator.
#   This oscillator has an enable signal which allows the oscillator to be
#   placed in low power mode when not required.
# 
#   Signed-off-by: Russell King
# 
#   6b7991b71016575b020f0b8585fefd967d7ecefe
#    arch/arm/mach-sa1100/neponset.c | 43 +++++++++++++++++++++++++++++++++++++++++
#    1 file changed, 43 insertions(+)
# 
# Author:    Russell King (Oracle) (Mon  7 Jun 11:15:52 BST 2021)
# Committer: Russell King (Oracle) (Sun 30 Jun 15:40:34 BST 2024)
# 
#   Revert "clk: gpio: Use DT way of specifying parents"
# 
#   This reverts commit 9a9b5a4af066f1245d7848a5d5d5c8147c18aa28.
# 
#   ad18c74e9a19aa2ee787ffcf748f2730e2cd4504
#    drivers/clk/clk-gpio.c       | 172 ++++++++++++++++++++++++++++---------------
#    include/linux/clk-provider.h |  38 ++++++++++
#    2 files changed, 151 insertions(+), 59 deletions(-)
# 
diff --git a/arch/arm/common/sa1111.c b/arch/arm/common/sa1111.c
index 550978dc3c50..82dafeb05695 100644
--- a/arch/arm/common/sa1111.c
+++ b/arch/arm/common/sa1111.c
@@ -1177,6 +1177,7 @@ static unsigned char opdiv_table[] = { 1, 4, 2, 8 };
 
 static unsigned int __sa1111_pll_clock(struct sa1111 *sachip)
 {
+	unsigned long rate = clk_get_rate(sachip->clk);
 	unsigned int skcdr, fbdiv, ipdiv, opdiv;
 
 	skcdr = readl_relaxed(sachip->base + SA1111_SKCDR);
@@ -1185,7 +1186,7 @@ static unsigned int __sa1111_pll_clock(struct sa1111 *sachip)
 	ipdiv = ((skcdr & 0x0f80) >> 7) + 2;
 	opdiv = opdiv_table[(skcdr & 0x3000) >> 12];
 
-	return 3686400 * fbdiv / (ipdiv * opdiv);
+	return rate * fbdiv / (ipdiv * opdiv);
 }
 
 /**
diff --git a/arch/arm/mach-sa1100/Makefile b/arch/arm/mach-sa1100/Makefile
index b5816d675152..1ece35b1cc2d 100644
--- a/arch/arm/mach-sa1100/Makefile
+++ b/arch/arm/mach-sa1100/Makefile
@@ -11,6 +11,9 @@ obj-$(CONFIG_SA1100_ASSABET)		+= assabet.o
 obj-$(CONFIG_ASSABET_NEPONSET)		+= neponset.o
 obj-$(CONFIG_SA1100_COLLIE)		+= collie.o
 obj-$(CONFIG_SA1100_H3600)		+= h3600.o h3xxx.o
+obj-m					+= h3xxx-sleeve.o ipaq-sleeve-bus.o \
+					   ipaq-sleeve-cf.o ipaq-sleeve-dual.o
+
 obj-$(CONFIG_SA1100_JORNADA720)		+= jornada720.o
 obj-$(CONFIG_SA1100_JORNADA720_SSP)	+= jornada720_ssp.o
 
diff --git a/arch/arm/mach-sa1100/assabet.c b/arch/arm/mach-sa1100/assabet.c
index 2b833aa0212b..3d8aeda35e1b 100644
--- a/arch/arm/mach-sa1100/assabet.c
+++ b/arch/arm/mach-sa1100/assabet.c
@@ -46,6 +46,36 @@
 
 #include "generic.h"
 
+enum {
+	/* System Configuration Register flags */
+	SCR_SDRAM_LOW	= BIT(2),  /* SDRAM size (low bit) */
+	SCR_SDRAM_HIGH	= BIT(3),  /* SDRAM size (high bit) */
+	SCR_FLASH_LOW	= BIT(4),  /* Flash size (low bit) */
+	SCR_FLASH_HIGH	= BIT(5),  /* Flash size (high bit) */
+	SCR_GFX		= BIT(8),  /* Graphics Accelerator (0=present) */
+	SCR_SA1111	= BIT(9),  /* Neponset (0=present) */
+
+	/* GPIOs (bitmasks) for which the generic definition doesn't say much */
+	GPIO_RADIO_IRQ		= GPIO_GPIO(14), /* Radio interrupt request  */
+	GPIO_PS_MODE_SYNC	= GPIO_GPIO(16), /* Power supply mode/sync   */
+	GPIO_STEREO_64FS_CLK	= GPIO_GPIO(19), /* SSP UDA1341 clock input  */
+	GPIO_GFX_IRQ		= GPIO_GPIO(24), /* Graphics IRQ */
+	GPIO_BATT_LOW		= GPIO_GPIO(26), /* Low battery */
+	GPIO_SYS_CLK		= GPIO_GPIO(27),
+};
+
+#ifdef CONFIG_ASSABET_NEPONSET
+#define machine_has_neponset()  ((SCR_value & SCR_SA1111) == 0)
+#else
+#define machine_has_neponset()	(0)
+#endif
+
+/* The build phase of the hardware.  This isn't detectable */
+#define assabet_phase()		4
+
+#define ASSABET_BCR_BASE  0xf1000000
+#define ASSABET_BCR       IOMEM(ASSABET_BCR_BASE)
+
 #define ASSABET_BCR_DB1110 \
 	(ASSABET_BCR_SPK_OFF    | \
 	 ASSABET_BCR_LED_GREEN  | ASSABET_BCR_LED_RED   | \
@@ -59,8 +89,7 @@
 	 ASSABET_BCR_CF_BUS_OFF | ASSABET_BCR_STEREO_LB | \
 	 ASSABET_BCR_IRDA_MD0   | ASSABET_BCR_CF_RST)
 
-unsigned long SCR_value = ASSABET_SCR_INIT;
-EXPORT_SYMBOL(SCR_value);
+static unsigned long SCR_value = ~0UL;
 
 static struct gpio_chip *assabet_bcr_gc;
 
@@ -309,30 +338,26 @@ static struct mcp_plat_data assabet_mcp_data = {
 	.codec_pdata	= &assabet_ucb1x00_data,
 };
 
-static void assabet_lcd_set_visual(u32 visual)
-{
-	u_int is_true_color = visual == FB_VISUAL_TRUECOLOR;
+static struct gpiod_lookup_table assabet_platform_gpio_table = {
+	.table = {
+		GPIO_LOOKUP("assabet", 10, "lcd-depth", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("assabet", 11, "lcd-on", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
 
-	if (machine_is_assabet()) {
-#if 1		// phase 4 or newer Assabet's
-		if (is_true_color)
-			ASSABET_BCR_set(ASSABET_BCR_LCD_12RGB);
-		else
-			ASSABET_BCR_clear(ASSABET_BCR_LCD_12RGB);
-#else
-		// older Assabet's
-		if (is_true_color)
-			ASSABET_BCR_clear(ASSABET_BCR_LCD_12RGB);
-		else
-			ASSABET_BCR_set(ASSABET_BCR_LCD_12RGB);
-#endif
-	}
+static struct gpio_desc *gpio_lcd_depth;
+static struct gpio_desc *gpio_lcd_on;
+
+static void assabet_set_visual(u32 visual)
+{
+	gpiod_set_value(gpio_lcd_depth, visual == FB_VISUAL_TRUECOLOR);
 }
 
 #ifndef ASSABET_PAL_VIDEO
 static void assabet_lcd_backlight_power(int on)
 {
-	if (on)
+	if (on && 0)
 		ASSABET_BCR_set(ASSABET_BCR_LIGHT_ON);
 	else
 		ASSABET_BCR_clear(ASSABET_BCR_LIGHT_ON);
@@ -345,11 +370,13 @@ static void assabet_lcd_backlight_power(int on)
  */
 static void assabet_lcd_power(int on)
 {
-	if (on) {
-		ASSABET_BCR_set(ASSABET_BCR_LCD_ON);
-		udelay(500);
-	} else
-		ASSABET_BCR_clear(ASSABET_BCR_LCD_ON);
+	if (on && 0) {
+		msleep(500);
+		gpiod_set_value(gpio_lcd_on, 1);
+		msleep(500);
+	} else {
+		gpiod_set_value(gpio_lcd_on, 0);
+	}
 }
 
 /*
@@ -372,7 +399,7 @@ static struct sa1100fb_mach_info lq039q2ds54_info = {
 
 	.backlight_power = assabet_lcd_backlight_power,
 	.lcd_power = assabet_lcd_power,
-	.set_visual = assabet_lcd_set_visual,
+	.set_visual = assabet_set_visual,
 };
 #else
 static void assabet_pal_backlight_power(int on)
@@ -382,7 +409,6 @@ static void assabet_pal_backlight_power(int on)
 
 static void assabet_pal_power(int on)
 {
-	ASSABET_BCR_clear(ASSABET_BCR_LCD_ON);
 }
 
 static struct sa1100fb_mach_info pal_info = {
@@ -398,7 +424,7 @@ static struct sa1100fb_mach_info pal_info = {
 
 	.backlight_power = assabet_pal_backlight_power,
 	.lcd_power = assabet_pal_power,
-	.set_visual = assabet_lcd_set_visual,
+	.set_visual = assabet_set_visual,
 };
 #endif
 
@@ -411,6 +437,22 @@ static struct resource neponset_resources[] = {
 };
 #endif
 
+static struct gpiod_lookup_table assabet_uda1341_gpio_table = {
+	.dev_id = "uda134x-codec",
+	.table = {
+		GPIO_LOOKUP("assabet", 21, "qmute", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
+static struct gpiod_lookup_table assabet_audio_gpio_table = {
+	.dev_id = "assabet-asoc",
+	.table = {
+		GPIO_LOOKUP("assabet", 23, "spkr-shtdn", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
 static struct gpiod_lookup_table assabet_cf_gpio_table = {
 	.dev_id = "sa11x0-pcmcia.1",
 	.table = {
@@ -494,6 +536,7 @@ static const struct gpio_keys_platform_data assabet_keys_pdata = {
 static struct gpiod_lookup_table assabet_uart1_gpio_table = {
 	.dev_id = "sa11x0-uart.1",
 	.table = {
+		GPIO_LOOKUP("assabet", 12, "xcvr-enable", GPIO_ACTIVE_HIGH),
 		GPIO_LOOKUP("assabet", 16, "dtr", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("assabet", 17, "rts", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("assabet", 25, "dcd", GPIO_ACTIVE_LOW),
@@ -516,11 +559,19 @@ static struct gpiod_lookup_table assabet_uart3_gpio_table = {
 
 static void __init assabet_init(void)
 {
+	/* Assabets earler than Phase4 have an inverted signal */
+	if (assabet_phase() < 4)
+		assabet_platform_gpio_table.table[0].flags = GPIO_ACTIVE_LOW;
+
+	gpiod_add_lookup_table(&assabet_uda1341_gpio_table);
+	gpiod_add_lookup_table(&assabet_audio_gpio_table);
+	gpiod_add_lookup_table(&assabet_platform_gpio_table);
+
 	/*
 	 * Ensure that the power supply is in "high power" mode.
 	 */
-	GPSR = GPIO_GPIO16;
-	GPDR |= GPIO_GPIO16;
+	GPSR = GPIO_PS_MODE_SYNC;
+	GPDR |= GPIO_PS_MODE_SYNC;
 
 	/*
 	 * Ensure that these pins are set as outputs and are driving
@@ -532,19 +583,18 @@ static void __init assabet_init(void)
 	GPDR |= GPIO_SSP_TXD | GPIO_SSP_SCLK | GPIO_SSP_SFRM;
 
 	/*
-	 * Also set GPIO27 as an output; this is used to clock UART3
-	 * via the FPGA and as otherwise has no pullups or pulldowns,
-	 * so stop it floating.
+	 * Also set GPIO27 as an output; this can be used supply an
+	 * external clock via the FPGA and GPIO18 to clock UART3 and
+	 * radio. It has no pullups or pulldowns, so stop it floating.
 	 */
-	GPCR = GPIO_GPIO27;
-	GPDR |= GPIO_GPIO27;
+	GPCR = GPIO_SYS_CLK;
+	GPDR |= GPIO_SYS_CLK;
 
 	/*
 	 * Set up registers for sleep mode.
 	 */
-	PWER = PWER_GPIO0;
-	PGSR = 0;
-	PCFR = 0;
+	PGSR = GPIO_GPIO(17);
+	PCFR = PCFR_OPDE | PCFR_FP;
 	PSDR = 0;
 	PPDR |= PPC_TXD3 | PPC_TXD1;
 	PPSR |= PPC_TXD3 | PPC_TXD1;
@@ -579,11 +629,16 @@ static void __init assabet_init(void)
 	gpiod_add_lookup_table(&assabet_leds_gpio_table);
 	gpio_led_register_device(-1, &assabet_leds_pdata);
 
+	gpio_lcd_depth = gpiod_get(NULL, "lcd-depth", GPIOD_OUT_HIGH);
+	gpio_lcd_on = gpiod_get(NULL, "lcd-on", GPIOD_OUT_LOW);
+	if (!IS_ERR(gpio_lcd_depth) && !IS_ERR(gpio_lcd_on)) {
 #ifndef ASSABET_PAL_VIDEO
-	sa11x0_register_lcd(&lq039q2ds54_info);
+		sa11x0_register_lcd(&lq039q2ds54_info);
 #else
-	sa11x0_register_lcd(&pal_video);
+		sa11x0_register_lcd(&pal_video);
 #endif
+	}
+
 	sa11x0_register_mtd(&assabet_flash_data, assabet_flash_resources,
 			    ARRAY_SIZE(assabet_flash_resources));
 	sa11x0_register_mcp(&assabet_mcp_data);
@@ -637,6 +692,11 @@ static void __init get_assabet_scr(void)
 	SCR_value = scr;
 }
 
+static char neponset_cmdline[] __initdata = "console=ttySA0 "
+"cpufreq=221200 rw root=/dev/mtdblock2 "
+"mtdparts=sa1100:512K(boot),1M(kernel),2560K(initrd),4M(root) "
+"load_ramdisk=1 prompt_ramdisk=0 mem=32M noinitrd initrd=0xc0800000,3M profile=2";
+
 static void __init
 fixup_assabet(struct tag *tags, char **cmdline)
 {
@@ -644,28 +704,16 @@ fixup_assabet(struct tag *tags, char **cmdline)
 	map_sa1100_gpio_regs();
 	get_assabet_scr();
 
-	if (machine_has_neponset())
+	if (machine_has_neponset()) {
 		printk("Neponset expansion board detected\n");
-}
-
-
-static void assabet_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
-{
-	if (port->mapbase == _Ser1UTCR0) {
-		if (state)
-			ASSABET_BCR_clear(ASSABET_BCR_RS232EN);
-		else
-			ASSABET_BCR_set(ASSABET_BCR_RS232EN);
+		*cmdline = neponset_cmdline;
 	}
 }
 
-static struct sa1100_port_fns assabet_port_fns __initdata = {
-	.pm		= assabet_uart_pm,
-};
 
 static struct map_desc assabet_io_desc[] __initdata = {
   	{	/* Board Control Register */
-		.virtual	=  0xf1000000,
+		.virtual	= ASSABET_BCR_BASE,
 		.pfn		= __phys_to_pfn(0x12000000),
 		.length		= 0x00100000,
 		.type		= MT_DEVICE
@@ -687,12 +735,12 @@ static void __init assabet_map_io(void)
 	 * Its called GPCLKR0 in my SA1110 manual.
 	 */
 	Ser1SDCR0 |= SDCR0_SUS;
-	MSC1 = (MSC1 & ~0xffff) |
+	pr_info("MSC1 = 0x%08lx -> 0x%08lx\n", MSC1,
+//	MSC1 = (MSC1 & ~0xffff) |
+		(MSC1 & ~0xffff) |
 		MSC_NonBrst | MSC_32BitStMem |
-		MSC_RdAcc(2) | MSC_WrAcc(2) | MSC_Rec(0);
-
-	if (!machine_has_neponset())
-		sa1100_register_uart_fns(&assabet_port_fns);
+		MSC_RdAcc(2) | MSC_WrAcc(2) | MSC_Rec(0)
+		);
 
 	/*
 	 * When Neponset is attached, the first UART should be
@@ -726,7 +774,7 @@ static void __init assabet_init_irq(void)
 	 *
 	 * This must precede any driver calls to BCR_set() or BCR_clear().
 	 */
-	assabet_init_gpio((void *)&ASSABET_BCR, def_val);
+	assabet_init_gpio(ASSABET_BCR, def_val);
 }
 
 MACHINE_START(ASSABET, "Intel-Assabet")
diff --git a/arch/arm/mach-sa1100/clock.c b/arch/arm/mach-sa1100/clock.c
index e8691921c69a..d40ce6b0ae05 100644
--- a/arch/arm/mach-sa1100/clock.c
+++ b/arch/arm/mach-sa1100/clock.c
@@ -12,7 +12,8 @@
 #include <linux/spinlock.h>
 
 #include <mach/hardware.h>
-#include <mach/generic.h>
+
+#include "generic.h"
 
 static const char * const clk_tucr_parents[] = {
 	"clk32768", "clk3686400",
@@ -66,12 +67,12 @@ static const struct clk_init_data clk_gpio27_init_data __initconst = {
  * Derived from the table 8-1 in the SA1110 manual, the MPLL appears to
  * multiply its input rate by 4 x (4 + PPCR).  This calculation gives
  * the exact rate.  The figures given in the table are the rates rounded
- * to 100kHz.  Stick with sa11x0_getspeed() for the time being.
+ * to 100kHz.
  */
 static unsigned long clk_mpll_recalc_rate(struct clk_hw *hw,
 	unsigned long prate)
 {
-	return sa11x0_getspeed(0) * 1000;
+	return prate * 4 * (4 + (PPCR & 15));
 }
 
 static const struct clk_ops clk_mpll_ops = {
@@ -106,6 +107,9 @@ int __init sa11xx_clk_init(void)
 		return PTR_ERR(hw);
 
 	clk_hw_register_clkdev(hw, "OSTIMER0", NULL);
+	clk_hw_register_clkdev(hw, NULL, "sa11x0-uart.1");
+	clk_hw_register_clkdev(hw, NULL, "sa11x0-uart.2");
+	clk_hw_register_clkdev(hw, NULL, "sa11x0-uart.3");
 
 	hw = kzalloc(sizeof(*hw), GFP_KERNEL);
 	if (!hw)
@@ -123,6 +127,8 @@ int __init sa11xx_clk_init(void)
 	clk_hw_register_clkdev(hw, NULL, "sa11x0-pcmcia.1");
 	clk_hw_register_clkdev(hw, NULL, "1800");
 
+	clk_hw_register_clkdev(hw, NULL, "assabet-asoc");
+
 	hw = clk_hw_register_mux(NULL, "tucr-mux", clk_tucr_parents,
 				 ARRAY_SIZE(clk_tucr_parents), 0,
 				 (void __iomem *)&TUCR, FShft(TUCR_TSEL),
diff --git a/arch/arm/mach-sa1100/collie.c b/arch/arm/mach-sa1100/collie.c
index 466d755d5702..ed4792a50569 100644
--- a/arch/arm/mach-sa1100/collie.c
+++ b/arch/arm/mach-sa1100/collie.c
@@ -182,7 +182,7 @@ static u_int collie_uart_get_mctrl(struct uart_port *port)
 	return ret;
 }
 
-static struct sa1100_port_fns collie_port_fns __initdata = {
+static const struct sa1100_port_fns collie_port_fns __initconst = {
 	.set_mctrl	= collie_uart_set_mctrl,
 	.get_mctrl	= collie_uart_get_mctrl,
 };
diff --git a/arch/arm/mach-sa1100/h3600.c b/arch/arm/mach-sa1100/h3600.c
index 1cfc0b1fa41c..2b26a5c76e13 100644
--- a/arch/arm/mach-sa1100/h3600.c
+++ b/arch/arm/mach-sa1100/h3600.c
@@ -9,6 +9,7 @@
 #include <linux/init.h>
 #include <linux/kernel.h>
 #include <linux/gpio.h>
+#include <linux/platform_device.h>
 
 #include <video/sa1100fb.h>
 
@@ -114,8 +115,59 @@ static void __init h3600_map_io(void)
 	h3xxx_map_io();
 }
 
+static struct gpiod_lookup_table h3600_sleeve_gpio_table = {
+	.dev_id = "ipaq-h3xxx-sleeve",
+	.table = {
+		GPIO_LOOKUP("gpio", H3600_GPIO_OPT_DET,
+			    "option-detect", GPIO_ACTIVE_LOW),
+		GPIO_LOOKUP("htc-egpio", 2, "option-reset", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 4, "nvram-on", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 5, "option-on", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
+static struct gpiod_lookup_table h3600_audio_clk_gpio_table = {
+	.dev_id = "ipaq-audio-clk",
+	.table = {
+		GPIO_LOOKUP("gpio", 12, "clk0", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("gpio", 13, "clk1", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
+static struct platform_device_info h3600_audio_clk_device = {
+	.name = "ipaq-audio-clk",
+	.id = -1,
+};
+
+static struct gpiod_lookup_table h3600_uda134x_gpio_table = {
+	.dev_id = "uda134x-codec",
+	.table = {
+		GPIO_LOOKUP("htc-egpio", 12, "qmute", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
+static struct gpiod_lookup_table h3600_asoc_gpio_table = {
+	.dev_id = "h3xxx-asoc",
+	.table = {
+		GPIO_LOOKUP("htc-egpio", 3, "audio-reset", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 10, "amp-power", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-egpio", 11, "audio-power", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
 static void __init h3600_mach_init(void)
 {
+	gpiod_add_lookup_table(&h3600_sleeve_gpio_table);
+	gpiod_add_lookup_table(&h3600_audio_clk_gpio_table);
+	gpiod_add_lookup_table(&h3600_uda134x_gpio_table);
+	gpiod_add_lookup_table(&h3600_asoc_gpio_table);
+
+	platform_device_register_full(&h3600_audio_clk_device);
+
 	h3xxx_mach_init();
 
 	sa11x0_register_lcd(&h3600_lcd_info);
diff --git a/arch/arm/mach-sa1100/h3xxx-sleeve.c b/arch/arm/mach-sa1100/h3xxx-sleeve.c
new file mode 100644
index 000000000000..8aef18062e40
--- /dev/null
+++ b/arch/arm/mach-sa1100/h3xxx-sleeve.c
@@ -0,0 +1,486 @@
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/ipaq-micro.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/ipaq-sleeve.h>
+#include <asm/unaligned.h>
+
+#define SLEEVE_NVRAM_POWER_MS 5
+
+enum {
+	MAX_HEADER = 256,
+	TYPE_I = 1,
+	TYPE_II = 2,
+	NO_RESET = 0xffff,
+};
+
+struct ipaq_sleeve_id {
+	struct ipaq_option_id id;
+	const char *description;
+	u8 version;
+	u8 extended_battery;
+	u16 reset_width;
+	u16 reset_wait;
+	u16 skt_type[2];
+	u8 opt_on_ctl;
+	u8 nvram[256];
+};
+
+struct opt_info {
+	struct device *dev;
+	struct delayed_work detect_work;
+	bool detect_disabled;
+	bool sleeve_changed;
+	bool sleeve_present;
+	bool sleeve_wakeup;
+	struct gpio_desc *detect;
+	struct gpio_desc *nvram_on;
+	struct gpio_desc *opt_on;
+	struct gpio_desc *opt_reset;
+
+	struct ipaq_sleeve_id id;
+};
+
+static int ipaq_option_spi_read(void *buf, unsigned int addr, size_t len)
+{
+	struct device *mdev = bus_find_device_by_name(&platform_bus_type, NULL,
+						      "ipaq-h3xxx-micro");
+	struct ipaq_micro *micro;
+	struct ipaq_micro_msg msg;
+
+	memset(buf, 0, len);
+
+	if (!mdev || !mdev->driver)
+		return -EINVAL;
+
+	micro = dev_get_drvdata(mdev);
+	if (!micro)
+		return -EINVAL;
+
+	memset(&msg, 0, sizeof(msg));
+
+	while (len) {
+		size_t this_len = len;
+
+		if (this_len > 8)
+			this_len = 8;
+
+		msg.id = MSG_SPI_READ;
+		msg.tx_len = 3;
+		msg.tx_data[0] = addr;
+		msg.tx_data[1] = addr >> 8;
+		msg.tx_data[2] = this_len;
+
+		ipaq_micro_tx_msg_sync(micro, &msg);
+
+		memcpy(buf, msg.rx_data, msg.rx_len);
+
+		buf += msg.rx_len;
+		len -= msg.rx_len;
+		addr += msg.rx_len;
+	}
+
+	return 0;
+}
+
+static char *ipaq_option_get_string(u8 **ptr, u8 *end)
+{
+	u8 *p = *ptr;
+	unsigned int i;
+
+	for (i = 0; p[i] && &p[i] < end; i++)
+		if (!isprint(p[i]) || &p[i] == end)
+			return NULL;
+
+	*ptr = p + i + 1;
+
+	return kstrndup(p, i, GFP_KERNEL);
+}
+
+static u8 *ipaq_option_get_ptr(u8 **ptr, u8 *end, size_t size)
+{
+	u8 *p = *ptr;
+
+	if (p - end < size)
+		return NULL;
+
+	*ptr += size;
+
+	return p;
+}
+
+static u8 ipaq_option_get_u8(u8 **ptr, u8 *end)
+{
+	u8 *p = ipaq_option_get_ptr(ptr, end, 1);
+	return p ? *p : 0;
+}
+
+static u16 ipaq_option_get_le16(u8 **ptr, u8 *end)
+{
+	u8 *p = ipaq_option_get_ptr(ptr, end, 2);
+	return p ? get_unaligned_le16(p) : 0;
+}
+
+static u32 ipaq_option_get_le32(u8 **ptr, u8 *end)
+{
+	u8 *p = ipaq_option_get_ptr(ptr, end, 4);
+	return p ? get_unaligned_le32(p) : 0;
+}
+
+static int ipaq_option_read_id(struct opt_info *opt, struct ipaq_sleeve_id *id)
+{
+	unsigned int length;
+	u8 header[5], *ptr, *end;
+	int ret;
+
+	/* Enable the CF bus */
+	gpiod_set_value_cansleep(opt->nvram_on, 1);
+
+	/* Wait for power to stabilise */
+	msleep(SLEEVE_NVRAM_POWER_MS);
+
+	/* Read the first few bytes from the NVRAM */
+	ret = ipaq_option_spi_read(header, 0, sizeof(header));
+	if (ret)
+		goto err;
+
+	ptr = header;
+	end = header + sizeof(header);
+	if (ipaq_option_get_u8(&ptr, end) != 0xaa) {
+		dev_err(opt->dev, "invalid ID structure\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	length = ipaq_option_get_le32(&ptr, end);
+	if (length < sizeof(header) || length >= MAX_HEADER - 1) {
+		dev_err(opt->dev, "invalid ID length\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* Read the full NVRAM contents */
+	ret = ipaq_option_spi_read(id->nvram, 0, length + 1);
+	if (ret)
+		goto err;
+
+	/* reset the pointer */
+	ptr = id->nvram + (ptr - header);
+	end = ptr + length + 1;
+
+	id->version = ipaq_option_get_u8(&ptr, end);
+	id->id.vendor = ipaq_option_get_le16(&ptr, end);
+	id->id.device = ipaq_option_get_le16(&ptr, end);
+
+	id->description = ipaq_option_get_string(&ptr, end);
+	if (!id->description) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	ipaq_option_get_ptr(&ptr, end, 2); /* two reserved bytes */
+	id->extended_battery = ipaq_option_get_u8(&ptr, end);
+	ipaq_option_get_ptr(&ptr, end, 2); /* two reserved bytes */
+	id->reset_width = ipaq_option_get_le16(&ptr, end);
+	id->reset_wait = ipaq_option_get_le16(&ptr, end);
+	ipaq_option_get_le16(&ptr, end); /* bootstrap */
+	ipaq_option_get_le16(&ptr, end); /* OEM table */
+
+	if (id->version == TYPE_II) {
+		id->skt_type[0] = ipaq_option_get_le16(&ptr, end);
+		id->skt_type[1] = ipaq_option_get_le16(&ptr, end);
+		id->opt_on_ctl = ipaq_option_get_u8(&ptr, end);
+	} else {
+		id->skt_type[0] = 0;
+		id->skt_type[1] = 0;
+		id->opt_on_ctl = 1;
+	}
+
+	if (ipaq_option_get_le32(&ptr, end) != 0x0f0f0f0f) {
+		dev_err(opt->dev, "missing ID terminator\n");
+		ret = -EINVAL;
+		goto err;
+	}
+
+ err:
+	gpiod_set_value_cansleep(opt->nvram_on, 0);
+	return ret;
+}
+
+static void __ipaq_option_power_on(struct opt_info *opt, bool noirq)
+{
+	gpiod_set_value(opt->opt_reset, 1);
+	gpiod_set_value(opt->opt_on, 1);
+
+	if (opt->id.reset_wait != NO_RESET) {
+		if (noirq)
+			mdelay(opt->id.reset_width);
+		else
+			msleep(opt->id.reset_width);
+	}
+
+	gpiod_set_value(opt->opt_reset, 0);
+
+	if (opt->id.reset_wait != NO_RESET) {
+		if (noirq)
+			mdelay(opt->id.reset_wait);
+		else
+			msleep(opt->id.reset_wait);
+	}
+}
+
+static void ipaq_option_power_on(struct opt_info *opt)
+{
+	__ipaq_option_power_on(opt, false);
+}
+
+static void ipaq_option_power_off(struct opt_info *opt, bool noirq)
+{
+	gpiod_set_value(opt->opt_reset, 1);
+	gpiod_set_value(opt->opt_on, 0);
+}
+
+static void ipaq_option_insert(struct opt_info *opt)
+{
+	int ret;
+
+	ret = ipaq_option_read_id(opt, &opt->id);
+	if (ret)
+		return;
+
+	dev_info(opt->dev, "%s sleeve inserted\n", opt->id.description);
+
+	if (opt->id.opt_on_ctl)
+		ipaq_option_power_on(opt);
+
+	if (!opt->sleeve_present)
+		ipaq_option_device_add(opt->dev, opt->id.id,
+				       opt->id.description);
+	opt->sleeve_present = true;
+}
+
+static void ipaq_option_remove(struct opt_info *opt)
+{
+	if (opt->sleeve_present)
+		ipaq_option_device_del();
+	opt->sleeve_present = false;
+
+	ipaq_option_power_off(opt, false);
+
+	kfree(opt->id.description);
+	memset(&opt->id, 0, sizeof(opt->id));
+}
+
+static void ipaq_option_work(struct work_struct *work)
+{
+	struct opt_info *opt = container_of(work, struct opt_info, detect_work.work);
+	int present = gpiod_get_value_cansleep(opt->detect);
+
+	if (opt->sleeve_changed)
+		ipaq_option_remove(opt);
+
+	if (present)
+		ipaq_option_insert(opt);
+	else
+		ipaq_option_remove(opt);
+}
+
+static irqreturn_t ipaq_option_irq(int irq, void *data)
+{
+	struct opt_info *opt = data;
+
+	dev_info(opt->dev, "irq: option pack: %u\n", gpiod_get_value(opt->detect));
+
+	if (!opt->detect_disabled)
+		mod_delayed_work(system_wq, &opt->detect_work,
+				 msecs_to_jiffies(100));
+
+	return IRQ_HANDLED;
+}
+
+static int ipaq_h3xxx_sleeve_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct opt_info *opt;
+	int irq, ret;
+
+	opt = devm_kzalloc(dev, sizeof(*opt), GFP_KERNEL);
+	if (!opt)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, opt);
+	opt->dev = dev;
+	INIT_DELAYED_WORK(&opt->detect_work, ipaq_option_work);
+
+	opt->nvram_on = devm_gpiod_get(dev, "nvram-on", GPIOD_OUT_LOW);
+	if (IS_ERR(opt->nvram_on))
+		return PTR_ERR(opt->nvram_on);
+
+	opt->opt_on = devm_gpiod_get(dev, "option-on", GPIOD_OUT_LOW);
+	if (IS_ERR(opt->opt_on))
+		return PTR_ERR(opt->opt_on);
+
+	opt->opt_reset = devm_gpiod_get(dev, "option-reset", GPIOD_OUT_LOW);
+	if (IS_ERR(opt->opt_reset))
+		return PTR_ERR(opt->opt_reset);
+
+	opt->detect = devm_gpiod_get_optional(dev, "option-detect", GPIOD_IN);
+	if (IS_ERR(opt->detect))
+		return PTR_ERR(opt->detect);
+
+	if (opt->detect) {
+		irq = gpiod_to_irq(opt->detect);
+		if (irq < 0)
+			return irq;
+
+		ret = devm_request_irq(dev, irq, ipaq_option_irq,
+					IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+					dev_name(dev), opt);
+		if (ret)
+			return ret;
+	}
+
+	schedule_delayed_work(&opt->detect_work, 0);
+	flush_delayed_work(&opt->detect_work);
+
+	device_set_wakeup_capable(dev, true);
+	device_set_wakeup_enable(dev, false);
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_remove(struct platform_device *pdev)
+{
+	struct opt_info *opt = platform_get_drvdata(pdev);
+
+	opt->detect_disabled = true;
+	cancel_delayed_work_sync(&opt->detect_work);
+
+	ipaq_option_remove(opt);
+
+	return 0;
+}
+
+static int ipaq_h3xxx_check_wakeup(struct device *dev, void *data)
+{
+	if (device_may_wakeup(dev))
+		return 1;
+
+	return device_for_each_child(dev, data, ipaq_h3xxx_check_wakeup);
+}
+
+static int ipaq_h3xxx_sleeve_suspend(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+	int may_wakeup;
+
+	opt->detect_disabled = true;
+	cancel_delayed_work_sync(&opt->detect_work);
+
+	/* Disable sleeve power if device isn't a wakeup source */
+	may_wakeup = device_for_each_child(dev, NULL, ipaq_h3xxx_check_wakeup);
+
+	opt->sleeve_wakeup = may_wakeup;
+
+	if (device_may_wakeup(dev))
+		enable_irq_wake(gpiod_to_irq(opt->detect));
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_suspend_noirq(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+
+	if (!opt->sleeve_wakeup && opt->sleeve_present) {
+		ipaq_option_power_off(opt, true);
+
+		/*
+		 * Give the power time to collapse before bringing the
+		 * reset signal to logic 0.
+		 */
+		mdelay(1);
+		gpiod_set_raw_value(opt->opt_reset, 0);
+	}
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_resume_noirq(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+
+	if (!opt->sleeve_wakeup && gpiod_get_value(opt->detect)) {
+		/*
+		 * We can't check the ID of the sleeve here, so assume that
+		 * it is the same sleeve inserted, and power it up.  This
+		 * avoids an eject/insert cycle on any inserted PCMCIA card.
+		 */
+		__ipaq_option_power_on(opt, true);
+	}
+
+	return 0;
+}
+
+static int ipaq_h3xxx_sleeve_resume(struct device *dev)
+{
+	struct opt_info *opt = dev_get_drvdata(dev);
+	struct ipaq_sleeve_id id;
+	int ret;
+
+	if (device_may_wakeup(dev))
+		disable_irq_wake(gpiod_to_irq(opt->detect));
+
+	if (opt->sleeve_present && gpiod_get_value_cansleep(opt->detect)) {
+		memset(&id, 0, sizeof(id));
+		ret = ipaq_option_read_id(opt, &id);
+		if (ret) {
+			dev_warn(opt->dev, "unable to read sleeve id: %d\n",
+				 ret);
+			opt->sleeve_changed = true;
+			goto finish;
+		}
+
+		if (memcmp(opt->id.nvram, id.nvram, sizeof(id.nvram))) {
+			/* sleeve changed */
+			dev_warn(opt->dev, "sleeve changed\n");
+			opt->sleeve_changed = true;
+		} else {
+			opt->detect_disabled = false;
+			return 0;
+		}
+	}
+
+finish:
+	opt->detect_disabled = false;
+	schedule_delayed_work(&opt->detect_work, 0);
+	flush_delayed_work(&opt->detect_work);
+
+	return 0;
+}
+
+static struct dev_pm_ops ipaq_h3xxx_sleeve_pm = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(ipaq_h3xxx_sleeve_suspend_noirq,
+				      ipaq_h3xxx_sleeve_resume_noirq)
+	SET_SYSTEM_SLEEP_PM_OPS(ipaq_h3xxx_sleeve_suspend,
+				ipaq_h3xxx_sleeve_resume)
+};
+
+static struct platform_driver ipaq_h3xxx_sleeve = {
+	.driver = {
+		.name = "ipaq-h3xxx-sleeve",
+		.pm = &ipaq_h3xxx_sleeve_pm,
+	},
+	.probe = ipaq_h3xxx_sleeve_probe,
+	.remove = ipaq_h3xxx_sleeve_remove,
+};
+
+module_platform_driver(ipaq_h3xxx_sleeve);
+
+MODULE_ALIAS("platform:ipaq-h3xxx-sleeve");
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-sa1100/h3xxx.c b/arch/arm/mach-sa1100/h3xxx.c
index d685f03f51f3..9a44a31626b9 100644
--- a/arch/arm/mach-sa1100/h3xxx.c
+++ b/arch/arm/mach-sa1100/h3xxx.c
@@ -83,45 +83,16 @@ static struct resource h3xxx_flash_resource =
 /*
  * H3xxx uart support
  */
-static void h3xxx_uart_pm(struct uart_port *port, u_int state, u_int oldstate)
-{
-	if (port->mapbase == _Ser3UTCR0) {
-		if (!gpio_request(H3XXX_EGPIO_RS232_ON, "RS232 transceiver")) {
-			gpio_direction_output(H3XXX_EGPIO_RS232_ON, !state);
-			gpio_free(H3XXX_EGPIO_RS232_ON);
-		} else {
-			pr_err("%s: can't request H3XXX_EGPIO_RS232_ON\n",
-				__func__);
-		}
-	}
-}
-
-/*
- * Enable/Disable wake up events for this serial port.
- * Obviously, we only support this on the normal COM port.
- */
-static int h3xxx_uart_set_wake(struct uart_port *port, u_int enable)
-{
-	int err = -EINVAL;
-
-	if (port->mapbase == _Ser3UTCR0) {
-		if (enable)
-			PWER |= PWER_GPIO23 | PWER_GPIO25; /* DCD and CTS */
-		else
-			PWER &= ~(PWER_GPIO23 | PWER_GPIO25); /* DCD and CTS */
-		err = 0;
-	}
-	return err;
-}
-
-static struct sa1100_port_fns h3xxx_port_fns __initdata = {
-	.pm		= h3xxx_uart_pm,
-	.set_wake	= h3xxx_uart_set_wake,
+static const struct sa1100_port_fns h3xxx_port_fns __initconst = {
+	.wake		= {
+		[0] = SA11X0_WAKE_CTS | SA11X0_WAKE_DCD,
+	},
 };
 
 static struct gpiod_lookup_table h3xxx_uart3_gpio_table = {
 	.dev_id = "sa11x0-uart.3",
 	.table = {
+		GPIO_LOOKUP("htc-egpio", 7, "xcvr-enable", GPIO_ACTIVE_HIGH),
 		GPIO_LOOKUP("gpio", H3XXX_GPIO_COM_DCD, "dcd", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("gpio", H3XXX_GPIO_COM_CTS, "cts", GPIO_ACTIVE_LOW),
 		GPIO_LOOKUP("gpio", H3XXX_GPIO_COM_RTS, "rts", GPIO_ACTIVE_LOW),
@@ -212,10 +183,16 @@ struct platform_device h3xxx_micro_asic = {
 	.num_resources = ARRAY_SIZE(h3xxx_micro_resources),
 };
 
+static struct platform_device h3xxx_sleeve = {
+	.name = "ipaq-h3xxx-sleeve",
+	.id = -1,
+};
+
 static struct platform_device *h3xxx_devices[] = {
 	&h3xxx_egpio,
 	&h3xxx_keys,
 	&h3xxx_micro_asic,
+	&h3xxx_sleeve,
 };
 
 static struct gpiod_lookup_table h3xxx_pcmcia_gpio_table = {
@@ -233,9 +210,22 @@ static struct gpiod_lookup_table h3xxx_pcmcia_gpio_table = {
 	},
 };
 
+static struct gpiod_lookup_table h3xxx_pcmcia_socket0_gpio_table = {
+	.dev_id = "sa11x0-pcmcia",
+	.table = {
+		GPIO_LOOKUP("gpio", H3XXX_GPIO_PCMCIA_CD0,
+			    "detect", GPIO_ACTIVE_LOW),
+		GPIO_LOOKUP("gpio", H3XXX_GPIO_PCMCIA_IRQ0,
+			    "ready", GPIO_ACTIVE_HIGH),
+		GPIO_LOOKUP("htc-gpio", 1, "reset", GPIO_ACTIVE_HIGH),
+		{ },
+	},
+};
+
 void __init h3xxx_mach_init(void)
 {
 	gpiod_add_lookup_table(&h3xxx_pcmcia_gpio_table);
+	gpiod_add_lookup_table(&h3xxx_pcmcia_socket0_gpio_table);
 	gpiod_add_lookup_table(&h3xxx_uart3_gpio_table);
 	sa1100_register_uart_fns(&h3xxx_port_fns);
 	sa11x0_register_mtd(&h3xxx_flash_data, &h3xxx_flash_resource, 1);
diff --git a/arch/arm/mach-sa1100/include/mach/assabet.h b/arch/arm/mach-sa1100/include/mach/assabet.h
index 641a961653af..2022fcae7791 100644
--- a/arch/arm/mach-sa1100/include/mach/assabet.h
+++ b/arch/arm/mach-sa1100/include/mach/assabet.h
@@ -13,31 +13,8 @@
 #ifndef __ASM_ARCH_ASSABET_H
 #define __ASM_ARCH_ASSABET_H
 
-
-/* System Configuration Register flags */
-
-#define ASSABET_SCR_SDRAM_LOW	(1<<2)	/* SDRAM size (low bit) */
-#define ASSABET_SCR_SDRAM_HIGH	(1<<3)	/* SDRAM size (high bit) */
-#define ASSABET_SCR_FLASH_LOW	(1<<4)	/* Flash size (low bit) */
-#define ASSABET_SCR_FLASH_HIGH	(1<<5)	/* Flash size (high bit) */
-#define ASSABET_SCR_GFX		(1<<8)	/* Graphics Accelerator (0 = present) */
-#define ASSABET_SCR_SA1111	(1<<9)	/* Neponset (0 = present) */
-
-#define ASSABET_SCR_INIT	-1
-
-extern unsigned long SCR_value;
-
-#ifdef CONFIG_ASSABET_NEPONSET
-#define machine_has_neponset()  ((SCR_value & ASSABET_SCR_SA1111) == 0)
-#else
-#define machine_has_neponset()	(0)
-#endif
-
 /* Board Control Register */
 
-#define ASSABET_BCR_BASE  0xf1000000
-#define ASSABET_BCR (*(volatile unsigned int *)(ASSABET_BCR_BASE))
-
 #define ASSABET_BCR_CF_PWR	(1<<0)	/* Compact Flash Power (1 = 3.3v, 0 = off) */
 #define ASSABET_BCR_CF_RST	(1<<1)	/* Compact Flash Reset (1 = power up reset) */
 #define ASSABET_BCR_NGFX_RST	(1<<1)	/* Graphics Accelerator Reset (0 = hold reset) */
@@ -75,25 +52,4 @@ extern void assabet_uda1341_reset(int set);
 #define ASSABET_BCR_set(x)	ASSABET_BCR_frob((x), (x))
 #define ASSABET_BCR_clear(x)	ASSABET_BCR_frob((x), 0)
 
-#define ASSABET_BSR_BASE	0xf1000000
-#define ASSABET_BSR (*(volatile unsigned int*)(ASSABET_BSR_BASE))
-
-#define ASSABET_BSR_RS232_VALID	(1 << 24)
-#define ASSABET_BSR_COM_DCD	(1 << 25)
-#define ASSABET_BSR_COM_CTS	(1 << 26)
-#define ASSABET_BSR_COM_DSR	(1 << 27)
-#define ASSABET_BSR_RAD_CTS	(1 << 28)
-#define ASSABET_BSR_RAD_DSR	(1 << 29)
-#define ASSABET_BSR_RAD_DCD	(1 << 30)
-#define ASSABET_BSR_RAD_RI	(1 << 31)
-
-
-/* GPIOs (bitmasks) for which the generic definition doesn't say much */
-#define ASSABET_GPIO_RADIO_IRQ		GPIO_GPIO (14)	/* Radio interrupt request  */
-#define ASSABET_GPIO_PS_MODE_SYNC	GPIO_GPIO (16)	/* Power supply mode/sync   */
-#define ASSABET_GPIO_STEREO_64FS_CLK	GPIO_GPIO (19)	/* SSP UDA1341 clock input  */
-#define ASSABET_GPIO_GFX_IRQ		GPIO_GPIO (24)	/* Graphics IRQ */
-#define ASSABET_GPIO_BATT_LOW		GPIO_GPIO (26)	/* Low battery */
-#define ASSABET_GPIO_RCLK		GPIO_GPIO (26)	/* CCLK/2  */
-
 #endif
diff --git a/arch/arm/mach-sa1100/include/mach/neponset.h b/arch/arm/mach-sa1100/include/mach/neponset.h
deleted file mode 100644
index 18411cfdfe8e..000000000000
--- a/arch/arm/mach-sa1100/include/mach/neponset.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0 */
-/*
- * arch/arm/mach-sa1100/include/mach/neponset.h
- *
- * Created 2000/06/05 by Nicolas Pitre <nico@fluxnic.net>
- *
- * This file contains the hardware specific definitions for Assabet
- * Only include this file from SA1100-specific files.
- *
- * 2000/05/23 John Dorsey <john+@cs.cmu.edu>
- *      Definitions for Neponset added.
- */
-#ifndef __ASM_ARCH_NEPONSET_H
-#define __ASM_ARCH_NEPONSET_H
-
-/*
- * Neponset definitions: 
- */
-#define NCR_GP01_OFF		(1<<0)
-#define NCR_TP_PWR_EN		(1<<1)
-#define NCR_MS_PWR_EN		(1<<2)
-#define NCR_ENET_OSC_EN		(1<<3)
-#define NCR_SPI_KB_WK_UP	(1<<4)
-#define NCR_A0VPP		(1<<5)
-#define NCR_A1VPP		(1<<6)
-
-void neponset_ncr_frob(unsigned int, unsigned int);
-#define neponset_ncr_set(v)	neponset_ncr_frob(0, v)
-#define neponset_ncr_clear(v)	neponset_ncr_frob(v, 0)
-
-#endif
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-bus.c b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
new file mode 100644
index 000000000000..31299589d4a9
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-bus.c
@@ -0,0 +1,141 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define to_ipaq_option_driver(x) \
+	container_of(x, struct ipaq_option_driver, driver)
+
+static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	return snprintf(buf, PAGE_SIZE, "%04x\n", idev->id.vendor);
+}
+static DEVICE_ATTR_RO(vendor);
+
+static ssize_t device_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	return snprintf(buf, PAGE_SIZE, "%04x\n", idev->id.device);
+}
+static DEVICE_ATTR_RO(device);
+
+static ssize_t description_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	return snprintf(buf, PAGE_SIZE, "%s\n", idev->description);
+}
+static DEVICE_ATTR_RO(description);
+
+static struct attribute *ipaq_option_sleeve_attrs[] = {
+	&dev_attr_description.attr,
+	&dev_attr_device.attr,
+	&dev_attr_vendor.attr,
+	NULL,
+};
+
+static const struct attribute_group ipaq_option_sleeve_dev_group = {
+	.attrs = ipaq_option_sleeve_attrs,
+};
+
+static const struct attribute_group *ipaq_option_sleeve_dev_groups[] = {
+	&ipaq_option_sleeve_dev_group,
+	NULL,
+};
+
+static int ipaq_option_sleeve_match(struct device *dev, struct device_driver *drv)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	struct ipaq_option_driver *idrv = to_ipaq_option_driver(drv);
+	const struct ipaq_option_id *id;
+
+	for (id = idrv->id_table; id->vendor || id->device; id++)
+		if (id->vendor == idev->id.vendor &&
+		    id->device == idev->id.device)
+			return 1;
+
+	return 0;
+}
+
+static int ipaq_option_sleeve_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+
+	if (!dev)
+		return -ENODEV;
+
+	if (add_uevent_var(env, "DESCRIPTION=%s", idev->description) ||
+	    add_uevent_var(env, "MODALIAS=ipaq:v%04Xd%04X",
+			   idev->id.vendor, idev->id.device))
+		return -ENOMEM;
+
+	return 0;
+}
+
+struct bus_type ipaq_option_sleeve_bus = {
+	.name = "ipaq-sleeve",
+	.dev_groups = ipaq_option_sleeve_dev_groups,
+	.match = ipaq_option_sleeve_match,
+	.uevent = ipaq_option_sleeve_uevent,
+};
+EXPORT_SYMBOL_GPL(ipaq_option_sleeve_bus);
+
+static void ipaq_option_release(struct device *dev)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+
+	kfree(idev->description);
+	kfree(idev);
+}
+
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id,
+	const char *description)
+{
+	struct ipaq_option_device *idev;
+
+	idev = kzalloc(sizeof(*idev), GFP_KERNEL);
+	if (!idev)
+		return -ENOMEM;
+
+	idev->description = kstrdup(description, GFP_KERNEL);
+	if (!idev->description) {
+		kfree(idev);
+		return -ENOMEM;
+	}
+
+	idev->id = id;
+	device_initialize(&idev->dev);
+	dev_set_name(&idev->dev, "sleeve");
+	idev->dev.parent = parent;
+	idev->dev.release = ipaq_option_release;
+	idev->dev.bus = &ipaq_option_sleeve_bus;
+
+	return device_add(&idev->dev);
+}
+EXPORT_SYMBOL_GPL(ipaq_option_device_add);
+
+static int ipaq_option_device_remove(struct device *dev, void *data)
+{
+	device_unregister(dev);
+	return 0;
+}
+
+void ipaq_option_device_del(void)
+{
+	bus_for_each_dev(&ipaq_option_sleeve_bus, NULL, NULL,
+			 ipaq_option_device_remove);
+}
+EXPORT_SYMBOL_GPL(ipaq_option_device_del);
+
+static int ipaq_option_sleeve_init(void)
+{
+	return bus_register(&ipaq_option_sleeve_bus);
+}
+core_initcall(ipaq_option_sleeve_init);
+
+static void ipaq_option_sleeve_exit(void)
+{
+	bus_unregister(&ipaq_option_sleeve_bus);
+}
+module_exit(ipaq_option_sleeve_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-cf.c b/arch/arm/mach-sa1100/ipaq-sleeve-cf.c
new file mode 100644
index 000000000000..8f0b8ae8b75c
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-cf.c
@@ -0,0 +1,70 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct ipaq_sleeve_cf_data {
+	struct platform_device *skt;
+};
+
+static int ipaq_sleeve_cf_probe(struct device *dev)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	struct ipaq_sleeve_cf_data *cf;
+	struct platform_device_info pinfo = {
+		.parent = dev,
+		.name = "sa11x0-pcmcia",
+		.id = 0,
+		.data = &idev->id,
+		.size_data = sizeof(idev->id),
+	};
+
+	cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
+	if (!cf)
+		return -ENOMEM;
+
+	cf->skt = platform_device_register_full(&pinfo);
+	if (IS_ERR(cf->skt)) {
+		dev_err(dev, "unable to register pcmcia device: %ld\n",
+			PTR_ERR(cf->skt));
+		return PTR_ERR(cf->skt);
+	}
+
+	dev_set_drvdata(dev, cf);
+
+	return 0;
+}
+
+static int ipaq_sleeve_cf_remove(struct device *dev)
+{
+	struct ipaq_sleeve_cf_data *cf = dev_get_drvdata(dev);
+
+	platform_device_unregister(cf->skt);
+
+	return 0;
+}
+
+static const struct ipaq_option_id ipaq_sleeve_cf_ids[] = {
+	{	/* Compaq CompactFlash sleeve */
+		.vendor = 0x1125,
+		.device = 0xcf11,
+	}, {	/* Compaq single PCMCIA sleeve */
+		.vendor = 0x1125,
+		.device = 0xd7c3,
+	}, { },
+};
+
+static struct ipaq_option_driver ipaq_sleeve_cf = {
+	.id_table = ipaq_sleeve_cf_ids,
+	.driver = {
+		.name = "ipaq-sleeve-cf",
+		.probe = ipaq_sleeve_cf_probe,
+		.remove = ipaq_sleeve_cf_remove,
+	},
+};
+
+module_driver(ipaq_sleeve_cf, ipaq_option_driver_register,
+	ipaq_option_driver_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ipaq, ipaq_sleeve_cf_ids);
diff --git a/arch/arm/mach-sa1100/ipaq-sleeve-dual.c b/arch/arm/mach-sa1100/ipaq-sleeve-dual.c
new file mode 100644
index 000000000000..dd4c3f756885
--- /dev/null
+++ b/arch/arm/mach-sa1100/ipaq-sleeve-dual.c
@@ -0,0 +1,68 @@
+#include <linux/ipaq-sleeve.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+struct ipaq_sleeve_dual_data {
+	struct platform_device *skt;
+};
+
+static int ipaq_sleeve_dual_probe(struct device *dev)
+{
+	struct ipaq_option_device *idev = to_ipaq_option_device(dev);
+	struct ipaq_sleeve_dual_data *cf;
+	struct platform_device_info pinfo = {
+		.parent = dev,
+		.name = "sa11x0-pcmcia",
+		.id = -1,
+	};
+
+	cf = devm_kzalloc(dev, sizeof(*cf), GFP_KERNEL);
+	if (!cf)
+		return -ENOMEM;
+
+	pinfo.data = &idev->id;
+	pinfo.size_data = sizeof(idev->id);
+
+	cf->skt = platform_device_register_full(&pinfo);
+	if (IS_ERR(cf->skt)) {
+		dev_err(dev, "unable to register pcmcia device: %ld\n",
+			PTR_ERR(cf->skt));
+		return PTR_ERR(cf->skt);
+	}
+
+	dev_set_drvdata(dev, cf);
+
+	return 0;
+}
+
+static int ipaq_sleeve_dual_remove(struct device *dev)
+{
+	struct ipaq_sleeve_dual_data *cf = dev_get_drvdata(dev);
+
+	platform_device_unregister(cf->skt);
+
+	return 0;
+}
+
+static const struct ipaq_option_id ipaq_sleeve_dual_ids[] = {
+	{	/* Compaq dual PCMCIA sleeve */
+		.vendor = 0x1125,
+		.device = 0x0001,
+	}, { },
+};
+
+static struct ipaq_option_driver ipaq_sleeve_dual = {
+	.id_table = ipaq_sleeve_dual_ids,
+	.driver = {
+		.name = "ipaq-sleeve-dual",
+		.probe = ipaq_sleeve_dual_probe,
+		.remove = ipaq_sleeve_dual_remove,
+	},
+};
+
+module_driver(ipaq_sleeve_dual, ipaq_option_driver_register,
+	ipaq_option_driver_unregister);
+
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(ipaq, ipaq_sleeve_dual_ids);
diff --git a/arch/arm/mach-sa1100/neponset.c b/arch/arm/mach-sa1100/neponset.c
index 0ef0ebbf31ac..186da44d554e 100644
--- a/arch/arm/mach-sa1100/neponset.c
+++ b/arch/arm/mach-sa1100/neponset.c
@@ -2,10 +2,13 @@
 /*
  * linux/arch/arm/mach-sa1100/neponset.c
  */
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
 #include <linux/err.h>
 #include <linux/gpio/driver.h>
 #include <linux/gpio/gpio-reg.h>
 #include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
 #include <linux/init.h>
 #include <linux/ioport.h>
 #include <linux/irq.h>
@@ -24,7 +27,6 @@
 
 #include <mach/hardware.h>
 #include <mach/assabet.h>
-#include <mach/neponset.h>
 #include <mach/irqs.h>
 
 #define NEP_IRQ_SMC91X	0
@@ -47,6 +49,7 @@
 #define IRR_USAR	(1 << 1)
 #define IRR_SA1111	(1 << 2)
 
+#define NCR_GP01_OFF	BIT(0)
 #define NCR_NGPIO	7
 #define MDM_CTL0_NGPIO	4
 #define MDM_CTL1_NGPIO	6
@@ -75,10 +78,15 @@ static const char *neponset_aud_names[] = {
 
 struct neponset_drvdata {
 	void __iomem *base;
+	struct device *dev;
 	struct platform_device *sa1111;
 	struct platform_device *smc91x;
 	unsigned irq_base;
+	struct irq_domain *irq_domain;
 	struct gpio_chip *gpio[4];
+	struct clk_hw *enet_osc_ck;
+	struct clk_hw *enet_osc_gate;
+	struct clk_lookup *enet_osc_cl;
 };
 
 static struct gpiod_lookup_table neponset_uart1_gpio_table = {
@@ -120,17 +128,17 @@ static struct gpiod_lookup_table neponset_pcmcia_table = {
 
 static struct neponset_drvdata *nep;
 
-void neponset_ncr_frob(unsigned int mask, unsigned int val)
+static void neponset_handle_irq(struct neponset_drvdata *d, int irq)
 {
-	struct neponset_drvdata *n = nep;
-	unsigned long m = mask, v = val;
+	int virq = irq_find_mapping(d->irq_domain, irq);
 
-	if (nep)
-		n->gpio[0]->set_multiple(n->gpio[0], &m, &v);
-	else
-		WARN(1, "nep unset\n");
+	if (!virq) {
+		dev_warn(d->dev, "spurious irq detected, hwirq %d irq %d\n",
+			 irq, virq);
+		return;
+	}
+	generic_handle_irq(virq);
 }
-EXPORT_SYMBOL(neponset_ncr_frob);
 
 /*
  * Install handler for Neponset IRQ.  Note that we have to loop here
@@ -141,12 +149,11 @@ EXPORT_SYMBOL(neponset_ncr_frob);
 static void neponset_irq_handler(struct irq_desc *desc)
 {
 	struct neponset_drvdata *d = irq_desc_get_handler_data(desc);
-	unsigned int irr;
+	unsigned long irr;
+	int irq;
 
 	while (1) {
-		/*
-		 * Acknowledge the parent IRQ.
-		 */
+		// Acknowledge the parent IRQ before reading the IRR.
 		desc->irq_data.chip->irq_ack(&desc->irq_data);
 
 		/*
@@ -157,36 +164,12 @@ static void neponset_irq_handler(struct irq_desc *desc)
 		irr = readb_relaxed(d->base + IRR);
 		irr ^= IRR_ETHERNET | IRR_USAR;
 
+		// If no pending interrupts, then we're done.
 		if ((irr & (IRR_ETHERNET | IRR_USAR | IRR_SA1111)) == 0)
 			break;
 
-		/*
-		 * Since there is no individual mask, we have to
-		 * mask the parent IRQ.  This is safe, since we'll
-		 * recheck the register for any pending IRQs.
-		 */
-		if (irr & (IRR_ETHERNET | IRR_USAR)) {
-			desc->irq_data.chip->irq_mask(&desc->irq_data);
-
-			/*
-			 * Ack the interrupt now to prevent re-entering
-			 * this neponset handler.  Again, this is safe
-			 * since we'll check the IRR register prior to
-			 * leaving.
-			 */
-			desc->irq_data.chip->irq_ack(&desc->irq_data);
-
-			if (irr & IRR_ETHERNET)
-				generic_handle_irq(d->irq_base + NEP_IRQ_SMC91X);
-
-			if (irr & IRR_USAR)
-				generic_handle_irq(d->irq_base + NEP_IRQ_USAR);
-
-			desc->irq_data.chip->irq_unmask(&desc->irq_data);
-		}
-
-		if (irr & IRR_SA1111)
-			generic_handle_irq(d->irq_base + NEP_IRQ_SA1111);
+		for_each_set_bit(irq, &irr, 8)
+			neponset_handle_irq(d, irq);
 	}
 }
 
@@ -202,6 +185,44 @@ static struct irq_chip nochip = {
 	.irq_unmask = nochip_noop,
 };
 
+static int neponset_irqdomain_map(struct irq_domain *d, unsigned int irq,
+	irq_hw_number_t hwirq)
+{
+	irq_set_chip_and_handler(irq, &nochip, handle_simple_irq);
+	irq_clear_status_flags(irq, IRQ_NOREQUEST);
+
+	return 0;
+}
+
+static const struct irq_domain_ops nep_irqdomain_ops = {
+	.map = neponset_irqdomain_map,
+	.xlate = irq_domain_xlate_onetwocell,
+};
+
+static int neponset_init_irr(struct device *dev, struct neponset_drvdata *d, int irq)
+{
+	int ret;
+
+	ret = irq_alloc_descs(-1, IRQ_BOARD_START, NEP_IRQ_NR, -1);
+	if (ret <= 0) {
+		dev_err(dev, "unable to allocate %u irqs: %d\n",
+			NEP_IRQ_NR, ret);
+		if (ret == 0)
+			ret = -ENOMEM;
+		return ret;
+	}
+
+	d->irq_base = ret;
+
+	d->irq_domain = irq_domain_add_simple(NULL, NEP_IRQ_NR, ret,
+					      &nep_irqdomain_ops, d);
+
+	irq_set_irq_type(irq, IRQ_TYPE_EDGE_RISING);
+	irq_set_chained_handler_and_data(irq, neponset_irq_handler, d);
+
+	return 0;
+}
+
 static int neponset_init_gpio(struct gpio_chip **gcp,
 	struct device *dev, const char *label, void __iomem *reg,
 	unsigned num, bool in, const char *const * names)
@@ -218,6 +239,38 @@ static int neponset_init_gpio(struct gpio_chip **gcp,
 	return 0;
 }
 
+static int neponset_init_enet(struct device *dev, struct neponset_drvdata *d)
+{
+	struct gpio_desc *gpio;
+	int ret;
+
+	d->enet_osc_ck = clk_hw_register_fixed_rate(dev, "nep_enet_osc",
+						    NULL, 0, 20000000);
+	if (IS_ERR(d->enet_osc_ck))
+		return PTR_ERR(d->enet_osc_ck);
+
+	gpio = gpio_to_desc(d->gpio[0]->base + 3);
+	d->enet_osc_gate = clk_hw_register_gpio_gate(dev, "nep_enet_gate",
+						     "nep_enet_osc", gpio, 0);
+	if (IS_ERR(d->enet_osc_gate)) {
+		ret = PTR_ERR(d->enet_osc_gate);
+		goto err;
+	}
+
+	d->enet_osc_cl = clkdev_hw_create(d->enet_osc_gate, NULL, "smc91x.0");
+	if (!d->enet_osc_cl) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	if (d->enet_osc_ck)
+		clk_hw_unregister_fixed_rate(d->enet_osc_ck);
+	return ret;
+}
+
 static struct sa1111_platform_data sa1111_info = {
 	.disable_devs	= SA1111_DEVID_PS2_MSE,
 };
@@ -266,26 +319,28 @@ static int neponset_probe(struct platform_device *dev)
 
 	irq = ret = platform_get_irq(dev, 0);
 	if (ret < 0)
-		goto err_alloc;
+		goto err;
 
 	nep_res = platform_get_resource(dev, IORESOURCE_MEM, 0);
 	smc91x_res = platform_get_resource(dev, IORESOURCE_MEM, 1);
 	sa1111_res = platform_get_resource(dev, IORESOURCE_MEM, 2);
 	if (!nep_res || !smc91x_res || !sa1111_res) {
 		ret = -ENXIO;
-		goto err_alloc;
+		goto err;
 	}
 
-	d = kzalloc(sizeof(*d), GFP_KERNEL);
+	d = devm_kzalloc(&dev->dev, sizeof(*d), GFP_KERNEL);
 	if (!d) {
 		ret = -ENOMEM;
-		goto err_alloc;
+		goto err;
 	}
 
+	d->dev = &dev->dev;
+
 	d->base = ioremap(nep_res->start, SZ_4K);
 	if (!d->base) {
 		ret = -ENOMEM;
-		goto err_ioremap;
+		goto err;
 	}
 
 	if (readb_relaxed(d->base + WHOAMI) != 0x11) {
@@ -295,27 +350,9 @@ static int neponset_probe(struct platform_device *dev)
 		goto err_id;
 	}
 
-	ret = irq_alloc_descs(-1, IRQ_BOARD_START, NEP_IRQ_NR, -1);
-	if (ret <= 0) {
-		dev_err(&dev->dev, "unable to allocate %u irqs: %d\n",
-			NEP_IRQ_NR, ret);
-		if (ret == 0)
-			ret = -ENOMEM;
+	ret = neponset_init_irr(&dev->dev, d, irq);
+	if (ret < 0)
 		goto err_irq_alloc;
-	}
-
-	d->irq_base = ret;
-
-	irq_set_chip_and_handler(d->irq_base + NEP_IRQ_SMC91X, &nochip,
-		handle_simple_irq);
-	irq_clear_status_flags(d->irq_base + NEP_IRQ_SMC91X, IRQ_NOREQUEST | IRQ_NOPROBE);
-	irq_set_chip_and_handler(d->irq_base + NEP_IRQ_USAR, &nochip,
-		handle_simple_irq);
-	irq_clear_status_flags(d->irq_base + NEP_IRQ_USAR, IRQ_NOREQUEST | IRQ_NOPROBE);
-	irq_set_chip(d->irq_base + NEP_IRQ_SA1111, &nochip);
-
-	irq_set_irq_type(irq, IRQ_TYPE_EDGE_RISING);
-	irq_set_chained_handler_and_data(irq, neponset_irq_handler, d);
 
 	/* Disable GPIO 0/1 drivers so the buttons work on the Assabet */
 	writeb_relaxed(NCR_GP01_OFF, d->base + NCR_0);
@@ -337,6 +374,8 @@ static int neponset_probe(struct platform_device *dev)
 	gpiod_add_lookup_table(&neponset_uart3_gpio_table);
 	gpiod_add_lookup_table(&neponset_pcmcia_table);
 
+	neponset_init_enet(&dev->dev, d);
+
 	/*
 	 * We would set IRQ_GPIO25 to be a wake-up IRQ, but unfortunately
 	 * something on the Neponset activates this IRQ on sleep (eth?)
@@ -370,9 +409,7 @@ static int neponset_probe(struct platform_device *dev)
  err_irq_alloc:
  err_id:
 	iounmap(d->base);
- err_ioremap:
-	kfree(d);
- err_alloc:
+ err:
 	return ret;
 }
 
@@ -386,6 +423,9 @@ static void neponset_remove(struct platform_device *dev)
 	if (!IS_ERR(d->smc91x))
 		platform_device_unregister(d->smc91x);
 
+	clkdev_drop(d->enet_osc_cl);
+	clk_hw_unregister_fixed_rate(d->enet_osc_ck);
+
 	gpiod_remove_lookup_table(&neponset_pcmcia_table);
 	gpiod_remove_lookup_table(&neponset_uart3_gpio_table);
 	gpiod_remove_lookup_table(&neponset_uart1_gpio_table);
@@ -394,7 +434,6 @@ static void neponset_remove(struct platform_device *dev)
 	irq_free_descs(d->irq_base, NEP_IRQ_NR);
 	nep = NULL;
 	iounmap(d->base);
-	kfree(d);
 }
 
 #ifdef CONFIG_PM_SLEEP
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index f793a16cad40..04f727081e23 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -40,6 +40,7 @@ obj-$(CONFIG_COMMON_CLK_ASPEED)		+= clk-aspeed.o
 obj-$(CONFIG_MACH_ASPEED_G6)		+= clk-ast2600.o
 obj-$(CONFIG_ARCH_HIGHBANK)		+= clk-highbank.o
 obj-$(CONFIG_CLK_HSDK)			+= clk-hsdk-pll.o
+obj-$(CONFIG_SA1100_H3600)		+= clk-ipaq.o
 obj-$(CONFIG_COMMON_CLK_K210)		+= clk-k210.o
 obj-$(CONFIG_LMK04832)			+= clk-lmk04832.o
 obj-$(CONFIG_COMMON_CLK_LAN966X)	+= clk-lan966x.o
diff --git a/drivers/clk/clk-gpio.c b/drivers/clk/clk-gpio.c
index 5b114043771d..f53df89940d4 100644
--- a/drivers/clk/clk-gpio.c
+++ b/drivers/clk/clk-gpio.c
@@ -28,26 +28,6 @@
  * parent - fixed parent.  No clk_set_parent support
  */
 
-/**
- * struct clk_gpio - gpio gated clock
- *
- * @hw:		handle between common and hardware-specific interfaces
- * @gpiod:	gpio descriptor
- *
- * Clock with a gpio control for enabling and disabling the parent clock
- * or switching between two parents by asserting or deasserting the gpio.
- *
- * Implements .enable, .disable and .is_enabled or
- * .get_parent, .set_parent and .determine_rate depending on which clk_ops
- * is used.
- */
-struct clk_gpio {
-	struct clk_hw	hw;
-	struct gpio_desc *gpiod;
-};
-
-#define to_clk_gpio(_hw) container_of(_hw, struct clk_gpio, hw)
-
 static int clk_gpio_gate_enable(struct clk_hw *hw)
 {
 	struct clk_gpio *clk = to_clk_gpio(hw);
@@ -71,11 +51,12 @@ static int clk_gpio_gate_is_enabled(struct clk_hw *hw)
 	return gpiod_get_value(clk->gpiod);
 }
 
-static const struct clk_ops clk_gpio_gate_ops = {
+const struct clk_ops clk_gpio_gate_ops = {
 	.enable = clk_gpio_gate_enable,
 	.disable = clk_gpio_gate_disable,
 	.is_enabled = clk_gpio_gate_is_enabled,
 };
+EXPORT_SYMBOL_GPL(clk_gpio_gate_ops);
 
 static int clk_sleeping_gpio_gate_prepare(struct clk_hw *hw)
 {
@@ -130,49 +111,67 @@ static int clk_gpio_mux_set_parent(struct clk_hw *hw, u8 index)
 	return 0;
 }
 
-static const struct clk_ops clk_gpio_mux_ops = {
+const struct clk_ops clk_gpio_mux_ops = {
 	.get_parent = clk_gpio_mux_get_parent,
 	.set_parent = clk_gpio_mux_set_parent,
 	.determine_rate = __clk_mux_determine_rate,
 };
+EXPORT_SYMBOL_GPL(clk_gpio_mux_ops);
 
-static struct clk_hw *clk_register_gpio(struct device *dev, u8 num_parents,
-					struct gpio_desc *gpiod,
-					const struct clk_ops *clk_gpio_ops)
+static struct clk_hw *clk_register_gpio(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags, const struct clk_ops *clk_gpio_ops)
 {
 	struct clk_gpio *clk_gpio;
 	struct clk_hw *hw;
 	struct clk_init_data init = {};
 	int err;
-	const struct clk_parent_data gpio_parent_data[] = {
-		{ .index = 0 },
-		{ .index = 1 },
-	};
 
-	clk_gpio = devm_kzalloc(dev, sizeof(*clk_gpio),	GFP_KERNEL);
+	if (dev)
+		clk_gpio = devm_kzalloc(dev, sizeof(*clk_gpio),	GFP_KERNEL);
+	else
+		clk_gpio = kzalloc(sizeof(*clk_gpio), GFP_KERNEL);
+
 	if (!clk_gpio)
 		return ERR_PTR(-ENOMEM);
 
-	init.name = dev->of_node->name;
+	init.name = name;
 	init.ops = clk_gpio_ops;
-	init.parent_data = gpio_parent_data;
+	init.flags = flags;
+	init.parent_names = parent_names;
 	init.num_parents = num_parents;
-	init.flags = CLK_SET_RATE_PARENT;
 
 	clk_gpio->gpiod = gpiod;
 	clk_gpio->hw.init = &init;
 
 	hw = &clk_gpio->hw;
-	err = devm_clk_hw_register(dev, hw);
-	if (err)
-		return ERR_PTR(err);
+	if (dev)
+		err = devm_clk_hw_register(dev, hw);
+	else
+		err = clk_hw_register(NULL, hw);
+
+	if (!err)
+		return hw;
+
+	if (!dev) {
+		kfree(clk_gpio);
+	}
 
-	return hw;
+	return ERR_PTR(err);
 }
 
-static struct clk_hw *clk_hw_register_gpio_gate(struct device *dev,
-						int num_parents,
-						struct gpio_desc *gpiod)
+/**
+ * clk_hw_register_gpio_gate - register a gpio clock gate with the clock
+ * framework
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @parent_name: name of this clock's parent
+ * @gpiod: gpio descriptor to gate this clock
+ * @flags: clock flags
+ */
+struct clk_hw *clk_hw_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags)
 {
 	const struct clk_ops *ops;
 
@@ -181,36 +180,88 @@ static struct clk_hw *clk_hw_register_gpio_gate(struct device *dev,
 	else
 		ops = &clk_gpio_gate_ops;
 
-	return clk_register_gpio(dev, num_parents, gpiod, ops);
+	return clk_register_gpio(dev, name,
+			(parent_name ? &parent_name : NULL),
+			(parent_name ? 1 : 0), gpiod, flags, ops);
 }
+EXPORT_SYMBOL_GPL(clk_hw_register_gpio_gate);
 
-static struct clk_hw *clk_hw_register_gpio_mux(struct device *dev,
-					       struct gpio_desc *gpiod)
+struct clk *clk_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags)
 {
-	return clk_register_gpio(dev, 2, gpiod, &clk_gpio_mux_ops);
+	struct clk_hw *hw;
+
+	hw = clk_hw_register_gpio_gate(dev, name, parent_name, gpiod, flags);
+	if (IS_ERR(hw))
+		return ERR_CAST(hw);
+	return hw->clk;
 }
+EXPORT_SYMBOL_GPL(clk_register_gpio_gate);
+
+/**
+ * clk_hw_register_gpio_mux - register a gpio clock mux with the clock framework
+ * @dev: device that is registering this clock
+ * @name: name of this clock
+ * @parent_names: names of this clock's parents
+ * @num_parents: number of parents listed in @parent_names
+ * @gpiod: gpio descriptor to gate this clock
+ * @flags: clock flags
+ */
+struct clk_hw *clk_hw_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags)
+{
+	if (num_parents != 2) {
+		pr_err("mux-clock %s must have 2 parents\n", name);
+		return ERR_PTR(-EINVAL);
+	}
+
+	return clk_register_gpio(dev, name, parent_names, num_parents,
+			gpiod, flags, &clk_gpio_mux_ops);
+}
+EXPORT_SYMBOL_GPL(clk_hw_register_gpio_mux);
+
+struct clk *clk_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags)
+{
+	struct clk_hw *hw;
+
+	hw = clk_hw_register_gpio_mux(dev, name, parent_names, num_parents,
+			gpiod, flags);
+	if (IS_ERR(hw))
+		return ERR_CAST(hw);
+	return hw->clk;
+}
+EXPORT_SYMBOL_GPL(clk_register_gpio_mux);
 
 static int gpio_clk_driver_probe(struct platform_device *pdev)
 {
-	struct device *dev = &pdev->dev;
-	struct device_node *node = dev->of_node;
-	const char *gpio_name;
+	struct device_node *node = pdev->dev.of_node;
+	const char **parent_names, *gpio_name;
 	unsigned int num_parents;
 	struct gpio_desc *gpiod;
-	struct clk_hw *hw;
+	struct clk *clk;
 	bool is_mux;
 	int ret;
 
-	is_mux = of_device_is_compatible(node, "gpio-mux-clock");
-
 	num_parents = of_clk_get_parent_count(node);
-	if (is_mux && num_parents != 2) {
-		dev_err(dev, "mux-clock must have 2 parents\n");
-		return -EINVAL;
+	if (num_parents) {
+		parent_names = devm_kcalloc(&pdev->dev, num_parents,
+					    sizeof(char *), GFP_KERNEL);
+		if (!parent_names)
+			return -ENOMEM;
+
+		of_clk_parent_fill(node, parent_names, num_parents);
+	} else {
+		parent_names = NULL;
 	}
 
+	is_mux = of_device_is_compatible(node, "gpio-mux-clock");
+
 	gpio_name = is_mux ? "select" : "enable";
-	gpiod = devm_gpiod_get(dev, gpio_name, GPIOD_OUT_LOW);
+	gpiod = devm_gpiod_get(&pdev->dev, gpio_name, GPIOD_OUT_LOW);
 	if (IS_ERR(gpiod)) {
 		ret = PTR_ERR(gpiod);
 		if (ret == -EPROBE_DEFER)
@@ -224,13 +275,16 @@ static int gpio_clk_driver_probe(struct platform_device *pdev)
 	}
 
 	if (is_mux)
-		hw = clk_hw_register_gpio_mux(dev, gpiod);
+		clk = clk_register_gpio_mux(&pdev->dev, node->name,
+				parent_names, num_parents, gpiod, 0);
 	else
-		hw = clk_hw_register_gpio_gate(dev, num_parents, gpiod);
-	if (IS_ERR(hw))
-		return PTR_ERR(hw);
+		clk = clk_register_gpio_gate(&pdev->dev, node->name,
+				parent_names ?  parent_names[0] : NULL, gpiod,
+				CLK_SET_RATE_PARENT);
+	if (IS_ERR(clk))
+		return PTR_ERR(clk);
 
-	return devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, hw);
+	return of_clk_add_provider(node, of_clk_src_simple_get, clk);
 }
 
 static const struct of_device_id gpio_clk_match_table[] = {
diff --git a/drivers/clk/clk-ipaq.c b/drivers/clk/clk-ipaq.c
new file mode 100644
index 000000000000..af48cdb7da53
--- /dev/null
+++ b/drivers/clk/clk-ipaq.c
@@ -0,0 +1,135 @@
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+
+struct ipaq_audio_clk {
+	struct clk_hw clk;
+	struct clk_lookup *cl;
+	struct gpio_desc *gpios[2];
+};
+#define to_ipaq_audio_clk(x) container_of(x, struct ipaq_audio_clk, clk)
+
+static const char *clk_names[2] = { "clk0", "clk1" };
+
+static unsigned long ipaq_audio_clk_rates[] = {
+	12288000,
+	11289600,
+	4096000,
+	5624500,
+};
+
+static unsigned long ipaq_audio_clk_recalc_rate(struct clk_hw *hw,
+	unsigned long parent_rate)
+{
+	struct ipaq_audio_clk *ia = to_ipaq_audio_clk(hw);
+	unsigned int clk0 = !!gpiod_get_value(ia->gpios[0]);
+	unsigned int clk1 = !!gpiod_get_value(ia->gpios[1]);
+	unsigned int idx = clk1 * 2 + clk0;
+
+	return ipaq_audio_clk_rates[idx];
+}
+
+static long ipaq_audio_clk_round_rate(struct clk_hw *hw, unsigned long rate,
+	unsigned long *parent_rate)
+{
+	int i, best = -1;
+	long diff, best_diff = LONG_MAX;
+
+	for (i = 0; i < ARRAY_SIZE(ipaq_audio_clk_rates); i++) {
+		diff = ipaq_audio_clk_rates[i] - rate;
+		if (diff < 0)
+			diff = -diff;
+		if (diff < best_diff) {
+			best_diff = diff;
+			best = i;
+		}
+	}
+
+	return best == -1 ? -EINVAL : ipaq_audio_clk_rates[best];
+}
+
+static int ipaq_audio_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+	unsigned long parent_rate)
+{
+	struct ipaq_audio_clk *ia = to_ipaq_audio_clk(hw);
+	DECLARE_BITMAP(values, 2) = { 0, };
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(ipaq_audio_clk_rates); i++) {
+		if (ipaq_audio_clk_rates[i] == rate) {
+			__assign_bit(0, values, !!(i & 1));
+			__assign_bit(1, values, !!(i & 2));
+
+			gpiod_set_array_value(2, ia->gpios, NULL, values);
+			return 0;
+		}
+	}
+	return -EINVAL;
+}
+
+static const struct clk_ops ipaq_audio_clk_ops = {
+	.recalc_rate = ipaq_audio_clk_recalc_rate,
+	.round_rate = ipaq_audio_clk_round_rate,
+	.set_rate = ipaq_audio_clk_set_rate,
+};
+
+static struct clk_init_data ipaq_audio_clk_init = {
+	.name = "ipaq-audio",
+	.ops = &ipaq_audio_clk_ops,
+};
+
+static int ipaq_audio_clk_probe(struct platform_device *pdev)
+{
+	struct ipaq_audio_clk *ia;
+	int i, ret;
+
+	ia = devm_kzalloc(&pdev->dev, sizeof(*ia), GFP_KERNEL);
+	if (!ia)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(clk_names); i++) {
+		ia->gpios[i] = devm_gpiod_get(&pdev->dev, clk_names[i],
+					      GPIOD_OUT_LOW);
+		if (IS_ERR(ia->gpios[i]))
+			return PTR_ERR(ia->gpios[i]);
+	}
+
+	platform_set_drvdata(pdev, ia);
+
+	ia->clk.init = &ipaq_audio_clk_init;
+
+	ret = devm_clk_hw_register(&pdev->dev, &ia->clk);
+	if (ret)
+		return ret;
+
+	ia->cl = clkdev_hw_create(&ia->clk, "ext", "h3xxx-asoc");
+
+	return ia->cl ? 0 : -ENOMEM;
+}
+
+static int ipaq_audio_clk_remove(struct platform_device *pdev)
+{
+	struct ipaq_audio_clk *ia = platform_get_drvdata(pdev);
+
+	clkdev_drop(ia->cl);
+
+	return 0;
+}
+
+static struct platform_driver ipaq_audio_clk_driver = {
+	.probe = ipaq_audio_clk_probe,
+	.remove = ipaq_audio_clk_remove,
+	.driver = {
+		.name = "ipaq-audio-clk",
+	},
+};
+
+module_platform_driver(ipaq_audio_clk_driver);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
+MODULE_DESCRIPTION("iPAQ Audio clock driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ipaq-audio-clock");
diff --git a/drivers/mfd/ipaq-micro.c b/drivers/mfd/ipaq-micro.c
index c964ea6539aa..f5420e625bf9 100644
--- a/drivers/mfd/ipaq-micro.c
+++ b/drivers/mfd/ipaq-micro.c
@@ -88,6 +88,7 @@ static void micro_rx_msg(struct ipaq_micro *micro, u8 id, int len, u8 *data)
 	case MSG_BACKLIGHT:
 	case MSG_NOTIFY_LED:
 	case MSG_THERMAL_SENSOR:
+	case MSG_SPI_READ:
 	case MSG_BATTERY:
 		/* Handle synchronous messages */
 		if (micro->msg && micro->msg->id == id) {
diff --git a/drivers/net/ethernet/3com/3c589_cs.c b/drivers/net/ethernet/3com/3c589_cs.c
index be58dac0502a..dc1f8ccca5ec 100644
--- a/drivers/net/ethernet/3com/3c589_cs.c
+++ b/drivers/net/ethernet/3com/3c589_cs.c
@@ -137,6 +137,8 @@ struct el3_private {
 	u16			fast_poll;
 	unsigned long		last_irq;
 	spinlock_t		lock;
+	u16			pending;
+	struct napi_struct	napi;
 };
 
 static const char *if_names[] = { "auto", "10baseT", "10base2", "AUI" };
@@ -176,6 +178,7 @@ static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue);
 static void set_rx_mode(struct net_device *dev);
 static void set_multicast_list(struct net_device *dev);
 static const struct ethtool_ops netdev_ethtool_ops;
+static int el3_napi_poll(struct napi_struct *napi, int budget);
 
 static void tc589_detach(struct pcmcia_device *p_dev);
 
@@ -219,6 +222,8 @@ static int tc589_probe(struct pcmcia_device *link)
 
 	dev->ethtool_ops = &netdev_ethtool_ops;
 
+	netif_napi_add(dev, &lp->napi, el3_napi_poll);
+
 	ret = tc589_config(link);
 	if (ret)
 		goto err_free_netdev;
@@ -527,6 +532,8 @@ static int el3_open(struct net_device *dev)
 	timer_setup(&lp->media, media_check, 0);
 	mod_timer(&lp->media, jiffies + HZ);
 
+	napi_enable(&lp->napi);
+
 	dev_dbg(&link->dev, "%s: opened, status %4.4x.\n",
 	  dev->name, inw(dev->base_addr + EL3_STATUS));
 
@@ -547,10 +554,10 @@ static void el3_tx_timeout(struct net_device *dev, unsigned int txqueue)
 	netif_wake_queue(dev);
 }
 
-static void pop_tx_status(struct net_device *dev)
+static int pop_tx_status(struct net_device *dev)
 {
 	unsigned int ioaddr = dev->base_addr;
-	int i;
+	int i, work = 0;
 
 	/* Clear the Tx status stack. */
 	for (i = 32; i > 0; i--) {
@@ -566,7 +573,9 @@ static void pop_tx_status(struct net_device *dev)
 			dev->stats.tx_aborted_errors++;
 		}
 		outb(0x00, ioaddr + TX_STATUS); /* Pop the status stack. */
+		work++;
 	}
+	return work;
 }
 
 static netdev_tx_t el3_start_xmit(struct sk_buff *skb,
@@ -587,7 +596,11 @@ static netdev_tx_t el3_start_xmit(struct sk_buff *skb,
 	outw(skb->len, ioaddr + TX_FIFO);
 	outw(0x00, ioaddr + TX_FIFO);
 	/* ... and the packet rounded to a doubleword. */
+#if 0
 	outsl(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2);
+#else
+	outsw(ioaddr + TX_FIFO, skb->data, (skb->len + 3) >> 2 << 1);
+#endif
 
 	if (inw(ioaddr + TX_FREE) <= 1536) {
 		netif_stop_queue(dev);
@@ -602,47 +615,38 @@ static netdev_tx_t el3_start_xmit(struct sk_buff *skb,
 	return NETDEV_TX_OK;
 }
 
-/* The EL3 interrupt handler. */
-static irqreturn_t el3_interrupt(int irq, void *dev_id)
+static int el3_napi_handle(struct net_device *dev, int budget)
 {
-	struct net_device *dev = (struct net_device *) dev_id;
 	struct el3_private *lp = netdev_priv(dev);
-	unsigned int ioaddr;
+	unsigned int ioaddr = dev->base_addr;
 	__u16 status;
-	int i = 0, handled = 1;
-
-	if (!netif_device_present(dev))
-		return IRQ_NONE;
-
-	ioaddr = dev->base_addr;
-
-	netdev_dbg(dev, "interrupt, status %4.4x.\n", inw(ioaddr + EL3_STATUS));
+	int work = 0;
 
-	spin_lock(&lp->lock);
-	while ((status = inw(ioaddr + EL3_STATUS)) &
+	while ((status = (inw(ioaddr + EL3_STATUS) | lp->pending)) &
 	(IntLatch | RxComplete | StatsFull)) {
+		lp->pending = 0;
 		if ((status & 0xe000) != 0x2000) {
 			netdev_dbg(dev, "interrupt from dead card\n");
-			handled = 0;
-			break;
+			return 0;
 		}
 		if (status & RxComplete)
-			el3_rx(dev);
+			work += el3_rx(dev);
 		if (status & TxAvailable) {
 			netdev_dbg(dev, "    TX room bit was handled.\n");
 			/* There's room in the FIFO for a full-sized packet. */
 			outw(AckIntr | TxAvailable, ioaddr + EL3_CMD);
 			netif_wake_queue(dev);
+			work++;
 		}
 		if (status & TxComplete)
-			pop_tx_status(dev);
+			work += pop_tx_status(dev);
 		if (status & (AdapterFailure | RxEarly | StatsFull)) {
 			/* Handle all uncommon interrupts. */
 			if (status & StatsFull)		/* Empty statistics. */
 				update_stats(dev);
 			if (status & RxEarly) {
 				/* Rx early is unused. */
-				el3_rx(dev);
+				work += el3_rx(dev);
 				outw(AckIntr | RxEarly, ioaddr + EL3_CMD);
 			}
 			if (status & AdapterFailure) {
@@ -666,17 +670,74 @@ static irqreturn_t el3_interrupt(int irq, void *dev_id)
 				outw(AckIntr | AdapterFailure, ioaddr + EL3_CMD);
 			}
 		}
-		if (++i > 10) {
-			netdev_err(dev, "infinite loop in interrupt, status %4.4x.\n",
-					status);
-			/* Clear all interrupts */
-			outw(AckIntr | 0xFF, ioaddr + EL3_CMD);
-			break;
-		}
 		/* Acknowledge the IRQ. */
 		outw(AckIntr | IntReq | IntLatch, ioaddr + EL3_CMD);
 	}
 	lp->last_irq = jiffies;
+
+	return work;
+}
+
+static int el3_napi_poll(struct napi_struct *napi, int budget)
+{
+	struct net_device *dev = napi->dev;
+	struct el3_private *lp = netdev_priv(dev);
+	unsigned int ioaddr = dev->base_addr;
+	int work = 0;
+
+	if (!netif_running(dev)) {
+		napi_complete(napi);
+		return 0;
+	}
+
+	netdev_dbg(dev, "napi poll, status %4.4x.\n", inw(ioaddr + EL3_STATUS));
+
+	spin_lock(&lp->lock);
+	work = el3_napi_handle(dev, budget);
+	spin_unlock(&lp->lock);
+
+	netdev_dbg(dev, "exiting napi poll, status %4.4x.\n",
+			inw(ioaddr + EL3_STATUS));
+
+	budget -= work;
+	if (budget > 0) {
+		napi_complete(napi);
+		outw(SetIntrEnb | IntLatch | TxAvailable | RxComplete
+		 | StatsFull | AdapterFailure, ioaddr + EL3_CMD);
+	}
+
+	return work;
+}
+
+/* The EL3 interrupt handler. */
+static irqreturn_t el3_interrupt(int irq, void *dev_id)
+{
+	struct net_device *dev = (struct net_device *) dev_id;
+	struct el3_private *lp = netdev_priv(dev);
+	unsigned int ioaddr;
+	__u16 status;
+	int handled = 1;
+
+	if (!netif_device_present(dev))
+		return IRQ_NONE;
+
+	ioaddr = dev->base_addr;
+
+	netdev_dbg(dev, "interrupt, status %4.4x.\n", inw(ioaddr + EL3_STATUS));
+
+	spin_lock(&lp->lock);
+	status = inw(ioaddr + EL3_STATUS);
+	if ((status & 0xe000) != 0x2000) {
+		netdev_dbg(dev, "interrupt from dead card\n");
+		handled = 0;
+	} else {
+		lp->pending |= status;
+		outw(SetIntrEnb, ioaddr + EL3_CMD);
+		status &= IntLatch | TxAvailable | RxComplete
+		 | StatsFull | AdapterFailure;
+		outw(AckIntr | status, ioaddr + EL3_CMD);
+		napi_schedule(&lp->napi);
+	}
 	spin_unlock(&lp->lock);
 	netdev_dbg(dev, "exiting interrupt, status %4.4x.\n",
 			inw(ioaddr + EL3_STATUS));
@@ -867,8 +928,13 @@ static int el3_rx(struct net_device *dev)
 		       pkt_len, rx_status);
 			if (skb != NULL) {
 				skb_reserve(skb, 2);
+#if 0
 				insl(ioaddr+RX_FIFO, skb_put(skb, pkt_len),
 			(pkt_len+3)>>2);
+#else
+				insw(ioaddr+RX_FIFO, skb_put(skb, pkt_len),
+				     (pkt_len+3)>>2 << 1);
+#endif
 				skb->protocol = eth_type_trans(skb, dev);
 				netif_rx(skb);
 				dev->stats.rx_packets++;
@@ -884,7 +950,7 @@ static int el3_rx(struct net_device *dev)
 	}
 	if (worklimit == 0)
 		netdev_warn(dev, "too much work in el3_rx!\n");
-	return 0;
+	return 32 - worklimit;
 }
 
 static void set_rx_mode(struct net_device *dev)
@@ -944,6 +1010,8 @@ static int el3_close(struct net_device *dev)
 			update_stats(dev);
 	}
 
+	napi_disable(&lp->napi);
+
 	link->open--;
 	netif_stop_queue(dev);
 	del_timer_sync(&lp->media);
diff --git a/drivers/net/ethernet/smsc/smc91x.c b/drivers/net/ethernet/smsc/smc91x.c
index 907498848028..60dec9024c4e 100644
--- a/drivers/net/ethernet/smsc/smc91x.c
+++ b/drivers/net/ethernet/smsc/smc91x.c
@@ -62,6 +62,7 @@ static const char version[] =
 #include <linux/irq.h>
 #include <linux/errno.h>
 #include <linux/ioport.h>
+#include <linux/clk.h>
 #include <linux/crc32.h>
 #include <linux/platform_device.h>
 #include <linux/spinlock.h>
@@ -79,11 +80,6 @@ static const char version[] =
 
 #include "smc91x.h"
 
-#if defined(CONFIG_ASSABET_NEPONSET)
-#include <mach/assabet.h>
-#include <mach/neponset.h>
-#endif
-
 #ifndef SMC_NOWAIT
 # define SMC_NOWAIT		0
 #endif
@@ -2270,6 +2266,15 @@ static int smc_drv_probe(struct platform_device *pdev)
 		}
 	}
 
+	/*
+	 * Some platforms drive the xtal input from an external clock
+	 * source which may require some control.  Request the clock,
+	 * but allow probing to continue if no clock is specified.
+	 */
+	lp->xtal = devm_clk_get_optional(&pdev->dev, NULL);
+	if (IS_ERR(lp->xtal))
+		return PTR_ERR(lp->xtal);
+
 #if IS_BUILTIN(CONFIG_OF)
 	match = of_match_device(of_match_ptr(smc91x_match), &pdev->dev);
 	if (match) {
@@ -2362,19 +2367,20 @@ static int smc_drv_probe(struct platform_device *pdev)
 	ret = smc_request_attrib(pdev, ndev);
 	if (ret)
 		goto out_release_io;
-#if defined(CONFIG_ASSABET_NEPONSET)
-	if (machine_is_assabet() && machine_has_neponset())
-		neponset_ncr_set(NCR_ENET_OSC_EN);
-#endif
+	if (lp->xtal) {
+		ret = clk_prepare_enable(lp->xtal);
+		if (ret)
+			goto out_release_attrib;
+	}
 	platform_set_drvdata(pdev, ndev);
 	ret = smc_enable_device(pdev);
 	if (ret)
-		goto out_release_attrib;
+		goto out_disable_xtal;
 
 	addr = ioremap(res->start, SMC_IO_EXTENT);
 	if (!addr) {
 		ret = -ENOMEM;
-		goto out_release_attrib;
+		goto out_disable_xtal;
 	}
 
 #ifdef CONFIG_ARCH_PXA
@@ -2398,6 +2404,9 @@ static int smc_drv_probe(struct platform_device *pdev)
 	iounmap(addr);
  out_release_attrib:
 	smc_release_attrib(pdev, ndev);
+ out_disable_xtal:
+	if (lp->xtal)
+		clk_disable_unprepare(lp->xtal);
  out_release_io:
 	release_mem_region(res->start, SMC_IO_EXTENT);
  out_free_netdev:
@@ -2432,6 +2441,9 @@ static void smc_drv_remove(struct platform_device *pdev)
 		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 	release_mem_region(res->start, SMC_IO_EXTENT);
 
+	if (lp->xtal)
+		clk_disable_unprepare(lp->xtal);
+
 	free_netdev(ndev);
 }
 
@@ -2441,9 +2453,14 @@ static int smc_drv_suspend(struct device *dev)
 
 	if (ndev) {
 		if (netif_running(ndev)) {
+			struct smc_local *lp = netdev_priv(ndev);
+
 			netif_device_detach(ndev);
 			smc_shutdown(ndev);
 			smc_phy_powerdown(ndev);
+
+			if (lp->xtal)
+				clk_disable_unprepare(lp->xtal);
 		}
 	}
 	return 0;
@@ -2456,6 +2473,11 @@ static int smc_drv_resume(struct device *dev)
 
 	if (ndev) {
 		struct smc_local *lp = netdev_priv(ndev);
+		if (lp->xtal) {
+			int ret = clk_prepare_enable(lp->xtal);
+			if (ret)
+				return ret;
+		}
 		smc_enable_device(pdev);
 		if (netif_running(ndev)) {
 			smc_reset(ndev);
diff --git a/drivers/net/ethernet/smsc/smc91x.h b/drivers/net/ethernet/smsc/smc91x.h
index 38aa4374e813..8eac2fa37f6e 100644
--- a/drivers/net/ethernet/smsc/smc91x.h
+++ b/drivers/net/ethernet/smsc/smc91x.h
@@ -205,6 +205,7 @@ struct smc_local {
 
 	struct gpio_desc *power_gpio;
 	struct gpio_desc *reset_gpio;
+	struct clk *xtal;
 
 	/* version/revision of the SMC91x chip */
 	int	version;
diff --git a/drivers/pcmcia/Makefile b/drivers/pcmcia/Makefile
index c9d51b150682..0fbed0d213ab 100644
--- a/drivers/pcmcia/Makefile
+++ b/drivers/pcmcia/Makefile
@@ -40,7 +40,7 @@ sa1111_cs-$(CONFIG_SA1100_JORNADA720)		+= sa1111_jornada720.o
 
 sa1100_cs-y					+= sa1100_generic.o
 sa1100_cs-$(CONFIG_SA1100_COLLIE)		+= pxa2xx_sharpsl.o
-sa1100_cs-$(CONFIG_SA1100_H3600)		+= sa1100_h3600.o
+sa1100_cs-$(CONFIG_SA1100_H3600)		+= sa1100_h3600.o l1110.o
 
 pxa2xx-obj-$(CONFIG_PXA_SHARPSL)		+= pxa2xx_sharpsl.o
 obj-$(CONFIG_PCMCIA_PXA2XX)			+= pxa2xx_base.o $(pxa2xx-obj-y)
diff --git a/drivers/pcmcia/l1110.c b/drivers/pcmcia/l1110.c
new file mode 100644
index 000000000000..67507019d3db
--- /dev/null
+++ b/drivers/pcmcia/l1110.c
@@ -0,0 +1,109 @@
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include "l1110.h"
+#include "soc_common.h"
+
+enum {
+	PRS_S1 = BIT(0),
+	PRS_S2 = BIT(1),
+	PRS_S3 = BIT(2),
+	PRS_S4 = BIT(3),
+	PRS_BVD1 = BIT(4),
+	PRS_BVD2 = BIT(5),
+	PRS_VS1 = BIT(6),
+	PRS_VS2 = BIT(7),
+	PRS_RDY = BIT(8),
+	PRS_CD1 = BIT(9),
+	PRS_CD2 = BIT(10),
+
+	PRC_S1 = PRS_S1,	/* x1vcc - TPS2205 D2/D7 */
+	PRC_S2 = PRS_S2,	/* x0vcc - TPS2205 D3/D6 */
+	PRC_S3 = PRS_S3,	/* x0vpp - TPS2205 D1/D5 */
+	PRC_S4 = PRS_S4,	/* x1vpp - TPS2205 D0/D4 */
+	PRC_RST = BIT(4),
+	PRC_APOE = BIT(5), /* auto power off enable */
+	PRC_CFE = BIT(6), /* CF enable: A25:11 driven high */
+	PRC_SOE = BIT(7), /* signal output enable */
+	PRC_SSP = BIT(8), /* socket select polarity */
+};
+
+struct l1110 {
+	void __iomem *reg;
+	u16 prc;
+};
+
+int l1110_init(struct device *dev, struct l1110 **ptr, resource_size_t base,
+	unsigned int ssp)
+{
+	struct l1110 *l;
+
+	l = devm_kzalloc(dev, sizeof(*l), GFP_KERNEL);
+	if (!l)
+		return -ENOMEM;
+
+	l->prc = PRC_APOE | (ssp ? PRC_SSP : 0);
+	l->reg = devm_ioremap(dev, base, 2);
+	if (!l->reg)
+		return -ENOMEM;
+
+	*ptr = l;
+
+	writew_relaxed(l->prc, l->reg);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(l1110_init);
+
+void l1110_socket_state(struct l1110 *l, struct pcmcia_state *state)
+{
+	u16 prs = readw_relaxed(l->reg);
+
+	state->bvd1 = !!(prs & PRS_BVD1);
+	state->bvd2 = !!(prs & PRS_BVD2);
+	state->vs_3v = !(prs & PRS_VS1);
+	state->vs_Xv = !(prs & PRS_VS2);
+}
+EXPORT_SYMBOL_GPL(l1110_socket_state);
+
+int l1110_configure_socket(struct l1110 *l, const socket_state_t *state)
+{
+	u16 prc = l->prc;
+
+	switch (state->Vcc) {
+	case 0:
+		break;
+	case 33:
+		prc |= PRC_S1;
+		break;
+	case 50:
+		prc |= PRC_S2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (state->Vpp) {
+		if (state->Vpp == 12)
+			prc |= PRC_S4;
+		else if (state->Vpp == state->Vcc)
+			prc |= PRC_S3;
+		else
+			return -EINVAL;
+	}
+
+	if (state->flags & SS_RESET)
+		prc |= PRC_RST;
+
+	if (state->flags & SS_OUTPUT_ENA)
+		prc |= PRC_SOE;
+
+	writew_relaxed(prc, l->reg);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(l1110_configure_socket);
+
+MODULE_AUTHOR("Russell King <rmk+kernel@armlinux.org.uk>");
+MODULE_DESCRIPTION("LinkUp Systems L1110 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/pcmcia/l1110.h b/drivers/pcmcia/l1110.h
new file mode 100644
index 000000000000..2e404f64836a
--- /dev/null
+++ b/drivers/pcmcia/l1110.h
@@ -0,0 +1,15 @@
+#ifndef L1110_H
+#define L1110_H
+
+#include <linux/types.h>
+#include <pcmcia/ss.h>
+
+struct l1110;
+struct pcmcia_state;
+
+int l1110_init(struct device *dev, struct l1110 **ptr, resource_size_t base,
+	unsigned int ssp);
+void l1110_socket_state(struct l1110 *l, struct pcmcia_state *state);
+int l1110_configure_socket(struct l1110 *l, const socket_state_t *state);
+
+#endif
diff --git a/drivers/pcmcia/sa1100_h3600.c b/drivers/pcmcia/sa1100_h3600.c
index 10cb99c20a7f..8e25942010c6 100644
--- a/drivers/pcmcia/sa1100_h3600.c
+++ b/drivers/pcmcia/sa1100_h3600.c
@@ -5,23 +5,19 @@
  * PCMCIA implementation routines for H3600
  *
  */
-#include <linux/module.h>
 #include <linux/kernel.h>
 #include <linux/device.h>
-#include <linux/interrupt.h>
-#include <linux/init.h>
-#include <linux/delay.h>
-#include <linux/gpio.h>
+#include <linux/mod_devicetable.h>
 
-#include <mach/hardware.h>
-#include <asm/irq.h>
 #include <asm/mach-types.h>
-#include <mach/h3xxx.h>
 
+#include "l1110.h"
 #include "sa1100_generic.h"
 
-static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
+static int l1110_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
 {
+	struct device *dev = skt->socket.dev.parent;
+	struct l1110 *l;
 	int err;
 
 	skt->stat[SOC_STAT_CD].name = skt->nr ? "pcmcia1-detect" : "pcmcia0-detect";
@@ -31,133 +27,48 @@ static int h3600_pcmcia_hw_init(struct soc_pcmcia_socket *skt)
 	if (err)
 		return err;
 
-	switch (skt->nr) {
-	case 0:
-		err = gpio_request(H3XXX_EGPIO_OPT_NVRAM_ON, "OPT NVRAM ON");
-		if (err)
-			goto err01;
-		err = gpio_direction_output(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
-		if (err)
-			goto err03;
-		err = gpio_request(H3XXX_EGPIO_OPT_ON, "OPT ON");
-		if (err)
-			goto err03;
-		err = gpio_direction_output(H3XXX_EGPIO_OPT_ON, 0);
-		if (err)
-			goto err04;
-		err = gpio_request(H3XXX_EGPIO_OPT_RESET, "OPT RESET");
-		if (err)
-			goto err04;
-		err = gpio_direction_output(H3XXX_EGPIO_OPT_RESET, 0);
-		if (err)
-			goto err05;
-		err = gpio_request(H3XXX_EGPIO_CARD_RESET, "PCMCIA CARD RESET");
-		if (err)
-			goto err05;
-		err = gpio_direction_output(H3XXX_EGPIO_CARD_RESET, 0);
-		if (err)
-			goto err06;
-		break;
-	case 1:
-		break;
-	}
-	return 0;
-
-err06:	gpio_free(H3XXX_EGPIO_CARD_RESET);
-err05:	gpio_free(H3XXX_EGPIO_OPT_RESET);
-err04:	gpio_free(H3XXX_EGPIO_OPT_ON);
-err03:	gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
-err01:	gpio_free(H3XXX_GPIO_PCMCIA_IRQ0);
-	return err;
-}
-
-static void h3600_pcmcia_hw_shutdown(struct soc_pcmcia_socket *skt)
-{
-	switch (skt->nr) {
-	case 0:
-		/* Disable CF bus: */
-		gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
-		gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
-		gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
-
-		gpio_free(H3XXX_EGPIO_CARD_RESET);
-		gpio_free(H3XXX_EGPIO_OPT_RESET);
-		gpio_free(H3XXX_EGPIO_OPT_ON);
-		gpio_free(H3XXX_EGPIO_OPT_NVRAM_ON);
-		break;
-	case 1:
-		break;
-	}
-}
-
-static void
-h3600_pcmcia_socket_state(struct soc_pcmcia_socket *skt, struct pcmcia_state *state)
-{
-	state->bvd1 = 0;
-	state->bvd2 = 0;
-	state->vs_3v = 0;
-	state->vs_Xv = 0;
-}
-
-static int
-h3600_pcmcia_configure_socket(struct soc_pcmcia_socket *skt, const socket_state_t *state)
-{
-	if (state->Vcc != 0 && state->Vcc != 33 && state->Vcc != 50) {
-		printk(KERN_ERR "h3600_pcmcia: unrecognized Vcc %u.%uV\n",
-		       state->Vcc / 10, state->Vcc % 10);
-		return -1;
-	}
-
-	gpio_set_value(H3XXX_EGPIO_CARD_RESET, !!(state->flags & SS_RESET));
+	err = l1110_init(dev, &l, skt->nr ? 0x19000000 : 0x1a000000,
+			 skt->nr);
+	if (err)
+		return err;
 
-	/* Silently ignore Vpp, output enable, speaker enable. */
+	skt->socket.driver_data = l;
 
 	return 0;
 }
 
-static void h3600_pcmcia_socket_init(struct soc_pcmcia_socket *skt)
+static void l1110_pcmcia_socket_state(struct soc_pcmcia_socket *skt,
+	struct pcmcia_state *state)
 {
-	/* Enable CF bus: */
-	gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 1);
-	gpio_set_value(H3XXX_EGPIO_OPT_ON, 1);
-	gpio_set_value(H3XXX_EGPIO_OPT_RESET, 0);
+	struct l1110 *l = skt->socket.driver_data;
 
-	msleep(10);
+	l1110_socket_state(l, state);
 }
 
-static void h3600_pcmcia_socket_suspend(struct soc_pcmcia_socket *skt)
+static int l1110_pcmcia_configure_socket(struct soc_pcmcia_socket *skt,
+	const socket_state_t *state)
 {
-	/*
-	 * FIXME:  This doesn't fit well.  We don't have the mechanism in
-	 * the generic PCMCIA layer to deal with the idea of two sockets
-	 * on one bus.  We rely on the cs.c behaviour shutting down
-	 * socket 0 then socket 1.
-	 */
-	if (skt->nr == 1) {
-		gpio_set_value(H3XXX_EGPIO_OPT_ON, 0);
-		gpio_set_value(H3XXX_EGPIO_OPT_NVRAM_ON, 0);
-		/* hmm, does this suck power? */
-		gpio_set_value(H3XXX_EGPIO_OPT_RESET, 1);
-	}
+	struct l1110 *l = skt->socket.driver_data;
+
+	return l1110_configure_socket(l, state);
 }
 
-struct pcmcia_low_level h3600_pcmcia_ops = { 
+static struct pcmcia_low_level l1110_pcmcia_ops = {
 	.owner			= THIS_MODULE,
-	.hw_init		= h3600_pcmcia_hw_init,
-	.hw_shutdown		= h3600_pcmcia_hw_shutdown,
-	.socket_state		= h3600_pcmcia_socket_state,
-	.configure_socket	= h3600_pcmcia_configure_socket,
-
-	.socket_init		= h3600_pcmcia_socket_init,
-	.socket_suspend		= h3600_pcmcia_socket_suspend,
+	.hw_init		= l1110_pcmcia_hw_init,
+	.socket_state		= l1110_pcmcia_socket_state,
+	.configure_socket	= l1110_pcmcia_configure_socket,
 };
 
 int pcmcia_h3600_init(struct device *dev)
 {
-	int ret = -ENODEV;
+	struct ipaq_option_id *id = dev->platform_data;
+
+	if (!machine_is_h3600() || !id)
+		return -ENODEV;
 
-	if (machine_is_h3600())
-		ret = sa11xx_drv_pcmcia_probe(dev, &h3600_pcmcia_ops, 0, 2);
+	if (id->vendor != 0x1125 || id->device != 0x0001)
+		return -ENODEV;
 
-	return ret;
+	return sa11xx_drv_pcmcia_probe(dev, &l1110_pcmcia_ops, 0, 2);
 }
diff --git a/drivers/tty/serial/sa1100.c b/drivers/tty/serial/sa1100.c
index 79c794fa6545..77671d589f57 100644
--- a/drivers/tty/serial/sa1100.c
+++ b/drivers/tty/serial/sa1100.c
@@ -8,6 +8,8 @@
  */
 
 #include <linux/module.h>
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
 #include <linux/ioport.h>
 #include <linux/init.h>
 #include <linux/console.h>
@@ -75,7 +77,10 @@ struct sa1100_port {
 	struct uart_port	port;
 	struct timer_list	timer;
 	unsigned int		old_status;
+	struct clk		*clk;
 	struct mctrl_gpios	*gpios;
+	struct gpio_desc	*xcvr_enable;
+	unsigned		wake;
 };
 
 /*
@@ -337,13 +342,19 @@ static int sa1100_startup(struct uart_port *port)
 		container_of(port, struct sa1100_port, port);
 	int retval;
 
+	retval = clk_prepare_enable(sport->clk);
+	if (retval)
+		return retval;
+
 	/*
 	 * Allocate the IRQ
 	 */
 	retval = request_irq(sport->port.irq, sa1100_int, 0,
 			     "sa11x0-uart", sport);
-	if (retval)
+	if (retval) {
+		clk_disable_unprepare(sport->clk);
 		return retval;
+	}
 
 	/*
 	 * Finally, clear and enable interrupts
@@ -380,6 +391,8 @@ static void sa1100_shutdown(struct uart_port *port)
 	 * Disable all interrupts, port and break condition.
 	 */
 	UART_PUT_UTCR3(sport, 0);
+
+	clk_disable_unprepare(sport->clk);
 }
 
 static void
@@ -561,6 +574,16 @@ sa1100_verify_port(struct uart_port *port, struct serial_struct *ser)
 	return ret;
 }
 
+static void sa1100_pm(struct uart_port *port, unsigned int state,
+	unsigned int old)
+{
+	struct sa1100_port *sport =
+		container_of(port, struct sa1100_port, port);
+
+	if (sport->xcvr_enable)
+		gpiod_direction_output(sport->xcvr_enable, !state);
+}
+
 static struct uart_ops sa1100_pops = {
 	.tx_empty	= sa1100_tx_empty,
 	.set_mctrl	= sa1100_set_mctrl,
@@ -578,6 +601,7 @@ static struct uart_ops sa1100_pops = {
 	.request_port	= sa1100_request_port,
 	.config_port	= sa1100_config_port,
 	.verify_port	= sa1100_verify_port,
+	.pm		= sa1100_pm,
 };
 
 static struct sa1100_port sa1100_ports[NR_PORTS];
@@ -620,18 +644,19 @@ static void __init sa1100_init_ports(void)
 	PPSR |= PPC_TXD1 | PPC_TXD3;
 }
 
-void sa1100_register_uart_fns(struct sa1100_port_fns *fns)
+void sa1100_register_uart_fns(const struct sa1100_port_fns *fns)
 {
+	unsigned int i;
+
 	if (fns->get_mctrl)
 		sa1100_pops.get_mctrl = fns->get_mctrl;
 	if (fns->set_mctrl)
 		sa1100_pops.set_mctrl = fns->set_mctrl;
 
 	sa1100_pops.pm       = fns->pm;
-	/*
-	 * FIXME: fns->set_wake is unused - this should be called from
-	 * the suspend() callback if device_may_wakeup(dev)) is set.
-	 */
+
+	for (i = 0; i < NR_PORTS; i++)
+		sa1100_ports[i].wake = fns->wake[i];
 }
 
 void __init sa1100_register_uart(int idx, int port)
@@ -804,19 +829,83 @@ static struct uart_driver sa1100_reg = {
 	.cons			= SA1100_CONSOLE,
 };
 
+static unsigned int sa1100_translate_mctrl_wake_mask(unsigned int wake_bits)
+{
+	unsigned int wake = 0;
+
+	if (wake_bits & SA11X0_WAKE_CTS)
+		wake |= BIT(UART_GPIO_CTS);
+	if (wake_bits & SA11X0_WAKE_DCD)
+		wake |= BIT(UART_GPIO_DCD);
+	if (wake_bits & SA11X0_WAKE_DSR)
+		wake |= BIT(UART_GPIO_DSR);
+	if (wake_bits & SA11X0_WAKE_RI)
+		wake |= BIT(UART_GPIO_RI);
+
+	return wake;
+}
+
 static int sa1100_serial_suspend(struct platform_device *dev, pm_message_t state)
 {
 	struct sa1100_port *sport = platform_get_drvdata(dev);
+	unsigned int i, enabled = 0, wake;
+	int ret;
+
+	wake = sa1100_translate_mctrl_wake_mask(sport->wake);
+
+	for (i = 0; i < UART_GPIO_MAX; i++) {
+		struct gpio_desc *g;
+		int irq;
+
+		if (!(wake & BIT(i)))
+			continue;
+
+		g = mctrl_gpio_to_gpiod(sport->gpios, i);
+		if (!g)
+			continue;
+
+		irq = gpiod_to_irq(g);
+		if (irq > 0) {
+			ret = enable_irq_wake(irq);
+			if (ret)
+				goto disable;
+		}
+
+		enabled |= BIT(i);
+	}
 
 	if (sport)
 		uart_suspend_port(&sa1100_reg, &sport->port);
 
 	return 0;
+
+ disable:
+	for (i = 0; i < UART_GPIO_MAX; i++)
+		if (enabled & BIT(i))
+			mctrl_gpio_disable_wake(sport->gpios, i);
+	return ret;
 }
 
 static int sa1100_serial_resume(struct platform_device *dev)
 {
 	struct sa1100_port *sport = platform_get_drvdata(dev);
+	unsigned int i, wake = sa1100_translate_mctrl_wake_mask(sport->wake);
+
+	for (i = 0; i < UART_GPIO_MAX; i++) {
+		struct gpio_desc *g;
+		int irq;
+
+		if (!(wake & BIT(i)))
+			continue;
+
+		g = mctrl_gpio_to_gpiod(sport->gpios, i);
+		if (!g)
+			continue;
+
+		irq = gpiod_to_irq(g);
+		if (irq > 0)
+			disable_irq_wake(irq);
+	}
 
 	if (sport)
 		uart_resume_port(&sa1100_reg, &sport->port);
@@ -845,6 +934,26 @@ static int sa1100_serial_add_one_port(struct sa1100_port *sport, struct platform
 		sport->gpios = NULL;
 	}
 
+	sport->xcvr_enable = devm_gpiod_get_optional(sport->port.dev,
+						     "xcvr-enable", GPIOD_ASIS);
+	if (IS_ERR(sport->xcvr_enable)) {
+		int err = PTR_ERR(sport->xcvr_enable);
+
+		dev_err(sport->port.dev, "failed to get xcvr gpio: %d\n",
+			err);
+
+		if (err == -EPROBE_DEFER)
+			return err;
+
+		sport->xcvr_enable = NULL;
+	}
+
+	sport->clk = devm_clk_get(sport->port.dev, NULL);
+	if (IS_ERR(sport->clk))
+		return PTR_ERR(sport->clk);
+
+	device_init_wakeup(sport->port.dev, !!sport->wake);
+
 	platform_set_drvdata(dev, sport);
 
 	return uart_add_one_port(&sa1100_reg, &sport->port);
diff --git a/drivers/tty/serial/serial_mctrl_gpio.c b/drivers/tty/serial/serial_mctrl_gpio.c
index 8855688a5b6c..a03b476e785b 100644
--- a/drivers/tty/serial/serial_mctrl_gpio.c
+++ b/drivers/tty/serial/serial_mctrl_gpio.c
@@ -385,5 +385,24 @@ void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios)
 }
 EXPORT_SYMBOL_GPL(mctrl_gpio_disable_irq_wake);
 
+int mctrl_gpio_enable_wake(struct mctrl_gpios *gpios, enum mctrl_gpio_idx gidx)
+{
+	int ret = -EINVAL;
+
+	if (gidx < UART_GPIO_MAX && gpios->irq[gidx])
+		ret = enable_irq_wake(gpios->irq[gidx]);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(mctrl_gpio_enable_wake);
+
+void mctrl_gpio_disable_wake(struct mctrl_gpios *gpios,
+			     enum mctrl_gpio_idx gidx)
+{
+	if (gidx < UART_GPIO_MAX && gpios->irq[gidx])
+		disable_irq_wake(gpios->irq[gidx]);
+}
+EXPORT_SYMBOL_GPL(mctrl_gpio_disable_wake);
+
 MODULE_DESCRIPTION("Helpers for controlling modem lines via GPIO");
 MODULE_LICENSE("GPL");
diff --git a/drivers/tty/serial/serial_mctrl_gpio.h b/drivers/tty/serial/serial_mctrl_gpio.h
index fc76910fb105..07bec3aa2786 100644
--- a/drivers/tty/serial/serial_mctrl_gpio.h
+++ b/drivers/tty/serial/serial_mctrl_gpio.h
@@ -101,6 +101,17 @@ void mctrl_gpio_enable_irq_wake(struct mctrl_gpios *gpios);
  */
 void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios);
 
+/*
+ * Enable wakeup due to state change on the gpio
+ */
+int mctrl_gpio_enable_wake(struct mctrl_gpios *gpios, enum mctrl_gpio_idx gidx);
+
+/*
+ * Disable wakeup due to state change on the gpio
+ */
+void mctrl_gpio_disable_wake(struct mctrl_gpios *gpios,
+			     enum mctrl_gpio_idx gidx);
+
 #else /* GPIOLIB */
 
 static inline
@@ -160,6 +171,17 @@ static inline void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios)
 {
 }
 
+static inline int mctrl_gpio_enable_wake(struct mctrl_gpios *gpios,
+					 enum mctrl_gpio_idx gidx)
+{
+	return 0;
+}
+
+static inline void mctrl_gpio_disable_wake(struct mctrl_gpios *gpios,
+					   enum mctrl_gpio_idx gidx)
+{
+}
+
 #endif /* GPIOLIB */
 
 #endif
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index 4a537260f655..052be7d22f24 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -1306,6 +1306,44 @@ struct clk_hw *devm_clk_hw_register_composite_pdata(struct device *dev,
 		unsigned long flags);
 void clk_hw_unregister_composite(struct clk_hw *hw);
 
+/**
+ * struct clk_gpio - gpio gated clock
+ *
+ * @hw:		handle between common and hardware-specific interfaces
+ * @gpiod:	gpio descriptor
+ *
+ * Clock with a gpio control for enabling and disabling the parent clock
+ * or switching between two parents by asserting or deasserting the gpio.
+ *
+ * Implements .enable, .disable and .is_enabled or
+ * .get_parent, .set_parent and .determine_rate depending on which clk_ops
+ * is used.
+ */
+struct clk_gpio {
+	struct clk_hw	hw;
+	struct gpio_desc *gpiod;
+};
+
+#define to_clk_gpio(_hw) container_of(_hw, struct clk_gpio, hw)
+
+extern const struct clk_ops clk_gpio_gate_ops;
+struct clk *clk_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags);
+struct clk_hw *clk_hw_register_gpio_gate(struct device *dev, const char *name,
+		const char *parent_name, struct gpio_desc *gpiod,
+		unsigned long flags);
+void clk_hw_unregister_gpio_gate(struct clk_hw *hw);
+
+extern const struct clk_ops clk_gpio_mux_ops;
+struct clk *clk_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags);
+struct clk_hw *clk_hw_register_gpio_mux(struct device *dev, const char *name,
+		const char * const *parent_names, u8 num_parents, struct gpio_desc *gpiod,
+		unsigned long flags);
+void clk_hw_unregister_gpio_mux(struct clk_hw *hw);
+
 struct clk *clk_register(struct device *dev, struct clk_hw *hw);
 struct clk *devm_clk_register(struct device *dev, struct clk_hw *hw);
 
diff --git a/include/linux/ipaq-sleeve.h b/include/linux/ipaq-sleeve.h
new file mode 100644
index 000000000000..42352e942c3c
--- /dev/null
+++ b/include/linux/ipaq-sleeve.h
@@ -0,0 +1,36 @@
+#ifndef LINUX_IPAQ_SLEEVE_H
+#define LINUX_IPAQ_SLEEVE_H
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+
+struct ipaq_option_device {
+	struct ipaq_option_id id;
+	const char *description;
+	struct device dev;
+};
+#define to_ipaq_option_device(x) container_of(x, struct ipaq_option_device, dev)
+
+struct ipaq_option_driver {
+	const struct ipaq_option_id *id_table;
+	struct device_driver driver;
+};
+
+extern struct bus_type ipaq_option_sleeve_bus;
+
+int ipaq_option_device_add(struct device *parent, struct ipaq_option_id id,
+			   const char *description);
+void ipaq_option_device_del(void);
+
+static inline int ipaq_option_driver_register(struct ipaq_option_driver *drv)
+{
+	drv->driver.bus = &ipaq_option_sleeve_bus;
+	return driver_register(&drv->driver);
+}
+
+static inline void ipaq_option_driver_unregister(struct ipaq_option_driver *drv)
+{
+	driver_unregister(&drv->driver);
+}
+
+#endif
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index 4338b1b4ac44..a7bd02606bb3 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -652,6 +652,16 @@ struct isapnp_device_id {
 	kernel_ulong_t driver_data;	/* data private to the driver */
 };
 
+/**
+ * struct ipaq_option_id - iPAQ sleeve option id
+ * @vendor: 16-bit vendor identifier
+ * @device: 16-bit device identifier
+ */
+struct ipaq_option_id {
+	__u16 vendor;
+	__u16 device;
+};
+
 /**
  * struct amba_id - identifies a device on an AMBA bus
  * @id: The significant bits if the hardware device ID
diff --git a/include/linux/platform_data/sa11x0-serial.h b/include/linux/platform_data/sa11x0-serial.h
index 8b79ab08af45..9ecd9611d90f 100644
--- a/include/linux/platform_data/sa11x0-serial.h
+++ b/include/linux/platform_data/sa11x0-serial.h
@@ -12,6 +12,13 @@
 struct uart_port;
 struct uart_info;
 
+enum {
+	SA11X0_WAKE_CTS = BIT(0),
+	SA11X0_WAKE_DCD = BIT(1),
+	SA11X0_WAKE_DSR = BIT(2),
+	SA11X0_WAKE_RI  = BIT(3),
+};
+
 /*
  * This is a temporary structure for registering these
  * functions; it is intended to be discarded after boot.
@@ -20,14 +27,14 @@ struct sa1100_port_fns {
 	void	(*set_mctrl)(struct uart_port *, u_int);
 	u_int	(*get_mctrl)(struct uart_port *);
 	void	(*pm)(struct uart_port *, u_int, u_int);
-	int	(*set_wake)(struct uart_port *, u_int);
+	unsigned wake[3];
 };
 
 #ifdef CONFIG_SERIAL_SA1100
-void sa1100_register_uart_fns(struct sa1100_port_fns *fns);
+void sa1100_register_uart_fns(const struct sa1100_port_fns *fns);
 void sa1100_register_uart(int idx, int port);
 #else
-static inline void sa1100_register_uart_fns(struct sa1100_port_fns *fns)
+static inline void sa1100_register_uart_fns(const struct sa1100_port_fns *fns)
 {
 }
 static inline void sa1100_register_uart(int idx, int port)
diff --git a/include/sound/l3.h b/include/sound/l3.h
new file mode 100644
index 000000000000..b6f58072237a
--- /dev/null
+++ b/include/sound/l3.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _L3_H_
+#define _L3_H_ 1
+
+struct l3_pins {
+	void (*setdat)(struct l3_pins *, int);
+	void (*setclk)(struct l3_pins *, int);
+	void (*setmode)(struct l3_pins *, int);
+
+	int gpio_data;
+	int gpio_clk;
+	int gpio_mode;
+	int use_gpios;
+
+	int data_hold;
+	int data_setup;
+	int clock_high;
+	int mode_hold;
+	int mode;
+	int mode_setup;
+};
+
+struct device;
+
+int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len);
+int l3_set_gpio_ops(struct device *dev, struct l3_pins *adap);
+
+#endif
diff --git a/include/sound/soc-dmaengine.h b/include/sound/soc-dmaengine.h
new file mode 100644
index 000000000000..b435133de04d
--- /dev/null
+++ b/include/sound/soc-dmaengine.h
@@ -0,0 +1,26 @@
+/*
+ * Generic ASoC DMA engine backend
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+#ifndef _LINUX_SND_SOC_DMAENGINE_H
+#define _LINUX_SND_SOC_DMAENGINE_H
+
+#include <linux/dmaengine.h>
+
+struct soc_dma_config {
+	dma_filter_fn filter;
+	void *data;
+
+	dma_addr_t reg;
+	enum dma_slave_buswidth width;
+	u32 maxburst;
+	size_t align;
+	size_t fifo_size;
+};
+
+#endif
diff --git a/include/sound/uda134x.h b/include/sound/uda134x.h
new file mode 100644
index 000000000000..db82516da162
--- /dev/null
+++ b/include/sound/uda134x.h
@@ -0,0 +1,24 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * uda134x.h  --  UDA134x ALSA SoC Codec driver
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ */
+
+#ifndef _UDA134X_H
+#define _UDA134X_H
+
+#include <sound/l3.h>
+
+struct uda134x_platform_data {
+	struct l3_pins l3;
+	void (*power) (int);
+	int model;
+#define UDA134X_UDA1340 1
+#define UDA134X_UDA1341 2
+#define UDA134X_UDA1344 3
+#define UDA134X_UDA1345 4
+};
+
+#endif /* _UDA134X_H */
diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c
index 518200813d4e..d2f0872d9f3c 100644
--- a/scripts/mod/devicetable-offsets.c
+++ b/scripts/mod/devicetable-offsets.c
@@ -178,6 +178,10 @@ int main(void)
 	DEVID_FIELD(ipack_device_id, vendor);
 	DEVID_FIELD(ipack_device_id, device);
 
+	DEVID(ipaq_option_id);
+	DEVID_FIELD(ipaq_option_id, vendor);
+	DEVID_FIELD(ipaq_option_id, device);
+
 	DEVID(amba_id);
 	DEVID_FIELD(amba_id, id);
 	DEVID_FIELD(amba_id, mask);
diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c
index 5d1c61fa5a55..af2fd24b441a 100644
--- a/scripts/mod/file2alias.c
+++ b/scripts/mod/file2alias.c
@@ -1140,6 +1140,16 @@ static void append_nibble_mask(char **outp,
 	*outp = p;
 }
 
+/* Looks like: ipaq:vNdN */
+static int do_ipaq_entry(const char *filename, void *symval, char *alias)
+{
+	DEF_FIELD(symval, ipaq_option_id, vendor);
+	DEF_FIELD(symval, ipaq_option_id, device);
+
+	sprintf(alias, "ipaq:v%04Xd%04X", vendor, device);
+	return 1;
+}
+
 /*
  * looks like: "amba:dN"
  *
@@ -1562,6 +1572,7 @@ static const struct devtable devtable[] = {
 	{"zorro", SIZE_zorro_device_id, do_zorro_entry},
 	{"isapnp", SIZE_isapnp_device_id, do_isapnp_entry},
 	{"ipack", SIZE_ipack_device_id, do_ipack_entry},
+	{"ipaq", SIZE_ipaq_option_id, do_ipaq_entry},
 	{"amba", SIZE_amba_id, do_amba_entry},
 	{"mipscdmm", SIZE_mips_cdmm_device_id, do_mips_cdmm_entry},
 	{"x86cpu", SIZE_x86_cpu_id, do_x86cpu_entry},
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index a52afb423b46..e6eec854911c 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -26,6 +26,9 @@ if SND_SOC
 config SND_SOC_AC97_BUS
 	bool
 
+config SND_SOC_DMAENGINE_PCM
+	bool
+
 config SND_SOC_GENERIC_DMAENGINE_PCM
 	bool
 	select SND_DMAENGINE_PCM
@@ -34,6 +37,9 @@ config SND_SOC_COMPRESS
 	bool
 	select SND_COMPRESS_OFFLOAD
 
+config SND_SOC_DMAENGINE
+	tristate
+
 config SND_SOC_TOPOLOGY
 	bool
 	select SND_DYNAMIC_MINORS
@@ -107,6 +113,7 @@ source "sound/soc/mxs/Kconfig"
 source "sound/soc/pxa/Kconfig"
 source "sound/soc/qcom/Kconfig"
 source "sound/soc/rockchip/Kconfig"
+source "sound/soc/sa11x0/Kconfig"
 source "sound/soc/samsung/Kconfig"
 source "sound/soc/sh/Kconfig"
 source "sound/soc/sof/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index fd61847dd1eb..aa8a90c40b8d 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -2,6 +2,7 @@
 snd-soc-core-y := soc-core.o soc-dapm.o soc-jack.o soc-utils.o soc-dai.o soc-component.o
 snd-soc-core-y += soc-pcm.o soc-devres.o soc-ops.o soc-link.o soc-card.o
 snd-soc-core-$(CONFIG_SND_SOC_COMPRESS) += soc-compress.o
+snd-soc-dmaengine-objs := soc-dmaengine.o
 
 ifneq ($(CONFIG_SND_SOC_TOPOLOGY),)
 snd-soc-core-y += soc-topology.o
@@ -36,6 +37,7 @@ endif
 obj-$(CONFIG_SND_SOC_ACPI) += snd-soc-acpi.o
 
 obj-$(CONFIG_SND_SOC)	+= snd-soc-core.o
+obj-$(CONFIG_SND_SOC_DMAENGINE) += snd-soc-dmaengine.o
 obj-$(CONFIG_SND_SOC)	+= codecs/
 obj-$(CONFIG_SND_SOC)	+= generic/
 obj-$(CONFIG_SND_SOC)	+= apple/
@@ -60,6 +62,7 @@ obj-$(CONFIG_SND_SOC)	+= kirkwood/
 obj-$(CONFIG_SND_SOC)	+= pxa/
 obj-$(CONFIG_SND_SOC)	+= qcom/
 obj-$(CONFIG_SND_SOC)	+= rockchip/
+obj-$(CONFIG_SND_SOC)	+= sa11x0/
 obj-$(CONFIG_SND_SOC)	+= samsung/
 obj-$(CONFIG_SND_SOC)	+= sh/
 obj-$(CONFIG_SND_SOC)	+= sof/
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index b5e6d0a986c8..ffa14d3498ff 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -15,6 +15,7 @@ config SND_SOC_ALL_CODECS
 	tristate "Build all ASoC CODEC drivers"
 	depends on COMPILE_TEST
 	imply SND_SOC_88PM860X
+	imply SND_SOC_L3
 	imply SND_SOC_AB8500_CODEC
 	imply SND_SOC_AC97_CODEC
 	imply SND_SOC_AD1836
@@ -280,6 +281,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_TWL4030
 	imply SND_SOC_TWL6040
 	imply SND_SOC_UDA1334
+	imply SND_SOC_UDA134X
 	imply SND_SOC_UDA1380
 	imply SND_SOC_WCD9335
 	imply SND_SOC_WCD934X
@@ -1088,6 +1090,9 @@ config SND_SOC_JZ4770_CODEC
 	  This driver can also be built as a module. If so, the module
 	  will be called snd-soc-jz4770-codec.
 
+config SND_SOC_L3
+	tristate
+
 config SND_SOC_DA7210
 	tristate
 	depends on SND_SOC_I2C_AND_SPI
@@ -2113,6 +2118,9 @@ config SND_SOC_UDA1334
 	  and has basic features such as de-emphasis (at 44.1 kHz sampling
 	  rate) and mute.
 
+config SND_SOC_UDA134X
+	tristate
+
 config SND_SOC_UDA1380
 	tristate
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 622e360f0086..0c7a7edaee6c 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -57,7 +57,6 @@ snd-soc-aw88399-y := aw88399.o
 snd-soc-bd28623-y := bd28623.o
 snd-soc-bt-sco-y := bt-sco.o
 snd-soc-chv3-codec-y := chv3-codec.o
-snd-soc-cpcap-y := cpcap.o
 snd-soc-cq93vc-y := cq93vc.o
 snd-soc-cros-ec-codec-y := cros_ec_codec.o
 snd-soc-cs-amp-lib-y := cs-amp-lib.o
@@ -142,6 +141,7 @@ snd-soc-jz4740-codec-y := jz4740.o
 snd-soc-jz4725b-codec-y := jz4725b.o
 snd-soc-jz4760-codec-y := jz4760.o
 snd-soc-jz4770-codec-y := jz4770.o
+snd-soc-l3-y := l3.o
 snd-soc-lm4857-y := lm4857.o
 snd-soc-lm49453-y := lm49453.o
 snd-soc-lochnagar-sc-y := lochnagar-sc.o
@@ -318,6 +318,7 @@ snd-soc-ts3a227e-y := ts3a227e.o
 snd-soc-twl4030-y := twl4030.o
 snd-soc-twl6040-y := twl6040.o
 snd-soc-uda1334-y := uda1334.o
+snd-soc-uda134x-y := uda134x.o
 snd-soc-uda1380-y := uda1380.o
 snd-soc-wcd-classh-y := wcd-clsh-v2.o
 snd-soc-wcd-mbhc-y := wcd-mbhc-v2.o
@@ -548,6 +549,7 @@ obj-$(CONFIG_SND_SOC_JZ4740_CODEC)	+= snd-soc-jz4740-codec.o
 obj-$(CONFIG_SND_SOC_JZ4725B_CODEC)	+= snd-soc-jz4725b-codec.o
 obj-$(CONFIG_SND_SOC_JZ4760_CODEC)      += snd-soc-jz4760-codec.o
 obj-$(CONFIG_SND_SOC_JZ4770_CODEC)	+= snd-soc-jz4770-codec.o
+obj-$(CONFIG_SND_SOC_L3)	+= snd-soc-l3.o
 obj-$(CONFIG_SND_SOC_LM4857)	+= snd-soc-lm4857.o
 obj-$(CONFIG_SND_SOC_LM49453)   += snd-soc-lm49453.o
 obj-$(CONFIG_SND_SOC_LOCHNAGAR_SC)	+= snd-soc-lochnagar-sc.o
@@ -721,6 +723,7 @@ obj-$(CONFIG_SND_SOC_TS3A227E)	+= snd-soc-ts3a227e.o
 obj-$(CONFIG_SND_SOC_TWL4030)	+= snd-soc-twl4030.o
 obj-$(CONFIG_SND_SOC_TWL6040)	+= snd-soc-twl6040.o
 obj-$(CONFIG_SND_SOC_UDA1334)	+= snd-soc-uda1334.o
+obj-$(CONFIG_SND_SOC_UDA134X)	+= snd-soc-uda134x.o
 obj-$(CONFIG_SND_SOC_UDA1380)	+= snd-soc-uda1380.o
 obj-$(CONFIG_SND_SOC_WCD_CLASSH)	+= snd-soc-wcd-classh.o
 obj-$(CONFIG_SND_SOC_WCD_MBHC)	+= snd-soc-wcd-mbhc.o
diff --git a/sound/soc/codecs/l3.c b/sound/soc/codecs/l3.c
new file mode 100644
index 000000000000..b84f6f1f6800
--- /dev/null
+++ b/sound/soc/codecs/l3.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * L3 code
+ *
+ *  Copyright (C) 2008, Christian Pellegrin <chripell@evolware.org>
+ *
+ * based on:
+ *
+ * L3 bus algorithm module.
+ *
+ *  Copyright (C) 2001 Russell King, All Rights Reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+
+#include <sound/l3.h>
+
+/*
+ * Send one byte of data to the chip.  Data is latched into the chip on
+ * the rising edge of the clock.
+ */
+static void sendbyte(struct l3_pins *adap, unsigned int byte)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		adap->setclk(adap, 0);
+		udelay(adap->data_hold);
+		adap->setdat(adap, byte & 1);
+		udelay(adap->data_setup);
+		adap->setclk(adap, 1);
+		udelay(adap->clock_high);
+		byte >>= 1;
+	}
+}
+
+/*
+ * Send a set of bytes to the chip.  We need to pulse the MODE line
+ * between each byte, but never at the start nor at the end of the
+ * transfer.
+ */
+static void sendbytes(struct l3_pins *adap, const u8 *buf,
+		      int len)
+{
+	int i;
+
+	for (i = 0; i < len; i++) {
+		if (i) {
+			udelay(adap->mode_hold);
+			adap->setmode(adap, 0);
+			udelay(adap->mode);
+		}
+		adap->setmode(adap, 1);
+		udelay(adap->mode_setup);
+		sendbyte(adap, buf[i]);
+	}
+}
+
+int l3_write(struct l3_pins *adap, u8 addr, u8 *data, int len)
+{
+	adap->setclk(adap, 1);
+	adap->setdat(adap, 1);
+	adap->setmode(adap, 1);
+	udelay(adap->mode);
+
+	adap->setmode(adap, 0);
+	udelay(adap->mode_setup);
+	sendbyte(adap, addr);
+	udelay(adap->mode_hold);
+
+	sendbytes(adap, data, len);
+
+	adap->setclk(adap, 1);
+	adap->setdat(adap, 1);
+	adap->setmode(adap, 0);
+
+	return len;
+}
+EXPORT_SYMBOL_GPL(l3_write);
+
+
+static void l3_set_clk(struct l3_pins *adap, int val)
+{
+	gpio_set_value(adap->gpio_clk, val);
+}
+
+static void l3_set_data(struct l3_pins *adap, int val)
+{
+	gpio_set_value(adap->gpio_data, val);
+}
+
+static void l3_set_mode(struct l3_pins *adap, int val)
+{
+	gpio_set_value(adap->gpio_mode, val);
+}
+
+int l3_set_gpio_ops(struct device *dev, struct l3_pins *adap)
+{
+	int ret;
+
+	if (!adap->use_gpios)
+		return -EINVAL;
+
+	ret = devm_gpio_request_one(dev, adap->gpio_data,
+				GPIOF_OUT_INIT_LOW, "l3_data");
+	if (ret < 0)
+		return ret;
+	adap->setdat = l3_set_data;
+
+	ret = devm_gpio_request_one(dev, adap->gpio_clk,
+				GPIOF_OUT_INIT_LOW, "l3_clk");
+	if (ret < 0)
+		return ret;
+	adap->setclk = l3_set_clk;
+
+	ret = devm_gpio_request_one(dev, adap->gpio_mode,
+				GPIOF_OUT_INIT_LOW, "l3_mode");
+	if (ret < 0)
+		return ret;
+	adap->setmode = l3_set_mode;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(l3_set_gpio_ops);
+
+MODULE_DESCRIPTION("L3 bit-banging driver");
+MODULE_AUTHOR("Christian Pellegrin <chripell@evolware.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda134x.c b/sound/soc/codecs/uda134x.c
new file mode 100644
index 000000000000..d07b98bda2d3
--- /dev/null
+++ b/sound/soc/codecs/uda134x.c
@@ -0,0 +1,602 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * uda134x.c  --  UDA134X ALSA SoC Codec driver
+ *
+ * Modifications by Christian Pellegrin <chripell@evolware.org>
+ *
+ * Copyright 2007 Dension Audio Systems Ltd.
+ * Author: Zoltan Devai
+ *
+ * Based on the WM87xx drivers by Liam Girdwood and Richard Purdie
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/slab.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/initval.h>
+
+#include <sound/uda134x.h>
+#include <sound/l3.h>
+
+#include "uda134x.h"
+
+
+#define UDA134X_RATES SNDRV_PCM_RATE_8000_48000
+#define UDA134X_FORMATS (SNDRV_PCM_FMTBIT_S8 | SNDRV_PCM_FMTBIT_S16_LE | \
+		SNDRV_PCM_FMTBIT_S18_3LE | SNDRV_PCM_FMTBIT_S20_3LE)
+
+struct uda134x_priv {
+	int sysclk;
+	int dai_fmt;
+
+	struct snd_pcm_substream *master_substream;
+	struct snd_pcm_substream *slave_substream;
+
+	struct regmap *regmap;
+	struct uda134x_platform_data *pd;
+
+	struct gpio_desc *qmute;
+};
+
+static const struct reg_default uda134x_reg_defaults[] = {
+	{ UDA134X_EA000, 0x04 },
+	{ UDA134X_EA001, 0x04 },
+	{ UDA134X_EA010, 0x04 },
+	{ UDA134X_EA011, 0x00 },
+	{ UDA134X_EA100, 0x00 },
+	{ UDA134X_EA101, 0x00 },
+	{ UDA134X_EA110, 0x00 },
+	{ UDA134X_EA111, 0x00 },
+	{ UDA134X_STATUS0, 0x00 },
+	{ UDA134X_STATUS1, 0x03 },
+	{ UDA134X_DATA000, 0x00 },
+	{ UDA134X_DATA001, 0x00 },
+	{ UDA134X_DATA010, 0x00 },
+	{ UDA134X_DATA011, 0x00 },
+	{ UDA134X_DATA1, 0x00 },
+};
+
+/*
+ * Write to the uda134x registers
+ *
+ */
+static int uda134x_regmap_write(void *context, unsigned int reg,
+	unsigned int value)
+{
+	struct uda134x_platform_data *pd = context;
+	int ret;
+	u8 addr;
+	u8 data = value;
+
+	switch (reg) {
+	case UDA134X_STATUS0:
+	case UDA134X_STATUS1:
+		addr = UDA134X_STATUS_ADDR;
+		data |= (reg - UDA134X_STATUS0) << 7;
+		break;
+	case UDA134X_DATA000:
+	case UDA134X_DATA001:
+	case UDA134X_DATA010:
+	case UDA134X_DATA011:
+		addr = UDA134X_DATA0_ADDR;
+		data |= (reg - UDA134X_DATA000) << 6;
+		break;
+	case UDA134X_DATA1:
+		addr = UDA134X_DATA1_ADDR;
+		break;
+	default:
+		/* It's an extended address register */
+		addr =  (reg | UDA134X_EXTADDR_PREFIX);
+
+		ret = l3_write(&pd->l3,
+			       UDA134X_DATA0_ADDR, &addr, 1);
+		if (ret != 1)
+			return -EIO;
+
+		addr = UDA134X_DATA0_ADDR;
+		data = (value | UDA134X_EXTDATA_PREFIX);
+		break;
+	}
+
+	ret = l3_write(&pd->l3,
+		       addr, &data, 1);
+	if (ret != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static inline void uda134x_reset(struct snd_soc_component *component)
+{
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	unsigned int mask = 1<<6;
+
+	regmap_update_bits(uda134x->regmap, UDA134X_STATUS0, mask, mask);
+	msleep(1);
+	regmap_update_bits(uda134x->regmap, UDA134X_STATUS0, mask, 0);
+}
+
+static int uda134x_mute(struct snd_soc_dai *dai, int mute, int direction)
+{
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(dai->component);
+	unsigned int mask = 1<<2;
+	unsigned int val;
+
+	pr_debug("%s mute: %d\n", __func__, mute);
+
+	if (uda134x->qmute)
+		gpiod_set_value_cansleep(uda134x->qmute, mute);
+
+	if (mute)
+		val = mask;
+	else
+		val = 0;
+
+	return regmap_update_bits(uda134x->regmap, UDA134X_DATA010, mask, val);
+}
+
+static int uda134x_startup(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	struct snd_pcm_runtime *master_runtime;
+
+	if (uda134x->master_substream) {
+		master_runtime = uda134x->master_substream->runtime;
+
+		pr_debug("%s constraining to %d bits at %d\n", __func__,
+			 master_runtime->sample_bits,
+			 master_runtime->rate);
+
+		snd_pcm_hw_constraint_single(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_RATE,
+					     master_runtime->rate);
+
+		snd_pcm_hw_constraint_single(substream->runtime,
+					     SNDRV_PCM_HW_PARAM_SAMPLE_BITS,
+					     master_runtime->sample_bits);
+
+		uda134x->slave_substream = substream;
+	} else
+		uda134x->master_substream = substream;
+
+	return 0;
+}
+
+static void uda134x_shutdown(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+
+	if (uda134x->master_substream == substream)
+		uda134x->master_substream = uda134x->slave_substream;
+
+	uda134x->slave_substream = NULL;
+}
+
+static int uda134x_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params,
+	struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	unsigned int hw_params = 0;
+
+	if (substream == uda134x->slave_substream) {
+		pr_debug("%s ignoring hw_params for slave substream\n",
+			 __func__);
+		return 0;
+	}
+
+	pr_debug("%s sysclk: %d, rate:%d\n", __func__,
+		 uda134x->sysclk, params_rate(params));
+
+	/* set SYSCLK / fs ratio */
+	switch (uda134x->sysclk / params_rate(params)) {
+	case 512:
+		break;
+	case 384:
+		hw_params |= (1<<4);
+		break;
+	case 256:
+		hw_params |= (1<<5);
+		break;
+	default:
+		printk(KERN_ERR "%s unsupported fs\n", __func__);
+		return -EINVAL;
+	}
+
+	pr_debug("%s dai_fmt: %d, params_format:%d\n", __func__,
+		 uda134x->dai_fmt, params_format(params));
+
+	/* set DAI format and word length */
+	switch (uda134x->dai_fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	case SND_SOC_DAIFMT_RIGHT_J:
+		switch (params_width(params)) {
+		case 16:
+			hw_params |= (1<<1);
+			break;
+		case 18:
+			hw_params |= (1<<2);
+			break;
+		case 20:
+			hw_params |= ((1<<2) | (1<<1));
+			break;
+		default:
+			printk(KERN_ERR "%s unsupported format (right)\n",
+			       __func__);
+			return -EINVAL;
+		}
+		break;
+	case SND_SOC_DAIFMT_LEFT_J:
+		hw_params |= (1<<3);
+		break;
+	default:
+		printk(KERN_ERR "%s unsupported format\n", __func__);
+		return -EINVAL;
+	}
+
+	return regmap_update_bits(uda134x->regmap, UDA134X_STATUS0,
+		STATUS0_SYSCLK_MASK | STATUS0_DAIFMT_MASK, hw_params);
+}
+
+static int uda134x_set_dai_sysclk(struct snd_soc_dai *codec_dai,
+				  int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+
+	pr_debug("%s clk_id: %d, freq: %u, dir: %d\n", __func__,
+		 clk_id, freq, dir);
+
+	/* Anything between 256fs*8Khz and 512fs*48Khz should be acceptable
+	   because the codec is slave. Of course limitations of the clock
+	   master (the IIS controller) apply.
+	   We'll error out on set_hw_params if it's not OK */
+	if ((freq >= (256 * 8000)) && (freq <= (512 * 48000))) {
+		uda134x->sysclk = freq;
+		return 0;
+	}
+
+	printk(KERN_ERR "%s unsupported sysclk\n", __func__);
+	return -EINVAL;
+}
+
+static int uda134x_set_dai_fmt(struct snd_soc_dai *codec_dai,
+			       unsigned int fmt)
+{
+	struct snd_soc_component *component = codec_dai->component;
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+
+	pr_debug("%s fmt: %08X\n", __func__, fmt);
+
+	/* codec supports only full consumer mode */
+	if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) != SND_SOC_DAIFMT_CBC_CFC) {
+		printk(KERN_ERR "%s unsupported clocking mode\n", __func__);
+		return -EINVAL;
+	}
+
+	/* no support for clock inversion */
+	if ((fmt & SND_SOC_DAIFMT_INV_MASK) != SND_SOC_DAIFMT_NB_NF) {
+		printk(KERN_ERR "%s unsupported clock inversion\n", __func__);
+		return -EINVAL;
+	}
+
+	/* We can't setup DAI format here as it depends on the word bit num */
+	/* so let's just store the value for later */
+	uda134x->dai_fmt = fmt;
+
+	return 0;
+}
+
+static int uda134x_set_bias_level(struct snd_soc_component *component,
+				  enum snd_soc_bias_level level)
+{
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	struct uda134x_platform_data *pd = uda134x->pd;
+	pr_debug("%s bias level %d\n", __func__, level);
+
+	switch (level) {
+	case SND_SOC_BIAS_ON:
+		break;
+	case SND_SOC_BIAS_PREPARE:
+		/* power on */
+		if (pd->power) {
+			if (uda134x->qmute)
+				gpiod_set_value_cansleep(uda134x->qmute, 1);
+			pd->power(1);
+			regcache_sync(uda134x->regmap);
+		}
+		break;
+	case SND_SOC_BIAS_STANDBY:
+		break;
+	case SND_SOC_BIAS_OFF:
+		/* power off */
+		if (pd->power) {
+			pd->power(0);
+			regcache_mark_dirty(uda134x->regmap);
+			if (uda134x->qmute)
+				gpiod_set_value_cansleep(uda134x->qmute, 0);
+		}
+		break;
+	}
+	return 0;
+}
+
+static const char *uda134x_dsp_setting[] = {"Flat", "Minimum1",
+					    "Minimum2", "Maximum"};
+static const char *uda134x_deemph[] = {"None", "32Khz", "44.1Khz", "48Khz"};
+static const char *uda134x_mixmode[] = {"Differential", "Analog1",
+					"Analog2", "Both"};
+
+static const struct soc_enum uda134x_mixer_enum[] = {
+SOC_ENUM_SINGLE(UDA134X_DATA010, 0, 0x04, uda134x_dsp_setting),
+SOC_ENUM_SINGLE(UDA134X_DATA010, 3, 0x04, uda134x_deemph),
+SOC_ENUM_SINGLE(UDA134X_EA010, 0, 0x04, uda134x_mixmode),
+};
+
+static const struct snd_kcontrol_new uda1341_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+SOC_SINGLE("Capture Volume", UDA134X_EA010, 2, 0x07, 0),
+SOC_SINGLE("Analog1 Volume", UDA134X_EA000, 0, 0x1F, 1),
+SOC_SINGLE("Analog2 Volume", UDA134X_EA001, 0, 0x1F, 1),
+
+SOC_SINGLE("Mic Sensitivity", UDA134X_EA010, 2, 7, 0),
+SOC_SINGLE("Mic Volume", UDA134X_EA101, 0, 0x1F, 0),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+SOC_ENUM("Input Mux", uda134x_mixer_enum[2]),
+
+SOC_SINGLE("AGC Switch", UDA134X_EA100, 4, 1, 0),
+SOC_SINGLE("AGC Target Volume", UDA134X_EA110, 0, 0x03, 1),
+SOC_SINGLE("AGC Timing", UDA134X_EA110, 2, 0x07, 0),
+
+SOC_SINGLE("DAC +6dB Switch", UDA134X_STATUS1, 6, 1, 0),
+SOC_SINGLE("ADC +6dB Switch", UDA134X_STATUS1, 5, 1, 0),
+SOC_SINGLE("ADC Polarity Switch", UDA134X_STATUS1, 4, 1, 0),
+SOC_SINGLE("DAC Polarity Switch", UDA134X_STATUS1, 3, 1, 0),
+SOC_SINGLE("Double Speed Playback Switch", UDA134X_STATUS1, 2, 1, 0),
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1340_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_SINGLE("Tone Control - Bass", UDA134X_DATA001, 2, 0xF, 0),
+SOC_SINGLE("Tone Control - Treble", UDA134X_DATA001, 0, 3, 0),
+
+SOC_ENUM("Sound Processing Filter", uda134x_mixer_enum[0]),
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+static const struct snd_kcontrol_new uda1345_snd_controls[] = {
+SOC_SINGLE("Master Playback Volume", UDA134X_DATA000, 0, 0x3F, 1),
+
+SOC_ENUM("PCM Playback De-emphasis", uda134x_mixer_enum[1]),
+
+SOC_SINGLE("DC Filter Enable Switch", UDA134X_STATUS0, 0, 1, 0),
+};
+
+/* UDA1341 has the DAC/ADC power down in STATUS1 */
+static const struct snd_soc_dapm_widget uda1341_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("DAC", "Playback", UDA134X_STATUS1, 0, 0),
+	SND_SOC_DAPM_ADC("ADC", "Capture", UDA134X_STATUS1, 1, 0),
+};
+
+/* UDA1340/4/5 has the DAC/ADC pwoer down in DATA0 11 */
+static const struct snd_soc_dapm_widget uda1340_dapm_widgets[] = {
+	SND_SOC_DAPM_DAC("DAC", "Playback", UDA134X_DATA011, 0, 0),
+	SND_SOC_DAPM_ADC("ADC", "Capture", UDA134X_DATA011, 1, 0),
+};
+
+/* Common DAPM widgets */
+static const struct snd_soc_dapm_widget uda134x_dapm_widgets[] = {
+	SND_SOC_DAPM_INPUT("VINL1"),
+	SND_SOC_DAPM_INPUT("VINR1"),
+	SND_SOC_DAPM_INPUT("VINL2"),
+	SND_SOC_DAPM_INPUT("VINR2"),
+	SND_SOC_DAPM_OUTPUT("VOUTL"),
+	SND_SOC_DAPM_OUTPUT("VOUTR"),
+};
+
+static const struct snd_soc_dapm_route uda134x_dapm_routes[] = {
+	{ "ADC", NULL, "VINL1" },
+	{ "ADC", NULL, "VINR1" },
+	{ "ADC", NULL, "VINL2" },
+	{ "ADC", NULL, "VINR2" },
+	{ "VOUTL", NULL, "DAC" },
+	{ "VOUTR", NULL, "DAC" },
+};
+
+static const struct snd_soc_dai_ops uda134x_dai_ops = {
+	.startup	= uda134x_startup,
+	.shutdown	= uda134x_shutdown,
+	.hw_params	= uda134x_hw_params,
+	.mute_stream	= uda134x_mute,
+	.set_sysclk	= uda134x_set_dai_sysclk,
+	.set_fmt	= uda134x_set_dai_fmt,
+	.no_capture_mute = 1,
+};
+
+static struct snd_soc_dai_driver uda134x_dai = {
+	.name = "uda134x-hifi",
+	/* playback capabilities */
+	.playback = {
+		.stream_name = "Playback",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA134X_RATES,
+		.formats = UDA134X_FORMATS,
+	},
+	/* capture capabilities */
+	.capture = {
+		.stream_name = "Capture",
+		.channels_min = 1,
+		.channels_max = 2,
+		.rates = UDA134X_RATES,
+		.formats = UDA134X_FORMATS,
+	},
+	/* pcm operations */
+	.ops = &uda134x_dai_ops,
+};
+
+static int uda134x_soc_probe(struct snd_soc_component *component)
+{
+	struct snd_soc_dapm_context *dapm = snd_soc_component_get_dapm(component);
+	struct uda134x_priv *uda134x = snd_soc_component_get_drvdata(component);
+	struct uda134x_platform_data *pd = uda134x->pd;
+	const struct snd_soc_dapm_widget *widgets;
+	unsigned int num_widgets;
+	int ret;
+
+	printk(KERN_INFO "UDA134X SoC Audio Codec\n");
+
+	switch (pd->model) {
+	case UDA134X_UDA1340:
+	case UDA134X_UDA1341:
+	case UDA134X_UDA1344:
+	case UDA134X_UDA1345:
+		break;
+	default:
+		printk(KERN_ERR "UDA134X SoC codec: "
+		       "unsupported model %d\n",
+			pd->model);
+		return -EINVAL;
+	}
+
+	if (pd->power)
+		pd->power(1);
+
+	uda134x_reset(component);
+
+	if (pd->model == UDA134X_UDA1341) {
+		widgets = uda1341_dapm_widgets;
+		num_widgets = ARRAY_SIZE(uda1341_dapm_widgets);
+	} else {
+		widgets = uda1340_dapm_widgets;
+		num_widgets = ARRAY_SIZE(uda1340_dapm_widgets);
+	}
+
+	ret = snd_soc_dapm_new_controls(dapm, widgets, num_widgets);
+	if (ret) {
+		printk(KERN_ERR "%s failed to register dapm controls: %d",
+			__func__, ret);
+		return ret;
+	}
+
+	switch (pd->model) {
+	case UDA134X_UDA1340:
+	case UDA134X_UDA1344:
+		ret = snd_soc_add_component_controls(component, uda1340_snd_controls,
+					ARRAY_SIZE(uda1340_snd_controls));
+	break;
+	case UDA134X_UDA1341:
+		ret = snd_soc_add_component_controls(component, uda1341_snd_controls,
+					ARRAY_SIZE(uda1341_snd_controls));
+	break;
+	case UDA134X_UDA1345:
+		ret = snd_soc_add_component_controls(component, uda1345_snd_controls,
+					ARRAY_SIZE(uda1345_snd_controls));
+	break;
+	default:
+		printk(KERN_ERR "%s unknown codec type: %d",
+			__func__, pd->model);
+		return -EINVAL;
+	}
+
+	if (ret < 0) {
+		printk(KERN_ERR "UDA134X: failed to register controls\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver soc_component_dev_uda134x = {
+	.probe			= uda134x_soc_probe,
+	.set_bias_level		= uda134x_set_bias_level,
+	.dapm_widgets		= uda134x_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(uda134x_dapm_widgets),
+	.dapm_routes		= uda134x_dapm_routes,
+	.num_dapm_routes	= ARRAY_SIZE(uda134x_dapm_routes),
+	.suspend_bias_off	= 1,
+	.idle_bias_on		= 0,
+	.use_pmdown_time	= 1,
+	.endianness		= 1,
+};
+
+static const struct regmap_config uda134x_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.max_register = UDA134X_DATA1,
+	.reg_defaults = uda134x_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(uda134x_reg_defaults),
+	.cache_type = REGCACHE_RBTREE,
+
+	.reg_write = uda134x_regmap_write,
+};
+
+static int uda134x_codec_probe(struct platform_device *pdev)
+{
+	struct uda134x_platform_data *pd = pdev->dev.platform_data;
+	struct uda134x_priv *uda134x;
+	int ret;
+
+	if (!pd) {
+		dev_err(&pdev->dev, "Missing L3 bitbang function\n");
+		return -ENODEV;
+	}
+
+	uda134x = devm_kzalloc(&pdev->dev, sizeof(*uda134x), GFP_KERNEL);
+	if (!uda134x)
+		return -ENOMEM;
+
+	uda134x->pd = pd;
+	platform_set_drvdata(pdev, uda134x);
+
+	if (pd->l3.use_gpios) {
+		ret = l3_set_gpio_ops(&pdev->dev, &uda134x->pd->l3);
+		if (ret < 0)
+			return ret;
+	}
+
+	uda134x->regmap = devm_regmap_init(&pdev->dev, NULL, pd,
+		&uda134x_regmap_config);
+	if (IS_ERR(uda134x->regmap))
+		return PTR_ERR(uda134x->regmap);
+
+	uda134x->qmute = devm_gpiod_get_optional(&pdev->dev, "qmute",
+						 GPIOD_OUT_LOW);
+	if (IS_ERR(uda134x->qmute))
+		return PTR_ERR(uda134x->qmute);
+
+	return devm_snd_soc_register_component(&pdev->dev,
+			&soc_component_dev_uda134x, &uda134x_dai, 1);
+}
+
+static struct platform_driver uda134x_codec_driver = {
+	.driver = {
+		.name = "uda134x-codec",
+	},
+	.probe = uda134x_codec_probe,
+};
+
+module_platform_driver(uda134x_codec_driver);
+
+MODULE_DESCRIPTION("UDA134X ALSA soc codec driver");
+MODULE_AUTHOR("Zoltan Devai, Christian Pellegrin <chripell@evolware.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/uda134x.h b/sound/soc/codecs/uda134x.h
new file mode 100644
index 000000000000..664618c2571c
--- /dev/null
+++ b/sound/soc/codecs/uda134x.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _UDA134X_CODEC_H
+#define _UDA134X_CODEC_H
+
+#define UDA134X_L3ADDR	5
+#define UDA134X_DATA0_ADDR	((UDA134X_L3ADDR << 2) | 0)
+#define UDA134X_DATA1_ADDR	((UDA134X_L3ADDR << 2) | 1)
+#define UDA134X_STATUS_ADDR	((UDA134X_L3ADDR << 2) | 2)
+
+#define UDA134X_EXTADDR_PREFIX	0xC0
+#define UDA134X_EXTDATA_PREFIX	0xE0
+
+/* UDA134X registers */
+#define UDA134X_EA000	0
+#define UDA134X_EA001	1
+#define UDA134X_EA010	2
+#define UDA134X_EA011	3
+#define UDA134X_EA100	4
+#define UDA134X_EA101	5
+#define UDA134X_EA110	6
+#define UDA134X_EA111	7
+#define UDA134X_STATUS0 8
+#define UDA134X_STATUS1 9
+#define UDA134X_DATA000 10
+#define UDA134X_DATA001 11
+#define UDA134X_DATA010 12
+#define UDA134X_DATA011 13
+#define UDA134X_DATA1   14
+
+#define STATUS0_DAIFMT_MASK (~(7<<1))
+#define STATUS0_SYSCLK_MASK (~(3<<4))
+
+#endif
diff --git a/sound/soc/sa11x0/Kconfig b/sound/soc/sa11x0/Kconfig
new file mode 100644
index 000000000000..81ba6f9bccc8
--- /dev/null
+++ b/sound/soc/sa11x0/Kconfig
@@ -0,0 +1,24 @@
+config SND_SOC_SA11X0_SSP
+	tristate
+	depends on DMA_SA11X0
+	select SND_SOC_GENERIC_DMAENGINE_PCM
+
+config SND_SA11X0_ASSABET
+	tristate "SoC Audio support for the Intel Assabet board"
+	depends on SA1100_ASSABET && DMA_SA11X0
+	select SND_SOC_L3
+	select SND_SOC_UDA134X
+	select SND_SOC_SA11X0_SSP
+	help
+	  Enable support for the audio UDA1341 codec found on the Intel
+	  Assabet SA-1110 development board.
+
+config SND_SA11X0_H3XXX
+	tristate "SoC Audio support for H3xxx iPAQ"
+	depends on (SA1100_H3100 || SA1100_H3600) && DMA_SA11X0
+	select SND_SOC_L3
+	select SND_SOC_UDA134X
+	select SND_SOC_SA11X0_SSP
+	help
+	  Enable support for the audio UDA1341 codec found on the iPAQ
+	  H3100 and H3600 platforms.
diff --git a/sound/soc/sa11x0/Makefile b/sound/soc/sa11x0/Makefile
new file mode 100644
index 000000000000..dab7e2deff6e
--- /dev/null
+++ b/sound/soc/sa11x0/Makefile
@@ -0,0 +1,8 @@
+snd-soc-sa11x0-ssp-objs := ssp.o
+obj-$(CONFIG_SND_SOC_SA11X0_SSP) += snd-soc-sa11x0-ssp.o
+
+snd-soc-sa11x0-assabet-objs := assabet.o
+obj-$(CONFIG_SND_SA11X0_ASSABET) += snd-soc-sa11x0-assabet.o
+
+snd-soc-sa11x0-h3xxx-objs := h3xxx.o
+obj-$(CONFIG_SND_SA11X0_H3XXX) += snd-soc-sa11x0-h3xxx.o
diff --git a/sound/soc/sa11x0/assabet.c b/sound/soc/sa11x0/assabet.c
new file mode 100644
index 000000000000..5b11856d5abf
--- /dev/null
+++ b/sound/soc/sa11x0/assabet.c
@@ -0,0 +1,540 @@
+/*
+ * SA11x0 Assabet ASoC driver
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * 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.
+ *
+ * The Assabet board uses an unused SDRAM clock output as the source
+ * clock for the UDA1341 audio subsystem.  This clock is supplied to
+ * via a CPLD divider to the SA11x0 GPIO19 (alternately the SSP block
+ * external clock input) and to the UDA1341 as the bit clock.
+ *
+ * As the SSPs TXD,RXD,SFRM,SCLK outputs are not directly compatible
+ * with the UDA1341 input, the CPLD implements logic to provide the
+ * WS (LRCK) signal to the UDA1341, and buffer the RXD and clock signals.
+ *
+ * The UDA1341 is powered by the AUDIO3P3V supply, which can be turned
+ * off.  This tristates the CPLD outputs, preventing power draining
+ * into the UDA1341.  However, revision 4 Assabets do not tristate the
+ * WS signal, and so need to be worked-around to place this at logic 0.
+ *
+ * A side effect of using the SDRAM clock is that this scales with the
+ * core CPU frequency.  Hence, the available sample rate depends on the
+ * CPU clock rate.  At present, we have no logic here to restrict the
+ * requested sample rate; the UDA1341 driver will just fail the preparation.
+ * We rely on userspace (at present) to ask for the correct sample rate.
+ */
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/uda134x.h>
+
+#include <asm/mach-types.h>
+#include <mach/assabet.h>
+#include <mach/hardware.h>
+#include "ssp.h"
+
+struct assabet_card {
+	struct snd_soc_card card;
+	struct clk *clk_cpu;
+	struct gpio_desc *gpio_spkr_shtdn;
+};
+
+#define GPIO_TXD	10
+#define GPIO_RXD	11
+#define GPIO_SCLK	12
+#define GPIO_SFRM	13
+#define GPIO_L3_DAT	15
+#define GPIO_L3_MODE	17
+#define GPIO_L3_SCK	18
+#define GPIO_CLK	19
+
+/*
+ * The UDA1341 wants a WS (or LRCK), and the Assabet derives this from
+ * the SFRM pulses from the SSP.  Unfortunately, some Assabets have a bug
+ * in the hardware where the WS output from the CPLD remains high even
+ * after the codec is powered down, causing power to drain through the
+ * CPLD to the UDA1341.
+ *
+ * Although the Assabet provides a reset signal to initialize WS to a
+ * known state, this presents two problems: (a) this forces the WS output
+ * high, (b) this signal also resets the UCB1300 device.
+ */
+static void assabet_asoc_uda1341_power(int on)
+{
+	static bool state;
+
+	if (state == !!on)
+		return;
+	state = !!on;
+
+	if (on) {
+		/*
+		 * Enable the power for the UDA1341 before fixing WS.
+		 * Also assert the mute signal.
+		 */
+		ASSABET_BCR_set(ASSABET_BCR_AUDIO_ON);
+
+		/*
+		 * Toggle SFRM with the codec reset signal held active.
+		 * This will set LRCK high, which results in the left
+		 * sample being transmitted first.
+		 */
+		gpio_set_value(GPIO_SFRM, 1);
+		gpio_set_value(GPIO_SFRM, 0);
+
+		/*
+		 * If the reset was being held, release it now.  This
+		 * ensures that the above SFRM fiddling has no effect
+		 * should the reset be raised elsewhere.
+		 */
+		assabet_uda1341_reset(0);
+	} else {
+		/*
+		 * The SSP will have transmitted a whole number of 32-bit
+		 * words, so we know that LRCK will be left high at this
+		 * point.  Toggle SFRM once to kick the LRCK low before
+		 * powering down the codec.
+		 */
+		gpio_set_value(GPIO_SFRM, 1);
+		gpio_set_value(GPIO_SFRM, 0);
+
+		/* Finally, disable the audio power */
+		ASSABET_BCR_clear(ASSABET_BCR_AUDIO_ON);
+	}
+}
+
+static void assabet_asoc_init_clk(void)
+{
+	u32 mdrefr, mdrefr_old;
+
+	/*
+	 * The assabet board uses the SDRAM clock as the source clock for
+	 * audio. This is supplied to the SA11x0 from the CPLD on pin 19.
+	 * At 206MHz we need to run the audio clock (SDRAM bank 2) at half
+	 * speed.  This clock will scale with core frequency so the audio
+	 * sample rate will also scale. The CPLD on Assabet will need to
+	 * be programmed to match the core frequency.
+	 */
+	mdrefr_old = MDREFR;
+	mdrefr = mdrefr_old & ~(MDREFR_EAPD | MDREFR_KAPD);
+	mdrefr = mdrefr | MDREFR_K2DB2 | MDREFR_K2RUN;
+	if (mdrefr != mdrefr_old) {
+		MDREFR = mdrefr;
+		(void) MDREFR;
+	}
+}
+
+static int assabet_asoc_resume_pre(struct snd_soc_card *card)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	bool active = false;
+
+	list_for_each_entry(rtd, &card->rtd_list, list)
+		if (rtd->cpu_dai->active)
+			active = true;
+
+	if (active) {
+		unsigned long flags;
+
+		local_irq_save(flags);
+		assabet_asoc_init_clk();
+		local_irq_restore(flags);
+	}
+	return 0;
+}
+
+/*
+ * Calculate the sysclk, which is the codec input clock. This is supplied
+ * from the SDRAM bank 2 clock, divided by 18 by the CPLD.  This is then
+ * further divided by four by the CPLD, and supplied to the SA1110 GPIO19
+ * to synchronously drive the SSP block.
+ *
+ * Ideally, we would lock cpufreq against any transitions which would upset
+ * the sample rate, or have userspace renegotiate the sample rate, but let's
+ * keep this simple.
+ *
+ * We intentionally lose some precision here to avoid the codec bombing out
+ * as it claims to only do standard rates.
+ */
+static unsigned int assabet_asoc_get_sysclk(struct assabet_card *ac)
+{
+	return (clk_get_rate(ac->clk_cpu) / 18000) * 1000;
+}
+
+static int
+assabet_asoc_rate_constraint(struct snd_pcm_hw_params *params, struct snd_pcm_hw_rule *rule)
+{
+	struct assabet_card *ac = rule->private;
+	struct snd_interval range, *p = hw_param_interval(params, rule->var);
+	unsigned int sysclk = assabet_asoc_get_sysclk(ac);
+
+	snd_interval_any(&range);
+	range.min = UINT_MAX;
+	range.max = 0;
+
+	/* We do 512Fs and 256Fs only */
+	if (snd_interval_test(p, sysclk / 256)) {
+		range.min = min(range.min, sysclk / 256);
+		range.max = max(range.max, sysclk / 256);
+	}
+	if (snd_interval_test(p, sysclk / 512)) {
+		range.min = min(range.min, sysclk / 512);
+		range.max = max(range.max, sysclk / 512);
+	}
+	return snd_interval_refine(p, &range);
+}
+
+static int assabet_asoc_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct assabet_card *ac = snd_soc_card_get_drvdata(rtd->card);
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	int ret;
+
+	if (!cpu_dai->active) {
+		unsigned long flags;
+		unsigned int sysclk;
+
+		ret = clk_prepare_enable(ac->clk_cpu);
+		if (ret)
+			return ret;
+
+		/*
+		 * Hand the SFRM signal over to the SSP block as it is
+		 * now enabled.  This keeps SFRM low until the interface
+		 * starts outputting data.
+		 */
+		local_irq_save(flags);
+		GAFR |= GPIO_SSP_SFRM;
+
+		assabet_asoc_init_clk();
+		local_irq_restore(flags);
+
+		sysclk = assabet_asoc_get_sysclk(ac);
+
+		ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sysclk,
+					     SND_SOC_CLOCK_IN);
+		if (ret < 0)
+			goto err;
+
+		ret = snd_soc_dai_set_sysclk(cpu_dai, SA11X0_SSP_CLK_EXT,
+					     sysclk / 4, SND_SOC_CLOCK_IN);
+		if (ret < 0)
+			goto err;
+	}
+
+	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			    assabet_asoc_rate_constraint, ac,
+			    SNDRV_PCM_HW_PARAM_RATE, -1);
+
+	return 0;
+
+err:
+	clk_disable_unprepare(ac->clk_cpu);
+	return ret;
+}
+
+static void assabet_asoc_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct assabet_card *ac = snd_soc_card_get_drvdata(rtd->card);
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+
+	if (!cpu_dai->active) {
+		unsigned long flags;
+
+		/*
+		 * Take the SFRM pin away from the SSP block before it
+		 * shuts down.  We must do this to keep SFRM low.
+		 */
+		local_irq_save(flags);
+		GAFR &= ~GPIO_SSP_SFRM;
+		local_irq_restore(flags);
+
+		clk_disable_unprepare(ac->clk_cpu);
+	}
+}
+
+static struct snd_soc_ops assabet_asoc_ops = {
+	.startup = assabet_asoc_startup,
+	.shutdown = assabet_asoc_shutdown,
+};
+
+static struct snd_soc_dai_link assabet_asoc_dais[] = {
+	{
+		.name = "Assabet",
+		.stream_name = "System Playback/Capture",
+		.cpu_dai_name = "sa11x0-ssp",
+		.codec_name = "uda134x-codec",
+		.codec_dai_name = "uda134x-hifi",
+		.platform_name = "sa11x0-ssp",
+		.dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBS_CFS,
+		.ops = &assabet_asoc_ops,
+	},
+};
+
+static int
+assabet_pwramp_event(struct snd_soc_dapm_widget *w, struct snd_kcontrol *k,
+	int event)
+{
+	struct assabet_card *ac = snd_soc_card_get_drvdata(w->dapm->card);
+
+	gpiod_set_value(ac->gpio_spkr_shtdn, !SND_SOC_DAPM_EVENT_ON(event));
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget assabet_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Power Amp", assabet_pwramp_event),
+};
+
+static const struct snd_soc_dapm_route assabet_dapm_routes[] = {
+	{ "Power Amp", NULL, "VOUTL" },
+	{ "Power Amp", NULL, "VOUTR" },
+};
+
+static struct snd_soc_card snd_soc_assabet = {
+	.name = "Assabet",
+	.resume_pre = assabet_asoc_resume_pre,
+	.dai_link = assabet_asoc_dais,
+	.num_links = ARRAY_SIZE(assabet_asoc_dais),
+	.dapm_widgets = assabet_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(assabet_dapm_widgets),
+	.dapm_routes = assabet_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(assabet_dapm_routes),
+};
+
+/*
+ * This is not-quite-right stuff: we have to _hope_ by a miracle that
+ * we don't stamp on the toes of I2C, which shares these pins.
+ */
+static void assabet_setdat(struct l3_pins *adap, int v)
+{
+	gpio_set_value(GPIO_L3_DAT, !!v);
+}
+
+static void assabet_setclk(struct l3_pins *adap, int v)
+{
+	gpio_set_value(GPIO_L3_SCK, !!v);
+}
+
+static void assabet_setmode(struct l3_pins *adap, int v)
+{
+	gpio_set_value(GPIO_L3_MODE, !!v);
+}
+
+static struct uda134x_platform_data uda1341_data = {
+	.l3 = {
+		.setdat = assabet_setdat,
+		.setclk = assabet_setclk,
+		.setmode = assabet_setmode,
+		.data_hold = 1,
+		.data_setup = 1,
+		.clock_high = 1,
+		.mode_hold = 1,
+		.mode = 1,
+		.mode_setup = 1,
+	},
+	.model = UDA134X_UDA1341,
+	.power = assabet_asoc_uda1341_power,
+};
+
+static struct gpio ssp_gpio[] = {
+	{
+		.gpio = GPIO_TXD,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "ssp_txd",
+	}, {
+		.gpio = GPIO_RXD,
+		.flags = GPIOF_IN,
+		.label = "ssp_rxd",
+	}, {
+		.gpio = GPIO_SCLK,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "ssp_sclk",
+	}, {
+		.gpio = GPIO_SFRM,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "ssp_sfrm",
+	}, {
+		.gpio = GPIO_CLK,
+		.flags = GPIOF_IN,
+		.label = "ssp_clk",
+	}, {
+		.gpio = GPIO_L3_MODE,
+		.flags = GPIOF_OUT_INIT_LOW,
+		.label = "l3_mode",
+	},
+};
+
+static int assabet_asoc_probe(struct platform_device *pdev)
+{
+	struct assabet_card *ac;
+
+	ac = devm_kzalloc(&pdev->dev, sizeof(*ac), GFP_KERNEL);
+	if (!ac)
+		return -ENOMEM;
+
+	memcpy(&ac->card, &snd_soc_assabet, sizeof(ac->card));
+	ac->card.dev = &pdev->dev;
+
+	ac->clk_cpu = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(ac->clk_cpu))
+		return PTR_ERR(ac->clk_cpu);
+
+	ac->gpio_spkr_shtdn = devm_gpiod_get(&pdev->dev, "spkr-shtdn",
+					     GPIOD_OUT_HIGH);
+	if (IS_ERR(ac->gpio_spkr_shtdn))
+		return PTR_ERR(ac->gpio_spkr_shtdn);
+
+	snd_soc_card_set_drvdata(&ac->card, ac);
+
+	return snd_soc_register_card(&ac->card);
+}
+
+static int assabet_asoc_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+
+	return 0;
+}
+
+static struct platform_driver assabet_asoc_driver = {
+	.probe	= assabet_asoc_probe,
+	.remove	= assabet_asoc_remove,
+	.driver = {
+		.name = "assabet-asoc",
+		.owner = THIS_MODULE,
+	},
+};
+
+static struct platform_device *soc, *uda, *dma;
+
+static int assabet_init(void)
+{
+	unsigned long flags;
+	int ret;
+
+	if (!machine_is_assabet())
+		return -ENODEV;
+
+	ret = gpio_request_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+	if (ret)
+		return ret;
+
+	/*
+	 * Request these irrespective of whether they succeed.  Wish we
+	 * could have our shared I2C/L3 driver from v2.4 kernels, but
+	 * alas this would require a buggeration of the ASoC code.
+	 * Not only this, but we have no way to do any kind of locking
+	 * between the UDA134x L3 driver and I2C.
+	 */
+	gpio_request_one(GPIO_L3_DAT, GPIOF_OUT_INIT_LOW, "l3_dat");
+	gpio_request_one(GPIO_L3_SCK, GPIOF_OUT_INIT_LOW, "l3_sck");
+
+	/*
+	 * Put the LRCK signal into a known good state: early Assabets
+	 * do not mask the LRCK signal when the codec is powered down,
+	 * and it defaults to logic 1.  So, first release the reset,
+	 * and then toggle the SFRM signal to set LRCK to zero.
+	 */
+	ASSABET_BCR_clear(ASSABET_BCR_STEREO_LB);
+	assabet_uda1341_reset(0);
+
+	gpio_set_value(GPIO_SFRM, 1);
+	gpio_set_value(GPIO_SFRM, 0);
+
+	local_irq_save(flags);
+	/*
+	 * Ensure SSP pin reassignment is enabled, so that the SSP appears
+	 * on the GPIO pins rather than the deddicated UART4 pins.
+	 */
+	PPAR |= PPAR_SPR;
+
+	/* Configure the SSP input clock, and most SSP output signals */
+	GAFR |= GPIO_SSP_TXD | GPIO_SSP_RXD | GPIO_SSP_SCLK | GPIO_SSP_CLK;
+
+	local_irq_restore(flags);
+
+	soc = platform_device_alloc("assabet-asoc", -1);
+	uda = platform_device_alloc(assabet_asoc_dais[0].codec_name, -1);
+	dma = platform_device_alloc("soc-dmaengine", -1);
+	if (!soc || !uda || !dma) {
+		ret = -ENOMEM;
+		goto err_dev_alloc;
+	}
+
+	/* Set some platform data for the UDA1341 codec driver... */
+	uda->dev.platform_data = &uda1341_data;
+
+	/* Set some sensible parents */
+	uda->dev.parent = &soc->dev;
+	dma->dev.parent = &soc->dev;
+
+	ret = platform_device_add(soc);
+	if (ret)
+		goto err_dev_alloc;
+	ret = platform_device_add(uda);
+	if (ret)
+		goto err_dev_add_soc;
+	ret = platform_device_add(dma);
+	if (ret)
+		goto err_dev_add_uda;
+	ret = platform_driver_register(&assabet_asoc_driver);
+	if (ret)
+		goto err_dev_add_dma;
+	return 0;
+
+ err_dev_add_dma:
+	platform_device_del(dma);
+ err_dev_add_uda:
+	platform_device_del(uda);
+ err_dev_add_soc:
+	platform_device_del(soc);
+ err_dev_alloc:
+	if (dma)
+		platform_device_put(dma);
+	if (uda)
+		platform_device_put(uda);
+	if (soc)
+		platform_device_put(soc);
+
+	gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+
+	return ret;
+}
+module_init(assabet_init);
+
+static void assabet_exit(void)
+{
+	if (!machine_is_assabet())
+		return;
+
+	platform_driver_unregister(&assabet_asoc_driver);
+	platform_device_unregister(dma);
+	platform_device_unregister(uda);
+
+	/* Don't try to kfree this data */
+	soc->dev.platform_data = NULL;
+
+	platform_device_unregister(soc);
+	gpio_free_array(ssp_gpio, ARRAY_SIZE(ssp_gpio));
+}
+module_exit(assabet_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sa11x0/h3xxx.c b/sound/soc/sa11x0/h3xxx.c
new file mode 100644
index 000000000000..baed22e2dc2a
--- /dev/null
+++ b/sound/soc/sa11x0/h3xxx.c
@@ -0,0 +1,317 @@
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/gpio.h> /* HACK */
+#include <linux/gpio/consumer.h>
+#include <linux/module.h>
+
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/uda134x.h>
+
+#include <asm/mach-types.h>
+#include <mach/hardware.h>
+#include "ssp.h"
+
+struct h3xxx_card {
+	struct snd_soc_card card;
+	struct clk *clk_ext;
+	struct gpio_desc *reset;
+	struct gpio_desc *aud_pwr;
+	struct gpio_desc *amp_pwr;
+};
+
+static int h3xxx_asoc_startup(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card);
+	int ret = 0;
+
+	if (!rtd->cpu_dai->active)
+		ret = clk_prepare_enable(hc->clk_ext);
+
+	return ret;
+}
+
+static int h3xxx_asoc_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card);
+	unsigned long rate = params_rate(params);
+	unsigned long sclk;
+	int ret;
+
+	switch (rate) {
+	case 24000:
+	case 32000:
+	case 48000:
+		sclk = 12288000;
+		break;
+	case 22050:
+	case 29400:
+	case 44100:
+		sclk = 11289600;
+		break;
+	case 8000:
+	case 10666:
+	case 16000:
+		sclk = 4096000;
+		break;
+	case 10985:
+	case 14647:
+	case 21970:
+		sclk = 5624500;
+		break;
+	default:
+		pr_warn("invalid rate: %luHz\n", rate);
+		return -EINVAL;
+	}
+
+	ret = clk_set_rate(hc->clk_ext, sclk);
+	if (ret) {
+		pr_warn("clk_set_rate: %d (%luHz)\n", ret, sclk);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(rtd->codec_dai, 0, sclk, SND_SOC_CLOCK_IN);
+	if (ret < 0) {
+		pr_warn("snd_soc_dai_set_sysclk(codec): %d (%luHz)\n", ret, sclk);
+		return ret;
+	}
+
+	ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, SA11X0_SSP_CLK_EXT,
+				     sclk, SND_SOC_CLOCK_IN);
+	if (ret < 0)  {
+		pr_warn("snd_soc_dai_set_sysclk(cpu): %d (%luHz)\n", ret, sclk);
+		return ret;
+	}
+
+	return ret;
+}
+
+static void h3xxx_asoc_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(rtd->card);
+
+	if (!rtd->cpu_dai->active)
+		clk_disable_unprepare(hc->clk_ext);
+}
+
+static const struct snd_soc_ops h3xxx_asoc_ops = {
+	.startup = h3xxx_asoc_startup,
+	.hw_params = h3xxx_asoc_hw_params,
+	.shutdown = h3xxx_asoc_shutdown,
+};
+
+static struct snd_soc_dai_link h3xxx_asoc_dais[1] = {
+	{
+		.name = "h3xxx",
+		.stream_name = "iPAQ Playback/Capture",
+		.cpu_dai_name = "sa11x0-ssp",
+		.codec_name = "uda134x-codec",
+		.codec_dai_name = "uda134x-hifi",
+		.platform_name = "sa11x0-ssp",
+		.dai_fmt = SND_SOC_DAIFMT_RIGHT_J | SND_SOC_DAIFMT_NB_NF |
+			   SND_SOC_DAIFMT_CBS_CFS,
+		.ops = &h3xxx_asoc_ops,
+	},
+};
+
+static int h3xxx_asoc_pwramp_event(struct snd_soc_dapm_widget *w,
+	struct snd_kcontrol *k, int event)
+{
+	struct h3xxx_card *hc = snd_soc_card_get_drvdata(w->dapm->card);
+
+	gpiod_set_value(hc->amp_pwr, SND_SOC_DAPM_EVENT_ON(event));
+
+	return 0;
+}
+
+static const struct snd_soc_dapm_widget h3xxx_asoc_dapm_widgets[] = {
+	SND_SOC_DAPM_SPK("Power Amp", h3xxx_asoc_pwramp_event),
+};
+
+static const struct snd_soc_dapm_route h3xxx_asoc_dapm_routes[] = {
+	{ "Power Amp", NULL, "VOUTL" },
+	{ "Power Amp", NULL, "VOUTR" },
+};
+
+static const struct snd_soc_card h3xxx_asoc_card_template = {
+	.name = "H3xxx",
+	.long_name = "iPAQ H3xxx HiFi Audio",
+	.owner = THIS_MODULE,
+	.dai_link = h3xxx_asoc_dais,
+	.num_links = ARRAY_SIZE(h3xxx_asoc_dais),
+	.dapm_widgets = h3xxx_asoc_dapm_widgets,
+	.num_dapm_widgets = ARRAY_SIZE(h3xxx_asoc_dapm_widgets),
+	.dapm_routes = h3xxx_asoc_dapm_routes,
+	.num_dapm_routes = ARRAY_SIZE(h3xxx_asoc_dapm_routes),
+};
+
+static struct h3xxx_card *card; /* HACK */
+
+static int h3xxx_asoc_probe(struct platform_device *pdev)
+{
+	struct h3xxx_card *hc;
+
+	GAFR |= GPIO_SSP_CLK;
+	GPDR &= ~GPIO_SSP_CLK;
+
+	hc = devm_kzalloc(&pdev->dev, sizeof(*hc), GFP_KERNEL);
+	if (!hc)
+		return -ENOMEM;
+
+	memcpy(&hc->card, &h3xxx_asoc_card_template, sizeof(hc->card));
+	hc->card.dev = &pdev->dev;
+
+	hc->reset = devm_gpiod_get(&pdev->dev, "audio-reset", GPIOD_OUT_LOW);
+	if (IS_ERR(hc->reset))
+		return PTR_ERR(hc->reset);
+
+	/* should be regulators? */
+	hc->aud_pwr = devm_gpiod_get(&pdev->dev, "audio-power", GPIOD_OUT_LOW);
+	if (IS_ERR(hc->aud_pwr))
+		return PTR_ERR(hc->aud_pwr);
+
+	hc->amp_pwr = devm_gpiod_get(&pdev->dev, "amp-power", GPIOD_OUT_LOW);
+	if (IS_ERR(hc->amp_pwr))
+		return PTR_ERR(hc->amp_pwr);
+
+	hc->clk_ext = devm_clk_get(&pdev->dev, "ext");
+	if (IS_ERR(hc->clk_ext))
+		return PTR_ERR(hc->clk_ext);
+
+	snd_soc_card_set_drvdata(&hc->card, hc);
+
+	card = hc;
+
+	return snd_soc_register_card(&hc->card);
+}
+
+static int h3xxx_asoc_remove(struct platform_device *pdev)
+{
+	struct snd_soc_card *card = platform_get_drvdata(pdev);
+
+	snd_soc_unregister_card(card);
+
+	card = NULL;
+
+	return 0;
+}
+
+static struct platform_driver h3xxx_asoc_driver = {
+	.probe = h3xxx_asoc_probe,
+	.remove = h3xxx_asoc_remove,
+	.driver = {
+		.name = "h3xxx-asoc",
+	},
+};
+
+/* HACK */
+static void h3xxx_asoc_setdat(struct l3_pins *adap, int v)
+{
+	gpio_set_value(14, v);
+}
+
+static void h3xxx_asoc_setclk(struct l3_pins *adap, int v)
+{
+	gpio_set_value(16, v);
+}
+
+static void h3xxx_asoc_setmode(struct l3_pins *adap, int v)
+{
+	gpio_set_value(15, v);
+}
+
+static void h3xxx_asoc_power(int on)
+{
+	struct h3xxx_card *hc = card;
+
+	if (!hc)
+		return;
+
+	if (on) {
+		gpiod_set_value_cansleep(hc->aud_pwr, 1);
+		gpiod_set_value_cansleep(hc->reset, 1);
+		mdelay(1);
+	} else {
+		gpiod_set_value_cansleep(hc->reset, 0);
+		gpiod_set_value_cansleep(hc->aud_pwr, 0);
+	}
+}
+
+static struct uda134x_platform_data uda134x_data = {
+	.l3 = {
+		.setdat = h3xxx_asoc_setdat,
+		.setclk = h3xxx_asoc_setclk,
+		.setmode = h3xxx_asoc_setmode,
+		.data_hold = 1,
+		.data_setup = 1,
+		.clock_high = 1,
+		.mode_hold = 1,
+		.mode = 1,
+		.mode_setup = 1,
+	},
+	.model = UDA134X_UDA1341,
+	.power = h3xxx_asoc_power,
+};
+static struct platform_device *h3xxx_asoc, *h3xxx_uda;
+static void h3xxx_hack_init(void)
+{
+	gpio_direction_output(14, 0);
+	gpio_direction_output(15, 0);
+	gpio_direction_output(16, 0);
+
+	h3xxx_asoc = platform_device_register_simple("h3xxx-asoc", -1,
+						     NULL, 0);
+	if (IS_ERR(h3xxx_asoc))
+		h3xxx_asoc = NULL;
+	h3xxx_uda = platform_device_register_data(NULL, "uda134x-codec", -1,
+						    &uda134x_data,
+						    sizeof(uda134x_data));
+	if (IS_ERR(h3xxx_uda))
+		h3xxx_uda = NULL;
+}
+
+static void h3xxx_hack_exit(void)
+{
+	if (h3xxx_asoc) {
+		platform_device_unregister(h3xxx_asoc);
+		h3xxx_asoc = NULL;
+	}
+	if (h3xxx_uda) {
+		platform_device_unregister(h3xxx_uda);
+		h3xxx_uda = NULL;
+	}
+}
+
+static int h3xxx_asoc_init(void)
+{
+	int ret;
+
+	if (machine_is_h3100() || machine_is_h3600())
+		h3xxx_hack_init();
+
+	ret = platform_driver_register(&h3xxx_asoc_driver);
+	if (ret)
+		goto err_driver;
+
+	return 0;
+
+err_driver:
+	h3xxx_hack_exit();
+	return ret;
+}
+module_init(h3xxx_asoc_init);
+
+static void h3xxx_asoc_exit(void)
+{
+	platform_driver_unregister(&h3xxx_asoc_driver);
+	h3xxx_hack_exit();
+}
+module_exit(h3xxx_asoc_exit)
+
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/sa11x0/ssp.c b/sound/soc/sa11x0/ssp.c
new file mode 100644
index 000000000000..2656df529f96
--- /dev/null
+++ b/sound/soc/sa11x0/ssp.c
@@ -0,0 +1,411 @@
+/*
+ * ASoC SA11x0 SSP DAI driver
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * 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.
+ *
+ * The SA11x0 SSP interface gives us four signals: TXD, RXD, SFRM and SCLK,
+ * and SFRM pulses for each and every word transmitted.  These signals can
+ * be routed to GPIO10(TXD), GPIO11(RXD), GPIO12(SCLK), and GPIO13(SFRM)
+ * when the PPAR_SPR bit it set, along with the appropriate GAFR and GPDR
+ * configuration.  If PPAR_SPR is clear, the SSP shares the same pins with
+ * the MCP.
+ */
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+#include <linux/slab.h>
+
+#include <sound/core.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "ssp.h"
+
+#define SA11X0_SSP_CHANNELS_MIN	1
+#define SA11X0_SSP_CHANNELS_MAX	8
+
+/*
+ * This isn't really up to us - it depends how the board implements the
+ * clocking, whether the board uses the on-board clock source, or whether
+ * the SSP is clocked via GPIO19.
+ */
+#define SA11X0_SSP_RATES \
+	(SNDRV_PCM_RATE_8000_192000 | SNDRV_PCM_RATE_CONTINUOUS)
+#define SA11X0_SSP_FORMATS \
+	(SNDRV_PCM_FORMAT_S8		| SNDRV_PCM_FORMAT_U8		| \
+	 SNDRV_PCM_FORMAT_S16_LE	| SNDRV_PCM_FORMAT_U16_LE)
+
+#define BUFFER_SIZE_MAX		65536
+#define PERIOD_SIZE_MIN		32
+#define PERIOD_SIZE_MAX		16384
+#define PERIODS_MIN		2
+#define PERIODS_MAX		256
+
+#define SSCR0		0x60
+#define SSCR0_DSS	(15 << 0)	/* s */
+#define SSCR0_FRF_MOT	(0 << 4)
+#define SSCR0_FRF_TI	(1 << 4)
+#define SSCR0_FRF_NAT	(2 << 4)
+#define SSCR0_SSE	(1 << 7)	/* s */
+#define SSCR0_SCR	(0xff << 8)	/* s */
+#define SSCR1		0x64
+#define SSCR1_RIE	(1 << 0)
+#define SSCR1_TIE	(1 << 1)
+#define SSCR1_LBM	(1 << 2)
+#define SSCR1_SPO	(1 << 3)
+#define SSCR1_SP	(1 << 4)
+#define SSCR1_ECS	(1 << 5)	/* s */
+#define SSDR		0x6c
+#define SSSR		0x74
+
+struct ssp_priv {
+	void __iomem *base;
+	unsigned long clk_hz;
+	u32 cr0;
+	u32 cr1;
+	struct snd_dmaengine_dai_dma_data tx;
+	struct snd_dmaengine_dai_dma_data rx;
+};
+
+static const struct snd_pcm_hardware sa11x0_ssp_pcm_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE |
+				  SNDRV_PCM_INFO_RESUME,
+	.period_bytes_min	= PERIOD_SIZE_MIN,
+	.period_bytes_max	= PERIOD_SIZE_MAX,
+	.periods_min		= PERIODS_MIN,
+	.periods_max		= PERIODS_MAX,
+	.buffer_bytes_max	= BUFFER_SIZE_MAX,
+	/* This is for playback only */
+	.fifo_size		= 8 * 2,
+};
+
+/*
+ * Constrain the rate according to the bitrate(s) that the interface can
+ * do, which is determined by its input clock and the range of divisors
+ * that we can program (1 to 256 with an initial /2).
+ */
+static int
+sa11x0_ssp_rate_constraint(struct snd_pcm_hw_params *params,
+	struct snd_pcm_hw_rule *rule)
+{
+	struct snd_interval *p = hw_param_interval(params, rule->var);
+	struct ssp_priv *ssp = rule->private;
+	snd_pcm_format_t fmt = params_format(params);
+	struct snd_ratnum ratnum;
+
+	ratnum.num = ssp->clk_hz /
+		(2 * snd_pcm_format_width(fmt) * params_channels(params));
+	ratnum.den_min = 1;
+	ratnum.den_max = 256;
+	ratnum.den_step = 1;
+
+	return snd_interval_ratnum(p, 1, &ratnum, &params->rate_num,
+				   &params->rate_den);
+}
+
+static int
+sa11x0_ssp_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+	int ret;
+
+	ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_PERIOD_BYTES, 32);
+	if (ret)
+		return ret;
+
+	ret = snd_pcm_hw_constraint_step(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 32);
+	if (ret)
+		return ret;
+
+	ret = snd_pcm_hw_rule_add(substream->runtime, 0,
+		SNDRV_PCM_HW_PARAM_RATE, sa11x0_ssp_rate_constraint, ssp,
+		SNDRV_PCM_HW_PARAM_RATE, SNDRV_PCM_HW_PARAM_FORMAT,
+		SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+	if (ret)
+		return ret;
+
+	if (!dai->active) {
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+		writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+		ssp->cr0 |= SSCR0_SSE;
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+	}
+
+	return 0;
+}
+
+static void sa11x0_ssp_shutdown(struct snd_pcm_substream *substream,
+	struct snd_soc_dai *dai)
+{
+	if (!dai->active) {
+		struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+		ssp->cr0 &= ~SSCR0_SSE;
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+	}
+}
+
+static int sa11x0_ssp_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params, struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+	snd_pcm_format_t fmt = params_format(params);
+	int width = snd_pcm_format_width(fmt);
+	unsigned int divisor;
+	u32 cr0;
+
+	if (width < 0)
+		return width;
+	if (width < 4 || width > 16)
+		return -EINVAL;
+
+	divisor = ssp->clk_hz / (2 * width * params_channels(params) *
+				params_rate(params));
+	if (divisor == 0)
+		return -EINVAL;
+
+	/* Set the bit-width for the format and the divisor */
+	cr0 = ssp->cr0 & ~(SSCR0_DSS | SSCR0_SCR);
+	/* rate_den is calculated by snd_interval_ratnum() above. */
+	cr0 |= (width - 1) | (divisor - 1) << 8;
+	if (cr0 != ssp->cr0) {
+		ssp->cr0 = cr0;
+		writel_relaxed(cr0, ssp->base + SSCR0);
+	}
+	writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+
+	return 0;
+}
+
+static int sa11x0_ssp_set_sysclk(struct snd_soc_dai *dai, int clk_id,
+	unsigned int freq, int dir)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	switch (clk_id) {
+	case SA11X0_SSP_CLK_INT:
+		ssp->cr1 &= ~SSCR1_ECS;
+		ssp->clk_hz = 3686400;
+		break;
+
+	case SA11X0_SSP_CLK_EXT:
+		/*
+		 * The SA11x0 developer's manual section 11.12.10.6 says the
+		 * external clock can be up to 3.6864MHz, but iPAQs supply
+		 * this with up to a 12.288MHz clock, which appears to work.
+		 * We trust that the frequency is correct.
+		 */
+		if (dir != SND_SOC_CLOCK_IN)
+			return -EINVAL;
+		ssp->cr1 |= SSCR1_ECS;
+		ssp->clk_hz = freq;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int sa11x0_ssp_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	/* We always generate the clock and frm signals */
+	if ((fmt & SND_SOC_DAIFMT_MASTER_MASK) != SND_SOC_DAIFMT_CBS_CFS)
+		return -EINVAL;
+
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_NB_NF:
+		ssp->cr1 &= ~SSCR1_SPO;
+		break;
+
+	case SND_SOC_DAIFMT_IB_NF:
+		ssp->cr1 |= SSCR1_SPO;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct snd_soc_dai_ops sa11x0_ssp_ops = {
+	.startup = sa11x0_ssp_startup,
+	.shutdown = sa11x0_ssp_shutdown,
+	.hw_params = sa11x0_ssp_hw_params,
+	.set_sysclk = sa11x0_ssp_set_sysclk,
+	.set_fmt = sa11x0_ssp_set_fmt,
+};
+
+static int sa11x0_ssp_probe(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	/* Default to TI mode and a bit-width of 16 */
+	ssp->cr0 = SSCR0_FRF_TI | 15;
+
+	/*
+	 * Set the DMA data now - it's needed for the dmaengine backend to
+	 * obtain its DMA channel, in turn its struct device, and therefore
+	 * a struct device to allocate DMA memory against.
+	 */
+	dai->playback_dma_data = &ssp->tx;
+	dai->capture_dma_data = &ssp->rx;
+
+	return 0;
+}
+
+static int sa11x0_ssp_remove(struct snd_soc_dai *dai)
+{
+	return 0;
+}
+
+#ifdef CONFIG_PM
+static int sa11x0_ssp_suspend(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	writel_relaxed(ssp->cr0 & ~SSCR0_SSE, ssp->base + SSCR0);
+
+	return 0;
+}
+
+static int sa11x0_ssp_resume(struct snd_soc_dai *dai)
+{
+	struct ssp_priv *ssp = snd_soc_dai_get_drvdata(dai);
+
+	writel_relaxed(ssp->cr0 & ~SSCR0_SSE, ssp->base + SSCR0);
+	writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+	if (ssp->cr0 & SSCR0_SSE)
+		writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+
+	return 0;
+}
+#else
+#define sa11x0_ssp_suspend NULL
+#define sa11x0_ssp_resume NULL
+#endif
+
+static struct snd_soc_dai_driver sa11x0_ssp_driver = {
+	.probe = sa11x0_ssp_probe,
+	.remove = sa11x0_ssp_remove,
+	.suspend = sa11x0_ssp_suspend,
+	.resume = sa11x0_ssp_resume,
+	.ops = &sa11x0_ssp_ops,
+	.capture = {
+		.channels_min = SA11X0_SSP_CHANNELS_MIN,
+		.channels_max = SA11X0_SSP_CHANNELS_MAX,
+		.rates = SA11X0_SSP_RATES,
+		.formats = SA11X0_SSP_FORMATS,
+	},
+	.playback = {
+		.channels_min = SA11X0_SSP_CHANNELS_MIN,
+		.channels_max = SA11X0_SSP_CHANNELS_MAX,
+		.rates = SA11X0_SSP_RATES,
+		.formats = SA11X0_SSP_FORMATS,
+	},
+	.symmetric_rates = 1,
+};
+
+static const struct snd_soc_component_driver sa11x0_ssp_component = {
+	.name = "sa11x0-ssp",
+};
+
+static const struct snd_dmaengine_pcm_config sa11x0_ssp_pcm_config = {
+	.prepare_slave_config = snd_dmaengine_pcm_prepare_slave_config,
+	.pcm_hardware = &sa11x0_ssp_pcm_hardware,
+	.prealloc_buffer_size = SZ_64K,
+};
+
+static int sa11x0_ssp_plat_probe(struct platform_device *pdev)
+{
+	struct ssp_priv *ssp;
+	struct resource *res;
+	int ret;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!res)
+		return -EINVAL;
+
+	ssp = devm_kzalloc(&pdev->dev, sizeof(*ssp), GFP_KERNEL);
+	if (!ssp)
+		return -ENOMEM;
+
+	/* Assume internal clock, as that's what we program initially */
+	ssp->clk_hz = 3686400;
+
+	/*
+	 * FIXME: The generic ASoC dmaengine backend does not allow us to set
+	 * a different FIFO size for the transmit and receive sides of the
+	 * SSP: the transmit has a 8 x 16bit FIFO, but the receive side has
+	 * a 12 x 16bit FIFO.
+	 */
+	ssp->tx.addr = res->start + SSDR;
+	ssp->tx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+	ssp->tx.maxburst = 4;
+	ssp->tx.filter_data = "Ser4SSPTr";
+
+	ssp->rx.addr = res->start + SSDR;
+	ssp->rx.addr_width = DMA_SLAVE_BUSWIDTH_2_BYTES;
+	ssp->rx.maxburst = 4;
+	ssp->rx.filter_data = "Ser4SSPRc";
+
+	ssp->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(ssp->base))
+		return PTR_ERR(ssp->base);
+
+	writel_relaxed(ssp->cr0, ssp->base + SSCR0);
+	writel_relaxed(ssp->cr1, ssp->base + SSCR1);
+
+	platform_set_drvdata(pdev, ssp);
+
+	ret = snd_dmaengine_pcm_register(&pdev->dev, &sa11x0_ssp_pcm_config,
+					 SND_DMAENGINE_PCM_FLAG_COMPAT);
+	if (ret)
+		return ret;
+
+	ret = snd_soc_register_component(&pdev->dev, &sa11x0_ssp_component,
+					 &sa11x0_ssp_driver, 1);
+	if (ret)
+		snd_dmaengine_pcm_unregister(&pdev->dev);
+
+	return ret;
+}
+
+static int sa11x0_ssp_plat_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_component(&pdev->dev);
+	snd_dmaengine_pcm_unregister(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver sa11x0_ssp_plat_driver = {
+	.driver = {
+		.name = "sa11x0-ssp",
+		.owner = THIS_MODULE,
+	},
+	.probe = sa11x0_ssp_plat_probe,
+	.remove = sa11x0_ssp_plat_remove,
+};
+module_platform_driver(sa11x0_ssp_plat_driver);
+
+MODULE_AUTHOR("Russell King <linux@arm.linux.org.uk>");
+MODULE_DESCRIPTION("SA11x0 SSP/PCM SoC Interface");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:sa11x0-ssp");
diff --git a/sound/soc/sa11x0/ssp.h b/sound/soc/sa11x0/ssp.h
new file mode 100644
index 000000000000..22f1e5b8f45c
--- /dev/null
+++ b/sound/soc/sa11x0/ssp.h
@@ -0,0 +1,11 @@
+#ifndef SA11X0_SSP_H
+#define SA11X0_SSP_H
+
+/* SSP clock sources */
+#define SA11X0_SSP_CLK_INT	0x11000000
+#define SA11X0_SSP_CLK_EXT	0x11000001
+
+/* SSP audio dividers */
+#define SA11X0_SSP_DIV_SCR	0x11000100
+
+#endif
diff --git a/sound/soc/soc-dmaengine.c b/sound/soc/soc-dmaengine.c
new file mode 100644
index 000000000000..4b1bfce9e641
--- /dev/null
+++ b/sound/soc/soc-dmaengine.c
@@ -0,0 +1,591 @@
+/*
+ * Generic ASoC DMA engine backend
+ *
+ * Copyright (C) 2012 Russell King
+ *
+ * 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.
+ *
+ * We expect the DMA engine to give accurate residue information,
+ * returning the number of bytes left to complete to the requested
+ * cookie.  We also expect the DMA engine to be able to resume
+ * submitted descriptors after a suspend/resume cycle.
+ */
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#include <sound/core.h>
+#include <sound/soc-dmaengine.h>
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+
+#define BUFFER_SIZE_MAX		65536
+#define PERIOD_SIZE_MIN		32
+#define PERIOD_SIZE_MAX		16384
+#define PERIODS_MIN		2
+#define PERIODS_MAX		256
+
+struct buf_info {
+	struct scatterlist sg;
+	dma_cookie_t cookie;
+};
+
+struct soc_dma_chan {
+	const struct soc_dma_config *conf;
+	spinlock_t lock;
+	struct dma_chan *chan;
+	struct dma_slave_config cfg;
+	enum dma_transfer_direction dir;
+	unsigned nr_periods;
+	unsigned sg_index;
+	unsigned stopped;
+	unsigned cyclic;
+	dma_cookie_t cyclic_cookie;
+	struct buf_info buf[PERIODS_MAX];
+};
+
+struct soc_dma_info {
+	struct soc_dma_chan *chan[2];
+};
+
+static const struct snd_pcm_hardware soc_dmaengine_hardware = {
+	.info			= SNDRV_PCM_INFO_MMAP |
+				  SNDRV_PCM_INFO_MMAP_VALID |
+				  SNDRV_PCM_INFO_INTERLEAVED |
+				  SNDRV_PCM_INFO_PAUSE |
+				  SNDRV_PCM_INFO_RESUME,
+	.period_bytes_min	= PERIOD_SIZE_MIN,
+	.period_bytes_max	= PERIOD_SIZE_MAX,
+	.periods_min		= PERIODS_MIN,
+	.periods_max		= PERIODS_MAX,
+	.buffer_bytes_max	= BUFFER_SIZE_MAX,
+};
+
+static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
+	struct soc_dma_chan *dma);
+
+static void soc_dmaengine_callback(void *data)
+{
+	struct snd_pcm_substream *substream = data;
+	struct soc_dma_chan *dma = substream->runtime->private_data;
+	int ret;
+
+	snd_pcm_period_elapsed(substream);
+
+	if (!dma->stopped && !dma->cyclic) {
+		spin_lock(&dma->lock);
+		ret = soc_dmaengine_submit(substream, dma);
+		spin_unlock(&dma->lock);
+
+		if (ret == 0)
+			dma_async_issue_pending(dma->chan);
+		else
+			pr_err("%s: failed to submit next DMA buffer\n", __func__);
+	}
+}
+
+static int soc_dmaengine_submit(struct snd_pcm_substream *substream,
+	struct soc_dma_chan *dma)
+{
+	struct dma_async_tx_descriptor *tx;
+	struct dma_chan *ch = dma->chan;
+	struct buf_info *buf;
+	unsigned sg_index;
+
+	sg_index = dma->sg_index;
+
+	buf = &dma->buf[sg_index];
+
+	tx = dmaengine_prep_slave_sg(ch, &buf->sg, 1, dma->dir,
+		DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+	if (tx) {
+		tx->callback = soc_dmaengine_callback;
+		tx->callback_param = substream;
+
+		buf->cookie = dmaengine_submit(tx);
+
+		sg_index++;
+		if (sg_index >= dma->nr_periods)
+			sg_index = 0;
+		dma->sg_index = sg_index;
+	}
+
+	return tx ? 0 : -ENOMEM;
+}
+
+static int soc_dmaengine_start(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	unsigned long flags;
+	unsigned i;
+	int ret = 0;
+
+	if (!dma->cyclic) {
+		spin_lock_irqsave(&dma->lock, flags);
+		for (i = 0; i < dma->nr_periods; i++) {
+			ret = soc_dmaengine_submit(substream, dma);
+			if (ret)
+				break;
+		}
+		spin_unlock_irqrestore(&dma->lock, flags);
+		if (ret == 0) {
+			dma->stopped = 0;
+			dma_async_issue_pending(dma->chan);
+		} else {
+			dma->stopped = 1;
+			dmaengine_terminate_all(dma->chan);
+		}
+	} else {
+		struct dma_async_tx_descriptor *tx;
+
+		tx = dmaengine_prep_dma_cyclic(dma->chan, runtime->dma_addr,
+			snd_pcm_lib_buffer_bytes(substream),
+			snd_pcm_lib_period_bytes(substream), dma->dir,
+			DMA_CTRL_ACK | DMA_PREP_INTERRUPT);
+		if (tx) {
+			tx->callback = soc_dmaengine_callback;
+			tx->callback_param = substream;
+
+			dma->cyclic_cookie = dmaengine_submit(tx);
+			dma_async_issue_pending(dma->chan);
+		}
+	}
+
+	return ret;
+}
+
+static int soc_dmaengine_stop(struct snd_pcm_substream *substream)
+{
+	struct soc_dma_chan *dma = substream->runtime->private_data;
+
+	dma->stopped = 1;
+	dmaengine_terminate_all(dma->chan);
+
+	return 0;
+}
+
+static int soc_dmaengine_open(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_soc_pcm_runtime *rtd = substream->private_data;
+	struct soc_dma_info *info = snd_soc_platform_get_drvdata(rtd->platform);
+	struct soc_dma_chan *dma = info->chan[substream->stream];
+	int ret = 0;
+
+	if (!dma)
+		return -EINVAL;
+
+	runtime->hw = soc_dmaengine_hardware;
+	runtime->hw.fifo_size = dma->conf->fifo_size;
+
+	if (dma->conf->align) {
+		/*
+		 * FIXME: Ideally, there should be some way to query
+		 * this from the DMA engine itself.
+		 *
+		 * It would also be helpful to know the maximum size
+		 * a scatterlist entry can be to set the period size.
+		 */
+		ret = snd_pcm_hw_constraint_step(runtime, 0,
+			SNDRV_PCM_HW_PARAM_PERIOD_BYTES, dma->conf->align);
+		if (ret)
+			goto err;
+
+		ret = snd_pcm_hw_constraint_step(runtime, 0,
+			SNDRV_PCM_HW_PARAM_BUFFER_BYTES, dma->conf->align);
+		if (ret)
+			goto err;
+	}
+
+	runtime->private_data = dma;
+
+ err:
+	return ret;
+}
+
+static int soc_dmaengine_close(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int soc_dmaengine_hw_params(struct snd_pcm_substream *substream,
+	struct snd_pcm_hw_params *params)
+{
+	int ret = snd_pcm_lib_malloc_pages(substream,
+			params_buffer_bytes(params));
+
+	return ret < 0 ? ret : 0;
+}
+
+static int soc_dmaengine_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	size_t buf_size = snd_pcm_lib_buffer_bytes(substream);
+	size_t period = snd_pcm_lib_period_bytes(substream);
+	dma_addr_t addr = runtime->dma_addr;
+	unsigned i;
+
+	/* Create an array of sg entries, one for each period */
+	for (i = 0; i < PERIODS_MAX && buf_size; i++) {
+		BUG_ON(buf_size < period);
+
+		sg_dma_address(&dma->buf[i].sg) = addr;
+		sg_dma_len(&dma->buf[i].sg) = period;
+
+		addr += period;
+		buf_size -= period;
+	}
+
+	if (buf_size) {
+		pr_err("DMA buffer size not a multiple of the period size: residue=%zu\n", buf_size);
+		return -EINVAL;
+	}
+
+	dma->nr_periods = i;
+	dma->sg_index = 0;
+
+	return 0;
+}
+
+static int soc_dmaengine_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	int ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		ret = soc_dmaengine_start(substream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+		ret = soc_dmaengine_stop(substream);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ret = dmaengine_pause(dma->chan);
+		break;
+
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		ret = dmaengine_resume(dma->chan);
+		break;
+
+	default:
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static snd_pcm_uframes_t soc_dmaengine_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct soc_dma_chan *dma = runtime->private_data;
+	struct dma_chan *ch = dma->chan;
+	struct dma_tx_state state;
+	unsigned bytes;
+
+	if (dma->cyclic) {
+		ch->device->device_tx_status(ch, dma->cyclic_cookie, &state);
+
+		bytes = snd_pcm_lib_buffer_bytes(substream) - state.residue;
+	} else {
+		unsigned index, pos;
+		size_t period = snd_pcm_lib_period_bytes(substream);
+		unsigned long flags;
+		enum dma_status status;
+		dma_cookie_t cookie;
+
+		/*
+		 * Get the next-to-be-submitted index, and the last submitted
+		 * cookie value.  We use this to obtain the DMA engine state.
+		 */
+		spin_lock_irqsave(&dma->lock, flags);
+		index = dma->sg_index;
+		do {
+			cookie = dma->buf[index].cookie;
+			status = ch->device->device_tx_status(ch, cookie, &state);
+			if (status == DMA_SUCCESS) {
+				index++;
+				if (index > dma->nr_periods)
+					index = 0;
+			}
+		} while (status == DMA_SUCCESS);
+		spin_unlock_irqrestore(&dma->lock, flags);
+
+		/* The end of the current DMA buffer */
+		pos = (index + 1) * period;
+		/* Now take off the residue */
+		bytes = pos - state.residue;
+		/* And correct for wrap-around */
+		if (bytes >= period * dma->nr_periods)
+			bytes -= period * dma->nr_periods;
+	}
+
+	return bytes_to_frames(runtime, bytes);
+}
+
+static int soc_dmaengine_mmap(struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_dma_buffer *buf = runtime->dma_buffer_p;
+
+	return dma_mmap_writecombine(buf->dev.dev, vma,
+		runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
+}
+
+static struct snd_pcm_ops soc_dmaengine_ops = {
+	.open = soc_dmaengine_open,
+	.close = soc_dmaengine_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = soc_dmaengine_hw_params,
+	.hw_free = snd_pcm_lib_free_pages,
+	.prepare = soc_dmaengine_prepare,
+	.trigger = soc_dmaengine_trigger,
+	.pointer = soc_dmaengine_pointer,
+	.mmap = soc_dmaengine_mmap,
+};
+
+static struct soc_dma_chan *soc_dmaengine_alloc(void)
+{
+	struct soc_dma_chan *dma = kzalloc(sizeof(*dma), GFP_KERNEL);
+	unsigned i;
+
+	if (dma) {
+		spin_lock_init(&dma->lock);
+		for (i = 0; i < PERIODS_MAX; i++)
+			sg_init_table(&dma->buf[i].sg, 1);
+	}
+	return dma;
+}
+
+static struct dma_chan *soc_dmaengine_get(enum dma_transaction_type type,
+	struct soc_dma_config *cfg)
+{
+	dma_cap_mask_t m;
+
+	dma_cap_zero(m);
+	dma_cap_set(type, m);
+	return dma_request_channel(m, cfg->filter, cfg->data);
+}
+
+static int soc_dmaengine_request(struct soc_dma_chan *dma,
+	struct soc_dma_config *cfg, unsigned stream)
+{
+	int ret;
+
+	dma->conf = cfg;
+	dma->chan = soc_dmaengine_get(DMA_CYCLIC, cfg);
+	if (!dma->chan)
+		dma->chan = soc_dmaengine_get(DMA_SLAVE, cfg);
+	else
+		dma->cyclic = 1;
+	if (!dma->chan) {
+		ret = -ENXIO;
+		goto err_dma_req;
+	}
+
+	if (stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		dma->dir = DMA_MEM_TO_DEV;
+		dma->cfg.direction = DMA_MEM_TO_DEV;
+		dma->cfg.dst_addr = cfg->reg;
+		dma->cfg.dst_addr_width = cfg->width;
+		dma->cfg.dst_maxburst = cfg->maxburst;
+	} else {
+		dma->dir = DMA_DEV_TO_MEM;
+		dma->cfg.direction = DMA_DEV_TO_MEM;
+		dma->cfg.src_addr = cfg->reg;
+		dma->cfg.src_addr_width = cfg->width;
+		dma->cfg.src_maxburst = cfg->maxburst;
+	}
+
+	ret = dmaengine_slave_config(dma->chan, &dma->cfg);
+	if (ret)
+		goto err_dma_cfg;
+
+	return 0;
+
+ err_dma_cfg:
+	dma_release_channel(dma->chan);
+	dma->chan = NULL;
+ err_dma_req:
+	return ret;
+}
+
+static void soc_dmaengine_release(struct soc_dma_chan *dma)
+{
+	if (dma && dma->chan)
+		dma_release_channel(dma->chan);
+	kfree(dma);
+}
+
+static int soc_dmaengine_preallocate_buffer(struct snd_pcm *pcm,
+	unsigned stream, struct soc_dma_chan *dma)
+{
+	struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+	int ret = 0;
+
+	if (substream) {
+		struct snd_dma_buffer *buf = &substream->dma_buffer;
+
+		buf->dev.type = SNDRV_DMA_TYPE_DEV;
+		buf->dev.dev = dma->chan->device->dev;
+		buf->private_data = NULL;
+		buf->area = dma_alloc_writecombine(buf->dev.dev,
+				BUFFER_SIZE_MAX, &buf->addr, GFP_KERNEL);
+		if (!buf->area)
+			return -ENOMEM;
+
+		buf->bytes = BUFFER_SIZE_MAX;
+	}
+	return ret;
+}
+
+static int soc_dmaengine_pcm_new(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_pcm *pcm = rtd->pcm;
+	struct snd_soc_dai *cpu_dai = rtd->cpu_dai;
+	struct soc_dma_info *info;
+	unsigned stream;
+	int ret = 0;
+
+	if (!cpu_dai)
+		return -EINVAL;
+
+	if (!cpu_dai->playback_dma_data &&
+	    !cpu_dai->capture_dma_data) {
+		pr_err("soc_dmaengine: %s has no cpu_dai DMA data - incorrect probe ordering?\n",
+			rtd->card->name);
+		return -EINVAL;
+	}
+
+	info = kzalloc(sizeof(*info), GFP_KERNEL);
+	if (!info)
+		return -ENOMEM;
+
+	for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
+		struct soc_dma_config *cfg = NULL;
+		struct soc_dma_chan *dma;
+
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			cfg = cpu_dai->playback_dma_data;
+		else if (stream == SNDRV_PCM_STREAM_CAPTURE)
+			cfg = cpu_dai->capture_dma_data;
+
+		if (!cfg)
+			continue;
+
+		info->chan[stream] = dma = soc_dmaengine_alloc();
+		if (!dma) {
+			ret = -ENOMEM;
+			break;
+		}
+
+		ret = soc_dmaengine_request(dma, cfg, stream);
+		if (ret)
+			return ret;
+
+		ret = soc_dmaengine_preallocate_buffer(pcm, stream, dma);
+		if (ret)
+			break;
+	}
+
+	if (ret) {
+		for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
+			soc_dmaengine_release(info->chan[stream]);
+		kfree(info);
+	} else {
+		snd_soc_platform_set_drvdata(rtd->platform, info);
+	}
+
+	return ret;
+}
+
+/*
+ * Use write-combining memory here: the standard ALSA
+ * snd_free_dev_pages() doesn't support this.
+ */
+static void soc_dmaengine_pcm_free(struct snd_pcm *pcm)
+{
+	unsigned stream;
+
+	for (stream = 0; stream < ARRAY_SIZE(pcm->streams); stream++) {
+		struct snd_pcm_substream *substream = pcm->streams[stream].substream;
+		struct snd_dma_buffer *buf;
+
+		if (!substream)
+			continue;
+		buf = &substream->dma_buffer;
+		if (!buf->area)
+			continue;
+
+		if (buf->dev.type == SNDRV_DMA_TYPE_DEV)
+			dma_free_writecombine(buf->dev.dev, buf->bytes,
+					      buf->area, buf->addr);
+		else
+			snd_dma_free_pages(buf);
+		buf->area = NULL;
+	}
+}
+
+/*
+ * This is annoying - we can't have symetry with .pcm_new because .pcm_free
+ * is called after the runtime information has been removed.  It would be
+ * better if we could find somewhere else to store our soc_dma_info pointer.
+ */
+static int soc_dmaengine_plat_remove(struct snd_soc_platform *platform)
+{
+	struct soc_dma_info *info = snd_soc_platform_get_drvdata(platform);
+	unsigned stream;
+
+	for (stream = 0; stream < ARRAY_SIZE(info->chan); stream++)
+		soc_dmaengine_release(info->chan[stream]);
+	kfree(info);
+
+	return 0;
+}
+
+static struct snd_soc_platform_driver soc_dmaengine_platform = {
+	.remove = soc_dmaengine_plat_remove,
+	.pcm_new = soc_dmaengine_pcm_new,
+	.pcm_free = soc_dmaengine_pcm_free,
+	.ops = &soc_dmaengine_ops,
+	/* Wait until the cpu_dai has been probed */
+	.probe_order = SND_SOC_COMP_ORDER_LATE,
+};
+
+static int soc_dmaengine_probe(struct platform_device *pdev)
+{
+	return snd_soc_register_platform(&pdev->dev, &soc_dmaengine_platform);
+}
+
+static int soc_dmaengine_remove(struct platform_device *pdev)
+{
+	snd_soc_unregister_platform(&pdev->dev);
+	return 0;
+}
+
+static struct platform_driver soc_dmaengine_driver = {
+	.driver = {
+		.name = "soc-dmaengine",
+		.owner = THIS_MODULE,
+	},
+	.probe = soc_dmaengine_probe,
+	.remove = soc_dmaengine_remove,
+};
+
+module_platform_driver(soc_dmaengine_driver);
+
+MODULE_AUTHOR("Russell King");
+MODULE_DESCRIPTION("ALSA SoC DMA engine driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:soc-dmaengine");
diff --git a/sound/soc/soc-generic-dmaengine-pcm.c b/sound/soc/soc-generic-dmaengine-pcm.c
index a63e942fdc0b..6767d80dc489 100644
--- a/sound/soc/soc-generic-dmaengine-pcm.c
+++ b/sound/soc/soc-generic-dmaengine-pcm.c
@@ -185,14 +185,14 @@ static struct dma_chan *dmaengine_pcm_compat_request_channel(
 		return NULL;
 	}
 
-	dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
-
 	if ((pcm->flags & SND_DMAENGINE_PCM_FLAG_HALF_DUPLEX) && pcm->chan[0])
 		return pcm->chan[0];
 
 	if (pcm->config->compat_request_channel)
 		return pcm->config->compat_request_channel(rtd, substream);
 
+	dma_data = snd_soc_dai_get_dma_data(snd_soc_rtd_to_cpu(rtd, 0), substream);
+
 	return snd_dmaengine_pcm_request_channel(pcm->config->compat_filter_fn,
 						 dma_data->filter_data);
 }
