Commit da2bb7fa authored by Jarkko Nikula's avatar Jarkko Nikula Committed by Tony Lindgren

SPI: Add support for TSC2301 protocol driver

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