From d58eb964986dec534c88e2937af9a28f8af2140a Mon Sep 17 00:00:00 2001 From: Imre Deak Date: Mon, 2 Apr 2007 13:11:36 -0400 Subject: [PATCH] SPI: Add support for TSC2101 The new driver is functionally the same as the old drivers/ssi/omap-tsc2101 driver in the linux-omap tree. Signed-off-by: Imre Deak --- drivers/spi/Kconfig | 8 + drivers/spi/Makefile | 1 + drivers/spi/tsc2101.c | 316 ++++++++++++++++++++++++++++++++++++ include/linux/spi/tsc2101.h | 43 +++++ 4 files changed, 368 insertions(+) create mode 100644 drivers/spi/tsc2101.c create mode 100644 include/linux/spi/tsc2101.h diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig index f19e395d758c..0edda175881c 100644 --- a/drivers/spi/Kconfig +++ b/drivers/spi/Kconfig @@ -158,6 +158,14 @@ config SPI_AT25 This driver can also be built as a module. If so, the module will be called at25. +config SPI_TSC2101 + depends on SPI_MASTER + tristate "TSC2101 chip support" + ---help--- + Say Y here if you want support for the TSC2101 chip. + At the moment it provides basic register read / write interface + as well as a way to enable the MCLK clock. + # # Add new SPI protocol masters in alphabetical order above this line # diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile index 457f35be3d24..3ed26bff0afe 100644 --- a/drivers/spi/Makefile +++ b/drivers/spi/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_SPI_S3C24XX) += spi_s3c24xx.o # SPI protocol drivers (device/link on bus) obj-$(CONFIG_SPI_AT25) += at25.o +obj-$(CONFIG_SPI_TSC2101) += tsc2101.o # ... add above this line ... # SPI slave controller drivers (upstream link) diff --git a/drivers/spi/tsc2101.c b/drivers/spi/tsc2101.c new file mode 100644 index 000000000000..258d856349ac --- /dev/null +++ b/drivers/spi/tsc2101.c @@ -0,0 +1,316 @@ +/* + * linux/drivers/spi/tsc2101.c + * + * TSC2101 codec interface driver for the OMAP platform + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004/11/07 Nishanth Menon - Modified for common hooks for Audio and Touchscreen + */ + +#include +#include +#include +#include +#include + +struct tsc2101_device { + struct mutex mutex; + int mclk_enabled; + struct clock *mclk_ck; + struct spi_message message; + struct spi_transfer transfer[2]; + u16 command; + void (*enable_mclk)(struct spi_device *spi); + void (*disable_mclk)(struct spi_device *spi); +}; + +int tsc2101_enable_mclk(struct spi_device *spi) +{ + struct tsc2101_device *tsc2101; + + tsc2101 = spi_get_drvdata(spi); + + mutex_lock(&tsc2101->mutex); + + if (spi->dev.power.power_state.event != PM_EVENT_ON) { + mutex_unlock(&tsc2101->mutex); + return -ENODEV; + } + + if (tsc2101->mclk_enabled++ == 0) { + if (tsc2101->enable_mclk != NULL) + tsc2101->enable_mclk(spi); + } + + mutex_unlock(&tsc2101->mutex); + return 0; +} +EXPORT_SYMBOL(tsc2101_enable_mclk); + +void tsc2101_disable_mclk(struct spi_device *spi) +{ + struct tsc2101_device *tsc2101; + + tsc2101 = spi_get_drvdata(spi); + + mutex_lock(&tsc2101->mutex); + + if (--tsc2101->mclk_enabled == 0) { + if (tsc2101->disable_mclk != NULL && + spi->dev.power.power_state.event == PM_EVENT_ON) + tsc2101->disable_mclk(spi); + } + + mutex_lock(&tsc2101->mutex); +} +EXPORT_SYMBOL(tsc2101_disable_mclk); + +int tsc2101_write_sync(struct spi_device *spi, int page, u8 address, u16 data) +{ + struct tsc2101_device *tsc2101; + struct spi_message *m; + struct spi_transfer *t; + int ret; + + tsc2101 = spi_get_drvdata(spi); + + mutex_lock(&tsc2101->mutex); + if (spi->dev.power.power_state.event != PM_EVENT_ON) { + mutex_unlock(&tsc2101->mutex); + return -ENODEV; + } + + m = &tsc2101->message; + spi_message_init(m); + t = &tsc2101->transfer[0]; + memset(t, 0, sizeof(tsc2101->transfer)); + + /* Address */ + tsc2101->command = (page << 11) | (address << 5); + t->tx_buf = &tsc2101->command; + t->len = 2; + spi_message_add_tail(t, m); + + /* Data */ + t++; + t->tx_buf = &data; + t->len = 2; + spi_message_add_tail(t, m); + + ret = spi_sync(spi, m); + if (!ret) + ret = tsc2101->message.status; + mutex_unlock(&tsc2101->mutex); + + return ret; +} +EXPORT_SYMBOL(tsc2101_write_sync); + +int tsc2101_reads_sync(struct spi_device *spi, + int page, u8 startaddress, u16 *data, int numregs) +{ + struct tsc2101_device *tsc2101; + struct spi_message *m; + struct spi_transfer *t; + int ret; + + tsc2101 = spi_get_drvdata(spi); + + mutex_lock(&tsc2101->mutex); + if (spi->dev.power.power_state.event != PM_EVENT_ON) { + mutex_unlock(&tsc2101->mutex); + return -ENODEV; + } + + m = &tsc2101->message; + spi_message_init(m); + t = &tsc2101->transfer[0]; + memset(t, 0, sizeof(tsc2101->transfer)); + + /* Address */ + tsc2101->command = 0x8000 | (page << 11) | (startaddress << 5); + t->tx_buf = &tsc2101->command; + t->len = 2; + spi_message_add_tail(t, m); + + /* Data */ + t++; + t->rx_buf = data; + t->len = numregs << 1; + spi_message_add_tail(t, m); + + ret = spi_sync(spi, m); + if (!ret) + ret = tsc2101->message.status; + + mutex_unlock(&tsc2101->mutex); + + return ret; +} +EXPORT_SYMBOL(tsc2101_reads_sync); + +int tsc2101_read_sync(struct spi_device *spi, int page, u8 address) +{ + int err; + u16 val; + + err = tsc2101_reads_sync(spi, page, address, &val, 1); + if (err) + return err; + return val; +} +EXPORT_SYMBOL(tsc2101_read_sync); + +static int tsc2101_suspend(struct spi_device *spi, pm_message_t state) +{ + struct tsc2101_device *tsc2101; + + tsc2101 = spi_get_drvdata(spi); + + if (tsc2101 == NULL) + return 0; + + mutex_lock(&tsc2101->mutex); + + spi->dev.power.power_state = state; + if (tsc2101->mclk_enabled && tsc2101->disable_mclk != NULL) + tsc2101->disable_mclk(spi); + + mutex_unlock(&tsc2101->mutex); + + return 0; +} + +static int tsc2101_resume(struct spi_device *spi) +{ + struct tsc2101_device *tsc2101; + + tsc2101 = spi_get_drvdata(spi); + + if (tsc2101 == NULL) + return 0; + + mutex_lock(&tsc2101->mutex); + + spi->dev.power.power_state = PMSG_ON; + if (tsc2101->mclk_enabled && tsc2101->enable_mclk != NULL) + tsc2101->enable_mclk(spi); + + mutex_unlock(&tsc2101->mutex); + + return 0; +} + +static int tsc2101_probe(struct spi_device *spi) +{ + struct tsc2101_platform_data *pdata; + struct tsc2101_device *tsc2101; + u16 w; + int r; + + pdata = spi->dev.platform_data; + if (pdata == NULL) { + dev_err(&spi->dev, "no platform data\n"); + return -ENODEV; + } + + tsc2101 = kzalloc(sizeof(*tsc2101), GFP_KERNEL); + if (tsc2101 == NULL) { + dev_err(&spi->dev, "out of mem\n"); + return -ENOMEM; + } + + spi_set_drvdata(spi, tsc2101); + tsc2101->enable_mclk = pdata->enable_mclk; + tsc2101->disable_mclk = pdata->disable_mclk; + + mutex_init(&tsc2101->mutex); + + spi->mode = SPI_MODE_0; + spi->bits_per_word = 16; + if ((r = spi_setup(spi)) < 0) { + dev_err(&spi->dev, "SPI setup failed\n"); + goto err; + } + + w = tsc2101_read_sync(spi, 1, 0); + if (!(w & (1 << 14))) { + dev_err(&spi->dev, "invalid ADC register value %04x\n", w); + goto err; + } + + if (pdata->init != NULL) { + if ((r = pdata->init(spi)) < 0) { + dev_err(&spi->dev, "platform init failed\n"); + goto err; + } + } + + dev_info(&spi->dev, "initialized\n"); + + return 0; +err: + kfree(tsc2101); + return r; +} + +static int tsc2101_remove(struct spi_device *spi) +{ + struct tsc2101_platform_data *pdata; + struct tsc2101_device *tsc2101; + + pdata = spi->dev.platform_data; + tsc2101 = spi_get_drvdata(spi); + + /* We assume that this can't race with the rest of the driver. */ + if (tsc2101->mclk_enabled && tsc2101->disable_mclk != NULL) + tsc2101->disable_mclk(spi); + + if (pdata->cleanup != NULL) + pdata->cleanup(spi); + + spi_set_drvdata(spi, NULL); + kfree(tsc2101); + + return 0; +} + +static struct spi_driver tsc2101_driver = { + .probe = tsc2101_probe, + .remove = tsc2101_remove, + .suspend = tsc2101_suspend, + .resume = tsc2101_resume, + .driver = { + .name = "tsc2101", + .owner = THIS_MODULE, + }, +}; + +static int tsc2101_init(void) +{ + return spi_register_driver(&tsc2101_driver); +} + +static void tsc2101_exit(void) +{ + spi_unregister_driver(&tsc2101_driver); +} + +module_init(tsc2101_init); +module_exit(tsc2101_exit); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION + ("Glue audio driver for the TI OMAP1610/OMAP1710 TSC2101 codec."); +MODULE_LICENSE("GPL"); diff --git a/include/linux/spi/tsc2101.h b/include/linux/spi/tsc2101.h new file mode 100644 index 000000000000..01e6d231a3b0 --- /dev/null +++ b/include/linux/spi/tsc2101.h @@ -0,0 +1,43 @@ +/* + * include/linux/spi/tsc2101.h + * + * TSC2101 codec interface driver for the OMAP platform + * + * Copyright (C) 2004 Texas Instruments, Inc. + * + * This package is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * + * History: + * + * 2004/11/07 Nishanth Menon - Provided common hooks for Audio and Touchscreen + */ + +#ifndef __OMAP_TSC2101_H +#define __OMAP_TSC2101_H + +#include + +struct tsc2101_platform_data { + int (*init)(struct spi_device *spi); + void (*cleanup)(struct spi_device *spi); + void (*enable_mclk)(struct spi_device *spi); + void (*disable_mclk)(struct spi_device *spi); +}; + +extern int tsc2101_read_sync(struct spi_device *spi, int page, u8 address); +extern int tsc2101_reads_sync(struct spi_device *spi, int page, + u8 startaddress, u16 * data, int numregs); +extern int tsc2101_write_sync(struct spi_device *spi, int page, u8 address, + u16 data); + +extern int tsc2101_enable_mclk(struct spi_device *spi); +extern void tsc2101_disable_mclk(struct spi_device *spi); + +#endif + -- 2.39.2