From b96dbc75c274014171a53da13686bd90d239c023 Mon Sep 17 00:00:00 2001 From: Radek Polak Date: Fri, 9 Apr 2010 09:15:40 +0200 Subject: [PATCH 02/14] accels.patch adds support for accelerometers. You will need include/linux/lis302dl.h and drivers/input/misc/lis302dl.c from andy-tracking. The patch needs spi_bitbang_transfer_sync() and bitbang_work() to be ported correctly (some fixes from original 2.6.32 are missing). Signed-off-by: Martin Jansa --- arch/arm/mach-s3c2410/include/mach/spi-gpio.h | 3 +- arch/arm/mach-s3c2442/mach-gta02.c | 157 ++++ drivers/input/misc/Kconfig | 9 + drivers/input/misc/Makefile | 1 + drivers/input/misc/lis302dl.c | 952 +++++++++++++++++++++++++ drivers/spi/spi_bitbang.c | 231 ++++--- drivers/spi/spi_s3c24xx_gpio.c | 7 +- include/linux/lis302dl.h | 152 ++++ include/linux/spi/spi.h | 30 + include/linux/spi/spi_bitbang.h | 5 + 10 files changed, 1433 insertions(+), 114 deletions(-) create mode 100644 drivers/input/misc/lis302dl.c create mode 100644 include/linux/lis302dl.h diff --git a/arch/arm/mach-s3c2410/include/mach/spi-gpio.h b/arch/arm/mach-s3c2410/include/mach/spi-gpio.h index 980a099..0ff8949 100644 --- a/arch/arm/mach-s3c2410/include/mach/spi-gpio.h +++ b/arch/arm/mach-s3c2410/include/mach/spi-gpio.h @@ -21,7 +21,8 @@ struct s3c2410_spigpio_info { int num_chipselect; int bus_num; - void (*chip_select)(struct s3c2410_spigpio_info *spi, int cs); + int non_blocking_transfer; + void (*chip_select)(struct s3c2410_spigpio_info *spi, int csid, int cs); }; diff --git a/arch/arm/mach-s3c2442/mach-gta02.c b/arch/arm/mach-s3c2442/mach-gta02.c index 7965cad..0675181 100644 --- a/arch/arm/mach-s3c2442/mach-gta02.c +++ b/arch/arm/mach-s3c2442/mach-gta02.c @@ -63,6 +63,7 @@ #include #include +#include #include #include @@ -120,6 +121,22 @@ #include #endif +#define S3C2410_GPIONO(bank,offset) ((bank) + (offset)) + +#define S3C2410_GPIO_BANKD (32*3) +#define S3C2410_GPIO_BANKG (32*6) + +#define S3C2410_GPG5 S3C2410_GPIONO(S3C2410_GPIO_BANKG, 5) +#define S3C2410_GPG6 S3C2410_GPIONO(S3C2410_GPIO_BANKG, 6) +#define S3C2410_GPG7 S3C2410_GPIONO(S3C2410_GPIO_BANKG, 7) +#define S3C2410_GPD12 S3C2410_GPIONO(S3C2410_GPIO_BANKD, 12) +#define S3C2410_GPD13 S3C2410_GPIONO(S3C2410_GPIO_BANKD, 13) + +#define BITBANG_CS_ACTIVE 1 /* normally nCS, active low */ +#define BITBANG_CS_INACTIVE 0 + +#define S3C_SYSTEM_REV_ATAG GTA02v6_SYSTEM_REV + struct pcf50633 *gta02_pcf; /* @@ -775,6 +792,60 @@ const struct jbt6k74_platform_data jbt6k74_pdata = { .gpio_reset = GTA02_GPIO_GLAMO(4), }; +/*----------- SPI: Accelerometers attached to SPI of s3c244x ----------------- */ + +void gta02_lis302dl_suspend_io(struct lis302dl_info *lis, int resume) +{ + struct lis302dl_platform_data *pdata = lis->pdata; + + if (!resume) { + /* + * we don't want to power them with a high level + * because GSENSOR_3V3 is not up during suspend + */ + s3c2410_gpio_setpin(pdata->pin_chip_select, 0); + s3c2410_gpio_setpin(pdata->pin_clk, 0); + s3c2410_gpio_setpin(pdata->pin_mosi, 0); + /* misnomer: it is a pullDOWN in 2442 */ + s3c2410_gpio_pullup(pdata->pin_miso, 1); + return; + } + + /* back to normal */ + s3c2410_gpio_setpin(pdata->pin_chip_select, 1); + s3c2410_gpio_setpin(pdata->pin_clk, 1); + /* misnomer: it is a pullDOWN in 2442 */ + s3c2410_gpio_pullup(pdata->pin_miso, 0); + + s3c2410_gpio_cfgpin(pdata->pin_chip_select, S3C2410_GPIO_OUTPUT); + s3c2410_gpio_cfgpin(pdata->pin_clk, S3C2410_GPIO_OUTPUT); + s3c2410_gpio_cfgpin(pdata->pin_mosi, S3C2410_GPIO_OUTPUT); + s3c2410_gpio_cfgpin(pdata->pin_miso, S3C2410_GPIO_INPUT); + +} + +struct lis302dl_platform_data lis302_pdata_top = { + .name = "lis302-1 (top)", + .pin_chip_select= S3C2410_GPD12, + .pin_clk = S3C2410_GPG7, + .pin_mosi = S3C2410_GPG6, + .pin_miso = S3C2410_GPG5, + .interrupt = GTA02_IRQ_GSENSOR_1, + .open_drain = 1, /* altered at runtime by PCB rev */ + .lis302dl_suspend_io = gta02_lis302dl_suspend_io, +}; + +struct lis302dl_platform_data lis302_pdata_bottom = { + .name = "lis302-2 (bottom)", + .pin_chip_select= S3C2410_GPD13, + .pin_clk = S3C2410_GPG7, + .pin_mosi = S3C2410_GPG6, + .pin_miso = S3C2410_GPG5, + .interrupt = GTA02_IRQ_GSENSOR_2, + .open_drain = 1, /* altered at runtime by PCB rev */ + .lis302dl_suspend_io = gta02_lis302dl_suspend_io, +}; + static struct spi_board_info gta02_spi_board_info[] = { { .modalias = "jbt6k74", @@ -785,6 +856,81 @@ static struct spi_board_info gta02_spi_board_info[] = { .bus_num = 2, .chip_select = 0 }, + { + .modalias = "lis302dl", + /* platform_data */ + .platform_data = &lis302_pdata_top, + /* controller_data */ + /* irq */ + .max_speed_hz = 100 * 1000, + .bus_num = 3, + .chip_select = 0, + }, + { + .modalias = "lis302dl", + /* platform_data */ + .platform_data = &lis302_pdata_bottom, + /* controller_data */ + /* irq */ + .max_speed_hz = 100 * 1000, + .bus_num = 3, + .chip_select = 1, + }, +}; + +static void gta02_lis302_chip_select(struct s3c2410_spigpio_info *info, int csid, int cs) +{ + + /* + * Huh... "quirk"... CS on this device is not really "CS" like you can + * expect. + * + * When it is 0 it selects SPI interface mode. + * When it is 1 it selects I2C interface mode. + * + * Because we have 2 devices on one interface we have to make sure + * that the "disabled" device (actually in I2C mode) don't think we're + * talking to it. + * + * When we talk to the "enabled" device, the "disabled" device sees + * the clocks as I2C clocks, creating havoc. + * + * I2C sees MOSI going LOW while CLK HIGH as a START action, thus we + * must ensure this is never issued. + */ + + int cs_gpio, other_cs_gpio; + + cs_gpio = csid ? S3C2410_GPD13 : S3C2410_GPD12; + other_cs_gpio = (1 - csid) ? S3C2410_GPD13 : S3C2410_GPD12; + + + if (cs == BITBANG_CS_ACTIVE) { + s3c2410_gpio_setpin(other_cs_gpio, 1); + s3c2410_gpio_setpin(cs_gpio, 1); + s3c2410_gpio_setpin(info->pin_clk, 1); + s3c2410_gpio_setpin(cs_gpio, 0); + } else { + s3c2410_gpio_setpin(cs_gpio, 1); + s3c2410_gpio_setpin(other_cs_gpio, 1); + } +} + +static struct s3c2410_spigpio_info gta02_spigpio_cfg = { + .pin_clk = S3C2410_GPG7, + .pin_mosi = S3C2410_GPG6, + .pin_miso = S3C2410_GPG5, + .bus_num = 3, + .num_chipselect = 2, + .chip_select = gta02_lis302_chip_select, + .non_blocking_transfer = 1, +}; + +static struct platform_device gta02_spi_gpio_dev = { + .name = "spi_s3c24xx_gpio", + .dev = { + .platform_data = >a02_spigpio_cfg, + }, }; /* BQ27000 Battery */ @@ -1135,6 +1281,7 @@ static struct platform_device *gta02_devices[] __initdata = { static struct platform_device *gta02_devices_pmu_children[] = { &s3c_device_ts, >a02_glamo_dev, + >a02_spi_gpio_dev, &s3c_device_timer[2], >a02_hdq_device, >a02_gps_userspace_consumer @@ -1361,6 +1508,16 @@ static void __init gta02_machine_init(void) /* Set the panic callback to make AUX LED blink at ~5Hz. */ panic_blink = gta02_panic_blink; + switch (S3C_SYSTEM_REV_ATAG) { + case GTA02v6_SYSTEM_REV: + /* we need push-pull interrupt from motion sensors */ + lis302_pdata_top.open_drain = 0; + lis302_pdata_bottom.open_drain = 0; + break; + default: + break; + } + s3c_device_ts.name = "s3c2440-ts"; bus_register_notifier(&platform_bus_type, >a02_device_register_notifier); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index a9bb254..2e9bc5d 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -317,4 +317,13 @@ config INPUT_PCAP To compile this driver as a module, choose M here: the module will be called pcap_keys. +config INPUT_LIS302DL + tristate "STmicro LIS302DL 3-axis accelerometer" + depends on SPI_MASTER + help + SPI driver for the STmicro LIS302DL 3-axis accelerometer. + + The userspece interface is a 3-axis (X/Y/Z) relative movement + Linux input device, reporting REL_[XYZ] events. + endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index a8b8485..7e63293 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -30,4 +30,5 @@ obj-$(CONFIG_INPUT_WINBOND_CIR) += winbond-cir.o obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o obj-$(CONFIG_INPUT_YEALINK) += yealink.o +obj-$(CONFIG_INPUT_LIS302DL) += lis302dl.o diff --git a/drivers/input/misc/lis302dl.c b/drivers/input/misc/lis302dl.c new file mode 100644 index 0000000..d345bfb --- /dev/null +++ b/drivers/input/misc/lis302dl.c @@ -0,0 +1,952 @@ +/* Linux kernel driver for the ST LIS302D 3-axis accelerometer + * + * Copyright (C) 2007-2008 by Openmoko, Inc. + * Author: Harald Welte + * converted to private bitbang by: + * Andy Green + * ability to set acceleration threshold added by: + * Simon Kagstrom + * All rights reserved. + * + * 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 + * + * TODO + * * statistics for overflow events + * * configuration interface (sysfs) for + * * enable/disable x/y/z axis data ready + * * enable/disable resume from freee fall / click + * * free fall / click parameters + * * high pass filter parameters + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* Utility functions */ +static u8 __reg_read(struct lis302dl_info *lis, u8 reg) +{ + struct spi_message msg; + struct spi_transfer t; + u8 data[2] = {0xc0 | reg}; + int rc; + + spi_message_init(&msg); + memset(&t, 0, sizeof t); + t.len = 2; + spi_message_add_tail(&t, &msg); + t.tx_buf = &data[0]; + t.rx_buf = &data[0]; + + /* Should complete without blocking */ + rc = spi_non_blocking_transfer(lis->spi, &msg); + if (rc < 0) { + dev_err(lis->dev, "Error reading register\n"); + return rc; + } + + return data[1]; +} + +static void __reg_write(struct lis302dl_info *lis, u8 reg, u8 val) +{ + struct spi_message msg; + struct spi_transfer t; + u8 data[2] = {reg, val}; + + spi_message_init(&msg); + memset(&t, 0, sizeof t); + t.len = 2; + spi_message_add_tail(&t, &msg); + t.tx_buf = &data[0]; + t.rx_buf = &data[0]; + + /* Completes without blocking */ + if (spi_non_blocking_transfer(lis->spi, &msg) < 0) + dev_err(lis->dev, "Error writing register\n"); +} + +static void __reg_set_bit_mask(struct lis302dl_info *lis, u8 reg, u8 mask, + u8 val) +{ + u_int8_t tmp; + + val &= mask; + + tmp = __reg_read(lis, reg); + tmp &= ~mask; + tmp |= val; + __reg_write(lis, reg, tmp); +} + +static int __ms_to_duration(struct lis302dl_info *lis, int ms) +{ + /* If we have 400 ms sampling rate, the stepping is 2.5 ms, + * on 100 ms the stepping is 10ms */ + if (lis->flags & LIS302DL_F_DR) + return min((ms * 10) / 25, 637); + + return min(ms / 10, 2550); +} + +static int __duration_to_ms(struct lis302dl_info *lis, int duration) +{ + if (lis->flags & LIS302DL_F_DR) + return (duration * 25) / 10; + + return duration * 10; +} + +static u8 __mg_to_threshold(struct lis302dl_info *lis, int mg) +{ + /* If FS is set each bit is 71mg, otherwise 18mg. The THS register + * has 7 bits for the threshold value */ + if (lis->flags & LIS302DL_F_FS) + return min(mg / 71, 127); + + return min(mg / 18, 127); +} + +static int __threshold_to_mg(struct lis302dl_info *lis, u8 threshold) +{ + if (lis->flags & LIS302DL_F_FS) + return threshold * 71; + + return threshold * 18; +} + +/* interrupt handling related */ + +enum lis302dl_intmode { + LIS302DL_INTMODE_GND = 0x00, + LIS302DL_INTMODE_FF_WU_1 = 0x01, + LIS302DL_INTMODE_FF_WU_2 = 0x02, + LIS302DL_INTMODE_FF_WU_12 = 0x03, + LIS302DL_INTMODE_DATA_READY = 0x04, + LIS302DL_INTMODE_CLICK = 0x07, +}; + +static void __lis302dl_int_mode(struct device *dev, int int_pin, + enum lis302dl_intmode mode) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + + switch (int_pin) { + case 1: + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL3, 0x07, mode); + break; + case 2: + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL3, 0x38, mode << 3); + break; + default: + BUG(); + } +} + +static void __enable_wakeup(struct lis302dl_info *lis) +{ + __reg_write(lis, LIS302DL_REG_CTRL1, 0); + + /* First zero to get to a known state */ + __reg_write(lis, LIS302DL_REG_FF_WU_CFG_1, LIS302DL_FFWUCFG_XHIE | + LIS302DL_FFWUCFG_YHIE | LIS302DL_FFWUCFG_ZHIE | + LIS302DL_FFWUCFG_LIR); + __reg_write(lis, LIS302DL_REG_FF_WU_THS_1, + __mg_to_threshold(lis, lis->wakeup.threshold)); + __reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1, + __ms_to_duration(lis, lis->wakeup.duration)); + + /* Route the interrupt for wakeup */ + __lis302dl_int_mode(lis->dev, 1, + LIS302DL_INTMODE_FF_WU_1); + + __reg_read(lis, LIS302DL_REG_HP_FILTER_RESET); + __reg_read(lis, LIS302DL_REG_OUT_X); + __reg_read(lis, LIS302DL_REG_OUT_Y); + __reg_read(lis, LIS302DL_REG_OUT_Z); + __reg_read(lis, LIS302DL_REG_STATUS); + __reg_read(lis, LIS302DL_REG_FF_WU_SRC_1); + __reg_read(lis, LIS302DL_REG_FF_WU_SRC_2); + __reg_write(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD | 7); +} + +static void __enable_data_collection(struct lis302dl_info *lis) +{ + u_int8_t ctrl1 = LIS302DL_CTRL1_PD | LIS302DL_CTRL1_Xen | + LIS302DL_CTRL1_Yen | LIS302DL_CTRL1_Zen; + + /* make sure we're powered up and generate data ready */ + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, ctrl1, ctrl1); + + /* If the threshold is zero, let the device generated an interrupt + * on each datum */ + if (lis->threshold == 0) { + __reg_write(lis, LIS302DL_REG_CTRL2, 0); + __lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_DATA_READY); + __lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_DATA_READY); + } else { + __reg_write(lis, LIS302DL_REG_CTRL2, + LIS302DL_CTRL2_HPFF1); + __reg_write(lis, LIS302DL_REG_FF_WU_THS_1, + __mg_to_threshold(lis, lis->threshold)); + __reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1, + __ms_to_duration(lis, lis->duration)); + + /* Clear the HP filter "starting point" */ + __reg_read(lis, LIS302DL_REG_HP_FILTER_RESET); + __reg_write(lis, LIS302DL_REG_FF_WU_CFG_1, + LIS302DL_FFWUCFG_XHIE | LIS302DL_FFWUCFG_YHIE | + LIS302DL_FFWUCFG_ZHIE | LIS302DL_FFWUCFG_LIR); + __lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_FF_WU_12); + __lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_FF_WU_12); + } +} + +#if 0 +static void _report_btn_single(struct input_dev *inp, int btn) +{ + input_report_key(inp, btn, 1); + input_sync(inp); + input_report_key(inp, btn, 0); +} + +static void _report_btn_double(struct input_dev *inp, int btn) +{ + input_report_key(inp, btn, 1); + input_sync(inp); + input_report_key(inp, btn, 0); + input_sync(inp); + input_report_key(inp, btn, 1); + input_sync(inp); + input_report_key(inp, btn, 0); +} +#endif + + +static void lis302dl_bitbang_read_sample(struct lis302dl_info *lis) +{ + u8 data[(LIS302DL_REG_OUT_Z - LIS302DL_REG_STATUS) + 2] = {0xC0 | LIS302DL_REG_STATUS}; + u8 *read = data + 1; + unsigned long flags; + int mg_per_sample = __threshold_to_mg(lis, 1); + struct spi_message msg; + struct spi_transfer t; + + spi_message_init(&msg); + memset(&t, 0, sizeof t); + t.len = sizeof(data); + spi_message_add_tail(&t, &msg); + t.tx_buf = &data[0]; + t.rx_buf = &data[0]; + + /* grab the set of register containing status and XYZ data */ + + local_irq_save(flags); + + /* Should complete without blocking */ + if (spi_non_blocking_transfer(lis->spi, &msg) < 0) + dev_err(lis->dev, "Error reading registers\n"); + + local_irq_restore(flags); + + /* + * at the minute the test below fails 50% of the time due to + * a problem with level interrupts causing ISRs to get called twice. + * This is a workaround for that, but actually this test is still + * valid and the information can be used for overrrun stats. + */ + + /* has any kind of overrun been observed by the lis302dl? */ + if (read[0] & (LIS302DL_STATUS_XOR | + LIS302DL_STATUS_YOR | + LIS302DL_STATUS_ZOR)) + lis->overruns++; + + /* we have a valid sample set? */ + if (read[0] & LIS302DL_STATUS_XYZDA) { + input_report_abs(lis->input_dev, ABS_X, mg_per_sample * + (s8)read[LIS302DL_REG_OUT_X - LIS302DL_REG_STATUS]); + input_report_abs(lis->input_dev, ABS_Y, mg_per_sample * + (s8)read[LIS302DL_REG_OUT_Y - LIS302DL_REG_STATUS]); + input_report_abs(lis->input_dev, ABS_Z, mg_per_sample * + (s8)read[LIS302DL_REG_OUT_Z - LIS302DL_REG_STATUS]); + + input_sync(lis->input_dev); + } + + if (lis->threshold) + /* acknowledge the wakeup source */ + __reg_read(lis, LIS302DL_REG_FF_WU_SRC_1); +} + +static irqreturn_t lis302dl_interrupt(int irq, void *_lis) +{ + struct lis302dl_info *lis = _lis; + + lis302dl_bitbang_read_sample(lis); + return IRQ_HANDLED; +} + +/* sysfs */ + +static ssize_t show_overruns(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", lis->overruns); +} + +static DEVICE_ATTR(overruns, S_IRUGO, show_overruns, NULL); + +static ssize_t show_rate(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + u8 ctrl1; + unsigned long flags; + + local_irq_save(flags); + ctrl1 = __reg_read(lis, LIS302DL_REG_CTRL1); + local_irq_restore(flags); + + return sprintf(buf, "%d\n", ctrl1 & LIS302DL_CTRL1_DR ? 400 : 100); +} + +static ssize_t set_rate(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + unsigned long flags; + + local_irq_save(flags); + + if (!strcmp(buf, "400\n")) { + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR, + LIS302DL_CTRL1_DR); + lis->flags |= LIS302DL_F_DR; + } else { + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_DR, + 0); + lis->flags &= ~LIS302DL_F_DR; + } + local_irq_restore(flags); + + return count; +} + +static DEVICE_ATTR(sample_rate, S_IRUGO | S_IWUSR, show_rate, set_rate); + +static ssize_t show_scale(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + u_int8_t ctrl1; + unsigned long flags; + + local_irq_save(flags); + ctrl1 = __reg_read(lis, LIS302DL_REG_CTRL1); + local_irq_restore(flags); + + return sprintf(buf, "%s\n", ctrl1 & LIS302DL_CTRL1_FS ? "9.2" : "2.3"); +} + +static ssize_t set_scale(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + unsigned long flags; + + local_irq_save(flags); + + if (!strcmp(buf, "9.2\n")) { + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_FS, + LIS302DL_CTRL1_FS); + lis->flags |= LIS302DL_F_FS; + } else { + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_FS, + 0); + lis->flags &= ~LIS302DL_F_FS; + } + + if (lis->flags & LIS302DL_F_INPUT_OPEN) + __enable_data_collection(lis); + + local_irq_restore(flags); + + return count; +} + +static DEVICE_ATTR(full_scale, S_IRUGO | S_IWUSR, show_scale, set_scale); + +static ssize_t show_threshold(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + + /* Display the device view of the threshold setting */ + return sprintf(buf, "%d\n", __threshold_to_mg(lis, + __mg_to_threshold(lis, lis->threshold))); +} + +static ssize_t set_threshold(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + unsigned int val; + + if (sscanf(buf, "%u\n", &val) != 1) + return -EINVAL; + /* 8g is the maximum if FS is 1 */ + if (val > 8000) + return -ERANGE; + + /* Set the threshold and write it out if the device is used */ + lis->threshold = val; + + if (lis->flags & LIS302DL_F_INPUT_OPEN) { + unsigned long flags; + + local_irq_save(flags); + __enable_data_collection(lis); + local_irq_restore(flags); + } + + return count; +} + +static DEVICE_ATTR(threshold, S_IRUGO | S_IWUSR, show_threshold, set_threshold); + +static ssize_t show_duration(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + + return sprintf(buf, "%d\n", __duration_to_ms(lis, + __ms_to_duration(lis, lis->duration))); +} + +static ssize_t set_duration(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + unsigned int val; + + if (sscanf(buf, "%u\n", &val) != 1) + return -EINVAL; + if (val > 2550) + return -ERANGE; + + lis->duration = val; + if (lis->flags & LIS302DL_F_INPUT_OPEN) + __reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1, + __ms_to_duration(lis, lis->duration)); + + return count; +} + +static DEVICE_ATTR(duration, S_IRUGO | S_IWUSR, show_duration, set_duration); + +static ssize_t lis302dl_dump(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + int n = 0; + u8 reg[0x40]; + char *end = buf; + unsigned long flags; + + local_irq_save(flags); + + for (n = 0; n < sizeof(reg); n++) + reg[n] = __reg_read(lis, n); + + local_irq_restore(flags); + + for (n = 0; n < sizeof(reg); n += 16) { + hex_dump_to_buffer(reg + n, 16, 16, 1, end, 128, 0); + end += strlen(end); + *end++ = '\n'; + *end++ = '\0'; + } + + return end - buf; +} +static DEVICE_ATTR(dump, S_IRUGO, lis302dl_dump, NULL); + +/* Configure freefall/wakeup interrupts */ +static ssize_t set_wakeup_threshold(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + unsigned int threshold; + + if (sscanf(buf, "%u\n", &threshold) != 1) + return -EINVAL; + + if (threshold > 8000) + return -ERANGE; + + /* Zero turns the feature off */ + if (threshold == 0) { + if (lis->flags & LIS302DL_F_IRQ_WAKE) { + disable_irq_wake(lis->pdata->interrupt); + lis->flags &= ~LIS302DL_F_IRQ_WAKE; + } + + return count; + } + + lis->wakeup.threshold = threshold; + + if (!(lis->flags & LIS302DL_F_IRQ_WAKE)) { + enable_irq_wake(lis->pdata->interrupt); + lis->flags |= LIS302DL_F_IRQ_WAKE; + } + + return count; +} + +static ssize_t show_wakeup_threshold(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + + /* All events off? */ + if (lis->wakeup.threshold == 0) + return sprintf(buf, "off\n"); + + return sprintf(buf, "%u\n", lis->wakeup.threshold); +} + +static DEVICE_ATTR(wakeup_threshold, S_IRUGO | S_IWUSR, show_wakeup_threshold, + set_wakeup_threshold); + +static ssize_t set_wakeup_duration(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + unsigned int duration; + + if (sscanf(buf, "%u\n", &duration) != 1) + return -EINVAL; + + if (duration > 2550) + return -ERANGE; + + lis->wakeup.duration = duration; + + return count; +} + +static ssize_t show_wakeup_duration(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct lis302dl_info *lis = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", lis->wakeup.duration); +} + +static DEVICE_ATTR(wakeup_duration, S_IRUGO | S_IWUSR, show_wakeup_duration, + set_wakeup_duration); + +static struct attribute *lis302dl_sysfs_entries[] = { + &dev_attr_sample_rate.attr, + &dev_attr_full_scale.attr, + &dev_attr_threshold.attr, + &dev_attr_duration.attr, + &dev_attr_dump.attr, + &dev_attr_wakeup_threshold.attr, + &dev_attr_wakeup_duration.attr, + &dev_attr_overruns.attr, + NULL +}; + +static struct attribute_group lis302dl_attr_group = { + .name = NULL, + .attrs = lis302dl_sysfs_entries, +}; + +/* input device handling and driver core interaction */ + +static int lis302dl_input_open(struct input_dev *inp) +{ + struct lis302dl_info *lis = input_get_drvdata(inp); + unsigned long flags; + + local_irq_save(flags); + + __enable_data_collection(lis); + lis->flags |= LIS302DL_F_INPUT_OPEN; + + local_irq_restore(flags); + + return 0; +} + +static void lis302dl_input_close(struct input_dev *inp) +{ + struct lis302dl_info *lis = input_get_drvdata(inp); + u_int8_t ctrl1 = LIS302DL_CTRL1_Xen | LIS302DL_CTRL1_Yen | + LIS302DL_CTRL1_Zen; + unsigned long flags; + + local_irq_save(flags); + + /* since the input core already serializes access and makes sure we + * only see close() for the close of the last user, we can safely + * disable the data ready events */ + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, ctrl1, 0x00); + lis->flags &= ~LIS302DL_F_INPUT_OPEN; + + /* however, don't power down the whole device if still needed */ + if (!(lis->flags & LIS302DL_F_WUP_FF || + lis->flags & LIS302DL_F_WUP_CLICK)) { + __reg_set_bit_mask(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD, + 0x00); + } + local_irq_restore(flags); +} + +/* get the device to reload its coefficients from EEPROM and wait for it + * to complete + */ + +static int __lis302dl_reset_device(struct lis302dl_info *lis) +{ + int timeout = 10; + + __reg_write(lis, LIS302DL_REG_CTRL2, + LIS302DL_CTRL2_BOOT | LIS302DL_CTRL2_FDS); + + while ((__reg_read(lis, LIS302DL_REG_CTRL2) + & LIS302DL_CTRL2_BOOT) && (timeout--)) + mdelay(1); + + return !!(timeout < 0); +} + +static int __devinit lis302dl_probe(struct spi_device *spi) +{ + int rc; + struct lis302dl_info *lis; + u_int8_t wai; + unsigned long flags; + struct lis302dl_platform_data *pdata = spi->dev.platform_data; + + spi->mode = SPI_MODE_3; + rc = spi_setup(spi); + if (rc < 0) { + dev_err(&spi->dev, "spi_setup failed\n"); + return rc; + } + + lis = kzalloc(sizeof(*lis), GFP_KERNEL); + if (!lis) + return -ENOMEM; + + lis->dev = &spi->dev; + lis->spi = spi; + + dev_set_drvdata(lis->dev, lis); + + lis->pdata = pdata; + + rc = sysfs_create_group(&lis->dev->kobj, &lis302dl_attr_group); + if (rc) { + dev_err(lis->dev, "error creating sysfs group\n"); + goto bail_free_lis; + } + + /* initialize input layer details */ + lis->input_dev = input_allocate_device(); + if (!lis->input_dev) { + dev_err(lis->dev, "Unable to allocate input device\n"); + goto bail_sysfs; + } + + input_set_drvdata(lis->input_dev, lis); + lis->input_dev->name = pdata->name; + /* SPI Bus not defined as a valid bus for input subsystem*/ + lis->input_dev->id.bustype = BUS_I2C; /* lie about it */ + lis->input_dev->open = lis302dl_input_open; + lis->input_dev->close = lis302dl_input_close; + + rc = input_register_device(lis->input_dev); + if (rc) { + dev_err(lis->dev, "error %d registering input device\n", rc); + goto bail_inp_dev; + } + + local_irq_save(flags); + /* Configure our IO */ + (lis->pdata->lis302dl_suspend_io)(lis, 1); + + wai = __reg_read(lis, LIS302DL_REG_WHO_AM_I); + if (wai != LIS302DL_WHO_AM_I_MAGIC) { + dev_err(lis->dev, "unknown who_am_i signature 0x%02x\n", wai); + dev_set_drvdata(lis->dev, NULL); + rc = -ENODEV; + local_irq_restore(flags); + goto bail_inp_reg; + } + + set_bit(EV_ABS, lis->input_dev->evbit); + input_set_abs_params(lis->input_dev, ABS_X, 0, 0, 0, 0); + input_set_abs_params(lis->input_dev, ABS_Y, 0, 0, 0, 0); + input_set_abs_params(lis->input_dev, ABS_Z, 0, 0, 0, 0); + + + lis->threshold = 0; + lis->duration = 0; + memset(&lis->wakeup, 0, sizeof(lis->wakeup)); + + if (__lis302dl_reset_device(lis)) + dev_err(lis->dev, "device BOOT reload failed\n"); + + /* force us powered */ + __reg_write(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_PD | + LIS302DL_CTRL1_Xen | + LIS302DL_CTRL1_Yen | + LIS302DL_CTRL1_Zen); + mdelay(1); + + __reg_write(lis, LIS302DL_REG_CTRL2, 0); + __reg_write(lis, LIS302DL_REG_CTRL3, + LIS302DL_CTRL3_PP_OD | LIS302DL_CTRL3_IHL); + __reg_write(lis, LIS302DL_REG_FF_WU_THS_1, 0x0); + __reg_write(lis, LIS302DL_REG_FF_WU_DURATION_1, 0x00); + __reg_write(lis, LIS302DL_REG_FF_WU_CFG_1, 0x0); + + /* start off in powered down mode; we power up when someone opens us */ + __reg_write(lis, LIS302DL_REG_CTRL1, LIS302DL_CTRL1_Xen | + LIS302DL_CTRL1_Yen | LIS302DL_CTRL1_Zen); + + if (pdata->open_drain) + /* switch interrupt to open collector, active-low */ + __reg_write(lis, LIS302DL_REG_CTRL3, + LIS302DL_CTRL3_PP_OD | LIS302DL_CTRL3_IHL); + else + /* push-pull, active-low */ + __reg_write(lis, LIS302DL_REG_CTRL3, LIS302DL_CTRL3_IHL); + + __lis302dl_int_mode(lis->dev, 1, LIS302DL_INTMODE_GND); + __lis302dl_int_mode(lis->dev, 2, LIS302DL_INTMODE_GND); + + __reg_read(lis, LIS302DL_REG_STATUS); + __reg_read(lis, LIS302DL_REG_FF_WU_SRC_1); + __reg_read(lis, LIS302DL_REG_FF_WU_SRC_2); + __reg_read(lis, LIS302DL_REG_CLICK_SRC); + local_irq_restore(flags); + + dev_info(lis->dev, "Found %s\n", pdata->name); + + lis->pdata = pdata; + + set_irq_handler(lis->pdata->interrupt, handle_level_irq); + + rc = request_irq(lis->pdata->interrupt, lis302dl_interrupt, + IRQF_TRIGGER_LOW, "lis302dl", lis); + + if (rc < 0) { + dev_err(lis->dev, "error requesting IRQ %d\n", + lis->pdata->interrupt); + goto bail_inp_reg; + } + return 0; + +bail_inp_reg: + input_unregister_device(lis->input_dev); +bail_inp_dev: + input_free_device(lis->input_dev); +bail_sysfs: + sysfs_remove_group(&lis->dev->kobj, &lis302dl_attr_group); +bail_free_lis: + kfree(lis); + return rc; +} + +static int __devexit lis302dl_remove(struct spi_device *spi) +{ + struct lis302dl_info *lis = dev_get_drvdata(&spi->dev); + unsigned long flags; + + /* Disable interrupts */ + if (lis->flags & LIS302DL_F_IRQ_WAKE) + disable_irq_wake(lis->pdata->interrupt); + free_irq(lis->pdata->interrupt, lis); + + /* Reset and power down the device */ + local_irq_save(flags); + __reg_write(lis, LIS302DL_REG_CTRL3, 0x00); + __reg_write(lis, LIS302DL_REG_CTRL2, 0x00); + __reg_write(lis, LIS302DL_REG_CTRL1, 0x00); + local_irq_restore(flags); + + /* Cleanup resources */ + sysfs_remove_group(&spi->dev.kobj, &lis302dl_attr_group); + input_unregister_device(lis->input_dev); + if (lis->input_dev) + input_free_device(lis->input_dev); + dev_set_drvdata(lis->dev, NULL); + kfree(lis); + + return 0; +} + +#ifdef CONFIG_PM + +static u8 regs_to_save[] = { + LIS302DL_REG_CTRL2, + LIS302DL_REG_CTRL3, + LIS302DL_REG_FF_WU_CFG_1, + LIS302DL_REG_FF_WU_THS_1, + LIS302DL_REG_FF_WU_DURATION_1, + LIS302DL_REG_FF_WU_CFG_2, + LIS302DL_REG_FF_WU_THS_2, + LIS302DL_REG_FF_WU_DURATION_2, + LIS302DL_REG_CLICK_CFG, + LIS302DL_REG_CLICK_THSY_X, + LIS302DL_REG_CLICK_THSZ, + LIS302DL_REG_CLICK_TIME_LIMIT, + LIS302DL_REG_CLICK_LATENCY, + LIS302DL_REG_CLICK_WINDOW, + LIS302DL_REG_CTRL1, +}; + +static int lis302dl_suspend(struct spi_device *spi, pm_message_t state) +{ + struct lis302dl_info *lis = dev_get_drvdata(&spi->dev); + unsigned long flags; + u_int8_t tmp; + int n; + + /* determine if we want to wake up from the accel. */ + if (lis->flags & LIS302DL_F_WUP_CLICK) + return 0; + + disable_irq(lis->pdata->interrupt); + local_irq_save(flags); + + /* + * When we share SPI over multiple sensors, there is a race here + * that one or more sensors will lose. In that case, the shared + * SPI bus GPIO will be in sleep mode and partially pulled down. So + * we explicitly put our IO into "wake" mode here before the final + * traffic to the sensor. + */ + (lis->pdata->lis302dl_suspend_io)(lis, 1); + + /* save registers */ + for (n = 0; n < ARRAY_SIZE(regs_to_save); n++) + lis->regs[regs_to_save[n]] = + __reg_read(lis, regs_to_save[n]); + + /* power down or enable wakeup */ + + if (lis->wakeup.threshold == 0) { + tmp = __reg_read(lis, LIS302DL_REG_CTRL1); + tmp &= ~LIS302DL_CTRL1_PD; + __reg_write(lis, LIS302DL_REG_CTRL1, tmp); + } else + __enable_wakeup(lis); + + /* place our IO to the device in sleep-compatible states */ + (lis->pdata->lis302dl_suspend_io)(lis, 0); + + local_irq_restore(flags); + + return 0; +} + +static int lis302dl_resume(struct spi_device *spi) +{ + struct lis302dl_info *lis = dev_get_drvdata(&spi->dev); + unsigned long flags; + int n; + + if (lis->flags & LIS302DL_F_WUP_CLICK) + return 0; + + local_irq_save(flags); + + /* get our IO to the device back in operational states */ + (lis->pdata->lis302dl_suspend_io)(lis, 1); + + /* resume from powerdown first! */ + __reg_write(lis, LIS302DL_REG_CTRL1, + LIS302DL_CTRL1_PD | + LIS302DL_CTRL1_Xen | + LIS302DL_CTRL1_Yen | + LIS302DL_CTRL1_Zen); + mdelay(1); + + if (__lis302dl_reset_device(lis)) + dev_err(&spi->dev, "device BOOT reload failed\n"); + + /* restore registers after resume */ + for (n = 0; n < ARRAY_SIZE(regs_to_save); n++) + __reg_write(lis, regs_to_save[n], lis->regs[regs_to_save[n]]); + + /* if someone had us open, reset the non-wake threshold stuff */ + if (lis->flags & LIS302DL_F_INPUT_OPEN) + __enable_data_collection(lis); + + local_irq_restore(flags); + enable_irq(lis->pdata->interrupt); + + return 0; +} +#else +#define lis302dl_suspend NULL +#define lis302dl_resume NULL +#endif + +static struct spi_driver lis302dl_spi_driver = { + .driver = { + .name = "lis302dl", + .owner = THIS_MODULE, + }, + + .probe = lis302dl_probe, + .remove = __devexit_p(lis302dl_remove), + .suspend = lis302dl_suspend, + .resume = lis302dl_resume, +}; + +static int __devinit lis302dl_init(void) +{ + return spi_register_driver(&lis302dl_spi_driver); +} + +static void __exit lis302dl_exit(void) +{ + spi_unregister_driver(&lis302dl_spi_driver); +} + +MODULE_AUTHOR("Harald Welte "); +MODULE_LICENSE("GPL"); + +module_init(lis302dl_init); +module_exit(lis302dl_exit); diff --git a/drivers/spi/spi_bitbang.c b/drivers/spi/spi_bitbang.c index f1db395..5c50329 100644 --- a/drivers/spi/spi_bitbang.c +++ b/drivers/spi/spi_bitbang.c @@ -253,134 +253,139 @@ static int spi_bitbang_bufs(struct spi_device *spi, struct spi_transfer *t) * Drivers can provide word-at-a-time i/o primitives, or provide * transfer-at-a-time ones to leverage dma or fifo hardware. */ -static void bitbang_work(struct work_struct *work) +/* Synchronous non blocking transfer */ +int +spi_bitbang_transfer_sync(struct spi_device *spi, struct spi_message *m) { - struct spi_bitbang *bitbang = - container_of(work, struct spi_bitbang, work); - unsigned long flags; - int do_setup = -1; - int (*setup_transfer)(struct spi_device *, - struct spi_transfer *); + struct spi_bitbang *bitbang = spi_master_get_devdata(spi->master); + struct spi_transfer *t; + unsigned long flags; + int cs_change = 1; + int status; + int nsecs; + int (*setup_transfer)(struct spi_device *, struct spi_transfer *); + + /* FIXME this is made-up ... the correct value is known to + * word-at-a-time bitbang code, and presumably chipselect() + * should enforce these requirements too? + */ + nsecs = 100; + cs_change = 1; + status = 0; + setup_transfer = NULL; + + local_irq_save(flags); + list_for_each_entry (t, &m->transfers, transfer_list) { + /* override or restore speed and wordsize */ + if (t->speed_hz || t->bits_per_word) { + setup_transfer = bitbang->setup_transfer; + if (!setup_transfer) { + status = -ENOPROTOOPT; + break; + } + } + if (setup_transfer) { + status = setup_transfer(spi, t); + if (status < 0) + break; + } - setup_transfer = bitbang->setup_transfer; + /* set up default clock polarity, and activate chip; + * this implicitly updates clock and spi modes as + * previously recorded for this device via setup(). + * (and also deselects any other chip that might be + * selected ...) + */ - spin_lock_irqsave(&bitbang->lock, flags); - bitbang->busy = 1; - while (!list_empty(&bitbang->queue)) { - struct spi_message *m; - struct spi_device *spi; - unsigned nsecs; - struct spi_transfer *t = NULL; - unsigned tmp; - unsigned cs_change; - int status; + if (cs_change) { + bitbang->chipselect(spi, BITBANG_CS_ACTIVE); + ndelay(nsecs); + } - m = container_of(bitbang->queue.next, struct spi_message, - queue); - list_del_init(&m->queue); - spin_unlock_irqrestore(&bitbang->lock, flags); + cs_change = t->cs_change; + if (!t->tx_buf && !t->rx_buf && t->len) { + status = -EINVAL; + break; + } - /* FIXME this is made-up ... the correct value is known to - * word-at-a-time bitbang code, and presumably chipselect() - * should enforce these requirements too? + /* transfer data. the lower level code handles any + * new dma mappings it needs. our caller always gave + * us dma-safe buffers. */ - nsecs = 100; + if (t->len) { + /* REVISIT dma API still needs a designated + * DMA_ADDR_INVALID; ~0 might be better. + */ + if (!m->is_dma_mapped) + t->rx_dma = t->tx_dma = 0; + status = bitbang->txrx_bufs(spi, t); + } - spi = m->spi; - tmp = 0; - cs_change = 1; + if (status > 0) + m->actual_length += status; + if (status != t->len) { + /* always report some kind of error */ + if (status >= 0) + status = -EREMOTEIO; + break; + } status = 0; + /* protocol tweaks before next transfer */ + if (t->delay_usecs) + udelay(t->delay_usecs); + if (!cs_change) + continue; + if (t->transfer_list.next == &m->transfers) + break; + /* sometimes a short mid-message deselect of the chip + * may be needed to terminate a mode or command + */ + ndelay(nsecs); + bitbang->chipselect(spi, BITBANG_CS_INACTIVE); + ndelay(nsecs); + } - list_for_each_entry (t, &m->transfers, transfer_list) { - - /* override speed or wordsize? */ - if (t->speed_hz || t->bits_per_word) - do_setup = 1; - - /* init (-1) or override (1) transfer params */ - if (do_setup != 0) { - if (!setup_transfer) { - status = -ENOPROTOOPT; - break; - } - status = setup_transfer(spi, t); - if (status < 0) - break; - } + m->status = status; + if (m->complete) + m->complete(m->context); - /* set up default clock polarity, and activate chip; - * this implicitly updates clock and spi modes as - * previously recorded for this device via setup(). - * (and also deselects any other chip that might be - * selected ...) - */ - if (cs_change) { - bitbang->chipselect(spi, BITBANG_CS_ACTIVE); - ndelay(nsecs); - } - cs_change = t->cs_change; - if (!t->tx_buf && !t->rx_buf && t->len) { - status = -EINVAL; - break; - } + /* restore speed and wordsize */ + if (setup_transfer) + setup_transfer(spi, NULL); - /* transfer data. the lower level code handles any - * new dma mappings it needs. our caller always gave - * us dma-safe buffers. - */ - if (t->len) { - /* REVISIT dma API still needs a designated - * DMA_ADDR_INVALID; ~0 might be better. - */ - if (!m->is_dma_mapped) - t->rx_dma = t->tx_dma = 0; - status = bitbang->txrx_bufs(spi, t); - } - if (status > 0) - m->actual_length += status; - if (status != t->len) { - /* always report some kind of error */ - if (status >= 0) - status = -EREMOTEIO; - break; - } - status = 0; - - /* protocol tweaks before next transfer */ - if (t->delay_usecs) - udelay(t->delay_usecs); + /* normally deactivate chipselect ... unless no error and + * cs_change has hinted that the next message will probably + * be for this chip too. + */ + if (!(status == 0 && cs_change)) { + ndelay(nsecs); + bitbang->chipselect(spi, BITBANG_CS_INACTIVE); + ndelay(nsecs); + } - if (!cs_change) - continue; - if (t->transfer_list.next == &m->transfers) - break; + local_irq_restore(flags); - /* sometimes a short mid-message deselect of the chip - * may be needed to terminate a mode or command - */ - ndelay(nsecs); - bitbang->chipselect(spi, BITBANG_CS_INACTIVE); - ndelay(nsecs); - } + return status; +} +EXPORT_SYMBOL_GPL(spi_bitbang_transfer_sync); - m->status = status; - m->complete(m->context); +static void bitbang_work(struct work_struct *work) +{ + struct spi_bitbang *bitbang = + container_of(work, struct spi_bitbang, work); + unsigned long flags; - /* restore speed and wordsize if it was overridden */ - if (do_setup == 1) - setup_transfer(spi, NULL); - do_setup = 0; + spin_lock_irqsave(&bitbang->lock, flags); + bitbang->busy = 1; + while (!list_empty(&bitbang->queue)) { + struct spi_message *m; - /* normally deactivate chipselect ... unless no error and - * cs_change has hinted that the next message will probably - * be for this chip too. - */ - if (!(status == 0 && cs_change)) { - ndelay(nsecs); - bitbang->chipselect(spi, BITBANG_CS_INACTIVE); - ndelay(nsecs); - } + m = container_of(bitbang->queue.next, struct spi_message, + queue); + list_del_init(&m->queue); + spin_unlock_irqrestore(&bitbang->lock, flags); + spi_bitbang_transfer_sync(m->spi, m); spin_lock_irqsave(&bitbang->lock, flags); } bitbang->busy = 0; @@ -455,6 +460,10 @@ int spi_bitbang_start(struct spi_bitbang *bitbang) if (!bitbang->master->transfer) bitbang->master->transfer = spi_bitbang_transfer; + + if (!bitbang->master->transfer_sync && bitbang->non_blocking_transfer) + bitbang->master->transfer_sync = spi_bitbang_transfer_sync; + if (!bitbang->txrx_bufs) { bitbang->use_dma = 0; bitbang->txrx_bufs = spi_bitbang_bufs; diff --git a/drivers/spi/spi_s3c24xx_gpio.c b/drivers/spi/spi_s3c24xx_gpio.c index bbf9371..5685b78 100644 --- a/drivers/spi/spi_s3c24xx_gpio.c +++ b/drivers/spi/spi_s3c24xx_gpio.c @@ -92,7 +92,7 @@ static void s3c2410_spigpio_chipselect(struct spi_device *dev, int value) struct s3c2410_spigpio *sg = spidev_to_sg(dev); if (sg->info && sg->info->chip_select) - (sg->info->chip_select)(sg->info, value); + (sg->info->chip_select)(sg->info, dev->chip_select, value); } static int s3c2410_spigpio_probe(struct platform_device *dev) @@ -113,14 +113,17 @@ static int s3c2410_spigpio_probe(struct platform_device *dev) platform_set_drvdata(dev, sp); - /* copy in the plkatform data */ + /* copy in the platform data */ info = sp->info = dev->dev.platform_data; + master->num_chipselect = info->num_chipselect; + /* setup spi bitbang adaptor */ sp->bitbang.master = spi_master_get(master); sp->bitbang.master->bus_num = info->bus_num; sp->bitbang.master->num_chipselect = info->num_chipselect; sp->bitbang.chipselect = s3c2410_spigpio_chipselect; + sp->bitbang.non_blocking_transfer = info->non_blocking_transfer; sp->bitbang.txrx_word[SPI_MODE_0] = s3c2410_spigpio_txrx_mode0; sp->bitbang.txrx_word[SPI_MODE_1] = s3c2410_spigpio_txrx_mode1; diff --git a/include/linux/lis302dl.h b/include/linux/lis302dl.h new file mode 100644 index 0000000..0c1fc30 --- /dev/null +++ b/include/linux/lis302dl.h @@ -0,0 +1,152 @@ +#ifndef _LINUX_LIS302DL_H +#define _LINUX_LIS302DL_H + +#include +#include +#include +#include + +struct lis302dl_info; + +struct lis302dl_platform_data { + char *name; + unsigned long pin_chip_select; + unsigned long pin_clk; + unsigned long pin_mosi; + unsigned long pin_miso; + int open_drain; + int interrupt; + void (*lis302dl_suspend_io)(struct lis302dl_info *, int resuming); +}; + +struct lis302dl_info { + struct lis302dl_platform_data *pdata; + struct device *dev; + struct input_dev *input_dev; + unsigned int flags; + unsigned int threshold; + unsigned int duration; + u32 overruns; + struct { + unsigned int threshold; /* mg */ + unsigned int duration; /* ms */ + } wakeup; + + struct spi_device *spi; + u_int8_t regs[0x40]; +}; + +enum lis302dl_reg { + LIS302DL_REG_WHO_AM_I = 0x0f, + LIS302DL_REG_CTRL1 = 0x20, + LIS302DL_REG_CTRL2 = 0x21, + LIS302DL_REG_CTRL3 = 0x22, + LIS302DL_REG_HP_FILTER_RESET = 0x23, + LIS302DL_REG_STATUS = 0x27, + LIS302DL_REG_OUT_X = 0x29, + LIS302DL_REG_OUT_Y = 0x2b, + LIS302DL_REG_OUT_Z = 0x2d, + LIS302DL_REG_FF_WU_CFG_1 = 0x30, + LIS302DL_REG_FF_WU_SRC_1 = 0x31, + LIS302DL_REG_FF_WU_THS_1 = 0x32, + LIS302DL_REG_FF_WU_DURATION_1 = 0x33, + LIS302DL_REG_FF_WU_CFG_2 = 0x34, + LIS302DL_REG_FF_WU_SRC_2 = 0x35, + LIS302DL_REG_FF_WU_THS_2 = 0x36, + LIS302DL_REG_FF_WU_DURATION_2 = 0x37, + LIS302DL_REG_CLICK_CFG = 0x38, + LIS302DL_REG_CLICK_SRC = 0x39, + LIS302DL_REG_CLICK_THSY_X = 0x3b, + LIS302DL_REG_CLICK_THSZ = 0x3c, + LIS302DL_REG_CLICK_TIME_LIMIT = 0x3d, + LIS302DL_REG_CLICK_LATENCY = 0x3e, + LIS302DL_REG_CLICK_WINDOW = 0x3f, +}; + +enum lis302dl_reg_ctrl1 { + LIS302DL_CTRL1_Xen = 0x01, + LIS302DL_CTRL1_Yen = 0x02, + LIS302DL_CTRL1_Zen = 0x04, + LIS302DL_CTRL1_STM = 0x08, + LIS302DL_CTRL1_STP = 0x10, + LIS302DL_CTRL1_FS = 0x20, + LIS302DL_CTRL1_PD = 0x40, + LIS302DL_CTRL1_DR = 0x80, +}; + +enum lis302dl_reg_ctrl2 { + LIS302DL_CTRL2_HPC1 = 0x01, + LIS302DL_CTRL2_HPC2 = 0x02, + LIS302DL_CTRL2_HPFF1 = 0x04, + LIS302DL_CTRL2_HPFF2 = 0x08, + LIS302DL_CTRL2_FDS = 0x10, + LIS302DL_CTRL2_BOOT = 0x40, + LIS302DL_CTRL2_SIM = 0x80, +}; +enum lis302dl_reg_ctrl3 { + LIS302DL_CTRL3_PP_OD = 0x40, + LIS302DL_CTRL3_IHL = 0x80, +}; + +enum lis302dl_reg_status { + LIS302DL_STATUS_XDA = 0x01, + LIS302DL_STATUS_YDA = 0x02, + LIS302DL_STATUS_ZDA = 0x04, + LIS302DL_STATUS_XYZDA = 0x08, + LIS302DL_STATUS_XOR = 0x10, + LIS302DL_STATUS_YOR = 0x20, + LIS302DL_STATUS_ZOR = 0x40, + LIS302DL_STATUS_XYZOR = 0x80, +}; + +/* Wakeup/freefall interrupt defs */ +enum lis302dl_reg_ffwucfg { + LIS302DL_FFWUCFG_XLIE = 0x01, + LIS302DL_FFWUCFG_XHIE = 0x02, + LIS302DL_FFWUCFG_YLIE = 0x04, + LIS302DL_FFWUCFG_YHIE = 0x08, + LIS302DL_FFWUCFG_ZLIE = 0x10, + LIS302DL_FFWUCFG_ZHIE = 0x20, + LIS302DL_FFWUCFG_LIR = 0x40, + LIS302DL_FFWUCFG_AOI = 0x80, +}; + +enum lis302dl_reg_ffwuths { + LIS302DL_FFWUTHS_DCRM = 0x80, +}; + +enum lis302dl_reg_ffwusrc { + LIS302DL_FFWUSRC_XL = 0x01, + LIS302DL_FFWUSRC_XH = 0x02, + LIS302DL_FFWUSRC_YL = 0x04, + LIS302DL_FFWUSRC_YH = 0x08, + LIS302DL_FFWUSRC_ZL = 0x10, + LIS302DL_FFWUSRC_ZH = 0x20, + LIS302DL_FFWUSRC_IA = 0x40, +}; + +enum lis302dl_reg_cloik_src { + LIS302DL_CLICKSRC_SINGLE_X = 0x01, + LIS302DL_CLICKSRC_DOUBLE_X = 0x02, + LIS302DL_CLICKSRC_SINGLE_Y = 0x04, + LIS302DL_CLICKSRC_DOUBLE_Y = 0x08, + LIS302DL_CLICKSRC_SINGLE_Z = 0x10, + LIS302DL_CLICKSRC_DOUBLE_Z = 0x20, + LIS302DL_CLICKSRC_IA = 0x40, +}; + +#define LIS302DL_WHO_AM_I_MAGIC 0x3b + +#define LIS302DL_F_WUP_FF_1 0x0001 /* wake up from free fall */ +#define LIS302DL_F_WUP_FF_2 0x0002 +#define LIS302DL_F_WUP_FF 0x0003 +#define LIS302DL_F_WUP_CLICK 0x0004 +#define LIS302DL_F_POWER 0x0010 +#define LIS302DL_F_FS 0x0020 /* ADC full scale */ +#define LIS302DL_F_INPUT_OPEN 0x0040 /* Set if input device is opened */ +#define LIS302DL_F_IRQ_WAKE 0x0080 /* IRQ is setup in wake mode */ +#define LIS302DL_F_DR 0x0100 /* Data rate, 400Hz/100Hz */ + + +#endif /* _LINUX_LIS302DL_H */ + diff --git a/include/linux/spi/spi.h b/include/linux/spi/spi.h index 97b60b3..9c5484b 100644 --- a/include/linux/spi/spi.h +++ b/include/linux/spi/spi.h @@ -291,6 +291,13 @@ struct spi_master { int (*transfer)(struct spi_device *spi, struct spi_message *mesg); + /* + * Synchronous non blocking transfer function. Should guarantee + * data availability when it returns + */ + int (*transfer_sync)(struct spi_device *spi, + struct spi_message *mesg); + /* called on release() to free memory provided by spi_master */ void (*cleanup)(struct spi_device *spi); }; @@ -542,6 +549,29 @@ static inline void spi_message_free(struct spi_message *m) extern int spi_setup(struct spi_device *spi); extern int spi_async(struct spi_device *spi, struct spi_message *message); +/** + * spi_non_blocking_transfer - Synchronous, non blocking transfer + * @spi: device with which data will be exchanged + * @message: describes the data transfers with optional completion handlers + * Context: any (irqs may be blocked, etc) + * + * Data is guaranteed to be written or read when this function returns. + * + * Note : This may not be supported by all spi masters. + */ + +static inline int +spi_non_blocking_transfer(struct spi_device *spi, struct spi_message *message) +{ + if (unlikely(!spi->master->transfer_sync)) { + dev_err(&spi->master->dev, + "non-blocking transfers not supported\n"); + return -EIO; + } + + return spi->master->transfer_sync(spi, message); +} + /*---------------------------------------------------------------------------*/ /* All these synchronous SPI transfer routines are utilities layered diff --git a/include/linux/spi/spi_bitbang.h b/include/linux/spi/spi_bitbang.h index eed4254..f92de01 100644 --- a/include/linux/spi/spi_bitbang.h +++ b/include/linux/spi/spi_bitbang.h @@ -31,6 +31,9 @@ struct spi_bitbang { u8 use_dma; u8 flags; /* extra spi->mode support */ + /* Support for synchronous non blocking transfers */ + int non_blocking_transfer; + struct spi_master *master; /* setup_transfer() changes clock and/or wordsize to match settings @@ -62,6 +65,8 @@ extern void spi_bitbang_cleanup(struct spi_device *spi); extern int spi_bitbang_transfer(struct spi_device *spi, struct spi_message *m); extern int spi_bitbang_setup_transfer(struct spi_device *spi, struct spi_transfer *t); +extern int spi_bitbang_transfer_sync(struct spi_device *spi, + struct spi_message *m); /* start or stop queue processing */ extern int spi_bitbang_start(struct spi_bitbang *spi); -- 1.7.1