]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/commitdiff
SPI: Add support for TSC2301 protocol driver
authorJarkko Nikula <jarkko.nikula@nokia.com>
Mon, 2 Apr 2007 17:21:35 +0000 (13:21 -0400)
committerTony Lindgren <tony@atomide.com>
Fri, 11 May 2007 00:11:20 +0000 (17:11 -0700)
This adds a driver for TSC2301 including support for audio, keypad and
touchscreen. Patch is originally developed for Nokia N800 by Imre Deak,
Jarkko Nikula, Jarkko Oikarinen and Juha Yrjola.

Signed-off-by: Jarkko Nikula <jarkko.nikula@nokia.com>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/tsc2301-core.c [new file with mode: 0644]
drivers/spi/tsc2301-mixer.c [new file with mode: 0644]
include/linux/spi/tsc2301.h [new file with mode: 0644]

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