]> 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, 12 Mar 2007 14:16:46 +0000 (16:16 +0200)
committerJuha Yrjola <juha.yrjola@solidboot.com>
Tue, 13 Mar 2007 17:04:46 +0000 (19:04 +0200)
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>
Signed-off-by: Juha Yrjola <juha.yrjola@solidboot.com>
drivers/spi/Kconfig
drivers/spi/Makefile
drivers/spi/tsc2301-core.c [new file with mode: 0644]
drivers/spi/tsc2301-kp.c [new file with mode: 0644]
drivers/spi/tsc2301-mixer.c [new file with mode: 0644]
drivers/spi/tsc2301-ts.c [new file with mode: 0644]
include/linux/spi/tsc2301.h

index bde6231c3b54075fa3e65b94bd477a5af1a2299c..c98392ce0680bdc0fc2a3723a4760a26d0088f6b 100644 (file)
@@ -167,6 +167,42 @@ config SPI_AT25
          This driver can also be built as a module.  If so, the module
          will be called at25.
 
+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_KEYPAD
+       boolean "TSC2301 keypad support"
+       depends on SPI_TSC2301
+       select INPUT_KEYBOARD
+       help
+         Say Y here for if you are using the keypad features of TSC2301.
+
+config SPI_TSC2301_TOUCHSCREEN
+       boolean "TSC2301 touchscreen support"
+       depends on SPI_TSC2301
+       select INPUT_TOUCHSCREEN
+       help
+         Say Y here for if you are using the touchscreen features of 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.
+
 #
 # Add new SPI protocol masters in alphabetical order above this line
 #
index 6d84bacc989228e522be98f5e7b704b93049d7b0..bfa8ea35d5a823c42d2f9c9823422a7e51f6fa4d 100644 (file)
@@ -26,6 +26,11 @@ obj-$(CONFIG_SPI_OMAP_UWIRE)         += omap_uwire.o
 # SPI protocol drivers (device/link on bus)
 obj-$(CONFIG_TSC2102)                  += tsc2102.o
 obj-$(CONFIG_SPI_AT25)         += at25.o
+obj-$(CONFIG_SPI_TSC2301)              += tsc2301.o
+tsc2301-objs                                   := tsc2301-core.o
+tsc2301-$(CONFIG_SPI_TSC2301_TOUCHSCREEN)      += tsc2301-ts.o
+tsc2301-$(CONFIG_SPI_TSC2301_KEYPAD)           += tsc2301-kp.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-kp.c b/drivers/spi/tsc2301-kp.c
new file mode 100644 (file)
index 0000000..da14be5
--- /dev/null
@@ -0,0 +1,559 @@
+/*
+ * TSC2301 keypad driver
+ *
+ * Copyright (C) 2005-2006 Nokia Corporation
+ *
+ * Written by Jarkko Oikarinen
+ * Rewritten by Juha Yrjola <juha.yrjola@nokia.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#ifdef CONFIG_ARCH_OMAP
+#include <asm/arch/gpio.h>
+#endif
+
+#include <linux/spi/tsc2301.h>
+
+#define TSC2301_KEYBOARD_PRODUCT_ID      0x0051
+#define TSC2301_KEYBOARD_PRODUCT_VERSION 0x0001
+#define TSC2301_DEBOUNCE_TIME_2MS        0x0000
+#define TSC2301_DEBOUNCE_TIME_10MS       0x0800
+#define TSC2301_DEBOUNCE_TIME_20MS       0x1000
+#define TSC2301_DEBOUNCE_TIME_50MS       0x1800
+#define TSC2301_DEBOUNCE_TIME_60MS       0x2000
+#define TSC2301_DEBOUNCE_TIME_80MS       0x2800
+#define TSC2301_DEBOUNCE_TIME_100MS      0x3000
+#define TSC2301_DEBOUNCE_TIME_120MS      0x3800
+
+#define TSC2301_DEBOUNCE_TIME          TSC2301_DEBOUNCE_TIME_20MS
+
+#define TSC2301_POLL_TIME              30
+
+struct tsc2301_kp {
+       struct input_dev        *idev;
+       char                    phys[32];
+       spinlock_t              lock;
+       struct mutex            mutex;
+       struct timer_list       timer;
+       u16                     keys_pressed;
+       unsigned                pending:1;
+       unsigned                user_disabled:1;
+       unsigned                disable_depth;
+
+       struct spi_transfer     read_xfer[4];
+       struct spi_message      read_msg;
+       u16                     state;
+       u16                     data;
+       u16                     mask;
+
+       int                     irq;
+       s16                     keymap[16];
+
+       int                     (*get_keyb_irq_state)(struct device *dev);
+};
+
+static inline int tsc2301_kp_disabled(struct tsc2301 *tsc)
+{
+       return tsc->kp->disable_depth != 0;
+}
+
+static inline void tsc2301_kp_set_keypressed_state(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+
+       /* kp->state is updated only if we don't have the callback */
+       if (kp->get_keyb_irq_state != NULL) {
+               if (kp->get_keyb_irq_state(&tsc->spi->dev))
+                       kp->state = 1 << 15;
+               else
+                       kp->state = 0;
+       }
+}
+
+static inline int tsc2301_kp_key_is_pressed(struct tsc2301 *tsc)
+{
+       return tsc->kp->state & (1 << 15);
+}
+
+static void tsc2301_kp_send_key_events(struct tsc2301 *tsc,
+                                      u16 prev_state,
+                                      u16 new_state)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+       u16 common, released, pressed;
+       int i;
+
+       common = prev_state & new_state;
+       released = common ^ prev_state;
+       pressed = common ^ new_state;
+       if (!released && !pressed)
+               return;
+       for (i = 0; i < 16 && (released || pressed); i++) {
+               if (released & 1) {
+                       dev_dbg(&tsc->spi->dev, "key %d released\n", i);
+                       input_report_key(kp->idev, kp->keymap[i], 0);
+               }
+               released >>= 1;
+               if (pressed & 1) {
+                       dev_dbg(&tsc->spi->dev, "key %d pressed\n", i);
+                       input_report_key(kp->idev, kp->keymap[i], 1);
+               }
+               pressed >>= 1;
+       }
+       input_sync(kp->idev);
+}
+
+static inline void tsc2301_kp_release_all_keys(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+       unsigned long flags;
+       int keys_pressed;
+
+       spin_lock_irqsave(&kp->lock, flags);
+       keys_pressed = kp->keys_pressed;
+       kp->keys_pressed = 0;
+       spin_unlock_irqrestore(&kp->lock, flags);
+       if (keys_pressed)
+               tsc2301_kp_send_key_events(tsc, keys_pressed, 0);
+}
+
+static inline void _filter_out(struct tsc2301 *tsc, u16 prev_state,
+                              u16 *new_state, int row1, int row2, u8 rect_pat)
+{
+       u16 mask;
+
+       mask = (rect_pat << (row1 * 4)) | (rect_pat << (row2 * 4));
+       mask &= ~prev_state;
+       *new_state &= ~mask;
+       dev_dbg(&tsc->spi->dev, "filtering ghost keys %02x\n", mask);
+}
+
+static void tsc2301_filter_ghost_keys(struct tsc2301 *tsc, u16 prev_state,
+                                     u16 *new_state)
+{
+       int row1, row2;
+       u16 key_map;
+       u16 row1_map;
+       static const u8 rect_pat[] = {
+               0x3, 0x5, 0x9, 0x6, 0xa, 0xc, 0,
+       };
+
+       key_map = *new_state;
+       for (row1 = 0; row1 < 4; row1++) {
+               row1_map = (key_map >> (row1 * 4)) & 0xf;
+               if (!row1_map)
+                       continue;
+               for (row2 = row1 + 1; row2 < 4; row2++) {
+                       u16 rect_map = (key_map >> (row2 * 4)) & 0xf;
+                       const u8 *rp;
+
+                       rect_map &= row1_map;
+                       if (!rect_map)
+                               continue;
+                       for (rp = rect_pat; *rp; rp++)
+                               if ((rect_map & *rp) == *rp)
+                                       _filter_out(tsc, prev_state, new_state,
+                                                   row1, row2, *rp);
+               }
+       }
+}
+
+static void tsc2301_kp_timer(unsigned long arg)
+{
+       struct tsc2301 *tsc = (void *) arg;
+       int r;
+
+       /* This needs to be done early enough, since reading the key data
+        * register clears the IRQ line, which may be used to determine
+        * the key pressed state.
+        */
+       tsc2301_kp_set_keypressed_state(tsc);
+       r = spi_async(tsc->spi, &tsc->kp->read_msg);
+       if (unlikely(r < 0 && printk_ratelimit()))
+               dev_err(&tsc->spi->dev, "kp: spi_async() failed");
+}
+
+static void tsc2301_kp_rx(void *arg)
+{
+       struct tsc2301 *tsc = arg;
+       struct tsc2301_kp *kp = tsc->kp;
+       unsigned long flags;
+       int key_pressed;
+       u16 kp_data;
+
+       kp_data = kp->data;
+       key_pressed = tsc2301_kp_key_is_pressed(tsc);
+       dev_dbg(&tsc->spi->dev, "KP data %04x (%s)\n",
+               kp_data, key_pressed ? "pressed" : "not pressed");
+       if (!key_pressed)
+               kp_data = 0;
+       else
+               tsc2301_filter_ghost_keys(tsc, kp->keys_pressed, &kp_data);
+       tsc2301_kp_send_key_events(tsc, kp->keys_pressed, kp_data);
+       kp->keys_pressed = kp_data;
+       spin_lock_irqsave(&kp->lock, flags);
+       if (likely(!tsc2301_kp_disabled(tsc))) {
+               if (likely(key_pressed))
+                       mod_timer(&kp->timer,
+                                jiffies + msecs_to_jiffies(TSC2301_POLL_TIME));
+               else {
+                       kp->pending = 0;
+                       enable_irq(kp->irq);
+               }
+       } else
+               kp->pending = 0;
+       spin_unlock_irqrestore(&kp->lock, flags);
+}
+
+static irqreturn_t tsc2301_kp_irq_handler(int irq, void *dev_id)
+{
+       struct tsc2301 *tsc = dev_id;
+       struct tsc2301_kp *kp = tsc->kp;
+       unsigned long flags;
+
+       spin_lock_irqsave(&kp->lock, flags);
+       BUG_ON(kp->pending);
+       if (tsc2301_kp_disabled(tsc)) {
+               spin_unlock_irqrestore(&kp->lock, flags);
+               return IRQ_HANDLED;
+       }
+       kp->pending = 1;
+       disable_irq_nosync(irq);
+       spin_unlock_irqrestore(&kp->lock, flags);
+       tsc2301_kp_timer((unsigned long) tsc);
+       return IRQ_HANDLED;
+}
+
+static void tsc2301_kp_start_scan(struct tsc2301 *tsc)
+{
+       tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, tsc->kp->mask);
+       tsc2301_write_reg(tsc, TSC2301_REG_KEY, TSC2301_DEBOUNCE_TIME);
+}
+
+static void tsc2301_kp_stop_scan(struct tsc2301 *tsc)
+{
+       tsc2301_write_reg(tsc, TSC2301_REG_KEY, 1 << 14);
+       tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, 0xffff);
+}
+
+/* Must be called with the mutex held */
+static void tsc2301_kp_enable(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+       unsigned long flags;
+
+       spin_lock_irqsave(&kp->lock, flags);
+       BUG_ON(!tsc2301_kp_disabled(tsc));
+       if (--kp->disable_depth != 0) {
+               spin_unlock_irqrestore(&kp->lock, flags);
+               return;
+       }
+       if (kp->keys_pressed)
+               kp->pending = 1;
+       spin_unlock_irqrestore(&kp->lock, flags);
+
+       set_irq_type(kp->irq, IRQT_FALLING);
+       tsc2301_kp_start_scan(tsc);
+       if (kp->pending)
+               /* continue an interrupted polling */
+               mod_timer(&kp->timer,
+                         jiffies + msecs_to_jiffies(TSC2301_POLL_TIME));
+       else
+               enable_irq(kp->irq);
+}
+
+/* Must be called with the mutex held */
+static int tsc2301_kp_disable(struct tsc2301 *tsc, int release_keys)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+       unsigned long flags;
+
+       spin_lock_irqsave(&kp->lock, flags);
+       if (kp->disable_depth++ != 0) {
+               spin_unlock_irqrestore(&kp->lock, flags);
+               goto out;
+       }
+       if (!kp->pending)
+               disable_irq_nosync(kp->irq);
+
+       while (kp->pending) {
+               spin_unlock_irqrestore(&kp->lock, flags);
+               msleep(1);
+               spin_lock_irqsave(&kp->lock, flags);
+       }
+       spin_unlock_irqrestore(&kp->lock, flags);
+
+       tsc2301_kp_stop_scan(tsc);
+       set_irq_type(kp->irq, IRQT_NOEDGE);
+out:
+       if (release_keys)
+               tsc2301_kp_release_all_keys(tsc);
+
+       return 0;
+}
+
+/* The following workaround is needed for a HW bug triggered by the
+ * following:
+ * 1. keep any key pressed
+ * 2. disable keypad
+ * 3. release all keys
+ * 4. reenable keypad
+ * 5. disable touch screen controller
+ *
+ * After this the keypad scanner will get stuck in busy state and won't
+ * report any interrupts for further keypresses. One way to recover is to
+ * restart the keypad scanner whenever we enable / disable the
+ * touchscreen controller.
+ */
+void tsc2301_kp_restart(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+
+       mutex_lock(&kp->mutex);
+       if (!kp->user_disabled) {
+               tsc2301_kp_disable(tsc, 0);
+               tsc2301_kp_enable(tsc);
+       }
+       mutex_unlock(&kp->mutex);
+}
+
+static ssize_t tsc2301_kp_disable_show(struct device *dev,
+                                      struct device_attribute *attr, char *buf)
+{
+       struct tsc2301          *tsc = dev_get_drvdata(dev);
+
+       return sprintf(buf, "%u\n", tsc2301_kp_disabled(tsc) ? 1 : 0);
+}
+
+static ssize_t tsc2301_kp_disable_store(struct device *dev,
+                                       struct device_attribute *attr,
+                                       const char *buf, size_t count)
+{
+       struct tsc2301          *tsc = dev_get_drvdata(dev);
+       struct tsc2301_kp       *kp = tsc->kp;
+       char *endp;
+       int i;
+
+       i = simple_strtoul(buf, &endp, 10);
+       i = i ? 1 : 0;
+
+       mutex_lock(&kp->mutex);
+       if (i == kp->user_disabled) {
+               mutex_unlock(&kp->mutex);
+               return count;
+       }
+       kp->user_disabled = i;
+
+       if (i)
+               tsc2301_kp_disable(tsc, 1);
+       else
+               tsc2301_kp_enable(tsc);
+       mutex_unlock(&kp->mutex);
+
+       return count;
+}
+
+static DEVICE_ATTR(disable_kp, 0664, tsc2301_kp_disable_show,
+                  tsc2301_kp_disable_store);
+
+static const u16 tsc2301_kp_read_data = 0x8000 | TSC2301_REG_KPDATA;
+static const u16 tsc2301_kp_read_state = 0x8000 | TSC2301_REG_KEY;
+
+static void tsc2301_kp_setup_spi_xfer(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+       struct spi_message *m = &kp->read_msg;
+       struct spi_transfer *x = &kp->read_xfer[0];
+
+       spi_message_init(&kp->read_msg);
+
+       if (kp->get_keyb_irq_state == NULL) {
+               /* No platform specific way for determining the keypress
+                * state, so we'll need an extra status register read.
+                */
+               x->tx_buf = &tsc2301_kp_read_state;
+               x->len = 2;
+               spi_message_add_tail(x, m);
+               x++;
+
+               x->rx_buf = &kp->state;
+               x->len = 2;
+               x->cs_change = 1;
+               spi_message_add_tail(x, m);
+               x++;
+       }
+
+       x->tx_buf = &tsc2301_kp_read_data;
+       x->len = 2;
+       spi_message_add_tail(x, m);
+       x++;
+
+       x->rx_buf = &kp->data;
+       x->len = 2;
+       spi_message_add_tail(x, m);
+
+       m->complete = tsc2301_kp_rx;
+       m->context = tsc;
+}
+
+#ifdef CONFIG_PM
+int tsc2301_kp_suspend(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+
+       mutex_lock(&kp->mutex);
+       tsc2301_kp_disable(tsc, 1);
+       mutex_unlock(&kp->mutex);
+       return 0;
+}
+
+void tsc2301_kp_resume(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+
+       mutex_lock(&kp->mutex);
+       tsc2301_kp_enable(tsc);
+       mutex_unlock(&kp->mutex);
+}
+#endif
+
+int __devinit tsc2301_kp_init(struct tsc2301 *tsc,
+                             struct tsc2301_platform_data *pdata)
+{
+       struct input_dev *idev;
+       struct tsc2301_kp *kp;
+       int r, i;
+       u16 mask;
+
+       if (pdata->keyb_int < 0) {
+               dev_err(&tsc->spi->dev, "need kbirq");
+               return -EINVAL;
+       }
+
+       kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+       if (kp == NULL)
+               return -ENOMEM;
+       tsc->kp = kp;
+
+       kp->irq = pdata->keyb_int;
+       spin_lock_init(&kp->lock);
+       mutex_init(&kp->mutex);
+
+       init_timer(&kp->timer);
+       kp->timer.data = (unsigned long) tsc;
+       kp->timer.function = tsc2301_kp_timer;
+
+       kp->get_keyb_irq_state = pdata->get_keyb_irq_state;
+
+       idev = input_allocate_device();
+       if (idev == NULL) {
+               r = -ENOMEM;
+               goto err1;
+       }
+       idev->name = "TSC2301 keypad";
+       snprintf(kp->phys, sizeof(kp->phys), "%s/input-kp", tsc->spi->dev.bus_id);
+       idev->phys = kp->phys;
+
+       mask = 0;
+       idev->evbit[0] = BIT(EV_KEY);
+       for (i = 0; i < 16; i++) {
+               if (pdata->keymap[i] > 0) {
+                       set_bit(pdata->keymap[i], idev->keybit);
+                       kp->keymap[i] = pdata->keymap[i];
+               } else {
+                       kp->keymap[i] = -1;
+                       mask |= 1 << i;
+               }
+       }
+
+       if (pdata->kp_rep)
+               set_bit(EV_REP, idev->evbit);
+
+       kp->idev = idev;
+
+       tsc2301_kp_setup_spi_xfer(tsc);
+
+       r = device_create_file(&tsc->spi->dev, &dev_attr_disable_kp);
+       if (r < 0)
+               goto err2;
+
+       tsc2301_kp_start_scan(tsc);
+
+       /* IRQ mode 0 is faulty, it can cause the KBIRQ to get stuck.
+        * Mode 2 deasserts the IRQ at:
+        * - HW or SW reset
+        * - Setting SCS flag in REG_KEY register
+        * - Releasing all keys
+        * - Reading the REG_KPDATA
+        */
+       tsc2301_write_kbc(tsc, 2);
+
+       tsc2301_write_reg(tsc, TSC2301_REG_KPMASK, mask);
+       kp->mask = mask;
+
+       set_irq_type(kp->irq, IRQT_FALLING);
+
+       r = request_irq(kp->irq, tsc2301_kp_irq_handler, SA_SAMPLE_RANDOM,
+                       "tsc2301-kp", tsc);
+       if (r < 0) {
+               dev_err(&tsc->spi->dev, "unable to get kbirq IRQ");
+               goto err3;
+       }
+       set_irq_wake(kp->irq, 1);
+
+       /* We need to read the register once..? */
+       tsc2301_read_reg(tsc, TSC2301_REG_KPDATA);
+
+       r = input_register_device(idev);
+       if (r < 0) {
+               dev_err(&tsc->spi->dev, "can't register keypad device\n");
+               goto err4;
+       }
+
+       return 0;
+
+err4:
+       free_irq(kp->irq, tsc);
+err3:
+       tsc2301_kp_stop_scan(tsc);
+       device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp);
+err2:
+       input_free_device(kp->idev);
+err1:
+       kfree(kp);
+       return r;
+}
+
+void __devexit tsc2301_kp_exit(struct tsc2301 *tsc)
+{
+       struct tsc2301_kp *kp = tsc->kp;
+
+       tsc2301_kp_disable(tsc, 1);
+       input_unregister_device(kp->idev);
+       free_irq(kp->irq, tsc);
+       device_remove_file(&tsc->spi->dev, &dev_attr_disable_kp);
+
+       kfree(kp);
+}
diff --git a/drivers/spi/tsc2301-mixer.c b/drivers/spi/tsc2301-mixer.c
new file mode 100644 (file)
index 0000000..27eba13
--- /dev/null
@@ -0,0 +1,1064 @@
+/*
+ * drivers/spi/tsc2301-mixer.c
+ *
+ * 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>
+
+#ifdef TSC2301_MIXER_SELF_REGISTRATION
+static char *id = NULL;
+MODULE_PARM_DESC(id, "TSC2301 ALSA Mixer Driver");
+#endif
+
+/* 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;
+
+#ifdef TSC2301_MIXER_SELF_REGISTRATION
+       struct snd_card *card;
+#endif
+
+       /* 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;
+}
+
+#ifdef TSC2301_MIXER_SELF_REGISTRATION
+static int tsc2301_mixer_register_card(struct tsc2301 *tsc)
+{
+       struct snd_card *card;
+       int err;
+
+       /* create new sound card instance */
+       card = snd_card_new(-1, id, THIS_MODULE, 0);
+       if (card == NULL)
+               return -ENOMEM;
+
+       tsc->mixer->card = card;
+
+       strcpy(card->driver, "TSC2301");
+       strcpy(card->shortname, "TSC2301");
+       sprintf(card->longname, "TSC2301 ALSA Mixer");
+       strcpy(card->mixername, "TSC2301 Mixer");
+
+       /* register mixer controls for the sound card */
+       if ((err = tsc2301_mixer_register_controls(&tsc->spi->dev, card)) != 0)
+               goto err1;
+
+       /* register the sound card instance */
+       if ((err = snd_card_register(card)) != 0)
+               goto err1;
+
+       printk(KERN_INFO "TSC2301 ALSA Mixer support initialized\n");
+
+       return 0;
+err1:
+       snd_card_free(card);
+       return err;
+}
+#endif
+
+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);
+
+#ifdef TSC2301_MIXER_SELF_REGISTRATION
+       err = tsc2301_mixer_register_card(tsc);
+       if (err < 0)
+               goto err2;
+#endif
+
+       if (mix->platform_init != NULL) {
+               err = mix->platform_init(&tsc->spi->dev);
+               if (err < 0)
+                       goto err3;
+       }
+
+       return 0;
+err3:
+#ifdef TSC2301_MIXER_SELF_REGISTRATION
+       snd_card_free(mix->card);
+err2:
+#endif
+       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;
+
+#ifdef TSC2301_MIXER_SELF_REGISTRATION
+       snd_card_free(mixer->card);
+#endif
+
+       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/drivers/spi/tsc2301-ts.c b/drivers/spi/tsc2301-ts.c
new file mode 100644 (file)
index 0000000..da91736
--- /dev/null
@@ -0,0 +1,679 @@
+/*
+ * TSC2301 touchscreen driver
+ *
+ * Copyright (C) 2005-2006 Nokia Corporation
+ *
+ * Written by Jarkko Oikarinen, Imre Deak and Juha Yrjola
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/spi/spi.h>
+
+#ifdef CONFIG_ARCH_OMAP
+#include <asm/arch/gpio.h>
+#endif
+
+#include <linux/spi/tsc2301.h>
+
+/**
+ * The touchscreen interface operates as follows:
+ *
+ * Initialize:
+ *    Request access to GPIO103 (DAV)
+ *    tsc2301_dav_irq_handler will trigger when DAV line goes down
+ *
+ *  1) Pen is pressed against touchscreeen
+ *  2) TSC2301 performs AD conversion
+ *  3) After the conversion is done TSC2301 drives DAV line down
+ *  4) GPIO IRQ is received and tsc2301_dav_irq_handler is called
+ *  5) tsc2301_dav_irq_handler sets up tsc2301_ts_timer in TSC2301_TS_SCAN_TIME
+ *  6) tsc2301_ts_timer disables the irq and requests spi driver
+ *     to read X, Y, Z1 and Z2
+ *  7) SPI framework calls tsc2301_ts_rx after the coordinates are read
+ *  8) tsc2301_ts_rx reports coordinates to input layer and
+ *     sets up tsc2301_ts_timer to be called after TSC2301_TS_SCAN_TIME
+ *  9) if tsc2301_tx_timer notices that the pen has been lifted, the lift event
+ *     is sent, and irq is again enabled.
+ */
+
+
+#define TSC2301_TOUCHSCREEN_PRODUCT_ID                 0x0052
+#define TSC2301_TOUCHSCREEN_PRODUCT_VERSION            0x0001
+
+#define TSC2301_TS_SCAN_TIME                           1
+
+#define TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301      0x8000
+#define TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST         0x0000
+
+#define TSC2301_ADCREG_FUNCTION_NONE                   0x0000
+#define TSC2301_ADCREG_FUNCTION_XY                     0x0400
+#define TSC2301_ADCREG_FUNCTION_XYZ                    0x0800
+#define TSC2301_ADCREG_FUNCTION_X                      0x0C00
+#define TSC2301_ADCREG_FUNCTION_Y                      0x1000
+#define TSC2301_ADCREG_FUNCTION_Z                      0x1400
+#define TSC2301_ADCREG_FUNCTION_DAT1                   0x1800
+#define TSC2301_ADCREG_FUNCTION_DAT2                   0x1C00
+#define TSC2301_ADCREG_FUNCTION_AUX1                   0x2000
+#define TSC2301_ADCREG_FUNCTION_AUX2                   0x2400
+#define TSC2301_ADCREG_FUNCTION_TEMP                   0x2800
+
+#define TSC2301_ADCREG_RESOLUTION_8BIT                 0x0100
+#define TSC2301_ADCREG_RESOLUTION_10BIT                        0x0200
+#define TSC2301_ADCREG_RESOLUTION_12BIT                        0x0300
+
+#define TSC2301_ADCREG_AVERAGING_NONE                  0x0000
+#define TSC2301_ADCREG_AVERAGING_4AVG                  0x0040
+#define TSC2301_ADCREG_AVERAGING_8AVG                  0x0080
+#define TSC2301_ADCREG_AVERAGING_16AVG                 0x00C0
+
+#define TSC2301_ADCREG_CLOCK_8MHZ                      0x0000
+#define TSC2301_ADCREG_CLOCK_4MHZ                      0x0010
+#define TSC2301_ADCREG_CLOCK_2MHZ                      0x0020
+#define TSC2301_ADCREG_CLOCK_1MHZ                      0x0030
+
+#define TSC2301_ADCREG_VOLTAGE_STAB_0US                        0x0000
+#define TSC2301_ADCREG_VOLTAGE_STAB_100US              0x0002
+#define TSC2301_ADCREG_VOLTAGE_STAB_500US              0x0004
+#define TSC2301_ADCREG_VOLTAGE_STAB_1MS                        0x0006
+#define TSC2301_ADCREG_VOLTAGE_STAB_5MS                        0x0008
+#define TSC2301_ADCREG_VOLTAGE_STAB_10MS               0x000A
+#define TSC2301_ADCREG_VOLTAGE_STAB_50MS               0x000C
+#define TSC2301_ADCREG_VOLTAGE_STAB_100MS              0x000E
+
+#define TSC2301_ADCREG_STOP_CONVERSION                 0x4000
+
+#define MAX_12BIT                                      ((1 << 12) - 1)
+
+struct tsc2301_ts {
+       struct input_dev        *idev;
+       char                    phys[32];
+       struct timer_list       timer;
+       spinlock_t              lock;
+
+       struct spi_transfer     read_xfer[2];
+       struct spi_message      read_msg;
+       u16                     data[4];
+
+       int                     hw_avg_max;
+       u16                     x;
+       u16                     y;
+       u16                     p;
+       int                     sample_cnt;
+
+       int                     ignore_last : 1;
+       u16                     x_plate_ohm;
+       int                     stab_time;
+       int                     max_pressure;
+       int                     touch_pressure;
+       int                     pressure_limit;
+
+       u16                     irq_enabled:1;
+       u16                     pen_down:1;
+       u16                     disabled:1;
+       u16                     pending:1;
+
+       int                     hw_flags;
+
+       s16                     dav_gpio;
+       int                     irq;
+};
+
+
+static const u16 tsc2301_ts_read_data = 0x8000 | TSC2301_REG_X;
+
+static int tsc2301_ts_check_config(struct tsc2301_ts *ts, int *hw_flags)
+{
+       int flags;
+
+       flags = 0;
+       switch (ts->hw_avg_max) {
+       case 0:
+               flags |= TSC2301_ADCREG_AVERAGING_NONE;
+               break;
+       case 4:
+               flags |= TSC2301_ADCREG_AVERAGING_4AVG;
+               break;
+       case 8:
+               flags |= TSC2301_ADCREG_AVERAGING_8AVG;
+               break;
+       case 16:
+               flags |= TSC2301_ADCREG_AVERAGING_16AVG;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       switch (ts->stab_time) {
+       case 0:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_0US;
+               break;
+       case 100:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_100US;
+               break;
+       case 500:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_500US;
+               break;
+       case 1000:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_1MS;
+               break;
+       case 5000:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_5MS;
+               break;
+       case 10000:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_10MS;
+               break;
+       case 50000:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_50MS;
+               break;
+       case 100000:
+               flags |= TSC2301_ADCREG_VOLTAGE_STAB_100MS;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       *hw_flags = flags;
+       return 0;
+}
+
+/*
+ * This odd three-time initialization is to work around a bug in TSC2301.
+ * See TSC2301 errata for details.
+ */
+static int tsc2301_ts_configure(struct tsc2301 *tsc, int flags)
+{
+       struct spi_transfer xfer[3];
+       struct spi_transfer *x;
+       struct spi_message m;
+       int reg = TSC2301_REG_ADC;
+       u16 val1, val2, val3;
+       u16 data[6];
+
+       val1 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST |
+               TSC2301_ADCREG_STOP_CONVERSION |
+               TSC2301_ADCREG_FUNCTION_NONE |
+               TSC2301_ADCREG_RESOLUTION_12BIT |
+               TSC2301_ADCREG_AVERAGING_NONE |
+               TSC2301_ADCREG_CLOCK_2MHZ |
+               TSC2301_ADCREG_VOLTAGE_STAB_100MS;
+
+       val2 = TSC2301_ADCREG_CONVERSION_CTRL_BY_HOST |
+               TSC2301_ADCREG_FUNCTION_XYZ |
+               TSC2301_ADCREG_RESOLUTION_12BIT |
+               TSC2301_ADCREG_AVERAGING_16AVG |
+               TSC2301_ADCREG_CLOCK_1MHZ |
+               TSC2301_ADCREG_VOLTAGE_STAB_100MS;
+
+       /* Averaging and voltage stabilization settings in flags */
+       val3 = TSC2301_ADCREG_CONVERSION_CTRL_BY_TSC2301 |
+               TSC2301_ADCREG_FUNCTION_XYZ |
+               TSC2301_ADCREG_RESOLUTION_12BIT |
+               TSC2301_ADCREG_CLOCK_1MHZ |
+               flags;
+
+       /* Now we prepare the command for transferring */
+       data[0] = reg;
+       data[1] = val1;
+       data[2] = reg;
+       data[3] = val2;
+       data[4] = reg;
+       data[5] = val3;
+
+       spi_message_init(&m);
+       m.spi = tsc->spi;
+
+       memset(xfer, 0, sizeof(xfer));
+       x = &xfer[0];
+
+       x->tx_buf = &data[0];
+       x->len = 4;
+       x->cs_change = 1;
+       spi_message_add_tail(x, &m);
+
+       x++;
+       x->tx_buf = &data[2];
+       x->len = 4;
+       x->cs_change = 1;
+       spi_message_add_tail(x, &m);
+
+       x++;
+       x->tx_buf = &data[4];
+       x->len = 4;
+       spi_message_add_tail(x, &m);
+
+       spi_sync(m.spi, &m);
+
+       return 0;
+}
+
+static void tsc2301_ts_start_scan(struct tsc2301 *tsc)
+{
+       tsc2301_ts_configure(tsc, tsc->ts->hw_flags);
+}
+
+static void tsc2301_ts_stop_scan(struct tsc2301 *tsc)
+{
+       tsc2301_ts_configure(tsc, TSC2301_ADCREG_STOP_CONVERSION);
+}
+
+static int device_suspended(struct device *dev)
+{
+       struct tsc2301 *tsc = dev_get_drvdata(dev);
+       return dev->power.power_state.event != PM_EVENT_ON || tsc->ts->disabled;
+}
+
+static void update_pen_state(struct tsc2301_ts *ts, int x, int y, int pressure)
+{
+       int sync = 0;
+
+       if (pressure) {
+               input_report_abs(ts->idev, ABS_X, x);
+               input_report_abs(ts->idev, ABS_Y, y);
+               input_report_abs(ts->idev, ABS_PRESSURE, pressure);
+               if (!ts->pen_down)
+                       input_report_key(ts->idev, BTN_TOUCH, 1);
+               sync = 1;
+       } else if (ts->pen_down) {
+               input_report_abs(ts->idev, ABS_PRESSURE, 0);
+               input_report_key(ts->idev, BTN_TOUCH, 0);
+               sync = 1;
+       }
+
+       if (sync)
+               input_sync(ts->idev);
+
+       ts->pen_down = pressure ? 1 : 0;
+#ifdef VERBOSE
+       dev_dbg(&tsc->spi->dev, "x %4d y %4d p %4d\n", x, y, pressure);
+#endif
+}
+
+/*
+ * This procedure is called by the SPI framework after the coordinates
+ * have been read from TSC2301
+ */
+static void tsc2301_ts_rx(void *arg)
+{
+       struct tsc2301 *tsc = arg;
+       struct tsc2301_ts *ts = tsc->ts;
+       unsigned int x, y, z1, z2, pressure;
+
+       x  = ts->data[0];
+       y  = ts->data[1];
+       z1 = ts->data[2];
+       z2 = ts->data[3];
+
+       if (z1) {
+               pressure = ts->x_plate_ohm * x;
+               pressure /= 4096;
+               pressure *= z2 - z1;
+               pressure /= z1;
+       } else
+               pressure = 0;
+
+       /* If pressure value is above a preset limit (pen is barely
+        * touching the screen) we can't trust the coordinate values.
+        */
+       if (pressure < ts->pressure_limit && x < MAX_12BIT && y < MAX_12BIT) {
+               ts->pressure_limit = ts->max_pressure;
+               if (ts->ignore_last) {
+                       if (ts->sample_cnt)
+                               update_pen_state(ts, ts->x, ts->y, ts->p);
+                       ts->x = x;
+                       ts->y = y;
+                       ts->p = pressure;
+               } else
+                       update_pen_state(ts, x, y, pressure);
+               ts->sample_cnt++;
+       }
+
+       mod_timer(&ts->timer,
+                 jiffies + msecs_to_jiffies(TSC2301_TS_SCAN_TIME));
+}
+
+static int is_pen_down(struct tsc2301_ts *ts)
+{
+       return ts->pen_down;
+}
+
+/*
+ * Timer is called every TSC2301_TS_SCAN_TIME when the pen is down
+ */
+static void tsc2301_ts_timer(unsigned long arg)
+{
+       struct tsc2301 *tsc = (void *) arg;
+       struct tsc2301_ts *ts = tsc->ts;
+       unsigned long flags;
+       int ndav;
+       int r;
+
+       spin_lock_irqsave(&ts->lock, flags);
+       ndav = omap_get_gpio_datain(ts->dav_gpio);
+       if (ndav || device_suspended(&tsc->spi->dev)) {
+               /* Pen has been lifted */
+               if (!device_suspended(&tsc->spi->dev)) {
+                       ts->irq_enabled = 1;
+                       enable_irq(ts->irq);
+               }
+               update_pen_state(ts, 0, 0, 0);
+               ts->pending = 0;
+               spin_unlock_irqrestore(&ts->lock, flags);
+
+       } else {
+               ts->pen_down = 1;
+               spin_unlock_irqrestore(&ts->lock, flags);
+
+               r = spi_async(tsc->spi, &ts->read_msg);
+               if (r)
+                       dev_err(&tsc->spi->dev, "ts: spi_async() failed");
+       }
+}
+
+/*
+ * This interrupt is called when pen is down and first coordinates are
+ * available. That is indicated by a falling edge on DEV line.  IRQ is
+ * disabled here because while the pen is down the coordinates are
+ * read by a timer.
+ */
+static irqreturn_t tsc2301_ts_irq_handler(int irq, void *dev_id)
+{
+       struct tsc2301 *tsc = dev_id;
+       struct tsc2301_ts *ts = tsc->ts;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ts->lock, flags);
+       if (ts->irq_enabled) {
+               ts->irq_enabled = 0;
+               disable_irq(ts->irq);
+               ts->pending = 1;
+               ts->pressure_limit = ts->touch_pressure;
+               ts->sample_cnt = 0;
+               mod_timer(&ts->timer,
+                         jiffies + msecs_to_jiffies(TSC2301_TS_SCAN_TIME));
+       }
+       spin_unlock_irqrestore(&ts->lock, flags);
+
+       return IRQ_HANDLED;
+}
+
+/* Must be called with ts->lock held */
+static void tsc2301_ts_disable(struct tsc2301 *tsc)
+{
+       struct tsc2301_ts *ts = tsc->ts;
+
+       if (ts->disabled)
+               return;
+
+       ts->disabled = 1;
+       if (!ts->pending) {
+               ts->irq_enabled = 0;
+               disable_irq(ts->irq);
+       } else {
+               while (ts->pending) {
+                       spin_unlock_irq(&ts->lock);
+                       msleep(1);
+                       spin_lock_irq(&ts->lock);
+               }
+       }
+
+       spin_unlock_irq(&ts->lock);
+       tsc2301_ts_stop_scan(tsc);
+       /* Workaround a bug where turning on / off touchscreen scanner
+        * can get the keypad scanner stuck.
+        */
+       tsc2301_kp_restart(tsc);
+       spin_lock_irq(&ts->lock);
+}
+
+static void tsc2301_ts_enable(struct tsc2301 *tsc)
+{
+       struct tsc2301_ts *ts = tsc->ts;
+
+       if (!ts->disabled)
+               return;
+
+       ts->disabled = 0;
+       ts->irq_enabled = 1;
+       enable_irq(ts->irq);
+
+       spin_unlock_irq(&ts->lock);
+       tsc2301_ts_start_scan(tsc);
+       /* Same workaround as above. */
+       tsc2301_kp_restart(tsc);
+       spin_lock_irq(&ts->lock);
+}
+
+#ifdef CONFIG_PM
+int tsc2301_ts_suspend(struct tsc2301 *tsc)
+{
+       struct tsc2301_ts *ts = tsc->ts;
+
+       spin_lock_irq(&ts->lock);
+       tsc2301_ts_disable(tsc);
+       spin_unlock_irq(&ts->lock);
+
+       return 0;
+}
+
+void tsc2301_ts_resume(struct tsc2301 *tsc)
+{
+       struct tsc2301_ts *ts = tsc->ts;
+
+       spin_lock_irq(&ts->lock);
+       tsc2301_ts_enable(tsc);
+       spin_unlock_irq(&ts->lock);
+}
+#endif
+
+static void tsc2301_ts_setup_spi_xfer(struct tsc2301 *tsc)
+{
+       struct tsc2301_ts *ts = tsc->ts;
+       struct spi_message *m = &ts->read_msg;
+       struct spi_transfer *x = &ts->read_xfer[0];
+
+       spi_message_init(m);
+
+       x->tx_buf = &tsc2301_ts_read_data;
+       x->len = 2;
+       spi_message_add_tail(x, m);
+
+       x++;
+       x->rx_buf = &ts->data;
+       x->len = 8;
+       spi_message_add_tail(x, m);
+
+       m->complete = tsc2301_ts_rx;
+       m->context = tsc;
+}
+
+static ssize_t tsc2301_ts_pen_down_show(struct device *dev,
+                                       struct device_attribute *attr,
+                                       char *buf)
+{
+       struct tsc2301 *tsc = dev_get_drvdata(dev);
+
+       return sprintf(buf, "%u\n", is_pen_down(tsc->ts));
+}
+
+static DEVICE_ATTR(pen_down, S_IRUGO, tsc2301_ts_pen_down_show, NULL);
+
+static ssize_t tsc2301_ts_disable_show(struct device *dev,
+                                      struct device_attribute *attr, char *buf)
+{
+       struct tsc2301          *tsc = dev_get_drvdata(dev);
+       struct tsc2301_ts       *ts = tsc->ts;
+
+       return sprintf(buf, "%u\n", ts->disabled);
+}
+
+static ssize_t tsc2301_ts_disable_store(struct device *dev,
+                                       struct device_attribute *attr,
+                                       const char *buf, size_t count)
+{
+       struct tsc2301          *tsc = dev_get_drvdata(dev);
+       struct tsc2301_ts       *ts = tsc->ts;
+       char *endp;
+       int i;
+
+       i = simple_strtoul(buf, &endp, 10);
+       spin_lock_irq(&ts->lock);
+
+       if (i)
+               tsc2301_ts_disable(tsc);
+       else
+               tsc2301_ts_enable(tsc);
+
+       spin_unlock_irq(&ts->lock);
+
+       return count;
+}
+
+static DEVICE_ATTR(disable_ts, 0664, tsc2301_ts_disable_show,
+                  tsc2301_ts_disable_store);
+
+int __devinit tsc2301_ts_init(struct tsc2301 *tsc,
+                             struct tsc2301_platform_data *pdata)
+{
+       struct tsc2301_ts *ts;
+       struct input_dev *idev;
+       int dav_gpio, r;
+
+       if (pdata->dav_gpio < 0) {
+               dev_err(&tsc->spi->dev, "need DAV GPIO");
+               return -EINVAL;
+       }
+       dav_gpio = pdata->dav_gpio;
+
+       ts = kzalloc(sizeof(*ts), GFP_KERNEL);
+       if (ts == NULL)
+               return -ENOMEM;
+       tsc->ts = ts;
+
+       ts->dav_gpio = dav_gpio;
+#ifdef CONFIG_ARCH_OMAP
+       r = omap_request_gpio(dav_gpio);
+       if (r < 0) {
+               dev_err(&tsc->spi->dev, "unable to get DAV GPIO");
+               goto err1;
+       }
+       omap_set_gpio_direction(dav_gpio, 1);
+       ts->irq = OMAP_GPIO_IRQ(dav_gpio);
+#endif
+       init_timer(&ts->timer);
+       ts->timer.data = (unsigned long) tsc;
+       ts->timer.function = tsc2301_ts_timer;
+
+       spin_lock_init(&ts->lock);
+
+       ts->x_plate_ohm = pdata->ts_x_plate_ohm ? : 280;
+       ts->hw_avg_max  = pdata->ts_hw_avg;
+       ts->max_pressure= pdata->ts_max_pressure ? : MAX_12BIT;
+       ts->touch_pressure = pdata->ts_touch_pressure ? : ts->max_pressure;
+       ts->ignore_last = pdata->ts_ignore_last;
+       ts->stab_time   = pdata->ts_stab_time;
+
+       if ((r = tsc2301_ts_check_config(ts, &ts->hw_flags))) {
+               dev_err(&tsc->spi->dev, "invalid configuration\n");
+               goto err2;
+       }
+
+       idev = input_allocate_device();
+       if (idev == NULL) {
+               r = -ENOMEM;
+               goto err2;
+       }
+       idev->name = "TSC2301 touchscreen";
+       snprintf(ts->phys, sizeof(ts->phys),
+                "%s/input-ts", tsc->spi->dev.bus_id);
+       idev->phys = ts->phys;
+
+       idev->evbit[0] = BIT(EV_ABS) | BIT(EV_KEY);
+       idev->absbit[0] = BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE);
+       ts->idev = idev;
+
+       tsc2301_ts_setup_spi_xfer(tsc);
+
+       /* These parameters should perhaps be configurable? */
+       input_set_abs_params(idev, ABS_X, 0, 4096, 0, 0);
+       input_set_abs_params(idev, ABS_Y, 0, 4096, 0, 0);
+       input_set_abs_params(idev, ABS_PRESSURE, 0, 1024, 0, 0);
+
+       tsc2301_ts_start_scan(tsc);
+
+       ts->irq_enabled = 1;
+       r = request_irq(ts->irq, tsc2301_ts_irq_handler,
+                       SA_SAMPLE_RANDOM | SA_TRIGGER_FALLING,
+                       "tsc2301-ts", tsc);
+       if (r < 0) {
+               dev_err(&tsc->spi->dev, "unable to get DAV IRQ");
+               goto err3;
+       }
+       set_irq_wake(ts->irq, 1);
+
+       device_create_file(&tsc->spi->dev, &dev_attr_pen_down);
+       device_create_file(&tsc->spi->dev, &dev_attr_disable_ts);
+
+       r = input_register_device(idev);
+       if (r < 0) {
+               dev_err(&tsc->spi->dev, "can't register touchscreen device\n");
+               goto err4;
+       }
+
+       return 0;
+err4:
+       device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts);
+       device_remove_file(&tsc->spi->dev, &dev_attr_pen_down);
+       free_irq(ts->irq, tsc);
+err3:
+       tsc2301_ts_stop_scan(tsc);
+       input_free_device(idev);
+err2:
+#ifdef CONFIG_ARCH_OMAP
+       omap_free_gpio(dav_gpio);
+#endif
+err1:
+       kfree(ts);
+       return r;
+}
+
+void __devexit tsc2301_ts_exit(struct tsc2301 *tsc)
+{
+       struct tsc2301_ts *ts = tsc->ts;
+       unsigned long flags;
+
+       spin_lock_irqsave(&ts->lock, flags);
+       tsc2301_ts_disable(tsc);
+       spin_unlock_irqrestore(&ts->lock, flags);
+
+       device_remove_file(&tsc->spi->dev, &dev_attr_disable_ts);
+       device_remove_file(&tsc->spi->dev, &dev_attr_pen_down);
+
+       free_irq(ts->irq, tsc);
+       input_unregister_device(ts->idev);
+
+#ifdef CONFIG_ARCH_OMAP
+       omap_free_gpio(ts->dav_gpio);
+#endif
+       kfree(ts);
+}
+MODULE_AUTHOR("Jarkko Oikarinen <jarkko.oikarinen@nokia.com>");
+MODULE_LICENSE("GPL");
index 6621042bf96f45e5829ad561346dcbf62c56ccd8..adf460e39bef383dee32eeebc0acb3006e4660a1 100644 (file)
@@ -96,6 +96,7 @@ struct tsc2301 {
 #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)
@@ -118,6 +119,7 @@ struct tsc2301 {
 #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
@@ -156,8 +158,6 @@ extern void tsc2301_read_buf(struct tsc2301 *tsc, int reg, u16 *buf, int len);
 extern int  tsc2301_##module##_init(struct tsc2301 *tsc,               \
                           struct tsc2301_platform_data *pdata);        \
 extern void tsc2301_##module##_exit(struct tsc2301 *tsc);              \
-extern void tsc2301_##module##_prep_for_clk_stop(struct tsc2301 *tsc); \
-extern void tsc2301_##module##_cont_after_clk_stop(struct tsc2301 *tsc);\
 extern int  tsc2301_##module##_suspend(struct tsc2301 *tsc);           \
 extern void tsc2301_##module##_resume(struct tsc2301 *tsc);
 
@@ -168,10 +168,6 @@ static inline int tsc2301_##module##_init(struct tsc2301 *tsc,             \
        return 0;                                                       \
 }                                                                      \
 static inline void tsc2301_##module##_exit(struct tsc2301 *tsc) {}     \
-static inline void tsc2301_##module##_prep_for_clk_stop                        \
-                                       (struct tsc2301 *tsc) {}        \
-static inline void tsc2301_##module##_cont_after_clk_stop              \
-                                       (struct tsc2301 *tsc) {}        \
 static inline int  tsc2301_##module##_suspend(struct tsc2301 *tsc)     \
 {                                                                      \
        return 0;                                                       \
@@ -180,8 +176,10 @@ static inline void tsc2301_##module##_resume(struct tsc2301 *tsc) {}
 
 #ifdef CONFIG_SPI_TSC2301_KEYPAD
 TSC2301_DECL_MOD(kp)
+void tsc2301_kp_restart(struct tsc2301 *tsc);
 #else
 TSC2301_DECL_EMPTY_MOD(kp)
+void tsc2301_kp_restart(struct tsc2301 *tsc) {}
 #endif
 
 #ifdef CONFIG_SPI_TSC2301_TOUCHSCREEN
@@ -201,7 +199,7 @@ extern int tsc2301_mixer_register_controls(struct device *tsc_dev,
 TSC2301_DECL_EMPTY_MOD(mixer)
 #endif
 
-extern void tsc2301_enable_mclk(struct device *tsc_dev);
-extern void tsc2301_disable_mclk(struct device *tsc_dev);
+extern void tsc2301_mixer_enable_mclk(struct device *tsc_dev);
+extern void tsc2301_mixer_disable_mclk(struct device *tsc_dev);
 
 #endif