diff options
Diffstat (limited to 'drivers/input/misc')
86 files changed, 60295 insertions, 0 deletions
diff --git a/drivers/input/misc/88pm80x_onkey.c b/drivers/input/misc/88pm80x_onkey.c new file mode 100644 index 00000000..ee43e5b7 --- /dev/null +++ b/drivers/input/misc/88pm80x_onkey.c @@ -0,0 +1,168 @@ +/* + * Marvell 88PM80x ONKEY driver + * + * Copyright (C) 2012 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * Qiao Zhou <zhouqiao@marvell.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/mfd/88pm80x.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +#define PM800_LONG_ONKEY_EN (1 << 0) +#define PM800_LONG_KEY_DELAY (8) /* 1 .. 16 seconds */ +#define PM800_LONKEY_PRESS_TIME ((PM800_LONG_KEY_DELAY-1) << 4) +#define PM800_LONKEY_PRESS_TIME_MASK (0xF0) +#define PM800_SW_PDOWN (1 << 5) + +struct pm80x_onkey_info { + struct input_dev *idev; + struct pm80x_chip *pm80x; + struct regmap *map; + int irq; +}; + +/* 88PM80x gives us an interrupt when ONKEY is held */ +static irqreturn_t pm80x_onkey_handler(int irq, void *data) +{ + struct pm80x_onkey_info *info = data; + int ret = 0; + unsigned int val; + + ret = regmap_read(info->map, PM800_STATUS_1, &val); + if (ret < 0) { + dev_err(info->idev->dev.parent, "failed to read status: %d\n", ret); + return IRQ_NONE; + } + val &= PM800_ONKEY_STS1; + + input_report_key(info->idev, KEY_POWER, val); + input_sync(info->idev); + + return IRQ_HANDLED; +} + +static SIMPLE_DEV_PM_OPS(pm80x_onkey_pm_ops, pm80x_dev_suspend, + pm80x_dev_resume); + +static int pm80x_onkey_probe(struct platform_device *pdev) +{ + + struct pm80x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm80x_onkey_info *info; + int err; + + info = kzalloc(sizeof(struct pm80x_onkey_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pm80x = chip; + + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + err = -EINVAL; + goto out; + } + + info->map = info->pm80x->regmap; + if (!info->map) { + dev_err(&pdev->dev, "no regmap!\n"); + err = -EINVAL; + goto out; + } + + info->idev = input_allocate_device(); + if (!info->idev) { + dev_err(&pdev->dev, "Failed to allocate input dev\n"); + err = -ENOMEM; + goto out; + } + + info->idev->name = "88pm80x_on"; + info->idev->phys = "88pm80x_on/input0"; + info->idev->id.bustype = BUS_I2C; + info->idev->dev.parent = &pdev->dev; + info->idev->evbit[0] = BIT_MASK(EV_KEY); + __set_bit(KEY_POWER, info->idev->keybit); + + err = pm80x_request_irq(info->pm80x, info->irq, pm80x_onkey_handler, + IRQF_ONESHOT, "onkey", info); + if (err < 0) { + dev_err(&pdev->dev, "Failed to request IRQ: #%d: %d\n", + info->irq, err); + goto out_reg; + } + + err = input_register_device(info->idev); + if (err) { + dev_err(&pdev->dev, "Can't register input device: %d\n", err); + goto out_irq; + } + + platform_set_drvdata(pdev, info); + + /* Enable long onkey detection */ + regmap_update_bits(info->map, PM800_RTC_MISC4, PM800_LONG_ONKEY_EN, + PM800_LONG_ONKEY_EN); + /* Set 8-second interval */ + regmap_update_bits(info->map, PM800_RTC_MISC3, + PM800_LONKEY_PRESS_TIME_MASK, + PM800_LONKEY_PRESS_TIME); + + device_init_wakeup(&pdev->dev, 1); + return 0; + +out_irq: + pm80x_free_irq(info->pm80x, info->irq, info); +out_reg: + input_free_device(info->idev); +out: + kfree(info); + return err; +} + +static int pm80x_onkey_remove(struct platform_device *pdev) +{ + struct pm80x_onkey_info *info = platform_get_drvdata(pdev); + + device_init_wakeup(&pdev->dev, 0); + pm80x_free_irq(info->pm80x, info->irq, info); + input_unregister_device(info->idev); + kfree(info); + return 0; +} + +static struct platform_driver pm80x_onkey_driver = { + .driver = { + .name = "88pm80x-onkey", + .owner = THIS_MODULE, + .pm = &pm80x_onkey_pm_ops, + }, + .probe = pm80x_onkey_probe, + .remove = pm80x_onkey_remove, +}; + +module_platform_driver(pm80x_onkey_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Marvell 88PM80x ONKEY driver"); +MODULE_AUTHOR("Qiao Zhou <zhouqiao@marvell.com>"); +MODULE_ALIAS("platform:88pm80x-onkey"); diff --git a/drivers/input/misc/88pm860x_onkey.c b/drivers/input/misc/88pm860x_onkey.c new file mode 100644 index 00000000..abd8453e --- /dev/null +++ b/drivers/input/misc/88pm860x_onkey.c @@ -0,0 +1,170 @@ +/* + * 88pm860x_onkey.c - Marvell 88PM860x ONKEY driver + * + * Copyright (C) 2009-2010 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/mfd/88pm860x.h> +#include <linux/slab.h> + +#define PM8607_WAKEUP 0x0b + +#define LONG_ONKEY_EN (1 << 1) +#define ONKEY_STATUS (1 << 0) + +struct pm860x_onkey_info { + struct input_dev *idev; + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct device *dev; + int irq; +}; + +/* 88PM860x gives us an interrupt when ONKEY is held */ +static irqreturn_t pm860x_onkey_handler(int irq, void *data) +{ + struct pm860x_onkey_info *info = data; + int ret; + + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + ret &= ONKEY_STATUS; + input_report_key(info->idev, KEY_POWER, ret); + input_sync(info->idev); + + /* Enable 8-second long onkey detection */ + pm860x_set_bits(info->i2c, PM8607_WAKEUP, 3, LONG_ONKEY_EN); + return IRQ_HANDLED; +} + +static int pm860x_onkey_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct pm860x_onkey_info *info; + int irq, ret; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct pm860x_onkey_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + info->chip = chip; + info->i2c = (chip->id == CHIP_PM8607) ? chip->client : chip->companion; + info->dev = &pdev->dev; + info->irq = irq; + + info->idev = input_allocate_device(); + if (!info->idev) { + dev_err(chip->dev, "Failed to allocate input dev\n"); + ret = -ENOMEM; + goto out; + } + + info->idev->name = "88pm860x_on"; + info->idev->phys = "88pm860x_on/input0"; + info->idev->id.bustype = BUS_I2C; + info->idev->dev.parent = &pdev->dev; + info->idev->evbit[0] = BIT_MASK(EV_KEY); + info->idev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + + ret = input_register_device(info->idev); + if (ret) { + dev_err(chip->dev, "Can't register input device: %d\n", ret); + goto out_reg; + } + + ret = request_threaded_irq(info->irq, NULL, pm860x_onkey_handler, + IRQF_ONESHOT, "onkey", info); + if (ret < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + info->irq, ret); + goto out_irq; + } + + platform_set_drvdata(pdev, info); + device_init_wakeup(&pdev->dev, 1); + + return 0; + +out_irq: + input_unregister_device(info->idev); + kfree(info); + return ret; + +out_reg: + input_free_device(info->idev); +out: + kfree(info); + return ret; +} + +static int pm860x_onkey_remove(struct platform_device *pdev) +{ + struct pm860x_onkey_info *info = platform_get_drvdata(pdev); + + free_irq(info->irq, info); + input_unregister_device(info->idev); + kfree(info); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pm860x_onkey_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + + if (device_may_wakeup(dev)) + chip->wakeup_flag |= 1 << PM8607_IRQ_ONKEY; + return 0; +} +static int pm860x_onkey_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + + if (device_may_wakeup(dev)) + chip->wakeup_flag &= ~(1 << PM8607_IRQ_ONKEY); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pm860x_onkey_pm_ops, pm860x_onkey_suspend, pm860x_onkey_resume); + +static struct platform_driver pm860x_onkey_driver = { + .driver = { + .name = "88pm860x-onkey", + .owner = THIS_MODULE, + .pm = &pm860x_onkey_pm_ops, + }, + .probe = pm860x_onkey_probe, + .remove = pm860x_onkey_remove, +}; +module_platform_driver(pm860x_onkey_driver); + +MODULE_DESCRIPTION("Marvell 88PM860x ONKEY driver"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig new file mode 100644 index 00000000..08e1faf2 --- /dev/null +++ b/drivers/input/misc/Kconfig @@ -0,0 +1,776 @@ +# +# Input misc drivers configuration +# +menuconfig INPUT_MISC + bool "Miscellaneous devices" + help + Say Y here, and a list of miscellaneous input drivers will be displayed. + Everything that didn't fit into the other categories is here. This option + doesn't affect the kernel. + + If unsure, say Y. + +if INPUT_MISC + +config INPUT_88PM860X_ONKEY + tristate "88PM860x ONKEY support" + depends on MFD_88PM860X + help + Support the ONKEY of Marvell 88PM860x PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called 88pm860x_onkey. + +config INPUT_88PM80X_ONKEY + tristate "88PM80x ONKEY support" + depends on MFD_88PM800 + help + Support the ONKEY of Marvell 88PM80x PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called 88pm80x_onkey. + +config INPUT_AB8500_PONKEY + tristate "AB8500 Pon (PowerOn) Key" + depends on AB8500_CORE + help + Say Y here to use the PowerOn Key for ST-Ericsson's AB8500 + Mix-Sig PMIC. + + To compile this driver as a module, choose M here: the module + will be called ab8500-ponkey. + +config INPUT_AD714X + tristate "Analog Devices AD714x Capacitance Touch Sensor" + help + Say Y here if you want to support an AD7142/3/7/8/7A touch sensor. + + You should select a bus connection too. + + To compile this driver as a module, choose M here: the + module will be called ad714x. + +config INPUT_AD714X_I2C + tristate "support I2C bus connection" + depends on INPUT_AD714X && I2C + default y + help + Say Y here if you have AD7142/AD7147 hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called ad714x-i2c. + +config INPUT_AD714X_SPI + tristate "support SPI bus connection" + depends on INPUT_AD714X && SPI + default y + help + Say Y here if you have AD7142/AD7147 hooked to a SPI bus. + + To compile this driver as a module, choose M here: the + module will be called ad714x-spi. + +config INPUT_ARIZONA_HAPTICS + tristate "Arizona haptics support" + depends on MFD_ARIZONA && SND_SOC + select INPUT_FF_MEMLESS + help + Say Y to enable support for the haptics module in Arizona CODECs. + + To compile this driver as a module, choose M here: the + module will be called arizona-haptics. + +config INPUT_BMA150 + tristate "BMA150/SMB380 acceleration sensor support" + depends on I2C + select INPUT_POLLDEV + help + Say Y here if you have Bosch Sensortec's BMA150 or SMB380 + acceleration sensor hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called bma150. + +config INPUT_PCSPKR + tristate "PC Speaker support" + depends on PCSPKR_PLATFORM + help + Say Y here if you want the standard PC Speaker to be used for + bells and whistles. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called pcspkr. + +config INPUT_PM8XXX_VIBRATOR + tristate "Qualcomm PM8XXX vibrator support" + depends on MFD_PM8XXX + select INPUT_FF_MEMLESS + help + This option enables device driver support for the vibrator + on Qualcomm PM8xxx chip. This driver supports ff-memless interface + from input framework. + + To compile this driver as module, choose M here: the + module will be called pm8xxx-vibrator. + +config INPUT_PMIC8XXX_PWRKEY + tristate "PMIC8XXX power key support" + depends on MFD_PM8XXX + help + Say Y here if you want support for the PMIC8XXX power key. + + If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called pmic8xxx-pwrkey. + +config INPUT_SPARCSPKR + tristate "SPARC Speaker support" + depends on PCI && SPARC64 + help + Say Y here if you want the standard Speaker on Sparc PCI systems + to be used for bells and whistles. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called sparcspkr. + +config INPUT_M68K_BEEP + tristate "M68k Beeper support" + depends on M68K + +config INPUT_MAX8925_ONKEY + tristate "MAX8925 ONKEY support" + depends on MFD_MAX8925 + help + Support the ONKEY of MAX8925 PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called max8925_onkey. + +config INPUT_MAX8997_HAPTIC + tristate "MAXIM MAX8997 haptic controller support" + depends on HAVE_PWM && MFD_MAX8997 + select INPUT_FF_MEMLESS + help + This option enables device driver support for the haptic controller + on MAXIM MAX8997 chip. This driver supports ff-memless interface + from input framework. + + To compile this driver as module, choose M here: the + module will be called max8997-haptic. + +config INPUT_MC13783_PWRBUTTON + tristate "MC13783 ON buttons" + depends on MFD_MC13783 + help + Support the ON buttons of MC13783 PMIC as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called mc13783-pwrbutton. + +config INPUT_MMA8450 + tristate "MMA8450 - Freescale's 3-Axis, 8/12-bit Digital Accelerometer" + depends on I2C + select INPUT_POLLDEV + help + Say Y here if you want to support Freescale's MMA8450 Accelerometer + through I2C interface. + + To compile this driver as a module, choose M here: the + module will be called mma8450. + +config INPUT_MPU3050 + tristate "MPU3050 Triaxial gyroscope sensor" + depends on I2C + help + Say Y here if you want to support InvenSense MPU3050 + connected via an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called mpu3050. + +config INPUT_APANEL + tristate "Fujitsu Lifebook Application Panel buttons" + depends on X86 && I2C && LEDS_CLASS + select INPUT_POLLDEV + select CHECK_SIGNATURE + help + Say Y here for support of the Application Panel buttons, used on + Fujitsu Lifebook. These are attached to the mainboard through + an SMBus interface managed by the I2C Intel ICH (i801) driver, + which you should also build for this kernel. + + To compile this driver as a module, choose M here: the module will + be called apanel. + +config INPUT_GP2A + tristate "Sharp GP2AP002A00F I2C Proximity/Opto sensor driver" + depends on I2C + depends on GPIOLIB + help + Say Y here if you have a Sharp GP2AP002A00F proximity/als combo-chip + hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called gp2ap002a00f. + +config INPUT_GPIO_TILT_POLLED + tristate "Polled GPIO tilt switch" + depends on GPIOLIB + select INPUT_POLLDEV + help + This driver implements support for tilt switches connected + to GPIO pins that are not capable of generating interrupts. + + The list of gpios to use and the mapping of their states + to specific angles is done via platform data. + + To compile this driver as a module, choose M here: the + module will be called gpio_tilt_polled. + +config INPUT_IXP4XX_BEEPER + tristate "IXP4XX Beeper support" + depends on ARCH_IXP4XX + help + If you say yes here, you can connect a beeper to the + ixp4xx gpio pins. This is used by the LinkSys NSLU2. + + If unsure, say Y. + + To compile this driver as a module, choose M here: the + module will be called ixp4xx-beeper. + +config INPUT_COBALT_BTNS + tristate "Cobalt button interface" + depends on MIPS_COBALT + select INPUT_POLLDEV + help + Say Y here if you want to support MIPS Cobalt button interface. + + To compile this driver as a module, choose M here: the + module will be called cobalt_btns. + +config INPUT_WISTRON_BTNS + tristate "x86 Wistron laptop button interface" + depends on X86 && !X86_64 + select INPUT_POLLDEV + select INPUT_SPARSEKMAP + select NEW_LEDS + select LEDS_CLASS + select CHECK_SIGNATURE + help + Say Y here for support of Wistron laptop button interfaces, used on + laptops of various brands, including Acer and Fujitsu-Siemens. If + available, mail and wifi LEDs will be controllable via /sys/class/leds. + + To compile this driver as a module, choose M here: the module will + be called wistron_btns. + +config INPUT_ATLAS_BTNS + tristate "x86 Atlas button interface" + depends on X86 && ACPI + help + Say Y here for support of Atlas wallmount touchscreen buttons. + The events will show up as scancodes F1 through F9 via evdev. + + To compile this driver as a module, choose M here: the module will + be called atlas_btns. + +config INPUT_ATI_REMOTE2 + tristate "ATI / Philips USB RF remote control" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use an ATI or Philips USB RF remote control. + These are RF remotes with USB receivers. + ATI Remote Wonder II comes with some ATI's All-In-Wonder video cards + and is also available as a separate product. + This driver provides mouse pointer, left and right mouse buttons, + and maps all the other remote buttons to keypress events. + + To compile this driver as a module, choose M here: the module will be + called ati_remote2. + +config INPUT_KEYCHORD + tristate "Key chord input driver support" + help + Say Y here if you want to enable the key chord driver + accessible at /dev/keychord. This driver can be used + for receiving notifications when client specified key + combinations are pressed. + + To compile this driver as a module, choose M here: the + module will be called keychord. + +config INPUT_KEYSPAN_REMOTE + tristate "Keyspan DMR USB remote control" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use a Keyspan DMR USB remote control. + Currently only the UIA-11 type of receiver has been tested. The tag + on the receiver that connects to the USB port should have a P/N that + will tell you what type of DMR you have. The UIA-10 type is not + supported at this time. This driver maps all buttons to keypress + events. + + To compile this driver as a module, choose M here: the module will + be called keyspan_remote. + +config INPUT_KXTJ9 + tristate "Kionix KXTJ9 tri-axis digital accelerometer" + depends on I2C + help + Say Y here to enable support for the Kionix KXTJ9 digital tri-axis + accelerometer. + + To compile this driver as a module, choose M here: the module will + be called kxtj9. + +config INPUT_KXTJ9_POLLED_MODE + bool "Enable polling mode support" + depends on INPUT_KXTJ9 + select INPUT_POLLDEV + help + Say Y here if you need accelerometer to work in polling mode. + +config INPUT_POWERMATE + tristate "Griffin PowerMate and Contour Jog support" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use Griffin PowerMate or Contour Jog devices. + These are aluminum dials which can measure clockwise and anticlockwise + rotation. The dial also acts as a pushbutton. The base contains an LED + which can be instructed to pulse or to switch to a particular intensity. + + You can download userspace tools from + <http://sowerbutts.com/powermate/>. + + To compile this driver as a module, choose M here: the + module will be called powermate. + +config INPUT_YEALINK + tristate "Yealink usb-p1k voip phone" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to enable keyboard and LCD functions of the + Yealink usb-p1k usb phones. The audio part is enabled by the generic + usb sound driver, so you might want to enable that as well. + + For information about how to use these additional functions, see + <file:Documentation/input/yealink.txt>. + + To compile this driver as a module, choose M here: the module will be + called yealink. + +config INPUT_CM109 + tristate "C-Media CM109 USB I/O Controller" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to enable keyboard and buzzer functions of the + C-Media CM109 usb phones. The audio part is enabled by the generic + usb sound driver, so you might want to enable that as well. + + To compile this driver as a module, choose M here: the module will be + called cm109. + +config INPUT_RETU_PWRBUTTON + tristate "Retu Power button Driver" + depends on MFD_RETU + help + Say Y here if you want to enable power key reporting via the + Retu chips found in Nokia Internet Tablets (770, N800, N810). + + To compile this driver as a module, choose M here. The module will + be called retu-pwrbutton. + +config INPUT_TWL4030_PWRBUTTON + tristate "TWL4030 Power button Driver" + depends on TWL4030_CORE + help + Say Y here if you want to enable power key reporting via the + TWL4030 family of chips. + + To compile this driver as a module, choose M here. The module will + be called twl4030_pwrbutton. + +config INPUT_TWL4030_VIBRA + tristate "Support for TWL4030 Vibrator" + depends on TWL4030_CORE + select MFD_TWL4030_AUDIO + select INPUT_FF_MEMLESS + help + This option enables support for TWL4030 Vibrator Driver. + + To compile this driver as a module, choose M here. The module will + be called twl4030_vibra. + +config INPUT_TWL6040_VIBRA + tristate "Support for TWL6040 Vibrator" + depends on TWL6040_CORE + select INPUT_FF_MEMLESS + help + This option enables support for TWL6040 Vibrator Driver. + + To compile this driver as a module, choose M here. The module will + be called twl6040_vibra. + +config INPUT_UINPUT + tristate "User level driver support" + help + Say Y here if you want to support user level drivers for input + subsystem accessible under char device 10:223 - /dev/input/uinput. + + To compile this driver as a module, choose M here: the + module will be called uinput. + +config INPUT_SGI_BTNS + tristate "SGI Indy/O2 volume button interface" + depends on SGI_IP22 || SGI_IP32 + select INPUT_POLLDEV + help + Say Y here if you want to support SGI Indy/O2 volume button interface. + + To compile this driver as a module, choose M here: the + module will be called sgi_btns. + +config INPUT_GPIO + tristate "GPIO driver support" + help + Say Y here if you want to support gpio based keys, wheels etc... + +config HP_SDC_RTC + tristate "HP SDC Real Time Clock" + depends on (GSC || HP300) && SERIO + select HP_SDC + help + Say Y here if you want to support the built-in real time clock + of the HP SDC controller. + +config INPUT_PCF50633_PMU + tristate "PCF50633 PMU events" + depends on MFD_PCF50633 + help + Say Y to include support for delivering PMU events via input + layer on NXP PCF50633. + +config INPUT_PCF8574 + tristate "PCF8574 Keypad input device" + depends on I2C + help + Say Y here if you want to support a keypad connected via I2C + with a PCF8574. + + To compile this driver as a module, choose M here: the + module will be called pcf8574_keypad. + +config INPUT_PWM_BEEPER + tristate "PWM beeper support" + depends on HAVE_PWM || PWM + help + Say Y here to get support for PWM based beeper devices. + + If unsure, say N. + + To compile this driver as a module, choose M here: the module will be + called pwm-beeper. + +config INPUT_GPIO_ROTARY_ENCODER + tristate "Rotary encoders connected to GPIO pins" + depends on GPIOLIB + help + Say Y here to add support for rotary encoders connected to GPIO lines. + Check file:Documentation/input/rotary-encoder.txt for more + information. + + To compile this driver as a module, choose M here: the + module will be called rotary_encoder. + +config INPUT_RB532_BUTTON + tristate "Mikrotik Routerboard 532 button interface" + depends on MIKROTIK_RB532 + depends on GPIOLIB + select INPUT_POLLDEV + help + Say Y here if you want support for the S1 button built into + Mikrotik's Routerboard 532. + + To compile this driver as a module, choose M here: the + module will be called rb532_button. + +config INPUT_DA9052_ONKEY + tristate "Dialog DA9052/DA9053 Onkey" + depends on PMIC_DA9052 + help + Support the ONKEY of Dialog DA9052 PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the + module will be called da9052_onkey. + +config INPUT_DA9055_ONKEY + tristate "Dialog Semiconductor DA9055 ONKEY" + depends on MFD_DA9055 + help + Support the ONKEY of DA9055 PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called da9055_onkey. + +config INPUT_DM355EVM + tristate "TI DaVinci DM355 EVM Keypad and IR Remote" + depends on MFD_DM355EVM_MSP + select INPUT_SPARSEKMAP + help + Supports the pushbuttons and IR remote used with + the DM355 EVM board. + + To compile this driver as a module, choose M here: the + module will be called dm355evm_keys. + +config INPUT_BFIN_ROTARY + tristate "Blackfin Rotary support" + depends on BF54x || BF52x + help + Say Y here if you want to use the Blackfin Rotary. + + To compile this driver as a module, choose M here: the + module will be called bfin-rotary. + +config INPUT_WM831X_ON + tristate "WM831X ON pin" + depends on MFD_WM831X + help + Support the ON pin of WM831X PMICs as an input device + reporting power button status. + + To compile this driver as a module, choose M here: the module + will be called wm831x_on. + +config INPUT_PCAP + tristate "Motorola EZX PCAP misc input events" + depends on EZX_PCAP + help + Say Y here if you want to use Power key and Headphone button + on Motorola EZX phones. + + To compile this driver as a module, choose M here: the + module will be called pcap_keys. + +config INPUT_ADXL34X + tristate "Analog Devices ADXL34x Three-Axis Digital Accelerometer" + default n + help + Say Y here if you have a Accelerometer interface using the + ADXL345/6 controller, and your board-specific initialization + code includes that in its table of devices. + + This driver can use either I2C or SPI communication to the + ADXL345/6 controller. Select the appropriate method for + your system. + + If unsure, say N (but it's safe to say "Y"). + + To compile this driver as a module, choose M here: the + module will be called adxl34x. + +config INPUT_ADXL34X_I2C + tristate "support I2C bus connection" + depends on INPUT_ADXL34X && I2C + default y + help + Say Y here if you have ADXL345/6 hooked to an I2C bus. + + To compile this driver as a module, choose M here: the + module will be called adxl34x-i2c. + +config INPUT_ADXL34X_SPI + tristate "support SPI bus connection" + depends on INPUT_ADXL34X && SPI + default y + help + Say Y here if you have ADXL345/6 hooked to a SPI bus. + + To compile this driver as a module, choose M here: the + module will be called adxl34x-spi. + +config INPUT_IMS_PCU + tristate "IMS Passenger Control Unit driver" + depends on USB + depends on LEDS_CLASS + help + Say Y here if you have system with IMS Rave Passenger Control Unit. + + To compile this driver as a module, choose M here: the module will be + called ims_pcu. + +config INPUT_CMA3000 + tristate "VTI CMA3000 Tri-axis accelerometer" + help + Say Y here if you want to use VTI CMA3000_D0x Accelerometer + driver + + This driver currently only supports I2C interface to the + controller. Also select the I2C method. + + If unsure, say N + + To compile this driver as a module, choose M here: the + module will be called cma3000_d0x. + +config INPUT_CMA3000_I2C + tristate "Support I2C bus connection" + depends on INPUT_CMA3000 && I2C + help + Say Y here if you want to use VTI CMA3000_D0x Accelerometer + through I2C interface. + + To compile this driver as a module, choose M here: the + module will be called cma3000_d0x_i2c. + +config INPUT_XEN_KBDDEV_FRONTEND + tristate "Xen virtual keyboard and mouse support" + depends on XEN + default y + select XEN_XENBUS_FRONTEND + help + This driver implements the front-end of the Xen virtual + keyboard and mouse device driver. It communicates with a back-end + in another domain. + + To compile this driver as a module, choose M here: the + module will be called xen-kbdfront. + +config INPUT_HEADSET_SPRD_SC2723 + tristate "SPRD SC2723 builtin headset & button detect driver" + default n + help + Say Y here if you are using SPRD platform. + + To compile this driver as a module, choose M here: the + module will be called headset. + +config INPUT_SPRD_HEADSET_SHARK + tristate "SPRD platform builtin headset & button detect driver" + help + Say Y here if you are using SPRD platform. + + To compile this driver as a module, choose M here: the + module will be called headset. + +config HEADSET_KEY_VOLUME_SUPPORTED + bool "HEADSET_KEY_VOLUME_SUPPORTED" + depends on INPUT_SPRD_HEADSET_SHARK + help + Say Y to enable HEADSET_KEY_VOLUME_SUPPORT. + +config HEADSET_EXTERNAL_HEADMICBIAS_POWER_SUPPORTED + bool "HEADSET_EXTERNAL_HEADMICBIAS_POWER_SUPPORTED" + depends on INPUT_SPRD_HEADSET_SHARK + help + Say Y to enable HEADSET_EXTERNAL_HEADMICBIAS_POWER_SUPPORT. + +config HEADSET_STS_POLLING_DISABLED + bool "HEADSET_STS_POLLING_DISABLED" + depends on INPUT_SPRD_HEADSET_SHARK + help + Say Y to disable HEADSET_STS_POLLING. + +config HEADSET_AUXADC_CAL_SUPPORTED + bool "HEADSET_AUXADC_CAL_SUPPORTED" + depends on INPUT_SPRD_HEADSET_SHARK + help + Say Y to enable HEADSET_AUXADC_CAL_SUPPORTED. + +#add by lgd +config CAMERA_POLE_SUPPORT + bool "CAMERA_POLE_SUPPORT" + depends on INPUT_SPRD_HEADSET_SHARK + help + Say Y to enable camera pole support. + +config INPUT_LIS3DH_I2C + tristate "ST lid3dh 3-Axid accelerometer device with I2C bus" + depends on I2C + default n + help + Say Y here if you have a lis3dh device on the board and use I2C + communication, else say N. + + To compile this driver as a module, choose M here. + +config INPUT_LIS3DH_I2C_INTERRUPT + tristate "ST lid3dh 3-Axid accelerometer device with I2C bus for interrupt mode" + depends on I2C + default n + help + Say Y here if you have a lis3dh device on the board and use I2C + communication, else say N. + + To compile this driver as a module, choose M here. + +config INPUT_LIS3DH_ZT_I2C + tristate "ST lid3dh_zt 3-Axid accelerometer device with I2C bus" + depends on I2C + default n + help + Say Y here if you have a lis3dh_zt device on the board and use I2C + communication, else say N. + + To compile this driver as a module, choose M here. + +config INPUT_LTR558_I2C + tristate "LTR558 proximity and light sensor device with I2C bus" + depends on I2C + default n + help + Say Y here if you have a LTR558 device on the board and use I2C + communication, else say N. + +config INPUT_STK3X1X_I2C + tristate "STK3X1X proximity and light sensor device with I2C bus" + depends on I2C + default n + help + Say Y here if you have a LTR558 device on the board and use I2C + communication, else say N. + + To compile this driver as a module, choose M here. +config INPUT_SENSORS_BMA2X2 + tristate "BMA2x2 acceleration sensor support" + depends on I2C + help + To compile this driver as a module, choose M here. + +config INPUT_EPL2182_I2C + tristate "epl2182 proximity and light sensor device with I2C bus" + depends on I2C + default n + help + Say Y here if you have a epl2182 device on the board and use I2C + communication, else say N. + + To compile this driver as a module, choose M here. + +config INPUT_EPL259X_I2C + tristate "epl259x proximity and light sensor device with I2C bus" + depends on I2C + default n + help + Say Y here if you have a epl259x device on the board and use I2C + communication, else say N. + + To compile this driver as a module, choose M here. +endif diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile new file mode 100644 index 00000000..201efeac --- /dev/null +++ b/drivers/input/misc/Makefile @@ -0,0 +1,81 @@ +# +# Makefile for the input misc drivers. +# + +# Each configuration option enables a list of files. + +obj-$(CONFIG_INPUT_88PM860X_ONKEY) += 88pm860x_onkey.o +obj-$(CONFIG_INPUT_88PM80X_ONKEY) += 88pm80x_onkey.o +obj-$(CONFIG_INPUT_AB8500_PONKEY) += ab8500-ponkey.o +obj-$(CONFIG_INPUT_AD714X) += ad714x.o +obj-$(CONFIG_INPUT_AD714X_I2C) += ad714x-i2c.o +obj-$(CONFIG_INPUT_AD714X_SPI) += ad714x-spi.o +obj-$(CONFIG_INPUT_ADXL34X) += adxl34x.o +obj-$(CONFIG_INPUT_ADXL34X_I2C) += adxl34x-i2c.o +obj-$(CONFIG_INPUT_ADXL34X_SPI) += adxl34x-spi.o +obj-$(CONFIG_INPUT_APANEL) += apanel.o +obj-$(CONFIG_INPUT_ARIZONA_HAPTICS) += arizona-haptics.o +obj-$(CONFIG_INPUT_ATI_REMOTE2) += ati_remote2.o +obj-$(CONFIG_INPUT_ATLAS_BTNS) += atlas_btns.o +obj-$(CONFIG_INPUT_BFIN_ROTARY) += bfin_rotary.o +obj-$(CONFIG_INPUT_BMA150) += bma150.o +obj-$(CONFIG_INPUT_CM109) += cm109.o +obj-$(CONFIG_INPUT_CMA3000) += cma3000_d0x.o +obj-$(CONFIG_INPUT_CMA3000_I2C) += cma3000_d0x_i2c.o +obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o +obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o +obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o +obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o +obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o +obj-$(CONFIG_INPUT_GPIO_TILT_POLLED) += gpio_tilt_polled.o +obj-$(CONFIG_INPUT_GPIO) += gpio_event.o gpio_matrix.o gpio_input.o gpio_output.o gpio_axis.o +obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o +obj-$(CONFIG_INPUT_IMS_PCU) += ims-pcu.o +obj-$(CONFIG_INPUT_IXP4XX_BEEPER) += ixp4xx-beeper.o +obj-$(CONFIG_INPUT_KEYCHORD) += keychord.o +obj-$(CONFIG_INPUT_KEYSPAN_REMOTE) += keyspan_remote.o +obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o +obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o +obj-$(CONFIG_INPUT_MAX8925_ONKEY) += max8925_onkey.o +obj-$(CONFIG_INPUT_MAX8997_HAPTIC) += max8997_haptic.o +obj-$(CONFIG_INPUT_MC13783_PWRBUTTON) += mc13783-pwrbutton.o +obj-$(CONFIG_INPUT_MMA8450) += mma8450.o +obj-$(CONFIG_INPUT_MPU3050) += mpu3050.o +obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o +obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o +obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o +obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o +obj-$(CONFIG_INPUT_PM8XXX_VIBRATOR) += pm8xxx-vibrator.o +obj-$(CONFIG_INPUT_PMIC8XXX_PWRKEY) += pmic8xxx-pwrkey.o +obj-$(CONFIG_INPUT_POWERMATE) += powermate.o +obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o +obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o +obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o +obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o +obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o +obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o +obj-$(CONFIG_INPUT_TWL4030_PWRBUTTON) += twl4030-pwrbutton.o +obj-$(CONFIG_INPUT_TWL4030_VIBRA) += twl4030-vibra.o +obj-$(CONFIG_INPUT_TWL6040_VIBRA) += twl6040-vibra.o +obj-$(CONFIG_INPUT_UINPUT) += uinput.o +obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o +obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o +obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o +obj-$(CONFIG_INPUT_YEALINK) += yealink.o +obj-$(CONFIG_INPUT_LTR558_I2C) += ltr_558als.o +obj-$(CONFIG_INPUT_EPL2182_I2C) += epl2182_pls_v2.o +obj-$(CONFIG_INPUT_EPL259X_I2C) += epl259x.o +obj-$(CONFIG_INPUT_LIS3DH_I2C) += lis3dh.o +obj-$(CONFIG_INPUT_LIS3DH_I2C_INTERRUPT)+= lis3dh_interrupt.o +obj-$(CONFIG_INPUT_LIS3DH_ZT_I2C) += lis3dh_zt.o +obj-$(CONFIG_INPUT_SPRD_HEADSET_SHARK) += headset_sprd.o +obj-$(CONFIG_INPUT_HEADSET_SPRD_SC2723) += headset_sprd_sc2723.o +obj-$(CONFIG_INPUT_STK3X1X_I2C) += stk3x1x.o +obj-$(CONFIG_INPUT_SENSORS_BMA2X2) += bstclass.o bma2x2.o +ifeq ($(CONFIG_SENSORS_BMA2X2_ENABLE_INT1),y) + EXTRA_CFLAGS += -DBMA2X2_ENABLE_INT1 +endif + +ifeq ($(CONFIG_BOSCH_BMA2X2_ENABLE_INT2),y) + EXTRA_CFLAGS += -DBMA2X2_ENABLE_INT2 +endif diff --git a/drivers/input/misc/ab8500-ponkey.c b/drivers/input/misc/ab8500-ponkey.c new file mode 100644 index 00000000..2f090b46 --- /dev/null +++ b/drivers/input/misc/ab8500-ponkey.c @@ -0,0 +1,155 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * License Terms: GNU General Public License v2 + * Author: Sundar Iyer <sundar.iyer@stericsson.com> for ST-Ericsson + * + * AB8500 Power-On Key handler + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/of.h> +#include <linux/slab.h> + +/** + * struct ab8500_ponkey - ab8500 ponkey information + * @input_dev: pointer to input device + * @ab8500: ab8500 parent + * @irq_dbf: irq number for falling transition + * @irq_dbr: irq number for rising transition + */ +struct ab8500_ponkey { + struct input_dev *idev; + struct ab8500 *ab8500; + int irq_dbf; + int irq_dbr; +}; + +/* AB8500 gives us an interrupt when ONKEY is held */ +static irqreturn_t ab8500_ponkey_handler(int irq, void *data) +{ + struct ab8500_ponkey *ponkey = data; + + if (irq == ponkey->irq_dbf) + input_report_key(ponkey->idev, KEY_POWER, true); + else if (irq == ponkey->irq_dbr) + input_report_key(ponkey->idev, KEY_POWER, false); + + input_sync(ponkey->idev); + + return IRQ_HANDLED; +} + +static int ab8500_ponkey_probe(struct platform_device *pdev) +{ + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); + struct ab8500_ponkey *ponkey; + struct input_dev *input; + int irq_dbf, irq_dbr; + int error; + + irq_dbf = platform_get_irq_byname(pdev, "ONKEY_DBF"); + if (irq_dbf < 0) { + dev_err(&pdev->dev, "No IRQ for ONKEY_DBF, error=%d\n", irq_dbf); + return irq_dbf; + } + + irq_dbr = platform_get_irq_byname(pdev, "ONKEY_DBR"); + if (irq_dbr < 0) { + dev_err(&pdev->dev, "No IRQ for ONKEY_DBR, error=%d\n", irq_dbr); + return irq_dbr; + } + + ponkey = kzalloc(sizeof(struct ab8500_ponkey), GFP_KERNEL); + input = input_allocate_device(); + if (!ponkey || !input) { + error = -ENOMEM; + goto err_free_mem; + } + + ponkey->idev = input; + ponkey->ab8500 = ab8500; + ponkey->irq_dbf = irq_dbf; + ponkey->irq_dbr = irq_dbr; + + input->name = "AB8500 POn(PowerOn) Key"; + input->dev.parent = &pdev->dev; + + input_set_capability(input, EV_KEY, KEY_POWER); + + error = request_any_context_irq(ponkey->irq_dbf, ab8500_ponkey_handler, + 0, "ab8500-ponkey-dbf", ponkey); + if (error < 0) { + dev_err(ab8500->dev, "Failed to request dbf IRQ#%d: %d\n", + ponkey->irq_dbf, error); + goto err_free_mem; + } + + error = request_any_context_irq(ponkey->irq_dbr, ab8500_ponkey_handler, + 0, "ab8500-ponkey-dbr", ponkey); + if (error < 0) { + dev_err(ab8500->dev, "Failed to request dbr IRQ#%d: %d\n", + ponkey->irq_dbr, error); + goto err_free_dbf_irq; + } + + error = input_register_device(ponkey->idev); + if (error) { + dev_err(ab8500->dev, "Can't register input device: %d\n", error); + goto err_free_dbr_irq; + } + + platform_set_drvdata(pdev, ponkey); + return 0; + +err_free_dbr_irq: + free_irq(ponkey->irq_dbr, ponkey); +err_free_dbf_irq: + free_irq(ponkey->irq_dbf, ponkey); +err_free_mem: + input_free_device(input); + kfree(ponkey); + + return error; +} + +static int ab8500_ponkey_remove(struct platform_device *pdev) +{ + struct ab8500_ponkey *ponkey = platform_get_drvdata(pdev); + + free_irq(ponkey->irq_dbf, ponkey); + free_irq(ponkey->irq_dbr, ponkey); + input_unregister_device(ponkey->idev); + kfree(ponkey); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id ab8500_ponkey_match[] = { + { .compatible = "stericsson,ab8500-ponkey", }, + {} +}; +#endif + +static struct platform_driver ab8500_ponkey_driver = { + .driver = { + .name = "ab8500-poweron-key", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(ab8500_ponkey_match), + }, + .probe = ab8500_ponkey_probe, + .remove = ab8500_ponkey_remove, +}; +module_platform_driver(ab8500_ponkey_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Sundar Iyer <sundar.iyer@stericsson.com>"); +MODULE_DESCRIPTION("ST-Ericsson AB8500 Power-ON(Pon) Key driver"); diff --git a/drivers/input/misc/ad714x-i2c.c b/drivers/input/misc/ad714x-i2c.c new file mode 100644 index 00000000..e0f52251 --- /dev/null +++ b/drivers/input/misc/ad714x-i2c.c @@ -0,0 +1,123 @@ +/* + * AD714X CapTouch Programmable Controller driver (I2C bus) + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/input.h> /* BUS_I2C */ +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/pm.h> +#include "ad714x.h" + +#ifdef CONFIG_PM_SLEEP +static int ad714x_i2c_suspend(struct device *dev) +{ + return ad714x_disable(i2c_get_clientdata(to_i2c_client(dev))); +} + +static int ad714x_i2c_resume(struct device *dev) +{ + return ad714x_enable(i2c_get_clientdata(to_i2c_client(dev))); +} +#endif + +static SIMPLE_DEV_PM_OPS(ad714x_i2c_pm, ad714x_i2c_suspend, ad714x_i2c_resume); + +static int ad714x_i2c_write(struct ad714x_chip *chip, + unsigned short reg, unsigned short data) +{ + struct i2c_client *client = to_i2c_client(chip->dev); + int error; + + chip->xfer_buf[0] = cpu_to_be16(reg); + chip->xfer_buf[1] = cpu_to_be16(data); + + error = i2c_master_send(client, (u8 *)chip->xfer_buf, + 2 * sizeof(*chip->xfer_buf)); + if (unlikely(error < 0)) { + dev_err(&client->dev, "I2C write error: %d\n", error); + return error; + } + + return 0; +} + +static int ad714x_i2c_read(struct ad714x_chip *chip, + unsigned short reg, unsigned short *data, size_t len) +{ + struct i2c_client *client = to_i2c_client(chip->dev); + int i; + int error; + + chip->xfer_buf[0] = cpu_to_be16(reg); + + error = i2c_master_send(client, (u8 *)chip->xfer_buf, + sizeof(*chip->xfer_buf)); + if (error >= 0) + error = i2c_master_recv(client, (u8 *)chip->xfer_buf, + len * sizeof(*chip->xfer_buf)); + + if (unlikely(error < 0)) { + dev_err(&client->dev, "I2C read error: %d\n", error); + return error; + } + + for (i = 0; i < len; i++) + data[i] = be16_to_cpu(chip->xfer_buf[i]); + + return 0; +} + +static int ad714x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ad714x_chip *chip; + + chip = ad714x_probe(&client->dev, BUS_I2C, client->irq, + ad714x_i2c_read, ad714x_i2c_write); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + i2c_set_clientdata(client, chip); + + return 0; +} + +static int ad714x_i2c_remove(struct i2c_client *client) +{ + struct ad714x_chip *chip = i2c_get_clientdata(client); + + ad714x_remove(chip); + + return 0; +} + +static const struct i2c_device_id ad714x_id[] = { + { "ad7142_captouch", 0 }, + { "ad7143_captouch", 0 }, + { "ad7147_captouch", 0 }, + { "ad7147a_captouch", 0 }, + { "ad7148_captouch", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ad714x_id); + +static struct i2c_driver ad714x_i2c_driver = { + .driver = { + .name = "ad714x_captouch", + .pm = &ad714x_i2c_pm, + }, + .probe = ad714x_i2c_probe, + .remove = ad714x_i2c_remove, + .id_table = ad714x_id, +}; + +module_i2c_driver(ad714x_i2c_driver); + +MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor I2C Bus Driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/ad714x-spi.c b/drivers/input/misc/ad714x-spi.c new file mode 100644 index 00000000..61891486 --- /dev/null +++ b/drivers/input/misc/ad714x-spi.c @@ -0,0 +1,130 @@ +/* + * AD714X CapTouch Programmable Controller driver (SPI bus) + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/input.h> /* BUS_SPI */ +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/pm.h> +#include <linux/types.h> +#include "ad714x.h" + +#define AD714x_SPI_CMD_PREFIX 0xE000 /* bits 15:11 */ +#define AD714x_SPI_READ BIT(10) + +#ifdef CONFIG_PM_SLEEP +static int ad714x_spi_suspend(struct device *dev) +{ + return ad714x_disable(spi_get_drvdata(to_spi_device(dev))); +} + +static int ad714x_spi_resume(struct device *dev) +{ + return ad714x_enable(spi_get_drvdata(to_spi_device(dev))); +} +#endif + +static SIMPLE_DEV_PM_OPS(ad714x_spi_pm, ad714x_spi_suspend, ad714x_spi_resume); + +static int ad714x_spi_read(struct ad714x_chip *chip, + unsigned short reg, unsigned short *data, size_t len) +{ + struct spi_device *spi = to_spi_device(chip->dev); + struct spi_message message; + struct spi_transfer xfer[2]; + int i; + int error; + + spi_message_init(&message); + memset(xfer, 0, sizeof(xfer)); + + chip->xfer_buf[0] = cpu_to_be16(AD714x_SPI_CMD_PREFIX | + AD714x_SPI_READ | reg); + xfer[0].tx_buf = &chip->xfer_buf[0]; + xfer[0].len = sizeof(chip->xfer_buf[0]); + spi_message_add_tail(&xfer[0], &message); + + xfer[1].rx_buf = &chip->xfer_buf[1]; + xfer[1].len = sizeof(chip->xfer_buf[1]) * len; + spi_message_add_tail(&xfer[1], &message); + + error = spi_sync(spi, &message); + if (unlikely(error)) { + dev_err(chip->dev, "SPI read error: %d\n", error); + return error; + } + + for (i = 0; i < len; i++) + data[i] = be16_to_cpu(chip->xfer_buf[i + 1]); + + return 0; +} + +static int ad714x_spi_write(struct ad714x_chip *chip, + unsigned short reg, unsigned short data) +{ + struct spi_device *spi = to_spi_device(chip->dev); + int error; + + chip->xfer_buf[0] = cpu_to_be16(AD714x_SPI_CMD_PREFIX | reg); + chip->xfer_buf[1] = cpu_to_be16(data); + + error = spi_write(spi, (u8 *)chip->xfer_buf, + 2 * sizeof(*chip->xfer_buf)); + if (unlikely(error)) { + dev_err(chip->dev, "SPI write error: %d\n", error); + return error; + } + + return 0; +} + +static int ad714x_spi_probe(struct spi_device *spi) +{ + struct ad714x_chip *chip; + int err; + + spi->bits_per_word = 8; + err = spi_setup(spi); + if (err < 0) + return err; + + chip = ad714x_probe(&spi->dev, BUS_SPI, spi->irq, + ad714x_spi_read, ad714x_spi_write); + if (IS_ERR(chip)) + return PTR_ERR(chip); + + spi_set_drvdata(spi, chip); + + return 0; +} + +static int ad714x_spi_remove(struct spi_device *spi) +{ + struct ad714x_chip *chip = spi_get_drvdata(spi); + + ad714x_remove(chip); + spi_set_drvdata(spi, NULL); + + return 0; +} + +static struct spi_driver ad714x_spi_driver = { + .driver = { + .name = "ad714x_captouch", + .owner = THIS_MODULE, + .pm = &ad714x_spi_pm, + }, + .probe = ad714x_spi_probe, + .remove = ad714x_spi_remove, +}; + +module_spi_driver(ad714x_spi_driver); + +MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor SPI Bus Driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/ad714x.c b/drivers/input/misc/ad714x.c new file mode 100644 index 00000000..2e5d5e1d --- /dev/null +++ b/drivers/input/misc/ad714x.c @@ -0,0 +1,1261 @@ +/* + * AD714X CapTouch Programmable Controller driver supporting AD7142/3/7/8/7A + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/input/ad714x.h> +#include <linux/module.h> +#include "ad714x.h" + +#define AD714X_PWR_CTRL 0x0 +#define AD714X_STG_CAL_EN_REG 0x1 +#define AD714X_AMB_COMP_CTRL0_REG 0x2 +#define AD714X_PARTID_REG 0x17 +#define AD7142_PARTID 0xE620 +#define AD7143_PARTID 0xE630 +#define AD7147_PARTID 0x1470 +#define AD7148_PARTID 0x1480 +#define AD714X_STAGECFG_REG 0x80 +#define AD714X_SYSCFG_REG 0x0 + +#define STG_LOW_INT_EN_REG 0x5 +#define STG_HIGH_INT_EN_REG 0x6 +#define STG_COM_INT_EN_REG 0x7 +#define STG_LOW_INT_STA_REG 0x8 +#define STG_HIGH_INT_STA_REG 0x9 +#define STG_COM_INT_STA_REG 0xA + +#define CDC_RESULT_S0 0xB +#define CDC_RESULT_S1 0xC +#define CDC_RESULT_S2 0xD +#define CDC_RESULT_S3 0xE +#define CDC_RESULT_S4 0xF +#define CDC_RESULT_S5 0x10 +#define CDC_RESULT_S6 0x11 +#define CDC_RESULT_S7 0x12 +#define CDC_RESULT_S8 0x13 +#define CDC_RESULT_S9 0x14 +#define CDC_RESULT_S10 0x15 +#define CDC_RESULT_S11 0x16 + +#define STAGE0_AMBIENT 0xF1 +#define STAGE1_AMBIENT 0x115 +#define STAGE2_AMBIENT 0x139 +#define STAGE3_AMBIENT 0x15D +#define STAGE4_AMBIENT 0x181 +#define STAGE5_AMBIENT 0x1A5 +#define STAGE6_AMBIENT 0x1C9 +#define STAGE7_AMBIENT 0x1ED +#define STAGE8_AMBIENT 0x211 +#define STAGE9_AMBIENT 0x234 +#define STAGE10_AMBIENT 0x259 +#define STAGE11_AMBIENT 0x27D + +#define PER_STAGE_REG_NUM 36 +#define STAGE_CFGREG_NUM 8 +#define SYS_CFGREG_NUM 8 + +/* + * driver information which will be used to maintain the software flow + */ +enum ad714x_device_state { IDLE, JITTER, ACTIVE, SPACE }; + +struct ad714x_slider_drv { + int highest_stage; + int abs_pos; + int flt_pos; + enum ad714x_device_state state; + struct input_dev *input; +}; + +struct ad714x_wheel_drv { + int abs_pos; + int flt_pos; + int pre_highest_stage; + int highest_stage; + enum ad714x_device_state state; + struct input_dev *input; +}; + +struct ad714x_touchpad_drv { + int x_highest_stage; + int x_flt_pos; + int x_abs_pos; + int y_highest_stage; + int y_flt_pos; + int y_abs_pos; + int left_ep; + int left_ep_val; + int right_ep; + int right_ep_val; + int top_ep; + int top_ep_val; + int bottom_ep; + int bottom_ep_val; + enum ad714x_device_state state; + struct input_dev *input; +}; + +struct ad714x_button_drv { + enum ad714x_device_state state; + /* + * Unlike slider/wheel/touchpad, all buttons point to + * same input_dev instance + */ + struct input_dev *input; +}; + +struct ad714x_driver_data { + struct ad714x_slider_drv *slider; + struct ad714x_wheel_drv *wheel; + struct ad714x_touchpad_drv *touchpad; + struct ad714x_button_drv *button; +}; + +/* + * information to integrate all things which will be private data + * of spi/i2c device + */ + +static void ad714x_use_com_int(struct ad714x_chip *ad714x, + int start_stage, int end_stage) +{ + unsigned short data; + unsigned short mask; + + mask = ((1 << (end_stage + 1)) - 1) - ((1 << start_stage) - 1); + + ad714x->read(ad714x, STG_COM_INT_EN_REG, &data, 1); + data |= 1 << end_stage; + ad714x->write(ad714x, STG_COM_INT_EN_REG, data); + + ad714x->read(ad714x, STG_HIGH_INT_EN_REG, &data, 1); + data &= ~mask; + ad714x->write(ad714x, STG_HIGH_INT_EN_REG, data); +} + +static void ad714x_use_thr_int(struct ad714x_chip *ad714x, + int start_stage, int end_stage) +{ + unsigned short data; + unsigned short mask; + + mask = ((1 << (end_stage + 1)) - 1) - ((1 << start_stage) - 1); + + ad714x->read(ad714x, STG_COM_INT_EN_REG, &data, 1); + data &= ~(1 << end_stage); + ad714x->write(ad714x, STG_COM_INT_EN_REG, data); + + ad714x->read(ad714x, STG_HIGH_INT_EN_REG, &data, 1); + data |= mask; + ad714x->write(ad714x, STG_HIGH_INT_EN_REG, data); +} + +static int ad714x_cal_highest_stage(struct ad714x_chip *ad714x, + int start_stage, int end_stage) +{ + int max_res = 0; + int max_idx = 0; + int i; + + for (i = start_stage; i <= end_stage; i++) { + if (ad714x->sensor_val[i] > max_res) { + max_res = ad714x->sensor_val[i]; + max_idx = i; + } + } + + return max_idx; +} + +static int ad714x_cal_abs_pos(struct ad714x_chip *ad714x, + int start_stage, int end_stage, + int highest_stage, int max_coord) +{ + int a_param, b_param; + + if (highest_stage == start_stage) { + a_param = ad714x->sensor_val[start_stage + 1]; + b_param = ad714x->sensor_val[start_stage] + + ad714x->sensor_val[start_stage + 1]; + } else if (highest_stage == end_stage) { + a_param = ad714x->sensor_val[end_stage] * + (end_stage - start_stage) + + ad714x->sensor_val[end_stage - 1] * + (end_stage - start_stage - 1); + b_param = ad714x->sensor_val[end_stage] + + ad714x->sensor_val[end_stage - 1]; + } else { + a_param = ad714x->sensor_val[highest_stage] * + (highest_stage - start_stage) + + ad714x->sensor_val[highest_stage - 1] * + (highest_stage - start_stage - 1) + + ad714x->sensor_val[highest_stage + 1] * + (highest_stage - start_stage + 1); + b_param = ad714x->sensor_val[highest_stage] + + ad714x->sensor_val[highest_stage - 1] + + ad714x->sensor_val[highest_stage + 1]; + } + + return (max_coord / (end_stage - start_stage)) * a_param / b_param; +} + +/* + * One button can connect to multi positive and negative of CDCs + * Multi-buttons can connect to same positive/negative of one CDC + */ +static void ad714x_button_state_machine(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_button_plat *hw = &ad714x->hw->button[idx]; + struct ad714x_button_drv *sw = &ad714x->sw->button[idx]; + + switch (sw->state) { + case IDLE: + if (((ad714x->h_state & hw->h_mask) == hw->h_mask) && + ((ad714x->l_state & hw->l_mask) == hw->l_mask)) { + dev_dbg(ad714x->dev, "button %d touched\n", idx); + input_report_key(sw->input, hw->keycode, 1); + input_sync(sw->input); + sw->state = ACTIVE; + } + break; + + case ACTIVE: + if (((ad714x->h_state & hw->h_mask) != hw->h_mask) || + ((ad714x->l_state & hw->l_mask) != hw->l_mask)) { + dev_dbg(ad714x->dev, "button %d released\n", idx); + input_report_key(sw->input, hw->keycode, 0); + input_sync(sw->input); + sw->state = IDLE; + } + break; + + default: + break; + } +} + +/* + * The response of a sensor is defined by the absolute number of codes + * between the current CDC value and the ambient value. + */ +static void ad714x_slider_cal_sensor_val(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx]; + int i; + + ad714x->read(ad714x, CDC_RESULT_S0 + hw->start_stage, + &ad714x->adc_reg[hw->start_stage], + hw->end_stage - hw->start_stage + 1); + + for (i = hw->start_stage; i <= hw->end_stage; i++) { + ad714x->read(ad714x, STAGE0_AMBIENT + i * PER_STAGE_REG_NUM, + &ad714x->amb_reg[i], 1); + + ad714x->sensor_val[i] = + abs(ad714x->adc_reg[i] - ad714x->amb_reg[i]); + } +} + +static void ad714x_slider_cal_highest_stage(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx]; + struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx]; + + sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage, + hw->end_stage); + + dev_dbg(ad714x->dev, "slider %d highest_stage:%d\n", idx, + sw->highest_stage); +} + +/* + * The formulae are very straight forward. It uses the sensor with the + * highest response and the 2 adjacent ones. + * When Sensor 0 has the highest response, only sensor 0 and sensor 1 + * are used in the calculations. Similarly when the last sensor has the + * highest response, only the last sensor and the second last sensors + * are used in the calculations. + * + * For i= idx_of_peak_Sensor-1 to i= idx_of_peak_Sensor+1 + * v += Sensor response(i)*i + * w += Sensor response(i) + * POS=(Number_of_Positions_Wanted/(Number_of_Sensors_Used-1)) *(v/w) + */ +static void ad714x_slider_cal_abs_pos(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx]; + struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx]; + + sw->abs_pos = ad714x_cal_abs_pos(ad714x, hw->start_stage, hw->end_stage, + sw->highest_stage, hw->max_coord); + + dev_dbg(ad714x->dev, "slider %d absolute position:%d\n", idx, + sw->abs_pos); +} + +/* + * To minimise the Impact of the noise on the algorithm, ADI developed a + * routine that filters the CDC results after they have been read by the + * host processor. + * The filter used is an Infinite Input Response(IIR) filter implemented + * in firmware and attenuates the noise on the CDC results after they've + * been read by the host processor. + * Filtered_CDC_result = (Filtered_CDC_result * (10 - Coefficient) + + * Latest_CDC_result * Coefficient)/10 + */ +static void ad714x_slider_cal_flt_pos(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx]; + + sw->flt_pos = (sw->flt_pos * (10 - 4) + + sw->abs_pos * 4)/10; + + dev_dbg(ad714x->dev, "slider %d filter position:%d\n", idx, + sw->flt_pos); +} + +static void ad714x_slider_use_com_int(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx]; + + ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage); +} + +static void ad714x_slider_use_thr_int(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx]; + + ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage); +} + +static void ad714x_slider_state_machine(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_slider_plat *hw = &ad714x->hw->slider[idx]; + struct ad714x_slider_drv *sw = &ad714x->sw->slider[idx]; + unsigned short h_state, c_state; + unsigned short mask; + + mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1); + + h_state = ad714x->h_state & mask; + c_state = ad714x->c_state & mask; + + switch (sw->state) { + case IDLE: + if (h_state) { + sw->state = JITTER; + /* In End of Conversion interrupt mode, the AD714X + * continuously generates hardware interrupts. + */ + ad714x_slider_use_com_int(ad714x, idx); + dev_dbg(ad714x->dev, "slider %d touched\n", idx); + } + break; + + case JITTER: + if (c_state == mask) { + ad714x_slider_cal_sensor_val(ad714x, idx); + ad714x_slider_cal_highest_stage(ad714x, idx); + ad714x_slider_cal_abs_pos(ad714x, idx); + sw->flt_pos = sw->abs_pos; + sw->state = ACTIVE; + } + break; + + case ACTIVE: + if (c_state == mask) { + if (h_state) { + ad714x_slider_cal_sensor_val(ad714x, idx); + ad714x_slider_cal_highest_stage(ad714x, idx); + ad714x_slider_cal_abs_pos(ad714x, idx); + ad714x_slider_cal_flt_pos(ad714x, idx); + input_report_abs(sw->input, ABS_X, sw->flt_pos); + input_report_key(sw->input, BTN_TOUCH, 1); + } else { + /* When the user lifts off the sensor, configure + * the AD714X back to threshold interrupt mode. + */ + ad714x_slider_use_thr_int(ad714x, idx); + sw->state = IDLE; + input_report_key(sw->input, BTN_TOUCH, 0); + dev_dbg(ad714x->dev, "slider %d released\n", + idx); + } + input_sync(sw->input); + } + break; + + default: + break; + } +} + +/* + * When the scroll wheel is activated, we compute the absolute position based + * on the sensor values. To calculate the position, we first determine the + * sensor that has the greatest response among the 8 sensors that constitutes + * the scrollwheel. Then we determined the 2 sensors on either sides of the + * sensor with the highest response and we apply weights to these sensors. + */ +static void ad714x_wheel_cal_highest_stage(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx]; + struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx]; + + sw->pre_highest_stage = sw->highest_stage; + sw->highest_stage = ad714x_cal_highest_stage(ad714x, hw->start_stage, + hw->end_stage); + + dev_dbg(ad714x->dev, "wheel %d highest_stage:%d\n", idx, + sw->highest_stage); +} + +static void ad714x_wheel_cal_sensor_val(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx]; + int i; + + ad714x->read(ad714x, CDC_RESULT_S0 + hw->start_stage, + &ad714x->adc_reg[hw->start_stage], + hw->end_stage - hw->start_stage + 1); + + for (i = hw->start_stage; i <= hw->end_stage; i++) { + ad714x->read(ad714x, STAGE0_AMBIENT + i * PER_STAGE_REG_NUM, + &ad714x->amb_reg[i], 1); + if (ad714x->adc_reg[i] > ad714x->amb_reg[i]) + ad714x->sensor_val[i] = + ad714x->adc_reg[i] - ad714x->amb_reg[i]; + else + ad714x->sensor_val[i] = 0; + } +} + +/* + * When the scroll wheel is activated, we compute the absolute position based + * on the sensor values. To calculate the position, we first determine the + * sensor that has the greatest response among the sensors that constitutes + * the scrollwheel. Then we determined the sensors on either sides of the + * sensor with the highest response and we apply weights to these sensors. The + * result of this computation gives us the mean value. + */ + +static void ad714x_wheel_cal_abs_pos(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx]; + struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx]; + int stage_num = hw->end_stage - hw->start_stage + 1; + int first_before, highest, first_after; + int a_param, b_param; + + first_before = (sw->highest_stage + stage_num - 1) % stage_num; + highest = sw->highest_stage; + first_after = (sw->highest_stage + stage_num + 1) % stage_num; + + a_param = ad714x->sensor_val[highest] * + (highest - hw->start_stage) + + ad714x->sensor_val[first_before] * + (highest - hw->start_stage - 1) + + ad714x->sensor_val[first_after] * + (highest - hw->start_stage + 1); + b_param = ad714x->sensor_val[highest] + + ad714x->sensor_val[first_before] + + ad714x->sensor_val[first_after]; + + sw->abs_pos = ((hw->max_coord / (hw->end_stage - hw->start_stage)) * + a_param) / b_param; + + if (sw->abs_pos > hw->max_coord) + sw->abs_pos = hw->max_coord; + else if (sw->abs_pos < 0) + sw->abs_pos = 0; +} + +static void ad714x_wheel_cal_flt_pos(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx]; + struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx]; + if (((sw->pre_highest_stage == hw->end_stage) && + (sw->highest_stage == hw->start_stage)) || + ((sw->pre_highest_stage == hw->start_stage) && + (sw->highest_stage == hw->end_stage))) + sw->flt_pos = sw->abs_pos; + else + sw->flt_pos = ((sw->flt_pos * 30) + (sw->abs_pos * 71)) / 100; + + if (sw->flt_pos > hw->max_coord) + sw->flt_pos = hw->max_coord; +} + +static void ad714x_wheel_use_com_int(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx]; + + ad714x_use_com_int(ad714x, hw->start_stage, hw->end_stage); +} + +static void ad714x_wheel_use_thr_int(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx]; + + ad714x_use_thr_int(ad714x, hw->start_stage, hw->end_stage); +} + +static void ad714x_wheel_state_machine(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_wheel_plat *hw = &ad714x->hw->wheel[idx]; + struct ad714x_wheel_drv *sw = &ad714x->sw->wheel[idx]; + unsigned short h_state, c_state; + unsigned short mask; + + mask = ((1 << (hw->end_stage + 1)) - 1) - ((1 << hw->start_stage) - 1); + + h_state = ad714x->h_state & mask; + c_state = ad714x->c_state & mask; + + switch (sw->state) { + case IDLE: + if (h_state) { + sw->state = JITTER; + /* In End of Conversion interrupt mode, the AD714X + * continuously generates hardware interrupts. + */ + ad714x_wheel_use_com_int(ad714x, idx); + dev_dbg(ad714x->dev, "wheel %d touched\n", idx); + } + break; + + case JITTER: + if (c_state == mask) { + ad714x_wheel_cal_sensor_val(ad714x, idx); + ad714x_wheel_cal_highest_stage(ad714x, idx); + ad714x_wheel_cal_abs_pos(ad714x, idx); + sw->flt_pos = sw->abs_pos; + sw->state = ACTIVE; + } + break; + + case ACTIVE: + if (c_state == mask) { + if (h_state) { + ad714x_wheel_cal_sensor_val(ad714x, idx); + ad714x_wheel_cal_highest_stage(ad714x, idx); + ad714x_wheel_cal_abs_pos(ad714x, idx); + ad714x_wheel_cal_flt_pos(ad714x, idx); + input_report_abs(sw->input, ABS_WHEEL, + sw->flt_pos); + input_report_key(sw->input, BTN_TOUCH, 1); + } else { + /* When the user lifts off the sensor, configure + * the AD714X back to threshold interrupt mode. + */ + ad714x_wheel_use_thr_int(ad714x, idx); + sw->state = IDLE; + input_report_key(sw->input, BTN_TOUCH, 0); + + dev_dbg(ad714x->dev, "wheel %d released\n", + idx); + } + input_sync(sw->input); + } + break; + + default: + break; + } +} + +static void touchpad_cal_sensor_val(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + int i; + + ad714x->read(ad714x, CDC_RESULT_S0 + hw->x_start_stage, + &ad714x->adc_reg[hw->x_start_stage], + hw->x_end_stage - hw->x_start_stage + 1); + + for (i = hw->x_start_stage; i <= hw->x_end_stage; i++) { + ad714x->read(ad714x, STAGE0_AMBIENT + i * PER_STAGE_REG_NUM, + &ad714x->amb_reg[i], 1); + if (ad714x->adc_reg[i] > ad714x->amb_reg[i]) + ad714x->sensor_val[i] = + ad714x->adc_reg[i] - ad714x->amb_reg[i]; + else + ad714x->sensor_val[i] = 0; + } +} + +static void touchpad_cal_highest_stage(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx]; + + sw->x_highest_stage = ad714x_cal_highest_stage(ad714x, + hw->x_start_stage, hw->x_end_stage); + sw->y_highest_stage = ad714x_cal_highest_stage(ad714x, + hw->y_start_stage, hw->y_end_stage); + + dev_dbg(ad714x->dev, + "touchpad %d x_highest_stage:%d, y_highest_stage:%d\n", + idx, sw->x_highest_stage, sw->y_highest_stage); +} + +/* + * If 2 fingers are touching the sensor then 2 peaks can be observed in the + * distribution. + * The arithmetic doesn't support to get absolute coordinates for multi-touch + * yet. + */ +static int touchpad_check_second_peak(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx]; + int i; + + for (i = hw->x_start_stage; i < sw->x_highest_stage; i++) { + if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1]) + > (ad714x->sensor_val[i + 1] / 10)) + return 1; + } + + for (i = sw->x_highest_stage; i < hw->x_end_stage; i++) { + if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i]) + > (ad714x->sensor_val[i] / 10)) + return 1; + } + + for (i = hw->y_start_stage; i < sw->y_highest_stage; i++) { + if ((ad714x->sensor_val[i] - ad714x->sensor_val[i + 1]) + > (ad714x->sensor_val[i + 1] / 10)) + return 1; + } + + for (i = sw->y_highest_stage; i < hw->y_end_stage; i++) { + if ((ad714x->sensor_val[i + 1] - ad714x->sensor_val[i]) + > (ad714x->sensor_val[i] / 10)) + return 1; + } + + return 0; +} + +/* + * If only one finger is used to activate the touch pad then only 1 peak will be + * registered in the distribution. This peak and the 2 adjacent sensors will be + * used in the calculation of the absolute position. This will prevent hand + * shadows to affect the absolute position calculation. + */ +static void touchpad_cal_abs_pos(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx]; + + sw->x_abs_pos = ad714x_cal_abs_pos(ad714x, hw->x_start_stage, + hw->x_end_stage, sw->x_highest_stage, hw->x_max_coord); + sw->y_abs_pos = ad714x_cal_abs_pos(ad714x, hw->y_start_stage, + hw->y_end_stage, sw->y_highest_stage, hw->y_max_coord); + + dev_dbg(ad714x->dev, "touchpad %d absolute position:(%d, %d)\n", idx, + sw->x_abs_pos, sw->y_abs_pos); +} + +static void touchpad_cal_flt_pos(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx]; + + sw->x_flt_pos = (sw->x_flt_pos * (10 - 4) + + sw->x_abs_pos * 4)/10; + sw->y_flt_pos = (sw->y_flt_pos * (10 - 4) + + sw->y_abs_pos * 4)/10; + + dev_dbg(ad714x->dev, "touchpad %d filter position:(%d, %d)\n", + idx, sw->x_flt_pos, sw->y_flt_pos); +} + +/* + * To prevent distortion from showing in the absolute position, it is + * necessary to detect the end points. When endpoints are detected, the + * driver stops updating the status variables with absolute positions. + * End points are detected on the 4 edges of the touchpad sensor. The + * method to detect them is the same for all 4. + * To detect the end points, the firmware computes the difference in + * percent between the sensor on the edge and the adjacent one. The + * difference is calculated in percent in order to make the end point + * detection independent of the pressure. + */ + +#define LEFT_END_POINT_DETECTION_LEVEL 550 +#define RIGHT_END_POINT_DETECTION_LEVEL 750 +#define LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL 850 +#define TOP_END_POINT_DETECTION_LEVEL 550 +#define BOTTOM_END_POINT_DETECTION_LEVEL 950 +#define TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL 700 +static int touchpad_check_endpoint(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx]; + int percent_sensor_diff; + + /* left endpoint detect */ + percent_sensor_diff = (ad714x->sensor_val[hw->x_start_stage] - + ad714x->sensor_val[hw->x_start_stage + 1]) * 100 / + ad714x->sensor_val[hw->x_start_stage + 1]; + if (!sw->left_ep) { + if (percent_sensor_diff >= LEFT_END_POINT_DETECTION_LEVEL) { + sw->left_ep = 1; + sw->left_ep_val = + ad714x->sensor_val[hw->x_start_stage + 1]; + } + } else { + if ((percent_sensor_diff < LEFT_END_POINT_DETECTION_LEVEL) && + (ad714x->sensor_val[hw->x_start_stage + 1] > + LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->left_ep_val)) + sw->left_ep = 0; + } + + /* right endpoint detect */ + percent_sensor_diff = (ad714x->sensor_val[hw->x_end_stage] - + ad714x->sensor_val[hw->x_end_stage - 1]) * 100 / + ad714x->sensor_val[hw->x_end_stage - 1]; + if (!sw->right_ep) { + if (percent_sensor_diff >= RIGHT_END_POINT_DETECTION_LEVEL) { + sw->right_ep = 1; + sw->right_ep_val = + ad714x->sensor_val[hw->x_end_stage - 1]; + } + } else { + if ((percent_sensor_diff < RIGHT_END_POINT_DETECTION_LEVEL) && + (ad714x->sensor_val[hw->x_end_stage - 1] > + LEFT_RIGHT_END_POINT_DEAVTIVALION_LEVEL + sw->right_ep_val)) + sw->right_ep = 0; + } + + /* top endpoint detect */ + percent_sensor_diff = (ad714x->sensor_val[hw->y_start_stage] - + ad714x->sensor_val[hw->y_start_stage + 1]) * 100 / + ad714x->sensor_val[hw->y_start_stage + 1]; + if (!sw->top_ep) { + if (percent_sensor_diff >= TOP_END_POINT_DETECTION_LEVEL) { + sw->top_ep = 1; + sw->top_ep_val = + ad714x->sensor_val[hw->y_start_stage + 1]; + } + } else { + if ((percent_sensor_diff < TOP_END_POINT_DETECTION_LEVEL) && + (ad714x->sensor_val[hw->y_start_stage + 1] > + TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->top_ep_val)) + sw->top_ep = 0; + } + + /* bottom endpoint detect */ + percent_sensor_diff = (ad714x->sensor_val[hw->y_end_stage] - + ad714x->sensor_val[hw->y_end_stage - 1]) * 100 / + ad714x->sensor_val[hw->y_end_stage - 1]; + if (!sw->bottom_ep) { + if (percent_sensor_diff >= BOTTOM_END_POINT_DETECTION_LEVEL) { + sw->bottom_ep = 1; + sw->bottom_ep_val = + ad714x->sensor_val[hw->y_end_stage - 1]; + } + } else { + if ((percent_sensor_diff < BOTTOM_END_POINT_DETECTION_LEVEL) && + (ad714x->sensor_val[hw->y_end_stage - 1] > + TOP_BOTTOM_END_POINT_DEAVTIVALION_LEVEL + sw->bottom_ep_val)) + sw->bottom_ep = 0; + } + + return sw->left_ep || sw->right_ep || sw->top_ep || sw->bottom_ep; +} + +static void touchpad_use_com_int(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + + ad714x_use_com_int(ad714x, hw->x_start_stage, hw->x_end_stage); +} + +static void touchpad_use_thr_int(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + + ad714x_use_thr_int(ad714x, hw->x_start_stage, hw->x_end_stage); + ad714x_use_thr_int(ad714x, hw->y_start_stage, hw->y_end_stage); +} + +static void ad714x_touchpad_state_machine(struct ad714x_chip *ad714x, int idx) +{ + struct ad714x_touchpad_plat *hw = &ad714x->hw->touchpad[idx]; + struct ad714x_touchpad_drv *sw = &ad714x->sw->touchpad[idx]; + unsigned short h_state, c_state; + unsigned short mask; + + mask = (((1 << (hw->x_end_stage + 1)) - 1) - + ((1 << hw->x_start_stage) - 1)) + + (((1 << (hw->y_end_stage + 1)) - 1) - + ((1 << hw->y_start_stage) - 1)); + + h_state = ad714x->h_state & mask; + c_state = ad714x->c_state & mask; + + switch (sw->state) { + case IDLE: + if (h_state) { + sw->state = JITTER; + /* In End of Conversion interrupt mode, the AD714X + * continuously generates hardware interrupts. + */ + touchpad_use_com_int(ad714x, idx); + dev_dbg(ad714x->dev, "touchpad %d touched\n", idx); + } + break; + + case JITTER: + if (c_state == mask) { + touchpad_cal_sensor_val(ad714x, idx); + touchpad_cal_highest_stage(ad714x, idx); + if ((!touchpad_check_second_peak(ad714x, idx)) && + (!touchpad_check_endpoint(ad714x, idx))) { + dev_dbg(ad714x->dev, + "touchpad%d, 2 fingers or endpoint\n", + idx); + touchpad_cal_abs_pos(ad714x, idx); + sw->x_flt_pos = sw->x_abs_pos; + sw->y_flt_pos = sw->y_abs_pos; + sw->state = ACTIVE; + } + } + break; + + case ACTIVE: + if (c_state == mask) { + if (h_state) { + touchpad_cal_sensor_val(ad714x, idx); + touchpad_cal_highest_stage(ad714x, idx); + if ((!touchpad_check_second_peak(ad714x, idx)) + && (!touchpad_check_endpoint(ad714x, idx))) { + touchpad_cal_abs_pos(ad714x, idx); + touchpad_cal_flt_pos(ad714x, idx); + input_report_abs(sw->input, ABS_X, + sw->x_flt_pos); + input_report_abs(sw->input, ABS_Y, + sw->y_flt_pos); + input_report_key(sw->input, BTN_TOUCH, + 1); + } + } else { + /* When the user lifts off the sensor, configure + * the AD714X back to threshold interrupt mode. + */ + touchpad_use_thr_int(ad714x, idx); + sw->state = IDLE; + input_report_key(sw->input, BTN_TOUCH, 0); + dev_dbg(ad714x->dev, "touchpad %d released\n", + idx); + } + input_sync(sw->input); + } + break; + + default: + break; + } +} + +static int ad714x_hw_detect(struct ad714x_chip *ad714x) +{ + unsigned short data; + + ad714x->read(ad714x, AD714X_PARTID_REG, &data, 1); + switch (data & 0xFFF0) { + case AD7142_PARTID: + ad714x->product = 0x7142; + ad714x->version = data & 0xF; + dev_info(ad714x->dev, "found AD7142 captouch, rev:%d\n", + ad714x->version); + return 0; + + case AD7143_PARTID: + ad714x->product = 0x7143; + ad714x->version = data & 0xF; + dev_info(ad714x->dev, "found AD7143 captouch, rev:%d\n", + ad714x->version); + return 0; + + case AD7147_PARTID: + ad714x->product = 0x7147; + ad714x->version = data & 0xF; + dev_info(ad714x->dev, "found AD7147(A) captouch, rev:%d\n", + ad714x->version); + return 0; + + case AD7148_PARTID: + ad714x->product = 0x7148; + ad714x->version = data & 0xF; + dev_info(ad714x->dev, "found AD7148 captouch, rev:%d\n", + ad714x->version); + return 0; + + default: + dev_err(ad714x->dev, + "fail to detect AD714X captouch, read ID is %04x\n", + data); + return -ENODEV; + } +} + +static void ad714x_hw_init(struct ad714x_chip *ad714x) +{ + int i, j; + unsigned short reg_base; + unsigned short data; + + /* configuration CDC and interrupts */ + + for (i = 0; i < STAGE_NUM; i++) { + reg_base = AD714X_STAGECFG_REG + i * STAGE_CFGREG_NUM; + for (j = 0; j < STAGE_CFGREG_NUM; j++) + ad714x->write(ad714x, reg_base + j, + ad714x->hw->stage_cfg_reg[i][j]); + } + + for (i = 0; i < SYS_CFGREG_NUM; i++) + ad714x->write(ad714x, AD714X_SYSCFG_REG + i, + ad714x->hw->sys_cfg_reg[i]); + for (i = 0; i < SYS_CFGREG_NUM; i++) + ad714x->read(ad714x, AD714X_SYSCFG_REG + i, &data, 1); + + ad714x->write(ad714x, AD714X_STG_CAL_EN_REG, 0xFFF); + + /* clear all interrupts */ + ad714x->read(ad714x, STG_LOW_INT_STA_REG, &ad714x->l_state, 3); +} + +static irqreturn_t ad714x_interrupt_thread(int irq, void *data) +{ + struct ad714x_chip *ad714x = data; + int i; + + mutex_lock(&ad714x->mutex); + + ad714x->read(ad714x, STG_LOW_INT_STA_REG, &ad714x->l_state, 3); + + for (i = 0; i < ad714x->hw->button_num; i++) + ad714x_button_state_machine(ad714x, i); + for (i = 0; i < ad714x->hw->slider_num; i++) + ad714x_slider_state_machine(ad714x, i); + for (i = 0; i < ad714x->hw->wheel_num; i++) + ad714x_wheel_state_machine(ad714x, i); + for (i = 0; i < ad714x->hw->touchpad_num; i++) + ad714x_touchpad_state_machine(ad714x, i); + + mutex_unlock(&ad714x->mutex); + + return IRQ_HANDLED; +} + +#define MAX_DEVICE_NUM 8 +struct ad714x_chip *ad714x_probe(struct device *dev, u16 bus_type, int irq, + ad714x_read_t read, ad714x_write_t write) +{ + int i, alloc_idx; + int error; + struct input_dev *input[MAX_DEVICE_NUM]; + + struct ad714x_platform_data *plat_data = dev->platform_data; + struct ad714x_chip *ad714x; + void *drv_mem; + unsigned long irqflags; + + struct ad714x_button_drv *bt_drv; + struct ad714x_slider_drv *sd_drv; + struct ad714x_wheel_drv *wl_drv; + struct ad714x_touchpad_drv *tp_drv; + + + if (irq <= 0) { + dev_err(dev, "IRQ not configured!\n"); + error = -EINVAL; + goto err_out; + } + + if (dev->platform_data == NULL) { + dev_err(dev, "platform data for ad714x doesn't exist\n"); + error = -EINVAL; + goto err_out; + } + + ad714x = kzalloc(sizeof(*ad714x) + sizeof(*ad714x->sw) + + sizeof(*sd_drv) * plat_data->slider_num + + sizeof(*wl_drv) * plat_data->wheel_num + + sizeof(*tp_drv) * plat_data->touchpad_num + + sizeof(*bt_drv) * plat_data->button_num, GFP_KERNEL); + if (!ad714x) { + error = -ENOMEM; + goto err_out; + } + + ad714x->hw = plat_data; + + drv_mem = ad714x + 1; + ad714x->sw = drv_mem; + drv_mem += sizeof(*ad714x->sw); + ad714x->sw->slider = sd_drv = drv_mem; + drv_mem += sizeof(*sd_drv) * ad714x->hw->slider_num; + ad714x->sw->wheel = wl_drv = drv_mem; + drv_mem += sizeof(*wl_drv) * ad714x->hw->wheel_num; + ad714x->sw->touchpad = tp_drv = drv_mem; + drv_mem += sizeof(*tp_drv) * ad714x->hw->touchpad_num; + ad714x->sw->button = bt_drv = drv_mem; + drv_mem += sizeof(*bt_drv) * ad714x->hw->button_num; + + ad714x->read = read; + ad714x->write = write; + ad714x->irq = irq; + ad714x->dev = dev; + + error = ad714x_hw_detect(ad714x); + if (error) + goto err_free_mem; + + /* initialize and request sw/hw resources */ + + ad714x_hw_init(ad714x); + mutex_init(&ad714x->mutex); + + /* + * Allocate and register AD714X input device + */ + alloc_idx = 0; + + /* a slider uses one input_dev instance */ + if (ad714x->hw->slider_num > 0) { + struct ad714x_slider_plat *sd_plat = ad714x->hw->slider; + + for (i = 0; i < ad714x->hw->slider_num; i++) { + sd_drv[i].input = input[alloc_idx] = input_allocate_device(); + if (!input[alloc_idx]) { + error = -ENOMEM; + goto err_free_dev; + } + + __set_bit(EV_ABS, input[alloc_idx]->evbit); + __set_bit(EV_KEY, input[alloc_idx]->evbit); + __set_bit(ABS_X, input[alloc_idx]->absbit); + __set_bit(BTN_TOUCH, input[alloc_idx]->keybit); + input_set_abs_params(input[alloc_idx], + ABS_X, 0, sd_plat->max_coord, 0, 0); + + input[alloc_idx]->id.bustype = bus_type; + input[alloc_idx]->id.product = ad714x->product; + input[alloc_idx]->id.version = ad714x->version; + input[alloc_idx]->name = "ad714x_captouch_slider"; + input[alloc_idx]->dev.parent = dev; + + error = input_register_device(input[alloc_idx]); + if (error) + goto err_free_dev; + + alloc_idx++; + } + } + + /* a wheel uses one input_dev instance */ + if (ad714x->hw->wheel_num > 0) { + struct ad714x_wheel_plat *wl_plat = ad714x->hw->wheel; + + for (i = 0; i < ad714x->hw->wheel_num; i++) { + wl_drv[i].input = input[alloc_idx] = input_allocate_device(); + if (!input[alloc_idx]) { + error = -ENOMEM; + goto err_free_dev; + } + + __set_bit(EV_KEY, input[alloc_idx]->evbit); + __set_bit(EV_ABS, input[alloc_idx]->evbit); + __set_bit(ABS_WHEEL, input[alloc_idx]->absbit); + __set_bit(BTN_TOUCH, input[alloc_idx]->keybit); + input_set_abs_params(input[alloc_idx], + ABS_WHEEL, 0, wl_plat->max_coord, 0, 0); + + input[alloc_idx]->id.bustype = bus_type; + input[alloc_idx]->id.product = ad714x->product; + input[alloc_idx]->id.version = ad714x->version; + input[alloc_idx]->name = "ad714x_captouch_wheel"; + input[alloc_idx]->dev.parent = dev; + + error = input_register_device(input[alloc_idx]); + if (error) + goto err_free_dev; + + alloc_idx++; + } + } + + /* a touchpad uses one input_dev instance */ + if (ad714x->hw->touchpad_num > 0) { + struct ad714x_touchpad_plat *tp_plat = ad714x->hw->touchpad; + + for (i = 0; i < ad714x->hw->touchpad_num; i++) { + tp_drv[i].input = input[alloc_idx] = input_allocate_device(); + if (!input[alloc_idx]) { + error = -ENOMEM; + goto err_free_dev; + } + + __set_bit(EV_ABS, input[alloc_idx]->evbit); + __set_bit(EV_KEY, input[alloc_idx]->evbit); + __set_bit(ABS_X, input[alloc_idx]->absbit); + __set_bit(ABS_Y, input[alloc_idx]->absbit); + __set_bit(BTN_TOUCH, input[alloc_idx]->keybit); + input_set_abs_params(input[alloc_idx], + ABS_X, 0, tp_plat->x_max_coord, 0, 0); + input_set_abs_params(input[alloc_idx], + ABS_Y, 0, tp_plat->y_max_coord, 0, 0); + + input[alloc_idx]->id.bustype = bus_type; + input[alloc_idx]->id.product = ad714x->product; + input[alloc_idx]->id.version = ad714x->version; + input[alloc_idx]->name = "ad714x_captouch_pad"; + input[alloc_idx]->dev.parent = dev; + + error = input_register_device(input[alloc_idx]); + if (error) + goto err_free_dev; + + alloc_idx++; + } + } + + /* all buttons use one input node */ + if (ad714x->hw->button_num > 0) { + struct ad714x_button_plat *bt_plat = ad714x->hw->button; + + input[alloc_idx] = input_allocate_device(); + if (!input[alloc_idx]) { + error = -ENOMEM; + goto err_free_dev; + } + + __set_bit(EV_KEY, input[alloc_idx]->evbit); + for (i = 0; i < ad714x->hw->button_num; i++) { + bt_drv[i].input = input[alloc_idx]; + __set_bit(bt_plat[i].keycode, input[alloc_idx]->keybit); + } + + input[alloc_idx]->id.bustype = bus_type; + input[alloc_idx]->id.product = ad714x->product; + input[alloc_idx]->id.version = ad714x->version; + input[alloc_idx]->name = "ad714x_captouch_button"; + input[alloc_idx]->dev.parent = dev; + + error = input_register_device(input[alloc_idx]); + if (error) + goto err_free_dev; + + alloc_idx++; + } + + irqflags = plat_data->irqflags ?: IRQF_TRIGGER_FALLING; + irqflags |= IRQF_ONESHOT; + + error = request_threaded_irq(ad714x->irq, NULL, ad714x_interrupt_thread, + irqflags, "ad714x_captouch", ad714x); + if (error) { + dev_err(dev, "can't allocate irq %d\n", ad714x->irq); + goto err_unreg_dev; + } + + return ad714x; + + err_free_dev: + dev_err(dev, "failed to setup AD714x input device %i\n", alloc_idx); + input_free_device(input[alloc_idx]); + err_unreg_dev: + while (--alloc_idx >= 0) + input_unregister_device(input[alloc_idx]); + err_free_mem: + kfree(ad714x); + err_out: + return ERR_PTR(error); +} +EXPORT_SYMBOL(ad714x_probe); + +void ad714x_remove(struct ad714x_chip *ad714x) +{ + struct ad714x_platform_data *hw = ad714x->hw; + struct ad714x_driver_data *sw = ad714x->sw; + int i; + + free_irq(ad714x->irq, ad714x); + + /* unregister and free all input devices */ + + for (i = 0; i < hw->slider_num; i++) + input_unregister_device(sw->slider[i].input); + + for (i = 0; i < hw->wheel_num; i++) + input_unregister_device(sw->wheel[i].input); + + for (i = 0; i < hw->touchpad_num; i++) + input_unregister_device(sw->touchpad[i].input); + + if (hw->button_num) + input_unregister_device(sw->button[0].input); + + kfree(ad714x); +} +EXPORT_SYMBOL(ad714x_remove); + +#ifdef CONFIG_PM +int ad714x_disable(struct ad714x_chip *ad714x) +{ + unsigned short data; + + dev_dbg(ad714x->dev, "%s enter\n", __func__); + + mutex_lock(&ad714x->mutex); + + data = ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL] | 0x3; + ad714x->write(ad714x, AD714X_PWR_CTRL, data); + + mutex_unlock(&ad714x->mutex); + + return 0; +} +EXPORT_SYMBOL(ad714x_disable); + +int ad714x_enable(struct ad714x_chip *ad714x) +{ + dev_dbg(ad714x->dev, "%s enter\n", __func__); + + mutex_lock(&ad714x->mutex); + + /* resume to non-shutdown mode */ + + ad714x->write(ad714x, AD714X_PWR_CTRL, + ad714x->hw->sys_cfg_reg[AD714X_PWR_CTRL]); + + /* make sure the interrupt output line is not low level after resume, + * otherwise we will get no chance to enter falling-edge irq again + */ + + ad714x->read(ad714x, STG_LOW_INT_STA_REG, &ad714x->l_state, 3); + + mutex_unlock(&ad714x->mutex); + + return 0; +} +EXPORT_SYMBOL(ad714x_enable); +#endif + +MODULE_DESCRIPTION("Analog Devices AD714X Capacitance Touch Sensor Driver"); +MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/ad714x.h b/drivers/input/misc/ad714x.h new file mode 100644 index 00000000..3c85455a --- /dev/null +++ b/drivers/input/misc/ad714x.h @@ -0,0 +1,55 @@ +/* + * AD714X CapTouch Programmable Controller driver (bus interfaces) + * + * Copyright 2009-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#ifndef _AD714X_H_ +#define _AD714X_H_ + +#include <linux/types.h> + +#define STAGE_NUM 12 + +struct device; +struct ad714x_platform_data; +struct ad714x_driver_data; +struct ad714x_chip; + +typedef int (*ad714x_read_t)(struct ad714x_chip *, unsigned short, unsigned short *, size_t); +typedef int (*ad714x_write_t)(struct ad714x_chip *, unsigned short, unsigned short); + +struct ad714x_chip { + unsigned short l_state; + unsigned short h_state; + unsigned short c_state; + unsigned short adc_reg[STAGE_NUM]; + unsigned short amb_reg[STAGE_NUM]; + unsigned short sensor_val[STAGE_NUM]; + + struct ad714x_platform_data *hw; + struct ad714x_driver_data *sw; + + int irq; + struct device *dev; + ad714x_read_t read; + ad714x_write_t write; + + struct mutex mutex; + + unsigned product; + unsigned version; + + __be16 xfer_buf[16] ____cacheline_aligned; + +}; + +int ad714x_disable(struct ad714x_chip *ad714x); +int ad714x_enable(struct ad714x_chip *ad714x); +struct ad714x_chip *ad714x_probe(struct device *dev, u16 bus_type, int irq, + ad714x_read_t read, ad714x_write_t write); +void ad714x_remove(struct ad714x_chip *ad714x); + +#endif diff --git a/drivers/input/misc/adxl34x-i2c.c b/drivers/input/misc/adxl34x-i2c.c new file mode 100644 index 00000000..416f47dd --- /dev/null +++ b/drivers/input/misc/adxl34x-i2c.c @@ -0,0 +1,155 @@ +/* + * ADLX345/346 Three-Axis Digital Accelerometers (I2C Interface) + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc. + * Licensed under the GPL-2 or later. + */ + +#include <linux/input.h> /* BUS_I2C */ +#include <linux/i2c.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/pm.h> +#include "adxl34x.h" + +static int adxl34x_smbus_read(struct device *dev, unsigned char reg) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_read_byte_data(client, reg); +} + +static int adxl34x_smbus_write(struct device *dev, + unsigned char reg, unsigned char val) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_write_byte_data(client, reg, val); +} + +static int adxl34x_smbus_read_block(struct device *dev, + unsigned char reg, int count, + void *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + + return i2c_smbus_read_i2c_block_data(client, reg, count, buf); +} + +static int adxl34x_i2c_read_block(struct device *dev, + unsigned char reg, int count, + void *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret; + + ret = i2c_master_send(client, ®, 1); + if (ret < 0) + return ret; + + ret = i2c_master_recv(client, buf, count); + if (ret < 0) + return ret; + + if (ret != count) + return -EIO; + + return 0; +} + +static const struct adxl34x_bus_ops adxl34x_smbus_bops = { + .bustype = BUS_I2C, + .write = adxl34x_smbus_write, + .read = adxl34x_smbus_read, + .read_block = adxl34x_smbus_read_block, +}; + +static const struct adxl34x_bus_ops adxl34x_i2c_bops = { + .bustype = BUS_I2C, + .write = adxl34x_smbus_write, + .read = adxl34x_smbus_read, + .read_block = adxl34x_i2c_read_block, +}; + +static int adxl34x_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct adxl34x *ac; + int error; + + error = i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA); + if (!error) { + dev_err(&client->dev, "SMBUS Byte Data not Supported\n"); + return -EIO; + } + + ac = adxl34x_probe(&client->dev, client->irq, false, + i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_I2C_BLOCK) ? + &adxl34x_smbus_bops : &adxl34x_i2c_bops); + if (IS_ERR(ac)) + return PTR_ERR(ac); + + i2c_set_clientdata(client, ac); + + return 0; +} + +static int adxl34x_i2c_remove(struct i2c_client *client) +{ + struct adxl34x *ac = i2c_get_clientdata(client); + + return adxl34x_remove(ac); +} + +#ifdef CONFIG_PM_SLEEP +static int adxl34x_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adxl34x *ac = i2c_get_clientdata(client); + + adxl34x_suspend(ac); + + return 0; +} + +static int adxl34x_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct adxl34x *ac = i2c_get_clientdata(client); + + adxl34x_resume(ac); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(adxl34x_i2c_pm, adxl34x_i2c_suspend, + adxl34x_i2c_resume); + +static const struct i2c_device_id adxl34x_id[] = { + { "adxl34x", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, adxl34x_id); + +static struct i2c_driver adxl34x_driver = { + .driver = { + .name = "adxl34x", + .owner = THIS_MODULE, + .pm = &adxl34x_i2c_pm, + }, + .probe = adxl34x_i2c_probe, + .remove = adxl34x_i2c_remove, + .id_table = adxl34x_id, +}; + +module_i2c_driver(adxl34x_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("ADXL345/346 Three-Axis Digital Accelerometer I2C Bus Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/adxl34x-spi.c b/drivers/input/misc/adxl34x-spi.c new file mode 100644 index 00000000..76dc0679 --- /dev/null +++ b/drivers/input/misc/adxl34x-spi.c @@ -0,0 +1,136 @@ +/* + * ADLX345/346 Three-Axis Digital Accelerometers (SPI Interface) + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc. + * Licensed under the GPL-2 or later. + */ + +#include <linux/input.h> /* BUS_SPI */ +#include <linux/module.h> +#include <linux/spi/spi.h> +#include <linux/pm.h> +#include <linux/types.h> +#include "adxl34x.h" + +#define MAX_SPI_FREQ_HZ 5000000 +#define MAX_FREQ_NO_FIFODELAY 1500000 +#define ADXL34X_CMD_MULTB (1 << 6) +#define ADXL34X_CMD_READ (1 << 7) +#define ADXL34X_WRITECMD(reg) (reg & 0x3F) +#define ADXL34X_READCMD(reg) (ADXL34X_CMD_READ | (reg & 0x3F)) +#define ADXL34X_READMB_CMD(reg) (ADXL34X_CMD_READ | ADXL34X_CMD_MULTB \ + | (reg & 0x3F)) + +static int adxl34x_spi_read(struct device *dev, unsigned char reg) +{ + struct spi_device *spi = to_spi_device(dev); + unsigned char cmd; + + cmd = ADXL34X_READCMD(reg); + + return spi_w8r8(spi, cmd); +} + +static int adxl34x_spi_write(struct device *dev, + unsigned char reg, unsigned char val) +{ + struct spi_device *spi = to_spi_device(dev); + unsigned char buf[2]; + + buf[0] = ADXL34X_WRITECMD(reg); + buf[1] = val; + + return spi_write(spi, buf, sizeof(buf)); +} + +static int adxl34x_spi_read_block(struct device *dev, + unsigned char reg, int count, + void *buf) +{ + struct spi_device *spi = to_spi_device(dev); + ssize_t status; + + reg = ADXL34X_READMB_CMD(reg); + status = spi_write_then_read(spi, ®, 1, buf, count); + + return (status < 0) ? status : 0; +} + +static const struct adxl34x_bus_ops adxl34x_spi_bops = { + .bustype = BUS_SPI, + .write = adxl34x_spi_write, + .read = adxl34x_spi_read, + .read_block = adxl34x_spi_read_block, +}; + +static int adxl34x_spi_probe(struct spi_device *spi) +{ + struct adxl34x *ac; + + /* don't exceed max specified SPI CLK frequency */ + if (spi->max_speed_hz > MAX_SPI_FREQ_HZ) { + dev_err(&spi->dev, "SPI CLK %d Hz too fast\n", spi->max_speed_hz); + return -EINVAL; + } + + ac = adxl34x_probe(&spi->dev, spi->irq, + spi->max_speed_hz > MAX_FREQ_NO_FIFODELAY, + &adxl34x_spi_bops); + + if (IS_ERR(ac)) + return PTR_ERR(ac); + + spi_set_drvdata(spi, ac); + + return 0; +} + +static int adxl34x_spi_remove(struct spi_device *spi) +{ + struct adxl34x *ac = spi_get_drvdata(spi); + + return adxl34x_remove(ac); +} + +#ifdef CONFIG_PM_SLEEP +static int adxl34x_spi_suspend(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct adxl34x *ac = spi_get_drvdata(spi); + + adxl34x_suspend(ac); + + return 0; +} + +static int adxl34x_spi_resume(struct device *dev) +{ + struct spi_device *spi = to_spi_device(dev); + struct adxl34x *ac = spi_get_drvdata(spi); + + adxl34x_resume(ac); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(adxl34x_spi_pm, adxl34x_spi_suspend, + adxl34x_spi_resume); + +static struct spi_driver adxl34x_driver = { + .driver = { + .name = "adxl34x", + .owner = THIS_MODULE, + .pm = &adxl34x_spi_pm, + }, + .probe = adxl34x_spi_probe, + .remove = adxl34x_spi_remove, +}; + +module_spi_driver(adxl34x_driver); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("ADXL345/346 Three-Axis Digital Accelerometer SPI Bus Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/adxl34x.c b/drivers/input/misc/adxl34x.c new file mode 100644 index 00000000..0735de3a --- /dev/null +++ b/drivers/input/misc/adxl34x.c @@ -0,0 +1,914 @@ +/* + * ADXL345/346 Three-Axis Digital Accelerometers + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc. + * Licensed under the GPL-2 or later. + */ + +#include <linux/device.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/workqueue.h> +#include <linux/input/adxl34x.h> +#include <linux/module.h> + +#include "adxl34x.h" + +/* ADXL345/6 Register Map */ +#define DEVID 0x00 /* R Device ID */ +#define THRESH_TAP 0x1D /* R/W Tap threshold */ +#define OFSX 0x1E /* R/W X-axis offset */ +#define OFSY 0x1F /* R/W Y-axis offset */ +#define OFSZ 0x20 /* R/W Z-axis offset */ +#define DUR 0x21 /* R/W Tap duration */ +#define LATENT 0x22 /* R/W Tap latency */ +#define WINDOW 0x23 /* R/W Tap window */ +#define THRESH_ACT 0x24 /* R/W Activity threshold */ +#define THRESH_INACT 0x25 /* R/W Inactivity threshold */ +#define TIME_INACT 0x26 /* R/W Inactivity time */ +#define ACT_INACT_CTL 0x27 /* R/W Axis enable control for activity and */ + /* inactivity detection */ +#define THRESH_FF 0x28 /* R/W Free-fall threshold */ +#define TIME_FF 0x29 /* R/W Free-fall time */ +#define TAP_AXES 0x2A /* R/W Axis control for tap/double tap */ +#define ACT_TAP_STATUS 0x2B /* R Source of tap/double tap */ +#define BW_RATE 0x2C /* R/W Data rate and power mode control */ +#define POWER_CTL 0x2D /* R/W Power saving features control */ +#define INT_ENABLE 0x2E /* R/W Interrupt enable control */ +#define INT_MAP 0x2F /* R/W Interrupt mapping control */ +#define INT_SOURCE 0x30 /* R Source of interrupts */ +#define DATA_FORMAT 0x31 /* R/W Data format control */ +#define DATAX0 0x32 /* R X-Axis Data 0 */ +#define DATAX1 0x33 /* R X-Axis Data 1 */ +#define DATAY0 0x34 /* R Y-Axis Data 0 */ +#define DATAY1 0x35 /* R Y-Axis Data 1 */ +#define DATAZ0 0x36 /* R Z-Axis Data 0 */ +#define DATAZ1 0x37 /* R Z-Axis Data 1 */ +#define FIFO_CTL 0x38 /* R/W FIFO control */ +#define FIFO_STATUS 0x39 /* R FIFO status */ +#define TAP_SIGN 0x3A /* R Sign and source for tap/double tap */ +/* Orientation ADXL346 only */ +#define ORIENT_CONF 0x3B /* R/W Orientation configuration */ +#define ORIENT 0x3C /* R Orientation status */ + +/* DEVIDs */ +#define ID_ADXL345 0xE5 +#define ID_ADXL346 0xE6 + +/* INT_ENABLE/INT_MAP/INT_SOURCE Bits */ +#define DATA_READY (1 << 7) +#define SINGLE_TAP (1 << 6) +#define DOUBLE_TAP (1 << 5) +#define ACTIVITY (1 << 4) +#define INACTIVITY (1 << 3) +#define FREE_FALL (1 << 2) +#define WATERMARK (1 << 1) +#define OVERRUN (1 << 0) + +/* ACT_INACT_CONTROL Bits */ +#define ACT_ACDC (1 << 7) +#define ACT_X_EN (1 << 6) +#define ACT_Y_EN (1 << 5) +#define ACT_Z_EN (1 << 4) +#define INACT_ACDC (1 << 3) +#define INACT_X_EN (1 << 2) +#define INACT_Y_EN (1 << 1) +#define INACT_Z_EN (1 << 0) + +/* TAP_AXES Bits */ +#define SUPPRESS (1 << 3) +#define TAP_X_EN (1 << 2) +#define TAP_Y_EN (1 << 1) +#define TAP_Z_EN (1 << 0) + +/* ACT_TAP_STATUS Bits */ +#define ACT_X_SRC (1 << 6) +#define ACT_Y_SRC (1 << 5) +#define ACT_Z_SRC (1 << 4) +#define ASLEEP (1 << 3) +#define TAP_X_SRC (1 << 2) +#define TAP_Y_SRC (1 << 1) +#define TAP_Z_SRC (1 << 0) + +/* BW_RATE Bits */ +#define LOW_POWER (1 << 4) +#define RATE(x) ((x) & 0xF) + +/* POWER_CTL Bits */ +#define PCTL_LINK (1 << 5) +#define PCTL_AUTO_SLEEP (1 << 4) +#define PCTL_MEASURE (1 << 3) +#define PCTL_SLEEP (1 << 2) +#define PCTL_WAKEUP(x) ((x) & 0x3) + +/* DATA_FORMAT Bits */ +#define SELF_TEST (1 << 7) +#define SPI (1 << 6) +#define INT_INVERT (1 << 5) +#define FULL_RES (1 << 3) +#define JUSTIFY (1 << 2) +#define RANGE(x) ((x) & 0x3) +#define RANGE_PM_2g 0 +#define RANGE_PM_4g 1 +#define RANGE_PM_8g 2 +#define RANGE_PM_16g 3 + +/* + * Maximum value our axis may get in full res mode for the input device + * (signed 13 bits) + */ +#define ADXL_FULLRES_MAX_VAL 4096 + +/* + * Maximum value our axis may get in fixed res mode for the input device + * (signed 10 bits) + */ +#define ADXL_FIXEDRES_MAX_VAL 512 + +/* FIFO_CTL Bits */ +#define FIFO_MODE(x) (((x) & 0x3) << 6) +#define FIFO_BYPASS 0 +#define FIFO_FIFO 1 +#define FIFO_STREAM 2 +#define FIFO_TRIGGER 3 +#define TRIGGER (1 << 5) +#define SAMPLES(x) ((x) & 0x1F) + +/* FIFO_STATUS Bits */ +#define FIFO_TRIG (1 << 7) +#define ENTRIES(x) ((x) & 0x3F) + +/* TAP_SIGN Bits ADXL346 only */ +#define XSIGN (1 << 6) +#define YSIGN (1 << 5) +#define ZSIGN (1 << 4) +#define XTAP (1 << 3) +#define YTAP (1 << 2) +#define ZTAP (1 << 1) + +/* ORIENT_CONF ADXL346 only */ +#define ORIENT_DEADZONE(x) (((x) & 0x7) << 4) +#define ORIENT_DIVISOR(x) ((x) & 0x7) + +/* ORIENT ADXL346 only */ +#define ADXL346_2D_VALID (1 << 6) +#define ADXL346_2D_ORIENT(x) (((x) & 0x3) >> 4) +#define ADXL346_3D_VALID (1 << 3) +#define ADXL346_3D_ORIENT(x) ((x) & 0x7) +#define ADXL346_2D_PORTRAIT_POS 0 /* +X */ +#define ADXL346_2D_PORTRAIT_NEG 1 /* -X */ +#define ADXL346_2D_LANDSCAPE_POS 2 /* +Y */ +#define ADXL346_2D_LANDSCAPE_NEG 3 /* -Y */ + +#define ADXL346_3D_FRONT 3 /* +X */ +#define ADXL346_3D_BACK 4 /* -X */ +#define ADXL346_3D_RIGHT 2 /* +Y */ +#define ADXL346_3D_LEFT 5 /* -Y */ +#define ADXL346_3D_TOP 1 /* +Z */ +#define ADXL346_3D_BOTTOM 6 /* -Z */ + +#undef ADXL_DEBUG + +#define ADXL_X_AXIS 0 +#define ADXL_Y_AXIS 1 +#define ADXL_Z_AXIS 2 + +#define AC_READ(ac, reg) ((ac)->bops->read((ac)->dev, reg)) +#define AC_WRITE(ac, reg, val) ((ac)->bops->write((ac)->dev, reg, val)) + +struct axis_triple { + int x; + int y; + int z; +}; + +struct adxl34x { + struct device *dev; + struct input_dev *input; + struct mutex mutex; /* reentrant protection for struct */ + struct adxl34x_platform_data pdata; + struct axis_triple swcal; + struct axis_triple hwcal; + struct axis_triple saved; + char phys[32]; + unsigned orient2d_saved; + unsigned orient3d_saved; + bool disabled; /* P: mutex */ + bool opened; /* P: mutex */ + bool suspended; /* P: mutex */ + bool fifo_delay; + int irq; + unsigned model; + unsigned int_mask; + + const struct adxl34x_bus_ops *bops; +}; + +static const struct adxl34x_platform_data adxl34x_default_init = { + .tap_threshold = 35, + .tap_duration = 3, + .tap_latency = 20, + .tap_window = 20, + .tap_axis_control = ADXL_TAP_X_EN | ADXL_TAP_Y_EN | ADXL_TAP_Z_EN, + .act_axis_control = 0xFF, + .activity_threshold = 6, + .inactivity_threshold = 4, + .inactivity_time = 3, + .free_fall_threshold = 8, + .free_fall_time = 0x20, + .data_rate = 8, + .data_range = ADXL_FULL_RES, + + .ev_type = EV_ABS, + .ev_code_x = ABS_X, /* EV_REL */ + .ev_code_y = ABS_Y, /* EV_REL */ + .ev_code_z = ABS_Z, /* EV_REL */ + + .ev_code_tap = {BTN_TOUCH, BTN_TOUCH, BTN_TOUCH}, /* EV_KEY {x,y,z} */ + .power_mode = ADXL_AUTO_SLEEP | ADXL_LINK, + .fifo_mode = ADXL_FIFO_STREAM, + .watermark = 0, +}; + +static void adxl34x_get_triple(struct adxl34x *ac, struct axis_triple *axis) +{ + short buf[3]; + + ac->bops->read_block(ac->dev, DATAX0, DATAZ1 - DATAX0 + 1, buf); + + mutex_lock(&ac->mutex); + ac->saved.x = (s16) le16_to_cpu(buf[0]); + axis->x = ac->saved.x; + + ac->saved.y = (s16) le16_to_cpu(buf[1]); + axis->y = ac->saved.y; + + ac->saved.z = (s16) le16_to_cpu(buf[2]); + axis->z = ac->saved.z; + mutex_unlock(&ac->mutex); +} + +static void adxl34x_service_ev_fifo(struct adxl34x *ac) +{ + struct adxl34x_platform_data *pdata = &ac->pdata; + struct axis_triple axis; + + adxl34x_get_triple(ac, &axis); + + input_event(ac->input, pdata->ev_type, pdata->ev_code_x, + axis.x - ac->swcal.x); + input_event(ac->input, pdata->ev_type, pdata->ev_code_y, + axis.y - ac->swcal.y); + input_event(ac->input, pdata->ev_type, pdata->ev_code_z, + axis.z - ac->swcal.z); +} + +static void adxl34x_report_key_single(struct input_dev *input, int key) +{ + input_report_key(input, key, true); + input_sync(input); + input_report_key(input, key, false); +} + +static void adxl34x_send_key_events(struct adxl34x *ac, + struct adxl34x_platform_data *pdata, int status, int press) +{ + int i; + + for (i = ADXL_X_AXIS; i <= ADXL_Z_AXIS; i++) { + if (status & (1 << (ADXL_Z_AXIS - i))) + input_report_key(ac->input, + pdata->ev_code_tap[i], press); + } +} + +static void adxl34x_do_tap(struct adxl34x *ac, + struct adxl34x_platform_data *pdata, int status) +{ + adxl34x_send_key_events(ac, pdata, status, true); + input_sync(ac->input); + adxl34x_send_key_events(ac, pdata, status, false); +} + +static irqreturn_t adxl34x_irq(int irq, void *handle) +{ + struct adxl34x *ac = handle; + struct adxl34x_platform_data *pdata = &ac->pdata; + int int_stat, tap_stat, samples, orient, orient_code; + + /* + * ACT_TAP_STATUS should be read before clearing the interrupt + * Avoid reading ACT_TAP_STATUS in case TAP detection is disabled + */ + + if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN)) + tap_stat = AC_READ(ac, ACT_TAP_STATUS); + else + tap_stat = 0; + + int_stat = AC_READ(ac, INT_SOURCE); + + if (int_stat & FREE_FALL) + adxl34x_report_key_single(ac->input, pdata->ev_code_ff); + + if (int_stat & OVERRUN) + dev_dbg(ac->dev, "OVERRUN\n"); + + if (int_stat & (SINGLE_TAP | DOUBLE_TAP)) { + adxl34x_do_tap(ac, pdata, tap_stat); + + if (int_stat & DOUBLE_TAP) + adxl34x_do_tap(ac, pdata, tap_stat); + } + + if (pdata->ev_code_act_inactivity) { + if (int_stat & ACTIVITY) + input_report_key(ac->input, + pdata->ev_code_act_inactivity, 1); + if (int_stat & INACTIVITY) + input_report_key(ac->input, + pdata->ev_code_act_inactivity, 0); + } + + /* + * ORIENTATION SENSING ADXL346 only + */ + if (pdata->orientation_enable) { + orient = AC_READ(ac, ORIENT); + if ((pdata->orientation_enable & ADXL_EN_ORIENTATION_2D) && + (orient & ADXL346_2D_VALID)) { + + orient_code = ADXL346_2D_ORIENT(orient); + /* Report orientation only when it changes */ + if (ac->orient2d_saved != orient_code) { + ac->orient2d_saved = orient_code; + adxl34x_report_key_single(ac->input, + pdata->ev_codes_orient_2d[orient_code]); + } + } + + if ((pdata->orientation_enable & ADXL_EN_ORIENTATION_3D) && + (orient & ADXL346_3D_VALID)) { + + orient_code = ADXL346_3D_ORIENT(orient) - 1; + /* Report orientation only when it changes */ + if (ac->orient3d_saved != orient_code) { + ac->orient3d_saved = orient_code; + adxl34x_report_key_single(ac->input, + pdata->ev_codes_orient_3d[orient_code]); + } + } + } + + if (int_stat & (DATA_READY | WATERMARK)) { + + if (pdata->fifo_mode) + samples = ENTRIES(AC_READ(ac, FIFO_STATUS)) + 1; + else + samples = 1; + + for (; samples > 0; samples--) { + adxl34x_service_ev_fifo(ac); + /* + * To ensure that the FIFO has + * completely popped, there must be at least 5 us between + * the end of reading the data registers, signified by the + * transition to register 0x38 from 0x37 or the CS pin + * going high, and the start of new reads of the FIFO or + * reading the FIFO_STATUS register. For SPI operation at + * 1.5 MHz or lower, the register addressing portion of the + * transmission is sufficient delay to ensure the FIFO has + * completely popped. It is necessary for SPI operation + * greater than 1.5 MHz to de-assert the CS pin to ensure a + * total of 5 us, which is at most 3.4 us at 5 MHz + * operation. + */ + if (ac->fifo_delay && (samples > 1)) + udelay(3); + } + } + + input_sync(ac->input); + + return IRQ_HANDLED; +} + +static void __adxl34x_disable(struct adxl34x *ac) +{ + /* + * A '0' places the ADXL34x into standby mode + * with minimum power consumption. + */ + AC_WRITE(ac, POWER_CTL, 0); +} + +static void __adxl34x_enable(struct adxl34x *ac) +{ + AC_WRITE(ac, POWER_CTL, ac->pdata.power_mode | PCTL_MEASURE); +} + +void adxl34x_suspend(struct adxl34x *ac) +{ + mutex_lock(&ac->mutex); + + if (!ac->suspended && !ac->disabled && ac->opened) + __adxl34x_disable(ac); + + ac->suspended = true; + + mutex_unlock(&ac->mutex); +} +EXPORT_SYMBOL_GPL(adxl34x_suspend); + +void adxl34x_resume(struct adxl34x *ac) +{ + mutex_lock(&ac->mutex); + + if (ac->suspended && !ac->disabled && ac->opened) + __adxl34x_enable(ac); + + ac->suspended = false; + + mutex_unlock(&ac->mutex); +} +EXPORT_SYMBOL_GPL(adxl34x_resume); + +static ssize_t adxl34x_disable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", ac->disabled); +} + +static ssize_t adxl34x_disable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + unsigned int val; + int error; + + error = kstrtouint(buf, 10, &val); + if (error) + return error; + + mutex_lock(&ac->mutex); + + if (!ac->suspended && ac->opened) { + if (val) { + if (!ac->disabled) + __adxl34x_disable(ac); + } else { + if (ac->disabled) + __adxl34x_enable(ac); + } + } + + ac->disabled = !!val; + + mutex_unlock(&ac->mutex); + + return count; +} + +static DEVICE_ATTR(disable, 0664, adxl34x_disable_show, adxl34x_disable_store); + +static ssize_t adxl34x_calibrate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + ssize_t count; + + mutex_lock(&ac->mutex); + count = sprintf(buf, "%d,%d,%d\n", + ac->hwcal.x * 4 + ac->swcal.x, + ac->hwcal.y * 4 + ac->swcal.y, + ac->hwcal.z * 4 + ac->swcal.z); + mutex_unlock(&ac->mutex); + + return count; +} + +static ssize_t adxl34x_calibrate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + + /* + * Hardware offset calibration has a resolution of 15.6 mg/LSB. + * We use HW calibration and handle the remaining bits in SW. (4mg/LSB) + */ + + mutex_lock(&ac->mutex); + ac->hwcal.x -= (ac->saved.x / 4); + ac->swcal.x = ac->saved.x % 4; + + ac->hwcal.y -= (ac->saved.y / 4); + ac->swcal.y = ac->saved.y % 4; + + ac->hwcal.z -= (ac->saved.z / 4); + ac->swcal.z = ac->saved.z % 4; + + AC_WRITE(ac, OFSX, (s8) ac->hwcal.x); + AC_WRITE(ac, OFSY, (s8) ac->hwcal.y); + AC_WRITE(ac, OFSZ, (s8) ac->hwcal.z); + mutex_unlock(&ac->mutex); + + return count; +} + +static DEVICE_ATTR(calibrate, 0664, + adxl34x_calibrate_show, adxl34x_calibrate_store); + +static ssize_t adxl34x_rate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", RATE(ac->pdata.data_rate)); +} + +static ssize_t adxl34x_rate_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + unsigned char val; + int error; + + error = kstrtou8(buf, 10, &val); + if (error) + return error; + + mutex_lock(&ac->mutex); + + ac->pdata.data_rate = RATE(val); + AC_WRITE(ac, BW_RATE, + ac->pdata.data_rate | + (ac->pdata.low_power_mode ? LOW_POWER : 0)); + + mutex_unlock(&ac->mutex); + + return count; +} + +static DEVICE_ATTR(rate, 0664, adxl34x_rate_show, adxl34x_rate_store); + +static ssize_t adxl34x_autosleep_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + + return sprintf(buf, "%u\n", + ac->pdata.power_mode & (PCTL_AUTO_SLEEP | PCTL_LINK) ? 1 : 0); +} + +static ssize_t adxl34x_autosleep_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + unsigned int val; + int error; + + error = kstrtouint(buf, 10, &val); + if (error) + return error; + + mutex_lock(&ac->mutex); + + if (val) + ac->pdata.power_mode |= (PCTL_AUTO_SLEEP | PCTL_LINK); + else + ac->pdata.power_mode &= ~(PCTL_AUTO_SLEEP | PCTL_LINK); + + if (!ac->disabled && !ac->suspended && ac->opened) + AC_WRITE(ac, POWER_CTL, ac->pdata.power_mode | PCTL_MEASURE); + + mutex_unlock(&ac->mutex); + + return count; +} + +static DEVICE_ATTR(autosleep, 0664, + adxl34x_autosleep_show, adxl34x_autosleep_store); + +static ssize_t adxl34x_position_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + ssize_t count; + + mutex_lock(&ac->mutex); + count = sprintf(buf, "(%d, %d, %d)\n", + ac->saved.x, ac->saved.y, ac->saved.z); + mutex_unlock(&ac->mutex); + + return count; +} + +static DEVICE_ATTR(position, S_IRUGO, adxl34x_position_show, NULL); + +#ifdef ADXL_DEBUG +static ssize_t adxl34x_write_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct adxl34x *ac = dev_get_drvdata(dev); + unsigned int val; + int error; + + /* + * This allows basic ADXL register write access for debug purposes. + */ + error = kstrtouint(buf, 16, &val); + if (error) + return error; + + mutex_lock(&ac->mutex); + AC_WRITE(ac, val >> 8, val & 0xFF); + mutex_unlock(&ac->mutex); + + return count; +} + +static DEVICE_ATTR(write, 0664, NULL, adxl34x_write_store); +#endif + +static struct attribute *adxl34x_attributes[] = { + &dev_attr_disable.attr, + &dev_attr_calibrate.attr, + &dev_attr_rate.attr, + &dev_attr_autosleep.attr, + &dev_attr_position.attr, +#ifdef ADXL_DEBUG + &dev_attr_write.attr, +#endif + NULL +}; + +static const struct attribute_group adxl34x_attr_group = { + .attrs = adxl34x_attributes, +}; + +static int adxl34x_input_open(struct input_dev *input) +{ + struct adxl34x *ac = input_get_drvdata(input); + + mutex_lock(&ac->mutex); + + if (!ac->suspended && !ac->disabled) + __adxl34x_enable(ac); + + ac->opened = true; + + mutex_unlock(&ac->mutex); + + return 0; +} + +static void adxl34x_input_close(struct input_dev *input) +{ + struct adxl34x *ac = input_get_drvdata(input); + + mutex_lock(&ac->mutex); + + if (!ac->suspended && !ac->disabled) + __adxl34x_disable(ac); + + ac->opened = false; + + mutex_unlock(&ac->mutex); +} + +struct adxl34x *adxl34x_probe(struct device *dev, int irq, + bool fifo_delay_default, + const struct adxl34x_bus_ops *bops) +{ + struct adxl34x *ac; + struct input_dev *input_dev; + const struct adxl34x_platform_data *pdata; + int err, range, i; + unsigned char revid; + + if (!irq) { + dev_err(dev, "no IRQ?\n"); + err = -ENODEV; + goto err_out; + } + + ac = kzalloc(sizeof(*ac), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!ac || !input_dev) { + err = -ENOMEM; + goto err_free_mem; + } + + ac->fifo_delay = fifo_delay_default; + + pdata = dev->platform_data; + if (!pdata) { + dev_dbg(dev, + "No platform data: Using default initialization\n"); + pdata = &adxl34x_default_init; + } + + ac->pdata = *pdata; + pdata = &ac->pdata; + + ac->input = input_dev; + ac->dev = dev; + ac->irq = irq; + ac->bops = bops; + + mutex_init(&ac->mutex); + + input_dev->name = "ADXL34x accelerometer"; + revid = AC_READ(ac, DEVID); + + switch (revid) { + case ID_ADXL345: + ac->model = 345; + break; + case ID_ADXL346: + ac->model = 346; + break; + default: + dev_err(dev, "Failed to probe %s\n", input_dev->name); + err = -ENODEV; + goto err_free_mem; + } + + snprintf(ac->phys, sizeof(ac->phys), "%s/input0", dev_name(dev)); + + input_dev->phys = ac->phys; + input_dev->dev.parent = dev; + input_dev->id.product = ac->model; + input_dev->id.bustype = bops->bustype; + input_dev->open = adxl34x_input_open; + input_dev->close = adxl34x_input_close; + + input_set_drvdata(input_dev, ac); + + __set_bit(ac->pdata.ev_type, input_dev->evbit); + + if (ac->pdata.ev_type == EV_REL) { + __set_bit(REL_X, input_dev->relbit); + __set_bit(REL_Y, input_dev->relbit); + __set_bit(REL_Z, input_dev->relbit); + } else { + /* EV_ABS */ + __set_bit(ABS_X, input_dev->absbit); + __set_bit(ABS_Y, input_dev->absbit); + __set_bit(ABS_Z, input_dev->absbit); + + if (pdata->data_range & FULL_RES) + range = ADXL_FULLRES_MAX_VAL; /* Signed 13-bit */ + else + range = ADXL_FIXEDRES_MAX_VAL; /* Signed 10-bit */ + + input_set_abs_params(input_dev, ABS_X, -range, range, 3, 3); + input_set_abs_params(input_dev, ABS_Y, -range, range, 3, 3); + input_set_abs_params(input_dev, ABS_Z, -range, range, 3, 3); + } + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit); + __set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit); + __set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit); + + if (pdata->ev_code_ff) { + ac->int_mask = FREE_FALL; + __set_bit(pdata->ev_code_ff, input_dev->keybit); + } + + if (pdata->ev_code_act_inactivity) + __set_bit(pdata->ev_code_act_inactivity, input_dev->keybit); + + ac->int_mask |= ACTIVITY | INACTIVITY; + + if (pdata->watermark) { + ac->int_mask |= WATERMARK; + if (!FIFO_MODE(pdata->fifo_mode)) + ac->pdata.fifo_mode |= FIFO_STREAM; + } else { + ac->int_mask |= DATA_READY; + } + + if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN)) + ac->int_mask |= SINGLE_TAP | DOUBLE_TAP; + + if (FIFO_MODE(pdata->fifo_mode) == FIFO_BYPASS) + ac->fifo_delay = false; + + AC_WRITE(ac, POWER_CTL, 0); + + err = request_threaded_irq(ac->irq, NULL, adxl34x_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + dev_name(dev), ac); + if (err) { + dev_err(dev, "irq %d busy?\n", ac->irq); + goto err_free_mem; + } + + err = sysfs_create_group(&dev->kobj, &adxl34x_attr_group); + if (err) + goto err_free_irq; + + err = input_register_device(input_dev); + if (err) + goto err_remove_attr; + + AC_WRITE(ac, OFSX, pdata->x_axis_offset); + ac->hwcal.x = pdata->x_axis_offset; + AC_WRITE(ac, OFSY, pdata->y_axis_offset); + ac->hwcal.y = pdata->y_axis_offset; + AC_WRITE(ac, OFSZ, pdata->z_axis_offset); + ac->hwcal.z = pdata->z_axis_offset; + AC_WRITE(ac, THRESH_TAP, pdata->tap_threshold); + AC_WRITE(ac, DUR, pdata->tap_duration); + AC_WRITE(ac, LATENT, pdata->tap_latency); + AC_WRITE(ac, WINDOW, pdata->tap_window); + AC_WRITE(ac, THRESH_ACT, pdata->activity_threshold); + AC_WRITE(ac, THRESH_INACT, pdata->inactivity_threshold); + AC_WRITE(ac, TIME_INACT, pdata->inactivity_time); + AC_WRITE(ac, THRESH_FF, pdata->free_fall_threshold); + AC_WRITE(ac, TIME_FF, pdata->free_fall_time); + AC_WRITE(ac, TAP_AXES, pdata->tap_axis_control); + AC_WRITE(ac, ACT_INACT_CTL, pdata->act_axis_control); + AC_WRITE(ac, BW_RATE, RATE(ac->pdata.data_rate) | + (pdata->low_power_mode ? LOW_POWER : 0)); + AC_WRITE(ac, DATA_FORMAT, pdata->data_range); + AC_WRITE(ac, FIFO_CTL, FIFO_MODE(pdata->fifo_mode) | + SAMPLES(pdata->watermark)); + + if (pdata->use_int2) { + /* Map all INTs to INT2 */ + AC_WRITE(ac, INT_MAP, ac->int_mask | OVERRUN); + } else { + /* Map all INTs to INT1 */ + AC_WRITE(ac, INT_MAP, 0); + } + + if (ac->model == 346 && ac->pdata.orientation_enable) { + AC_WRITE(ac, ORIENT_CONF, + ORIENT_DEADZONE(ac->pdata.deadzone_angle) | + ORIENT_DIVISOR(ac->pdata.divisor_length)); + + ac->orient2d_saved = 1234; + ac->orient3d_saved = 1234; + + if (pdata->orientation_enable & ADXL_EN_ORIENTATION_3D) + for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_3d); i++) + __set_bit(pdata->ev_codes_orient_3d[i], + input_dev->keybit); + + if (pdata->orientation_enable & ADXL_EN_ORIENTATION_2D) + for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_2d); i++) + __set_bit(pdata->ev_codes_orient_2d[i], + input_dev->keybit); + } else { + ac->pdata.orientation_enable = 0; + } + + AC_WRITE(ac, INT_ENABLE, ac->int_mask | OVERRUN); + + ac->pdata.power_mode &= (PCTL_AUTO_SLEEP | PCTL_LINK); + + return ac; + + err_remove_attr: + sysfs_remove_group(&dev->kobj, &adxl34x_attr_group); + err_free_irq: + free_irq(ac->irq, ac); + err_free_mem: + input_free_device(input_dev); + kfree(ac); + err_out: + return ERR_PTR(err); +} +EXPORT_SYMBOL_GPL(adxl34x_probe); + +int adxl34x_remove(struct adxl34x *ac) +{ + sysfs_remove_group(&ac->dev->kobj, &adxl34x_attr_group); + free_irq(ac->irq, ac); + input_unregister_device(ac->input); + dev_dbg(ac->dev, "unregistered accelerometer\n"); + kfree(ac); + + return 0; +} +EXPORT_SYMBOL_GPL(adxl34x_remove); + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("ADXL345/346 Three-Axis Digital Accelerometer Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/adxl34x.h b/drivers/input/misc/adxl34x.h new file mode 100644 index 00000000..bbbc80fd --- /dev/null +++ b/drivers/input/misc/adxl34x.h @@ -0,0 +1,30 @@ +/* + * ADXL345/346 Three-Axis Digital Accelerometers (I2C/SPI Interface) + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Copyright (C) 2009 Michael Hennerich, Analog Devices Inc. + * Licensed under the GPL-2 or later. + */ + +#ifndef _ADXL34X_H_ +#define _ADXL34X_H_ + +struct device; +struct adxl34x; + +struct adxl34x_bus_ops { + u16 bustype; + int (*read)(struct device *, unsigned char); + int (*read_block)(struct device *, unsigned char, int, void *); + int (*write)(struct device *, unsigned char, unsigned char); +}; + +void adxl34x_suspend(struct adxl34x *ac); +void adxl34x_resume(struct adxl34x *ac); +struct adxl34x *adxl34x_probe(struct device *dev, int irq, + bool fifo_delay_default, + const struct adxl34x_bus_ops *bops); +int adxl34x_remove(struct adxl34x *ac); + +#endif diff --git a/drivers/input/misc/al3006_pls.c b/drivers/input/misc/al3006_pls.c new file mode 100644 index 00000000..8d72a055 --- /dev/null +++ b/drivers/input/misc/al3006_pls.c @@ -0,0 +1,838 @@ +/* + * File: al3006_pls.c + * Based on: + * Author: Yunlong Wang <Yunlong.Wang @spreadtrum.com> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/input.h> +#include <linux/gpio.h> +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif +#include <linux/fs.h> +#include <asm/uaccess.h> +#include <linux/i2c/al3006_pls.h> +#include <linux/miscdevice.h> + +static struct i2c_client *this_client; +static atomic_t p_flag; +static atomic_t l_flag; +static int al3006_pls_opened=0; + +/*0: sleep out; 1: sleep in*/ +static unsigned char suspend_flag=0; + + +static ssize_t al3006_pls_show_suspend(struct device* cd,struct device_attribute *attr, char* buf); +static ssize_t al3006_pls_store_suspend(struct device* cd, struct device_attribute *attr,const char* buf, size_t len); +static ssize_t al3006_pls_show_version(struct device* cd,struct device_attribute *attr, char* buf); +#ifdef CONFIG_HAS_EARLYSUSPEND +static void al3006_pls_early_suspend(struct early_suspend *handler); +static void al3006_pls_early_resume(struct early_suspend *handler); +#endif +static int al3006_pls_write_data(unsigned char addr, unsigned char data); +static int al3006_pls_read_data(unsigned char addr, unsigned char *data); +static int al3006_pls_enable(SENSOR_TYPE type); +static int al3006_pls_disable(SENSOR_TYPE type); +static void al3006_pls_report_init(void); + +static int al3006_pls_adc2lux[64]={ + 1, 1, 1, 2, 2, 2, 3, 4, + 4, 5, 6, 7, 9, 11, 13, 16, + 19, 22, 27, 32, 39, 46, 56, 67, + 80, 96, 116, 139, 167, 200, 240, 289, + 346, 416, 499, 599, 720, 864, 1037, 1245, + 1495, 1795, 2154, 2586, 3105, 3728, 4475, 5372, + 6449, 7743, 9295, 11159, 13396, 16082, 19307, 23178, + 27862, 33405, 40103, 48144, 57797, 69386, 83298, 100000 +}; + + +static DEVICE_ATTR(suspend, S_IRUGO | S_IWUSR, al3006_pls_show_suspend, al3006_pls_store_suspend); +static DEVICE_ATTR(version, S_IRUGO | S_IWUSR, al3006_pls_show_version, NULL); + +static ssize_t al3006_pls_show_suspend(struct device* cd, + struct device_attribute *attr, char* buf) +{ + ssize_t ret = 0; + + if(suspend_flag==1) + sprintf(buf, "AL3006 Resume\n"); + else + sprintf(buf, "AL3006 Suspend\n"); + + ret = strlen(buf) + 1; + + return ret; +} + +static ssize_t al3006_pls_store_suspend(struct device* cd, struct device_attribute *attr, + const char* buf, size_t len) +{ + unsigned long on_off = simple_strtoul(buf, NULL, 10); + suspend_flag = on_off; + + if(on_off==1) + { + printk(KERN_INFO "AL3006 Entry Resume\n"); + al3006_pls_enable(AL3006_PLS_BOTH); + } + else + { + printk(KERN_INFO "AL3006 Entry Suspend\n"); + al3006_pls_disable(AL3006_PLS_BOTH); + } + + return len; +} + +static ssize_t al3006_pls_show_version(struct device* cd, + struct device_attribute *attr, char* buf) +{ + ssize_t ret = 0; + + sprintf(buf, "AL3006"); + ret = strlen(buf) + 1; + + return ret; +} + +static int al3006_pls_create_sysfs(struct i2c_client *client) +{ + int err; + struct device *dev = &(client->dev); + + PLS_DBG("%s", __func__); + + err = device_create_file(dev, &dev_attr_suspend); + err = device_create_file(dev, &dev_attr_version); + + return err; +} + +/******************************************************************************* +* Function : al3006_pls_enable +* Description : type: proximity / light +*******************************************************************************/ +static int al3006_pls_enable(SENSOR_TYPE type) +{ + unsigned char config; + + al3006_pls_read_data(AL3006_PLS_REG_CONFIG, &config); + + PLS_DBG("%s: type=%d; config=0x%x", __func__, type, config); + + switch(type) { + case AL3006_PLS_ALPS: + if(config == AL3006_PLS_PXY_ACTIVE) + type = AL3006_PLS_BOTH; + break; + case AL3006_PLS_PXY: + if(config == AL3006_PLS_ALPS_ACTIVE) + type = AL3006_PLS_BOTH; + break; + case AL3006_PLS_BOTH: + break; + } + + PLS_DBG("%s: after type=%d\n",__func__, type); + + switch(type) { + case AL3006_PLS_ALPS: + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_ALPS_ACTIVE); + break; + case AL3006_PLS_PXY: + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_PXY_ACTIVE); + al3006_pls_report_init(); + break; + case AL3006_PLS_BOTH: + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_ACTIVE); + al3006_pls_report_init(); + break; + default: + break; + } + + return 0; +} + +/******************************************************************************* +* Function : al3006_pls_disable +* Description : type: proximity / light +*******************************************************************************/ +static int al3006_pls_disable(SENSOR_TYPE type) +{ + int pxy_flag, apls_flag; + + PLS_DBG("%s: type=%d", __func__, type); + + switch(type) { + case AL3006_PLS_ALPS: + pxy_flag = atomic_read(&p_flag); + /*check proximiy sensor status*/ + if(pxy_flag == 0) { + PLS_DBG("%s: Disable both sensor for pxy_flag=%d", __func__, pxy_flag); + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_DEACTIVE); + } else { + PLS_DBG("%s: Only Proximity Sensor alive", __func__); + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_PXY_ACTIVE); + } + + break; + case AL3006_PLS_PXY: + apls_flag = atomic_read(&l_flag); + /*check light sensor status*/ + if(apls_flag == 0) { + PLS_DBG("%s: Disable both sensor for apls_flag=%d", __func__, apls_flag); + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_DEACTIVE); + } else { + PLS_DBG("%s: Only Light Sensor alive", __func__); + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_ALPS_ACTIVE); + } + break; + case AL3006_PLS_BOTH: + PLS_DBG("%s: Disable both sensors", __func__); + al3006_pls_write_data(AL3006_PLS_REG_CONFIG, AL3006_PLS_BOTH_DEACTIVE); + break; + default: + break; + } + + return 0; +} + +static int al3006_pls_open(struct inode *inode, struct file *file) +{ + PLS_DBG("%s\n", __func__); + if (al3006_pls_opened) + return -EBUSY; + al3006_pls_opened = 1; + return 0; +} + +static int al3006_pls_release(struct inode *inode, struct file *file) +{ + PLS_DBG("%s", __func__); + al3006_pls_opened = 0; + return al3006_pls_disable(AL3006_PLS_BOTH); +} + + +/******************************************************************************* +* Function : al3006_pls_ioctl +* Description : ioctl interface to control the sensor +*******************************************************************************/ +static long al3006_pls_ioctl(struct file *file, unsigned int cmd,unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int flag; + unsigned char data; + + PLS_DBG("%s: cmd %d", __func__, _IOC_NR(cmd)); + + /*get ioctl parameter*/ + switch (cmd) { + case LTR_IOCTL_SET_PFLAG: + case LTR_IOCTL_SET_LFLAG: + if (copy_from_user(&flag, argp, sizeof(flag))) { + return -EFAULT; + } + if (flag < 0 || flag > 1) { + return -EINVAL; + } + PLS_DBG("%s: set flag=%d", __func__, flag); + break; + default: + break; + } + + /*handle ioctl*/ + switch (cmd) { + case LTR_IOCTL_GET_PFLAG: + flag = atomic_read(&p_flag); + break; + + case LTR_IOCTL_GET_LFLAG: + flag = atomic_read(&l_flag); + break; + + case LTR_IOCTL_GET_DATA: + break; + + case LTR_IOCTL_SET_PFLAG: + atomic_set(&p_flag, flag); + if(flag==1){ + al3006_pls_enable(AL3006_PLS_PXY); + } + else if(flag==0) { + al3006_pls_disable(AL3006_PLS_PXY); + } + break; + + case LTR_IOCTL_SET_LFLAG: + atomic_set(&l_flag, flag); + if(flag==1){ + al3006_pls_enable(AL3006_PLS_ALPS); + } + else if(flag==0) { + al3006_pls_disable(AL3006_PLS_ALPS); + } + break; + + default: + pr_err("%s: invalid cmd %d\n", __func__, _IOC_NR(cmd)); + return -EINVAL; + } + + /*report ioctl*/ + switch (cmd) { + case LTR_IOCTL_GET_PFLAG: + case LTR_IOCTL_GET_LFLAG: + if (copy_to_user(argp, &flag, sizeof(flag))) { + return -EFAULT; + } + PLS_DBG("%s: get flag=%d", __func__, flag); + break; + + case LTR_IOCTL_GET_DATA: + al3006_pls_read_data(AL3006_PLS_REG_DATA,&data); + if (copy_to_user(argp, &data, sizeof(data))) { + return -EFAULT; + } + PLS_DBG("%s: get data=%d", __func__, flag); + break; + + default: + break; + } + + return 0; + +} + + +static struct file_operations al3006_pls_fops = { + .owner = THIS_MODULE, + .open = al3006_pls_open, + .release = al3006_pls_release, + .unlocked_ioctl = al3006_pls_ioctl, +}; + +static struct miscdevice al3006_pls_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = AL3006_PLS_DEVICE, + .fops = &al3006_pls_fops, +}; + +/******************************************************************************* +* Function : al3006_pls_rx_data +* Description : read data from i2c +* Parameters : buf: content, len: length +*******************************************************************************/ +static int al3006_pls_rx_data(char *buf, int len) +{ + int ret; + + struct i2c_msg msgs[] = { + { + .addr = this_client->addr, + .flags = 0, + .len = 1, + .buf = buf, + }, + { + .addr = this_client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buf, + } + }; + + ret = i2c_transfer(this_client->adapter, msgs, 2); + if (ret < 0) + printk(KERN_ERR "%s i2c read error: %d\n", __func__, ret); + + + return ret; +} + +/******************************************************************************* +* Function : al3006_pls_tx_data +* Description : send data to i2c +* Parameters : buf: content, len: length +*******************************************************************************/ +static int al3006_pls_tx_data(char *buf, int len) +{ + int ret; + + struct i2c_msg msg[] = { + { + .addr = this_client->addr, + .flags = 0, + .len = len, + .buf = buf, + } + }; + + ret = i2c_transfer(this_client->adapter, msg, 1); + if (ret < 0) + printk(KERN_ERR "%s i2c write error: %d\n", __func__, ret); + + return ret; +} + +/******************************************************************************* +* Function : al3006_pls_write_data +* Description : write data to IC +* Parameters : addr: register address, data: register data +*******************************************************************************/ +static int al3006_pls_write_data(unsigned char addr, unsigned char data) +{ + unsigned char buf[2]; + buf[0]=addr; + buf[1]=data; + return al3006_pls_tx_data(buf, 2); +} + +/******************************************************************************* +* Function : al3006_pls_read_data +* Description : read data from IC +* Parameters : addr: register address, data: read data +*******************************************************************************/ +static int al3006_pls_read_data(unsigned char addr, unsigned char *data) +{ + int ret; + unsigned char buf; + + buf=addr; + ret = al3006_pls_rx_data(&buf, 1); + *data = buf; + + return ret;; +} + +#if PLS_DEBUG +static void al3006_pls_reg_dump(void) +{ + unsigned char config,time_ctrl,dls_ctrl,int_status,dps_ctrl,data,dls_win; + al3006_pls_read_data(AL3006_PLS_REG_CONFIG,&config); + al3006_pls_read_data(AL3006_PLS_REG_TIME_CTRL,&time_ctrl); + al3006_pls_read_data(AL3006_PLS_REG_DLS_CTRL,&dls_ctrl); + al3006_pls_read_data(AL3006_PLS_REG_INT_STATUS,&int_status); + al3006_pls_read_data(AL3006_PLS_REG_DPS_CTRL,&dps_ctrl); + al3006_pls_read_data(AL3006_PLS_REG_DATA,&data); + al3006_pls_read_data(AL3006_PLS_REG_DLS_WIN,&dls_win); + + PLS_DBG("%s: config=0x%x,time_ctrl=0x%x,dls_ctrl=0x%x,int_status=0x%x,dps_ctrl=0x%x,data=0x%x,dls_win=0x%x", \ + __func__, config,time_ctrl,dls_ctrl,int_status,dps_ctrl,data,dls_win); +} +#endif + +/******************************************************************************* +* Function : al3006_pls_reg_init +* Description : set al3006 registers +* Parameters : none +* Return : void +*******************************************************************************/ +static int al3006_pls_reg_init(void) +{ + int ret=0; + + /*set window loss*/ + if(al3006_pls_write_data(AL3006_PLS_REG_DLS_WIN,0x0D)<0) { + printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_REG_DLS_WIN); + ret = -1; + } + + /*set time control*/ + if(al3006_pls_write_data(AL3006_PLS_REG_TIME_CTRL,0x1)<0) { + printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_REG_TIME_CTRL); + ret = -1; + } + + /*set alps level*/ +#if defined(AL3006_PLS_ADC_LEVEL64) + al3006_pls_write_data(AL3006_PLS_REG_DLS_CTRL,0xA0); +#elif defined(AL3006_PLS_ADC_LEVEL9) + al3006_pls_write_data(AL3006_PLS_REG_DLS_CTRL,0x40); +#elif defined(AL3006_PLS_ADC_LEVEL5) + al3006_pls_write_data(AL3006_PLS_REG_DLS_CTRL,0x20); +#endif + + /*set dps distance*/ + if(al3006_pls_write_data(AL3006_PLS_REG_DPS_CTRL,0x10)<0) { + printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_REG_DPS_CTRL); + ret = -1; + } + + /*enter power down mode*/ + if(al3006_pls_write_data(AL3006_PLS_REG_CONFIG,AL3006_PLS_BOTH_DEACTIVE)<0) { + printk(KERN_ERR "%s: I2C Write Reg %x Failed\n", __func__, AL3006_PLS_BOTH_DEACTIVE); + ret = -1; + } + +#if PLS_DEBUG + al3006_pls_reg_dump(); +#endif + + return ret; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +/******************************************************************************* +* Function : al3006_pls_early_suspend +* Description : cancel the delayed work and put ts to shutdown mode +* Parameters : handler +* Return : none +*******************************************************************************/ +static void al3006_pls_early_suspend(struct early_suspend *handler) +{ + PLS_DBG("%s\n", __func__); +#if 0 + al3006_pls_disable(AL3006_PLS_BOTH); +#endif +} + +/******************************************************************************* +* Function : al3006_pls_early_resume +* Description : ts re-entry the normal mode and schedule the work, there need to be a litte time + for ts ready +* Parameters : handler +* Return : none +*******************************************************************************/ +static void al3006_pls_early_resume(struct early_suspend *handler) +{ + PLS_DBG("%s\n", __func__); +#if 0 + al3006_pls_enable(AL3006_PLS_BOTH); +#endif +} +#endif + +static void al3006_pls_report_init(void) +{ + al3006_pls_struct *al3006_pls = (al3006_pls_struct *)i2c_get_clientdata(this_client); + PLS_DBG("%s\n",__func__); + /*report far*/ + input_report_abs(al3006_pls->input, ABS_DISTANCE, 1); + input_sync(al3006_pls->input); +} + +/******************************************************************************* +* Function : al3006_pls_report_dps +* Description : reg data,input dev +* Parameters : report proximity sensor data to input system +* Return : none +*******************************************************************************/ +static void al3006_pls_report_dps(unsigned char data, struct input_dev *input) +{ + unsigned char dps_data = (data&0x80)>>7; + + dps_data = (dps_data==1) ? 0: 1; + PLS_DBG("%s: proximity=%d", __func__, dps_data); + + input_report_abs(input, ABS_DISTANCE, dps_data); + input_sync(input); +} + +/******************************************************************************* +* Function : al3006_pls_report_dls +* Description : reg data,input dev +* Parameters : report light sensor data to input system +* Return : none +*******************************************************************************/ +static void al3006_pls_report_dls(unsigned char data, struct input_dev *input) +{ + int lux; + unsigned char adc = data&0x3f; + +#if defined(AL3006_PLS_ADC_LEVEL9) + adc = adc<<3; +#elif defined(AL3006_PLS_ADC_LEVEL5) + adc = adc<<4; +#endif + + lux = al3006_pls_adc2lux[adc]; + PLS_DBG("%s: adc=%d, lux=%d", __func__, adc, lux); + input_report_abs(input, ABS_MISC, lux); + input_sync(input); +} + +/******************************************************************************* +* Function : al3006_pls_work +* Description : handler current data and report coordinate +* Parameters : work +* Return : none +*******************************************************************************/ + +static void al3006_pls_work(struct work_struct *work) +{ + unsigned char int_status, data, enable; + al3006_pls_struct *pls = container_of(work, al3006_pls_struct, work); + + al3006_pls_read_data(AL3006_PLS_REG_CONFIG,&enable); + al3006_pls_read_data(AL3006_PLS_REG_INT_STATUS,&int_status); + al3006_pls_read_data(AL3006_PLS_REG_DATA,&data); + + PLS_DBG("\033[33;1m %s: enable=0x%x; int_status=0x%x; data=0x%x \033[m",\ + __func__, enable, int_status, data); + + switch(int_status & AL3006_PLS_INT_MASK) { + case AL3006_PLS_DPS_INT: + al3006_pls_report_dps(data, pls->input); + break; + + case AL3006_PLS_DLS_INT: + al3006_pls_report_dls(data, pls->input); + break; + + case AL3006_PLS_INT_MASK: + al3006_pls_report_dps(data, pls->input); + al3006_pls_report_dls(data, pls->input); + break; + + default: + break; + } + + enable_irq(pls->client->irq); + +} + + +/******************************************************************************* +* Function : al3006_pls_irq_handler +* Description : handle ts irq +* Parameters : handler +* Return : none +*******************************************************************************/ +static irqreturn_t al3006_pls_irq_handler(int irq, void *dev_id) +{ + al3006_pls_struct *pls = (al3006_pls_struct *)dev_id; + + disable_irq_nosync(pls->client->irq); + queue_work(pls->ltr_work_queue,&pls->work); + return IRQ_HANDLED; +} + +static void al3006_pls_pininit(int irq_pin) +{ + printk(KERN_INFO "%s [irq=%d]\n",__func__,irq_pin); + gpio_request(irq_pin, AL3006_PLS_IRQ_PIN); +} + + +static int al3006_pls_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + + int err = 0; + struct al3006_pls_platform_data *pdata = client->dev.platform_data; + struct input_dev *input_dev; + al3006_pls_struct *al3006_pls; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + printk("%s: functionality check failed\n", __func__); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + al3006_pls = kzalloc(sizeof(al3006_pls_struct), GFP_KERNEL); + if (!al3006_pls) + { + printk("%s: request memory failed\n", __func__); + err= -ENOMEM; + goto exit_request_memory_failed; + } + + this_client = client; + al3006_pls->client = client; + i2c_set_clientdata(client, al3006_pls); + + /*pin init*/ + al3006_pls_pininit(pdata->irq_gpio_number); + + /*get irq*/ + client->irq = gpio_to_irq(pdata->irq_gpio_number); + al3006_pls->irq = client->irq; + + PLS_DBG("I2C name=%s, addr=0x%x, gpio=%d, irq=%d",client->name,client->addr, \ + pdata->irq_gpio_number, client->irq); + + /*init AL3006_PLS*/ + if(al3006_pls_reg_init()<0) + { + printk(KERN_ERR "%s: device init failed\n", __func__); + err=-1; + goto exit_device_init_failed; + } + + /*register device*/ + err = misc_register(&al3006_pls_device); + if (err) { + printk(KERN_ERR "%s: al3006_pls_device register failed\n", __func__); + goto exit_device_register_failed; + } + + + /* register input device*/ + input_dev = input_allocate_device(); + if (!input_dev) + { + printk(KERN_ERR "%s: input allocate device failed\n", __func__); + err = -ENOMEM; + goto exit_input_dev_allocate_failed; + } + + al3006_pls->input = input_dev; + + input_dev->name = AL3006_PLS_INPUT_DEV; + input_dev->phys = AL3006_PLS_INPUT_DEV; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0010; + + __set_bit(EV_ABS, input_dev->evbit); + /*for proximity*/ + input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0); + /*for lightsensor*/ + input_set_abs_params(input_dev, ABS_MISC, 0, 100001, 0, 0); + + err = input_register_device(input_dev); + if (err < 0) + { + printk(KERN_ERR "%s: input device regist failed\n", __func__); + goto exit_input_register_failed; + } + + /*create work queue*/ + INIT_WORK(&al3006_pls->work, al3006_pls_work); + al3006_pls->ltr_work_queue= create_singlethread_workqueue(AL3006_PLS_DEVICE); + +#ifdef CONFIG_HAS_EARLYSUSPEND + /*register early suspend*/ + al3006_pls->ltr_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + al3006_pls->ltr_early_suspend.suspend = al3006_pls_early_suspend; + al3006_pls->ltr_early_suspend.resume = al3006_pls_early_resume; + register_early_suspend(&al3006_pls->ltr_early_suspend); +#endif + + if(client->irq > 0) + { + err = request_irq(client->irq, al3006_pls_irq_handler, IRQ_TYPE_LEVEL_LOW, client->name,al3006_pls); + if (err <0) + { + printk(KERN_ERR "%s: IRQ setup failed %d\n", __func__, err); + goto irq_request_err; + } + } + + /*create attribute files*/ + al3006_pls_create_sysfs(client); + + + printk(KERN_INFO "%s: Probe Success!\n",__func__); + + return 0; + +irq_request_err: +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&al3006_pls->ltr_early_suspend); +#endif + destroy_workqueue(al3006_pls->ltr_work_queue); +exit_input_register_failed: + input_free_device(input_dev); + misc_deregister(&al3006_pls_device); +exit_device_register_failed: +exit_input_dev_allocate_failed: +exit_device_init_failed: + kfree(al3006_pls); +exit_request_memory_failed: +exit_check_functionality_failed: + printk(KERN_ERR "%s: Probe Fail!\n",__func__); + return err; + +} + +static int al3006_pls_remove(struct i2c_client *client) +{ + al3006_pls_struct *al3006_pls = i2c_get_clientdata(client); + + PLS_DBG("%s", __func__); + + /*remove queue*/ + flush_workqueue(al3006_pls->ltr_work_queue); + destroy_workqueue(al3006_pls->ltr_work_queue); + +#ifdef CONFIG_HAS_EARLYSUSPEND + /*free suspend*/ + unregister_early_suspend(&al3006_pls->ltr_early_suspend); +#endif + misc_deregister(&al3006_pls_device); + /*free input*/ + input_unregister_device(al3006_pls->input); + input_free_device(al3006_pls->input); + /*free irq*/ + free_irq(al3006_pls->client->irq, al3006_pls); + /*free malloc*/ + kfree(al3006_pls); + + return 0; +} + +static const struct i2c_device_id al3006_pls_id[] = { + { AL3006_PLS_DEVICE, 0 }, + { } +}; +/*----------------------------------------------------------------------------*/ +static struct i2c_driver al3006_pls_driver = { + .driver = { + .owner = THIS_MODULE, + .name = AL3006_PLS_DEVICE, + }, + .probe = al3006_pls_probe, + .remove = al3006_pls_remove, + .id_table = al3006_pls_id, +}; +/*----------------------------------------------------------------------------*/ + +static int __init al3006_pls_init(void) +{ + PLS_DBG("%s", __func__); + return i2c_add_driver(&al3006_pls_driver); +} + +static void __exit al3006_pls_exit(void) +{ + PLS_DBG("%s", __func__); + i2c_del_driver(&al3006_pls_driver); +} + +module_init(al3006_pls_init); +module_exit(al3006_pls_exit); + + +MODULE_AUTHOR("Yunlong Wang <Yunlong.Wang@spreadtrum.com>"); +MODULE_DESCRIPTION("Proximity&Light Sensor AL3006 DRIVER"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/misc/apanel.c b/drivers/input/misc/apanel.c new file mode 100644 index 00000000..a8d2b8db --- /dev/null +++ b/drivers/input/misc/apanel.c @@ -0,0 +1,350 @@ +/* + * Fujitsu Lifebook Application Panel button drive + * + * Copyright (C) 2007 Stephen Hemminger <shemminger@linux-foundation.org> + * Copyright (C) 2001-2003 Jochen Eisinger <jochen@penguin-breeder.org> + * + * 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. + * + * Many Fujitsu Lifebook laptops have a small panel of buttons that are + * accessible via the i2c/smbus interface. This driver polls those + * buttons and generates input events. + * + * For more details see: + * http://apanel.sourceforge.net/tech.php + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/input-polldev.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/leds.h> + +#define APANEL_NAME "Fujitsu Application Panel" +#define APANEL_VERSION "1.3.1" +#define APANEL "apanel" + +/* How often we poll keys - msecs */ +#define POLL_INTERVAL_DEFAULT 1000 + +/* Magic constants in BIOS that tell about buttons */ +enum apanel_devid { + APANEL_DEV_NONE = 0, + APANEL_DEV_APPBTN = 1, + APANEL_DEV_CDBTN = 2, + APANEL_DEV_LCD = 3, + APANEL_DEV_LED = 4, + + APANEL_DEV_MAX, +}; + +enum apanel_chip { + CHIP_NONE = 0, + CHIP_OZ992C = 1, + CHIP_OZ163T = 2, + CHIP_OZ711M3 = 4, +}; + +/* Result of BIOS snooping/probing -- what features are supported */ +static enum apanel_chip device_chip[APANEL_DEV_MAX]; + +#define MAX_PANEL_KEYS 12 + +struct apanel { + struct input_polled_dev *ipdev; + struct i2c_client *client; + unsigned short keymap[MAX_PANEL_KEYS]; + u16 nkeys; + u16 led_bits; + struct work_struct led_work; + struct led_classdev mail_led; +}; + + +static int apanel_probe(struct i2c_client *, const struct i2c_device_id *); + +static void report_key(struct input_dev *input, unsigned keycode) +{ + pr_debug(APANEL ": report key %#x\n", keycode); + input_report_key(input, keycode, 1); + input_sync(input); + + input_report_key(input, keycode, 0); + input_sync(input); +} + +/* Poll for key changes + * + * Read Application keys via SMI + * A (0x4), B (0x8), Internet (0x2), Email (0x1). + * + * CD keys: + * Forward (0x100), Rewind (0x200), Stop (0x400), Pause (0x800) + */ +static void apanel_poll(struct input_polled_dev *ipdev) +{ + struct apanel *ap = ipdev->private; + struct input_dev *idev = ipdev->input; + u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; + s32 data; + int i; + + data = i2c_smbus_read_word_data(ap->client, cmd); + if (data < 0) + return; /* ignore errors (due to ACPI??) */ + + /* write back to clear latch */ + i2c_smbus_write_word_data(ap->client, cmd, 0); + + if (!data) + return; + + dev_dbg(&idev->dev, APANEL ": data %#x\n", data); + for (i = 0; i < idev->keycodemax; i++) + if ((1u << i) & data) + report_key(idev, ap->keymap[i]); +} + +/* Track state changes of LED */ +static void led_update(struct work_struct *work) +{ + struct apanel *ap = container_of(work, struct apanel, led_work); + + i2c_smbus_write_word_data(ap->client, 0x10, ap->led_bits); +} + +static void mail_led_set(struct led_classdev *led, + enum led_brightness value) +{ + struct apanel *ap = container_of(led, struct apanel, mail_led); + + if (value != LED_OFF) + ap->led_bits |= 0x8000; + else + ap->led_bits &= ~0x8000; + + schedule_work(&ap->led_work); +} + +static int apanel_remove(struct i2c_client *client) +{ + struct apanel *ap = i2c_get_clientdata(client); + + if (device_chip[APANEL_DEV_LED] != CHIP_NONE) + led_classdev_unregister(&ap->mail_led); + + input_unregister_polled_device(ap->ipdev); + input_free_polled_device(ap->ipdev); + + return 0; +} + +static void apanel_shutdown(struct i2c_client *client) +{ + apanel_remove(client); +} + +static const struct i2c_device_id apanel_id[] = { + { "fujitsu_apanel", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apanel_id); + +static struct i2c_driver apanel_driver = { + .driver = { + .name = APANEL, + }, + .probe = &apanel_probe, + .remove = &apanel_remove, + .shutdown = &apanel_shutdown, + .id_table = apanel_id, +}; + +static struct apanel apanel = { + .keymap = { + [0] = KEY_MAIL, + [1] = KEY_WWW, + [2] = KEY_PROG2, + [3] = KEY_PROG1, + + [8] = KEY_FORWARD, + [9] = KEY_REWIND, + [10] = KEY_STOPCD, + [11] = KEY_PLAYPAUSE, + + }, + .mail_led = { + .name = "mail:blue", + .brightness_set = mail_led_set, + }, +}; + +/* NB: Only one panel on the i2c. */ +static int apanel_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct apanel *ap; + struct input_polled_dev *ipdev; + struct input_dev *idev; + u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; + int i, err = -ENOMEM; + + ap = &apanel; + + ipdev = input_allocate_polled_device(); + if (!ipdev) + goto out1; + + ap->ipdev = ipdev; + ap->client = client; + + i2c_set_clientdata(client, ap); + + err = i2c_smbus_write_word_data(client, cmd, 0); + if (err) { + dev_warn(&client->dev, APANEL ": smbus write error %d\n", + err); + goto out3; + } + + ipdev->poll = apanel_poll; + ipdev->poll_interval = POLL_INTERVAL_DEFAULT; + ipdev->private = ap; + + idev = ipdev->input; + idev->name = APANEL_NAME " buttons"; + idev->phys = "apanel/input0"; + idev->id.bustype = BUS_HOST; + idev->dev.parent = &client->dev; + + set_bit(EV_KEY, idev->evbit); + + idev->keycode = ap->keymap; + idev->keycodesize = sizeof(ap->keymap[0]); + idev->keycodemax = (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) ? 12 : 4; + + for (i = 0; i < idev->keycodemax; i++) + if (ap->keymap[i]) + set_bit(ap->keymap[i], idev->keybit); + + err = input_register_polled_device(ipdev); + if (err) + goto out3; + + INIT_WORK(&ap->led_work, led_update); + if (device_chip[APANEL_DEV_LED] != CHIP_NONE) { + err = led_classdev_register(&client->dev, &ap->mail_led); + if (err) + goto out4; + } + + return 0; +out4: + input_unregister_polled_device(ipdev); +out3: + input_free_polled_device(ipdev); +out1: + return err; +} + +/* Scan the system ROM for the signature "FJKEYINF" */ +static __init const void __iomem *bios_signature(const void __iomem *bios) +{ + ssize_t offset; + const unsigned char signature[] = "FJKEYINF"; + + for (offset = 0; offset < 0x10000; offset += 0x10) { + if (check_signature(bios + offset, signature, + sizeof(signature)-1)) + return bios + offset; + } + pr_notice(APANEL ": Fujitsu BIOS signature '%s' not found...\n", + signature); + return NULL; +} + +static int __init apanel_init(void) +{ + void __iomem *bios; + const void __iomem *p; + u8 devno; + unsigned char i2c_addr; + int found = 0; + + bios = ioremap(0xF0000, 0x10000); /* Can't fail */ + + p = bios_signature(bios); + if (!p) { + iounmap(bios); + return -ENODEV; + } + + /* just use the first address */ + p += 8; + i2c_addr = readb(p + 3) >> 1; + + for ( ; (devno = readb(p)) & 0x7f; p += 4) { + unsigned char method, slave, chip; + + method = readb(p + 1); + chip = readb(p + 2); + slave = readb(p + 3) >> 1; + + if (slave != i2c_addr) { + pr_notice(APANEL ": only one SMBus slave " + "address supported, skiping device...\n"); + continue; + } + + /* translate alternative device numbers */ + switch (devno) { + case 6: + devno = APANEL_DEV_APPBTN; + break; + case 7: + devno = APANEL_DEV_LED; + break; + } + + if (devno >= APANEL_DEV_MAX) + pr_notice(APANEL ": unknown device %u found\n", devno); + else if (device_chip[devno] != CHIP_NONE) + pr_warning(APANEL ": duplicate entry for devno %u\n", devno); + + else if (method != 1 && method != 2 && method != 4) { + pr_notice(APANEL ": unknown method %u for devno %u\n", + method, devno); + } else { + device_chip[devno] = (enum apanel_chip) chip; + ++found; + } + } + iounmap(bios); + + if (found == 0) { + pr_info(APANEL ": no input devices reported by BIOS\n"); + return -EIO; + } + + return i2c_add_driver(&apanel_driver); +} +module_init(apanel_init); + +static void __exit apanel_cleanup(void) +{ + i2c_del_driver(&apanel_driver); +} +module_exit(apanel_cleanup); + +MODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>"); +MODULE_DESCRIPTION(APANEL_NAME " driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(APANEL_VERSION); + +MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*"); +MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*"); diff --git a/drivers/input/misc/arizona-haptics.c b/drivers/input/misc/arizona-haptics.c new file mode 100644 index 00000000..e7e12a5f --- /dev/null +++ b/drivers/input/misc/arizona-haptics.c @@ -0,0 +1,252 @@ +/* + * Arizona haptics driver + * + * Copyright 2012 Wolfson Microelectronics plc + * + * Author: Mark Brown <broonie@opensource.wolfsonmicro.com> + * + * 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. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/slab.h> + +#include <sound/soc.h> +#include <sound/soc-dapm.h> + +#include <linux/mfd/arizona/core.h> +#include <linux/mfd/arizona/pdata.h> +#include <linux/mfd/arizona/registers.h> + +struct arizona_haptics { + struct arizona *arizona; + struct input_dev *input_dev; + struct work_struct work; + + struct mutex mutex; + u8 intensity; +}; + +static void arizona_haptics_work(struct work_struct *work) +{ + struct arizona_haptics *haptics = container_of(work, + struct arizona_haptics, + work); + struct arizona *arizona = haptics->arizona; + struct mutex *dapm_mutex = &arizona->dapm->card->dapm_mutex; + int ret; + + if (!haptics->arizona->dapm) { + dev_err(arizona->dev, "No DAPM context\n"); + return; + } + + if (haptics->intensity) { + ret = regmap_update_bits(arizona->regmap, + ARIZONA_HAPTICS_PHASE_2_INTENSITY, + ARIZONA_PHASE2_INTENSITY_MASK, + haptics->intensity); + if (ret != 0) { + dev_err(arizona->dev, "Failed to set intensity: %d\n", + ret); + return; + } + + /* This enable sequence will be a noop if already enabled */ + ret = regmap_update_bits(arizona->regmap, + ARIZONA_HAPTICS_CONTROL_1, + ARIZONA_HAP_CTRL_MASK, + 1 << ARIZONA_HAP_CTRL_SHIFT); + if (ret != 0) { + dev_err(arizona->dev, "Failed to start haptics: %d\n", + ret); + return; + } + + mutex_lock_nested(dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + ret = snd_soc_dapm_enable_pin(arizona->dapm, "HAPTICS"); + if (ret != 0) { + dev_err(arizona->dev, "Failed to start HAPTICS: %d\n", + ret); + mutex_unlock(dapm_mutex); + return; + } + + mutex_unlock(dapm_mutex); + + ret = snd_soc_dapm_sync(arizona->dapm); + if (ret != 0) { + dev_err(arizona->dev, "Failed to sync DAPM: %d\n", + ret); + return; + } + } else { + /* This disable sequence will be a noop if already enabled */ + mutex_lock_nested(dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + ret = snd_soc_dapm_disable_pin(arizona->dapm, "HAPTICS"); + if (ret != 0) { + dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n", + ret); + mutex_unlock(dapm_mutex); + return; + } + + mutex_unlock(dapm_mutex); + + ret = snd_soc_dapm_sync(arizona->dapm); + if (ret != 0) { + dev_err(arizona->dev, "Failed to sync DAPM: %d\n", + ret); + return; + } + + ret = regmap_update_bits(arizona->regmap, + ARIZONA_HAPTICS_CONTROL_1, + ARIZONA_HAP_CTRL_MASK, + 1 << ARIZONA_HAP_CTRL_SHIFT); + if (ret != 0) { + dev_err(arizona->dev, "Failed to stop haptics: %d\n", + ret); + return; + } + } +} + +static int arizona_haptics_play(struct input_dev *input, void *data, + struct ff_effect *effect) +{ + struct arizona_haptics *haptics = input_get_drvdata(input); + struct arizona *arizona = haptics->arizona; + + if (!arizona->dapm) { + dev_err(arizona->dev, "No DAPM context\n"); + return -EBUSY; + } + + if (effect->u.rumble.strong_magnitude) { + /* Scale the magnitude into the range the device supports */ + if (arizona->pdata.hap_act) { + haptics->intensity = + effect->u.rumble.strong_magnitude >> 9; + if (effect->direction < 0x8000) + haptics->intensity += 0x7f; + } else { + haptics->intensity = + effect->u.rumble.strong_magnitude >> 8; + } + } else { + haptics->intensity = 0; + } + + schedule_work(&haptics->work); + + return 0; +} + +static void arizona_haptics_close(struct input_dev *input) +{ + struct arizona_haptics *haptics = input_get_drvdata(input); + struct mutex *dapm_mutex = &haptics->arizona->dapm->card->dapm_mutex; + + cancel_work_sync(&haptics->work); + + mutex_lock_nested(dapm_mutex, SND_SOC_DAPM_CLASS_RUNTIME); + + if (haptics->arizona->dapm) + snd_soc_dapm_disable_pin(haptics->arizona->dapm, "HAPTICS"); + + mutex_unlock(dapm_mutex); +} + +static int arizona_haptics_probe(struct platform_device *pdev) +{ + struct arizona *arizona = dev_get_drvdata(pdev->dev.parent); + struct arizona_haptics *haptics; + int ret; + + haptics = devm_kzalloc(&pdev->dev, sizeof(*haptics), GFP_KERNEL); + if (!haptics) + return -ENOMEM; + + haptics->arizona = arizona; + + ret = regmap_update_bits(arizona->regmap, ARIZONA_HAPTICS_CONTROL_1, + ARIZONA_HAP_ACT, arizona->pdata.hap_act); + if (ret != 0) { + dev_err(arizona->dev, "Failed to set haptics actuator: %d\n", + ret); + return ret; + } + + INIT_WORK(&haptics->work, arizona_haptics_work); + + haptics->input_dev = input_allocate_device(); + if (haptics->input_dev == NULL) { + dev_err(arizona->dev, "Failed to allocate input device\n"); + return -ENOMEM; + } + + input_set_drvdata(haptics->input_dev, haptics); + + haptics->input_dev->name = "arizona:haptics"; + haptics->input_dev->dev.parent = pdev->dev.parent; + haptics->input_dev->close = arizona_haptics_close; + __set_bit(FF_RUMBLE, haptics->input_dev->ffbit); + + ret = input_ff_create_memless(haptics->input_dev, NULL, + arizona_haptics_play); + if (ret < 0) { + dev_err(arizona->dev, "input_ff_create_memless() failed: %d\n", + ret); + goto err_ialloc; + } + + ret = input_register_device(haptics->input_dev); + if (ret < 0) { + dev_err(arizona->dev, "couldn't register input device: %d\n", + ret); + goto err_iff; + } + + platform_set_drvdata(pdev, haptics); + + return 0; + +err_iff: + if (haptics->input_dev) + input_ff_destroy(haptics->input_dev); +err_ialloc: + input_free_device(haptics->input_dev); + + return ret; +} + +static int arizona_haptics_remove(struct platform_device *pdev) +{ + struct arizona_haptics *haptics = platform_get_drvdata(pdev); + + input_unregister_device(haptics->input_dev); + + return 0; +} + +static struct platform_driver arizona_haptics_driver = { + .probe = arizona_haptics_probe, + .remove = arizona_haptics_remove, + .driver = { + .name = "arizona-haptics", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(arizona_haptics_driver); + +MODULE_ALIAS("platform:arizona-haptics"); +MODULE_DESCRIPTION("Arizona haptics driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); diff --git a/drivers/input/misc/ati_remote2.c b/drivers/input/misc/ati_remote2.c new file mode 100644 index 00000000..f63341f2 --- /dev/null +++ b/drivers/input/misc/ati_remote2.c @@ -0,0 +1,1016 @@ +/* + * ati_remote2 - ATI/Philips USB RF remote driver + * + * Copyright (C) 2005-2008 Ville Syrjala <syrjala@sci.fi> + * Copyright (C) 2007-2008 Peter Stokes <linux@dadeos.co.uk> + * + * 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. + */ + +#include <linux/usb/input.h> +#include <linux/slab.h> +#include <linux/module.h> + +#define DRIVER_DESC "ATI/Philips USB RF remote driver" +#define DRIVER_VERSION "0.3" + +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_VERSION(DRIVER_VERSION); +MODULE_AUTHOR("Ville Syrjala <syrjala@sci.fi>"); +MODULE_LICENSE("GPL"); + +/* + * ATI Remote Wonder II Channel Configuration + * + * The remote control can by assigned one of sixteen "channels" in order to facilitate + * the use of multiple remote controls within range of each other. + * A remote's "channel" may be altered by pressing and holding the "PC" button for + * approximately 3 seconds, after which the button will slowly flash the count of the + * currently configured "channel", using the numeric keypad enter a number between 1 and + * 16 and then press the "PC" button again, the button will slowly flash the count of the + * newly configured "channel". + */ + +enum { + ATI_REMOTE2_MAX_CHANNEL_MASK = 0xFFFF, + ATI_REMOTE2_MAX_MODE_MASK = 0x1F, +}; + +static int ati_remote2_set_mask(const char *val, + const struct kernel_param *kp, + unsigned int max) +{ + unsigned int mask; + int ret; + + if (!val) + return -EINVAL; + + ret = kstrtouint(val, 0, &mask); + if (ret) + return ret; + + if (mask & ~max) + return -EINVAL; + + *(unsigned int *)kp->arg = mask; + + return 0; +} + +static int ati_remote2_set_channel_mask(const char *val, + const struct kernel_param *kp) +{ + pr_debug("%s()\n", __func__); + + return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_CHANNEL_MASK); +} + +static int ati_remote2_get_channel_mask(char *buffer, + const struct kernel_param *kp) +{ + pr_debug("%s()\n", __func__); + + return sprintf(buffer, "0x%04x", *(unsigned int *)kp->arg); +} + +static int ati_remote2_set_mode_mask(const char *val, + const struct kernel_param *kp) +{ + pr_debug("%s()\n", __func__); + + return ati_remote2_set_mask(val, kp, ATI_REMOTE2_MAX_MODE_MASK); +} + +static int ati_remote2_get_mode_mask(char *buffer, + const struct kernel_param *kp) +{ + pr_debug("%s()\n", __func__); + + return sprintf(buffer, "0x%02x", *(unsigned int *)kp->arg); +} + +static unsigned int channel_mask = ATI_REMOTE2_MAX_CHANNEL_MASK; +#define param_check_channel_mask(name, p) __param_check(name, p, unsigned int) +static struct kernel_param_ops param_ops_channel_mask = { + .set = ati_remote2_set_channel_mask, + .get = ati_remote2_get_channel_mask, +}; +module_param(channel_mask, channel_mask, 0644); +MODULE_PARM_DESC(channel_mask, "Bitmask of channels to accept <15:Channel16>...<1:Channel2><0:Channel1>"); + +static unsigned int mode_mask = ATI_REMOTE2_MAX_MODE_MASK; +#define param_check_mode_mask(name, p) __param_check(name, p, unsigned int) +static struct kernel_param_ops param_ops_mode_mask = { + .set = ati_remote2_set_mode_mask, + .get = ati_remote2_get_mode_mask, +}; +module_param(mode_mask, mode_mask, 0644); +MODULE_PARM_DESC(mode_mask, "Bitmask of modes to accept <4:PC><3:AUX4><2:AUX3><1:AUX2><0:AUX1>"); + +static struct usb_device_id ati_remote2_id_table[] = { + { USB_DEVICE(0x0471, 0x0602) }, /* ATI Remote Wonder II */ + { } +}; +MODULE_DEVICE_TABLE(usb, ati_remote2_id_table); + +static DEFINE_MUTEX(ati_remote2_mutex); + +enum { + ATI_REMOTE2_OPENED = 0x1, + ATI_REMOTE2_SUSPENDED = 0x2, +}; + +enum { + ATI_REMOTE2_AUX1, + ATI_REMOTE2_AUX2, + ATI_REMOTE2_AUX3, + ATI_REMOTE2_AUX4, + ATI_REMOTE2_PC, + ATI_REMOTE2_MODES, +}; + +static const struct { + u8 hw_code; + u16 keycode; +} ati_remote2_key_table[] = { + { 0x00, KEY_0 }, + { 0x01, KEY_1 }, + { 0x02, KEY_2 }, + { 0x03, KEY_3 }, + { 0x04, KEY_4 }, + { 0x05, KEY_5 }, + { 0x06, KEY_6 }, + { 0x07, KEY_7 }, + { 0x08, KEY_8 }, + { 0x09, KEY_9 }, + { 0x0c, KEY_POWER }, + { 0x0d, KEY_MUTE }, + { 0x10, KEY_VOLUMEUP }, + { 0x11, KEY_VOLUMEDOWN }, + { 0x20, KEY_CHANNELUP }, + { 0x21, KEY_CHANNELDOWN }, + { 0x28, KEY_FORWARD }, + { 0x29, KEY_REWIND }, + { 0x2c, KEY_PLAY }, + { 0x30, KEY_PAUSE }, + { 0x31, KEY_STOP }, + { 0x37, KEY_RECORD }, + { 0x38, KEY_DVD }, + { 0x39, KEY_TV }, + { 0x3f, KEY_PROG1 }, /* AUX1-AUX4 and PC */ + { 0x54, KEY_MENU }, + { 0x58, KEY_UP }, + { 0x59, KEY_DOWN }, + { 0x5a, KEY_LEFT }, + { 0x5b, KEY_RIGHT }, + { 0x5c, KEY_OK }, + { 0x78, KEY_A }, + { 0x79, KEY_B }, + { 0x7a, KEY_C }, + { 0x7b, KEY_D }, + { 0x7c, KEY_E }, + { 0x7d, KEY_F }, + { 0x82, KEY_ENTER }, + { 0x8e, KEY_VENDOR }, + { 0x96, KEY_COFFEE }, + { 0xa9, BTN_LEFT }, + { 0xaa, BTN_RIGHT }, + { 0xbe, KEY_QUESTION }, + { 0xd0, KEY_EDIT }, + { 0xd5, KEY_FRONT }, + { 0xf9, KEY_INFO }, +}; + +struct ati_remote2 { + struct input_dev *idev; + struct usb_device *udev; + + struct usb_interface *intf[2]; + struct usb_endpoint_descriptor *ep[2]; + struct urb *urb[2]; + void *buf[2]; + dma_addr_t buf_dma[2]; + + unsigned long jiffies; + int mode; + + char name[64]; + char phys[64]; + + /* Each mode (AUX1-AUX4 and PC) can have an independent keymap. */ + u16 keycode[ATI_REMOTE2_MODES][ARRAY_SIZE(ati_remote2_key_table)]; + + unsigned int flags; + + unsigned int channel_mask; + unsigned int mode_mask; +}; + +static int ati_remote2_probe(struct usb_interface *interface, const struct usb_device_id *id); +static void ati_remote2_disconnect(struct usb_interface *interface); +static int ati_remote2_suspend(struct usb_interface *interface, pm_message_t message); +static int ati_remote2_resume(struct usb_interface *interface); +static int ati_remote2_reset_resume(struct usb_interface *interface); +static int ati_remote2_pre_reset(struct usb_interface *interface); +static int ati_remote2_post_reset(struct usb_interface *interface); + +static struct usb_driver ati_remote2_driver = { + .name = "ati_remote2", + .probe = ati_remote2_probe, + .disconnect = ati_remote2_disconnect, + .id_table = ati_remote2_id_table, + .suspend = ati_remote2_suspend, + .resume = ati_remote2_resume, + .reset_resume = ati_remote2_reset_resume, + .pre_reset = ati_remote2_pre_reset, + .post_reset = ati_remote2_post_reset, + .supports_autosuspend = 1, +}; + +static int ati_remote2_submit_urbs(struct ati_remote2 *ar2) +{ + int r; + + r = usb_submit_urb(ar2->urb[0], GFP_KERNEL); + if (r) { + dev_err(&ar2->intf[0]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); + return r; + } + r = usb_submit_urb(ar2->urb[1], GFP_KERNEL); + if (r) { + usb_kill_urb(ar2->urb[0]); + dev_err(&ar2->intf[1]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); + return r; + } + + return 0; +} + +static void ati_remote2_kill_urbs(struct ati_remote2 *ar2) +{ + usb_kill_urb(ar2->urb[1]); + usb_kill_urb(ar2->urb[0]); +} + +static int ati_remote2_open(struct input_dev *idev) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + int r; + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + r = usb_autopm_get_interface(ar2->intf[0]); + if (r) { + dev_err(&ar2->intf[0]->dev, + "%s(): usb_autopm_get_interface() = %d\n", __func__, r); + goto fail1; + } + + mutex_lock(&ati_remote2_mutex); + + if (!(ar2->flags & ATI_REMOTE2_SUSPENDED)) { + r = ati_remote2_submit_urbs(ar2); + if (r) + goto fail2; + } + + ar2->flags |= ATI_REMOTE2_OPENED; + + mutex_unlock(&ati_remote2_mutex); + + usb_autopm_put_interface(ar2->intf[0]); + + return 0; + + fail2: + mutex_unlock(&ati_remote2_mutex); + usb_autopm_put_interface(ar2->intf[0]); + fail1: + return r; +} + +static void ati_remote2_close(struct input_dev *idev) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + if (!(ar2->flags & ATI_REMOTE2_SUSPENDED)) + ati_remote2_kill_urbs(ar2); + + ar2->flags &= ~ATI_REMOTE2_OPENED; + + mutex_unlock(&ati_remote2_mutex); +} + +static void ati_remote2_input_mouse(struct ati_remote2 *ar2) +{ + struct input_dev *idev = ar2->idev; + u8 *data = ar2->buf[0]; + int channel, mode; + + channel = data[0] >> 4; + + if (!((1 << channel) & ar2->channel_mask)) + return; + + mode = data[0] & 0x0F; + + if (mode > ATI_REMOTE2_PC) { + dev_err(&ar2->intf[0]->dev, + "Unknown mode byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + if (!((1 << mode) & ar2->mode_mask)) + return; + + input_event(idev, EV_REL, REL_X, (s8) data[1]); + input_event(idev, EV_REL, REL_Y, (s8) data[2]); + input_sync(idev); +} + +static int ati_remote2_lookup(unsigned int hw_code) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ati_remote2_key_table); i++) + if (ati_remote2_key_table[i].hw_code == hw_code) + return i; + + return -1; +} + +static void ati_remote2_input_key(struct ati_remote2 *ar2) +{ + struct input_dev *idev = ar2->idev; + u8 *data = ar2->buf[1]; + int channel, mode, hw_code, index; + + channel = data[0] >> 4; + + if (!((1 << channel) & ar2->channel_mask)) + return; + + mode = data[0] & 0x0F; + + if (mode > ATI_REMOTE2_PC) { + dev_err(&ar2->intf[1]->dev, + "Unknown mode byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + hw_code = data[2]; + if (hw_code == 0x3f) { + /* + * For some incomprehensible reason the mouse pad generates + * events which look identical to the events from the last + * pressed mode key. Naturally we don't want to generate key + * events for the mouse pad so we filter out any subsequent + * events from the same mode key. + */ + if (ar2->mode == mode) + return; + + if (data[1] == 0) + ar2->mode = mode; + } + + if (!((1 << mode) & ar2->mode_mask)) + return; + + index = ati_remote2_lookup(hw_code); + if (index < 0) { + dev_err(&ar2->intf[1]->dev, + "Unknown code byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + switch (data[1]) { + case 0: /* release */ + break; + case 1: /* press */ + ar2->jiffies = jiffies + msecs_to_jiffies(idev->rep[REP_DELAY]); + break; + case 2: /* repeat */ + + /* No repeat for mouse buttons. */ + if (ar2->keycode[mode][index] == BTN_LEFT || + ar2->keycode[mode][index] == BTN_RIGHT) + return; + + if (!time_after_eq(jiffies, ar2->jiffies)) + return; + + ar2->jiffies = jiffies + msecs_to_jiffies(idev->rep[REP_PERIOD]); + break; + default: + dev_err(&ar2->intf[1]->dev, + "Unknown state byte (%02x %02x %02x %02x)\n", + data[3], data[2], data[1], data[0]); + return; + } + + input_event(idev, EV_KEY, ar2->keycode[mode][index], data[1]); + input_sync(idev); +} + +static void ati_remote2_complete_mouse(struct urb *urb) +{ + struct ati_remote2 *ar2 = urb->context; + int r; + + switch (urb->status) { + case 0: + usb_mark_last_busy(ar2->udev); + ati_remote2_input_mouse(ar2); + break; + case -ENOENT: + case -EILSEQ: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&ar2->intf[0]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + return; + default: + usb_mark_last_busy(ar2->udev); + dev_err(&ar2->intf[0]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + } + + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) + dev_err(&ar2->intf[0]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); +} + +static void ati_remote2_complete_key(struct urb *urb) +{ + struct ati_remote2 *ar2 = urb->context; + int r; + + switch (urb->status) { + case 0: + usb_mark_last_busy(ar2->udev); + ati_remote2_input_key(ar2); + break; + case -ENOENT: + case -EILSEQ: + case -ECONNRESET: + case -ESHUTDOWN: + dev_dbg(&ar2->intf[1]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + return; + default: + usb_mark_last_busy(ar2->udev); + dev_err(&ar2->intf[1]->dev, + "%s(): urb status = %d\n", __func__, urb->status); + } + + r = usb_submit_urb(urb, GFP_ATOMIC); + if (r) + dev_err(&ar2->intf[1]->dev, + "%s(): usb_submit_urb() = %d\n", __func__, r); +} + +static int ati_remote2_getkeycode(struct input_dev *idev, + struct input_keymap_entry *ke) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + unsigned int mode; + int offset; + unsigned int index; + unsigned int scancode; + + if (ke->flags & INPUT_KEYMAP_BY_INDEX) { + index = ke->index; + if (index >= ATI_REMOTE2_MODES * + ARRAY_SIZE(ati_remote2_key_table)) + return -EINVAL; + + mode = ke->index / ARRAY_SIZE(ati_remote2_key_table); + offset = ke->index % ARRAY_SIZE(ati_remote2_key_table); + scancode = (mode << 8) + ati_remote2_key_table[offset].hw_code; + } else { + if (input_scancode_to_scalar(ke, &scancode)) + return -EINVAL; + + mode = scancode >> 8; + if (mode > ATI_REMOTE2_PC) + return -EINVAL; + + offset = ati_remote2_lookup(scancode & 0xff); + if (offset < 0) + return -EINVAL; + + index = mode * ARRAY_SIZE(ati_remote2_key_table) + offset; + } + + ke->keycode = ar2->keycode[mode][offset]; + ke->len = sizeof(scancode); + memcpy(&ke->scancode, &scancode, sizeof(scancode)); + ke->index = index; + + return 0; +} + +static int ati_remote2_setkeycode(struct input_dev *idev, + const struct input_keymap_entry *ke, + unsigned int *old_keycode) +{ + struct ati_remote2 *ar2 = input_get_drvdata(idev); + unsigned int mode; + int offset; + unsigned int index; + unsigned int scancode; + + if (ke->flags & INPUT_KEYMAP_BY_INDEX) { + if (ke->index >= ATI_REMOTE2_MODES * + ARRAY_SIZE(ati_remote2_key_table)) + return -EINVAL; + + mode = ke->index / ARRAY_SIZE(ati_remote2_key_table); + offset = ke->index % ARRAY_SIZE(ati_remote2_key_table); + } else { + if (input_scancode_to_scalar(ke, &scancode)) + return -EINVAL; + + mode = scancode >> 8; + if (mode > ATI_REMOTE2_PC) + return -EINVAL; + + offset = ati_remote2_lookup(scancode & 0xff); + if (offset < 0) + return -EINVAL; + } + + *old_keycode = ar2->keycode[mode][offset]; + ar2->keycode[mode][offset] = ke->keycode; + __set_bit(ke->keycode, idev->keybit); + + for (mode = 0; mode < ATI_REMOTE2_MODES; mode++) { + for (index = 0; index < ARRAY_SIZE(ati_remote2_key_table); index++) { + if (ar2->keycode[mode][index] == *old_keycode) + return 0; + } + } + + __clear_bit(*old_keycode, idev->keybit); + + return 0; +} + +static int ati_remote2_input_init(struct ati_remote2 *ar2) +{ + struct input_dev *idev; + int index, mode, retval; + + idev = input_allocate_device(); + if (!idev) + return -ENOMEM; + + ar2->idev = idev; + input_set_drvdata(idev, ar2); + + idev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP) | BIT_MASK(EV_REL); + idev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | + BIT_MASK(BTN_RIGHT); + idev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y); + + for (mode = 0; mode < ATI_REMOTE2_MODES; mode++) { + for (index = 0; index < ARRAY_SIZE(ati_remote2_key_table); index++) { + ar2->keycode[mode][index] = ati_remote2_key_table[index].keycode; + __set_bit(ar2->keycode[mode][index], idev->keybit); + } + } + + /* AUX1-AUX4 and PC generate the same scancode. */ + index = ati_remote2_lookup(0x3f); + ar2->keycode[ATI_REMOTE2_AUX1][index] = KEY_PROG1; + ar2->keycode[ATI_REMOTE2_AUX2][index] = KEY_PROG2; + ar2->keycode[ATI_REMOTE2_AUX3][index] = KEY_PROG3; + ar2->keycode[ATI_REMOTE2_AUX4][index] = KEY_PROG4; + ar2->keycode[ATI_REMOTE2_PC][index] = KEY_PC; + __set_bit(KEY_PROG1, idev->keybit); + __set_bit(KEY_PROG2, idev->keybit); + __set_bit(KEY_PROG3, idev->keybit); + __set_bit(KEY_PROG4, idev->keybit); + __set_bit(KEY_PC, idev->keybit); + + idev->rep[REP_DELAY] = 250; + idev->rep[REP_PERIOD] = 33; + + idev->open = ati_remote2_open; + idev->close = ati_remote2_close; + + idev->getkeycode = ati_remote2_getkeycode; + idev->setkeycode = ati_remote2_setkeycode; + + idev->name = ar2->name; + idev->phys = ar2->phys; + + usb_to_input_id(ar2->udev, &idev->id); + idev->dev.parent = &ar2->udev->dev; + + retval = input_register_device(idev); + if (retval) + input_free_device(idev); + + return retval; +} + +static int ati_remote2_urb_init(struct ati_remote2 *ar2) +{ + struct usb_device *udev = ar2->udev; + int i, pipe, maxp; + + for (i = 0; i < 2; i++) { + ar2->buf[i] = usb_alloc_coherent(udev, 4, GFP_KERNEL, &ar2->buf_dma[i]); + if (!ar2->buf[i]) + return -ENOMEM; + + ar2->urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!ar2->urb[i]) + return -ENOMEM; + + pipe = usb_rcvintpipe(udev, ar2->ep[i]->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + maxp = maxp > 4 ? 4 : maxp; + + usb_fill_int_urb(ar2->urb[i], udev, pipe, ar2->buf[i], maxp, + i ? ati_remote2_complete_key : ati_remote2_complete_mouse, + ar2, ar2->ep[i]->bInterval); + ar2->urb[i]->transfer_dma = ar2->buf_dma[i]; + ar2->urb[i]->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + } + + return 0; +} + +static void ati_remote2_urb_cleanup(struct ati_remote2 *ar2) +{ + int i; + + for (i = 0; i < 2; i++) { + usb_free_urb(ar2->urb[i]); + usb_free_coherent(ar2->udev, 4, ar2->buf[i], ar2->buf_dma[i]); + } +} + +static int ati_remote2_setup(struct ati_remote2 *ar2, unsigned int ch_mask) +{ + int r, i, channel; + + /* + * Configure receiver to only accept input from remote "channel" + * channel == 0 -> Accept input from any remote channel + * channel == 1 -> Only accept input from remote channel 1 + * channel == 2 -> Only accept input from remote channel 2 + * ... + * channel == 16 -> Only accept input from remote channel 16 + */ + + channel = 0; + for (i = 0; i < 16; i++) { + if ((1 << i) & ch_mask) { + if (!(~(1 << i) & ch_mask)) + channel = i + 1; + break; + } + } + + r = usb_control_msg(ar2->udev, usb_sndctrlpipe(ar2->udev, 0), + 0x20, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE, + channel, 0x0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (r) { + dev_err(&ar2->udev->dev, "%s - failed to set channel due to error: %d\n", + __func__, r); + return r; + } + + return 0; +} + +static ssize_t ati_remote2_show_channel_mask(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct ati_remote2 *ar2 = usb_get_intfdata(intf); + + return sprintf(buf, "0x%04x\n", ar2->channel_mask); +} + +static ssize_t ati_remote2_store_channel_mask(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct ati_remote2 *ar2 = usb_get_intfdata(intf); + unsigned int mask; + int r; + + r = kstrtouint(buf, 0, &mask); + if (r) + return r; + + if (mask & ~ATI_REMOTE2_MAX_CHANNEL_MASK) + return -EINVAL; + + r = usb_autopm_get_interface(ar2->intf[0]); + if (r) { + dev_err(&ar2->intf[0]->dev, + "%s(): usb_autopm_get_interface() = %d\n", __func__, r); + return r; + } + + mutex_lock(&ati_remote2_mutex); + + if (mask != ar2->channel_mask) { + r = ati_remote2_setup(ar2, mask); + if (!r) + ar2->channel_mask = mask; + } + + mutex_unlock(&ati_remote2_mutex); + + usb_autopm_put_interface(ar2->intf[0]); + + return r ? r : count; +} + +static ssize_t ati_remote2_show_mode_mask(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct usb_device *udev = to_usb_device(dev); + struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct ati_remote2 *ar2 = usb_get_intfdata(intf); + + return sprintf(buf, "0x%02x\n", ar2->mode_mask); +} + +static ssize_t ati_remote2_store_mode_mask(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_device *udev = to_usb_device(dev); + struct usb_interface *intf = usb_ifnum_to_if(udev, 0); + struct ati_remote2 *ar2 = usb_get_intfdata(intf); + unsigned int mask; + int err; + + err = kstrtouint(buf, 0, &mask); + if (err) + return err; + + if (mask & ~ATI_REMOTE2_MAX_MODE_MASK) + return -EINVAL; + + ar2->mode_mask = mask; + + return count; +} + +static DEVICE_ATTR(channel_mask, 0644, ati_remote2_show_channel_mask, + ati_remote2_store_channel_mask); + +static DEVICE_ATTR(mode_mask, 0644, ati_remote2_show_mode_mask, + ati_remote2_store_mode_mask); + +static struct attribute *ati_remote2_attrs[] = { + &dev_attr_channel_mask.attr, + &dev_attr_mode_mask.attr, + NULL, +}; + +static struct attribute_group ati_remote2_attr_group = { + .attrs = ati_remote2_attrs, +}; + +static int ati_remote2_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *alt = interface->cur_altsetting; + struct ati_remote2 *ar2; + int r; + + if (alt->desc.bInterfaceNumber) + return -ENODEV; + + ar2 = kzalloc(sizeof (struct ati_remote2), GFP_KERNEL); + if (!ar2) + return -ENOMEM; + + ar2->udev = udev; + + ar2->intf[0] = interface; + ar2->ep[0] = &alt->endpoint[0].desc; + + ar2->intf[1] = usb_ifnum_to_if(udev, 1); + r = usb_driver_claim_interface(&ati_remote2_driver, ar2->intf[1], ar2); + if (r) + goto fail1; + alt = ar2->intf[1]->cur_altsetting; + ar2->ep[1] = &alt->endpoint[0].desc; + + r = ati_remote2_urb_init(ar2); + if (r) + goto fail2; + + ar2->channel_mask = channel_mask; + ar2->mode_mask = mode_mask; + + r = ati_remote2_setup(ar2, ar2->channel_mask); + if (r) + goto fail2; + + usb_make_path(udev, ar2->phys, sizeof(ar2->phys)); + strlcat(ar2->phys, "/input0", sizeof(ar2->phys)); + + strlcat(ar2->name, "ATI Remote Wonder II", sizeof(ar2->name)); + + r = sysfs_create_group(&udev->dev.kobj, &ati_remote2_attr_group); + if (r) + goto fail2; + + r = ati_remote2_input_init(ar2); + if (r) + goto fail3; + + usb_set_intfdata(interface, ar2); + + interface->needs_remote_wakeup = 1; + + return 0; + + fail3: + sysfs_remove_group(&udev->dev.kobj, &ati_remote2_attr_group); + fail2: + ati_remote2_urb_cleanup(ar2); + usb_driver_release_interface(&ati_remote2_driver, ar2->intf[1]); + fail1: + kfree(ar2); + + return r; +} + +static void ati_remote2_disconnect(struct usb_interface *interface) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + + if (alt->desc.bInterfaceNumber) + return; + + ar2 = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + input_unregister_device(ar2->idev); + + sysfs_remove_group(&ar2->udev->dev.kobj, &ati_remote2_attr_group); + + ati_remote2_urb_cleanup(ar2); + + usb_driver_release_interface(&ati_remote2_driver, ar2->intf[1]); + + kfree(ar2); +} + +static int ati_remote2_suspend(struct usb_interface *interface, + pm_message_t message) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + + if (alt->desc.bInterfaceNumber) + return 0; + + ar2 = usb_get_intfdata(interface); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + if (ar2->flags & ATI_REMOTE2_OPENED) + ati_remote2_kill_urbs(ar2); + + ar2->flags |= ATI_REMOTE2_SUSPENDED; + + mutex_unlock(&ati_remote2_mutex); + + return 0; +} + +static int ati_remote2_resume(struct usb_interface *interface) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + int r = 0; + + if (alt->desc.bInterfaceNumber) + return 0; + + ar2 = usb_get_intfdata(interface); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + if (ar2->flags & ATI_REMOTE2_OPENED) + r = ati_remote2_submit_urbs(ar2); + + if (!r) + ar2->flags &= ~ATI_REMOTE2_SUSPENDED; + + mutex_unlock(&ati_remote2_mutex); + + return r; +} + +static int ati_remote2_reset_resume(struct usb_interface *interface) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + int r = 0; + + if (alt->desc.bInterfaceNumber) + return 0; + + ar2 = usb_get_intfdata(interface); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + r = ati_remote2_setup(ar2, ar2->channel_mask); + if (r) + goto out; + + if (ar2->flags & ATI_REMOTE2_OPENED) + r = ati_remote2_submit_urbs(ar2); + + if (!r) + ar2->flags &= ~ATI_REMOTE2_SUSPENDED; + + out: + mutex_unlock(&ati_remote2_mutex); + + return r; +} + +static int ati_remote2_pre_reset(struct usb_interface *interface) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + + if (alt->desc.bInterfaceNumber) + return 0; + + ar2 = usb_get_intfdata(interface); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + mutex_lock(&ati_remote2_mutex); + + if (ar2->flags == ATI_REMOTE2_OPENED) + ati_remote2_kill_urbs(ar2); + + return 0; +} + +static int ati_remote2_post_reset(struct usb_interface *interface) +{ + struct ati_remote2 *ar2; + struct usb_host_interface *alt = interface->cur_altsetting; + int r = 0; + + if (alt->desc.bInterfaceNumber) + return 0; + + ar2 = usb_get_intfdata(interface); + + dev_dbg(&ar2->intf[0]->dev, "%s()\n", __func__); + + if (ar2->flags == ATI_REMOTE2_OPENED) + r = ati_remote2_submit_urbs(ar2); + + mutex_unlock(&ati_remote2_mutex); + + return r; +} + +module_usb_driver(ati_remote2_driver); diff --git a/drivers/input/misc/atlas_btns.c b/drivers/input/misc/atlas_btns.c new file mode 100644 index 00000000..5d440236 --- /dev/null +++ b/drivers/input/misc/atlas_btns.c @@ -0,0 +1,159 @@ +/* + * atlas_btns.c - Atlas Wallmount Touchscreen ACPI Extras + * + * Copyright (C) 2006 Jaya Kumar + * Based on Toshiba ACPI by John Belmonte and ASUS ACPI + * This work was sponsored by CIS(M) Sdn Bhd. + * + * 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 + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/types.h> +#include <asm/uaccess.h> +#include <acpi/acpi_drivers.h> + +#define ACPI_ATLAS_NAME "Atlas ACPI" +#define ACPI_ATLAS_CLASS "Atlas" + +static unsigned short atlas_keymap[16]; +static struct input_dev *input_dev; + +/* button handling code */ +static acpi_status acpi_atlas_button_setup(acpi_handle region_handle, + u32 function, void *handler_context, void **return_context) +{ + *return_context = + (function != ACPI_REGION_DEACTIVATE) ? handler_context : NULL; + + return AE_OK; +} + +static acpi_status acpi_atlas_button_handler(u32 function, + acpi_physical_address address, + u32 bit_width, u64 *value, + void *handler_context, void *region_context) +{ + acpi_status status; + + if (function == ACPI_WRITE) { + int code = address & 0x0f; + int key_down = !(address & 0x10); + + input_event(input_dev, EV_MSC, MSC_SCAN, code); + input_report_key(input_dev, atlas_keymap[code], key_down); + input_sync(input_dev); + + status = AE_OK; + } else { + pr_warn("shrugged on unexpected function: function=%x,address=%lx,value=%x\n", + function, (unsigned long)address, (u32)*value); + status = AE_BAD_PARAMETER; + } + + return status; +} + +static int atlas_acpi_button_add(struct acpi_device *device) +{ + acpi_status status; + int i; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) { + pr_err("unable to allocate input device\n"); + return -ENOMEM; + } + + input_dev->name = "Atlas ACPI button driver"; + input_dev->phys = "ASIM0000/atlas/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->keycode = atlas_keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(atlas_keymap); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input_dev->evbit); + for (i = 0; i < ARRAY_SIZE(atlas_keymap); i++) { + if (i < 9) { + atlas_keymap[i] = KEY_F1 + i; + __set_bit(KEY_F1 + i, input_dev->keybit); + } else + atlas_keymap[i] = KEY_RESERVED; + } + + err = input_register_device(input_dev); + if (err) { + pr_err("couldn't register input device\n"); + input_free_device(input_dev); + return err; + } + + /* hookup button handler */ + status = acpi_install_address_space_handler(device->handle, + 0x81, &acpi_atlas_button_handler, + &acpi_atlas_button_setup, device); + if (ACPI_FAILURE(status)) { + pr_err("error installing addr spc handler\n"); + input_unregister_device(input_dev); + err = -EINVAL; + } + + return err; +} + +static int atlas_acpi_button_remove(struct acpi_device *device) +{ + acpi_status status; + + status = acpi_remove_address_space_handler(device->handle, + 0x81, &acpi_atlas_button_handler); + if (ACPI_FAILURE(status)) + pr_err("error removing addr spc handler\n"); + + input_unregister_device(input_dev); + + return 0; +} + +static const struct acpi_device_id atlas_device_ids[] = { + {"ASIM0000", 0}, + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, atlas_device_ids); + +static struct acpi_driver atlas_acpi_driver = { + .name = ACPI_ATLAS_NAME, + .class = ACPI_ATLAS_CLASS, + .owner = THIS_MODULE, + .ids = atlas_device_ids, + .ops = { + .add = atlas_acpi_button_add, + .remove = atlas_acpi_button_remove, + }, +}; +module_acpi_driver(atlas_acpi_driver); + +MODULE_AUTHOR("Jaya Kumar"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Atlas button driver"); + diff --git a/drivers/input/misc/bfin_rotary.c b/drivers/input/misc/bfin_rotary.c new file mode 100644 index 00000000..a6666e14 --- /dev/null +++ b/drivers/input/misc/bfin_rotary.c @@ -0,0 +1,272 @@ +/* + * Rotary counter driver for Analog Devices Blackfin Processors + * + * Copyright 2008-2009 Analog Devices Inc. + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/slab.h> + +#include <asm/portmux.h> +#include <asm/bfin_rotary.h> + +static const u16 per_cnt[] = { + P_CNT_CUD, + P_CNT_CDG, + P_CNT_CZM, + 0 +}; + +struct bfin_rot { + struct input_dev *input; + int irq; + unsigned int up_key; + unsigned int down_key; + unsigned int button_key; + unsigned int rel_code; + unsigned short cnt_config; + unsigned short cnt_imask; + unsigned short cnt_debounce; +}; + +static void report_key_event(struct input_dev *input, int keycode) +{ + /* simulate a press-n-release */ + input_report_key(input, keycode, 1); + input_sync(input); + input_report_key(input, keycode, 0); + input_sync(input); +} + +static void report_rotary_event(struct bfin_rot *rotary, int delta) +{ + struct input_dev *input = rotary->input; + + if (rotary->up_key) { + report_key_event(input, + delta > 0 ? rotary->up_key : rotary->down_key); + } else { + input_report_rel(input, rotary->rel_code, delta); + input_sync(input); + } +} + +static irqreturn_t bfin_rotary_isr(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct bfin_rot *rotary = platform_get_drvdata(pdev); + int delta; + + switch (bfin_read_CNT_STATUS()) { + + case ICII: + break; + + case UCII: + case DCII: + delta = bfin_read_CNT_COUNTER(); + if (delta) + report_rotary_event(rotary, delta); + break; + + case CZMII: + report_key_event(rotary->input, rotary->button_key); + break; + + default: + break; + } + + bfin_write_CNT_COMMAND(W1LCNT_ZERO); /* Clear COUNTER */ + bfin_write_CNT_STATUS(-1); /* Clear STATUS */ + + return IRQ_HANDLED; +} + +static int bfin_rotary_probe(struct platform_device *pdev) +{ + struct bfin_rotary_platform_data *pdata = pdev->dev.platform_data; + struct bfin_rot *rotary; + struct input_dev *input; + int error; + + /* Basic validation */ + if ((pdata->rotary_up_key && !pdata->rotary_down_key) || + (!pdata->rotary_up_key && pdata->rotary_down_key)) { + return -EINVAL; + } + + error = peripheral_request_list(per_cnt, dev_name(&pdev->dev)); + if (error) { + dev_err(&pdev->dev, "requesting peripherals failed\n"); + return error; + } + + rotary = kzalloc(sizeof(struct bfin_rot), GFP_KERNEL); + input = input_allocate_device(); + if (!rotary || !input) { + error = -ENOMEM; + goto out1; + } + + rotary->input = input; + + rotary->up_key = pdata->rotary_up_key; + rotary->down_key = pdata->rotary_down_key; + rotary->button_key = pdata->rotary_button_key; + rotary->rel_code = pdata->rotary_rel_code; + + error = rotary->irq = platform_get_irq(pdev, 0); + if (error < 0) + goto out1; + + input->name = pdev->name; + input->phys = "bfin-rotary/input0"; + input->dev.parent = &pdev->dev; + + input_set_drvdata(input, rotary); + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + if (rotary->up_key) { + __set_bit(EV_KEY, input->evbit); + __set_bit(rotary->up_key, input->keybit); + __set_bit(rotary->down_key, input->keybit); + } else { + __set_bit(EV_REL, input->evbit); + __set_bit(rotary->rel_code, input->relbit); + } + + if (rotary->button_key) { + __set_bit(EV_KEY, input->evbit); + __set_bit(rotary->button_key, input->keybit); + } + + error = request_irq(rotary->irq, bfin_rotary_isr, + 0, dev_name(&pdev->dev), pdev); + if (error) { + dev_err(&pdev->dev, + "unable to claim irq %d; error %d\n", + rotary->irq, error); + goto out1; + } + + error = input_register_device(input); + if (error) { + dev_err(&pdev->dev, + "unable to register input device (%d)\n", error); + goto out2; + } + + if (pdata->rotary_button_key) + bfin_write_CNT_IMASK(CZMIE); + + if (pdata->mode & ROT_DEBE) + bfin_write_CNT_DEBOUNCE(pdata->debounce & DPRESCALE); + + if (pdata->mode) + bfin_write_CNT_CONFIG(bfin_read_CNT_CONFIG() | + (pdata->mode & ~CNTE)); + + bfin_write_CNT_IMASK(bfin_read_CNT_IMASK() | UCIE | DCIE); + bfin_write_CNT_CONFIG(bfin_read_CNT_CONFIG() | CNTE); + + platform_set_drvdata(pdev, rotary); + device_init_wakeup(&pdev->dev, 1); + + return 0; + +out2: + free_irq(rotary->irq, pdev); +out1: + input_free_device(input); + kfree(rotary); + peripheral_free_list(per_cnt); + + return error; +} + +static int bfin_rotary_remove(struct platform_device *pdev) +{ + struct bfin_rot *rotary = platform_get_drvdata(pdev); + + bfin_write_CNT_CONFIG(0); + bfin_write_CNT_IMASK(0); + + free_irq(rotary->irq, pdev); + input_unregister_device(rotary->input); + peripheral_free_list(per_cnt); + + kfree(rotary); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM +static int bfin_rotary_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct bfin_rot *rotary = platform_get_drvdata(pdev); + + rotary->cnt_config = bfin_read_CNT_CONFIG(); + rotary->cnt_imask = bfin_read_CNT_IMASK(); + rotary->cnt_debounce = bfin_read_CNT_DEBOUNCE(); + + if (device_may_wakeup(&pdev->dev)) + enable_irq_wake(rotary->irq); + + return 0; +} + +static int bfin_rotary_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct bfin_rot *rotary = platform_get_drvdata(pdev); + + bfin_write_CNT_DEBOUNCE(rotary->cnt_debounce); + bfin_write_CNT_IMASK(rotary->cnt_imask); + bfin_write_CNT_CONFIG(rotary->cnt_config & ~CNTE); + + if (device_may_wakeup(&pdev->dev)) + disable_irq_wake(rotary->irq); + + if (rotary->cnt_config & CNTE) + bfin_write_CNT_CONFIG(rotary->cnt_config); + + return 0; +} + +static const struct dev_pm_ops bfin_rotary_pm_ops = { + .suspend = bfin_rotary_suspend, + .resume = bfin_rotary_resume, +}; +#endif + +static struct platform_driver bfin_rotary_device_driver = { + .probe = bfin_rotary_probe, + .remove = bfin_rotary_remove, + .driver = { + .name = "bfin-rotary", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &bfin_rotary_pm_ops, +#endif + }, +}; +module_platform_driver(bfin_rotary_device_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("Rotary Counter driver for Blackfin Processors"); +MODULE_ALIAS("platform:bfin-rotary"); diff --git a/drivers/input/misc/bma150.c b/drivers/input/misc/bma150.c new file mode 100644 index 00000000..865c2f9d --- /dev/null +++ b/drivers/input/misc/bma150.c @@ -0,0 +1,668 @@ +/* + * Copyright (c) 2011 Bosch Sensortec GmbH + * Copyright (c) 2011 Unixphere + * + * This driver adds support for Bosch Sensortec's digital acceleration + * sensors BMA150 and SMB380. + * The SMB380 is fully compatible with BMA150 and only differs in packaging. + * + * The datasheet for the BMA150 chip can be found here: + * http://www.bosch-sensortec.com/content/language1/downloads/BST-BMA150-DS000-07.pdf + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> +#include <linux/bma150.h> + +#define ABSMAX_ACC_VAL 0x01FF +#define ABSMIN_ACC_VAL -(ABSMAX_ACC_VAL) + +/* Each axis is represented by a 2-byte data word */ +#define BMA150_XYZ_DATA_SIZE 6 + +/* Input poll interval in milliseconds */ +#define BMA150_POLL_INTERVAL 10 +#define BMA150_POLL_MAX 200 +#define BMA150_POLL_MIN 0 + +#define BMA150_MODE_NORMAL 0 +#define BMA150_MODE_SLEEP 2 +#define BMA150_MODE_WAKE_UP 3 + +/* Data register addresses */ +#define BMA150_DATA_0_REG 0x00 +#define BMA150_DATA_1_REG 0x01 +#define BMA150_DATA_2_REG 0x02 + +/* Control register addresses */ +#define BMA150_CTRL_0_REG 0x0A +#define BMA150_CTRL_1_REG 0x0B +#define BMA150_CTRL_2_REG 0x14 +#define BMA150_CTRL_3_REG 0x15 + +/* Configuration/Setting register addresses */ +#define BMA150_CFG_0_REG 0x0C +#define BMA150_CFG_1_REG 0x0D +#define BMA150_CFG_2_REG 0x0E +#define BMA150_CFG_3_REG 0x0F +#define BMA150_CFG_4_REG 0x10 +#define BMA150_CFG_5_REG 0x11 + +#define BMA150_CHIP_ID 2 +#define BMA150_CHIP_ID_REG BMA150_DATA_0_REG + +#define BMA150_ACC_X_LSB_REG BMA150_DATA_2_REG + +#define BMA150_SLEEP_POS 0 +#define BMA150_SLEEP_MSK 0x01 +#define BMA150_SLEEP_REG BMA150_CTRL_0_REG + +#define BMA150_BANDWIDTH_POS 0 +#define BMA150_BANDWIDTH_MSK 0x07 +#define BMA150_BANDWIDTH_REG BMA150_CTRL_2_REG + +#define BMA150_RANGE_POS 3 +#define BMA150_RANGE_MSK 0x18 +#define BMA150_RANGE_REG BMA150_CTRL_2_REG + +#define BMA150_WAKE_UP_POS 0 +#define BMA150_WAKE_UP_MSK 0x01 +#define BMA150_WAKE_UP_REG BMA150_CTRL_3_REG + +#define BMA150_SW_RES_POS 1 +#define BMA150_SW_RES_MSK 0x02 +#define BMA150_SW_RES_REG BMA150_CTRL_0_REG + +/* Any-motion interrupt register fields */ +#define BMA150_ANY_MOTION_EN_POS 6 +#define BMA150_ANY_MOTION_EN_MSK 0x40 +#define BMA150_ANY_MOTION_EN_REG BMA150_CTRL_1_REG + +#define BMA150_ANY_MOTION_DUR_POS 6 +#define BMA150_ANY_MOTION_DUR_MSK 0xC0 +#define BMA150_ANY_MOTION_DUR_REG BMA150_CFG_5_REG + +#define BMA150_ANY_MOTION_THRES_REG BMA150_CFG_4_REG + +/* Advanced interrupt register fields */ +#define BMA150_ADV_INT_EN_POS 6 +#define BMA150_ADV_INT_EN_MSK 0x40 +#define BMA150_ADV_INT_EN_REG BMA150_CTRL_3_REG + +/* High-G interrupt register fields */ +#define BMA150_HIGH_G_EN_POS 1 +#define BMA150_HIGH_G_EN_MSK 0x02 +#define BMA150_HIGH_G_EN_REG BMA150_CTRL_1_REG + +#define BMA150_HIGH_G_HYST_POS 3 +#define BMA150_HIGH_G_HYST_MSK 0x38 +#define BMA150_HIGH_G_HYST_REG BMA150_CFG_5_REG + +#define BMA150_HIGH_G_DUR_REG BMA150_CFG_3_REG +#define BMA150_HIGH_G_THRES_REG BMA150_CFG_2_REG + +/* Low-G interrupt register fields */ +#define BMA150_LOW_G_EN_POS 0 +#define BMA150_LOW_G_EN_MSK 0x01 +#define BMA150_LOW_G_EN_REG BMA150_CTRL_1_REG + +#define BMA150_LOW_G_HYST_POS 0 +#define BMA150_LOW_G_HYST_MSK 0x07 +#define BMA150_LOW_G_HYST_REG BMA150_CFG_5_REG + +#define BMA150_LOW_G_DUR_REG BMA150_CFG_1_REG +#define BMA150_LOW_G_THRES_REG BMA150_CFG_0_REG + +struct bma150_data { + struct i2c_client *client; + struct input_polled_dev *input_polled; + struct input_dev *input; + u8 mode; +}; + +/* + * The settings for the given range, bandwidth and interrupt features + * are stated and verified by Bosch Sensortec where they are configured + * to provide a generic sensitivity performance. + */ +static struct bma150_cfg default_cfg = { + .any_motion_int = 1, + .hg_int = 1, + .lg_int = 1, + .any_motion_dur = 0, + .any_motion_thres = 0, + .hg_hyst = 0, + .hg_dur = 150, + .hg_thres = 160, + .lg_hyst = 0, + .lg_dur = 150, + .lg_thres = 20, + .range = BMA150_RANGE_2G, + .bandwidth = BMA150_BW_50HZ +}; + +static int bma150_write_byte(struct i2c_client *client, u8 reg, u8 val) +{ + s32 ret; + + /* As per specification, disable irq in between register writes */ + if (client->irq) + disable_irq_nosync(client->irq); + + ret = i2c_smbus_write_byte_data(client, reg, val); + + if (client->irq) + enable_irq(client->irq); + + return ret; +} + +static int bma150_set_reg_bits(struct i2c_client *client, + int val, int shift, u8 mask, u8 reg) +{ + int data; + + data = i2c_smbus_read_byte_data(client, reg); + if (data < 0) + return data; + + data = (data & ~mask) | ((val << shift) & mask); + return bma150_write_byte(client, reg, data); +} + +static int bma150_set_mode(struct bma150_data *bma150, u8 mode) +{ + int error; + + error = bma150_set_reg_bits(bma150->client, mode, BMA150_WAKE_UP_POS, + BMA150_WAKE_UP_MSK, BMA150_WAKE_UP_REG); + if (error) + return error; + + error = bma150_set_reg_bits(bma150->client, mode, BMA150_SLEEP_POS, + BMA150_SLEEP_MSK, BMA150_SLEEP_REG); + if (error) + return error; + + if (mode == BMA150_MODE_NORMAL) + msleep(2); + + bma150->mode = mode; + return 0; +} + +static int bma150_soft_reset(struct bma150_data *bma150) +{ + int error; + + error = bma150_set_reg_bits(bma150->client, 1, BMA150_SW_RES_POS, + BMA150_SW_RES_MSK, BMA150_SW_RES_REG); + if (error) + return error; + + msleep(2); + return 0; +} + +static int bma150_set_range(struct bma150_data *bma150, u8 range) +{ + return bma150_set_reg_bits(bma150->client, range, BMA150_RANGE_POS, + BMA150_RANGE_MSK, BMA150_RANGE_REG); +} + +static int bma150_set_bandwidth(struct bma150_data *bma150, u8 bw) +{ + return bma150_set_reg_bits(bma150->client, bw, BMA150_BANDWIDTH_POS, + BMA150_BANDWIDTH_MSK, BMA150_BANDWIDTH_REG); +} + +static int bma150_set_low_g_interrupt(struct bma150_data *bma150, + u8 enable, u8 hyst, u8 dur, u8 thres) +{ + int error; + + error = bma150_set_reg_bits(bma150->client, hyst, + BMA150_LOW_G_HYST_POS, BMA150_LOW_G_HYST_MSK, + BMA150_LOW_G_HYST_REG); + if (error) + return error; + + error = bma150_write_byte(bma150->client, BMA150_LOW_G_DUR_REG, dur); + if (error) + return error; + + error = bma150_write_byte(bma150->client, BMA150_LOW_G_THRES_REG, thres); + if (error) + return error; + + return bma150_set_reg_bits(bma150->client, !!enable, + BMA150_LOW_G_EN_POS, BMA150_LOW_G_EN_MSK, + BMA150_LOW_G_EN_REG); +} + +static int bma150_set_high_g_interrupt(struct bma150_data *bma150, + u8 enable, u8 hyst, u8 dur, u8 thres) +{ + int error; + + error = bma150_set_reg_bits(bma150->client, hyst, + BMA150_HIGH_G_HYST_POS, BMA150_HIGH_G_HYST_MSK, + BMA150_HIGH_G_HYST_REG); + if (error) + return error; + + error = bma150_write_byte(bma150->client, + BMA150_HIGH_G_DUR_REG, dur); + if (error) + return error; + + error = bma150_write_byte(bma150->client, + BMA150_HIGH_G_THRES_REG, thres); + if (error) + return error; + + return bma150_set_reg_bits(bma150->client, !!enable, + BMA150_HIGH_G_EN_POS, BMA150_HIGH_G_EN_MSK, + BMA150_HIGH_G_EN_REG); +} + + +static int bma150_set_any_motion_interrupt(struct bma150_data *bma150, + u8 enable, u8 dur, u8 thres) +{ + int error; + + error = bma150_set_reg_bits(bma150->client, dur, + BMA150_ANY_MOTION_DUR_POS, + BMA150_ANY_MOTION_DUR_MSK, + BMA150_ANY_MOTION_DUR_REG); + if (error) + return error; + + error = bma150_write_byte(bma150->client, + BMA150_ANY_MOTION_THRES_REG, thres); + if (error) + return error; + + error = bma150_set_reg_bits(bma150->client, !!enable, + BMA150_ADV_INT_EN_POS, BMA150_ADV_INT_EN_MSK, + BMA150_ADV_INT_EN_REG); + if (error) + return error; + + return bma150_set_reg_bits(bma150->client, !!enable, + BMA150_ANY_MOTION_EN_POS, + BMA150_ANY_MOTION_EN_MSK, + BMA150_ANY_MOTION_EN_REG); +} + +static void bma150_report_xyz(struct bma150_data *bma150) +{ + u8 data[BMA150_XYZ_DATA_SIZE]; + s16 x, y, z; + s32 ret; + + ret = i2c_smbus_read_i2c_block_data(bma150->client, + BMA150_ACC_X_LSB_REG, BMA150_XYZ_DATA_SIZE, data); + if (ret != BMA150_XYZ_DATA_SIZE) + return; + + x = ((0xc0 & data[0]) >> 6) | (data[1] << 2); + y = ((0xc0 & data[2]) >> 6) | (data[3] << 2); + z = ((0xc0 & data[4]) >> 6) | (data[5] << 2); + + /* sign extension */ + x = (s16) (x << 6) >> 6; + y = (s16) (y << 6) >> 6; + z = (s16) (z << 6) >> 6; + + input_report_abs(bma150->input, ABS_X, x); + input_report_abs(bma150->input, ABS_Y, y); + input_report_abs(bma150->input, ABS_Z, z); + input_sync(bma150->input); +} + +static irqreturn_t bma150_irq_thread(int irq, void *dev) +{ + bma150_report_xyz(dev); + + return IRQ_HANDLED; +} + +static void bma150_poll(struct input_polled_dev *dev) +{ + bma150_report_xyz(dev->private); +} + +static int bma150_open(struct bma150_data *bma150) +{ + int error; + + error = pm_runtime_get_sync(&bma150->client->dev); + if (error < 0 && error != -ENOSYS) + return error; + + /* + * See if runtime PM woke up the device. If runtime PM + * is disabled we need to do it ourselves. + */ + if (bma150->mode != BMA150_MODE_NORMAL) { + error = bma150_set_mode(bma150, BMA150_MODE_NORMAL); + if (error) + return error; + } + + return 0; +} + +static void bma150_close(struct bma150_data *bma150) +{ + pm_runtime_put_sync(&bma150->client->dev); + + if (bma150->mode != BMA150_MODE_SLEEP) + bma150_set_mode(bma150, BMA150_MODE_SLEEP); +} + +static int bma150_irq_open(struct input_dev *input) +{ + struct bma150_data *bma150 = input_get_drvdata(input); + + return bma150_open(bma150); +} + +static void bma150_irq_close(struct input_dev *input) +{ + struct bma150_data *bma150 = input_get_drvdata(input); + + bma150_close(bma150); +} + +static void bma150_poll_open(struct input_polled_dev *ipoll_dev) +{ + struct bma150_data *bma150 = ipoll_dev->private; + + bma150_open(bma150); +} + +static void bma150_poll_close(struct input_polled_dev *ipoll_dev) +{ + struct bma150_data *bma150 = ipoll_dev->private; + + bma150_close(bma150); +} + +static int bma150_initialize(struct bma150_data *bma150, + const struct bma150_cfg *cfg) +{ + int error; + + error = bma150_soft_reset(bma150); + if (error) + return error; + + error = bma150_set_bandwidth(bma150, cfg->bandwidth); + if (error) + return error; + + error = bma150_set_range(bma150, cfg->range); + if (error) + return error; + + if (bma150->client->irq) { + error = bma150_set_any_motion_interrupt(bma150, + cfg->any_motion_int, + cfg->any_motion_dur, + cfg->any_motion_thres); + if (error) + return error; + + error = bma150_set_high_g_interrupt(bma150, + cfg->hg_int, cfg->hg_hyst, + cfg->hg_dur, cfg->hg_thres); + if (error) + return error; + + error = bma150_set_low_g_interrupt(bma150, + cfg->lg_int, cfg->lg_hyst, + cfg->lg_dur, cfg->lg_thres); + if (error) + return error; + } + + return bma150_set_mode(bma150, BMA150_MODE_SLEEP); +} + +static void bma150_init_input_device(struct bma150_data *bma150, + struct input_dev *idev) +{ + idev->name = BMA150_DRIVER; + idev->phys = BMA150_DRIVER "/input0"; + idev->id.bustype = BUS_I2C; + idev->dev.parent = &bma150->client->dev; + + idev->evbit[0] = BIT_MASK(EV_ABS); + input_set_abs_params(idev, ABS_X, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0); + input_set_abs_params(idev, ABS_Y, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0); + input_set_abs_params(idev, ABS_Z, ABSMIN_ACC_VAL, ABSMAX_ACC_VAL, 0, 0); +} + +static int bma150_register_input_device(struct bma150_data *bma150) +{ + struct input_dev *idev; + int error; + + idev = input_allocate_device(); + if (!idev) + return -ENOMEM; + + bma150_init_input_device(bma150, idev); + + idev->open = bma150_irq_open; + idev->close = bma150_irq_close; + input_set_drvdata(idev, bma150); + + error = input_register_device(idev); + if (error) { + input_free_device(idev); + return error; + } + + bma150->input = idev; + return 0; +} + +static int bma150_register_polled_device(struct bma150_data *bma150) +{ + struct input_polled_dev *ipoll_dev; + int error; + + ipoll_dev = input_allocate_polled_device(); + if (!ipoll_dev) + return -ENOMEM; + + ipoll_dev->private = bma150; + ipoll_dev->open = bma150_poll_open; + ipoll_dev->close = bma150_poll_close; + ipoll_dev->poll = bma150_poll; + ipoll_dev->poll_interval = BMA150_POLL_INTERVAL; + ipoll_dev->poll_interval_min = BMA150_POLL_MIN; + ipoll_dev->poll_interval_max = BMA150_POLL_MAX; + + bma150_init_input_device(bma150, ipoll_dev->input); + + error = input_register_polled_device(ipoll_dev); + if (error) { + input_free_polled_device(ipoll_dev); + return error; + } + + bma150->input_polled = ipoll_dev; + bma150->input = ipoll_dev->input; + + return 0; +} + +static int bma150_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct bma150_platform_data *pdata = client->dev.platform_data; + const struct bma150_cfg *cfg; + struct bma150_data *bma150; + int chip_id; + int error; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "i2c_check_functionality error\n"); + return -EIO; + } + + chip_id = i2c_smbus_read_byte_data(client, BMA150_CHIP_ID_REG); + if (chip_id != BMA150_CHIP_ID) { + dev_err(&client->dev, "BMA150 chip id error: %d\n", chip_id); + return -EINVAL; + } + + bma150 = kzalloc(sizeof(struct bma150_data), GFP_KERNEL); + if (!bma150) + return -ENOMEM; + + bma150->client = client; + + if (pdata) { + if (pdata->irq_gpio_cfg) { + error = pdata->irq_gpio_cfg(); + if (error) { + dev_err(&client->dev, + "IRQ GPIO conf. error %d, error %d\n", + client->irq, error); + goto err_free_mem; + } + } + cfg = &pdata->cfg; + } else { + cfg = &default_cfg; + } + + error = bma150_initialize(bma150, cfg); + if (error) + goto err_free_mem; + + if (client->irq > 0) { + error = bma150_register_input_device(bma150); + if (error) + goto err_free_mem; + + error = request_threaded_irq(client->irq, + NULL, bma150_irq_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + BMA150_DRIVER, bma150); + if (error) { + dev_err(&client->dev, + "irq request failed %d, error %d\n", + client->irq, error); + input_unregister_device(bma150->input); + goto err_free_mem; + } + } else { + error = bma150_register_polled_device(bma150); + if (error) + goto err_free_mem; + } + + i2c_set_clientdata(client, bma150); + + pm_runtime_enable(&client->dev); + + return 0; + +err_free_mem: + kfree(bma150); + return error; +} + +static int bma150_remove(struct i2c_client *client) +{ + struct bma150_data *bma150 = i2c_get_clientdata(client); + + pm_runtime_disable(&client->dev); + + if (client->irq > 0) { + free_irq(client->irq, bma150); + input_unregister_device(bma150->input); + } else { + input_unregister_polled_device(bma150->input_polled); + input_free_polled_device(bma150->input_polled); + } + + kfree(bma150); + + return 0; +} + +#ifdef CONFIG_PM +static int bma150_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + return bma150_set_mode(bma150, BMA150_MODE_SLEEP); +} + +static int bma150_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma150_data *bma150 = i2c_get_clientdata(client); + + return bma150_set_mode(bma150, BMA150_MODE_NORMAL); +} +#endif + +static UNIVERSAL_DEV_PM_OPS(bma150_pm, bma150_suspend, bma150_resume, NULL); + +static const struct i2c_device_id bma150_id[] = { + { "bma150", 0 }, + { "smb380", 0 }, + { "bma023", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bma150_id); + +static struct i2c_driver bma150_driver = { + .driver = { + .owner = THIS_MODULE, + .name = BMA150_DRIVER, + .pm = &bma150_pm, + }, + .class = I2C_CLASS_HWMON, + .id_table = bma150_id, + .probe = bma150_probe, + .remove = bma150_remove, +}; + +module_i2c_driver(bma150_driver); + +MODULE_AUTHOR("Albert Zhang <xu.zhang@bosch-sensortec.com>"); +MODULE_DESCRIPTION("BMA150 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/bma2x2.c b/drivers/input/misc/bma2x2.c new file mode 100644 index 00000000..28ead0b5 --- /dev/null +++ b/drivers/input/misc/bma2x2.c @@ -0,0 +1,7564 @@ +/*! + * @section LICENSE + * $license$ + * + * @filename $filename$ + * @date 2015/07/13 15:32 + * @id $id$ + * @version 2.1.3 + * + * @brief + * This file contains all function implementations for the BMA2X2 in linux +*/ + +#ifdef CONFIG_SIG_MOTION +#undef CONFIG_HAS_EARLYSUSPEND +#endif +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/workqueue.h> +#include <linux/mutex.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <asm/irq.h> +#include <asm/mach/irq.h> +#include <linux/hrtimer.h> +#include <linux/timer.h> + + +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#ifdef __KERNEL__ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/unistd.h> +#include <linux/types.h> +#include <linux/string.h> +#else +#include <unistd.h> +#include <sys/types.h> +#include <string.h> +#endif + + +#include <linux/init.h> +#include <linux/syscalls.h> +#include <linux/file.h> +#include <linux/fs.h> +#include <linux/fcntl.h> +#include <asm/uaccess.h> + + +#include "bstclass.h" +#include "bs_log.h" + +#define ACC_NAME "ACC" +/*#define CONFIG_BMA_ENABLE_NEWDATA_INT 1*/ + +#define SENSOR_NAME "bma2x2" +#define ABSMIN -512 +#define ABSMAX 512 +#define SLOPE_THRESHOLD_VALUE 32 +#define SLOPE_DURATION_VALUE 1 +#define INTERRUPT_LATCH_MODE 13 +#define INTERRUPT_ENABLE 1 +#define INTERRUPT_DISABLE 0 +#define MAP_SLOPE_INTERRUPT 2 +#define SLOPE_X_INDEX 5 +#define SLOPE_Y_INDEX 6 +#define SLOPE_Z_INDEX 7 +#define BMA2X2_MAX_DELAY 200 +#define BMA2X2_RANGE_SET 3 /* +/- 2G */ +#define BMA2X2_BW_SET 12 /* 125HZ */ + +#define LOW_G_INTERRUPT REL_Z +#define HIGH_G_INTERRUPT REL_HWHEEL +#define SLOP_INTERRUPT REL_DIAL +#define DOUBLE_TAP_INTERRUPT REL_WHEEL +#define SINGLE_TAP_INTERRUPT REL_MISC +#define ORIENT_INTERRUPT ABS_PRESSURE +#define FLAT_INTERRUPT ABS_DISTANCE +#define SLOW_NO_MOTION_INTERRUPT REL_Y + +#define HIGH_G_INTERRUPT_X_HAPPENED 1 +#define HIGH_G_INTERRUPT_Y_HAPPENED 2 +#define HIGH_G_INTERRUPT_Z_HAPPENED 3 +#define HIGH_G_INTERRUPT_X_NEGATIVE_HAPPENED 4 +#define HIGH_G_INTERRUPT_Y_NEGATIVE_HAPPENED 5 +#define HIGH_G_INTERRUPT_Z_NEGATIVE_HAPPENED 6 +#define SLOPE_INTERRUPT_X_HAPPENED 7 +#define SLOPE_INTERRUPT_Y_HAPPENED 8 +#define SLOPE_INTERRUPT_Z_HAPPENED 9 +#define SLOPE_INTERRUPT_X_NEGATIVE_HAPPENED 10 +#define SLOPE_INTERRUPT_Y_NEGATIVE_HAPPENED 11 +#define SLOPE_INTERRUPT_Z_NEGATIVE_HAPPENED 12 +#define DOUBLE_TAP_INTERRUPT_HAPPENED 13 +#define SINGLE_TAP_INTERRUPT_HAPPENED 14 +#define UPWARD_PORTRAIT_UP_INTERRUPT_HAPPENED 15 +#define UPWARD_PORTRAIT_DOWN_INTERRUPT_HAPPENED 16 +#define UPWARD_LANDSCAPE_LEFT_INTERRUPT_HAPPENED 17 +#define UPWARD_LANDSCAPE_RIGHT_INTERRUPT_HAPPENED 18 +#define DOWNWARD_PORTRAIT_UP_INTERRUPT_HAPPENED 19 +#define DOWNWARD_PORTRAIT_DOWN_INTERRUPT_HAPPENED 20 +#define DOWNWARD_LANDSCAPE_LEFT_INTERRUPT_HAPPENED 21 +#define DOWNWARD_LANDSCAPE_RIGHT_INTERRUPT_HAPPENED 22 +#define FLAT_INTERRUPT_TURE_HAPPENED 23 +#define FLAT_INTERRUPT_FALSE_HAPPENED 24 +#define LOW_G_INTERRUPT_HAPPENED 25 +#define SLOW_NO_MOTION_INTERRUPT_HAPPENED 26 + +#define PAD_LOWG 0 +#define PAD_HIGHG 1 +#define PAD_SLOP 2 +#define PAD_DOUBLE_TAP 3 +#define PAD_SINGLE_TAP 4 +#define PAD_ORIENT 5 +#define PAD_FLAT 6 +#define PAD_SLOW_NO_MOTION 7 + +#define BMA2X2_EEP_OFFSET 0x16 +#define BMA2X2_IMAGE_BASE 0x38 +#define BMA2X2_IMAGE_LEN 22 + +#define BMA2X2_CHIP_ID_REG 0x00 +#define BMA2X2_VERSION_REG 0x01 +#define BMA2X2_X_AXIS_LSB_REG 0x02 +#define BMA2X2_X_AXIS_MSB_REG 0x03 +#define BMA2X2_Y_AXIS_LSB_REG 0x04 +#define BMA2X2_Y_AXIS_MSB_REG 0x05 +#define BMA2X2_Z_AXIS_LSB_REG 0x06 +#define BMA2X2_Z_AXIS_MSB_REG 0x07 +#define BMA2X2_TEMPERATURE_REG 0x08 +#define BMA2X2_STATUS1_REG 0x09 +#define BMA2X2_STATUS2_REG 0x0A +#define BMA2X2_STATUS_TAP_SLOPE_REG 0x0B +#define BMA2X2_STATUS_ORIENT_HIGH_REG 0x0C +#define BMA2X2_STATUS_FIFO_REG 0x0E +#define BMA2X2_RANGE_SEL_REG 0x0F +#define BMA2X2_BW_SEL_REG 0x10 +#define BMA2X2_MODE_CTRL_REG 0x11 +#define BMA2X2_LOW_NOISE_CTRL_REG 0x12 +#define BMA2X2_DATA_CTRL_REG 0x13 +#define BMA2X2_RESET_REG 0x14 +#define BMA2X2_INT_ENABLE1_REG 0x16 +#define BMA2X2_INT_ENABLE2_REG 0x17 +#define BMA2X2_INT_SLO_NO_MOT_REG 0x18 +#define BMA2X2_INT1_PAD_SEL_REG 0x19 +#define BMA2X2_INT_DATA_SEL_REG 0x1A +#define BMA2X2_INT2_PAD_SEL_REG 0x1B +#define BMA2X2_INT_SRC_REG 0x1E +#define BMA2X2_INT_SET_REG 0x20 +#define BMA2X2_INT_CTRL_REG 0x21 +#define BMA2X2_LOW_DURN_REG 0x22 +#define BMA2X2_LOW_THRES_REG 0x23 +#define BMA2X2_LOW_HIGH_HYST_REG 0x24 +#define BMA2X2_HIGH_DURN_REG 0x25 +#define BMA2X2_HIGH_THRES_REG 0x26 +#define BMA2X2_SLOPE_DURN_REG 0x27 +#define BMA2X2_SLOPE_THRES_REG 0x28 +#define BMA2X2_SLO_NO_MOT_THRES_REG 0x29 +#define BMA2X2_TAP_PARAM_REG 0x2A +#define BMA2X2_TAP_THRES_REG 0x2B +#define BMA2X2_ORIENT_PARAM_REG 0x2C +#define BMA2X2_THETA_BLOCK_REG 0x2D +#define BMA2X2_THETA_FLAT_REG 0x2E +#define BMA2X2_FLAT_HOLD_TIME_REG 0x2F +#define BMA2X2_FIFO_WML_TRIG 0x30 +#define BMA2X2_SELF_TEST_REG 0x32 +#define BMA2X2_EEPROM_CTRL_REG 0x33 +#define BMA2X2_SERIAL_CTRL_REG 0x34 +#define BMA2X2_EXTMODE_CTRL_REG 0x35 +#define BMA2X2_OFFSET_CTRL_REG 0x36 +#define BMA2X2_OFFSET_PARAMS_REG 0x37 +#define BMA2X2_OFFSET_X_AXIS_REG 0x38 +#define BMA2X2_OFFSET_Y_AXIS_REG 0x39 +#define BMA2X2_OFFSET_Z_AXIS_REG 0x3A +#define BMA2X2_GP0_REG 0x3B +#define BMA2X2_GP1_REG 0x3C +#define BMA2X2_FIFO_MODE_REG 0x3E +#define BMA2X2_FIFO_DATA_OUTPUT_REG 0x3F + +#define BMA2X2_CHIP_ID__POS 0 +#define BMA2X2_CHIP_ID__MSK 0xFF +#define BMA2X2_CHIP_ID__LEN 8 +#define BMA2X2_CHIP_ID__REG BMA2X2_CHIP_ID_REG + +#define BMA2X2_VERSION__POS 0 +#define BMA2X2_VERSION__LEN 8 +#define BMA2X2_VERSION__MSK 0xFF +#define BMA2X2_VERSION__REG BMA2X2_VERSION_REG + +#define BMA2x2_SLO_NO_MOT_DUR__POS 2 +#define BMA2x2_SLO_NO_MOT_DUR__LEN 6 +#define BMA2x2_SLO_NO_MOT_DUR__MSK 0xFC +#define BMA2x2_SLO_NO_MOT_DUR__REG BMA2X2_SLOPE_DURN_REG + +#define BMA2X2_NEW_DATA_X__POS 0 +#define BMA2X2_NEW_DATA_X__LEN 1 +#define BMA2X2_NEW_DATA_X__MSK 0x01 +#define BMA2X2_NEW_DATA_X__REG BMA2X2_X_AXIS_LSB_REG + +#define BMA2X2_ACC_X14_LSB__POS 2 +#define BMA2X2_ACC_X14_LSB__LEN 6 +#define BMA2X2_ACC_X14_LSB__MSK 0xFC +#define BMA2X2_ACC_X14_LSB__REG BMA2X2_X_AXIS_LSB_REG + +#define BMA2X2_ACC_X12_LSB__POS 4 +#define BMA2X2_ACC_X12_LSB__LEN 4 +#define BMA2X2_ACC_X12_LSB__MSK 0xF0 +#define BMA2X2_ACC_X12_LSB__REG BMA2X2_X_AXIS_LSB_REG + +#define BMA2X2_ACC_X10_LSB__POS 6 +#define BMA2X2_ACC_X10_LSB__LEN 2 +#define BMA2X2_ACC_X10_LSB__MSK 0xC0 +#define BMA2X2_ACC_X10_LSB__REG BMA2X2_X_AXIS_LSB_REG + +#define BMA2X2_ACC_X8_LSB__POS 0 +#define BMA2X2_ACC_X8_LSB__LEN 0 +#define BMA2X2_ACC_X8_LSB__MSK 0x00 +#define BMA2X2_ACC_X8_LSB__REG BMA2X2_X_AXIS_LSB_REG + +#define BMA2X2_ACC_X_MSB__POS 0 +#define BMA2X2_ACC_X_MSB__LEN 8 +#define BMA2X2_ACC_X_MSB__MSK 0xFF +#define BMA2X2_ACC_X_MSB__REG BMA2X2_X_AXIS_MSB_REG + +#define BMA2X2_NEW_DATA_Y__POS 0 +#define BMA2X2_NEW_DATA_Y__LEN 1 +#define BMA2X2_NEW_DATA_Y__MSK 0x01 +#define BMA2X2_NEW_DATA_Y__REG BMA2X2_Y_AXIS_LSB_REG + +#define BMA2X2_ACC_Y14_LSB__POS 2 +#define BMA2X2_ACC_Y14_LSB__LEN 6 +#define BMA2X2_ACC_Y14_LSB__MSK 0xFC +#define BMA2X2_ACC_Y14_LSB__REG BMA2X2_Y_AXIS_LSB_REG + +#define BMA2X2_ACC_Y12_LSB__POS 4 +#define BMA2X2_ACC_Y12_LSB__LEN 4 +#define BMA2X2_ACC_Y12_LSB__MSK 0xF0 +#define BMA2X2_ACC_Y12_LSB__REG BMA2X2_Y_AXIS_LSB_REG + +#define BMA2X2_ACC_Y10_LSB__POS 6 +#define BMA2X2_ACC_Y10_LSB__LEN 2 +#define BMA2X2_ACC_Y10_LSB__MSK 0xC0 +#define BMA2X2_ACC_Y10_LSB__REG BMA2X2_Y_AXIS_LSB_REG + +#define BMA2X2_ACC_Y8_LSB__POS 0 +#define BMA2X2_ACC_Y8_LSB__LEN 0 +#define BMA2X2_ACC_Y8_LSB__MSK 0x00 +#define BMA2X2_ACC_Y8_LSB__REG BMA2X2_Y_AXIS_LSB_REG + +#define BMA2X2_ACC_Y_MSB__POS 0 +#define BMA2X2_ACC_Y_MSB__LEN 8 +#define BMA2X2_ACC_Y_MSB__MSK 0xFF +#define BMA2X2_ACC_Y_MSB__REG BMA2X2_Y_AXIS_MSB_REG + +#define BMA2X2_NEW_DATA_Z__POS 0 +#define BMA2X2_NEW_DATA_Z__LEN 1 +#define BMA2X2_NEW_DATA_Z__MSK 0x01 +#define BMA2X2_NEW_DATA_Z__REG BMA2X2_Z_AXIS_LSB_REG + +#define BMA2X2_ACC_Z14_LSB__POS 2 +#define BMA2X2_ACC_Z14_LSB__LEN 6 +#define BMA2X2_ACC_Z14_LSB__MSK 0xFC +#define BMA2X2_ACC_Z14_LSB__REG BMA2X2_Z_AXIS_LSB_REG + +#define BMA2X2_ACC_Z12_LSB__POS 4 +#define BMA2X2_ACC_Z12_LSB__LEN 4 +#define BMA2X2_ACC_Z12_LSB__MSK 0xF0 +#define BMA2X2_ACC_Z12_LSB__REG BMA2X2_Z_AXIS_LSB_REG + +#define BMA2X2_ACC_Z10_LSB__POS 6 +#define BMA2X2_ACC_Z10_LSB__LEN 2 +#define BMA2X2_ACC_Z10_LSB__MSK 0xC0 +#define BMA2X2_ACC_Z10_LSB__REG BMA2X2_Z_AXIS_LSB_REG + +#define BMA2X2_ACC_Z8_LSB__POS 0 +#define BMA2X2_ACC_Z8_LSB__LEN 0 +#define BMA2X2_ACC_Z8_LSB__MSK 0x00 +#define BMA2X2_ACC_Z8_LSB__REG BMA2X2_Z_AXIS_LSB_REG + +#define BMA2X2_ACC_Z_MSB__POS 0 +#define BMA2X2_ACC_Z_MSB__LEN 8 +#define BMA2X2_ACC_Z_MSB__MSK 0xFF +#define BMA2X2_ACC_Z_MSB__REG BMA2X2_Z_AXIS_MSB_REG + +#define BMA2X2_TEMPERATURE__POS 0 +#define BMA2X2_TEMPERATURE__LEN 8 +#define BMA2X2_TEMPERATURE__MSK 0xFF +#define BMA2X2_TEMPERATURE__REG BMA2X2_TEMP_RD_REG + +#define BMA2X2_LOWG_INT_S__POS 0 +#define BMA2X2_LOWG_INT_S__LEN 1 +#define BMA2X2_LOWG_INT_S__MSK 0x01 +#define BMA2X2_LOWG_INT_S__REG BMA2X2_STATUS1_REG + +#define BMA2X2_HIGHG_INT_S__POS 1 +#define BMA2X2_HIGHG_INT_S__LEN 1 +#define BMA2X2_HIGHG_INT_S__MSK 0x02 +#define BMA2X2_HIGHG_INT_S__REG BMA2X2_STATUS1_REG + +#define BMA2X2_SLOPE_INT_S__POS 2 +#define BMA2X2_SLOPE_INT_S__LEN 1 +#define BMA2X2_SLOPE_INT_S__MSK 0x04 +#define BMA2X2_SLOPE_INT_S__REG BMA2X2_STATUS1_REG + + +#define BMA2X2_SLO_NO_MOT_INT_S__POS 3 +#define BMA2X2_SLO_NO_MOT_INT_S__LEN 1 +#define BMA2X2_SLO_NO_MOT_INT_S__MSK 0x08 +#define BMA2X2_SLO_NO_MOT_INT_S__REG BMA2X2_STATUS1_REG + +#define BMA2X2_DOUBLE_TAP_INT_S__POS 4 +#define BMA2X2_DOUBLE_TAP_INT_S__LEN 1 +#define BMA2X2_DOUBLE_TAP_INT_S__MSK 0x10 +#define BMA2X2_DOUBLE_TAP_INT_S__REG BMA2X2_STATUS1_REG + +#define BMA2X2_SINGLE_TAP_INT_S__POS 5 +#define BMA2X2_SINGLE_TAP_INT_S__LEN 1 +#define BMA2X2_SINGLE_TAP_INT_S__MSK 0x20 +#define BMA2X2_SINGLE_TAP_INT_S__REG BMA2X2_STATUS1_REG + +#define BMA2X2_ORIENT_INT_S__POS 6 +#define BMA2X2_ORIENT_INT_S__LEN 1 +#define BMA2X2_ORIENT_INT_S__MSK 0x40 +#define BMA2X2_ORIENT_INT_S__REG BMA2X2_STATUS1_REG + +#define BMA2X2_FLAT_INT_S__POS 7 +#define BMA2X2_FLAT_INT_S__LEN 1 +#define BMA2X2_FLAT_INT_S__MSK 0x80 +#define BMA2X2_FLAT_INT_S__REG BMA2X2_STATUS1_REG + +#define BMA2X2_FIFO_FULL_INT_S__POS 5 +#define BMA2X2_FIFO_FULL_INT_S__LEN 1 +#define BMA2X2_FIFO_FULL_INT_S__MSK 0x20 +#define BMA2X2_FIFO_FULL_INT_S__REG BMA2X2_STATUS2_REG + +#define BMA2X2_FIFO_WM_INT_S__POS 6 +#define BMA2X2_FIFO_WM_INT_S__LEN 1 +#define BMA2X2_FIFO_WM_INT_S__MSK 0x40 +#define BMA2X2_FIFO_WM_INT_S__REG BMA2X2_STATUS2_REG + +#define BMA2X2_DATA_INT_S__POS 7 +#define BMA2X2_DATA_INT_S__LEN 1 +#define BMA2X2_DATA_INT_S__MSK 0x80 +#define BMA2X2_DATA_INT_S__REG BMA2X2_STATUS2_REG + +#define BMA2X2_SLOPE_FIRST_X__POS 0 +#define BMA2X2_SLOPE_FIRST_X__LEN 1 +#define BMA2X2_SLOPE_FIRST_X__MSK 0x01 +#define BMA2X2_SLOPE_FIRST_X__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_SLOPE_FIRST_Y__POS 1 +#define BMA2X2_SLOPE_FIRST_Y__LEN 1 +#define BMA2X2_SLOPE_FIRST_Y__MSK 0x02 +#define BMA2X2_SLOPE_FIRST_Y__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_SLOPE_FIRST_Z__POS 2 +#define BMA2X2_SLOPE_FIRST_Z__LEN 1 +#define BMA2X2_SLOPE_FIRST_Z__MSK 0x04 +#define BMA2X2_SLOPE_FIRST_Z__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_SLOPE_SIGN_S__POS 3 +#define BMA2X2_SLOPE_SIGN_S__LEN 1 +#define BMA2X2_SLOPE_SIGN_S__MSK 0x08 +#define BMA2X2_SLOPE_SIGN_S__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_TAP_FIRST_X__POS 4 +#define BMA2X2_TAP_FIRST_X__LEN 1 +#define BMA2X2_TAP_FIRST_X__MSK 0x10 +#define BMA2X2_TAP_FIRST_X__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_TAP_FIRST_Y__POS 5 +#define BMA2X2_TAP_FIRST_Y__LEN 1 +#define BMA2X2_TAP_FIRST_Y__MSK 0x20 +#define BMA2X2_TAP_FIRST_Y__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_TAP_FIRST_Z__POS 6 +#define BMA2X2_TAP_FIRST_Z__LEN 1 +#define BMA2X2_TAP_FIRST_Z__MSK 0x40 +#define BMA2X2_TAP_FIRST_Z__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_TAP_SIGN_S__POS 7 +#define BMA2X2_TAP_SIGN_S__LEN 1 +#define BMA2X2_TAP_SIGN_S__MSK 0x80 +#define BMA2X2_TAP_SIGN_S__REG BMA2X2_STATUS_TAP_SLOPE_REG + +#define BMA2X2_HIGHG_FIRST_X__POS 0 +#define BMA2X2_HIGHG_FIRST_X__LEN 1 +#define BMA2X2_HIGHG_FIRST_X__MSK 0x01 +#define BMA2X2_HIGHG_FIRST_X__REG BMA2X2_STATUS_ORIENT_HIGH_REG + +#define BMA2X2_HIGHG_FIRST_Y__POS 1 +#define BMA2X2_HIGHG_FIRST_Y__LEN 1 +#define BMA2X2_HIGHG_FIRST_Y__MSK 0x02 +#define BMA2X2_HIGHG_FIRST_Y__REG BMA2X2_STATUS_ORIENT_HIGH_REG + +#define BMA2X2_HIGHG_FIRST_Z__POS 2 +#define BMA2X2_HIGHG_FIRST_Z__LEN 1 +#define BMA2X2_HIGHG_FIRST_Z__MSK 0x04 +#define BMA2X2_HIGHG_FIRST_Z__REG BMA2X2_STATUS_ORIENT_HIGH_REG + +#define BMA2X2_HIGHG_SIGN_S__POS 3 +#define BMA2X2_HIGHG_SIGN_S__LEN 1 +#define BMA2X2_HIGHG_SIGN_S__MSK 0x08 +#define BMA2X2_HIGHG_SIGN_S__REG BMA2X2_STATUS_ORIENT_HIGH_REG + +#define BMA2X2_ORIENT_S__POS 4 +#define BMA2X2_ORIENT_S__LEN 3 +#define BMA2X2_ORIENT_S__MSK 0x70 +#define BMA2X2_ORIENT_S__REG BMA2X2_STATUS_ORIENT_HIGH_REG + +#define BMA2X2_FLAT_S__POS 7 +#define BMA2X2_FLAT_S__LEN 1 +#define BMA2X2_FLAT_S__MSK 0x80 +#define BMA2X2_FLAT_S__REG BMA2X2_STATUS_ORIENT_HIGH_REG + +#define BMA2X2_FIFO_FRAME_COUNTER_S__POS 0 +#define BMA2X2_FIFO_FRAME_COUNTER_S__LEN 7 +#define BMA2X2_FIFO_FRAME_COUNTER_S__MSK 0x7F +#define BMA2X2_FIFO_FRAME_COUNTER_S__REG BMA2X2_STATUS_FIFO_REG +#define BMA2X2_FIFO_OVERRUN_S__POS 7 +#define BMA2X2_FIFO_OVERRUN_S__LEN 1 +#define BMA2X2_FIFO_OVERRUN_S__MSK 0x80 +#define BMA2X2_FIFO_OVERRUN_S__REG BMA2X2_STATUS_FIFO_REG + +#define BMA2X2_RANGE_SEL__POS 0 +#define BMA2X2_RANGE_SEL__LEN 4 +#define BMA2X2_RANGE_SEL__MSK 0x0F +#define BMA2X2_RANGE_SEL__REG BMA2X2_RANGE_SEL_REG + +#define BMA2X2_BANDWIDTH__POS 0 +#define BMA2X2_BANDWIDTH__LEN 5 +#define BMA2X2_BANDWIDTH__MSK 0x1F +#define BMA2X2_BANDWIDTH__REG BMA2X2_BW_SEL_REG + +#define BMA2X2_SLEEP_DUR__POS 1 +#define BMA2X2_SLEEP_DUR__LEN 4 +#define BMA2X2_SLEEP_DUR__MSK 0x1E +#define BMA2X2_SLEEP_DUR__REG BMA2X2_MODE_CTRL_REG + +#define BMA2X2_MODE_CTRL__POS 5 +#define BMA2X2_MODE_CTRL__LEN 3 +#define BMA2X2_MODE_CTRL__MSK 0xE0 +#define BMA2X2_MODE_CTRL__REG BMA2X2_MODE_CTRL_REG + +#define BMA2X2_DEEP_SUSPEND__POS 5 +#define BMA2X2_DEEP_SUSPEND__LEN 1 +#define BMA2X2_DEEP_SUSPEND__MSK 0x20 +#define BMA2X2_DEEP_SUSPEND__REG BMA2X2_MODE_CTRL_REG + +#define BMA2X2_EN_LOW_POWER__POS 6 +#define BMA2X2_EN_LOW_POWER__LEN 1 +#define BMA2X2_EN_LOW_POWER__MSK 0x40 +#define BMA2X2_EN_LOW_POWER__REG BMA2X2_MODE_CTRL_REG + +#define BMA2X2_EN_SUSPEND__POS 7 +#define BMA2X2_EN_SUSPEND__LEN 1 +#define BMA2X2_EN_SUSPEND__MSK 0x80 +#define BMA2X2_EN_SUSPEND__REG BMA2X2_MODE_CTRL_REG + +#define BMA2X2_SLEEP_TIMER__POS 5 +#define BMA2X2_SLEEP_TIMER__LEN 1 +#define BMA2X2_SLEEP_TIMER__MSK 0x20 +#define BMA2X2_SLEEP_TIMER__REG BMA2X2_LOW_NOISE_CTRL_REG + +#define BMA2X2_LOW_POWER_MODE__POS 6 +#define BMA2X2_LOW_POWER_MODE__LEN 1 +#define BMA2X2_LOW_POWER_MODE__MSK 0x40 +#define BMA2X2_LOW_POWER_MODE__REG BMA2X2_LOW_NOISE_CTRL_REG + +#define BMA2X2_EN_LOW_NOISE__POS 7 +#define BMA2X2_EN_LOW_NOISE__LEN 1 +#define BMA2X2_EN_LOW_NOISE__MSK 0x80 +#define BMA2X2_EN_LOW_NOISE__REG BMA2X2_LOW_NOISE_CTRL_REG + +#define BMA2X2_DIS_SHADOW_PROC__POS 6 +#define BMA2X2_DIS_SHADOW_PROC__LEN 1 +#define BMA2X2_DIS_SHADOW_PROC__MSK 0x40 +#define BMA2X2_DIS_SHADOW_PROC__REG BMA2X2_DATA_CTRL_REG + +#define BMA2X2_EN_DATA_HIGH_BW__POS 7 +#define BMA2X2_EN_DATA_HIGH_BW__LEN 1 +#define BMA2X2_EN_DATA_HIGH_BW__MSK 0x80 +#define BMA2X2_EN_DATA_HIGH_BW__REG BMA2X2_DATA_CTRL_REG + +#define BMA2X2_EN_SOFT_RESET__POS 0 +#define BMA2X2_EN_SOFT_RESET__LEN 8 +#define BMA2X2_EN_SOFT_RESET__MSK 0xFF +#define BMA2X2_EN_SOFT_RESET__REG BMA2X2_RESET_REG + +#define BMA2X2_EN_SOFT_RESET_VALUE 0xB6 + +#define BMA2X2_EN_SLOPE_X_INT__POS 0 +#define BMA2X2_EN_SLOPE_X_INT__LEN 1 +#define BMA2X2_EN_SLOPE_X_INT__MSK 0x01 +#define BMA2X2_EN_SLOPE_X_INT__REG BMA2X2_INT_ENABLE1_REG + +#define BMA2X2_EN_SLOPE_Y_INT__POS 1 +#define BMA2X2_EN_SLOPE_Y_INT__LEN 1 +#define BMA2X2_EN_SLOPE_Y_INT__MSK 0x02 +#define BMA2X2_EN_SLOPE_Y_INT__REG BMA2X2_INT_ENABLE1_REG + +#define BMA2X2_EN_SLOPE_Z_INT__POS 2 +#define BMA2X2_EN_SLOPE_Z_INT__LEN 1 +#define BMA2X2_EN_SLOPE_Z_INT__MSK 0x04 +#define BMA2X2_EN_SLOPE_Z_INT__REG BMA2X2_INT_ENABLE1_REG + +#define BMA2X2_EN_DOUBLE_TAP_INT__POS 4 +#define BMA2X2_EN_DOUBLE_TAP_INT__LEN 1 +#define BMA2X2_EN_DOUBLE_TAP_INT__MSK 0x10 +#define BMA2X2_EN_DOUBLE_TAP_INT__REG BMA2X2_INT_ENABLE1_REG + +#define BMA2X2_EN_SINGLE_TAP_INT__POS 5 +#define BMA2X2_EN_SINGLE_TAP_INT__LEN 1 +#define BMA2X2_EN_SINGLE_TAP_INT__MSK 0x20 +#define BMA2X2_EN_SINGLE_TAP_INT__REG BMA2X2_INT_ENABLE1_REG + +#define BMA2X2_EN_ORIENT_INT__POS 6 +#define BMA2X2_EN_ORIENT_INT__LEN 1 +#define BMA2X2_EN_ORIENT_INT__MSK 0x40 +#define BMA2X2_EN_ORIENT_INT__REG BMA2X2_INT_ENABLE1_REG + +#define BMA2X2_EN_FLAT_INT__POS 7 +#define BMA2X2_EN_FLAT_INT__LEN 1 +#define BMA2X2_EN_FLAT_INT__MSK 0x80 +#define BMA2X2_EN_FLAT_INT__REG BMA2X2_INT_ENABLE1_REG + +#define BMA2X2_EN_HIGHG_X_INT__POS 0 +#define BMA2X2_EN_HIGHG_X_INT__LEN 1 +#define BMA2X2_EN_HIGHG_X_INT__MSK 0x01 +#define BMA2X2_EN_HIGHG_X_INT__REG BMA2X2_INT_ENABLE2_REG + +#define BMA2X2_EN_HIGHG_Y_INT__POS 1 +#define BMA2X2_EN_HIGHG_Y_INT__LEN 1 +#define BMA2X2_EN_HIGHG_Y_INT__MSK 0x02 +#define BMA2X2_EN_HIGHG_Y_INT__REG BMA2X2_INT_ENABLE2_REG + +#define BMA2X2_EN_HIGHG_Z_INT__POS 2 +#define BMA2X2_EN_HIGHG_Z_INT__LEN 1 +#define BMA2X2_EN_HIGHG_Z_INT__MSK 0x04 +#define BMA2X2_EN_HIGHG_Z_INT__REG BMA2X2_INT_ENABLE2_REG + +#define BMA2X2_EN_LOWG_INT__POS 3 +#define BMA2X2_EN_LOWG_INT__LEN 1 +#define BMA2X2_EN_LOWG_INT__MSK 0x08 +#define BMA2X2_EN_LOWG_INT__REG BMA2X2_INT_ENABLE2_REG + +#define BMA2X2_EN_NEW_DATA_INT__POS 4 +#define BMA2X2_EN_NEW_DATA_INT__LEN 1 +#define BMA2X2_EN_NEW_DATA_INT__MSK 0x10 +#define BMA2X2_EN_NEW_DATA_INT__REG BMA2X2_INT_ENABLE2_REG + +#define BMA2X2_INT_FFULL_EN_INT__POS 5 +#define BMA2X2_INT_FFULL_EN_INT__LEN 1 +#define BMA2X2_INT_FFULL_EN_INT__MSK 0x20 +#define BMA2X2_INT_FFULL_EN_INT__REG BMA2X2_INT_ENABLE2_REG + +#define BMA2X2_INT_FWM_EN_INT__POS 6 +#define BMA2X2_INT_FWM_EN_INT__LEN 1 +#define BMA2X2_INT_FWM_EN_INT__MSK 0x40 +#define BMA2X2_INT_FWM_EN_INT__REG BMA2X2_INT_ENABLE2_REG + +#define BMA2X2_INT_SLO_NO_MOT_EN_X_INT__POS 0 +#define BMA2X2_INT_SLO_NO_MOT_EN_X_INT__LEN 1 +#define BMA2X2_INT_SLO_NO_MOT_EN_X_INT__MSK 0x01 +#define BMA2X2_INT_SLO_NO_MOT_EN_X_INT__REG BMA2X2_INT_SLO_NO_MOT_REG + +#define BMA2X2_INT_SLO_NO_MOT_EN_Y_INT__POS 1 +#define BMA2X2_INT_SLO_NO_MOT_EN_Y_INT__LEN 1 +#define BMA2X2_INT_SLO_NO_MOT_EN_Y_INT__MSK 0x02 +#define BMA2X2_INT_SLO_NO_MOT_EN_Y_INT__REG BMA2X2_INT_SLO_NO_MOT_REG + +#define BMA2X2_INT_SLO_NO_MOT_EN_Z_INT__POS 2 +#define BMA2X2_INT_SLO_NO_MOT_EN_Z_INT__LEN 1 +#define BMA2X2_INT_SLO_NO_MOT_EN_Z_INT__MSK 0x04 +#define BMA2X2_INT_SLO_NO_MOT_EN_Z_INT__REG BMA2X2_INT_SLO_NO_MOT_REG + +#define BMA2X2_INT_SLO_NO_MOT_EN_SEL_INT__POS 3 +#define BMA2X2_INT_SLO_NO_MOT_EN_SEL_INT__LEN 1 +#define BMA2X2_INT_SLO_NO_MOT_EN_SEL_INT__MSK 0x08 +#define BMA2X2_INT_SLO_NO_MOT_EN_SEL_INT__REG BMA2X2_INT_SLO_NO_MOT_REG + +#define BMA2X2_EN_INT1_PAD_LOWG__POS 0 +#define BMA2X2_EN_INT1_PAD_LOWG__LEN 1 +#define BMA2X2_EN_INT1_PAD_LOWG__MSK 0x01 +#define BMA2X2_EN_INT1_PAD_LOWG__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_HIGHG__POS 1 +#define BMA2X2_EN_INT1_PAD_HIGHG__LEN 1 +#define BMA2X2_EN_INT1_PAD_HIGHG__MSK 0x02 +#define BMA2X2_EN_INT1_PAD_HIGHG__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_SLOPE__POS 2 +#define BMA2X2_EN_INT1_PAD_SLOPE__LEN 1 +#define BMA2X2_EN_INT1_PAD_SLOPE__MSK 0x04 +#define BMA2X2_EN_INT1_PAD_SLOPE__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_SLO_NO_MOT__POS 3 +#define BMA2X2_EN_INT1_PAD_SLO_NO_MOT__LEN 1 +#define BMA2X2_EN_INT1_PAD_SLO_NO_MOT__MSK 0x08 +#define BMA2X2_EN_INT1_PAD_SLO_NO_MOT__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_DB_TAP__POS 4 +#define BMA2X2_EN_INT1_PAD_DB_TAP__LEN 1 +#define BMA2X2_EN_INT1_PAD_DB_TAP__MSK 0x10 +#define BMA2X2_EN_INT1_PAD_DB_TAP__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_SNG_TAP__POS 5 +#define BMA2X2_EN_INT1_PAD_SNG_TAP__LEN 1 +#define BMA2X2_EN_INT1_PAD_SNG_TAP__MSK 0x20 +#define BMA2X2_EN_INT1_PAD_SNG_TAP__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_ORIENT__POS 6 +#define BMA2X2_EN_INT1_PAD_ORIENT__LEN 1 +#define BMA2X2_EN_INT1_PAD_ORIENT__MSK 0x40 +#define BMA2X2_EN_INT1_PAD_ORIENT__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_FLAT__POS 7 +#define BMA2X2_EN_INT1_PAD_FLAT__LEN 1 +#define BMA2X2_EN_INT1_PAD_FLAT__MSK 0x80 +#define BMA2X2_EN_INT1_PAD_FLAT__REG BMA2X2_INT1_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_LOWG__POS 0 +#define BMA2X2_EN_INT2_PAD_LOWG__LEN 1 +#define BMA2X2_EN_INT2_PAD_LOWG__MSK 0x01 +#define BMA2X2_EN_INT2_PAD_LOWG__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_HIGHG__POS 1 +#define BMA2X2_EN_INT2_PAD_HIGHG__LEN 1 +#define BMA2X2_EN_INT2_PAD_HIGHG__MSK 0x02 +#define BMA2X2_EN_INT2_PAD_HIGHG__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_SLOPE__POS 2 +#define BMA2X2_EN_INT2_PAD_SLOPE__LEN 1 +#define BMA2X2_EN_INT2_PAD_SLOPE__MSK 0x04 +#define BMA2X2_EN_INT2_PAD_SLOPE__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_SLO_NO_MOT__POS 3 +#define BMA2X2_EN_INT2_PAD_SLO_NO_MOT__LEN 1 +#define BMA2X2_EN_INT2_PAD_SLO_NO_MOT__MSK 0x08 +#define BMA2X2_EN_INT2_PAD_SLO_NO_MOT__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_DB_TAP__POS 4 +#define BMA2X2_EN_INT2_PAD_DB_TAP__LEN 1 +#define BMA2X2_EN_INT2_PAD_DB_TAP__MSK 0x10 +#define BMA2X2_EN_INT2_PAD_DB_TAP__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_SNG_TAP__POS 5 +#define BMA2X2_EN_INT2_PAD_SNG_TAP__LEN 1 +#define BMA2X2_EN_INT2_PAD_SNG_TAP__MSK 0x20 +#define BMA2X2_EN_INT2_PAD_SNG_TAP__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_ORIENT__POS 6 +#define BMA2X2_EN_INT2_PAD_ORIENT__LEN 1 +#define BMA2X2_EN_INT2_PAD_ORIENT__MSK 0x40 +#define BMA2X2_EN_INT2_PAD_ORIENT__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT2_PAD_FLAT__POS 7 +#define BMA2X2_EN_INT2_PAD_FLAT__LEN 1 +#define BMA2X2_EN_INT2_PAD_FLAT__MSK 0x80 +#define BMA2X2_EN_INT2_PAD_FLAT__REG BMA2X2_INT2_PAD_SEL_REG + +#define BMA2X2_EN_INT1_PAD_NEWDATA__POS 0 +#define BMA2X2_EN_INT1_PAD_NEWDATA__LEN 1 +#define BMA2X2_EN_INT1_PAD_NEWDATA__MSK 0x01 +#define BMA2X2_EN_INT1_PAD_NEWDATA__REG BMA2X2_INT_DATA_SEL_REG + +#define BMA2X2_EN_INT1_PAD_FWM__POS 1 +#define BMA2X2_EN_INT1_PAD_FWM__LEN 1 +#define BMA2X2_EN_INT1_PAD_FWM__MSK 0x02 +#define BMA2X2_EN_INT1_PAD_FWM__REG BMA2X2_INT_DATA_SEL_REG + +#define BMA2X2_EN_INT1_PAD_FFULL__POS 2 +#define BMA2X2_EN_INT1_PAD_FFULL__LEN 1 +#define BMA2X2_EN_INT1_PAD_FFULL__MSK 0x04 +#define BMA2X2_EN_INT1_PAD_FFULL__REG BMA2X2_INT_DATA_SEL_REG + +#define BMA2X2_EN_INT2_PAD_FFULL__POS 5 +#define BMA2X2_EN_INT2_PAD_FFULL__LEN 1 +#define BMA2X2_EN_INT2_PAD_FFULL__MSK 0x20 +#define BMA2X2_EN_INT2_PAD_FFULL__REG BMA2X2_INT_DATA_SEL_REG + +#define BMA2X2_EN_INT2_PAD_FWM__POS 6 +#define BMA2X2_EN_INT2_PAD_FWM__LEN 1 +#define BMA2X2_EN_INT2_PAD_FWM__MSK 0x40 +#define BMA2X2_EN_INT2_PAD_FWM__REG BMA2X2_INT_DATA_SEL_REG + +#define BMA2X2_EN_INT2_PAD_NEWDATA__POS 7 +#define BMA2X2_EN_INT2_PAD_NEWDATA__LEN 1 +#define BMA2X2_EN_INT2_PAD_NEWDATA__MSK 0x80 +#define BMA2X2_EN_INT2_PAD_NEWDATA__REG BMA2X2_INT_DATA_SEL_REG + +#define BMA2X2_UNFILT_INT_SRC_LOWG__POS 0 +#define BMA2X2_UNFILT_INT_SRC_LOWG__LEN 1 +#define BMA2X2_UNFILT_INT_SRC_LOWG__MSK 0x01 +#define BMA2X2_UNFILT_INT_SRC_LOWG__REG BMA2X2_INT_SRC_REG + +#define BMA2X2_UNFILT_INT_SRC_HIGHG__POS 1 +#define BMA2X2_UNFILT_INT_SRC_HIGHG__LEN 1 +#define BMA2X2_UNFILT_INT_SRC_HIGHG__MSK 0x02 +#define BMA2X2_UNFILT_INT_SRC_HIGHG__REG BMA2X2_INT_SRC_REG + +#define BMA2X2_UNFILT_INT_SRC_SLOPE__POS 2 +#define BMA2X2_UNFILT_INT_SRC_SLOPE__LEN 1 +#define BMA2X2_UNFILT_INT_SRC_SLOPE__MSK 0x04 +#define BMA2X2_UNFILT_INT_SRC_SLOPE__REG BMA2X2_INT_SRC_REG + +#define BMA2X2_UNFILT_INT_SRC_SLO_NO_MOT__POS 3 +#define BMA2X2_UNFILT_INT_SRC_SLO_NO_MOT__LEN 1 +#define BMA2X2_UNFILT_INT_SRC_SLO_NO_MOT__MSK 0x08 +#define BMA2X2_UNFILT_INT_SRC_SLO_NO_MOT__REG BMA2X2_INT_SRC_REG + +#define BMA2X2_UNFILT_INT_SRC_TAP__POS 4 +#define BMA2X2_UNFILT_INT_SRC_TAP__LEN 1 +#define BMA2X2_UNFILT_INT_SRC_TAP__MSK 0x10 +#define BMA2X2_UNFILT_INT_SRC_TAP__REG BMA2X2_INT_SRC_REG + +#define BMA2X2_UNFILT_INT_SRC_DATA__POS 5 +#define BMA2X2_UNFILT_INT_SRC_DATA__LEN 1 +#define BMA2X2_UNFILT_INT_SRC_DATA__MSK 0x20 +#define BMA2X2_UNFILT_INT_SRC_DATA__REG BMA2X2_INT_SRC_REG + +#define BMA2X2_INT1_PAD_ACTIVE_LEVEL__POS 0 +#define BMA2X2_INT1_PAD_ACTIVE_LEVEL__LEN 1 +#define BMA2X2_INT1_PAD_ACTIVE_LEVEL__MSK 0x01 +#define BMA2X2_INT1_PAD_ACTIVE_LEVEL__REG BMA2X2_INT_SET_REG + +#define BMA2X2_INT2_PAD_ACTIVE_LEVEL__POS 2 +#define BMA2X2_INT2_PAD_ACTIVE_LEVEL__LEN 1 +#define BMA2X2_INT2_PAD_ACTIVE_LEVEL__MSK 0x04 +#define BMA2X2_INT2_PAD_ACTIVE_LEVEL__REG BMA2X2_INT_SET_REG + +#define BMA2X2_INT1_PAD_OUTPUT_TYPE__POS 1 +#define BMA2X2_INT1_PAD_OUTPUT_TYPE__LEN 1 +#define BMA2X2_INT1_PAD_OUTPUT_TYPE__MSK 0x02 +#define BMA2X2_INT1_PAD_OUTPUT_TYPE__REG BMA2X2_INT_SET_REG + +#define BMA2X2_INT2_PAD_OUTPUT_TYPE__POS 3 +#define BMA2X2_INT2_PAD_OUTPUT_TYPE__LEN 1 +#define BMA2X2_INT2_PAD_OUTPUT_TYPE__MSK 0x08 +#define BMA2X2_INT2_PAD_OUTPUT_TYPE__REG BMA2X2_INT_SET_REG + +#define BMA2X2_INT_MODE_SEL__POS 0 +#define BMA2X2_INT_MODE_SEL__LEN 4 +#define BMA2X2_INT_MODE_SEL__MSK 0x0F +#define BMA2X2_INT_MODE_SEL__REG BMA2X2_INT_CTRL_REG + +#define BMA2X2_RESET_INT__POS 7 +#define BMA2X2_RESET_INT__LEN 1 +#define BMA2X2_RESET_INT__MSK 0x80 +#define BMA2X2_RESET_INT__REG BMA2X2_INT_CTRL_REG + +#define BMA2X2_LOWG_DUR__POS 0 +#define BMA2X2_LOWG_DUR__LEN 8 +#define BMA2X2_LOWG_DUR__MSK 0xFF +#define BMA2X2_LOWG_DUR__REG BMA2X2_LOW_DURN_REG + +#define BMA2X2_LOWG_THRES__POS 0 +#define BMA2X2_LOWG_THRES__LEN 8 +#define BMA2X2_LOWG_THRES__MSK 0xFF +#define BMA2X2_LOWG_THRES__REG BMA2X2_LOW_THRES_REG + +#define BMA2X2_LOWG_HYST__POS 0 +#define BMA2X2_LOWG_HYST__LEN 2 +#define BMA2X2_LOWG_HYST__MSK 0x03 +#define BMA2X2_LOWG_HYST__REG BMA2X2_LOW_HIGH_HYST_REG + +#define BMA2X2_LOWG_INT_MODE__POS 2 +#define BMA2X2_LOWG_INT_MODE__LEN 1 +#define BMA2X2_LOWG_INT_MODE__MSK 0x04 +#define BMA2X2_LOWG_INT_MODE__REG BMA2X2_LOW_HIGH_HYST_REG + +#define BMA2X2_HIGHG_DUR__POS 0 +#define BMA2X2_HIGHG_DUR__LEN 8 +#define BMA2X2_HIGHG_DUR__MSK 0xFF +#define BMA2X2_HIGHG_DUR__REG BMA2X2_HIGH_DURN_REG + +#define BMA2X2_HIGHG_THRES__POS 0 +#define BMA2X2_HIGHG_THRES__LEN 8 +#define BMA2X2_HIGHG_THRES__MSK 0xFF +#define BMA2X2_HIGHG_THRES__REG BMA2X2_HIGH_THRES_REG + +#define BMA2X2_HIGHG_HYST__POS 6 +#define BMA2X2_HIGHG_HYST__LEN 2 +#define BMA2X2_HIGHG_HYST__MSK 0xC0 +#define BMA2X2_HIGHG_HYST__REG BMA2X2_LOW_HIGH_HYST_REG + +#define BMA2X2_SLOPE_DUR__POS 0 +#define BMA2X2_SLOPE_DUR__LEN 2 +#define BMA2X2_SLOPE_DUR__MSK 0x03 +#define BMA2X2_SLOPE_DUR__REG BMA2X2_SLOPE_DURN_REG + +#define BMA2X2_SLO_NO_MOT_DUR__POS 2 +#define BMA2X2_SLO_NO_MOT_DUR__LEN 6 +#define BMA2X2_SLO_NO_MOT_DUR__MSK 0xFC +#define BMA2X2_SLO_NO_MOT_DUR__REG BMA2X2_SLOPE_DURN_REG + +#define BMA2X2_SLOPE_THRES__POS 0 +#define BMA2X2_SLOPE_THRES__LEN 8 +#define BMA2X2_SLOPE_THRES__MSK 0xFF +#define BMA2X2_SLOPE_THRES__REG BMA2X2_SLOPE_THRES_REG + +#define BMA2X2_SLO_NO_MOT_THRES__POS 0 +#define BMA2X2_SLO_NO_MOT_THRES__LEN 8 +#define BMA2X2_SLO_NO_MOT_THRES__MSK 0xFF +#define BMA2X2_SLO_NO_MOT_THRES__REG BMA2X2_SLO_NO_MOT_THRES_REG + +#define BMA2X2_TAP_DUR__POS 0 +#define BMA2X2_TAP_DUR__LEN 3 +#define BMA2X2_TAP_DUR__MSK 0x07 +#define BMA2X2_TAP_DUR__REG BMA2X2_TAP_PARAM_REG + +#define BMA2X2_TAP_SHOCK_DURN__POS 6 +#define BMA2X2_TAP_SHOCK_DURN__LEN 1 +#define BMA2X2_TAP_SHOCK_DURN__MSK 0x40 +#define BMA2X2_TAP_SHOCK_DURN__REG BMA2X2_TAP_PARAM_REG + +#define BMA2X2_ADV_TAP_INT__POS 5 +#define BMA2X2_ADV_TAP_INT__LEN 1 +#define BMA2X2_ADV_TAP_INT__MSK 0x20 +#define BMA2X2_ADV_TAP_INT__REG BMA2X2_TAP_PARAM_REG + +#define BMA2X2_TAP_QUIET_DURN__POS 7 +#define BMA2X2_TAP_QUIET_DURN__LEN 1 +#define BMA2X2_TAP_QUIET_DURN__MSK 0x80 +#define BMA2X2_TAP_QUIET_DURN__REG BMA2X2_TAP_PARAM_REG + +#define BMA2X2_TAP_THRES__POS 0 +#define BMA2X2_TAP_THRES__LEN 5 +#define BMA2X2_TAP_THRES__MSK 0x1F +#define BMA2X2_TAP_THRES__REG BMA2X2_TAP_THRES_REG + +#define BMA2X2_TAP_SAMPLES__POS 6 +#define BMA2X2_TAP_SAMPLES__LEN 2 +#define BMA2X2_TAP_SAMPLES__MSK 0xC0 +#define BMA2X2_TAP_SAMPLES__REG BMA2X2_TAP_THRES_REG + +#define BMA2X2_ORIENT_MODE__POS 0 +#define BMA2X2_ORIENT_MODE__LEN 2 +#define BMA2X2_ORIENT_MODE__MSK 0x03 +#define BMA2X2_ORIENT_MODE__REG BMA2X2_ORIENT_PARAM_REG + +#define BMA2X2_ORIENT_BLOCK__POS 2 +#define BMA2X2_ORIENT_BLOCK__LEN 2 +#define BMA2X2_ORIENT_BLOCK__MSK 0x0C +#define BMA2X2_ORIENT_BLOCK__REG BMA2X2_ORIENT_PARAM_REG + +#define BMA2X2_ORIENT_HYST__POS 4 +#define BMA2X2_ORIENT_HYST__LEN 3 +#define BMA2X2_ORIENT_HYST__MSK 0x70 +#define BMA2X2_ORIENT_HYST__REG BMA2X2_ORIENT_PARAM_REG + +#define BMA2X2_ORIENT_AXIS__POS 7 +#define BMA2X2_ORIENT_AXIS__LEN 1 +#define BMA2X2_ORIENT_AXIS__MSK 0x80 +#define BMA2X2_ORIENT_AXIS__REG BMA2X2_THETA_BLOCK_REG + +#define BMA2X2_ORIENT_UD_EN__POS 6 +#define BMA2X2_ORIENT_UD_EN__LEN 1 +#define BMA2X2_ORIENT_UD_EN__MSK 0x40 +#define BMA2X2_ORIENT_UD_EN__REG BMA2X2_THETA_BLOCK_REG + +#define BMA2X2_THETA_BLOCK__POS 0 +#define BMA2X2_THETA_BLOCK__LEN 6 +#define BMA2X2_THETA_BLOCK__MSK 0x3F +#define BMA2X2_THETA_BLOCK__REG BMA2X2_THETA_BLOCK_REG + +#define BMA2X2_THETA_FLAT__POS 0 +#define BMA2X2_THETA_FLAT__LEN 6 +#define BMA2X2_THETA_FLAT__MSK 0x3F +#define BMA2X2_THETA_FLAT__REG BMA2X2_THETA_FLAT_REG + +#define BMA2X2_FLAT_HOLD_TIME__POS 4 +#define BMA2X2_FLAT_HOLD_TIME__LEN 2 +#define BMA2X2_FLAT_HOLD_TIME__MSK 0x30 +#define BMA2X2_FLAT_HOLD_TIME__REG BMA2X2_FLAT_HOLD_TIME_REG + +#define BMA2X2_FLAT_HYS__POS 0 +#define BMA2X2_FLAT_HYS__LEN 3 +#define BMA2X2_FLAT_HYS__MSK 0x07 +#define BMA2X2_FLAT_HYS__REG BMA2X2_FLAT_HOLD_TIME_REG + +#define BMA2X2_FIFO_WML_TRIG_RETAIN__POS 0 +#define BMA2X2_FIFO_WML_TRIG_RETAIN__LEN 6 +#define BMA2X2_FIFO_WML_TRIG_RETAIN__MSK 0x3F +#define BMA2X2_FIFO_WML_TRIG_RETAIN__REG BMA2X2_FIFO_WML_TRIG + +#define BMA2X2_EN_SELF_TEST__POS 0 +#define BMA2X2_EN_SELF_TEST__LEN 2 +#define BMA2X2_EN_SELF_TEST__MSK 0x03 +#define BMA2X2_EN_SELF_TEST__REG BMA2X2_SELF_TEST_REG + +#define BMA2X2_NEG_SELF_TEST__POS 2 +#define BMA2X2_NEG_SELF_TEST__LEN 1 +#define BMA2X2_NEG_SELF_TEST__MSK 0x04 +#define BMA2X2_NEG_SELF_TEST__REG BMA2X2_SELF_TEST_REG + +#define BMA2X2_SELF_TEST_AMP__POS 4 +#define BMA2X2_SELF_TEST_AMP__LEN 1 +#define BMA2X2_SELF_TEST_AMP__MSK 0x10 +#define BMA2X2_SELF_TEST_AMP__REG BMA2X2_SELF_TEST_REG + + +#define BMA2X2_UNLOCK_EE_PROG_MODE__POS 0 +#define BMA2X2_UNLOCK_EE_PROG_MODE__LEN 1 +#define BMA2X2_UNLOCK_EE_PROG_MODE__MSK 0x01 +#define BMA2X2_UNLOCK_EE_PROG_MODE__REG BMA2X2_EEPROM_CTRL_REG + +#define BMA2X2_START_EE_PROG_TRIG__POS 1 +#define BMA2X2_START_EE_PROG_TRIG__LEN 1 +#define BMA2X2_START_EE_PROG_TRIG__MSK 0x02 +#define BMA2X2_START_EE_PROG_TRIG__REG BMA2X2_EEPROM_CTRL_REG + +#define BMA2X2_EE_PROG_READY__POS 2 +#define BMA2X2_EE_PROG_READY__LEN 1 +#define BMA2X2_EE_PROG_READY__MSK 0x04 +#define BMA2X2_EE_PROG_READY__REG BMA2X2_EEPROM_CTRL_REG + +#define BMA2X2_UPDATE_IMAGE__POS 3 +#define BMA2X2_UPDATE_IMAGE__LEN 1 +#define BMA2X2_UPDATE_IMAGE__MSK 0x08 +#define BMA2X2_UPDATE_IMAGE__REG BMA2X2_EEPROM_CTRL_REG + +#define BMA2X2_EE_REMAIN__POS 4 +#define BMA2X2_EE_REMAIN__LEN 4 +#define BMA2X2_EE_REMAIN__MSK 0xF0 +#define BMA2X2_EE_REMAIN__REG BMA2X2_EEPROM_CTRL_REG + +#define BMA2X2_EN_SPI_MODE_3__POS 0 +#define BMA2X2_EN_SPI_MODE_3__LEN 1 +#define BMA2X2_EN_SPI_MODE_3__MSK 0x01 +#define BMA2X2_EN_SPI_MODE_3__REG BMA2X2_SERIAL_CTRL_REG + +#define BMA2X2_I2C_WATCHDOG_PERIOD__POS 1 +#define BMA2X2_I2C_WATCHDOG_PERIOD__LEN 1 +#define BMA2X2_I2C_WATCHDOG_PERIOD__MSK 0x02 +#define BMA2X2_I2C_WATCHDOG_PERIOD__REG BMA2X2_SERIAL_CTRL_REG + +#define BMA2X2_EN_I2C_WATCHDOG__POS 2 +#define BMA2X2_EN_I2C_WATCHDOG__LEN 1 +#define BMA2X2_EN_I2C_WATCHDOG__MSK 0x04 +#define BMA2X2_EN_I2C_WATCHDOG__REG BMA2X2_SERIAL_CTRL_REG + +#define BMA2X2_EXT_MODE__POS 7 +#define BMA2X2_EXT_MODE__LEN 1 +#define BMA2X2_EXT_MODE__MSK 0x80 +#define BMA2X2_EXT_MODE__REG BMA2X2_EXTMODE_CTRL_REG + +#define BMA2X2_ALLOW_UPPER__POS 6 +#define BMA2X2_ALLOW_UPPER__LEN 1 +#define BMA2X2_ALLOW_UPPER__MSK 0x40 +#define BMA2X2_ALLOW_UPPER__REG BMA2X2_EXTMODE_CTRL_REG + +#define BMA2X2_MAP_2_LOWER__POS 5 +#define BMA2X2_MAP_2_LOWER__LEN 1 +#define BMA2X2_MAP_2_LOWER__MSK 0x20 +#define BMA2X2_MAP_2_LOWER__REG BMA2X2_EXTMODE_CTRL_REG + +#define BMA2X2_MAGIC_NUMBER__POS 0 +#define BMA2X2_MAGIC_NUMBER__LEN 5 +#define BMA2X2_MAGIC_NUMBER__MSK 0x1F +#define BMA2X2_MAGIC_NUMBER__REG BMA2X2_EXTMODE_CTRL_REG + +#define BMA2X2_UNLOCK_EE_WRITE_TRIM__POS 4 +#define BMA2X2_UNLOCK_EE_WRITE_TRIM__LEN 4 +#define BMA2X2_UNLOCK_EE_WRITE_TRIM__MSK 0xF0 +#define BMA2X2_UNLOCK_EE_WRITE_TRIM__REG BMA2X2_CTRL_UNLOCK_REG + +#define BMA2X2_EN_SLOW_COMP_X__POS 0 +#define BMA2X2_EN_SLOW_COMP_X__LEN 1 +#define BMA2X2_EN_SLOW_COMP_X__MSK 0x01 +#define BMA2X2_EN_SLOW_COMP_X__REG BMA2X2_OFFSET_CTRL_REG + +#define BMA2X2_EN_SLOW_COMP_Y__POS 1 +#define BMA2X2_EN_SLOW_COMP_Y__LEN 1 +#define BMA2X2_EN_SLOW_COMP_Y__MSK 0x02 +#define BMA2X2_EN_SLOW_COMP_Y__REG BMA2X2_OFFSET_CTRL_REG + +#define BMA2X2_EN_SLOW_COMP_Z__POS 2 +#define BMA2X2_EN_SLOW_COMP_Z__LEN 1 +#define BMA2X2_EN_SLOW_COMP_Z__MSK 0x04 +#define BMA2X2_EN_SLOW_COMP_Z__REG BMA2X2_OFFSET_CTRL_REG + +#define BMA2X2_FAST_CAL_RDY_S__POS 4 +#define BMA2X2_FAST_CAL_RDY_S__LEN 1 +#define BMA2X2_FAST_CAL_RDY_S__MSK 0x10 +#define BMA2X2_FAST_CAL_RDY_S__REG BMA2X2_OFFSET_CTRL_REG + +#define BMA2X2_CAL_TRIGGER__POS 5 +#define BMA2X2_CAL_TRIGGER__LEN 2 +#define BMA2X2_CAL_TRIGGER__MSK 0x60 +#define BMA2X2_CAL_TRIGGER__REG BMA2X2_OFFSET_CTRL_REG + +#define BMA2X2_RESET_OFFSET_REGS__POS 7 +#define BMA2X2_RESET_OFFSET_REGS__LEN 1 +#define BMA2X2_RESET_OFFSET_REGS__MSK 0x80 +#define BMA2X2_RESET_OFFSET_REGS__REG BMA2X2_OFFSET_CTRL_REG + +#define BMA2X2_COMP_CUTOFF__POS 0 +#define BMA2X2_COMP_CUTOFF__LEN 1 +#define BMA2X2_COMP_CUTOFF__MSK 0x01 +#define BMA2X2_COMP_CUTOFF__REG BMA2X2_OFFSET_PARAMS_REG + +#define BMA2X2_COMP_TARGET_OFFSET_X__POS 1 +#define BMA2X2_COMP_TARGET_OFFSET_X__LEN 2 +#define BMA2X2_COMP_TARGET_OFFSET_X__MSK 0x06 +#define BMA2X2_COMP_TARGET_OFFSET_X__REG BMA2X2_OFFSET_PARAMS_REG + +#define BMA2X2_COMP_TARGET_OFFSET_Y__POS 3 +#define BMA2X2_COMP_TARGET_OFFSET_Y__LEN 2 +#define BMA2X2_COMP_TARGET_OFFSET_Y__MSK 0x18 +#define BMA2X2_COMP_TARGET_OFFSET_Y__REG BMA2X2_OFFSET_PARAMS_REG + +#define BMA2X2_COMP_TARGET_OFFSET_Z__POS 5 +#define BMA2X2_COMP_TARGET_OFFSET_Z__LEN 2 +#define BMA2X2_COMP_TARGET_OFFSET_Z__MSK 0x60 +#define BMA2X2_COMP_TARGET_OFFSET_Z__REG BMA2X2_OFFSET_PARAMS_REG + +#define BMA2X2_FIFO_DATA_SELECT__POS 0 +#define BMA2X2_FIFO_DATA_SELECT__LEN 2 +#define BMA2X2_FIFO_DATA_SELECT__MSK 0x03 +#define BMA2X2_FIFO_DATA_SELECT__REG BMA2X2_FIFO_MODE_REG + +#define BMA2X2_FIFO_TRIGGER_SOURCE__POS 2 +#define BMA2X2_FIFO_TRIGGER_SOURCE__LEN 2 +#define BMA2X2_FIFO_TRIGGER_SOURCE__MSK 0x0C +#define BMA2X2_FIFO_TRIGGER_SOURCE__REG BMA2X2_FIFO_MODE_REG + +#define BMA2X2_FIFO_TRIGGER_ACTION__POS 4 +#define BMA2X2_FIFO_TRIGGER_ACTION__LEN 2 +#define BMA2X2_FIFO_TRIGGER_ACTION__MSK 0x30 +#define BMA2X2_FIFO_TRIGGER_ACTION__REG BMA2X2_FIFO_MODE_REG + +#define BMA2X2_FIFO_MODE__POS 6 +#define BMA2X2_FIFO_MODE__LEN 2 +#define BMA2X2_FIFO_MODE__MSK 0xC0 +#define BMA2X2_FIFO_MODE__REG BMA2X2_FIFO_MODE_REG + + +#define BMA2X2_STATUS1 0 +#define BMA2X2_STATUS2 1 +#define BMA2X2_STATUS3 2 +#define BMA2X2_STATUS4 3 +#define BMA2X2_STATUS5 4 + + +#define BMA2X2_RANGE_2G 3 +#define BMA2X2_RANGE_4G 5 +#define BMA2X2_RANGE_8G 8 +#define BMA2X2_RANGE_16G 12 + + +#define BMA2X2_BW_7_81HZ 0x08 +#define BMA2X2_BW_15_63HZ 0x09 +#define BMA2X2_BW_31_25HZ 0x0A +#define BMA2X2_BW_62_50HZ 0x0B +#define BMA2X2_BW_125HZ 0x0C +#define BMA2X2_BW_250HZ 0x0D +#define BMA2X2_BW_500HZ 0x0E +#define BMA2X2_BW_1000HZ 0x0F + +#define BMA2X2_SLEEP_DUR_0_5MS 0x05 +#define BMA2X2_SLEEP_DUR_1MS 0x06 +#define BMA2X2_SLEEP_DUR_2MS 0x07 +#define BMA2X2_SLEEP_DUR_4MS 0x08 +#define BMA2X2_SLEEP_DUR_6MS 0x09 +#define BMA2X2_SLEEP_DUR_10MS 0x0A +#define BMA2X2_SLEEP_DUR_25MS 0x0B +#define BMA2X2_SLEEP_DUR_50MS 0x0C +#define BMA2X2_SLEEP_DUR_100MS 0x0D +#define BMA2X2_SLEEP_DUR_500MS 0x0E +#define BMA2X2_SLEEP_DUR_1S 0x0F + +#define BMA2X2_LATCH_DUR_NON_LATCH 0x00 +#define BMA2X2_LATCH_DUR_250MS 0x01 +#define BMA2X2_LATCH_DUR_500MS 0x02 +#define BMA2X2_LATCH_DUR_1S 0x03 +#define BMA2X2_LATCH_DUR_2S 0x04 +#define BMA2X2_LATCH_DUR_4S 0x05 +#define BMA2X2_LATCH_DUR_8S 0x06 +#define BMA2X2_LATCH_DUR_LATCH 0x07 +#define BMA2X2_LATCH_DUR_NON_LATCH1 0x08 +#define BMA2X2_LATCH_DUR_250US 0x09 +#define BMA2X2_LATCH_DUR_500US 0x0A +#define BMA2X2_LATCH_DUR_1MS 0x0B +#define BMA2X2_LATCH_DUR_12_5MS 0x0C +#define BMA2X2_LATCH_DUR_25MS 0x0D +#define BMA2X2_LATCH_DUR_50MS 0x0E +#define BMA2X2_LATCH_DUR_LATCH1 0x0F + +#define BMA2X2_MODE_NORMAL 0 +#define BMA2X2_MODE_LOWPOWER1 1 +#define BMA2X2_MODE_SUSPEND 2 +#define BMA2X2_MODE_DEEP_SUSPEND 3 +#define BMA2X2_MODE_LOWPOWER2 4 +#define BMA2X2_MODE_STANDBY 5 + +#define BMA2X2_X_AXIS 0 +#define BMA2X2_Y_AXIS 1 +#define BMA2X2_Z_AXIS 2 + +#define BMA2X2_Low_G_Interrupt 0 +#define BMA2X2_High_G_X_Interrupt 1 +#define BMA2X2_High_G_Y_Interrupt 2 +#define BMA2X2_High_G_Z_Interrupt 3 +#define BMA2X2_DATA_EN 4 +#define BMA2X2_Slope_X_Interrupt 5 +#define BMA2X2_Slope_Y_Interrupt 6 +#define BMA2X2_Slope_Z_Interrupt 7 +#define BMA2X2_Single_Tap_Interrupt 8 +#define BMA2X2_Double_Tap_Interrupt 9 +#define BMA2X2_Orient_Interrupt 10 +#define BMA2X2_Flat_Interrupt 11 +#define BMA2X2_FFULL_INTERRUPT 12 +#define BMA2X2_FWM_INTERRUPT 13 + +#define BMA2X2_INT1_LOWG 0 +#define BMA2X2_INT2_LOWG 1 +#define BMA2X2_INT1_HIGHG 0 +#define BMA2X2_INT2_HIGHG 1 +#define BMA2X2_INT1_SLOPE 0 +#define BMA2X2_INT2_SLOPE 1 +#define BMA2X2_INT1_SLO_NO_MOT 0 +#define BMA2X2_INT2_SLO_NO_MOT 1 +#define BMA2X2_INT1_DTAP 0 +#define BMA2X2_INT2_DTAP 1 +#define BMA2X2_INT1_STAP 0 +#define BMA2X2_INT2_STAP 1 +#define BMA2X2_INT1_ORIENT 0 +#define BMA2X2_INT2_ORIENT 1 +#define BMA2X2_INT1_FLAT 0 +#define BMA2X2_INT2_FLAT 1 +#define BMA2X2_INT1_NDATA 0 +#define BMA2X2_INT2_NDATA 1 +#define BMA2X2_INT1_FWM 0 +#define BMA2X2_INT2_FWM 1 +#define BMA2X2_INT1_FFULL 0 +#define BMA2X2_INT2_FFULL 1 + +#define BMA2X2_SRC_LOWG 0 +#define BMA2X2_SRC_HIGHG 1 +#define BMA2X2_SRC_SLOPE 2 +#define BMA2X2_SRC_SLO_NO_MOT 3 +#define BMA2X2_SRC_TAP 4 +#define BMA2X2_SRC_DATA 5 + +#define BMA2X2_INT1_OUTPUT 0 +#define BMA2X2_INT2_OUTPUT 1 +#define BMA2X2_INT1_LEVEL 0 +#define BMA2X2_INT2_LEVEL 1 + +#define BMA2X2_LOW_DURATION 0 +#define BMA2X2_HIGH_DURATION 1 +#define BMA2X2_SLOPE_DURATION 2 +#define BMA2X2_SLO_NO_MOT_DURATION 3 + +#define BMA2X2_LOW_THRESHOLD 0 +#define BMA2X2_HIGH_THRESHOLD 1 +#define BMA2X2_SLOPE_THRESHOLD 2 +#define BMA2X2_SLO_NO_MOT_THRESHOLD 3 + + +#define BMA2X2_LOWG_HYST 0 +#define BMA2X2_HIGHG_HYST 1 + +#define BMA2X2_ORIENT_THETA 0 +#define BMA2X2_FLAT_THETA 1 + +#define BMA2X2_I2C_SELECT 0 +#define BMA2X2_I2C_EN 1 + +#define BMA2X2_SLOW_COMP_X 0 +#define BMA2X2_SLOW_COMP_Y 1 +#define BMA2X2_SLOW_COMP_Z 2 + +#define BMA2X2_CUT_OFF 0 +#define BMA2X2_OFFSET_TRIGGER_X 1 +#define BMA2X2_OFFSET_TRIGGER_Y 2 +#define BMA2X2_OFFSET_TRIGGER_Z 3 + +#define BMA2X2_GP0 0 +#define BMA2X2_GP1 1 + +#define BMA2X2_SLO_NO_MOT_EN_X 0 +#define BMA2X2_SLO_NO_MOT_EN_Y 1 +#define BMA2X2_SLO_NO_MOT_EN_Z 2 +#define BMA2X2_SLO_NO_MOT_EN_SEL 3 + +#define BMA2X2_WAKE_UP_DUR_20MS 0 +#define BMA2X2_WAKE_UP_DUR_80MS 1 +#define BMA2X2_WAKE_UP_DUR_320MS 2 +#define BMA2X2_WAKE_UP_DUR_2560MS 3 + +#define BMA2X2_SELF_TEST0_ON 1 +#define BMA2X2_SELF_TEST1_ON 2 + +#define BMA2X2_EE_W_OFF 0 +#define BMA2X2_EE_W_ON 1 + +#define BMA2X2_LOW_TH_IN_G(gthres, range) ((256 * gthres) / range) + + +#define BMA2X2_HIGH_TH_IN_G(gthres, range) ((256 * gthres) / range) + + +#define BMA2X2_LOW_HY_IN_G(ghyst, range) ((32 * ghyst) / range) + + +#define BMA2X2_HIGH_HY_IN_G(ghyst, range) ((32 * ghyst) / range) + + +#define BMA2X2_SLOPE_TH_IN_G(gthres, range) ((128 * gthres) / range) + + +#define BMA2X2_GET_BITSLICE(regvar, bitname)\ + ((regvar & bitname##__MSK) >> bitname##__POS) + + +#define BMA2X2_SET_BITSLICE(regvar, bitname, val)\ + ((regvar & ~bitname##__MSK) | ((val<<bitname##__POS)&bitname##__MSK)) + +#define CHECK_CHIP_ID_TIME_MAX 5 +#define BMA255_CHIP_ID 0XFA +#define BMA250E_CHIP_ID 0XF9 +#define BMA222E_CHIP_ID 0XF8 +#define BMA280_CHIP_ID 0XFB +#define BMA355_CHIP_ID 0XEA + +#define BMA255_TYPE 0 +#define BMA250E_TYPE 1 +#define BMA222E_TYPE 2 +#define BMA280_TYPE 3 + +#define MAX_FIFO_F_LEVEL 32 +#define MAX_FIFO_F_BYTES 6 +#define BMA_MAX_RETRY_I2C_XFER (100) + +#ifdef CONFIG_DOUBLE_TAP +#define DEFAULT_TAP_JUDGE_PERIOD 1000 /* default judge in 1 second */ +#endif + +/*! Bosch sensor unknown place*/ +#define BOSCH_SENSOR_PLACE_UNKNOWN (-1) + +#define BOSCH_SENSOR_PLACE (4) + +/*! Bosch sensor remapping table size P0~P7*/ +#define MAX_AXIS_REMAP_TAB_SZ 8 + +/* How was BMA enabled(set to operation mode) */ +#define BMA_ENABLED_ALL 0 +#define BMA_ENABLED_SGM 1 +#define BMA_ENABLED_DTAP 2 +#define BMA_ENABLED_INPUT 3 +#define BMA_ENABLED_BSX 4 + +#define BOSCH_SENSOR_BAD_ADDR -1 + + +/*! + * @brief:BMI058 feature + * macro definition +*/ + +#define BMA2X2_FIFO_DAT_SEL_X 1 +#define BMA2X2_FIFO_DAT_SEL_Y 2 +#define BMA2X2_FIFO_DAT_SEL_Z 3 + +#ifdef CONFIG_SENSORS_BMI058 +#define C_BMI058_One_U8X 1 +#define C_BMI058_Two_U8X 2 +#define BMI058_OFFSET_TRIGGER_X BMA2X2_OFFSET_TRIGGER_Y +#define BMI058_OFFSET_TRIGGER_Y BMA2X2_OFFSET_TRIGGER_X + +/*! BMI058 X AXIS OFFSET REG definition*/ +#define BMI058_OFFSET_X_AXIS_REG BMA2X2_OFFSET_Y_AXIS_REG +/*! BMI058 Y AXIS OFFSET REG definition*/ +#define BMI058_OFFSET_Y_AXIS_REG BMA2X2_OFFSET_X_AXIS_REG + +#define BMI058_FIFO_DAT_SEL_X BMA2X2_FIFO_DAT_SEL_Y +#define BMI058_FIFO_DAT_SEL_Y BMA2X2_FIFO_DAT_SEL_X + +/*! BMA2x2 common slow no motion X interrupt type definition*/ +#define BMA2X2_SLOW_NO_MOT_X_INT 12 +/*! BMA2x2 common slow no motion Y interrupt type definition*/ +#define BMA2X2_SLOW_NO_MOT_Y_INT 13 +/*! BMA2x2 common High G X interrupt type definition*/ +#define BMA2X2_HIGHG_X_INT 1 +/*! BMA2x2 common High G Y interrupt type definition*/ +#define BMA2X2_HIGHG_Y_INT 2 +/*! BMA2x2 common slope X interrupt type definition*/ +#define BMA2X2_SLOPE_X_INT 5 +/*! BMA2x2 common slope Y interrupt type definition*/ +#define BMA2X2_SLOPE_Y_INT 6 + +/*! this structure holds some interrupt types difference +**between BMA2x2 and BMI058. +*/ +struct interrupt_map_t { + int x; + int y; +}; +/*!*Need to use BMA2x2 Common interrupt type definition to +* instead of Some of BMI058 reversed Interrupt type +* because of HW Register. +* The reversed Interrupt types contain: +* slow_no_mot_x_int && slow_not_mot_y_int +* highg_x_int && highg_y_int +* slope_x_int && slope_y_int +**/ +static const struct interrupt_map_t int_map[] = { + {BMA2X2_SLOW_NO_MOT_X_INT, BMA2X2_SLOW_NO_MOT_Y_INT}, + {BMA2X2_HIGHG_X_INT, BMA2X2_HIGHG_Y_INT}, + {BMA2X2_SLOPE_X_INT, BMA2X2_SLOPE_Y_INT} +}; + +/*! high g or slope interrupt type definition for BMI058*/ +/*! High G interrupt of x, y, z axis happened */ +#define HIGH_G_INTERRUPT_X HIGH_G_INTERRUPT_Y_HAPPENED +#define HIGH_G_INTERRUPT_Y HIGH_G_INTERRUPT_X_HAPPENED +#define HIGH_G_INTERRUPT_Z HIGH_G_INTERRUPT_Z_HAPPENED +/*! High G interrupt of x, y, z negative axis happened */ +#define HIGH_G_INTERRUPT_X_N HIGH_G_INTERRUPT_Y_NEGATIVE_HAPPENED +#define HIGH_G_INTERRUPT_Y_N HIGH_G_INTERRUPT_X_NEGATIVE_HAPPENED +#define HIGH_G_INTERRUPT_Z_N HIGH_G_INTERRUPT_Z_NEGATIVE_HAPPENED +/*! Slope interrupt of x, y, z axis happened */ +#define SLOPE_INTERRUPT_X SLOPE_INTERRUPT_Y_HAPPENED +#define SLOPE_INTERRUPT_Y SLOPE_INTERRUPT_X_HAPPENED +#define SLOPE_INTERRUPT_Z SLOPE_INTERRUPT_Z_HAPPENED +/*! Slope interrupt of x, y, z negative axis happened */ +#define SLOPE_INTERRUPT_X_N SLOPE_INTERRUPT_Y_NEGATIVE_HAPPENED +#define SLOPE_INTERRUPT_Y_N SLOPE_INTERRUPT_X_NEGATIVE_HAPPENED +#define SLOPE_INTERRUPT_Z_N SLOPE_INTERRUPT_Z_NEGATIVE_HAPPENED + + +#else + +/*! high g or slope interrupt type definition*/ +/*! High G interrupt of x, y, z axis happened */ +#define HIGH_G_INTERRUPT_X HIGH_G_INTERRUPT_X_HAPPENED +#define HIGH_G_INTERRUPT_Y HIGH_G_INTERRUPT_Y_HAPPENED +#define HIGH_G_INTERRUPT_Z HIGH_G_INTERRUPT_Z_HAPPENED +/*! High G interrupt of x, y, z negative axis happened */ +#define HIGH_G_INTERRUPT_X_N HIGH_G_INTERRUPT_X_NEGATIVE_HAPPENED +#define HIGH_G_INTERRUPT_Y_N HIGH_G_INTERRUPT_Y_NEGATIVE_HAPPENED +#define HIGH_G_INTERRUPT_Z_N HIGH_G_INTERRUPT_Z_NEGATIVE_HAPPENED +/*! Slope interrupt of x, y, z axis happened */ +#define SLOPE_INTERRUPT_X SLOPE_INTERRUPT_X_HAPPENED +#define SLOPE_INTERRUPT_Y SLOPE_INTERRUPT_Y_HAPPENED +#define SLOPE_INTERRUPT_Z SLOPE_INTERRUPT_Z_HAPPENED +/*! Slope interrupt of x, y, z negative axis happened */ +#define SLOPE_INTERRUPT_X_N SLOPE_INTERRUPT_X_NEGATIVE_HAPPENED +#define SLOPE_INTERRUPT_Y_N SLOPE_INTERRUPT_Y_NEGATIVE_HAPPENED +#define SLOPE_INTERRUPT_Z_N SLOPE_INTERRUPT_Z_NEGATIVE_HAPPENED + + +#endif/*End of CONFIG_SENSORS_BMI058*/ + + +struct bma2x2_type_map_t { + + /*! bma2x2 sensor chip id */ + uint16_t chip_id; + + /*! bma2x2 sensor type */ + uint16_t sensor_type; + + /*! bma2x2 sensor name */ + const char *sensor_name; +}; + +static const struct bma2x2_type_map_t sensor_type_map[] = { + + {BMA255_CHIP_ID, BMA255_TYPE, "BMA255/254"}, + {BMA355_CHIP_ID, BMA255_TYPE, "BMA355"}, + {BMA250E_CHIP_ID, BMA250E_TYPE, "BMA250E"}, + {BMA222E_CHIP_ID, BMA222E_TYPE, "BMA222E"}, + {BMA280_CHIP_ID, BMA280_TYPE, "BMA280"}, + +}; + +/*! +* Bst sensor common definition, +* please give parameters in BSP file. +*/ +struct bosch_sensor_specific { + char *name; + /* 0 to 7 */ + int place; + int irq; + int (*irq_gpio_cfg)(void); +}; + + +/*! + * we use a typedef to hide the detail, + * because this type might be changed + */ +struct bosch_sensor_axis_remap { + /* src means which source will be mapped to target x, y, z axis */ + /* if an target OS axis is remapped from (-)x, + * src is 0, sign_* is (-)1 */ + /* if an target OS axis is remapped from (-)y, + * src is 1, sign_* is (-)1 */ + /* if an target OS axis is remapped from (-)z, + * src is 2, sign_* is (-)1 */ + int src_x:3; + int src_y:3; + int src_z:3; + + int sign_x:2; + int sign_y:2; + int sign_z:2; +}; + +struct bosch_sensor_data { + union { + int16_t v[3]; + struct { + int16_t x; + int16_t y; + int16_t z; + }; + }; +}; + +struct bma2x2acc { + s16 x; + s16 y; + s16 z; +}; + +struct bma2x2_data { + struct i2c_client *bma2x2_client; + atomic_t delay; + atomic_t enable; + atomic_t selftest_result; + unsigned int chip_id; + unsigned int fifo_count; + unsigned char fifo_datasel; + unsigned char mode; + signed char sensor_type; + struct input_dev *input; + + struct bst_dev *bst_acc; + + struct bma2x2acc value; + struct mutex value_mutex; + struct mutex enable_mutex; + struct mutex mode_mutex; +// struct delayed_work work; + struct work_struct work; + struct hrtimer timer; + struct workqueue_struct *wq; + struct work_struct irq_work; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif + int IRQ; + struct bosch_sensor_specific *bst_pd; + + int bma_mode_enabled; + struct input_dev *dev_interrupt; + +#ifdef CONFIG_SIG_MOTION + struct class *g_sensor_class; + struct device *g_sensor_dev; + + /*struct bma250_platform_data *pdata;*/ + atomic_t en_sig_motion; +#endif + +#ifdef CONFIG_DOUBLE_TAP + struct class *g_sensor_class_doubletap; + struct device *g_sensor_dev_doubletap; + atomic_t en_double_tap; + unsigned char tap_times; + struct mutex tap_mutex; + struct timer_list tap_timer; + int tap_time_period; +#endif +}; + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void bma2x2_early_suspend(struct early_suspend *h); +static void bma2x2_late_resume(struct early_suspend *h); +#endif + +static int bma2x2_set_mode(struct i2c_client *client, + u8 mode, u8 enabled_mode); +static int bma2x2_get_mode(struct i2c_client *client, u8 *mode); +static int bma2x2_get_fifo_mode(struct i2c_client *client, u8 *fifo_mode); +static int bma2x2_set_fifo_mode(struct i2c_client *client, u8 fifo_mode); +static int bma2x2_normal_to_suspend(struct bma2x2_data *bma2x2, + unsigned char data1, unsigned char data2); + + +/*Remapping for BMA2X2*/ + +static const struct bosch_sensor_axis_remap +bst_axis_remap_tab_dft[MAX_AXIS_REMAP_TAB_SZ] = { + /* src_x src_y src_z sign_x sign_y sign_z */ + { 0, 1, 2, 1, 1, 1 }, /* P0 */ + { 1, 0, 2, 1, -1, 1 }, /* P1 */ + { 0, 1, 2, -1, -1, 1 }, /* P2 */ + { 1, 0, 2, -1, 1, 1 }, /* P3 */ + + { 0, 1, 2, -1, 1, -1 }, /* P4 */ + { 1, 0, 2, -1, -1, -1 }, /* P5 */ + { 0, 1, 2, 1, -1, -1 }, /* P6 */ + { 1, 0, 2, 1, 1, -1 }, /* P7 */ +}; + +static unsigned int read_file(char *filename) +{ + struct file *filp; + char bufs[100]; + int ret; + unsigned int data_val=0; + + // strcpy(bufs, "hello my world"); + + /* kernel memory access setting */ + mm_segment_t old_fs = get_fs(); + set_fs(KERNEL_DS); + + /* open a file */ + filp = filp_open(filename, O_RDWR, 0664); + if (IS_ERR(filp)) { + printk(KERN_ERR"open error\n"); + return -1; + } + else { + printk(KERN_ERR"open success\n"); + } + + /* write example */ + /// printk("filp->f_pos = %d\n", (int)filp->f_pos); + // vfs_write(filp, bufs, strlen(bufs), &filp->f_pos); + // printk("filp->f_pos = %d\n", (int)filp->f_pos); + + /* read example */ + printk(KERN_DEBUG); + + // int pos = (int)filp->f_pos; + printk(KERN_ERR"filp->f_pos=%d \n: ", (int)filp->f_pos); + memset(bufs,'\n',60); + ret = vfs_read(filp, bufs, 60, &filp->f_pos); + printk(KERN_ERR"char_cnt =%d \n: ", ret); + + if (ret != 0) { + printk(KERN_ERR"filp->f_pos=%d\n", (int)filp->f_pos); + } + printk("\n"); + + ret= sscanf(bufs,"%10d\n",&data_val); + printk(KERN_ERR"data_val=%d\n", data_val); + filp_close(filp, NULL); /* filp_close(filp, current->files) ? */ + /* restore kernel memory setting */ + + set_fs(old_fs); + return data_val; +} + +static void bst_remap_sensor_data(struct bosch_sensor_data *data, + const struct bosch_sensor_axis_remap *remap) +{ + struct bosch_sensor_data tmp; + + tmp.x = data->v[remap->src_x] * remap->sign_x; + tmp.y = data->v[remap->src_y] * remap->sign_y; + tmp.z = data->v[remap->src_z] * remap->sign_z; + + memcpy(data, &tmp, sizeof(*data)); +} + +static void bst_remap_sensor_data_dft_tab(struct bosch_sensor_data *data, + int place) +{ + /* sensor with place 0 needs not to be remapped */ + if ((place <= 0) || (place >= MAX_AXIS_REMAP_TAB_SZ)) + return; + + bst_remap_sensor_data(data, &bst_axis_remap_tab_dft[place]); +} + +static void bma2x2_remap_sensor_data(struct bma2x2acc *val, + struct bma2x2_data *client_data) +{ + struct bosch_sensor_data bsd; + int place; + + if ((NULL == client_data->bst_pd) || (BOSCH_SENSOR_PLACE_UNKNOWN + == client_data->bst_pd->place)) + place = BOSCH_SENSOR_PLACE_UNKNOWN; + else + place = client_data->bst_pd->place; + +#ifdef CONFIG_SENSORS_BMI058 +/*x,y need to be invesed becase of HW Register for BMI058*/ + bsd.y = val->x; + bsd.x = val->y; + bsd.z = val->z; +#else + bsd.x = val->x; + bsd.y = val->y; + bsd.z = val->z; +#endif + + bst_remap_sensor_data_dft_tab(&bsd, place); + + val->x = bsd.x; + val->y = bsd.y; + val->z = bsd.z; + +} + + +static int bma2x2_smbus_read_byte(struct i2c_client *client, + unsigned char reg_addr, unsigned char *data) +{ + s32 dummy; + dummy = i2c_smbus_read_byte_data(client, reg_addr); + if (dummy < 0) + return -1; + *data = dummy & 0x000000ff; + + return 0; +} + +static int bma2x2_smbus_write_byte(struct i2c_client *client, + unsigned char reg_addr, unsigned char *data) +{ + s32 dummy; + + dummy = i2c_smbus_write_byte_data(client, reg_addr, *data); + if (dummy < 0) + return -1; + udelay(2); + return 0; +} + +static int bma2x2_smbus_read_byte_block(struct i2c_client *client, + unsigned char reg_addr, unsigned char *data, unsigned char len) +{ + s32 dummy; + dummy = i2c_smbus_read_i2c_block_data(client, reg_addr, len, data); + if (dummy < 0) + return -1; + return 0; +} + +static int bma_i2c_burst_read(struct i2c_client *client, u8 reg_addr, + u8 *data, u16 len) +{ + int retry; + + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = ®_addr, + }, + + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = data, + }, + }; + + for (retry = 0; retry < BMA_MAX_RETRY_I2C_XFER; retry++) { + if (i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)) > 0) + break; + else + mdelay(1); + } + + if (BMA_MAX_RETRY_I2C_XFER <= retry) { + PINFO("I2C xfer error"); + return -EIO; + } + + return 0; +} + +static int bma2x2_check_chip_id(struct i2c_client *client, + struct bma2x2_data *data) +{ + int i = 0; + int err = 0; + unsigned char chip_id = 0; + unsigned char read_count = 0; + unsigned char bma2x2_sensor_type_count = 0; + + bma2x2_sensor_type_count = + sizeof(sensor_type_map) / sizeof(struct bma2x2_type_map_t); + + while (read_count++ < CHECK_CHIP_ID_TIME_MAX) { + if (bma2x2_smbus_read_byte(client, BMA2X2_CHIP_ID_REG, + &chip_id) < 0) { + PERR("Bosch Sensortec Device not found" + "i2c bus read error, read chip_id:%d\n", chip_id); + continue; + } else { + for (i = 0; i < bma2x2_sensor_type_count; i++) { + if (sensor_type_map[i].chip_id == chip_id) { + data->sensor_type = + sensor_type_map[i].sensor_type; + data->chip_id = chip_id; + PINFO("Bosch Sensortec Device detected," + " HW IC name: %s\n", + sensor_type_map[i].sensor_name); + return err; + } + } + if (i < bma2x2_sensor_type_count) + return err; + else { + if (read_count == CHECK_CHIP_ID_TIME_MAX) { + PERR("Failed! Bosch Sensortec Device" + " not found, mismatch chip_id:%d\n", + chip_id); + err = -ENODEV; + return err; + } + } + mdelay(1); + } + } + return err; +} + +#ifdef CONFIG_BMA_ENABLE_NEWDATA_INT +static int bma2x2_set_newdata(struct i2c_client *client, + unsigned char channel, unsigned char int_newdata) +{ + + unsigned char data = 0; + int comres = 0; + + switch (channel) { + case BMA2X2_INT1_NDATA: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_NEWDATA__REG, &data); + data = BMA2X2_SET_BITSLICE(data, + BMA2X2_EN_INT1_PAD_NEWDATA, int_newdata); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_NEWDATA__REG, &data); + break; + case BMA2X2_INT2_NDATA: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_NEWDATA__REG, &data); + data = BMA2X2_SET_BITSLICE(data, + BMA2X2_EN_INT2_PAD_NEWDATA, int_newdata); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_NEWDATA__REG, &data); + break; + default: + comres = -1; + break; + } + + return comres; + +} +#endif /* CONFIG_BMA_ENABLE_NEWDATA_INT */ + +#ifdef BMA2X2_ENABLE_INT1 +static int bma2x2_set_int1_pad_sel(struct i2c_client *client, unsigned char + int1sel) +{ + int comres = 0; + unsigned char data = 0; + unsigned char state; + state = 0x01; + + switch (int1sel) { + case 0: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_LOWG__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_LOWG, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_LOWG__REG, &data); + break; + case 1: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_HIGHG__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_HIGHG, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_HIGHG__REG, &data); + break; + case 2: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_SLOPE__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_SLOPE, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_SLOPE__REG, &data); + break; + case 3: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_DB_TAP__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_DB_TAP, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_DB_TAP__REG, &data); + break; + case 4: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_SNG_TAP__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_SNG_TAP, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_SNG_TAP__REG, &data); + break; + case 5: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_ORIENT__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_ORIENT, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_ORIENT__REG, &data); + break; + case 6: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_FLAT__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_FLAT, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_FLAT__REG, &data); + break; + case 7: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT1_PAD_SLO_NO_MOT__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT1_PAD_SLO_NO_MOT, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT1_PAD_SLO_NO_MOT__REG, &data); + break; + + default: + break; + } + + return comres; +} +#endif /* BMA2X2_ENABLE_INT1 */ + +#ifdef BMA2X2_ENABLE_INT2 +static int bma2x2_set_int2_pad_sel(struct i2c_client *client, unsigned char + int2sel) +{ + int comres = 0; + unsigned char data = 0; + unsigned char state; + state = 0x01; + + + switch (int2sel) { + case 0: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_LOWG__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_LOWG, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_LOWG__REG, &data); + break; + case 1: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_HIGHG__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_HIGHG, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_HIGHG__REG, &data); + break; + case 2: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_SLOPE__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_SLOPE, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_SLOPE__REG, &data); + break; + case 3: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_DB_TAP__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_DB_TAP, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_DB_TAP__REG, &data); + break; + case 4: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_SNG_TAP__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_SNG_TAP, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_SNG_TAP__REG, &data); + break; + case 5: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_ORIENT__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_ORIENT, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_ORIENT__REG, &data); + break; + case 6: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_FLAT__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_FLAT, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_FLAT__REG, &data); + break; + case 7: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_EN_INT2_PAD_SLO_NO_MOT__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_INT2_PAD_SLO_NO_MOT, + state); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_EN_INT2_PAD_SLO_NO_MOT__REG, &data); + break; + default: + break; + } + + return comres; +} +#endif /* BMA2X2_ENABLE_INT2 */ + +static int bma2x2_set_Int_Enable(struct i2c_client *client, unsigned char + InterruptType , unsigned char value) +{ + int comres = 0; + unsigned char data1 = 0; + unsigned char data2 = 0; + + if ((11 < InterruptType) && (InterruptType < 16)) { + switch (InterruptType) { + case 12: + /* slow/no motion X Interrupt */ + comres = bma2x2_smbus_read_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_X_INT__REG, &data1); + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_INT_SLO_NO_MOT_EN_X_INT, value); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_X_INT__REG, &data1); + break; + case 13: + /* slow/no motion Y Interrupt */ + comres = bma2x2_smbus_read_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_Y_INT__REG, &data1); + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_INT_SLO_NO_MOT_EN_Y_INT, value); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_Y_INT__REG, &data1); + break; + case 14: + /* slow/no motion Z Interrupt */ + comres = bma2x2_smbus_read_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_Z_INT__REG, &data1); + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_INT_SLO_NO_MOT_EN_Z_INT, value); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_Z_INT__REG, &data1); + break; + case 15: + /* slow / no motion Interrupt select */ + comres = bma2x2_smbus_read_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_SEL_INT__REG, &data1); + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_INT_SLO_NO_MOT_EN_SEL_INT, value); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_INT_SLO_NO_MOT_EN_SEL_INT__REG, &data1); + } + + return comres; + } + + + comres = bma2x2_smbus_read_byte(client, BMA2X2_INT_ENABLE1_REG, &data1); + comres = bma2x2_smbus_read_byte(client, BMA2X2_INT_ENABLE2_REG, &data2); + + value = value & 1; + switch (InterruptType) { + case 0: + /* Low G Interrupt */ + data2 = BMA2X2_SET_BITSLICE(data2, BMA2X2_EN_LOWG_INT, value); + break; + + case 1: + /* High G X Interrupt */ + data2 = BMA2X2_SET_BITSLICE(data2, BMA2X2_EN_HIGHG_X_INT, + value); + break; + + case 2: + /* High G Y Interrupt */ + data2 = BMA2X2_SET_BITSLICE(data2, BMA2X2_EN_HIGHG_Y_INT, + value); + break; + + case 3: + /* High G Z Interrupt */ + data2 = BMA2X2_SET_BITSLICE(data2, BMA2X2_EN_HIGHG_Z_INT, + value); + break; + + case 4: + /* New Data Interrupt */ + data2 = BMA2X2_SET_BITSLICE(data2, BMA2X2_EN_NEW_DATA_INT, + value); + break; + + case 5: + /* Slope X Interrupt */ + data1 = BMA2X2_SET_BITSLICE(data1, BMA2X2_EN_SLOPE_X_INT, + value); + break; + + case 6: + /* Slope Y Interrupt */ + data1 = BMA2X2_SET_BITSLICE(data1, BMA2X2_EN_SLOPE_Y_INT, + value); + break; + + case 7: + /* Slope Z Interrupt */ + data1 = BMA2X2_SET_BITSLICE(data1, BMA2X2_EN_SLOPE_Z_INT, + value); + break; + + case 8: + /* Single Tap Interrupt */ + data1 = BMA2X2_SET_BITSLICE(data1, BMA2X2_EN_SINGLE_TAP_INT, + value); + break; + + case 9: + /* Double Tap Interrupt */ + data1 = BMA2X2_SET_BITSLICE(data1, BMA2X2_EN_DOUBLE_TAP_INT, + value); + break; + + case 10: + /* Orient Interrupt */ + data1 = BMA2X2_SET_BITSLICE(data1, BMA2X2_EN_ORIENT_INT, value); + break; + + case 11: + /* Flat Interrupt */ + data1 = BMA2X2_SET_BITSLICE(data1, BMA2X2_EN_FLAT_INT, value); + break; + + default: + break; + } + comres = bma2x2_smbus_write_byte(client, BMA2X2_INT_ENABLE1_REG, + &data1); + comres = bma2x2_smbus_write_byte(client, BMA2X2_INT_ENABLE2_REG, + &data2); + + return comres; +} + + +#if defined(BMA2X2_ENABLE_INT1) || defined(BMA2X2_ENABLE_INT2) +static int bma2x2_get_interruptstatus1(struct i2c_client *client, unsigned char + *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_STATUS1_REG, &data); + *intstatus = data; + + return comres; +} + +#ifdef CONFIG_BMA_ENABLE_NEWDATA_INT +static int bma2x2_get_interruptstatus2(struct i2c_client *client, unsigned char + *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_STATUS2_REG, &data); + *intstatus = data; + + return comres; +} +#endif + +static int bma2x2_get_HIGH_first(struct i2c_client *client, unsigned char + param, unsigned char *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + switch (param) { + case 0: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_STATUS_ORIENT_HIGH_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_HIGHG_FIRST_X); + *intstatus = data; + break; + case 1: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_STATUS_ORIENT_HIGH_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_HIGHG_FIRST_Y); + *intstatus = data; + break; + case 2: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_STATUS_ORIENT_HIGH_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_HIGHG_FIRST_Z); + *intstatus = data; + break; + default: + break; + } + + return comres; +} + +static int bma2x2_get_HIGH_sign(struct i2c_client *client, unsigned char + *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_STATUS_ORIENT_HIGH_REG, + &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_HIGHG_SIGN_S); + *intstatus = data; + + return comres; +} + +#ifndef CONFIG_SIG_MOTION +static int bma2x2_get_slope_first(struct i2c_client *client, unsigned char + param, unsigned char *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + switch (param) { + case 0: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_STATUS_TAP_SLOPE_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_SLOPE_FIRST_X); + *intstatus = data; + break; + case 1: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_STATUS_TAP_SLOPE_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_SLOPE_FIRST_Y); + *intstatus = data; + break; + case 2: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_STATUS_TAP_SLOPE_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_SLOPE_FIRST_Z); + *intstatus = data; + break; + default: + break; + } + + return comres; +} + +static int bma2x2_get_slope_sign(struct i2c_client *client, unsigned char + *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_STATUS_TAP_SLOPE_REG, + &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_SLOPE_SIGN_S); + *intstatus = data; + + return comres; +} +#endif + +static int bma2x2_get_orient_status(struct i2c_client *client, unsigned char + *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_STATUS_ORIENT_HIGH_REG, + &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_ORIENT_S); + *intstatus = data; + + return comres; +} + +static int bma2x2_get_orient_flat_status(struct i2c_client *client, unsigned + char *intstatus) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_STATUS_ORIENT_HIGH_REG, + &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_FLAT_S); + *intstatus = data; + + return comres; +} +#endif /* defined(BMA2X2_ENABLE_INT1)||defined(BMA2X2_ENABLE_INT2) */ + +static int bma2x2_set_Int_Mode(struct i2c_client *client, unsigned char Mode) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_INT_MODE_SEL__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_INT_MODE_SEL, Mode); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_INT_MODE_SEL__REG, &data); + + + return comres; +} + +static int bma2x2_get_Int_Mode(struct i2c_client *client, unsigned char *Mode) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_INT_MODE_SEL__REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_INT_MODE_SEL); + *Mode = data; + + + return comres; +} +static int bma2x2_set_slope_duration(struct i2c_client *client, unsigned char + duration) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_SLOPE_DUR__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_SLOPE_DUR, duration); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_SLOPE_DUR__REG, &data); + + return comres; +} + +static int bma2x2_get_slope_duration(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_SLOPE_DURN_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_SLOPE_DUR); + *status = data; + + + return comres; +} + +static int bma2x2_set_slope_no_mot_duration(struct i2c_client *client, + unsigned char duration) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2x2_SLO_NO_MOT_DUR__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2x2_SLO_NO_MOT_DUR, duration); + comres = bma2x2_smbus_write_byte(client, + BMA2x2_SLO_NO_MOT_DUR__REG, &data); + + + return comres; +} + +static int bma2x2_get_slope_no_mot_duration(struct i2c_client *client, + unsigned char *status) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2x2_SLO_NO_MOT_DUR__REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2x2_SLO_NO_MOT_DUR); + *status = data; + + + return comres; +} + +static int bma2x2_set_slope_threshold(struct i2c_client *client, + unsigned char threshold) +{ + int comres = 0; + unsigned char data = 0; + + data = threshold; + comres = bma2x2_smbus_write_byte(client, + BMA2X2_SLOPE_THRES__REG, &data); + + return comres; +} + +static int bma2x2_get_slope_threshold(struct i2c_client *client, + unsigned char *status) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_SLOPE_THRES_REG, &data); + *status = data; + + return comres; +} + +static int bma2x2_set_slope_no_mot_threshold(struct i2c_client *client, + unsigned char threshold) +{ + int comres = 0; + unsigned char data = 0; + + data = threshold; + comres = bma2x2_smbus_write_byte(client, + BMA2X2_SLO_NO_MOT_THRES_REG, &data); + + return comres; +} + +static int bma2x2_get_slope_no_mot_threshold(struct i2c_client *client, + unsigned char *status) +{ + int comres = 0; + unsigned char data = 0; + + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_SLO_NO_MOT_THRES_REG, &data); + *status = data; + + return comres; +} + + +static int bma2x2_set_low_g_duration(struct i2c_client *client, unsigned char + duration) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_LOWG_DUR__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_LOWG_DUR, duration); + comres = bma2x2_smbus_write_byte(client, BMA2X2_LOWG_DUR__REG, &data); + + return comres; +} + +static int bma2x2_get_low_g_duration(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_LOW_DURN_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_LOWG_DUR); + *status = data; + + return comres; +} + +static int bma2x2_set_low_g_threshold(struct i2c_client *client, unsigned char + threshold) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_LOWG_THRES__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_LOWG_THRES, threshold); + comres = bma2x2_smbus_write_byte(client, BMA2X2_LOWG_THRES__REG, &data); + + return comres; +} + +static int bma2x2_get_low_g_threshold(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_LOW_THRES_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_LOWG_THRES); + *status = data; + + return comres; +} + +static int bma2x2_set_high_g_duration(struct i2c_client *client, unsigned char + duration) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_HIGHG_DUR__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_HIGHG_DUR, duration); + comres = bma2x2_smbus_write_byte(client, BMA2X2_HIGHG_DUR__REG, &data); + + return comres; +} + +static int bma2x2_get_high_g_duration(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_HIGH_DURN_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_HIGHG_DUR); + *status = data; + + return comres; +} + +static int bma2x2_set_high_g_threshold(struct i2c_client *client, unsigned char + threshold) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_HIGHG_THRES__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_HIGHG_THRES, threshold); + comres = bma2x2_smbus_write_byte(client, BMA2X2_HIGHG_THRES__REG, + &data); + + return comres; +} + +static int bma2x2_get_high_g_threshold(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_HIGH_THRES_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_HIGHG_THRES); + *status = data; + + return comres; +} + + +static int bma2x2_set_tap_duration(struct i2c_client *client, unsigned char + duration) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_DUR__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_TAP_DUR, duration); + comres = bma2x2_smbus_write_byte(client, BMA2X2_TAP_DUR__REG, &data); + + return comres; +} + +static int bma2x2_get_tap_duration(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_PARAM_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_TAP_DUR); + *status = data; + + return comres; +} + +static int bma2x2_set_tap_shock(struct i2c_client *client, unsigned char setval) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_SHOCK_DURN__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_TAP_SHOCK_DURN, setval); + comres = bma2x2_smbus_write_byte(client, BMA2X2_TAP_SHOCK_DURN__REG, + &data); + + return comres; +} + +static int bma2x2_get_tap_shock(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_PARAM_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_TAP_SHOCK_DURN); + *status = data; + + return comres; +} + +static int bma2x2_set_tap_quiet(struct i2c_client *client, unsigned char + duration) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_QUIET_DURN__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_TAP_QUIET_DURN, duration); + comres = bma2x2_smbus_write_byte(client, BMA2X2_TAP_QUIET_DURN__REG, + &data); + + return comres; +} + +static int bma2x2_get_tap_quiet(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_PARAM_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_TAP_QUIET_DURN); + *status = data; + + return comres; +} + +static int bma2x2_set_tap_threshold(struct i2c_client *client, unsigned char + threshold) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_THRES__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_TAP_THRES, threshold); + comres = bma2x2_smbus_write_byte(client, BMA2X2_TAP_THRES__REG, &data); + + return comres; +} + +static int bma2x2_get_tap_threshold(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_THRES_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_TAP_THRES); + *status = data; + + return comres; +} + +static int bma2x2_set_tap_samp(struct i2c_client *client, unsigned char samp) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_SAMPLES__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_TAP_SAMPLES, samp); + comres = bma2x2_smbus_write_byte(client, BMA2X2_TAP_SAMPLES__REG, + &data); + + return comres; +} + +static int bma2x2_get_tap_samp(struct i2c_client *client, unsigned char *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TAP_THRES_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_TAP_SAMPLES); + *status = data; + + return comres; +} + +static int bma2x2_set_orient_mode(struct i2c_client *client, unsigned char mode) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_ORIENT_MODE__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_ORIENT_MODE, mode); + comres = bma2x2_smbus_write_byte(client, BMA2X2_ORIENT_MODE__REG, + &data); + + return comres; +} + +static int bma2x2_get_orient_mode(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_ORIENT_PARAM_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_ORIENT_MODE); + *status = data; + + return comres; +} + +static int bma2x2_set_orient_blocking(struct i2c_client *client, unsigned char + samp) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_ORIENT_BLOCK__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_ORIENT_BLOCK, samp); + comres = bma2x2_smbus_write_byte(client, BMA2X2_ORIENT_BLOCK__REG, + &data); + + return comres; +} + +static int bma2x2_get_orient_blocking(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_ORIENT_PARAM_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_ORIENT_BLOCK); + *status = data; + + return comres; +} + +static int bma2x2_set_orient_hyst(struct i2c_client *client, unsigned char + orienthyst) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_ORIENT_HYST__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_ORIENT_HYST, orienthyst); + comres = bma2x2_smbus_write_byte(client, BMA2X2_ORIENT_HYST__REG, + &data); + + return comres; +} + +static int bma2x2_get_orient_hyst(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_ORIENT_PARAM_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_ORIENT_HYST); + *status = data; + + return comres; +} +static int bma2x2_set_theta_blocking(struct i2c_client *client, unsigned char + thetablk) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_THETA_BLOCK__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_THETA_BLOCK, thetablk); + comres = bma2x2_smbus_write_byte(client, BMA2X2_THETA_BLOCK__REG, + &data); + + return comres; +} + +static int bma2x2_get_theta_blocking(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_THETA_BLOCK_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_THETA_BLOCK); + *status = data; + + return comres; +} + +static int bma2x2_set_theta_flat(struct i2c_client *client, unsigned char + thetaflat) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_THETA_FLAT__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_THETA_FLAT, thetaflat); + comres = bma2x2_smbus_write_byte(client, BMA2X2_THETA_FLAT__REG, &data); + + return comres; +} + +static int bma2x2_get_theta_flat(struct i2c_client *client, unsigned char + *status) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_THETA_FLAT_REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_THETA_FLAT); + *status = data; + + return comres; +} + +static int bma2x2_set_flat_hold_time(struct i2c_client *client, unsigned char + holdtime) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_FLAT_HOLD_TIME__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_FLAT_HOLD_TIME, holdtime); + comres = bma2x2_smbus_write_byte(client, BMA2X2_FLAT_HOLD_TIME__REG, + &data); + + return comres; +} + +static int bma2x2_get_flat_hold_time(struct i2c_client *client, unsigned char + *holdtime) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_FLAT_HOLD_TIME_REG, + &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_FLAT_HOLD_TIME); + *holdtime = data; + + return comres; +} + +/*! + * brief: bma2x2 switch from normal to suspend mode + * @param[i] bma2x2 + * @param[i] data1, write to PMU_LPW + * @param[i] data2, write to PMU_LOW_NOSIE + * + * @return zero success, none-zero failed + */ +static int bma2x2_normal_to_suspend(struct bma2x2_data *bma2x2, + unsigned char data1, unsigned char data2) +{ + unsigned char current_fifo_mode; + unsigned char current_op_mode; + if (bma2x2 == NULL) + return -1; + /* get current op mode from mode register */ + if (bma2x2_get_mode(bma2x2->bma2x2_client, ¤t_op_mode) < 0) + return -1; + /* only aimed at operatiom mode chang from normal/lpw1 mode + * to suspend state. + */ + if (current_op_mode == BMA2X2_MODE_NORMAL || + current_op_mode == BMA2X2_MODE_LOWPOWER1) { + /* get current fifo mode from fifo config register */ + if (bma2x2_get_fifo_mode(bma2x2->bma2x2_client, + ¤t_fifo_mode) < 0) + return -1; + else { + bma2x2_smbus_write_byte(bma2x2->bma2x2_client, + BMA2X2_LOW_NOISE_CTRL_REG, &data2); + bma2x2_smbus_write_byte(bma2x2->bma2x2_client, + BMA2X2_MODE_CTRL_REG, &data1); + bma2x2_smbus_write_byte(bma2x2->bma2x2_client, + BMA2X2_FIFO_MODE__REG, ¤t_fifo_mode); + mdelay(3); + return 0; + } + } else { + bma2x2_smbus_write_byte(bma2x2->bma2x2_client, + BMA2X2_LOW_NOISE_CTRL_REG, &data2); + bma2x2_smbus_write_byte(bma2x2->bma2x2_client, + BMA2X2_MODE_CTRL_REG, &data1); + mdelay(3); + return 0; + } + +} + +static int bma2x2_set_mode(struct i2c_client *client, unsigned char mode, + unsigned char enabled_mode) +{ + int comres = 0; + unsigned char data1 = 0; + unsigned char data2 = 0; + int ret = 0; + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + mutex_lock(&bma2x2->mode_mutex); + if (BMA2X2_MODE_SUSPEND == mode) { + if (enabled_mode != BMA_ENABLED_ALL) { + if ((bma2x2->bma_mode_enabled & + (1<<enabled_mode)) == 0) { + /* sensor is already closed in this mode */ + mutex_unlock(&bma2x2->mode_mutex); + return 0; + } else { + bma2x2->bma_mode_enabled &= ~(1<<enabled_mode); + } + } else { + /* shut down, close all and force do it*/ + bma2x2->bma_mode_enabled = 0; + } + } else if (BMA2X2_MODE_NORMAL == mode) { + if ((bma2x2->bma_mode_enabled & (1<<enabled_mode)) != 0) { + /* sensor is already enabled in this mode */ + mutex_unlock(&bma2x2->mode_mutex); + return 0; + } else { + bma2x2->bma_mode_enabled |= (1<<enabled_mode); + } + } else { + /* other mode, close all and force do it*/ + bma2x2->bma_mode_enabled = 0; + } + mutex_unlock(&bma2x2->mode_mutex); + + if (mode < 6) { + comres = bma2x2_smbus_read_byte(client, BMA2X2_MODE_CTRL_REG, + &data1); + comres = bma2x2_smbus_read_byte(client, + BMA2X2_LOW_NOISE_CTRL_REG, + &data2); + switch (mode) { + case BMA2X2_MODE_NORMAL: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_MODE_CTRL, 0); + data2 = BMA2X2_SET_BITSLICE(data2, + BMA2X2_LOW_POWER_MODE, 0); + bma2x2_smbus_write_byte(client, + BMA2X2_MODE_CTRL_REG, &data1); + mdelay(3); + bma2x2_smbus_write_byte(client, + BMA2X2_LOW_NOISE_CTRL_REG, &data2); + break; + case BMA2X2_MODE_LOWPOWER1: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_MODE_CTRL, 2); + data2 = BMA2X2_SET_BITSLICE(data2, + BMA2X2_LOW_POWER_MODE, 0); + bma2x2_smbus_write_byte(client, + BMA2X2_MODE_CTRL_REG, &data1); + mdelay(3); + bma2x2_smbus_write_byte(client, + BMA2X2_LOW_NOISE_CTRL_REG, &data2); + break; + case BMA2X2_MODE_SUSPEND: + if (bma2x2->bma_mode_enabled != 0) { + PERR("bma still working"); + return 0; + } + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_MODE_CTRL, 4); + data2 = BMA2X2_SET_BITSLICE(data2, + BMA2X2_LOW_POWER_MODE, 0); + /*aimed at anomaly resolution when switch to suspend*/ + ret = bma2x2_normal_to_suspend(bma2x2, data1, data2); + if (ret < 0) + PERR("Error switching to suspend"); + break; + case BMA2X2_MODE_DEEP_SUSPEND: + if (bma2x2->bma_mode_enabled != 0) { + PERR("bma still working"); + return 0; + } + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_MODE_CTRL, 1); + data2 = BMA2X2_SET_BITSLICE(data2, + BMA2X2_LOW_POWER_MODE, 1); + bma2x2_smbus_write_byte(client, + BMA2X2_MODE_CTRL_REG, &data1); + mdelay(3); + bma2x2_smbus_write_byte(client, + BMA2X2_LOW_NOISE_CTRL_REG, &data2); + break; + case BMA2X2_MODE_LOWPOWER2: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_MODE_CTRL, 2); + data2 = BMA2X2_SET_BITSLICE(data2, + BMA2X2_LOW_POWER_MODE, 1); + bma2x2_smbus_write_byte(client, + BMA2X2_MODE_CTRL_REG, &data1); + mdelay(3); + bma2x2_smbus_write_byte(client, + BMA2X2_LOW_NOISE_CTRL_REG, &data2); + break; + case BMA2X2_MODE_STANDBY: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_MODE_CTRL, 4); + data2 = BMA2X2_SET_BITSLICE(data2, + BMA2X2_LOW_POWER_MODE, 1); + bma2x2_smbus_write_byte(client, + BMA2X2_LOW_NOISE_CTRL_REG, &data2); + mdelay(3); + bma2x2_smbus_write_byte(client, + BMA2X2_MODE_CTRL_REG, &data1); + break; + } + } else { + comres = -1; + } + + return comres; +} + + +static int bma2x2_get_mode(struct i2c_client *client, unsigned char *mode) +{ + int comres = 0; + unsigned char data1 = 0; + unsigned char data2 = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_MODE_CTRL_REG, &data1); + comres = bma2x2_smbus_read_byte(client, BMA2X2_LOW_NOISE_CTRL_REG, + &data2); + + data1 = (data1 & 0xE0) >> 5; + data2 = (data2 & 0x40) >> 6; + + + if ((data1 == 0x00) && (data2 == 0x00)) { + *mode = BMA2X2_MODE_NORMAL; + } else { + if ((data1 == 0x02) && (data2 == 0x00)) { + *mode = BMA2X2_MODE_LOWPOWER1; + } else { + if ((data1 == 0x04 || data1 == 0x06) && + (data2 == 0x00)) { + *mode = BMA2X2_MODE_SUSPEND; + } else { + if (((data1 & 0x01) == 0x01)) { + *mode = BMA2X2_MODE_DEEP_SUSPEND; + } else { + if ((data1 == 0x02) && + (data2 == 0x01)) { + *mode = BMA2X2_MODE_LOWPOWER2; + } else { + if ((data1 == 0x04) && (data2 == + 0x01)) { + *mode = + BMA2X2_MODE_STANDBY; + } else { + *mode = + BMA2X2_MODE_DEEP_SUSPEND; + } + } + } + } + } + } + + return comres; +} + +static int bma2x2_set_range(struct i2c_client *client, unsigned char Range) +{ + int comres = 0; + unsigned char data1 = 0; + + if ((Range == 3) || (Range == 5) || (Range == 8) || (Range == 12)) { + comres = bma2x2_smbus_read_byte(client, BMA2X2_RANGE_SEL_REG, + &data1); + switch (Range) { + case BMA2X2_RANGE_2G: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_RANGE_SEL, 3); + break; + case BMA2X2_RANGE_4G: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_RANGE_SEL, 5); + break; + case BMA2X2_RANGE_8G: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_RANGE_SEL, 8); + break; + case BMA2X2_RANGE_16G: + data1 = BMA2X2_SET_BITSLICE(data1, + BMA2X2_RANGE_SEL, 12); + break; + default: + break; + } + comres += bma2x2_smbus_write_byte(client, BMA2X2_RANGE_SEL_REG, + &data1); + } else { + comres = -1; + } + + return comres; +} + +static int bma2x2_get_range(struct i2c_client *client, unsigned char *Range) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_RANGE_SEL__REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_RANGE_SEL); + *Range = data; + + return comres; +} + + +static int bma2x2_set_bandwidth(struct i2c_client *client, unsigned char BW) +{ + int comres = 0; + unsigned char data = 0; + int Bandwidth = 0; + + if (BW > 7 && BW < 16) { + switch (BW) { + case BMA2X2_BW_7_81HZ: + Bandwidth = BMA2X2_BW_7_81HZ; + + /* 7.81 Hz 64000 uS */ + break; + case BMA2X2_BW_15_63HZ: + Bandwidth = BMA2X2_BW_15_63HZ; + + /* 15.63 Hz 32000 uS */ + break; + case BMA2X2_BW_31_25HZ: + Bandwidth = BMA2X2_BW_31_25HZ; + + /* 31.25 Hz 16000 uS */ + break; + case BMA2X2_BW_62_50HZ: + Bandwidth = BMA2X2_BW_62_50HZ; + + /* 62.50 Hz 8000 uS */ + break; + case BMA2X2_BW_125HZ: + Bandwidth = BMA2X2_BW_125HZ; + + /* 125 Hz 4000 uS */ + break; + case BMA2X2_BW_250HZ: + Bandwidth = BMA2X2_BW_250HZ; + + /* 250 Hz 2000 uS */ + break; + case BMA2X2_BW_500HZ: + Bandwidth = BMA2X2_BW_500HZ; + + /* 500 Hz 1000 uS */ + break; + case BMA2X2_BW_1000HZ: + Bandwidth = BMA2X2_BW_1000HZ; + + /* 1000 Hz 500 uS */ + break; + default: + break; + } + comres = bma2x2_smbus_read_byte(client, BMA2X2_BANDWIDTH__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_BANDWIDTH, Bandwidth); + comres += bma2x2_smbus_write_byte(client, BMA2X2_BANDWIDTH__REG, + &data); + } else { + comres = -1; + } + + return comres; +} + +static int bma2x2_get_bandwidth(struct i2c_client *client, unsigned char *BW) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_BANDWIDTH__REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_BANDWIDTH); + *BW = data; + + return comres; +} + +int bma2x2_get_sleep_duration(struct i2c_client *client, unsigned char + *sleep_dur) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_SLEEP_DUR__REG, &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_SLEEP_DUR); + *sleep_dur = data; + + return comres; +} + +int bma2x2_set_sleep_duration(struct i2c_client *client, unsigned char + sleep_dur) +{ + int comres = 0; + unsigned char data = 0; + int sleep_duration = 0; + + if (sleep_dur > 4 && sleep_dur < 16) { + switch (sleep_dur) { + case BMA2X2_SLEEP_DUR_0_5MS: + sleep_duration = BMA2X2_SLEEP_DUR_0_5MS; + + /* 0.5 MS */ + break; + case BMA2X2_SLEEP_DUR_1MS: + sleep_duration = BMA2X2_SLEEP_DUR_1MS; + + /* 1 MS */ + break; + case BMA2X2_SLEEP_DUR_2MS: + sleep_duration = BMA2X2_SLEEP_DUR_2MS; + + /* 2 MS */ + break; + case BMA2X2_SLEEP_DUR_4MS: + sleep_duration = BMA2X2_SLEEP_DUR_4MS; + + /* 4 MS */ + break; + case BMA2X2_SLEEP_DUR_6MS: + sleep_duration = BMA2X2_SLEEP_DUR_6MS; + + /* 6 MS */ + break; + case BMA2X2_SLEEP_DUR_10MS: + sleep_duration = BMA2X2_SLEEP_DUR_10MS; + + /* 10 MS */ + break; + case BMA2X2_SLEEP_DUR_25MS: + sleep_duration = BMA2X2_SLEEP_DUR_25MS; + + /* 25 MS */ + break; + case BMA2X2_SLEEP_DUR_50MS: + sleep_duration = BMA2X2_SLEEP_DUR_50MS; + + /* 50 MS */ + break; + case BMA2X2_SLEEP_DUR_100MS: + sleep_duration = BMA2X2_SLEEP_DUR_100MS; + + /* 100 MS */ + break; + case BMA2X2_SLEEP_DUR_500MS: + sleep_duration = BMA2X2_SLEEP_DUR_500MS; + + /* 500 MS */ + break; + case BMA2X2_SLEEP_DUR_1S: + sleep_duration = BMA2X2_SLEEP_DUR_1S; + + /* 1 SECS */ + break; + default: + break; + } + comres = bma2x2_smbus_read_byte(client, BMA2X2_SLEEP_DUR__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_SLEEP_DUR, + sleep_duration); + comres = bma2x2_smbus_write_byte(client, BMA2X2_SLEEP_DUR__REG, + &data); + } else { + comres = -1; + } + + + return comres; +} + +static int bma2x2_get_fifo_mode(struct i2c_client *client, unsigned char + *fifo_mode) +{ + int comres; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_FIFO_MODE__REG, &data); + *fifo_mode = BMA2X2_GET_BITSLICE(data, BMA2X2_FIFO_MODE); + + return comres; +} + +static int bma2x2_set_fifo_mode(struct i2c_client *client, unsigned char + fifo_mode) +{ + unsigned char data = 0; + int comres = 0; + + if (fifo_mode < 4) { + comres = bma2x2_smbus_read_byte(client, BMA2X2_FIFO_MODE__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_FIFO_MODE, fifo_mode); + comres = bma2x2_smbus_write_byte(client, BMA2X2_FIFO_MODE__REG, + &data); + } else { + comres = -1; + } + + return comres; +} + +static int bma2x2_get_fifo_trig(struct i2c_client *client, unsigned char + *fifo_trig) +{ + int comres; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_FIFO_TRIGGER_ACTION__REG, &data); + *fifo_trig = BMA2X2_GET_BITSLICE(data, BMA2X2_FIFO_TRIGGER_ACTION); + + return comres; +} + +static int bma2x2_set_fifo_trig(struct i2c_client *client, unsigned char + fifo_trig) +{ + unsigned char data = 0; + int comres = 0; + + if (fifo_trig < 4) { + comres = bma2x2_smbus_read_byte(client, + BMA2X2_FIFO_TRIGGER_ACTION__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_FIFO_TRIGGER_ACTION, + fifo_trig); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_FIFO_TRIGGER_ACTION__REG, &data); + } else { + comres = -1; + } + + return comres; +} + +static int bma2x2_get_fifo_trig_src(struct i2c_client *client, unsigned char + *trig_src) +{ + int comres; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_FIFO_TRIGGER_SOURCE__REG, &data); + *trig_src = BMA2X2_GET_BITSLICE(data, BMA2X2_FIFO_TRIGGER_SOURCE); + + return comres; +} + +static int bma2x2_set_fifo_trig_src(struct i2c_client *client, unsigned char + trig_src) +{ + unsigned char data = 0; + int comres = 0; + + if (trig_src < 4) { + comres = bma2x2_smbus_read_byte(client, + BMA2X2_FIFO_TRIGGER_SOURCE__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_FIFO_TRIGGER_SOURCE, + trig_src); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_FIFO_TRIGGER_SOURCE__REG, &data); + } else { + comres = -1; + } + + return comres; +} + +static int bma2x2_get_fifo_framecount(struct i2c_client *client, unsigned char + *framecount) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_FIFO_FRAME_COUNTER_S__REG, &data); + *framecount = BMA2X2_GET_BITSLICE(data, BMA2X2_FIFO_FRAME_COUNTER_S); + + return comres; +} + +static int bma2x2_get_fifo_data_sel(struct i2c_client *client, unsigned char + *data_sel) +{ + int comres; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, + BMA2X2_FIFO_DATA_SELECT__REG, &data); + *data_sel = BMA2X2_GET_BITSLICE(data, BMA2X2_FIFO_DATA_SELECT); + + return comres; +} + +static int bma2x2_set_fifo_data_sel(struct i2c_client *client, unsigned char + data_sel) +{ + unsigned char data = 0; + int comres = 0; + + if (data_sel < 4) { + comres = bma2x2_smbus_read_byte(client, + BMA2X2_FIFO_DATA_SELECT__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_FIFO_DATA_SELECT, + data_sel); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_FIFO_DATA_SELECT__REG, + &data); + } else { + comres = -1; + } + + return comres; +} + + +static int bma2x2_get_offset_target(struct i2c_client *client, unsigned char + channel, unsigned char *offset) +{ + unsigned char data = 0; + int comres = 0; + + switch (channel) { + case BMA2X2_CUT_OFF: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_CUTOFF__REG, &data); + *offset = BMA2X2_GET_BITSLICE(data, BMA2X2_COMP_CUTOFF); + break; + case BMA2X2_OFFSET_TRIGGER_X: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_TARGET_OFFSET_X__REG, &data); + *offset = BMA2X2_GET_BITSLICE(data, + BMA2X2_COMP_TARGET_OFFSET_X); + break; + case BMA2X2_OFFSET_TRIGGER_Y: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_TARGET_OFFSET_Y__REG, &data); + *offset = BMA2X2_GET_BITSLICE(data, + BMA2X2_COMP_TARGET_OFFSET_Y); + break; + case BMA2X2_OFFSET_TRIGGER_Z: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_TARGET_OFFSET_Z__REG, &data); + *offset = BMA2X2_GET_BITSLICE(data, + BMA2X2_COMP_TARGET_OFFSET_Z); + break; + default: + comres = -1; + break; + } + + return comres; +} + +static int bma2x2_set_offset_target(struct i2c_client *client, unsigned char + channel, unsigned char offset) +{ + unsigned char data = 0; + int comres = 0; + + switch (channel) { + case BMA2X2_CUT_OFF: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_CUTOFF__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_COMP_CUTOFF, + offset); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_COMP_CUTOFF__REG, &data); + break; + case BMA2X2_OFFSET_TRIGGER_X: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_TARGET_OFFSET_X__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, + BMA2X2_COMP_TARGET_OFFSET_X, + offset); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_COMP_TARGET_OFFSET_X__REG, + &data); + break; + case BMA2X2_OFFSET_TRIGGER_Y: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_TARGET_OFFSET_Y__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, + BMA2X2_COMP_TARGET_OFFSET_Y, + offset); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_COMP_TARGET_OFFSET_Y__REG, + &data); + break; + case BMA2X2_OFFSET_TRIGGER_Z: + comres = bma2x2_smbus_read_byte(client, + BMA2X2_COMP_TARGET_OFFSET_Z__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, + BMA2X2_COMP_TARGET_OFFSET_Z, + offset); + comres = bma2x2_smbus_write_byte(client, + BMA2X2_COMP_TARGET_OFFSET_Z__REG, + &data); + break; + default: + comres = -1; + break; + } + + return comres; +} + +static int bma2x2_get_cal_ready(struct i2c_client *client, + unsigned char *calrdy) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_FAST_CAL_RDY_S__REG, + &data); + data = BMA2X2_GET_BITSLICE(data, BMA2X2_FAST_CAL_RDY_S); + *calrdy = data; + + return comres; +} + +static int bma2x2_set_cal_trigger(struct i2c_client *client, unsigned char + caltrigger) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_CAL_TRIGGER__REG, &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_CAL_TRIGGER, caltrigger); + comres = bma2x2_smbus_write_byte(client, BMA2X2_CAL_TRIGGER__REG, + &data); + + return comres; +} + +static int bma2x2_write_reg(struct i2c_client *client, unsigned char addr, + unsigned char *data) +{ + int comres = 0; + comres = bma2x2_smbus_write_byte(client, addr, data); + + return comres; +} + + +static int bma2x2_set_offset_x(struct i2c_client *client, unsigned char + offsetfilt) +{ + int comres = 0; + unsigned char data = 0; + + data = offsetfilt; + +#ifdef CONFIG_SENSORS_BMI058 + comres = bma2x2_smbus_write_byte(client, BMI058_OFFSET_X_AXIS_REG, + &data); +#else + comres = bma2x2_smbus_write_byte(client, BMA2X2_OFFSET_X_AXIS_REG, + &data); +#endif + + return comres; +} + + +static int bma2x2_get_offset_x(struct i2c_client *client, unsigned char + *offsetfilt) +{ + int comres = 0; + unsigned char data = 0; + +#ifdef CONFIG_SENSORS_BMI058 + comres = bma2x2_smbus_read_byte(client, BMI058_OFFSET_X_AXIS_REG, + &data); +#else + comres = bma2x2_smbus_read_byte(client, BMA2X2_OFFSET_X_AXIS_REG, + &data); +#endif + *offsetfilt = data; + + return comres; +} + +static int bma2x2_set_offset_y(struct i2c_client *client, unsigned char + offsetfilt) +{ + int comres = 0; + unsigned char data = 0; + + data = offsetfilt; + +#ifdef CONFIG_SENSORS_BMI058 + comres = bma2x2_smbus_write_byte(client, BMI058_OFFSET_Y_AXIS_REG, + &data); +#else + comres = bma2x2_smbus_write_byte(client, BMA2X2_OFFSET_Y_AXIS_REG, + &data); +#endif + return comres; +} + +static int bma2x2_get_offset_y(struct i2c_client *client, unsigned char + *offsetfilt) +{ + int comres = 0; + unsigned char data = 0; + +#ifdef CONFIG_SENSORS_BMI058 + comres = bma2x2_smbus_read_byte(client, BMI058_OFFSET_Y_AXIS_REG, + &data); +#else + comres = bma2x2_smbus_read_byte(client, BMA2X2_OFFSET_Y_AXIS_REG, + &data); +#endif + *offsetfilt = data; + + return comres; +} + +static int bma2x2_set_offset_z(struct i2c_client *client, unsigned char + offsetfilt) +{ + int comres = 0; + unsigned char data = 0; + + data = offsetfilt; + comres = bma2x2_smbus_write_byte(client, BMA2X2_OFFSET_Z_AXIS_REG, + &data); + + return comres; +} + +static int bma2x2_get_offset_z(struct i2c_client *client, unsigned char + *offsetfilt) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_OFFSET_Z_AXIS_REG, + &data); + *offsetfilt = data; + + return comres; +} + + +static int bma2x2_set_selftest_st(struct i2c_client *client, unsigned char + selftest) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_EN_SELF_TEST__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_EN_SELF_TEST, selftest); + comres = bma2x2_smbus_write_byte(client, BMA2X2_EN_SELF_TEST__REG, + &data); + + return comres; +} + +static int bma2x2_set_selftest_stn(struct i2c_client *client, unsigned char stn) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_NEG_SELF_TEST__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_NEG_SELF_TEST, stn); + comres = bma2x2_smbus_write_byte(client, BMA2X2_NEG_SELF_TEST__REG, + &data); + + return comres; +} + +static int bma2x2_set_selftest_amp(struct i2c_client *client, unsigned char amp) +{ + int comres = 0; + unsigned char data = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_SELF_TEST_AMP__REG, + &data); + data = BMA2X2_SET_BITSLICE(data, BMA2X2_SELF_TEST_AMP, amp); + comres = bma2x2_smbus_write_byte(client, BMA2X2_SELF_TEST_AMP__REG, + &data); + + return comres; +} + +static int bma2x2_read_accel_x(struct i2c_client *client, + signed char sensor_type, short *a_x) +{ + int comres = 0; + unsigned char data[2]; + + switch (sensor_type) { + case 0: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_X12_LSB__REG, data, 2); + *a_x = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_X12_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_X_MSB)<<(BMA2X2_ACC_X12_LSB__LEN)); + *a_x = *a_x << (sizeof(short)*8-(BMA2X2_ACC_X12_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + *a_x = *a_x >> (sizeof(short)*8-(BMA2X2_ACC_X12_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + break; + case 1: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_X10_LSB__REG, data, 2); + *a_x = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_X10_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_X_MSB)<<(BMA2X2_ACC_X10_LSB__LEN)); + *a_x = *a_x << (sizeof(short)*8-(BMA2X2_ACC_X10_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + *a_x = *a_x >> (sizeof(short)*8-(BMA2X2_ACC_X10_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + break; + case 2: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_X8_LSB__REG, data, 2); + *a_x = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_X8_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_X_MSB)<<(BMA2X2_ACC_X8_LSB__LEN)); + *a_x = *a_x << (sizeof(short)*8-(BMA2X2_ACC_X8_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + *a_x = *a_x >> (sizeof(short)*8-(BMA2X2_ACC_X8_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + break; + case 3: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_X14_LSB__REG, data, 2); + *a_x = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_X14_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_X_MSB)<<(BMA2X2_ACC_X14_LSB__LEN)); + *a_x = *a_x << (sizeof(short)*8-(BMA2X2_ACC_X14_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + *a_x = *a_x >> (sizeof(short)*8-(BMA2X2_ACC_X14_LSB__LEN + + BMA2X2_ACC_X_MSB__LEN)); + break; + default: + break; + } + + return comres; +} + +static int bma2x2_soft_reset(struct i2c_client *client) +{ + int comres = 0; + unsigned char data = BMA2X2_EN_SOFT_RESET_VALUE; + + comres = bma2x2_smbus_write_byte(client, BMA2X2_EN_SOFT_RESET__REG, + &data); + + return comres; +} + +static int bma2x2_read_accel_y(struct i2c_client *client, + signed char sensor_type, short *a_y) +{ + int comres = 0; + unsigned char data[2]; + + switch (sensor_type) { + case 0: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Y12_LSB__REG, data, 2); + *a_y = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Y12_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Y_MSB)<<(BMA2X2_ACC_Y12_LSB__LEN)); + *a_y = *a_y << (sizeof(short)*8-(BMA2X2_ACC_Y12_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + *a_y = *a_y >> (sizeof(short)*8-(BMA2X2_ACC_Y12_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + break; + case 1: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Y10_LSB__REG, data, 2); + *a_y = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Y10_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Y_MSB)<<(BMA2X2_ACC_Y10_LSB__LEN)); + *a_y = *a_y << (sizeof(short)*8-(BMA2X2_ACC_Y10_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + *a_y = *a_y >> (sizeof(short)*8-(BMA2X2_ACC_Y10_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + break; + case 2: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Y8_LSB__REG, data, 2); + *a_y = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Y8_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Y_MSB)<<(BMA2X2_ACC_Y8_LSB__LEN)); + *a_y = *a_y << (sizeof(short)*8-(BMA2X2_ACC_Y8_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + *a_y = *a_y >> (sizeof(short)*8-(BMA2X2_ACC_Y8_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + break; + case 3: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Y14_LSB__REG, data, 2); + *a_y = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Y14_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Y_MSB)<<(BMA2X2_ACC_Y14_LSB__LEN)); + *a_y = *a_y << (sizeof(short)*8-(BMA2X2_ACC_Y14_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + *a_y = *a_y >> (sizeof(short)*8-(BMA2X2_ACC_Y14_LSB__LEN + + BMA2X2_ACC_Y_MSB__LEN)); + break; + default: + break; + } + + return comres; +} + +static int bma2x2_read_accel_z(struct i2c_client *client, + signed char sensor_type, short *a_z) +{ + int comres = 0; + unsigned char data[2]; + + switch (sensor_type) { + case 0: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Z12_LSB__REG, data, 2); + *a_z = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Z12_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Z_MSB)<<(BMA2X2_ACC_Z12_LSB__LEN)); + *a_z = *a_z << (sizeof(short)*8-(BMA2X2_ACC_Z12_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + *a_z = *a_z >> (sizeof(short)*8-(BMA2X2_ACC_Z12_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + break; + case 1: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Z10_LSB__REG, data, 2); + *a_z = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Z10_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Z_MSB)<<(BMA2X2_ACC_Z10_LSB__LEN)); + *a_z = *a_z << (sizeof(short)*8-(BMA2X2_ACC_Z10_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + *a_z = *a_z >> (sizeof(short)*8-(BMA2X2_ACC_Z10_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + break; + case 2: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Z8_LSB__REG, data, 2); + *a_z = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Z8_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Z_MSB)<<(BMA2X2_ACC_Z8_LSB__LEN)); + *a_z = *a_z << (sizeof(short)*8-(BMA2X2_ACC_Z8_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + *a_z = *a_z >> (sizeof(short)*8-(BMA2X2_ACC_Z8_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + break; + case 3: + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_Z14_LSB__REG, data, 2); + *a_z = BMA2X2_GET_BITSLICE(data[0], BMA2X2_ACC_Z14_LSB)| + (BMA2X2_GET_BITSLICE(data[1], + BMA2X2_ACC_Z_MSB)<<(BMA2X2_ACC_Z14_LSB__LEN)); + *a_z = *a_z << (sizeof(short)*8-(BMA2X2_ACC_Z14_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + *a_z = *a_z >> (sizeof(short)*8-(BMA2X2_ACC_Z14_LSB__LEN + + BMA2X2_ACC_Z_MSB__LEN)); + break; + default: + break; + } + + return comres; +} + + +static int bma2x2_read_temperature(struct i2c_client *client, + signed char *temperature) +{ + unsigned char data = 0; + int comres = 0; + + comres = bma2x2_smbus_read_byte(client, BMA2X2_TEMPERATURE_REG, &data); + *temperature = (signed char)data; + + return comres; +} + +static ssize_t bma2x2_enable_int_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int type, value; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); +#ifdef CONFIG_SENSORS_BMI058 + int i; +#endif + + sscanf(buf, "%3d %3d", &type, &value); + +#ifdef CONFIG_SENSORS_BMI058 + for (i = 0; i < sizeof(int_map) / sizeof(struct interrupt_map_t); i++) { + if (int_map[i].x == type) { + type = int_map[i].y; + break; + } + if (int_map[i].y == type) { + type = int_map[i].x; + break; + } + } +#endif + + if (bma2x2_set_Int_Enable(bma2x2->bma2x2_client, type, value) < 0) + return -EINVAL; + + return count; +} + + +static ssize_t bma2x2_int_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_Int_Mode(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma2x2_int_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_Int_Mode(bma2x2->bma2x2_client, (unsigned char)data) < 0) + return -EINVAL; + + return count; +} +static ssize_t bma2x2_slope_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_slope_duration(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_slope_duration_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_slope_duration(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_slope_no_mot_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_slope_no_mot_duration(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_slope_no_mot_duration_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_slope_no_mot_duration(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + + +static ssize_t bma2x2_slope_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_slope_threshold(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_slope_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_slope_threshold(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_slope_no_mot_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_slope_no_mot_threshold(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_slope_no_mot_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_slope_no_mot_threshold(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_high_g_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_high_g_duration(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_high_g_duration_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_high_g_duration(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_high_g_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_high_g_threshold(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_high_g_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_high_g_threshold(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_low_g_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_low_g_duration(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_low_g_duration_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_low_g_duration(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_low_g_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_low_g_threshold(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_low_g_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_low_g_threshold(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} +static ssize_t bma2x2_tap_threshold_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_tap_threshold(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_tap_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_tap_threshold(bma2x2->bma2x2_client, (unsigned char)data) + < 0) + return -EINVAL; + + return count; +} +static ssize_t bma2x2_tap_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_tap_duration(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_tap_duration_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_tap_duration(bma2x2->bma2x2_client, (unsigned char)data) + < 0) + return -EINVAL; + + return count; +} +static ssize_t bma2x2_tap_quiet_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_tap_quiet(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_tap_quiet_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_tap_quiet(bma2x2->bma2x2_client, (unsigned char)data) < + 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_tap_shock_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_tap_shock(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_tap_shock_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_tap_shock(bma2x2->bma2x2_client, (unsigned char)data) < + 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_tap_samp_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_tap_samp(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_tap_samp_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_tap_samp(bma2x2->bma2x2_client, (unsigned char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_orient_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_orient_mode(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_orient_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_orient_mode(bma2x2->bma2x2_client, (unsigned char)data) < + 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_orient_blocking_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_orient_blocking(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_orient_blocking_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_orient_blocking(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} +static ssize_t bma2x2_orient_hyst_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_orient_hyst(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_orient_hyst_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_orient_hyst(bma2x2->bma2x2_client, (unsigned char)data) < + 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_orient_theta_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_theta_blocking(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_orient_theta_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_theta_blocking(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_flat_theta_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_theta_flat(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_flat_theta_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_theta_flat(bma2x2->bma2x2_client, (unsigned char)data) < + 0) + return -EINVAL; + + return count; +} +static ssize_t bma2x2_flat_hold_time_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_get_flat_hold_time(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} +static ssize_t bma2x2_selftest_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", atomic_read(&bma2x2->selftest_result)); + +} + +static ssize_t bma2x2_softreset_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if (bma2x2_soft_reset(bma2x2->bma2x2_client) < 0) + return -EINVAL; + + return count; +} +static ssize_t bma2x2_selftest_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + + unsigned long data; + unsigned char clear_value = 0; + int error; + short value1 = 0; + short value2 = 0; + short diff = 0; + unsigned long result = 0; + unsigned char test_result_branch = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + bma2x2_soft_reset(bma2x2->bma2x2_client); + mdelay(5); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (data != 1) + return -EINVAL; + + bma2x2_write_reg(bma2x2->bma2x2_client, 0x32, &clear_value); + + if ((bma2x2->sensor_type == BMA280_TYPE) || + (bma2x2->sensor_type == BMA255_TYPE)) { +#ifdef CONFIG_SENSORS_BMI058 + /*set self test amp */ + if (bma2x2_set_selftest_amp(bma2x2->bma2x2_client, 1) < 0) + return -EINVAL; + /* set to 8 G range */ + if (bma2x2_set_range(bma2x2->bma2x2_client, + BMA2X2_RANGE_8G) < 0) + return -EINVAL; +#else + /* set to 4 G range */ + if (bma2x2_set_range(bma2x2->bma2x2_client, + BMA2X2_RANGE_4G) < 0) + return -EINVAL; +#endif + } + + if ((bma2x2->sensor_type == BMA250E_TYPE) || + (bma2x2->sensor_type == BMA222E_TYPE)) { + /* set to 8 G range */ + if (bma2x2_set_range(bma2x2->bma2x2_client, 8) < 0) + return -EINVAL; + if (bma2x2_set_selftest_amp(bma2x2->bma2x2_client, 1) < 0) + return -EINVAL; + } + + /* 1 for x-axis(but BMI058 is 1 for y-axis )*/ + bma2x2_set_selftest_st(bma2x2->bma2x2_client, 1); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 0); + mdelay(10); + bma2x2_read_accel_x(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value1); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 1); + mdelay(10); + bma2x2_read_accel_x(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value2); + diff = value1-value2; + +#ifdef CONFIG_SENSORS_BMI058 + PINFO("diff y is %d,value1 is %d, value2 is %d\n", diff, + value1, value2); + test_result_branch = 2; +#else + PINFO("diff x is %d,value1 is %d, value2 is %d\n", diff, + value1, value2); + test_result_branch = 1; +#endif + + if (bma2x2->sensor_type == BMA280_TYPE) { +#ifdef CONFIG_SENSORS_BMI058 + if (abs(diff) < 819) + result |= test_result_branch; +#else + if (abs(diff) < 1638) + result |= test_result_branch; +#endif + } + if (bma2x2->sensor_type == BMA255_TYPE) { + if (abs(diff) < 409) + result |= 1; + } + if (bma2x2->sensor_type == BMA250E_TYPE) { + if (abs(diff) < 51) + result |= 1; + } + if (bma2x2->sensor_type == BMA222E_TYPE) { + if (abs(diff) < 12) + result |= 1; + } + + /* 2 for y-axis but BMI058 is 1*/ + bma2x2_set_selftest_st(bma2x2->bma2x2_client, 2); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 0); + mdelay(10); + bma2x2_read_accel_y(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value1); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 1); + mdelay(10); + bma2x2_read_accel_y(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value2); + diff = value1-value2; + +#ifdef CONFIG_SENSORS_BMI058 + PINFO("diff x is %d,value1 is %d, value2 is %d\n", diff, + value1, value2); + test_result_branch = 1; +#else + PINFO("diff y is %d,value1 is %d, value2 is %d\n", diff, + value1, value2); + test_result_branch = 2; +#endif + + if (bma2x2->sensor_type == BMA280_TYPE) { +#ifdef CONFIG_SENSORS_BMI058 + if (abs(diff) < 819) + result |= test_result_branch; +#else + if (abs(diff) < 1638) + result |= test_result_branch; +#endif + } + if (bma2x2->sensor_type == BMA255_TYPE) { + if (abs(diff) < 409) + result |= test_result_branch; + } + if (bma2x2->sensor_type == BMA250E_TYPE) { + if (abs(diff) < 51) + result |= test_result_branch; + } + if (bma2x2->sensor_type == BMA222E_TYPE) { + if (abs(diff) < 12) + result |= test_result_branch; + } + + + bma2x2_set_selftest_st(bma2x2->bma2x2_client, 3); /* 3 for z-axis*/ + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 0); + mdelay(10); + bma2x2_read_accel_z(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value1); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 1); + mdelay(10); + bma2x2_read_accel_z(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value2); + diff = value1-value2; + + PINFO("diff z is %d,value1 is %d, value2 is %d\n", diff, + value1, value2); + + if (bma2x2->sensor_type == BMA280_TYPE) { +#ifdef CONFIG_SENSORS_BMI058 + if (abs(diff) < 409) + result |= 4; +#else + if (abs(diff) < 819) + result |= 4; +#endif + } + if (bma2x2->sensor_type == BMA255_TYPE) { + if (abs(diff) < 204) + result |= 4; + } + if (bma2x2->sensor_type == BMA250E_TYPE) { + if (abs(diff) < 25) + result |= 4; + } + if (bma2x2->sensor_type == BMA222E_TYPE) { + if (abs(diff) < 6) + result |= 4; + } + + /* self test for bma254 */ + if ((bma2x2->sensor_type == BMA255_TYPE) && (result > 0)) { + result = 0; + bma2x2_soft_reset(bma2x2->bma2x2_client); + mdelay(5); + bma2x2_write_reg(bma2x2->bma2x2_client, 0x32, &clear_value); + /* set to 8 G range */ + if (bma2x2_set_range(bma2x2->bma2x2_client, 8) < 0) + return -EINVAL; + if (bma2x2_set_selftest_amp(bma2x2->bma2x2_client, 1) < 0) + return -EINVAL; + + bma2x2_set_selftest_st(bma2x2->bma2x2_client, 1); /* 1 + for x-axis*/ + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 0); /* + positive direction*/ + mdelay(10); + bma2x2_read_accel_x(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value1); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 1); /* + negative direction*/ + mdelay(10); + bma2x2_read_accel_x(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value2); + diff = value1-value2; + + PINFO("diff x is %d,value1 is %d, value2 is %d\n", + diff, value1, value2); + if (abs(diff) < 204) + result |= 1; + + bma2x2_set_selftest_st(bma2x2->bma2x2_client, 2); /* 2 + for y-axis*/ + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 0); /* + positive direction*/ + mdelay(10); + bma2x2_read_accel_y(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value1); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 1); /* + negative direction*/ + mdelay(10); + bma2x2_read_accel_y(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value2); + diff = value1-value2; + PINFO("diff y is %d,value1 is %d, value2 is %d\n", + diff, value1, value2); + + if (abs(diff) < 204) + result |= 2; + + bma2x2_set_selftest_st(bma2x2->bma2x2_client, 3); /* 3 + for z-axis*/ + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 0); /* + positive direction*/ + mdelay(10); + bma2x2_read_accel_z(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value1); + bma2x2_set_selftest_stn(bma2x2->bma2x2_client, 1); /* + negative direction*/ + mdelay(10); + bma2x2_read_accel_z(bma2x2->bma2x2_client, + bma2x2->sensor_type, &value2); + diff = value1-value2; + + PINFO("diff z is %d,value1 is %d, value2 is %d\n", + diff, value1, value2); + if (abs(diff) < 102) + result |= 4; + } + + atomic_set(&bma2x2->selftest_result, (unsigned int)result); + + bma2x2_soft_reset(bma2x2->bma2x2_client); + mdelay(5); + PINFO("self test finished\n"); + + return count; +} + + + +static ssize_t bma2x2_flat_hold_time_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_flat_hold_time(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +const int bma2x2_sensor_bitwidth[] = { + 12, 10, 8, 14 +}; + +static int bma2x2_read_accel_xyz(struct i2c_client *client, + signed char sensor_type, struct bma2x2acc *acc) +{ + int comres = 0; + unsigned char data[6]; + struct bma2x2_data *client_data = i2c_get_clientdata(client); +#ifndef BMA2X2_SENSOR_IDENTIFICATION_ENABLE + int bitwidth; +#endif + + if(!client_data){ + return BOSCH_SENSOR_BAD_ADDR; + } + + comres = bma2x2_smbus_read_byte_block(client, + BMA2X2_ACC_X12_LSB__REG, data, 6); + if (sensor_type >= 4) + return -EINVAL; + + acc->x = (data[1]<<8)|data[0]; + acc->y = (data[3]<<8)|data[2]; + acc->z = (data[5]<<8)|data[4]; + +#ifndef BMA2X2_SENSOR_IDENTIFICATION_ENABLE + bitwidth = bma2x2_sensor_bitwidth[sensor_type]; + + acc->x = (acc->x >> (16 - bitwidth)); + acc->y = (acc->y >> (16 - bitwidth)); + acc->z = (acc->z >> (16 - bitwidth)); +#endif + + bma2x2_remap_sensor_data(acc, client_data); + return comres; +} + + +static int bma_cali_flag=0; + +int bma2x2_real_cali(struct bma2x2_data *bma2x2); + + +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT + +static void bma2x2_work_func(struct work_struct *work) +{ +// struct bma2x2_data *bma2x2 = container_of((struct delayed_work *)work, + struct bma2x2_data *bma2x2 = container_of((struct work_struct *)work, + struct bma2x2_data, work); + static struct bma2x2acc acc; + int ret=0; +// unsigned long delay = msecs_to_jiffies(atomic_read(&bma2x2->delay)); + + if(bma_cali_flag==0) + { + ret = bma2x2_real_cali(bma2x2); + if(ret >= 0) + { + bma_cali_flag = 1; + } + } + bma2x2_read_accel_xyz(bma2x2->bma2x2_client, bma2x2->sensor_type, &acc); +#if 0 + input_report_abs(bma2x2->input, ABS_X, acc.x); + input_report_abs(bma2x2->input, ABS_Y, acc.y); + input_report_abs(bma2x2->input, ABS_Z, acc.z); +#else + input_event(bma2x2->input, EV_MSC, MSC_RAW, acc.x); + input_event(bma2x2->input, EV_MSC, MSC_SCAN, acc.y); + input_event(bma2x2->input, EV_MSC, MSC_GESTURE, acc.z); +#endif + input_sync(bma2x2->input); + mutex_lock(&bma2x2->value_mutex); + bma2x2->value = acc; + mutex_unlock(&bma2x2->value_mutex); +// schedule_delayed_work(&bma2x2->work, delay); +} +#endif + +static ssize_t bma2x2_register_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int address, value; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + sscanf(buf, "%3d %3d", &address, &value); + if (bma2x2_write_reg(bma2x2->bma2x2_client, (unsigned char)address, + (unsigned char *)&value) < 0) + return -EINVAL; + return count; +} +static ssize_t bma2x2_register_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + size_t count = 0; + u8 reg[0x40]; + int i; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + for (i = 0; i < 0x40; i++) { + bma2x2_smbus_read_byte(bma2x2->bma2x2_client, i, reg+i); + + count += sprintf(&buf[count], "0x%x: %d\n", i, reg[i]); + } + return count; + + +} + +static ssize_t bma2x2_range_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_range(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma2x2_range_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_range(bma2x2->bma2x2_client, (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_bandwidth_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + if (bma2x2_get_bandwidth(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_bandwidth_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2->sensor_type == BMA280_TYPE) + if ((unsigned char) data > 14) + return -EINVAL; + + if (bma2x2_set_bandwidth(bma2x2->bma2x2_client, + (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_mode(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d %d\n", data, bma2x2->bma_mode_enabled); +} + +static ssize_t bma2x2_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_mode(bma2x2->bma2x2_client, + (unsigned char) data, BMA_ENABLED_BSX) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_value_cache_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct input_dev *input = to_input_dev(dev); + struct bma2x2_data *bma2x2 = input_get_drvdata(input); + struct bma2x2acc acc_value; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + mutex_lock(&bma2x2->value_mutex); + acc_value = bma2x2->value; + mutex_unlock(&bma2x2->value_mutex); + + return sprintf(buf, "%d %d %d\n", acc_value.x, acc_value.y, + acc_value.z); +} + +static ssize_t bma2x2_value_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct input_dev *input = to_input_dev(dev); + struct bma2x2_data *bma2x2 = input_get_drvdata(input); + struct bma2x2acc acc_value; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + bma2x2_read_accel_xyz(bma2x2->bma2x2_client, bma2x2->sensor_type, + &acc_value); + + return sprintf(buf, "%d %d %d\n", acc_value.x, acc_value.y, + acc_value.z); +} + +static ssize_t bma2x2_delay_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + return sprintf(buf, "%d\n", atomic_read(&bma2x2->delay)); + +} + +static ssize_t bma2x2_chip_id_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + return sprintf(buf, "%u\n", bma2x2->chip_id); + +} + + +static ssize_t bma2x2_place_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + int place = BOSCH_SENSOR_PLACE_UNKNOWN; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (NULL != bma2x2->bst_pd) + place = bma2x2->bst_pd->place; + + return sprintf(buf, "%d\n", place); +} + + +static ssize_t bma2x2_delay_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (data > BMA2X2_MAX_DELAY) + data = BMA2X2_MAX_DELAY; + atomic_set(&bma2x2->delay, (unsigned int) data); + + return count; +} + +int bma2x2_real_cali(struct bma2x2_data *bma2x2) +{ + int cali_xyz=0; + unsigned int flag=0; + unsigned char bst_offset_x=0; + unsigned char bst_offset_y=0; + unsigned char bst_offset_z=0; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + cali_xyz = read_file("/productinfo/gsensor_calibration.ini"); + + if(cali_xyz<0){ + return -EINVAL; + } + + flag=(cali_xyz>>24)&0xff; + + if(flag==0x2) + { + bst_offset_x = cali_xyz & 0xff; + bst_offset_y = (cali_xyz>>8)&0xff; + bst_offset_z = (cali_xyz>>16)&0xff; + } + else + { + bst_offset_x = 0; + bst_offset_y = 0; + bst_offset_z = 0; + } + + if (bma2x2_set_offset_x(bma2x2->bma2x2_client, bst_offset_x) < 0) + return -EINVAL; + + if (bma2x2_set_offset_y(bma2x2->bma2x2_client, bst_offset_y) < 0) + return -EINVAL; + + if (bma2x2_set_offset_z(bma2x2->bma2x2_client, bst_offset_z) < 0) + return -EINVAL; + + return 0; +} + +static ssize_t bma2x2_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + return sprintf(buf, "%d\n", atomic_read(&bma2x2->enable)); + +} + +static void bma2x2_set_enable(struct device *dev, int enable) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + int pre_enable; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + pre_enable = atomic_read(&bma2x2->enable); + + mutex_lock(&bma2x2->enable_mutex); + + if (enable) { + + if (pre_enable == 0) { + bma2x2_set_mode(bma2x2->bma2x2_client, + BMA2X2_MODE_NORMAL, BMA_ENABLED_INPUT); + +#if 0 +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT + schedule_delayed_work(&bma2x2->work, + msecs_to_jiffies(atomic_read(&bma2x2->delay))); +#endif +#endif + hrtimer_start(&bma2x2->timer, ktime_set(0, + atomic_read(&bma2x2->delay) * 1000000), HRTIMER_MODE_REL); + atomic_set(&bma2x2->enable, 1); + } + + } else { + if (pre_enable == 1) { + bma2x2_set_mode(bma2x2->bma2x2_client, + BMA2X2_MODE_SUSPEND, BMA_ENABLED_INPUT); +#if 0 +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT + cancel_delayed_work_sync(&bma2x2->work); +#endif +#endif + hrtimer_cancel(&bma2x2->timer); + atomic_set(&bma2x2->enable, 0); + } + } + mutex_unlock(&bma2x2->enable_mutex); + +} + +static ssize_t bma2x2_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if ((data == 0) || (data == 1)) + bma2x2_set_enable(dev, data); + + return count; +} +static ssize_t bma2x2_fast_calibration_x_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + +#ifdef CONFIG_SENSORS_BMI058 + if (bma2x2_get_offset_target(bma2x2->bma2x2_client, + BMI058_OFFSET_TRIGGER_X, &data) < 0) + return -EINVAL; +#else + if (bma2x2_get_offset_target(bma2x2->bma2x2_client, + BMA2X2_OFFSET_TRIGGER_X, &data) < 0) + return -EINVAL; +#endif + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_fast_calibration_x_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + signed char tmp; + unsigned char timeout = 0; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + +#ifdef CONFIG_SENSORS_BMI058 + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMI058_OFFSET_TRIGGER_X, (unsigned char)data) < 0) + return -EINVAL; +#else + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMA2X2_OFFSET_TRIGGER_X, (unsigned char)data) < 0) + return -EINVAL; +#endif + + if (bma2x2_set_cal_trigger(bma2x2->bma2x2_client, 1) < 0) + return -EINVAL; + + do { + mdelay(2); + bma2x2_get_cal_ready(bma2x2->bma2x2_client, &tmp); + + /*PINFO("wait 2ms cal ready flag is %d\n", tmp); */ + timeout++; + if (timeout == 50) { + PINFO("get fast calibration ready error\n"); + return -EINVAL; + }; + + } while (tmp == 0); + + PINFO("x axis fast calibration finished\n"); + return count; +} + + +static int bma2x2_fast_calibration_x(struct device *dev, unsigned char data) +{ + signed char tmp; + unsigned char timeout = 0; + + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + +#ifdef CONFIG_SENSORS_BMI058 + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMI058_OFFSET_TRIGGER_X, (unsigned char)data) < 0) + return -EINVAL; +#else + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMA2X2_OFFSET_TRIGGER_X, (unsigned char)data) < 0) + return -EINVAL; +#endif + + if (bma2x2_set_cal_trigger(bma2x2->bma2x2_client, 1) < 0) + return -EINVAL; + + do { + mdelay(2); + bma2x2_get_cal_ready(bma2x2->bma2x2_client, &tmp); + + /*PINFO("wait 2ms cal ready flag is %d\n", tmp); */ + timeout++; + if (timeout == 50) { + PINFO("get fast calibration ready error\n"); + return -EINVAL; + }; + + } while (tmp == 0); + + PINFO("x axis fast calibration finished\n"); + return 0; +} + + +static ssize_t bma2x2_fast_calibration_y_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + + + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + +#ifdef CONFIG_SENSORS_BMI058 + if (bma2x2_get_offset_target(bma2x2->bma2x2_client, + BMI058_OFFSET_TRIGGER_Y, &data) < 0) + return -EINVAL; +#else + if (bma2x2_get_offset_target(bma2x2->bma2x2_client, + BMA2X2_OFFSET_TRIGGER_Y, &data) < 0) + return -EINVAL; +#endif + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_fast_calibration_y_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + signed char tmp; + unsigned char timeout = 0; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + +#ifdef CONFIG_SENSORS_BMI058 + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMI058_OFFSET_TRIGGER_Y, (unsigned char)data) < 0) + return -EINVAL; +#else + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMA2X2_OFFSET_TRIGGER_Y, (unsigned char)data) < 0) + return -EINVAL; +#endif + + if (bma2x2_set_cal_trigger(bma2x2->bma2x2_client, 2) < 0) + return -EINVAL; + + do { + mdelay(2); + bma2x2_get_cal_ready(bma2x2->bma2x2_client, &tmp); + + /*PINFO("wait 2ms cal ready flag is %d\n", tmp);*/ + timeout++; + if (timeout == 50) { + PINFO("get fast calibration ready error\n"); + return -EINVAL; + }; + + } while (tmp == 0); + + PINFO("y axis fast calibration finished\n"); + return count; +} + + +static int bma2x2_fast_calibration_y(struct device *dev, unsigned char data) +{ + signed char tmp; + unsigned char timeout = 0; + + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + +#ifdef CONFIG_SENSORS_BMI058 + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMI058_OFFSET_TRIGGER_Y, (unsigned char)data) < 0) + return -EINVAL; +#else + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, + BMA2X2_OFFSET_TRIGGER_Y, (unsigned char)data) < 0) + return -EINVAL; +#endif + + if (bma2x2_set_cal_trigger(bma2x2->bma2x2_client, 2) < 0) + return -EINVAL; + + do { + mdelay(2); + bma2x2_get_cal_ready(bma2x2->bma2x2_client, &tmp); + + /*PINFO("wait 2ms cal ready flag is %d\n", tmp);*/ + timeout++; + if (timeout == 50) { + PINFO("get fast calibration ready error\n"); + return -EINVAL; + }; + + } while (tmp == 0); + + PINFO("y axis fast calibration finished\n"); + return 0; +} + + +static ssize_t bma2x2_fast_calibration_z_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_offset_target(bma2x2->bma2x2_client, 3, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_fast_calibration_z_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + signed char tmp; + unsigned char timeout = 0; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, 3, (unsigned + char)data) < 0) + return -EINVAL; + + if (bma2x2_set_cal_trigger(bma2x2->bma2x2_client, 3) < 0) + return -EINVAL; + + do { + mdelay(2); + bma2x2_get_cal_ready(bma2x2->bma2x2_client, &tmp); + + /*PINFO("wait 2ms cal ready flag is %d\n", tmp);*/ + timeout++; + if (timeout == 50) { + PINFO("get fast calibration ready error\n"); + return -EINVAL; + }; + + } while (tmp == 0); + + PINFO("z axis fast calibration finished\n"); + return count; +} + + +static ssize_t bma2x2_fast_calibration_z(struct device *dev,unsigned char data) +{ + signed char tmp; + unsigned char timeout = 0; + + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_set_offset_target(bma2x2->bma2x2_client, 3, (unsigned + char)data) < 0) + return -EINVAL; + + if (bma2x2_set_cal_trigger(bma2x2->bma2x2_client, 3) < 0) + return -EINVAL; + + do { + mdelay(2); + bma2x2_get_cal_ready(bma2x2->bma2x2_client, &tmp); + + /*PINFO("wait 2ms cal ready flag is %d\n", tmp);*/ + timeout++; + if (timeout == 50) { + PINFO("get fast calibration ready error\n"); + return -EINVAL; + }; + + } while (tmp == 0); + + PINFO("z axis fast calibration finished\n"); + return 0; +} + + +static ssize_t bma2x2_enable_cali_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char datax = 0; + unsigned char datay = 0; + unsigned char dataz = 0; + unsigned int data=0; + + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + bma2x2_fast_calibration_x(dev,3); + bma2x2_fast_calibration_y(dev,3); + bma2x2_fast_calibration_z(dev,2); + + if (bma2x2_get_offset_x(bma2x2->bma2x2_client, &datax) < 0) + return -1; + if (bma2x2_get_offset_y(bma2x2->bma2x2_client, &datay) < 0) + return -1; + if (bma2x2_get_offset_z(bma2x2->bma2x2_client, &dataz) < 0) + return -1; + + data = dataz; + data = (data<<8)|datay; + data = (data<<8)|datax; + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma2x2_SleepDur_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_sleep_duration(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma2x2_SleepDur_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_sleep_duration(bma2x2->bma2x2_client, + (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_fifo_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_fifo_mode(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_fifo_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_fifo_mode(bma2x2->bma2x2_client, + (unsigned char) data) < 0) + return -EINVAL; + return count; +} + +static ssize_t bma2x2_fifo_trig_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_fifo_trig(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_fifo_trig_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_fifo_trig(bma2x2->bma2x2_client, + (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_fifo_trig_src_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_fifo_trig_src(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_fifo_trig_src_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + if (bma2x2_set_fifo_trig_src(bma2x2->bma2x2_client, + (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + + +/*! + * @brief show fifo_data_sel axis definition(Android definition, not sensor HW reg). + * 0--> x, y, z axis fifo data for every frame + * 1--> only x axis fifo data for every frame + * 2--> only y axis fifo data for every frame + * 3--> only z axis fifo data for every frame + */ +static ssize_t bma2x2_fifo_data_sel_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + signed char place = BOSCH_SENSOR_PLACE_UNKNOWN; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_fifo_data_sel(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + +#ifdef CONFIG_SENSORS_BMI058 +/*Update BMI058 fifo_data_sel to the BMA2x2 common definition*/ + if (BMI058_FIFO_DAT_SEL_X == data) + data = BMA2X2_FIFO_DAT_SEL_X; + else if (BMI058_FIFO_DAT_SEL_Y == data) + data = BMA2X2_FIFO_DAT_SEL_Y; +#endif + + /*remaping fifo_dat_sel if define virtual place in BSP files*/ + if ((NULL != bma2x2->bst_pd) && + (BOSCH_SENSOR_PLACE_UNKNOWN != bma2x2->bst_pd->place)) { + place = bma2x2->bst_pd->place; + /* sensor with place 0 needs not to be remapped */ + if ((place > 0) && (place < MAX_AXIS_REMAP_TAB_SZ)) { + /* BMA2X2_FIFO_DAT_SEL_X: 1, Y:2, Z:3; + * but bst_axis_remap_tab_dft[i].src_x:0, y:1, z:2 + * so we need to +1*/ + if (BMA2X2_FIFO_DAT_SEL_X == data) + data = bst_axis_remap_tab_dft[place].src_x + 1; + else if (BMA2X2_FIFO_DAT_SEL_Y == data) + data = bst_axis_remap_tab_dft[place].src_y + 1; + } + } + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_fifo_framecount_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + unsigned char mode; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_fifo_framecount(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + if (data > MAX_FIFO_F_LEVEL) { + + if (bma2x2_get_mode(bma2x2->bma2x2_client, &mode) < 0) + return -EINVAL; + + if (BMA2X2_MODE_NORMAL == mode) { + PERR("bma2x2 fifo_count: %d abnormal, op_mode: %d", + data, mode); + data = MAX_FIFO_F_LEVEL; + } else { + /*chip already suspend or shutdown*/ + data = 0; + } + } + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma2x2_fifo_framecount_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + bma2x2->fifo_count = (unsigned int) data; + + return count; +} + +static ssize_t bma2x2_temperature_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_read_temperature(bma2x2->bma2x2_client, &data) < 0) + return -EINVAL; + + return sprintf(buf, "%d\n", data); + +} + +/*! + * @brief store fifo_data_sel axis definition(Android definition, not sensor HW reg). + * 0--> x, y, z axis fifo data for every frame + * 1--> only x axis fifo data for every frame + * 2--> only y axis fifo data for every frame + * 3--> only z axis fifo data for every frame + */ +static ssize_t bma2x2_fifo_data_sel_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + signed char place; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + /*save fifo_data_sel(android definition)*/ + bma2x2->fifo_datasel = (unsigned char) data; + + /*remaping fifo_dat_sel if define virtual place*/ + if ((NULL != bma2x2->bst_pd) && + (BOSCH_SENSOR_PLACE_UNKNOWN != bma2x2->bst_pd->place)) { + place = bma2x2->bst_pd->place; + /* sensor with place 0 needs not to be remapped */ + if ((place > 0) && (place < MAX_AXIS_REMAP_TAB_SZ)) { + /*Need X Y axis revesal sensor place: P1, P3, P5, P7 */ + /* BMA2X2_FIFO_DAT_SEL_X: 1, Y:2, Z:3; + * but bst_axis_remap_tab_dft[i].src_x:0, y:1, z:2 + * so we need to +1*/ + if (BMA2X2_FIFO_DAT_SEL_X == data) + data = bst_axis_remap_tab_dft[place].src_x + 1; + else if (BMA2X2_FIFO_DAT_SEL_Y == data) + data = bst_axis_remap_tab_dft[place].src_y + 1; + } + } +#ifdef CONFIG_SENSORS_BMI058 + /*Update BMI058 fifo_data_sel to the BMA2x2 common definition*/ + if (BMA2X2_FIFO_DAT_SEL_X == data) + data = BMI058_FIFO_DAT_SEL_X; + else if (BMA2X2_FIFO_DAT_SEL_Y == data) + data = BMI058_FIFO_DAT_SEL_Y; + +#endif + if (bma2x2_set_fifo_data_sel(bma2x2->bma2x2_client, + (unsigned char) data) < 0) + return -EINVAL; + + return count; +} + + +/*! + * brief: bma2x2 single axis data remaping + * @param[i] fifo_datasel fifo axis data select setting + * @param[i/o] remap_dir remapping direction + * @param[i] client_data to transfer sensor place + * + * @return none + */ +static void bma2x2_single_axis_remaping(unsigned char fifo_datasel, + unsigned char *remap_dir, struct bma2x2_data *client_data) +{ + if ((NULL == client_data->bst_pd) || + (BOSCH_SENSOR_PLACE_UNKNOWN + == client_data->bst_pd->place)) + return; + else { + signed char place = client_data->bst_pd->place; + /* sensor with place 0 needs not to be remapped */ + if ((place <= 0) || (place >= MAX_AXIS_REMAP_TAB_SZ)) + return; + + if (fifo_datasel < 1 || fifo_datasel > 3) + return; + else { + switch (fifo_datasel) { + /*P2, P3, P4, P5 X axis(andorid) need to reverse*/ + case BMA2X2_FIFO_DAT_SEL_X: + if (-1 == bst_axis_remap_tab_dft[place].sign_x) + *remap_dir = 1; + else + *remap_dir = 0; + break; + /*P1, P2, P5, P6 Y axis(andorid) need to reverse*/ + case BMA2X2_FIFO_DAT_SEL_Y: + if (-1 == bst_axis_remap_tab_dft[place].sign_y) + *remap_dir = 1; + else + *remap_dir = 0; + break; + case BMA2X2_FIFO_DAT_SEL_Z: + /*P4, P5, P6, P7 Z axis(andorid) need to reverse*/ + if (-1 == bst_axis_remap_tab_dft[place].sign_z) + *remap_dir = 1; + else + *remap_dir = 0; + break; + default: + break; + } + } + } + + return; +} + +static ssize_t bma2x2_fifo_data_out_frame_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int err, i, len; + signed char fifo_data_out[MAX_FIFO_F_LEVEL * MAX_FIFO_F_BYTES] = {0}; + unsigned char f_len = 0; + s16 value; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + struct bma2x2acc acc_lsb; + unsigned char axis_dir_remap = 0; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2->fifo_datasel) { + /*Select one axis data output for every fifo frame*/ + f_len = 2; + } else { + /*Select X Y Z axis data output for every fifo frame*/ + f_len = 6; + } + + if (bma2x2->fifo_count == 0) + return -EINVAL; + + if (bma2x2->bma_mode_enabled == 0) { + PERR("bma2x2_fifo_data, bma_mode_enabled 0"); + /*return -EINVAL;*/ + } + + if (bma_i2c_burst_read(bma2x2->bma2x2_client, + BMA2X2_FIFO_DATA_OUTPUT_REG, fifo_data_out, + bma2x2->fifo_count * f_len) < 0) + return -EINVAL; + + err = 0; + +/* please give attation for the fifo output data format*/ + if (f_len == 6) { + /* Select X Y Z axis data output for every frame */ + for (i = 0; i < bma2x2->fifo_count; i++) { + acc_lsb.x = + ((unsigned char)fifo_data_out[i * f_len + 1] << 8 | + (unsigned char)fifo_data_out[i * f_len + 0]); + acc_lsb.y = + ((unsigned char)fifo_data_out[i * f_len + 3] << 8 | + (unsigned char)fifo_data_out[i * f_len + 2]); + acc_lsb.z = + ((unsigned char)fifo_data_out[i * f_len + 5] << 8 | + (unsigned char)fifo_data_out[i * f_len + 4]); +#ifndef BMA2X2_SENSOR_IDENTIFICATION_ENABLE + acc_lsb.x >>= + (16 - bma2x2_sensor_bitwidth[bma2x2->sensor_type]); + acc_lsb.y >>= + (16 - bma2x2_sensor_bitwidth[bma2x2->sensor_type]); + acc_lsb.z >>= + (16 - bma2x2_sensor_bitwidth[bma2x2->sensor_type]); +#endif + bma2x2_remap_sensor_data(&acc_lsb, bma2x2); + len = sprintf(buf, "%d %d %d ", + acc_lsb.x, acc_lsb.y, acc_lsb.z); + buf += len; + err += len; + } + } else { + /* single axis data output for every frame */ + bma2x2_single_axis_remaping(bma2x2->fifo_datasel, + &axis_dir_remap, bma2x2); + for (i = 0; i < bma2x2->fifo_count * f_len / 2; i++) { + value = ((unsigned char)fifo_data_out[2 * i + 1] << 8 | + (unsigned char)fifo_data_out[2 * i]); +#ifndef BMA2X2_SENSOR_IDENTIFICATION_ENABLE + value >>= + (16 - bma2x2_sensor_bitwidth[bma2x2->sensor_type]); +#endif + if (axis_dir_remap) + value = 0 - value; + len = sprintf(buf, "%d ", value); + buf += len; + err += len; + } + } + + return err; +} + +static ssize_t bma2x2_offset_x_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_offset_x(bma2x2->bma2x2_client, &data) < 0) + return sprintf(buf, "Read error\n"); + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_offset_x_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_offset_x(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_offset_y_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_offset_y(bma2x2->bma2x2_client, &data) < 0) + return sprintf(buf, "Read error\n"); + + return sprintf(buf, "%d\n", data); +} + +static ssize_t bma2x2_offset_y_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_offset_y(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +static ssize_t bma2x2_offset_z_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned char data = 0; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (bma2x2_get_offset_z(bma2x2->bma2x2_client, &data) < 0) + return sprintf(buf, "Read error\n"); + + return sprintf(buf, "%d\n", data); + +} + +static ssize_t bma2x2_offset_z_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if (bma2x2_set_offset_z(bma2x2->bma2x2_client, (unsigned + char)data) < 0) + return -EINVAL; + + return count; +} + +#ifdef CONFIG_SIG_MOTION +static int bma2x2_set_en_slope_int(struct bma2x2_data *bma2x2, + int en) +{ + int err; + struct i2c_client *client = bma2x2->bma2x2_client; + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (en) { + /* Set the related parameters which needs to be fine tuned by + * interfaces: slope_threshold and slope_duration + */ + /*dur: 192 samples ~= 3s*/ + err = bma2x2_set_slope_duration(client, 0x0); + err += bma2x2_set_slope_threshold(client, 0x16); + + /*Enable the interrupts*/ + err += bma2x2_set_Int_Enable(client, 5, 1);/*Slope X*/ + err += bma2x2_set_Int_Enable(client, 6, 1);/*Slope Y*/ + err += bma2x2_set_Int_Enable(client, 7, 1);/*Slope Z*/ + #ifdef BMA2X2_ENABLE_INT1 + /* TODO: SLOPE can now only be routed to INT1 pin*/ + err += bma2x2_set_int1_pad_sel(client, PAD_SLOP); + #else + /* err += bma2x2_set_int2_pad_sel(client, PAD_SLOP); */ + #endif + } else { + err = bma2x2_set_Int_Enable(client, 5, 0);/*Slope X*/ + err += bma2x2_set_Int_Enable(client, 6, 0);/*Slope Y*/ + err += bma2x2_set_Int_Enable(client, 7, 0);/*Slope Z*/ + } + return err; +} + +static ssize_t bma2x2_en_sig_motion_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + return sprintf(buf, "%d\n", atomic_read(&bma2x2->en_sig_motion)); +} + +static int bma2x2_set_en_sig_motion(struct bma2x2_data *bma2x2, + int en) +{ + int err = 0; + + en = (en >= 1) ? 1 : 0; /* set sig motion sensor status */ + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + if (atomic_read(&bma2x2->en_sig_motion) != en) { + if (en) { + err = bma2x2_set_mode(bma2x2->bma2x2_client, + BMA2X2_MODE_NORMAL, BMA_ENABLED_SGM); + err = bma2x2_set_en_slope_int(bma2x2, en); + enable_irq_wake(bma2x2->IRQ); + } else { + disable_irq_wake(bma2x2->IRQ); + err = bma2x2_set_en_slope_int(bma2x2, en); + err = bma2x2_set_mode(bma2x2->bma2x2_client, + BMA2X2_MODE_SUSPEND, BMA_ENABLED_SGM); + } + atomic_set(&bma2x2->en_sig_motion, en); + } + return err; +} + +static ssize_t bma2x2_en_sig_motion_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + if(!bma2x2){ + return BOSCH_SENSOR_BAD_ADDR; + } + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if ((data == 0) || (data == 1)) + bma2x2_set_en_sig_motion(bma2x2, data); + + return count; +} +#endif + +#ifdef CONFIG_DOUBLE_TAP +static int bma2x2_set_en_single_tap_int(struct bma2x2_data *bma2x2, int en) +{ + int err; + struct i2c_client *client = bma2x2->bma2x2_client; + + if (en) { + /* set tap interruption parameter here if needed. + bma2x2_set_tap_duration(client, 0xc0); + bma2x2_set_tap_threshold(client, 0x16); + */ + + /*Enable the single tap interrupts*/ + err = bma2x2_set_Int_Enable(client, 8, 1); + #ifdef BMA2X2_ENABLE_INT1 + err += bma2x2_set_int1_pad_sel(client, PAD_SINGLE_TAP); + #else + err += bma2x2_set_int2_pad_sel(client, PAD_SINGLE_TAP); + #endif + } else { + err = bma2x2_set_Int_Enable(client, 8, 0); + } + return err; +} + +static ssize_t bma2x2_tap_time_period_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", bma2x2->tap_time_period); +} + +static ssize_t bma2x2_tap_time_period_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + bma2x2->tap_time_period = data; + + return count; +} + +static ssize_t bma2x2_en_double_tap_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", atomic_read(&bma2x2->en_double_tap)); +} + +static int bma2x2_set_en_double_tap(struct bma2x2_data *bma2x2, + int en) +{ + int err = 0; + + en = (en >= 1) ? 1 : 0; + + if (atomic_read(&bma2x2->en_double_tap) != en) { + if (en) { + err = bma2x2_set_mode(bma2x2->bma2x2_client, + BMA2X2_MODE_NORMAL, BMA_ENABLED_DTAP); + err = bma2x2_set_en_single_tap_int(bma2x2, en); + } else { + err = bma2x2_set_en_single_tap_int(bma2x2, en); + err = bma2x2_set_mode(bma2x2->bma2x2_client, + BMA2X2_MODE_SUSPEND, BMA_ENABLED_DTAP); + } + atomic_set(&bma2x2->en_double_tap, en); + } + return err; +} + +static ssize_t bma2x2_en_double_tap_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned long data; + int error; + struct i2c_client *client = to_i2c_client(dev); + struct bma2x2_data *bma2x2 = i2c_get_clientdata(client); + + error = kstrtoul(buf, 10, &data); + if (error) + return error; + + if ((data == 0) || (data == 1)) + bma2x2_set_en_double_tap(bma2x2, data); + + return count; +} + +static void bma2x2_tap_timeout_handle(unsigned long data) +{ + struct bma2x2_data *bma2x2 = (struct bma2x2_data *)data; + + PINFO("tap interrupt handle, timeout\n"); + mutex_lock(&bma2x2->tap_mutex); + bma2x2->tap_times = 0; + mutex_unlock(&bma2x2->tap_mutex); + + /* if a single tap need to report, open the define */ +#ifdef REPORT_SINGLE_TAP_WHEN_DOUBLE_TAP_SENSOR_ENABLED + input_report_rel(bma2x2->dev_interrupt, + SINGLE_TAP_INTERRUPT, + SINGLE_TAP_INTERRUPT_HAPPENED); + input_sync(bma2x2->dev_interrupt); +#endif + +} +#endif + +static DEVICE_ATTR(range, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_range_show, bma2x2_range_store); +static DEVICE_ATTR(bandwidth, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_bandwidth_show, bma2x2_bandwidth_store); +static DEVICE_ATTR(op_mode, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_mode_show, bma2x2_mode_store); +static DEVICE_ATTR(value, S_IRUGO, + bma2x2_value_show, NULL); +static DEVICE_ATTR(value_cache, S_IRUGO, + bma2x2_value_cache_show, NULL); +static DEVICE_ATTR(delay, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_delay_show, bma2x2_delay_store); +static DEVICE_ATTR(enable, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_enable_show, bma2x2_enable_store); +static DEVICE_ATTR(SleepDur, S_IRUGO|S_IWUSR|S_IWGRP, + bma2x2_SleepDur_show, bma2x2_SleepDur_store); +static DEVICE_ATTR(fast_calibration_x, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fast_calibration_x_show, + bma2x2_fast_calibration_x_store); +static DEVICE_ATTR(fast_calibration_y, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fast_calibration_y_show, + bma2x2_fast_calibration_y_store); +static DEVICE_ATTR(fast_calibration_z, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fast_calibration_z_show, + bma2x2_fast_calibration_z_store); +static DEVICE_ATTR(enable_cali, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_enable_cali_show, + NULL); +static DEVICE_ATTR(fifo_mode, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fifo_mode_show, bma2x2_fifo_mode_store); +static DEVICE_ATTR(fifo_framecount, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fifo_framecount_show, bma2x2_fifo_framecount_store); +static DEVICE_ATTR(fifo_trig, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fifo_trig_show, bma2x2_fifo_trig_store); +static DEVICE_ATTR(fifo_trig_src, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fifo_trig_src_show, bma2x2_fifo_trig_src_store); +static DEVICE_ATTR(fifo_data_sel, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_fifo_data_sel_show, bma2x2_fifo_data_sel_store); +static DEVICE_ATTR(fifo_data_frame, S_IRUGO, + bma2x2_fifo_data_out_frame_show, NULL); +static DEVICE_ATTR(reg, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_register_show, bma2x2_register_store); +static DEVICE_ATTR(chip_id, S_IRUGO, + bma2x2_chip_id_show, NULL); +static DEVICE_ATTR(offset_x, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_offset_x_show, + bma2x2_offset_x_store); +static DEVICE_ATTR(offset_y, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_offset_y_show, + bma2x2_offset_y_store); +static DEVICE_ATTR(offset_z, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_offset_z_show, + bma2x2_offset_z_store); +static DEVICE_ATTR(enable_int, S_IWUSR|S_IWGRP|S_IWOTH, + NULL, bma2x2_enable_int_store); +static DEVICE_ATTR(int_mode, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_int_mode_show, bma2x2_int_mode_store); +static DEVICE_ATTR(slope_duration, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_slope_duration_show, bma2x2_slope_duration_store); +static DEVICE_ATTR(slope_threshold, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_slope_threshold_show, bma2x2_slope_threshold_store); +static DEVICE_ATTR(slope_no_mot_duration, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_slope_no_mot_duration_show, + bma2x2_slope_no_mot_duration_store); +static DEVICE_ATTR(slope_no_mot_threshold, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_slope_no_mot_threshold_show, + bma2x2_slope_no_mot_threshold_store); +static DEVICE_ATTR(high_g_duration, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_high_g_duration_show, bma2x2_high_g_duration_store); +static DEVICE_ATTR(high_g_threshold, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_high_g_threshold_show, bma2x2_high_g_threshold_store); +static DEVICE_ATTR(low_g_duration, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_low_g_duration_show, bma2x2_low_g_duration_store); +static DEVICE_ATTR(low_g_threshold, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_low_g_threshold_show, bma2x2_low_g_threshold_store); +static DEVICE_ATTR(tap_duration, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_tap_duration_show, bma2x2_tap_duration_store); +static DEVICE_ATTR(tap_threshold, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_tap_threshold_show, bma2x2_tap_threshold_store); +static DEVICE_ATTR(tap_quiet, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_tap_quiet_show, bma2x2_tap_quiet_store); +static DEVICE_ATTR(tap_shock, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_tap_shock_show, bma2x2_tap_shock_store); +static DEVICE_ATTR(tap_samp, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_tap_samp_show, bma2x2_tap_samp_store); +static DEVICE_ATTR(orient_mode, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_orient_mode_show, bma2x2_orient_mode_store); +static DEVICE_ATTR(orient_blocking, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_orient_blocking_show, bma2x2_orient_blocking_store); +static DEVICE_ATTR(orient_hyst, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_orient_hyst_show, bma2x2_orient_hyst_store); +static DEVICE_ATTR(orient_theta, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_orient_theta_show, bma2x2_orient_theta_store); +static DEVICE_ATTR(flat_theta, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_flat_theta_show, bma2x2_flat_theta_store); +static DEVICE_ATTR(flat_hold_time, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_flat_hold_time_show, bma2x2_flat_hold_time_store); +static DEVICE_ATTR(selftest, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_selftest_show, bma2x2_selftest_store); +static DEVICE_ATTR(softreset, S_IWUSR|S_IWGRP|S_IWOTH, + NULL, bma2x2_softreset_store); +static DEVICE_ATTR(temperature, S_IRUGO, + bma2x2_temperature_show, NULL); +static DEVICE_ATTR(place, S_IRUGO, + bma2x2_place_show, NULL); +#ifdef CONFIG_SIG_MOTION +static DEVICE_ATTR(en_sig_motion, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_en_sig_motion_show, bma2x2_en_sig_motion_store); +#endif +#ifdef CONFIG_DOUBLE_TAP +static DEVICE_ATTR(tap_time_period, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_tap_time_period_show, bma2x2_tap_time_period_store); +static DEVICE_ATTR(en_double_tap, S_IRUGO|S_IWUSR|S_IWGRP|S_IWOTH, + bma2x2_en_double_tap_show, bma2x2_en_double_tap_store); +#endif + +static struct attribute *bma2x2_attributes[] = { + &dev_attr_range.attr, + &dev_attr_bandwidth.attr, + &dev_attr_op_mode.attr, + &dev_attr_value.attr, + &dev_attr_value_cache.attr, + &dev_attr_delay.attr, + &dev_attr_enable.attr, + &dev_attr_SleepDur.attr, + &dev_attr_reg.attr, + &dev_attr_fast_calibration_x.attr, + &dev_attr_fast_calibration_y.attr, + &dev_attr_fast_calibration_z.attr, + &dev_attr_enable_cali.attr, + &dev_attr_fifo_mode.attr, + &dev_attr_fifo_framecount.attr, + &dev_attr_fifo_trig.attr, + &dev_attr_fifo_trig_src.attr, + &dev_attr_fifo_data_sel.attr, + &dev_attr_fifo_data_frame.attr, + &dev_attr_chip_id.attr, + &dev_attr_offset_x.attr, + &dev_attr_offset_y.attr, + &dev_attr_offset_z.attr, + &dev_attr_enable_int.attr, + &dev_attr_int_mode.attr, + &dev_attr_slope_duration.attr, + &dev_attr_slope_threshold.attr, + &dev_attr_slope_no_mot_duration.attr, + &dev_attr_slope_no_mot_threshold.attr, + &dev_attr_high_g_duration.attr, + &dev_attr_high_g_threshold.attr, + &dev_attr_low_g_duration.attr, + &dev_attr_low_g_threshold.attr, + &dev_attr_tap_threshold.attr, + &dev_attr_tap_duration.attr, + &dev_attr_tap_quiet.attr, + &dev_attr_tap_shock.attr, + &dev_attr_tap_samp.attr, + &dev_attr_orient_mode.attr, + &dev_attr_orient_blocking.attr, + &dev_attr_orient_hyst.attr, + &dev_attr_orient_theta.attr, + &dev_attr_flat_theta.attr, + &dev_attr_flat_hold_time.attr, + &dev_attr_selftest.attr, + &dev_attr_softreset.attr, + &dev_attr_temperature.attr, + &dev_attr_place.attr, +#ifdef CONFIG_SIG_MOTION + &dev_attr_en_sig_motion.attr, +#endif +#ifdef CONFIG_DOUBLE_TAP + &dev_attr_en_double_tap.attr, +#endif + + NULL +}; + +static struct attribute_group bma2x2_attribute_group = { + .attrs = bma2x2_attributes +}; + +#ifdef CONFIG_SIG_MOTION +static struct attribute *bma2x2_sig_motion_attributes[] = { + &dev_attr_slope_duration.attr, + &dev_attr_slope_threshold.attr, + &dev_attr_en_sig_motion.attr, + NULL +}; +static struct attribute_group bma2x2_sig_motion_attribute_group = { + .attrs = bma2x2_sig_motion_attributes +}; +#endif + +#ifdef CONFIG_DOUBLE_TAP +static struct attribute *bma2x2_double_tap_attributes[] = { + &dev_attr_tap_threshold.attr, + &dev_attr_tap_duration.attr, + &dev_attr_tap_quiet.attr, + &dev_attr_tap_shock.attr, + &dev_attr_tap_samp.attr, + &dev_attr_tap_time_period.attr, + &dev_attr_en_double_tap.attr, + NULL +}; +static struct attribute_group bma2x2_double_tap_attribute_group = { + .attrs = bma2x2_double_tap_attributes +}; +#endif + + +#if defined(BMA2X2_ENABLE_INT1) || defined(BMA2X2_ENABLE_INT2) +unsigned char *orient[] = {"upward looking portrait upright", + "upward looking portrait upside-down", + "upward looking landscape left", + "upward looking landscape right", + "downward looking portrait upright", + "downward looking portrait upside-down", + "downward looking landscape left", + "downward looking landscape right"}; + + +static void bma2x2_high_g_interrupt_handle(struct bma2x2_data *bma2x2) +{ + unsigned char first_value = 0; + unsigned char sign_value = 0; + int i; + + for (i = 0; i < 3; i++) { + bma2x2_get_HIGH_first(bma2x2->bma2x2_client, i, &first_value); + if (first_value == 1) { + bma2x2_get_HIGH_sign(bma2x2->bma2x2_client, + &sign_value); + if (sign_value == 1) { + if (i == 0) + input_report_rel(bma2x2->dev_interrupt, + HIGH_G_INTERRUPT, + HIGH_G_INTERRUPT_X_N); + if (i == 1) + input_report_rel(bma2x2->dev_interrupt, + HIGH_G_INTERRUPT, + HIGH_G_INTERRUPT_Y_N); + if (i == 2) + input_report_rel(bma2x2->dev_interrupt, + HIGH_G_INTERRUPT, + HIGH_G_INTERRUPT_Z_N); + } else { + if (i == 0) + input_report_rel(bma2x2->dev_interrupt, + HIGH_G_INTERRUPT, + HIGH_G_INTERRUPT_X); + if (i == 1) + input_report_rel(bma2x2->dev_interrupt, + HIGH_G_INTERRUPT, + HIGH_G_INTERRUPT_Y); + if (i == 2) + input_report_rel(bma2x2->dev_interrupt, + HIGH_G_INTERRUPT, + HIGH_G_INTERRUPT_Z); + } + } + + PINFO("High G interrupt happened,exis is %d," + "first is %d,sign is %d\n", i, + first_value, sign_value); + } + + +} + +#ifndef CONFIG_SIG_MOTION +static void bma2x2_slope_interrupt_handle(struct bma2x2_data *bma2x2) +{ + unsigned char first_value = 0; + unsigned char sign_value = 0; + int i; + for (i = 0; i < 3; i++) { + bma2x2_get_slope_first(bma2x2->bma2x2_client, i, &first_value); + if (first_value == 1) { + bma2x2_get_slope_sign(bma2x2->bma2x2_client, + &sign_value); + if (sign_value == 1) { + if (i == 0) + input_report_rel(bma2x2->dev_interrupt, + SLOP_INTERRUPT, + SLOPE_INTERRUPT_X_N); + if (i == 1) + input_report_rel(bma2x2->dev_interrupt, + SLOP_INTERRUPT, + SLOPE_INTERRUPT_Y_N); + if (i == 2) + input_report_rel(bma2x2->dev_interrupt, + SLOP_INTERRUPT, + SLOPE_INTERRUPT_Z_N); + } else { + if (i == 0) + input_report_rel(bma2x2->dev_interrupt, + SLOP_INTERRUPT, + SLOPE_INTERRUPT_X); + if (i == 1) + input_report_rel(bma2x2->dev_interrupt, + SLOP_INTERRUPT, + SLOPE_INTERRUPT_Y); + if (i == 2) + input_report_rel(bma2x2->dev_interrupt, + SLOP_INTERRUPT, + SLOPE_INTERRUPT_Z); + + } + } + + PINFO("Slop interrupt happened,exis is %d," + "first is %d,sign is %d\n", i, + first_value, sign_value); + } +} +#endif + +static void bma2x2_irq_work_func(struct work_struct *work) +{ + struct bma2x2_data *bma2x2 = container_of((struct work_struct *)work, + struct bma2x2_data, irq_work); +#ifdef CONFIG_DOUBLE_TAP + struct i2c_client *client = bma2x2->bma2x2_client; +#endif + + unsigned char status = 0; + unsigned char first_value = 0; + unsigned char sign_value = 0; + +#ifdef CONFIG_BMA_ENABLE_NEWDATA_INT + static struct bma2x2acc acc; + + bma2x2_get_interruptstatus2(bma2x2->bma2x2_client, &status); + + if ((status&0x80) == 0x80) { + /* PINFO("New data interrupt happened\n");*/ + bma2x2_read_accel_xyz(bma2x2->bma2x2_client, + bma2x2->sensor_type, &acc); + input_report_abs(bma2x2->input, ABS_X, acc.x); + input_report_abs(bma2x2->input, ABS_Y, acc.y); + input_report_abs(bma2x2->input, ABS_Z, acc.z); + input_sync(bma2x2->input); + mutex_lock(&bma2x2->value_mutex); + bma2x2->value = acc; + mutex_unlock(&bma2x2->value_mutex); + return; + } +#endif + + bma2x2_get_interruptstatus1(bma2x2->bma2x2_client, &status); + PINFO("bma2x2_irq_work_func, status = 0x%x\n", status); + +#ifdef CONFIG_SIG_MOTION + if (status & 0x04) { + if (atomic_read(&bma2x2->en_sig_motion) == 1) { + PINFO("Significant motion interrupt happened\n"); + /* close sig sensor, + it will be open again if APP wants */ + bma2x2_set_en_sig_motion(bma2x2, 0); + + input_report_rel(bma2x2->dev_interrupt, + SLOP_INTERRUPT, 1); + input_sync(bma2x2->dev_interrupt); + } + } +#endif + +#ifdef CONFIG_DOUBLE_TAP + if (status & 0x20) { + if (atomic_read(&bma2x2->en_double_tap) == 1) { + PINFO("single tap interrupt happened\n"); + bma2x2_set_Int_Enable(client, 8, 0); + if (bma2x2->tap_times == 0) { + mod_timer(&bma2x2->tap_timer, jiffies + + msecs_to_jiffies(bma2x2->tap_time_period)); + bma2x2->tap_times = 1; + } else { + /* only double tap is judged */ + PINFO("double tap\n"); + mutex_lock(&bma2x2->tap_mutex); + bma2x2->tap_times = 0; + del_timer(&bma2x2->tap_timer); + mutex_unlock(&bma2x2->tap_mutex); + input_report_rel(bma2x2->dev_interrupt, + DOUBLE_TAP_INTERRUPT, + DOUBLE_TAP_INTERRUPT_HAPPENED); + input_sync(bma2x2->dev_interrupt); + } + bma2x2_set_Int_Enable(client, 8, 1); + } + } +#endif + + switch (status) { + + case 0x01: + PINFO("Low G interrupt happened\n"); + input_report_rel(bma2x2->dev_interrupt, LOW_G_INTERRUPT, + LOW_G_INTERRUPT_HAPPENED); + break; + + case 0x02: + bma2x2_high_g_interrupt_handle(bma2x2); + break; + +#ifndef CONFIG_SIG_MOTION + case 0x04: + bma2x2_slope_interrupt_handle(bma2x2); + break; +#endif + + case 0x08: + PINFO("slow/ no motion interrupt happened\n"); + input_report_rel(bma2x2->dev_interrupt, + SLOW_NO_MOTION_INTERRUPT, + SLOW_NO_MOTION_INTERRUPT_HAPPENED); + break; + +#ifndef CONFIG_DOUBLE_TAP + case 0x10: + PINFO("double tap interrupt happened\n"); + input_report_rel(bma2x2->dev_interrupt, + DOUBLE_TAP_INTERRUPT, + DOUBLE_TAP_INTERRUPT_HAPPENED); + break; + case 0x20: + PINFO("single tap interrupt happened\n"); + input_report_rel(bma2x2->dev_interrupt, + SINGLE_TAP_INTERRUPT, + SINGLE_TAP_INTERRUPT_HAPPENED); + break; +#endif + + case 0x40: + bma2x2_get_orient_status(bma2x2->bma2x2_client, + &first_value); + PINFO("orient interrupt happened,%s\n", + orient[first_value]); + if (first_value == 0) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + UPWARD_PORTRAIT_UP_INTERRUPT_HAPPENED); + else if (first_value == 1) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + UPWARD_PORTRAIT_DOWN_INTERRUPT_HAPPENED); + else if (first_value == 2) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + UPWARD_LANDSCAPE_LEFT_INTERRUPT_HAPPENED); + else if (first_value == 3) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + UPWARD_LANDSCAPE_RIGHT_INTERRUPT_HAPPENED); + else if (first_value == 4) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + DOWNWARD_PORTRAIT_UP_INTERRUPT_HAPPENED); + else if (first_value == 5) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + DOWNWARD_PORTRAIT_DOWN_INTERRUPT_HAPPENED); + else if (first_value == 6) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + DOWNWARD_LANDSCAPE_LEFT_INTERRUPT_HAPPENED); + else if (first_value == 7) + input_report_abs(bma2x2->dev_interrupt, + ORIENT_INTERRUPT, + DOWNWARD_LANDSCAPE_RIGHT_INTERRUPT_HAPPENED); + break; + case 0x80: + bma2x2_get_orient_flat_status(bma2x2->bma2x2_client, + &sign_value); + PINFO("flat interrupt happened,flat status is %d\n", + sign_value); + if (sign_value == 1) { + input_report_abs(bma2x2->dev_interrupt, + FLAT_INTERRUPT, + FLAT_INTERRUPT_TURE_HAPPENED); + } else { + input_report_abs(bma2x2->dev_interrupt, + FLAT_INTERRUPT, + FLAT_INTERRUPT_FALSE_HAPPENED); + } + break; + + default: + break; + } +} + +static irqreturn_t bma2x2_irq_handler(int irq, void *handle) +{ + struct bma2x2_data *data = handle; + + if (data == NULL) + return IRQ_HANDLED; + if (data->bma2x2_client == NULL) + return IRQ_HANDLED; + + schedule_work(&data->irq_work); + + return IRQ_HANDLED; +} +#endif /* defined(BMA2X2_ENABLE_INT1)||defined(BMA2X2_ENABLE_INT2) */ + +static enum hrtimer_restart timer_handler(struct hrtimer *handle) +{ + struct bma2x2_data *client_data; + + client_data = container_of(handle, struct bma2x2_data, timer); + queue_work(client_data->wq, &client_data->work); + + hrtimer_forward_now(&client_data->timer, ktime_set(0, atomic_read(&client_data->delay) * 1000000)); + + return HRTIMER_RESTART; +} + +static int bma2x2_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int err = 0; + struct bma2x2_data *data; + struct input_dev *dev; + struct bst_dev *dev_acc; +#if defined(BMA2X2_ENABLE_INT1) || defined(BMA2X2_ENABLE_INT2) + struct bosch_sensor_specific *pdata; +#endif + struct input_dev *dev_interrupt; + + PINFO("bma2x2_probe start\n"); + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + PERR("i2c_check_functionality error\n"); + err = -EIO; + goto exit; + } + data = kzalloc(sizeof(struct bma2x2_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + /* do soft reset */ + mdelay(5); + if (bma2x2_soft_reset(client) < 0) { + PERR("i2c bus write error, pls check HW connection\n"); + err = -EINVAL; + goto kfree_exit; + } + mdelay(5); + /* read and check chip id */ + if (bma2x2_check_chip_id(client, data) < 0) { + err = -EINVAL; + goto kfree_exit; + } + + i2c_set_clientdata(client, data); + data->bma2x2_client = client; + mutex_init(&data->value_mutex); + mutex_init(&data->mode_mutex); + mutex_init(&data->enable_mutex); + bma2x2_set_bandwidth(client, BMA2X2_BW_SET); + bma2x2_set_range(client, BMA2X2_RANGE_SET); + + +#if defined(BMA2X2_ENABLE_INT1) || defined(BMA2X2_ENABLE_INT2) + + pdata = client->dev.platform_data; + if (pdata) { + if (pdata->irq_gpio_cfg && (pdata->irq_gpio_cfg() < 0)) { + PERR("IRQ GPIO conf. error %d\n", + client->irq); + } + } + +#ifdef BMA2X2_ENABLE_INT1 + /* maps interrupt to INT1 pin */ + bma2x2_set_int1_pad_sel(client, PAD_LOWG); + bma2x2_set_int1_pad_sel(client, PAD_HIGHG); + bma2x2_set_int1_pad_sel(client, PAD_SLOP); + bma2x2_set_int1_pad_sel(client, PAD_DOUBLE_TAP); + bma2x2_set_int1_pad_sel(client, PAD_SINGLE_TAP); + bma2x2_set_int1_pad_sel(client, PAD_ORIENT); + bma2x2_set_int1_pad_sel(client, PAD_FLAT); + bma2x2_set_int1_pad_sel(client, PAD_SLOW_NO_MOTION); +#ifdef CONFIG_BMA_ENABLE_NEWDATA_INT + bma2x2_set_newdata(client, BMA2X2_INT1_NDATA, 1); + bma2x2_set_newdata(client, BMA2X2_INT2_NDATA, 0); +#endif +#endif + +#ifdef BMA2X2_ENABLE_INT2 + /* maps interrupt to INT2 pin */ + bma2x2_set_int2_pad_sel(client, PAD_LOWG); + bma2x2_set_int2_pad_sel(client, PAD_HIGHG); + bma2x2_set_int2_pad_sel(client, PAD_SLOP); + bma2x2_set_int2_pad_sel(client, PAD_DOUBLE_TAP); + bma2x2_set_int2_pad_sel(client, PAD_SINGLE_TAP); + bma2x2_set_int2_pad_sel(client, PAD_ORIENT); + bma2x2_set_int2_pad_sel(client, PAD_FLAT); + bma2x2_set_int2_pad_sel(client, PAD_SLOW_NO_MOTION); +#ifdef CONFIG_BMA_ENABLE_NEWDATA_INT + bma2x2_set_newdata(client, BMA2X2_INT1_NDATA, 0); + bma2x2_set_newdata(client, BMA2X2_INT2_NDATA, 1); +#endif +#endif + + bma2x2_set_Int_Mode(client, 1);/*latch interrupt 250ms*/ + + /* do not open any interrupt here */ + /*10,orient + 11,flat*/ + /* bma2x2_set_Int_Enable(client, 10, 1); */ + /* bma2x2_set_Int_Enable(client, 11, 1); */ + +#ifdef CONFIG_BMA_ENABLE_NEWDATA_INT + /* enable new data interrupt */ + bma2x2_set_Int_Enable(client, 4, 1); +#endif + + + data->IRQ = client->irq; + err = request_irq(data->IRQ, bma2x2_irq_handler, IRQF_TRIGGER_RISING, + "bma2x2", data); +#ifdef CONFIG_SIG_MOTION + enable_irq_wake(data->IRQ); +#endif + if (err) + PERR("could not request irq\n"); + + INIT_WORK(&data->irq_work, bma2x2_irq_work_func); +#endif + +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT + //INIT_DELAYED_WORK(&data->work, bma2x2_work_func); + INIT_WORK(&data->work, bma2x2_work_func); + data->wq = create_workqueue("bma_wq"); + hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + data->timer.function = timer_handler; +#endif + atomic_set(&data->delay, BMA2X2_MAX_DELAY); + atomic_set(&data->enable, 0); + + dev = input_allocate_device(); + if (!dev) + return -ENOMEM; + + dev_interrupt = input_allocate_device(); + if (!dev_interrupt) { + kfree(data); + input_free_device(dev); /*free the successful dev and return*/ + return -ENOMEM; + } + + /* only value events reported */ + dev->name = SENSOR_NAME; + dev->id.bustype = BUS_I2C; +#if 0 + input_set_capability(dev, EV_ABS, ABS_MISC); + input_set_abs_params(dev, ABS_X, ABSMIN, ABSMAX, 0, 0); + input_set_abs_params(dev, ABS_Y, ABSMIN, ABSMAX, 0, 0); + input_set_abs_params(dev, ABS_Z, ABSMIN, ABSMAX, 0, 0); +#else + input_set_capability(dev, EV_MSC, MSC_RAW); + input_set_capability(dev, EV_MSC, MSC_SCAN); + input_set_capability(dev, EV_MSC, MSC_GESTURE); +#endif + + input_set_drvdata(dev, data); + err = input_register_device(dev); + if (err < 0) + goto err_register_input_device; + + /* all interrupt generated events are moved to interrupt input devices*/ + dev_interrupt->name = "bma_interrupt"; + dev_interrupt->id.bustype = BUS_I2C; + input_set_capability(dev_interrupt, EV_REL, + SLOW_NO_MOTION_INTERRUPT); + input_set_capability(dev_interrupt, EV_REL, + LOW_G_INTERRUPT); + input_set_capability(dev_interrupt, EV_REL, + HIGH_G_INTERRUPT); + input_set_capability(dev_interrupt, EV_REL, + SLOP_INTERRUPT); + input_set_capability(dev_interrupt, EV_REL, + DOUBLE_TAP_INTERRUPT); + input_set_capability(dev_interrupt, EV_REL, + SINGLE_TAP_INTERRUPT); + input_set_capability(dev_interrupt, EV_ABS, + ORIENT_INTERRUPT); + input_set_capability(dev_interrupt, EV_ABS, + FLAT_INTERRUPT); + input_set_drvdata(dev_interrupt, data); + + err = input_register_device(dev_interrupt); + if (err < 0) + goto err_register_input_device_interrupt; + + data->dev_interrupt = dev_interrupt; + data->input = dev; + +#ifdef CONFIG_SIG_MOTION + data->g_sensor_class = class_create(THIS_MODULE, "sig_sensor"); + if (IS_ERR(data->g_sensor_class)) { + err = PTR_ERR(data->g_sensor_class); + data->g_sensor_class = NULL; + PERR("could not allocate g_sensor_class\n"); + goto err_create_class; + } + + data->g_sensor_dev = device_create(data->g_sensor_class, + NULL, 0, "%s", "g_sensor"); + if (unlikely(IS_ERR(data->g_sensor_dev))) { + err = PTR_ERR(data->g_sensor_dev); + data->g_sensor_dev = NULL; + + PERR("could not allocate g_sensor_dev\n"); + goto err_create_g_sensor_device; + } + + dev_set_drvdata(data->g_sensor_dev, data); + + err = sysfs_create_group(&data->g_sensor_dev->kobj, + &bma2x2_sig_motion_attribute_group); + if (err < 0) + goto error_sysfs; +#endif + + +#ifdef CONFIG_DOUBLE_TAP + data->g_sensor_class_doubletap = + class_create(THIS_MODULE, "dtap_sensor"); + if (IS_ERR(data->g_sensor_class_doubletap)) { + err = PTR_ERR(data->g_sensor_class_doubletap); + data->g_sensor_class_doubletap = NULL; + PERR("could not allocate g_sensor_class_doubletap\n"); + goto err_create_class; + } + + + data->g_sensor_dev_doubletap = device_create( + data->g_sensor_class_doubletap, + NULL, 0, "%s", "g_sensor"); + if (unlikely(IS_ERR(data->g_sensor_dev_doubletap))) { + err = PTR_ERR(data->g_sensor_dev_doubletap); + data->g_sensor_dev_doubletap = NULL; + + PERR("could not allocate g_sensor_dev_doubletap\n"); + goto err_create_g_sensor_device_double_tap; + } + + + dev_set_drvdata(data->g_sensor_dev_doubletap, data); + + err = sysfs_create_group(&data->g_sensor_dev_doubletap->kobj, + &bma2x2_double_tap_attribute_group); + if (err < 0) + goto error_sysfs; +#endif + + + err = sysfs_create_group(&data->input->dev.kobj, + &bma2x2_attribute_group); + if (err < 0) + goto error_sysfs; + + dev_acc = bst_allocate_device(); + if (!dev_acc) { + err = -ENOMEM; + goto error_sysfs; + } + dev_acc->name = ACC_NAME; + + bst_set_drvdata(dev_acc, data); + + err = bst_register_device(dev_acc); + if (err < 0) + goto bst_free_acc_exit; + + data->bst_acc = dev_acc; + err = sysfs_create_group(&data->bst_acc->dev.kobj, + &bma2x2_attribute_group); + + if (err < 0) + goto bst_free_exit; + +#if 0 + if (NULL != client->dev.platform_data) { + data->bst_pd = kzalloc(sizeof(*data->bst_pd), + GFP_KERNEL); + + if (NULL != data->bst_pd) { + memcpy(data->bst_pd, client->dev.platform_data, + sizeof(*data->bst_pd)); + PINFO("%s sensor driver set place: p%d", + data->bst_pd->name, data->bst_pd->place); + } + } +#endif + + data->bst_pd = kzalloc(sizeof(*data->bst_pd), + GFP_KERNEL); + + if (NULL != data->bst_pd) { + data->bst_pd->place=BOSCH_SENSOR_PLACE; + } + + +#ifdef CONFIG_HAS_EARLYSUSPEND + data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + data->early_suspend.suspend = bma2x2_early_suspend; + data->early_suspend.resume = bma2x2_late_resume; + register_early_suspend(&data->early_suspend); +#endif + + data->bma_mode_enabled = 0; + data->fifo_datasel = 0; + data->fifo_count = 0; + + +#ifdef CONFIG_SIG_MOTION + atomic_set(&data->en_sig_motion, 0); +#endif +#ifdef CONFIG_DOUBLE_TAP + atomic_set(&data->en_double_tap, 0); + data->tap_times = 0; + data->tap_time_period = DEFAULT_TAP_JUDGE_PERIOD; + mutex_init(&data->tap_mutex); + setup_timer(&data->tap_timer, bma2x2_tap_timeout_handle, + (unsigned long)data); +#endif + + if (bma2x2_set_mode(client, BMA2X2_MODE_SUSPEND, BMA_ENABLED_ALL) < 0) + return -EINVAL; + PINFO("BMA2x2 driver probe successfully"); + + return 0; + +bst_free_exit: + bst_unregister_device(dev_acc); + +bst_free_acc_exit: + bst_free_device(dev_acc); + +error_sysfs: + input_unregister_device(data->input); + +#ifdef CONFIG_DOUBLE_TAP +err_create_g_sensor_device_double_tap: + class_destroy(data->g_sensor_class_doubletap); +#endif + +#ifdef CONFIG_SIG_MOTION +err_create_g_sensor_device: + class_destroy(data->g_sensor_class); +#endif + +#if defined(CONFIG_SIG_MOTION) || defined(CONFIG_DOUBLE_TAP) +err_create_class: + input_unregister_device(data->dev_interrupt); +#endif + +err_register_input_device_interrupt: + input_free_device(dev_interrupt); + input_unregister_device(data->input); + +err_register_input_device: + input_free_device(dev); + +kfree_exit: + if ((NULL != data) && (NULL != data->bst_pd)) { + kfree(data->bst_pd); + data->bst_pd = NULL; + } + kfree(data); +exit: + return err; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void bma2x2_early_suspend(struct early_suspend *h) +{ + struct bma2x2_data *data = + container_of(h, struct bma2x2_data, early_suspend); + + mutex_lock(&data->enable_mutex); + if (atomic_read(&data->enable) == 1) { + bma2x2_set_mode(data->bma2x2_client, + BMA2X2_MODE_SUSPEND, BMA_ENABLED_INPUT); +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT +// cancel_delayed_work_sync(&data->work); + cancel_work_sync(&data->work); +#endif + } + mutex_unlock(&data->enable_mutex); +} + +static void bma2x2_late_resume(struct early_suspend *h) +{ + struct bma2x2_data *data = + container_of(h, struct bma2x2_data, early_suspend); + if (NULL == data) + return; + + mutex_lock(&data->enable_mutex); + if (atomic_read(&data->enable) == 1) { + bma2x2_set_mode(data->bma2x2_client, + BMA2X2_MODE_NORMAL, BMA_ENABLED_INPUT); +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT +// schedule_delayed_work(&data->work, +// msecs_to_jiffies(atomic_read(&data->delay))); + schedule_work(&data->work); +#endif + } + mutex_unlock(&data->enable_mutex); +} +#endif + +static int bma2x2_remove(struct i2c_client *client) +{ + struct bma2x2_data *data = i2c_get_clientdata(client); + + if (NULL == data) + return 0; + + bma2x2_set_enable(&client->dev, 0); +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(&data->early_suspend); +#endif + sysfs_remove_group(&data->input->dev.kobj, &bma2x2_attribute_group); + input_unregister_device(data->input); + + if (NULL != data->bst_pd) { + kfree(data->bst_pd); + data->bst_pd = NULL; + } + + kfree(data); + return 0; +} + +void bma2x2_shutdown(struct i2c_client *client) +{ + struct bma2x2_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->enable_mutex); + bma2x2_set_mode(data->bma2x2_client, + BMA2X2_MODE_DEEP_SUSPEND, BMA_ENABLED_ALL); + mutex_unlock(&data->enable_mutex); +} + +#ifdef CONFIG_PM +static int bma2x2_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct bma2x2_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->enable_mutex); + if (atomic_read(&data->enable) == 1) { + bma2x2_set_mode(data->bma2x2_client, + BMA2X2_MODE_SUSPEND, BMA_ENABLED_INPUT); +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT +// cancel_delayed_work_sync(&data->work); + cancel_work_sync(&data->work); + +#endif + } + mutex_unlock(&data->enable_mutex); + + return 0; +} + +static int bma2x2_resume(struct i2c_client *client) +{ + struct bma2x2_data *data = i2c_get_clientdata(client); + + mutex_lock(&data->enable_mutex); + if (atomic_read(&data->enable) == 1) { + bma2x2_set_mode(data->bma2x2_client, + BMA2X2_MODE_NORMAL, BMA_ENABLED_INPUT); +#ifndef CONFIG_BMA_ENABLE_NEWDATA_INT +// schedule_delayed_work(&data->work, +// msecs_to_jiffies(atomic_read(&data->delay))); + schedule_work(&data->work); +#endif + } + mutex_unlock(&data->enable_mutex); + + return 0; +} + +#else + +#define bma2x2_suspend NULL +#define bma2x2_resume NULL + +#endif /* CONFIG_PM */ + +static const struct i2c_device_id bma2x2_id[] = { + { SENSOR_NAME, 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, bma2x2_id); + + +static struct of_device_id bst_match_table[] = { + { .compatible = "bst,bma2x2", }, + { }, +}; + +static struct i2c_driver bma2x2_driver = { + .driver = { + .owner = THIS_MODULE, + .name = SENSOR_NAME, +#ifdef CONFIG_OF + .of_match_table = bst_match_table, +#endif + }, + .suspend = bma2x2_suspend, + .resume = bma2x2_resume, + .id_table = bma2x2_id, + .probe = bma2x2_probe, + .remove = bma2x2_remove, + .shutdown = bma2x2_shutdown, +}; + +static struct bosch_sensor_specific __initdata bma2x2_pdata = { + .name = "bma2x2", + .place = 0, +}; + +static struct i2c_board_info __initdata sprd_i2c_bst = { + I2C_BOARD_INFO("bma2x2", 0x18), + .platform_data = &bma2x2_pdata, +}; + +static int __init BMA2X2_init(void) +{ + return i2c_add_driver(&bma2x2_driver); +} + +static void __exit BMA2X2_exit(void) +{ + i2c_del_driver(&bma2x2_driver); +} + +MODULE_AUTHOR("contact@bosch-sensortec.com"); +MODULE_DESCRIPTION("BMA2X2 ACCELEROMETER SENSOR DRIVER"); +MODULE_LICENSE("GPL v2"); + +//module_init(BMA2X2_init); +late_initcall(BMA2X2_init); +module_exit(BMA2X2_exit); + diff --git a/drivers/input/misc/bs_log.h b/drivers/input/misc/bs_log.h new file mode 100644 index 00000000..0d3af001 --- /dev/null +++ b/drivers/input/misc/bs_log.h @@ -0,0 +1,167 @@ +/*! + * @section LICENSE + * $license$ + * + * @filename $filename$ + * @date $date$ + * @id $id$ + * + * @brief + * The head file of BOSCH SENSOR LOG +*/ + +#ifndef __BS_LOG_H +#define __BS_LOG_H + +#include <linux/kernel.h> + +/*! @ trace functions + @{*/ +/*! ERROR LOG LEVEL */ +#define LOG_LEVEL_E 3 +/*! NOTICE LOG LEVEL */ +#define LOG_LEVEL_N 5 +/*! INFORMATION LOG LEVEL */ +#define LOG_LEVEL_I 6 +/*! DEBUG LOG LEVEL */ +#define LOG_LEVEL_D 7 +/*! DEBUG_FWDL LOG LEVEL */ +#define LOG_LEVEL_DF 10 +/*! DEBUG_DATA LOG LEVEL */ +#define LOG_LEVEL_DA 15 +/*! ALL LOG LEVEL */ +#define LOG_LEVEL_A 20 + +#ifndef MODULE_TAG +/*! MODULE TAG DEFINATION */ +#define MODULE_TAG "<BS_LOG>" +#endif + +#ifndef LOG_LEVEL +/*! LOG LEVEL DEFINATION */ +#define LOG_LEVEL LOG_LEVEL_I +#endif + +#ifdef BOSCH_DRIVER_LOG_FUNC + #ifdef BSLOG_VAR_DEF + uint8_t debug_log_level = LOG_LEVEL; + #else + extern uint8_t debug_log_level; + #endif + + /*! print error message */ + #define PERR(fmt, args...) do\ + {\ + if (debug_log_level >= LOG_LEVEL_E)\ + printk(KERN_INFO "\n" "[E]" KERN_ERR MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args);\ + } while (0) + + /*! print notice message */ + #define PNOTICE(fmt, args...) do\ + {\ + if (debug_log_level >= LOG_LEVEL_N)\ + printk(KERN_INFO "\n" "[N]" KERN_NOTICE MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args);\ + } while (0) + + /*! print information message */ + #define PINFO(fmt, args...) do\ + {\ + if (debug_log_level >= LOG_LEVEL_I)\ + printk(KERN_INFO "\n" "[I]" KERN_INFO MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args);\ + } while (0) + + /*! print debug message */ + #define PDEBUG(fmt, args...) do\ + {\ + if (debug_log_level >= LOG_LEVEL_D)\ + printk(KERN_INFO "\n" "[D]" KERN_DEBUG MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args);\ + } while (0) + + /*! print debug fw download message */ + #define PDEBUG_FWDL(fmt, args...) do\ + {\ + if (debug_log_level >= LOG_LEVEL_DF)\ + printk(KERN_INFO "\n" "[DF]" KERN_DEBUG MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args);\ + } while (0) + + /*! print debug data log message */ + #define PDEBUG_DLOG(fmt, args...) do\ + {\ + if (debug_log_level >= LOG_LEVEL_DA)\ + printk(KERN_INFO "\n" "[DA]" KERN_DEBUG MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args);\ + } while (0) + + void set_debug_log_level(uint8_t level); + uint8_t get_debug_log_level(void); + +#else + + #if (LOG_LEVEL >= LOG_LEVEL_E) + /*! print error message */ + #define PERR(fmt, args...) \ + printk(KERN_INFO "\n" "[E]" KERN_ERR MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args) + #else + /*! invalid message */ + #define PERR(fmt, args...) + #endif + + #if (LOG_LEVEL >= LOG_LEVEL_N) + /*! print notice message */ + #define PNOTICE(fmt, args...) \ + printk(KERN_INFO "\n" "[N]" KERN_NOTICE MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args) + #else + /*! invalid message */ + #define PNOTICE(fmt, args...) + #endif + + #if (LOG_LEVEL >= LOG_LEVEL_I) + /*! print information message */ + #define PINFO(fmt, args...) printk(KERN_INFO "\n" "[I]" KERN_INFO MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args) + #else + /*! invalid message */ + #define PINFO(fmt, args...) + #endif + + #if (LOG_LEVEL >= LOG_LEVEL_D) + /*! print debug message */ + #define PDEBUG(fmt, args...) printk(KERN_INFO "\n" "[D]" KERN_DEBUG MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args) + #else + /*! invalid message */ + #define PDEBUG(fmt, args...) + #endif + + #if (LOG_LEVEL >= LOG_LEVEL_DF) + /*! print debug fw download message */ + #define PDEBUG_FWDL(fmt, args...) printk(KERN_INFO "\n" "[DF]" KERN_DEBUG MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args) + #else + /*! invalid message */ + #define PDEBUG_FWDL(fmt, args...) + #endif + + #if (LOG_LEVEL >= LOG_LEVEL_DA) + /*! print debug data log message */ + #define PDEBUG_DLOG(fmt, args...) printk(KERN_INFO "\n" "[DA]" KERN_DEBUG MODULE_TAG \ + "<%s><%d>" fmt "\n", __func__, __LINE__, ##args) + #else + /*! invalid message */ + #define PDEBUG_DLOG(fmt, args...) + #endif + + #define set_debug_log_level(level) {} + #define get_debug_log_level() (LOG_LEVEL) + +#endif + +#endif/*__BS_LOG_H*/ +/*@}*/ diff --git a/drivers/input/misc/bstclass.c b/drivers/input/misc/bstclass.c new file mode 100644 index 00000000..11c3c39d --- /dev/null +++ b/drivers/input/misc/bstclass.c @@ -0,0 +1,234 @@ +/*! + * @section LICENSE + * $license$ + * + * @filename $filename$ + * @date $date$ + * @id $id$ + * + * @brief + * The core code of bst device driver +*/ +#include <linux/init.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/random.h> +#include <linux/sched.h> +#include <linux/seq_file.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/rcupdate.h> +#include <linux/compiler.h> +#include <linux/compat.h> +#include "bstclass.h" +#include "bs_log.h" + +static LIST_HEAD(bst_dev_list); + +/* + * bst_mutex protects access to both bst_dev_list and input_handler_list. + * This also causes bst_[un]register_device and bst_[un]register_handler + * be mutually exclusive which simplifies locking in drivers implementing + * input handlers. + */ +static DEFINE_MUTEX(bst_mutex); + + +static void bst_dev_release(struct device *device) +{ + struct bst_dev *dev = to_bst_dev(device); + if (NULL != dev) + kfree(dev); + module_put(THIS_MODULE); +} + + +#ifdef CONFIG_PM +static int bst_dev_suspend(struct device *dev) +{ + return 0; +} + +static int bst_dev_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops bst_dev_pm_ops = { + .suspend = bst_dev_suspend, + .resume = bst_dev_resume, + .poweroff = bst_dev_suspend, + .restore = bst_dev_resume, +}; +#endif /* CONFIG_PM */ + +static const struct attribute_group *bst_dev_attr_groups[] = { + NULL +}; + +static struct device_type bst_dev_type = { + .groups = bst_dev_attr_groups, + .release = bst_dev_release, +#ifdef CONFIG_PM + .pm = &bst_dev_pm_ops, +#endif +}; + + + +static char *bst_devnode(struct device *dev, mode_t *mode) +{ + return kasprintf(GFP_KERNEL, "%s", dev_name(dev)); +} + +struct class bst_class = { + .name = "bst", + .owner = THIS_MODULE, + .devnode = bst_devnode, + .dev_release = bst_dev_release, +}; +EXPORT_SYMBOL_GPL(bst_class); + +/** + * bst_allocate_device - allocate memory for new input device + * + * Returns prepared struct bst_dev or NULL. + * + * NOTE: Use bst_free_device() to free devices that have not been + * registered; bst_unregister_device() should be used for already + * registered devices. + */ +struct bst_dev *bst_allocate_device(void) +{ + struct bst_dev *dev; + + dev = kzalloc(sizeof(struct bst_dev), GFP_KERNEL); + if (dev) { + dev->dev.type = &bst_dev_type; + dev->dev.class = &bst_class; + device_initialize(&dev->dev); + mutex_init(&dev->mutex); + INIT_LIST_HEAD(&dev->node); + __module_get(THIS_MODULE); + } + return dev; +} +EXPORT_SYMBOL(bst_allocate_device); + + + +/** + * bst_free_device - free memory occupied by bst_dev structure + * @dev: input device to free + * + * This function should only be used if bst_register_device() + * was not called yet or if it failed. Once device was registered + * use bst_unregister_device() and memory will be freed once last + * reference to the device is dropped. + * + * Device should be allocated by bst_allocate_device(). + * + * NOTE: If there are references to the input device then memory + * will not be freed until last reference is dropped. + */ +void bst_free_device(struct bst_dev *dev) +{ + if (dev) + bst_put_device(dev); +} +EXPORT_SYMBOL(bst_free_device); + +/** + * bst_register_device - register device with input core + * @dev: device to be registered + * + * This function registers device with input core. The device must be + * allocated with bst_allocate_device() and all it's capabilities + * set up before registering. + * If function fails the device must be freed with bst_free_device(). + * Once device has been successfully registered it can be unregistered + * with bst_unregister_device(); bst_free_device() should not be + * called in this case. + */ +int bst_register_device(struct bst_dev *dev) +{ + const char *path; + int error; + + + /* + * If delay and period are pre-set by the driver, then autorepeating + * is handled by the driver itself and we don't do it in input.c. + */ + dev_set_name(&dev->dev, dev->name); + + error = device_add(&dev->dev); + if (error) + return error; + + path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL); + PINFO("%s as %s\n", + dev->name ? dev->name : "Unspecified device", + path ? path : "N/A"); + kfree(path); + error = mutex_lock_interruptible(&bst_mutex); + if (error) { + device_del(&dev->dev); + return error; + } + + list_add_tail(&dev->node, &bst_dev_list); + + mutex_unlock(&bst_mutex); + return 0; +} +EXPORT_SYMBOL(bst_register_device); + +/** + * bst_unregister_device - unregister previously registered device + * @dev: device to be unregistered + * + * This function unregisters an input device. Once device is unregistered + * the caller should not try to access it as it may get freed at any moment. + */ +void bst_unregister_device(struct bst_dev *dev) +{ + int ret = 0; + ret = mutex_lock_interruptible(&bst_mutex); + if(ret){ + return; + } + + list_del_init(&dev->node); + mutex_unlock(&bst_mutex); + device_unregister(&dev->dev); +} +EXPORT_SYMBOL(bst_unregister_device); + +static int __init bst_init(void) +{ + int err; + /*bst class register*/ + err = class_register(&bst_class); + if (err) { + pr_err("unable to register bst_dev class\n"); + return err; + } + return err; +} + +static void __exit bst_exit(void) +{ + /*bst class*/ + class_unregister(&bst_class); +} + +/*subsys_initcall(bst_init);*/ + +MODULE_AUTHOR("contact@bosch-sensortec.com"); +MODULE_DESCRIPTION("BST CLASS CORE"); +MODULE_LICENSE("GPL V2"); + +module_init(bst_init); +module_exit(bst_exit); diff --git a/drivers/input/misc/bstclass.h b/drivers/input/misc/bstclass.h new file mode 100644 index 00000000..2291cca0 --- /dev/null +++ b/drivers/input/misc/bstclass.h @@ -0,0 +1,74 @@ +/*! + * @section LICENSE + * $license$ + * + * @filename $filename$ + * @date $date$ + * @id $id$ + * + * @brief + * The core code of bst device driver +*/ + +#ifndef _BSTCLASS_H +#define _BSTCLASS_H + +#ifdef __KERNEL__ +#include <linux/time.h> +#include <linux/list.h> +#else +#include <sys/time.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <linux/types.h> +#endif + +#include <linux/device.h> +#include <linux/fs.h> +#include <linux/mod_devicetable.h> + +struct bst_dev { + const char *name; + + int (*open)(struct bst_dev *dev); + void (*close)(struct bst_dev *dev); + struct mutex mutex; + struct device dev; + struct list_head node; +}; + +#define to_bst_dev(d) container_of(d, struct bst_dev, dev) + +struct bst_dev *bst_allocate_device(void); +void bst_free_device(struct bst_dev *dev); + +static inline struct bst_dev *bst_get_device(struct bst_dev *dev) +{ + return dev ? to_bst_dev(get_device(&dev->dev)) : NULL; +} + +static inline void bst_put_device(struct bst_dev *dev) +{ + if (dev) + put_device(&dev->dev); +} + +static inline void *bst_get_drvdata(struct bst_dev *dev) +{ + return dev_get_drvdata(&dev->dev); +} + +static inline void bst_set_drvdata(struct bst_dev *dev, void *data) +{ + dev_set_drvdata(&dev->dev, data); +} + +int __must_check bst_register_device(struct bst_dev *); +void bst_unregister_device(struct bst_dev *); + +void bst_reset_device(struct bst_dev *); + + +extern struct class bst_class; + +#endif diff --git a/drivers/input/misc/cm109.c b/drivers/input/misc/cm109.c new file mode 100644 index 00000000..082684e7 --- /dev/null +++ b/drivers/input/misc/cm109.c @@ -0,0 +1,918 @@ +/* + * Driver for the VoIP USB phones with CM109 chipsets. + * + * Copyright (C) 2007 - 2008 Alfred E. Heggestad <aeh@db.org> + * + * 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, version 2. + */ + +/* + * Tested devices: + * - Komunikate KIP1000 + * - Genius G-talk + * - Allied-Telesis Corega USBPH01 + * - ... + * + * This driver is based on the yealink.c driver + * + * Thanks to: + * - Authors of yealink.c + * - Thomas Reitmayr + * - Oliver Neukum for good review comments and code + * - Shaun Jackman <sjackman@gmail.com> for Genius G-talk keymap + * - Dmitry Torokhov for valuable input and review + * + * Todo: + * - Read/write EEPROM + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/rwsem.h> +#include <linux/usb/input.h> + +#define DRIVER_VERSION "20080805" +#define DRIVER_AUTHOR "Alfred E. Heggestad" +#define DRIVER_DESC "CM109 phone driver" + +static char *phone = "kip1000"; +module_param(phone, charp, S_IRUSR); +MODULE_PARM_DESC(phone, "Phone name {kip1000, gtalk, usbph01, atcom}"); + +enum { + /* HID Registers */ + HID_IR0 = 0x00, /* Record/Playback-mute button, Volume up/down */ + HID_IR1 = 0x01, /* GPI, generic registers or EEPROM_DATA0 */ + HID_IR2 = 0x02, /* Generic registers or EEPROM_DATA1 */ + HID_IR3 = 0x03, /* Generic registers or EEPROM_CTRL */ + HID_OR0 = 0x00, /* Mapping control, buzzer, SPDIF (offset 0x04) */ + HID_OR1 = 0x01, /* GPO - General Purpose Output */ + HID_OR2 = 0x02, /* Set GPIO to input/output mode */ + HID_OR3 = 0x03, /* SPDIF status channel or EEPROM_CTRL */ + + /* HID_IR0 */ + RECORD_MUTE = 1 << 3, + PLAYBACK_MUTE = 1 << 2, + VOLUME_DOWN = 1 << 1, + VOLUME_UP = 1 << 0, + + /* HID_OR0 */ + /* bits 7-6 + 0: HID_OR1-2 are used for GPO; HID_OR0, 3 are used for buzzer + and SPDIF + 1: HID_OR0-3 are used as generic HID registers + 2: Values written to HID_OR0-3 are also mapped to MCU_CTRL, + EEPROM_DATA0-1, EEPROM_CTRL (see Note) + 3: Reserved + */ + HID_OR_GPO_BUZ_SPDIF = 0 << 6, + HID_OR_GENERIC_HID_REG = 1 << 6, + HID_OR_MAP_MCU_EEPROM = 2 << 6, + + BUZZER_ON = 1 << 5, + + /* up to 256 normal keys, up to 16 special keys */ + KEYMAP_SIZE = 256 + 16, +}; + +/* CM109 protocol packet */ +struct cm109_ctl_packet { + u8 byte[4]; +} __attribute__ ((packed)); + +enum { USB_PKT_LEN = sizeof(struct cm109_ctl_packet) }; + +/* CM109 device structure */ +struct cm109_dev { + struct input_dev *idev; /* input device */ + struct usb_device *udev; /* usb device */ + struct usb_interface *intf; + + /* irq input channel */ + struct cm109_ctl_packet *irq_data; + dma_addr_t irq_dma; + struct urb *urb_irq; + + /* control output channel */ + struct cm109_ctl_packet *ctl_data; + dma_addr_t ctl_dma; + struct usb_ctrlrequest *ctl_req; + struct urb *urb_ctl; + /* + * The 3 bitfields below are protected by ctl_submit_lock. + * They have to be separate since they are accessed from IRQ + * context. + */ + unsigned irq_urb_pending:1; /* irq_urb is in flight */ + unsigned ctl_urb_pending:1; /* ctl_urb is in flight */ + unsigned buzzer_pending:1; /* need to issue buzz command */ + spinlock_t ctl_submit_lock; + + unsigned char buzzer_state; /* on/off */ + + /* flags */ + unsigned open:1; + unsigned resetting:1; + unsigned shutdown:1; + + /* This mutex protects writes to the above flags */ + struct mutex pm_mutex; + + unsigned short keymap[KEYMAP_SIZE]; + + char phys[64]; /* physical device path */ + int key_code; /* last reported key */ + int keybit; /* 0=new scan 1,2,4,8=scan columns */ + u8 gpi; /* Cached value of GPI (high nibble) */ +}; + +/****************************************************************************** + * CM109 key interface + *****************************************************************************/ + +static unsigned short special_keymap(int code) +{ + if (code > 0xff) { + switch (code - 0xff) { + case RECORD_MUTE: return KEY_MUTE; + case PLAYBACK_MUTE: return KEY_MUTE; + case VOLUME_DOWN: return KEY_VOLUMEDOWN; + case VOLUME_UP: return KEY_VOLUMEUP; + } + } + return KEY_RESERVED; +} + +/* Map device buttons to internal key events. + * + * The "up" and "down" keys, are symbolised by arrows on the button. + * The "pickup" and "hangup" keys are symbolised by a green and red phone + * on the button. + + Komunikate KIP1000 Keyboard Matrix + + -> -- 1 -- 2 -- 3 --> GPI pin 4 (0x10) + | | | | + <- -- 4 -- 5 -- 6 --> GPI pin 5 (0x20) + | | | | + END - 7 -- 8 -- 9 --> GPI pin 6 (0x40) + | | | | + OK -- * -- 0 -- # --> GPI pin 7 (0x80) + | | | | + + /|\ /|\ /|\ /|\ + | | | | +GPO +pin: 3 2 1 0 + 0x8 0x4 0x2 0x1 + + */ +static unsigned short keymap_kip1000(int scancode) +{ + switch (scancode) { /* phone key: */ + case 0x82: return KEY_NUMERIC_0; /* 0 */ + case 0x14: return KEY_NUMERIC_1; /* 1 */ + case 0x12: return KEY_NUMERIC_2; /* 2 */ + case 0x11: return KEY_NUMERIC_3; /* 3 */ + case 0x24: return KEY_NUMERIC_4; /* 4 */ + case 0x22: return KEY_NUMERIC_5; /* 5 */ + case 0x21: return KEY_NUMERIC_6; /* 6 */ + case 0x44: return KEY_NUMERIC_7; /* 7 */ + case 0x42: return KEY_NUMERIC_8; /* 8 */ + case 0x41: return KEY_NUMERIC_9; /* 9 */ + case 0x81: return KEY_NUMERIC_POUND; /* # */ + case 0x84: return KEY_NUMERIC_STAR; /* * */ + case 0x88: return KEY_ENTER; /* pickup */ + case 0x48: return KEY_ESC; /* hangup */ + case 0x28: return KEY_LEFT; /* IN */ + case 0x18: return KEY_RIGHT; /* OUT */ + default: return special_keymap(scancode); + } +} + +/* + Contributed by Shaun Jackman <sjackman@gmail.com> + + Genius G-Talk keyboard matrix + 0 1 2 3 + 4: 0 4 8 Talk + 5: 1 5 9 End + 6: 2 6 # Up + 7: 3 7 * Down +*/ +static unsigned short keymap_gtalk(int scancode) +{ + switch (scancode) { + case 0x11: return KEY_NUMERIC_0; + case 0x21: return KEY_NUMERIC_1; + case 0x41: return KEY_NUMERIC_2; + case 0x81: return KEY_NUMERIC_3; + case 0x12: return KEY_NUMERIC_4; + case 0x22: return KEY_NUMERIC_5; + case 0x42: return KEY_NUMERIC_6; + case 0x82: return KEY_NUMERIC_7; + case 0x14: return KEY_NUMERIC_8; + case 0x24: return KEY_NUMERIC_9; + case 0x44: return KEY_NUMERIC_POUND; /* # */ + case 0x84: return KEY_NUMERIC_STAR; /* * */ + case 0x18: return KEY_ENTER; /* Talk (green handset) */ + case 0x28: return KEY_ESC; /* End (red handset) */ + case 0x48: return KEY_UP; /* Menu up (rocker switch) */ + case 0x88: return KEY_DOWN; /* Menu down (rocker switch) */ + default: return special_keymap(scancode); + } +} + +/* + * Keymap for Allied-Telesis Corega USBPH01 + * http://www.alliedtelesis-corega.com/2/1344/1437/1360/chprd.html + * + * Contributed by july@nat.bg + */ +static unsigned short keymap_usbph01(int scancode) +{ + switch (scancode) { + case 0x11: return KEY_NUMERIC_0; /* 0 */ + case 0x21: return KEY_NUMERIC_1; /* 1 */ + case 0x41: return KEY_NUMERIC_2; /* 2 */ + case 0x81: return KEY_NUMERIC_3; /* 3 */ + case 0x12: return KEY_NUMERIC_4; /* 4 */ + case 0x22: return KEY_NUMERIC_5; /* 5 */ + case 0x42: return KEY_NUMERIC_6; /* 6 */ + case 0x82: return KEY_NUMERIC_7; /* 7 */ + case 0x14: return KEY_NUMERIC_8; /* 8 */ + case 0x24: return KEY_NUMERIC_9; /* 9 */ + case 0x44: return KEY_NUMERIC_POUND; /* # */ + case 0x84: return KEY_NUMERIC_STAR; /* * */ + case 0x18: return KEY_ENTER; /* pickup */ + case 0x28: return KEY_ESC; /* hangup */ + case 0x48: return KEY_LEFT; /* IN */ + case 0x88: return KEY_RIGHT; /* OUT */ + default: return special_keymap(scancode); + } +} + +/* + * Keymap for ATCom AU-100 + * http://www.atcom.cn/products.html + * http://www.packetizer.com/products/au100/ + * http://www.voip-info.org/wiki/view/AU-100 + * + * Contributed by daniel@gimpelevich.san-francisco.ca.us + */ +static unsigned short keymap_atcom(int scancode) +{ + switch (scancode) { /* phone key: */ + case 0x82: return KEY_NUMERIC_0; /* 0 */ + case 0x11: return KEY_NUMERIC_1; /* 1 */ + case 0x12: return KEY_NUMERIC_2; /* 2 */ + case 0x14: return KEY_NUMERIC_3; /* 3 */ + case 0x21: return KEY_NUMERIC_4; /* 4 */ + case 0x22: return KEY_NUMERIC_5; /* 5 */ + case 0x24: return KEY_NUMERIC_6; /* 6 */ + case 0x41: return KEY_NUMERIC_7; /* 7 */ + case 0x42: return KEY_NUMERIC_8; /* 8 */ + case 0x44: return KEY_NUMERIC_9; /* 9 */ + case 0x84: return KEY_NUMERIC_POUND; /* # */ + case 0x81: return KEY_NUMERIC_STAR; /* * */ + case 0x18: return KEY_ENTER; /* pickup */ + case 0x28: return KEY_ESC; /* hangup */ + case 0x48: return KEY_LEFT; /* left arrow */ + case 0x88: return KEY_RIGHT; /* right arrow */ + default: return special_keymap(scancode); + } +} + +static unsigned short (*keymap)(int) = keymap_kip1000; + +/* + * Completes a request by converting the data into events for the + * input subsystem. + */ +static void report_key(struct cm109_dev *dev, int key) +{ + struct input_dev *idev = dev->idev; + + if (dev->key_code >= 0) { + /* old key up */ + input_report_key(idev, dev->key_code, 0); + } + + dev->key_code = key; + if (key >= 0) { + /* new valid key */ + input_report_key(idev, key, 1); + } + + input_sync(idev); +} + +/****************************************************************************** + * CM109 usb communication interface + *****************************************************************************/ + +static void cm109_submit_buzz_toggle(struct cm109_dev *dev) +{ + int error; + + if (dev->buzzer_state) + dev->ctl_data->byte[HID_OR0] |= BUZZER_ON; + else + dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON; + + error = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC); + if (error) + dev_err(&dev->intf->dev, + "%s: usb_submit_urb (urb_ctl) failed %d\n", + __func__, error); +} + +/* + * IRQ handler + */ +static void cm109_urb_irq_callback(struct urb *urb) +{ + struct cm109_dev *dev = urb->context; + const int status = urb->status; + int error; + + dev_dbg(&dev->intf->dev, "### URB IRQ: [0x%02x 0x%02x 0x%02x 0x%02x] keybit=0x%02x\n", + dev->irq_data->byte[0], + dev->irq_data->byte[1], + dev->irq_data->byte[2], + dev->irq_data->byte[3], + dev->keybit); + + if (status) { + if (status == -ESHUTDOWN) + return; + dev_err(&dev->intf->dev, "%s: urb status %d\n", __func__, status); + } + + /* Special keys */ + if (dev->irq_data->byte[HID_IR0] & 0x0f) { + const int code = (dev->irq_data->byte[HID_IR0] & 0x0f); + report_key(dev, dev->keymap[0xff + code]); + } + + /* Scan key column */ + if (dev->keybit == 0xf) { + + /* Any changes ? */ + if ((dev->gpi & 0xf0) == (dev->irq_data->byte[HID_IR1] & 0xf0)) + goto out; + + dev->gpi = dev->irq_data->byte[HID_IR1] & 0xf0; + dev->keybit = 0x1; + } else { + report_key(dev, dev->keymap[dev->irq_data->byte[HID_IR1]]); + + dev->keybit <<= 1; + if (dev->keybit > 0x8) + dev->keybit = 0xf; + } + + out: + + spin_lock(&dev->ctl_submit_lock); + + dev->irq_urb_pending = 0; + + if (likely(!dev->shutdown)) { + + if (dev->buzzer_state) + dev->ctl_data->byte[HID_OR0] |= BUZZER_ON; + else + dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON; + + dev->ctl_data->byte[HID_OR1] = dev->keybit; + dev->ctl_data->byte[HID_OR2] = dev->keybit; + + dev->buzzer_pending = 0; + dev->ctl_urb_pending = 1; + + error = usb_submit_urb(dev->urb_ctl, GFP_ATOMIC); + if (error) + dev_err(&dev->intf->dev, + "%s: usb_submit_urb (urb_ctl) failed %d\n", + __func__, error); + } + + spin_unlock(&dev->ctl_submit_lock); +} + +static void cm109_urb_ctl_callback(struct urb *urb) +{ + struct cm109_dev *dev = urb->context; + const int status = urb->status; + int error; + + dev_dbg(&dev->intf->dev, "### URB CTL: [0x%02x 0x%02x 0x%02x 0x%02x]\n", + dev->ctl_data->byte[0], + dev->ctl_data->byte[1], + dev->ctl_data->byte[2], + dev->ctl_data->byte[3]); + + if (status) + dev_err(&dev->intf->dev, "%s: urb status %d\n", __func__, status); + + spin_lock(&dev->ctl_submit_lock); + + dev->ctl_urb_pending = 0; + + if (likely(!dev->shutdown)) { + + if (dev->buzzer_pending) { + dev->buzzer_pending = 0; + dev->ctl_urb_pending = 1; + cm109_submit_buzz_toggle(dev); + } else if (likely(!dev->irq_urb_pending)) { + /* ask for key data */ + dev->irq_urb_pending = 1; + error = usb_submit_urb(dev->urb_irq, GFP_ATOMIC); + if (error) + dev_err(&dev->intf->dev, + "%s: usb_submit_urb (urb_irq) failed %d\n", + __func__, error); + } + } + + spin_unlock(&dev->ctl_submit_lock); +} + +static void cm109_toggle_buzzer_async(struct cm109_dev *dev) +{ + unsigned long flags; + + spin_lock_irqsave(&dev->ctl_submit_lock, flags); + + if (dev->ctl_urb_pending) { + /* URB completion will resubmit */ + dev->buzzer_pending = 1; + } else { + dev->ctl_urb_pending = 1; + cm109_submit_buzz_toggle(dev); + } + + spin_unlock_irqrestore(&dev->ctl_submit_lock, flags); +} + +static void cm109_toggle_buzzer_sync(struct cm109_dev *dev, int on) +{ + int error; + + if (on) + dev->ctl_data->byte[HID_OR0] |= BUZZER_ON; + else + dev->ctl_data->byte[HID_OR0] &= ~BUZZER_ON; + + error = usb_control_msg(dev->udev, + usb_sndctrlpipe(dev->udev, 0), + dev->ctl_req->bRequest, + dev->ctl_req->bRequestType, + le16_to_cpu(dev->ctl_req->wValue), + le16_to_cpu(dev->ctl_req->wIndex), + dev->ctl_data, + USB_PKT_LEN, USB_CTRL_SET_TIMEOUT); + if (error < 0 && error != -EINTR) + dev_err(&dev->intf->dev, "%s: usb_control_msg() failed %d\n", + __func__, error); +} + +static void cm109_stop_traffic(struct cm109_dev *dev) +{ + dev->shutdown = 1; + /* + * Make sure other CPUs see this + */ + smp_wmb(); + + usb_kill_urb(dev->urb_ctl); + usb_kill_urb(dev->urb_irq); + + cm109_toggle_buzzer_sync(dev, 0); + + dev->shutdown = 0; + smp_wmb(); +} + +static void cm109_restore_state(struct cm109_dev *dev) +{ + if (dev->open) { + /* + * Restore buzzer state. + * This will also kick regular URB submission + */ + cm109_toggle_buzzer_async(dev); + } +} + +/****************************************************************************** + * input event interface + *****************************************************************************/ + +static int cm109_input_open(struct input_dev *idev) +{ + struct cm109_dev *dev = input_get_drvdata(idev); + int error; + + error = usb_autopm_get_interface(dev->intf); + if (error < 0) { + dev_err(&idev->dev, "%s - cannot autoresume, result %d\n", + __func__, error); + return error; + } + + mutex_lock(&dev->pm_mutex); + + dev->buzzer_state = 0; + dev->key_code = -1; /* no keys pressed */ + dev->keybit = 0xf; + + /* issue INIT */ + dev->ctl_data->byte[HID_OR0] = HID_OR_GPO_BUZ_SPDIF; + dev->ctl_data->byte[HID_OR1] = dev->keybit; + dev->ctl_data->byte[HID_OR2] = dev->keybit; + dev->ctl_data->byte[HID_OR3] = 0x00; + + error = usb_submit_urb(dev->urb_ctl, GFP_KERNEL); + if (error) + dev_err(&dev->intf->dev, "%s: usb_submit_urb (urb_ctl) failed %d\n", + __func__, error); + else + dev->open = 1; + + mutex_unlock(&dev->pm_mutex); + + if (error) + usb_autopm_put_interface(dev->intf); + + return error; +} + +static void cm109_input_close(struct input_dev *idev) +{ + struct cm109_dev *dev = input_get_drvdata(idev); + + mutex_lock(&dev->pm_mutex); + + /* + * Once we are here event delivery is stopped so we + * don't need to worry about someone starting buzzer + * again + */ + cm109_stop_traffic(dev); + dev->open = 0; + + mutex_unlock(&dev->pm_mutex); + + usb_autopm_put_interface(dev->intf); +} + +static int cm109_input_ev(struct input_dev *idev, unsigned int type, + unsigned int code, int value) +{ + struct cm109_dev *dev = input_get_drvdata(idev); + + dev_dbg(&dev->intf->dev, + "input_ev: type=%u code=%u value=%d\n", type, code, value); + + if (type != EV_SND) + return -EINVAL; + + switch (code) { + case SND_TONE: + case SND_BELL: + dev->buzzer_state = !!value; + if (!dev->resetting) + cm109_toggle_buzzer_async(dev); + return 0; + + default: + return -EINVAL; + } +} + + +/****************************************************************************** + * Linux interface and usb initialisation + *****************************************************************************/ + +struct driver_info { + char *name; +}; + +static const struct driver_info info_cm109 = { + .name = "CM109 USB driver", +}; + +enum { + VENDOR_ID = 0x0d8c, /* C-Media Electronics */ + PRODUCT_ID_CM109 = 0x000e, /* CM109 defines range 0x0008 - 0x000f */ +}; + +/* table of devices that work with this driver */ +static const struct usb_device_id cm109_usb_table[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = VENDOR_ID, + .idProduct = PRODUCT_ID_CM109, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .driver_info = (kernel_ulong_t) &info_cm109 + }, + /* you can add more devices here with product ID 0x0008 - 0x000f */ + { } +}; + +static void cm109_usb_cleanup(struct cm109_dev *dev) +{ + kfree(dev->ctl_req); + if (dev->ctl_data) + usb_free_coherent(dev->udev, USB_PKT_LEN, + dev->ctl_data, dev->ctl_dma); + if (dev->irq_data) + usb_free_coherent(dev->udev, USB_PKT_LEN, + dev->irq_data, dev->irq_dma); + + usb_free_urb(dev->urb_irq); /* parameter validation in core/urb */ + usb_free_urb(dev->urb_ctl); /* parameter validation in core/urb */ + kfree(dev); +} + +static void cm109_usb_disconnect(struct usb_interface *interface) +{ + struct cm109_dev *dev = usb_get_intfdata(interface); + + usb_set_intfdata(interface, NULL); + input_unregister_device(dev->idev); + cm109_usb_cleanup(dev); +} + +static int cm109_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct driver_info *nfo = (struct driver_info *)id->driver_info; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct cm109_dev *dev; + struct input_dev *input_dev = NULL; + int ret, pipe, i; + int error = -ENOMEM; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->ctl_submit_lock); + mutex_init(&dev->pm_mutex); + + dev->udev = udev; + dev->intf = intf; + + dev->idev = input_dev = input_allocate_device(); + if (!input_dev) + goto err_out; + + /* allocate usb buffers */ + dev->irq_data = usb_alloc_coherent(udev, USB_PKT_LEN, + GFP_KERNEL, &dev->irq_dma); + if (!dev->irq_data) + goto err_out; + + dev->ctl_data = usb_alloc_coherent(udev, USB_PKT_LEN, + GFP_KERNEL, &dev->ctl_dma); + if (!dev->ctl_data) + goto err_out; + + dev->ctl_req = kmalloc(sizeof(*(dev->ctl_req)), GFP_KERNEL); + if (!dev->ctl_req) + goto err_out; + + /* allocate urb structures */ + dev->urb_irq = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb_irq) + goto err_out; + + dev->urb_ctl = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb_ctl) + goto err_out; + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + ret = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + if (ret != USB_PKT_LEN) + dev_err(&intf->dev, "invalid payload size %d, expected %d\n", + ret, USB_PKT_LEN); + + /* initialise irq urb */ + usb_fill_int_urb(dev->urb_irq, udev, pipe, dev->irq_data, + USB_PKT_LEN, + cm109_urb_irq_callback, dev, endpoint->bInterval); + dev->urb_irq->transfer_dma = dev->irq_dma; + dev->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + dev->urb_irq->dev = udev; + + /* initialise ctl urb */ + dev->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_OUT; + dev->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION; + dev->ctl_req->wValue = cpu_to_le16(0x200); + dev->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); + dev->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN); + + usb_fill_control_urb(dev->urb_ctl, udev, usb_sndctrlpipe(udev, 0), + (void *)dev->ctl_req, dev->ctl_data, USB_PKT_LEN, + cm109_urb_ctl_callback, dev); + dev->urb_ctl->transfer_dma = dev->ctl_dma; + dev->urb_ctl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + dev->urb_ctl->dev = udev; + + /* find out the physical bus location */ + usb_make_path(udev, dev->phys, sizeof(dev->phys)); + strlcat(dev->phys, "/input0", sizeof(dev->phys)); + + /* register settings for the input device */ + input_dev->name = nfo->name; + input_dev->phys = dev->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, dev); + input_dev->open = cm109_input_open; + input_dev->close = cm109_input_close; + input_dev->event = cm109_input_ev; + + input_dev->keycode = dev->keymap; + input_dev->keycodesize = sizeof(unsigned char); + input_dev->keycodemax = ARRAY_SIZE(dev->keymap); + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + + /* register available key events */ + for (i = 0; i < KEYMAP_SIZE; i++) { + unsigned short k = keymap(i); + dev->keymap[i] = k; + __set_bit(k, input_dev->keybit); + } + __clear_bit(KEY_RESERVED, input_dev->keybit); + + error = input_register_device(dev->idev); + if (error) + goto err_out; + + usb_set_intfdata(intf, dev); + + return 0; + + err_out: + input_free_device(input_dev); + cm109_usb_cleanup(dev); + return error; +} + +static int cm109_usb_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + dev_info(&intf->dev, "cm109: usb_suspend (event=%d)\n", message.event); + + mutex_lock(&dev->pm_mutex); + cm109_stop_traffic(dev); + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static int cm109_usb_resume(struct usb_interface *intf) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + dev_info(&intf->dev, "cm109: usb_resume\n"); + + mutex_lock(&dev->pm_mutex); + cm109_restore_state(dev); + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static int cm109_usb_pre_reset(struct usb_interface *intf) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + mutex_lock(&dev->pm_mutex); + + /* + * Make sure input events don't try to toggle buzzer + * while we are resetting + */ + dev->resetting = 1; + smp_wmb(); + + cm109_stop_traffic(dev); + + return 0; +} + +static int cm109_usb_post_reset(struct usb_interface *intf) +{ + struct cm109_dev *dev = usb_get_intfdata(intf); + + dev->resetting = 0; + smp_wmb(); + + cm109_restore_state(dev); + + mutex_unlock(&dev->pm_mutex); + + return 0; +} + +static struct usb_driver cm109_driver = { + .name = "cm109", + .probe = cm109_usb_probe, + .disconnect = cm109_usb_disconnect, + .suspend = cm109_usb_suspend, + .resume = cm109_usb_resume, + .reset_resume = cm109_usb_resume, + .pre_reset = cm109_usb_pre_reset, + .post_reset = cm109_usb_post_reset, + .id_table = cm109_usb_table, + .supports_autosuspend = 1, +}; + +static int __init cm109_select_keymap(void) +{ + /* Load the phone keymap */ + if (!strcasecmp(phone, "kip1000")) { + keymap = keymap_kip1000; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for Komunikate KIP1000 phone loaded\n"); + } else if (!strcasecmp(phone, "gtalk")) { + keymap = keymap_gtalk; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for Genius G-talk phone loaded\n"); + } else if (!strcasecmp(phone, "usbph01")) { + keymap = keymap_usbph01; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for Allied-Telesis Corega USBPH01 phone loaded\n"); + } else if (!strcasecmp(phone, "atcom")) { + keymap = keymap_atcom; + printk(KERN_INFO KBUILD_MODNAME ": " + "Keymap for ATCom AU-100 phone loaded\n"); + } else { + printk(KERN_ERR KBUILD_MODNAME ": " + "Unsupported phone: %s\n", phone); + return -EINVAL; + } + + return 0; +} + +static int __init cm109_init(void) +{ + int err; + + err = cm109_select_keymap(); + if (err) + return err; + + err = usb_register(&cm109_driver); + if (err) + return err; + + printk(KERN_INFO KBUILD_MODNAME ": " + DRIVER_DESC ": " DRIVER_VERSION " (C) " DRIVER_AUTHOR "\n"); + + return 0; +} + +static void __exit cm109_exit(void) +{ + usb_deregister(&cm109_driver); +} + +module_init(cm109_init); +module_exit(cm109_exit); + +MODULE_DEVICE_TABLE(usb, cm109_usb_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/cma3000_d0x.c b/drivers/input/misc/cma3000_d0x.c new file mode 100644 index 00000000..df9b7565 --- /dev/null +++ b/drivers/input/misc/cma3000_d0x.c @@ -0,0 +1,399 @@ +/* + * VTI CMA3000_D0x Accelerometer driver + * + * Copyright (C) 2010 Texas Instruments + * Author: Hemanth V <hemanthv@ti.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/types.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/cma3000.h> +#include <linux/module.h> + +#include "cma3000_d0x.h" + +#define CMA3000_WHOAMI 0x00 +#define CMA3000_REVID 0x01 +#define CMA3000_CTRL 0x02 +#define CMA3000_STATUS 0x03 +#define CMA3000_RSTR 0x04 +#define CMA3000_INTSTATUS 0x05 +#define CMA3000_DOUTX 0x06 +#define CMA3000_DOUTY 0x07 +#define CMA3000_DOUTZ 0x08 +#define CMA3000_MDTHR 0x09 +#define CMA3000_MDFFTMR 0x0A +#define CMA3000_FFTHR 0x0B + +#define CMA3000_RANGE2G (1 << 7) +#define CMA3000_RANGE8G (0 << 7) +#define CMA3000_BUSI2C (0 << 4) +#define CMA3000_MODEMASK (7 << 1) +#define CMA3000_GRANGEMASK (1 << 7) + +#define CMA3000_STATUS_PERR 1 +#define CMA3000_INTSTATUS_FFDET (1 << 2) + +/* Settling time delay in ms */ +#define CMA3000_SETDELAY 30 + +/* Delay for clearing interrupt in us */ +#define CMA3000_INTDELAY 44 + + +/* + * Bit weights in mg for bit 0, other bits need + * multiply factor 2^n. Eight bit is the sign bit. + */ +#define BIT_TO_2G 18 +#define BIT_TO_8G 71 + +struct cma3000_accl_data { + const struct cma3000_bus_ops *bus_ops; + const struct cma3000_platform_data *pdata; + + struct device *dev; + struct input_dev *input_dev; + + int bit_to_mg; + int irq; + + int g_range; + u8 mode; + + struct mutex mutex; + bool opened; + bool suspended; +}; + +#define CMA3000_READ(data, reg, msg) \ + (data->bus_ops->read(data->dev, reg, msg)) +#define CMA3000_SET(data, reg, val, msg) \ + ((data)->bus_ops->write(data->dev, reg, val, msg)) + +/* + * Conversion for each of the eight modes to g, depending + * on G range i.e 2G or 8G. Some modes always operate in + * 8G. + */ + +static int mode_to_mg[8][2] = { + { 0, 0 }, + { BIT_TO_8G, BIT_TO_2G }, + { BIT_TO_8G, BIT_TO_2G }, + { BIT_TO_8G, BIT_TO_8G }, + { BIT_TO_8G, BIT_TO_8G }, + { BIT_TO_8G, BIT_TO_2G }, + { BIT_TO_8G, BIT_TO_2G }, + { 0, 0}, +}; + +static void decode_mg(struct cma3000_accl_data *data, int *datax, + int *datay, int *dataz) +{ + /* Data in 2's complement, convert to mg */ + *datax = ((s8)*datax) * data->bit_to_mg; + *datay = ((s8)*datay) * data->bit_to_mg; + *dataz = ((s8)*dataz) * data->bit_to_mg; +} + +static irqreturn_t cma3000_thread_irq(int irq, void *dev_id) +{ + struct cma3000_accl_data *data = dev_id; + int datax, datay, dataz, intr_status; + u8 ctrl, mode, range; + + intr_status = CMA3000_READ(data, CMA3000_INTSTATUS, "interrupt status"); + if (intr_status < 0) + return IRQ_NONE; + + /* Check if free fall is detected, report immediately */ + if (intr_status & CMA3000_INTSTATUS_FFDET) { + input_report_abs(data->input_dev, ABS_MISC, 1); + input_sync(data->input_dev); + } else { + input_report_abs(data->input_dev, ABS_MISC, 0); + } + + datax = CMA3000_READ(data, CMA3000_DOUTX, "X"); + datay = CMA3000_READ(data, CMA3000_DOUTY, "Y"); + dataz = CMA3000_READ(data, CMA3000_DOUTZ, "Z"); + + ctrl = CMA3000_READ(data, CMA3000_CTRL, "ctrl"); + mode = (ctrl & CMA3000_MODEMASK) >> 1; + range = (ctrl & CMA3000_GRANGEMASK) >> 7; + + data->bit_to_mg = mode_to_mg[mode][range]; + + /* Interrupt not for this device */ + if (data->bit_to_mg == 0) + return IRQ_NONE; + + /* Decode register values to milli g */ + decode_mg(data, &datax, &datay, &dataz); + + input_report_abs(data->input_dev, ABS_X, datax); + input_report_abs(data->input_dev, ABS_Y, datay); + input_report_abs(data->input_dev, ABS_Z, dataz); + input_sync(data->input_dev); + + return IRQ_HANDLED; +} + +static int cma3000_reset(struct cma3000_accl_data *data) +{ + int val; + + /* Reset sequence */ + CMA3000_SET(data, CMA3000_RSTR, 0x02, "Reset"); + CMA3000_SET(data, CMA3000_RSTR, 0x0A, "Reset"); + CMA3000_SET(data, CMA3000_RSTR, 0x04, "Reset"); + + /* Settling time delay */ + mdelay(10); + + val = CMA3000_READ(data, CMA3000_STATUS, "Status"); + if (val < 0) { + dev_err(data->dev, "Reset failed\n"); + return val; + } + + if (val & CMA3000_STATUS_PERR) { + dev_err(data->dev, "Parity Error\n"); + return -EIO; + } + + return 0; +} + +static int cma3000_poweron(struct cma3000_accl_data *data) +{ + const struct cma3000_platform_data *pdata = data->pdata; + u8 ctrl = 0; + int ret; + + if (data->g_range == CMARANGE_2G) { + ctrl = (data->mode << 1) | CMA3000_RANGE2G; + } else if (data->g_range == CMARANGE_8G) { + ctrl = (data->mode << 1) | CMA3000_RANGE8G; + } else { + dev_info(data->dev, + "Invalid G range specified, assuming 8G\n"); + ctrl = (data->mode << 1) | CMA3000_RANGE8G; + } + + ctrl |= data->bus_ops->ctrl_mod; + + CMA3000_SET(data, CMA3000_MDTHR, pdata->mdthr, + "Motion Detect Threshold"); + CMA3000_SET(data, CMA3000_MDFFTMR, pdata->mdfftmr, + "Time register"); + CMA3000_SET(data, CMA3000_FFTHR, pdata->ffthr, + "Free fall threshold"); + ret = CMA3000_SET(data, CMA3000_CTRL, ctrl, "Mode setting"); + if (ret < 0) + return -EIO; + + msleep(CMA3000_SETDELAY); + + return 0; +} + +static int cma3000_poweroff(struct cma3000_accl_data *data) +{ + int ret; + + ret = CMA3000_SET(data, CMA3000_CTRL, CMAMODE_POFF, "Mode setting"); + msleep(CMA3000_SETDELAY); + + return ret; +} + +static int cma3000_open(struct input_dev *input_dev) +{ + struct cma3000_accl_data *data = input_get_drvdata(input_dev); + + mutex_lock(&data->mutex); + + if (!data->suspended) + cma3000_poweron(data); + + data->opened = true; + + mutex_unlock(&data->mutex); + + return 0; +} + +static void cma3000_close(struct input_dev *input_dev) +{ + struct cma3000_accl_data *data = input_get_drvdata(input_dev); + + mutex_lock(&data->mutex); + + if (!data->suspended) + cma3000_poweroff(data); + + data->opened = false; + + mutex_unlock(&data->mutex); +} + +void cma3000_suspend(struct cma3000_accl_data *data) +{ + mutex_lock(&data->mutex); + + if (!data->suspended && data->opened) + cma3000_poweroff(data); + + data->suspended = true; + + mutex_unlock(&data->mutex); +} +EXPORT_SYMBOL(cma3000_suspend); + + +void cma3000_resume(struct cma3000_accl_data *data) +{ + mutex_lock(&data->mutex); + + if (data->suspended && data->opened) + cma3000_poweron(data); + + data->suspended = false; + + mutex_unlock(&data->mutex); +} +EXPORT_SYMBOL(cma3000_resume); + +struct cma3000_accl_data *cma3000_init(struct device *dev, int irq, + const struct cma3000_bus_ops *bops) +{ + const struct cma3000_platform_data *pdata = dev->platform_data; + struct cma3000_accl_data *data; + struct input_dev *input_dev; + int rev; + int error; + + if (!pdata) { + dev_err(dev, "platform data not found\n"); + error = -EINVAL; + goto err_out; + } + + + /* if no IRQ return error */ + if (irq == 0) { + error = -EINVAL; + goto err_out; + } + + data = kzalloc(sizeof(struct cma3000_accl_data), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!data || !input_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + data->dev = dev; + data->input_dev = input_dev; + data->bus_ops = bops; + data->pdata = pdata; + data->irq = irq; + mutex_init(&data->mutex); + + data->mode = pdata->mode; + if (data->mode > CMAMODE_POFF) { + data->mode = CMAMODE_MOTDET; + dev_warn(dev, + "Invalid mode specified, assuming Motion Detect\n"); + } + + data->g_range = pdata->g_range; + if (data->g_range != CMARANGE_2G && data->g_range != CMARANGE_8G) { + dev_info(dev, + "Invalid G range specified, assuming 8G\n"); + data->g_range = CMARANGE_8G; + } + + input_dev->name = "cma3000-accelerometer"; + input_dev->id.bustype = bops->bustype; + input_dev->open = cma3000_open; + input_dev->close = cma3000_close; + + __set_bit(EV_ABS, input_dev->evbit); + + input_set_abs_params(input_dev, ABS_X, + -data->g_range, data->g_range, pdata->fuzz_x, 0); + input_set_abs_params(input_dev, ABS_Y, + -data->g_range, data->g_range, pdata->fuzz_y, 0); + input_set_abs_params(input_dev, ABS_Z, + -data->g_range, data->g_range, pdata->fuzz_z, 0); + input_set_abs_params(input_dev, ABS_MISC, 0, 1, 0, 0); + + input_set_drvdata(input_dev, data); + + error = cma3000_reset(data); + if (error) + goto err_free_mem; + + rev = CMA3000_READ(data, CMA3000_REVID, "Revid"); + if (rev < 0) { + error = rev; + goto err_free_mem; + } + + pr_info("CMA3000 Accelerometer: Revision %x\n", rev); + + error = request_threaded_irq(irq, NULL, cma3000_thread_irq, + pdata->irqflags | IRQF_ONESHOT, + "cma3000_d0x", data); + if (error) { + dev_err(dev, "request_threaded_irq failed\n"); + goto err_free_mem; + } + + error = input_register_device(data->input_dev); + if (error) { + dev_err(dev, "Unable to register input device\n"); + goto err_free_irq; + } + + return data; + +err_free_irq: + free_irq(irq, data); +err_free_mem: + input_free_device(input_dev); + kfree(data); +err_out: + return ERR_PTR(error); +} +EXPORT_SYMBOL(cma3000_init); + +void cma3000_exit(struct cma3000_accl_data *data) +{ + free_irq(data->irq, data); + input_unregister_device(data->input_dev); + kfree(data); +} +EXPORT_SYMBOL(cma3000_exit); + +MODULE_DESCRIPTION("CMA3000-D0x Accelerometer Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hemanth V <hemanthv@ti.com>"); diff --git a/drivers/input/misc/cma3000_d0x.h b/drivers/input/misc/cma3000_d0x.h new file mode 100644 index 00000000..2304ce30 --- /dev/null +++ b/drivers/input/misc/cma3000_d0x.h @@ -0,0 +1,42 @@ +/* + * VTI CMA3000_D0x Accelerometer driver + * + * Copyright (C) 2010 Texas Instruments + * Author: Hemanth V <hemanthv@ti.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _INPUT_CMA3000_H +#define _INPUT_CMA3000_H + +#include <linux/types.h> +#include <linux/input.h> + +struct device; +struct cma3000_accl_data; + +struct cma3000_bus_ops { + u16 bustype; + u8 ctrl_mod; + int (*read)(struct device *, u8, char *); + int (*write)(struct device *, u8, u8, char *); +}; + +struct cma3000_accl_data *cma3000_init(struct device *dev, int irq, + const struct cma3000_bus_ops *bops); +void cma3000_exit(struct cma3000_accl_data *); +void cma3000_suspend(struct cma3000_accl_data *); +void cma3000_resume(struct cma3000_accl_data *); + +#endif diff --git a/drivers/input/misc/cma3000_d0x_i2c.c b/drivers/input/misc/cma3000_d0x_i2c.c new file mode 100644 index 00000000..4fdef98c --- /dev/null +++ b/drivers/input/misc/cma3000_d0x_i2c.c @@ -0,0 +1,132 @@ +/* + * Implements I2C interface for VTI CMA300_D0x Accelerometer driver + * + * Copyright (C) 2010 Texas Instruments + * Author: Hemanth V <hemanthv@ti.com> + * + * 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, see <http://www.gnu.org/licenses/>. + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/input/cma3000.h> +#include "cma3000_d0x.h" + +static int cma3000_i2c_set(struct device *dev, + u8 reg, u8 val, char *msg) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret; + + ret = i2c_smbus_write_byte_data(client, reg, val); + if (ret < 0) + dev_err(&client->dev, + "%s failed (%s, %d)\n", __func__, msg, ret); + return ret; +} + +static int cma3000_i2c_read(struct device *dev, u8 reg, char *msg) +{ + struct i2c_client *client = to_i2c_client(dev); + int ret; + + ret = i2c_smbus_read_byte_data(client, reg); + if (ret < 0) + dev_err(&client->dev, + "%s failed (%s, %d)\n", __func__, msg, ret); + return ret; +} + +static const struct cma3000_bus_ops cma3000_i2c_bops = { + .bustype = BUS_I2C, +#define CMA3000_BUSI2C (0 << 4) + .ctrl_mod = CMA3000_BUSI2C, + .read = cma3000_i2c_read, + .write = cma3000_i2c_set, +}; + +static int cma3000_i2c_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct cma3000_accl_data *data; + + data = cma3000_init(&client->dev, client->irq, &cma3000_i2c_bops); + if (IS_ERR(data)) + return PTR_ERR(data); + + i2c_set_clientdata(client, data); + + return 0; +} + +static int cma3000_i2c_remove(struct i2c_client *client) +{ + struct cma3000_accl_data *data = i2c_get_clientdata(client); + + cma3000_exit(data); + + return 0; +} + +#ifdef CONFIG_PM +static int cma3000_i2c_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cma3000_accl_data *data = i2c_get_clientdata(client); + + cma3000_suspend(data); + + return 0; +} + +static int cma3000_i2c_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct cma3000_accl_data *data = i2c_get_clientdata(client); + + cma3000_resume(data); + + return 0; +} + +static const struct dev_pm_ops cma3000_i2c_pm_ops = { + .suspend = cma3000_i2c_suspend, + .resume = cma3000_i2c_resume, +}; +#endif + +static const struct i2c_device_id cma3000_i2c_id[] = { + { "cma3000_d01", 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, cma3000_i2c_id); + +static struct i2c_driver cma3000_i2c_driver = { + .probe = cma3000_i2c_probe, + .remove = cma3000_i2c_remove, + .id_table = cma3000_i2c_id, + .driver = { + .name = "cma3000_i2c_accl", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &cma3000_i2c_pm_ops, +#endif + }, +}; + +module_i2c_driver(cma3000_i2c_driver); + +MODULE_DESCRIPTION("CMA3000-D0x Accelerometer I2C Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hemanth V <hemanthv@ti.com>"); diff --git a/drivers/input/misc/cobalt_btns.c b/drivers/input/misc/cobalt_btns.c new file mode 100644 index 00000000..4f77f878 --- /dev/null +++ b/drivers/input/misc/cobalt_btns.c @@ -0,0 +1,166 @@ +/* + * Cobalt button interface driver. + * + * Copyright (C) 2007-2008 Yoichi Yuasa <yuasa@linux-mips.org> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define BUTTONS_POLL_INTERVAL 30 /* msec */ +#define BUTTONS_COUNT_THRESHOLD 3 +#define BUTTONS_STATUS_MASK 0xfe000000 + +static const unsigned short cobalt_map[] = { + KEY_RESERVED, + KEY_RESTART, + KEY_LEFT, + KEY_UP, + KEY_DOWN, + KEY_RIGHT, + KEY_ENTER, + KEY_SELECT +}; + +struct buttons_dev { + struct input_polled_dev *poll_dev; + unsigned short keymap[ARRAY_SIZE(cobalt_map)]; + int count[ARRAY_SIZE(cobalt_map)]; + void __iomem *reg; +}; + +static void handle_buttons(struct input_polled_dev *dev) +{ + struct buttons_dev *bdev = dev->private; + struct input_dev *input = dev->input; + uint32_t status; + int i; + + status = ~readl(bdev->reg) >> 24; + + for (i = 0; i < ARRAY_SIZE(bdev->keymap); i++) { + if (status & (1U << i)) { + if (++bdev->count[i] == BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 1); + input_sync(input); + } + } else { + if (bdev->count[i] >= BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 0); + input_sync(input); + } + bdev->count[i] = 0; + } + } +} + +static int cobalt_buttons_probe(struct platform_device *pdev) +{ + struct buttons_dev *bdev; + struct input_polled_dev *poll_dev; + struct input_dev *input; + struct resource *res; + int error, i; + + bdev = kzalloc(sizeof(struct buttons_dev), GFP_KERNEL); + poll_dev = input_allocate_polled_device(); + if (!bdev || !poll_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + memcpy(bdev->keymap, cobalt_map, sizeof(bdev->keymap)); + + poll_dev->private = bdev; + poll_dev->poll = handle_buttons; + poll_dev->poll_interval = BUTTONS_POLL_INTERVAL; + + input = poll_dev->input; + input->name = "Cobalt buttons"; + input->phys = "cobalt/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + + input->keycode = bdev->keymap; + input->keycodemax = ARRAY_SIZE(bdev->keymap); + input->keycodesize = sizeof(unsigned short); + + input_set_capability(input, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < ARRAY_SIZE(cobalt_map); i++) + __set_bit(bdev->keymap[i], input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + error = -EBUSY; + goto err_free_mem; + } + + bdev->poll_dev = poll_dev; + bdev->reg = ioremap(res->start, resource_size(res)); + dev_set_drvdata(&pdev->dev, bdev); + + error = input_register_polled_device(poll_dev); + if (error) + goto err_iounmap; + + return 0; + + err_iounmap: + iounmap(bdev->reg); + err_free_mem: + input_free_polled_device(poll_dev); + kfree(bdev); + dev_set_drvdata(&pdev->dev, NULL); + return error; +} + +static int cobalt_buttons_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct buttons_dev *bdev = dev_get_drvdata(dev); + + input_unregister_polled_device(bdev->poll_dev); + input_free_polled_device(bdev->poll_dev); + iounmap(bdev->reg); + kfree(bdev); + dev_set_drvdata(dev, NULL); + + return 0; +} + +MODULE_AUTHOR("Yoichi Yuasa <yuasa@linux-mips.org>"); +MODULE_DESCRIPTION("Cobalt button interface driver"); +MODULE_LICENSE("GPL"); +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:Cobalt buttons"); + +static struct platform_driver cobalt_buttons_driver = { + .probe = cobalt_buttons_probe, + .remove = cobalt_buttons_remove, + .driver = { + .name = "Cobalt buttons", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(cobalt_buttons_driver); diff --git a/drivers/input/misc/da9052_onkey.c b/drivers/input/misc/da9052_onkey.c new file mode 100644 index 00000000..020569a4 --- /dev/null +++ b/drivers/input/misc/da9052_onkey.c @@ -0,0 +1,158 @@ +/* + * ON pin driver for Dialog DA9052 PMICs + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> + +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/reg.h> + +struct da9052_onkey { + struct da9052 *da9052; + struct input_dev *input; + struct delayed_work work; +}; + +static void da9052_onkey_query(struct da9052_onkey *onkey) +{ + int key_stat; + + key_stat = da9052_reg_read(onkey->da9052, DA9052_EVENT_B_REG); + if (key_stat < 0) { + dev_err(onkey->da9052->dev, + "Failed to read onkey event %d\n", key_stat); + } else { + /* + * Since interrupt for deassertion of ONKEY pin is not + * generated, onkey event state determines the onkey + * button state. + */ + key_stat &= DA9052_EVENTB_ENONKEY; + input_report_key(onkey->input, KEY_POWER, key_stat); + input_sync(onkey->input); + } + + /* + * Interrupt is generated only when the ONKEY pin is asserted. + * Hence the deassertion of the pin is simulated through work queue. + */ + if (key_stat) + schedule_delayed_work(&onkey->work, msecs_to_jiffies(50)); +} + +static void da9052_onkey_work(struct work_struct *work) +{ + struct da9052_onkey *onkey = container_of(work, struct da9052_onkey, + work.work); + + da9052_onkey_query(onkey); +} + +static irqreturn_t da9052_onkey_irq(int irq, void *data) +{ + struct da9052_onkey *onkey = data; + + da9052_onkey_query(onkey); + + return IRQ_HANDLED; +} + +static int da9052_onkey_probe(struct platform_device *pdev) +{ + struct da9052 *da9052 = dev_get_drvdata(pdev->dev.parent); + struct da9052_onkey *onkey; + struct input_dev *input_dev; + int error; + + if (!da9052) { + dev_err(&pdev->dev, "Failed to get the driver's data\n"); + return -EINVAL; + } + + onkey = kzalloc(sizeof(*onkey), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!onkey || !input_dev) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + onkey->input = input_dev; + onkey->da9052 = da9052; + INIT_DELAYED_WORK(&onkey->work, da9052_onkey_work); + + input_dev->name = "da9052-onkey"; + input_dev->phys = "da9052-onkey/input0"; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY); + __set_bit(KEY_POWER, input_dev->keybit); + + error = da9052_request_irq(onkey->da9052, DA9052_IRQ_NONKEY, "ONKEY", + da9052_onkey_irq, onkey); + if (error < 0) { + dev_err(onkey->da9052->dev, + "Failed to register ONKEY IRQ: %d\n", error); + goto err_free_mem; + } + + error = input_register_device(onkey->input); + if (error) { + dev_err(&pdev->dev, "Unable to register input device, %d\n", + error); + goto err_free_irq; + } + + platform_set_drvdata(pdev, onkey); + return 0; + +err_free_irq: + da9052_free_irq(onkey->da9052, DA9052_IRQ_NONKEY, onkey); + cancel_delayed_work_sync(&onkey->work); +err_free_mem: + input_free_device(input_dev); + kfree(onkey); + + return error; +} + +static int da9052_onkey_remove(struct platform_device *pdev) +{ + struct da9052_onkey *onkey = platform_get_drvdata(pdev); + + da9052_free_irq(onkey->da9052, DA9052_IRQ_NONKEY, onkey); + cancel_delayed_work_sync(&onkey->work); + + input_unregister_device(onkey->input); + kfree(onkey); + + return 0; +} + +static struct platform_driver da9052_onkey_driver = { + .probe = da9052_onkey_probe, + .remove = da9052_onkey_remove, + .driver = { + .name = "da9052-onkey", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(da9052_onkey_driver); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("Onkey driver for DA9052"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9052-onkey"); diff --git a/drivers/input/misc/da9055_onkey.c b/drivers/input/misc/da9055_onkey.c new file mode 100644 index 00000000..ee6ae3a0 --- /dev/null +++ b/drivers/input/misc/da9055_onkey.c @@ -0,0 +1,171 @@ +/* + * ON pin driver for Dialog DA9055 PMICs + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/init.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <linux/mfd/da9055/core.h> +#include <linux/mfd/da9055/reg.h> + +struct da9055_onkey { + struct da9055 *da9055; + struct input_dev *input; + struct delayed_work work; +}; + +static void da9055_onkey_query(struct da9055_onkey *onkey) +{ + int key_stat; + + key_stat = da9055_reg_read(onkey->da9055, DA9055_REG_STATUS_A); + if (key_stat < 0) { + dev_err(onkey->da9055->dev, + "Failed to read onkey event %d\n", key_stat); + } else { + key_stat &= DA9055_NOKEY_STS; + /* + * Onkey status bit is cleared when onkey button is relased. + */ + if (!key_stat) { + input_report_key(onkey->input, KEY_POWER, 0); + input_sync(onkey->input); + } + } + + /* + * Interrupt is generated only when the ONKEY pin is asserted. + * Hence the deassertion of the pin is simulated through work queue. + */ + if (key_stat) + schedule_delayed_work(&onkey->work, msecs_to_jiffies(10)); + +} + +static void da9055_onkey_work(struct work_struct *work) +{ + struct da9055_onkey *onkey = container_of(work, struct da9055_onkey, + work.work); + + da9055_onkey_query(onkey); +} + +static irqreturn_t da9055_onkey_irq(int irq, void *data) +{ + struct da9055_onkey *onkey = data; + + input_report_key(onkey->input, KEY_POWER, 1); + input_sync(onkey->input); + + da9055_onkey_query(onkey); + + return IRQ_HANDLED; +} + +static int da9055_onkey_probe(struct platform_device *pdev) +{ + struct da9055 *da9055 = dev_get_drvdata(pdev->dev.parent); + struct da9055_onkey *onkey; + struct input_dev *input_dev; + int irq, err; + + irq = platform_get_irq_byname(pdev, "ONKEY"); + if (irq < 0) { + dev_err(&pdev->dev, + "Failed to get an IRQ for input device, %d\n", irq); + return -EINVAL; + } + + onkey = devm_kzalloc(&pdev->dev, sizeof(*onkey), GFP_KERNEL); + if (!onkey) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&pdev->dev, "Failed to allocate memory\n"); + return -ENOMEM; + } + + onkey->input = input_dev; + onkey->da9055 = da9055; + input_dev->name = "da9055-onkey"; + input_dev->phys = "da9055-onkey/input0"; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_KEY); + __set_bit(KEY_POWER, input_dev->keybit); + + INIT_DELAYED_WORK(&onkey->work, da9055_onkey_work); + + irq = regmap_irq_get_virq(da9055->irq_data, irq); + err = request_threaded_irq(irq, NULL, da9055_onkey_irq, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "ONKEY", onkey); + if (err < 0) { + dev_err(&pdev->dev, + "Failed to register ONKEY IRQ %d, error = %d\n", + irq, err); + goto err_free_input; + } + + err = input_register_device(input_dev); + if (err) { + dev_err(&pdev->dev, "Unable to register input device, %d\n", + err); + goto err_free_irq; + } + + platform_set_drvdata(pdev, onkey); + + return 0; + +err_free_irq: + free_irq(irq, onkey); + cancel_delayed_work_sync(&onkey->work); +err_free_input: + input_free_device(input_dev); + + return err; +} + +static int da9055_onkey_remove(struct platform_device *pdev) +{ + struct da9055_onkey *onkey = platform_get_drvdata(pdev); + int irq = platform_get_irq_byname(pdev, "ONKEY"); + + irq = regmap_irq_get_virq(onkey->da9055->irq_data, irq); + free_irq(irq, onkey); + cancel_delayed_work_sync(&onkey->work); + input_unregister_device(onkey->input); + + return 0; +} + +static struct platform_driver da9055_onkey_driver = { + .probe = da9055_onkey_probe, + .remove = da9055_onkey_remove, + .driver = { + .name = "da9055-onkey", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(da9055_onkey_driver); + +MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); +MODULE_DESCRIPTION("Onkey driver for DA9055"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da9055-onkey"); diff --git a/drivers/input/misc/dm355evm_keys.c b/drivers/input/misc/dm355evm_keys.c new file mode 100644 index 00000000..a309a5c0 --- /dev/null +++ b/drivers/input/misc/dm355evm_keys.c @@ -0,0 +1,273 @@ +/* + * dm355evm_keys.c - support buttons and IR remote on DM355 EVM board + * + * Copyright (c) 2008 by David Brownell + * + * 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. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input/sparse-keymap.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> + +#include <linux/i2c/dm355evm_msp.h> +#include <linux/module.h> + + +/* + * The MSP430 firmware on the DM355 EVM monitors on-board pushbuttons + * and an IR receptor used for the remote control. When any key is + * pressed, or its autorepeat kicks in, an event is sent. This driver + * read those events from the small (32 event) queue and reports them. + * + * Note that physically there can only be one of these devices. + * + * This driver was tested with firmware revision A4. + */ +struct dm355evm_keys { + struct input_dev *input; + struct device *dev; + int irq; +}; + +/* These initial keycodes can be remapped */ +static const struct key_entry dm355evm_keys[] = { + /* + * Pushbuttons on the EVM board ... note that the labels for these + * are SW10/SW11/etc on the PC board. The left/right orientation + * comes only from the firmware's documentation, and presumes the + * power connector is immediately in front of you and the IR sensor + * is to the right. (That is, rotate the board counter-clockwise + * by 90 degrees from the SW10/etc and "DM355 EVM" labels.) + */ + { KE_KEY, 0x00d8, { KEY_OK } }, /* SW12 */ + { KE_KEY, 0x00b8, { KEY_UP } }, /* SW13 */ + { KE_KEY, 0x00e8, { KEY_DOWN } }, /* SW11 */ + { KE_KEY, 0x0078, { KEY_LEFT } }, /* SW14 */ + { KE_KEY, 0x00f0, { KEY_RIGHT } }, /* SW10 */ + + /* + * IR buttons ... codes assigned to match the universal remote + * provided with the EVM (Philips PM4S) using DVD code 0020. + * + * These event codes match firmware documentation, but other + * remote controls could easily send more RC5-encoded events. + * The PM4S manual was used in several cases to help select + * a keycode reflecting the intended usage. + * + * RC5 codes are 14 bits, with two start bits (0x3 prefix) + * and a toggle bit (masked out below). + */ + { KE_KEY, 0x300c, { KEY_POWER } }, /* NOTE: docs omit this */ + { KE_KEY, 0x3000, { KEY_NUMERIC_0 } }, + { KE_KEY, 0x3001, { KEY_NUMERIC_1 } }, + { KE_KEY, 0x3002, { KEY_NUMERIC_2 } }, + { KE_KEY, 0x3003, { KEY_NUMERIC_3 } }, + { KE_KEY, 0x3004, { KEY_NUMERIC_4 } }, + { KE_KEY, 0x3005, { KEY_NUMERIC_5 } }, + { KE_KEY, 0x3006, { KEY_NUMERIC_6 } }, + { KE_KEY, 0x3007, { KEY_NUMERIC_7 } }, + { KE_KEY, 0x3008, { KEY_NUMERIC_8 } }, + { KE_KEY, 0x3009, { KEY_NUMERIC_9 } }, + { KE_KEY, 0x3022, { KEY_ENTER } }, + { KE_KEY, 0x30ec, { KEY_MODE } }, /* "tv/vcr/..." */ + { KE_KEY, 0x300f, { KEY_SELECT } }, /* "info" */ + { KE_KEY, 0x3020, { KEY_CHANNELUP } }, /* "up" */ + { KE_KEY, 0x302e, { KEY_MENU } }, /* "in/out" */ + { KE_KEY, 0x3011, { KEY_VOLUMEDOWN } }, /* "left" */ + { KE_KEY, 0x300d, { KEY_MUTE } }, /* "ok" */ + { KE_KEY, 0x3010, { KEY_VOLUMEUP } }, /* "right" */ + { KE_KEY, 0x301e, { KEY_SUBTITLE } }, /* "cc" */ + { KE_KEY, 0x3021, { KEY_CHANNELDOWN } },/* "down" */ + { KE_KEY, 0x3022, { KEY_PREVIOUS } }, + { KE_KEY, 0x3026, { KEY_SLEEP } }, + { KE_KEY, 0x3172, { KEY_REWIND } }, /* NOTE: docs wrongly say 0x30ca */ + { KE_KEY, 0x3175, { KEY_PLAY } }, + { KE_KEY, 0x3174, { KEY_FASTFORWARD } }, + { KE_KEY, 0x3177, { KEY_RECORD } }, + { KE_KEY, 0x3176, { KEY_STOP } }, + { KE_KEY, 0x3169, { KEY_PAUSE } }, +}; + +/* + * Because we communicate with the MSP430 using I2C, and all I2C calls + * in Linux sleep, we use a threaded IRQ handler. The IRQ itself is + * active low, but we go through the GPIO controller so we can trigger + * on falling edges and not worry about enabling/disabling the IRQ in + * the keypress handling path. + */ +static irqreturn_t dm355evm_keys_irq(int irq, void *_keys) +{ + static u16 last_event; + struct dm355evm_keys *keys = _keys; + const struct key_entry *ke; + unsigned int keycode; + int status; + u16 event; + + /* For simplicity we ignore INPUT_COUNT and just read + * events until we get the "queue empty" indicator. + * Reading INPUT_LOW decrements the count. + */ + for (;;) { + status = dm355evm_msp_read(DM355EVM_MSP_INPUT_HIGH); + if (status < 0) { + dev_dbg(keys->dev, "input high err %d\n", + status); + break; + } + event = status << 8; + + status = dm355evm_msp_read(DM355EVM_MSP_INPUT_LOW); + if (status < 0) { + dev_dbg(keys->dev, "input low err %d\n", + status); + break; + } + event |= status; + if (event == 0xdead) + break; + + /* Press and release a button: two events, same code. + * Press and hold (autorepeat), then release: N events + * (N > 2), same code. For RC5 buttons the toggle bits + * distinguish (for example) "1-autorepeat" from "1 1"; + * but PCB buttons don't support that bit. + * + * So we must synthesize release events. We do that by + * mapping events to a press/release event pair; then + * to avoid adding extra events, skip the second event + * of each pair. + */ + if (event == last_event) { + last_event = 0; + continue; + } + last_event = event; + + /* ignore the RC5 toggle bit */ + event &= ~0x0800; + + /* find the key, or report it as unknown */ + ke = sparse_keymap_entry_from_scancode(keys->input, event); + keycode = ke ? ke->keycode : KEY_UNKNOWN; + dev_dbg(keys->dev, + "input event 0x%04x--> keycode %d\n", + event, keycode); + + /* report press + release */ + input_report_key(keys->input, keycode, 1); + input_sync(keys->input); + input_report_key(keys->input, keycode, 0); + input_sync(keys->input); + } + + return IRQ_HANDLED; +} + +/*----------------------------------------------------------------------*/ + +static int dm355evm_keys_probe(struct platform_device *pdev) +{ + struct dm355evm_keys *keys; + struct input_dev *input; + int status; + + /* allocate instance struct and input dev */ + keys = kzalloc(sizeof *keys, GFP_KERNEL); + input = input_allocate_device(); + if (!keys || !input) { + status = -ENOMEM; + goto fail1; + } + + keys->dev = &pdev->dev; + keys->input = input; + + /* set up "threaded IRQ handler" */ + status = platform_get_irq(pdev, 0); + if (status < 0) + goto fail1; + keys->irq = status; + + input_set_drvdata(input, keys); + + input->name = "DM355 EVM Controls"; + input->phys = "dm355evm/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_I2C; + input->id.product = 0x0355; + input->id.version = dm355evm_msp_read(DM355EVM_MSP_FIRMREV); + + status = sparse_keymap_setup(input, dm355evm_keys, NULL); + if (status) + goto fail1; + + /* REVISIT: flush the event queue? */ + + status = request_threaded_irq(keys->irq, NULL, dm355evm_keys_irq, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(&pdev->dev), keys); + if (status < 0) + goto fail2; + + /* register */ + status = input_register_device(input); + if (status < 0) + goto fail3; + + platform_set_drvdata(pdev, keys); + + return 0; + +fail3: + free_irq(keys->irq, keys); +fail2: + sparse_keymap_free(input); +fail1: + input_free_device(input); + kfree(keys); + dev_err(&pdev->dev, "can't register, err %d\n", status); + + return status; +} + +static int dm355evm_keys_remove(struct platform_device *pdev) +{ + struct dm355evm_keys *keys = platform_get_drvdata(pdev); + + free_irq(keys->irq, keys); + sparse_keymap_free(keys->input); + input_unregister_device(keys->input); + kfree(keys); + + return 0; +} + +/* REVISIT: add suspend/resume when DaVinci supports it. The IRQ should + * be able to wake up the system. When device_may_wakeup(&pdev->dev), call + * enable_irq_wake() on suspend, and disable_irq_wake() on resume. + */ + +/* + * I2C is used to talk to the MSP430, but this platform device is + * exposed by an MFD driver that manages I2C communications. + */ +static struct platform_driver dm355evm_keys_driver = { + .probe = dm355evm_keys_probe, + .remove = dm355evm_keys_remove, + .driver = { + .owner = THIS_MODULE, + .name = "dm355evm_keys", + }, +}; +module_platform_driver(dm355evm_keys_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/epl2182_pls_v2.c b/drivers/input/misc/epl2182_pls_v2.c new file mode 100644 index 00000000..6d14770c --- /dev/null +++ b/drivers/input/misc/epl2182_pls_v2.c @@ -0,0 +1,1496 @@ +/* drivers/i2c/chips/epl2182.c - light and proxmity sensors driver + * Copyright (C) 2011 ELAN Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/hrtimer.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/earlysuspend.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +//#include <asm/mach-types.h> +#include <asm/setup.h> +#include <linux/wakelock.h> +#include <linux/jiffies.h> +#include <linux/regulator/consumer.h> +#include <linux/platform_device.h> +#include <linux/poll.h> +#include <linux/i2c/epl2182_pls_v2.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> + +/****************************************************************************** + * configuration +*******************************************************************************/ +#define PS_INTERRUPT_MODE 1 // 0 is polling mode, 1 is interrupt mode +#define PLSENSOR_ADAPTIVE +#ifdef PLSENSOR_ADAPTIVE +/*ensure the initial value can enable Psensor interrupt,the ps_state of the initial value will be 0*/ +static u16 P_SENSOR_LTHD = 0x7fff; //init +static u16 P_SENSOR_HTHD = 0x0; //init +#else +#define P_SENSOR_LTHD 1900//120 //100 +#define P_SENSOR_HTHD 2000//170 //500 +#endif + +#define PS_POLLING_RATE 100 +#define ALS_POLLING_RATE 1000 + +#define LUX_PER_COUNT 440 // 660 = 1.1*0.6*1000 +#ifdef PLSENSOR_ADAPTIVE +#define DEBOUNCE 30 +#define MIN_SPACING 250 +#define DIVEDE 4 +static bool first_do_irq = true; +static u16 ps_min = 0; +static u16 ps_max = 0; +static u16 ps_threshold = 0; +#endif +/****************************************************************************** + * configuration +*******************************************************************************/ + +typedef enum +{ + CMC_MODE_ALS = 0x00, + CMC_MODE_PS = 0x10, +} CMC_MODE; + +#define TXBYTES 2 +#define RXBYTES 2 + +#define PS_DELAY 50 +#define ALS_DELAY 55 + +#define PACKAGE_SIZE 2 +#define I2C_RETRY_COUNT 10 +#define P_INTT 1 + +#define PS_INTT 4 +#define ALS_INTT 6 //5-8 + +static int set_psensor_intr_threshold(uint16_t low_thd, uint16_t high_thd); + +#if PS_INTERRUPT_MODE +static void epl_sensor_irq_do_work(struct work_struct *work); +static DECLARE_WORK(epl_sensor_irq_work, epl_sensor_irq_do_work); +#endif + +static void report_polling_do_work(struct work_struct *work); +static DECLARE_DELAYED_WORK(report_polling_work, report_polling_do_work); +static void polling_do_work(struct work_struct *work); +static DECLARE_DELAYED_WORK(polling_work, polling_do_work); + +/* primitive raw data from I2C */ +typedef struct _epl_raw_data +{ + u8 raw_bytes[PACKAGE_SIZE]; + u16 ps_state; + u16 ps_int_state; + u16 ps_ch1_raw; + u16 als_ch1_raw; +} epl_raw_data; + +struct elan_epl_data +{ + struct i2c_client *client; + //struct input_dev *als_input_dev; + struct input_dev *ps_input_dev; + struct workqueue_struct *epl_wq; + struct early_suspend early_suspend; + + int intr_pin; + //int (*power)(int on); + + int ps_opened; + int als_opened; + + int enable_pflag; + int enable_lflag; + int read_flag; + int irq; +} ; + +static DECLARE_WAIT_QUEUE_HEAD(ps_waitqueue); +static DECLARE_WAIT_QUEUE_HEAD(ls_waitqueue); + + +static int ps_data_changed; +static int ls_data_changed; +static struct i2c_client *this_client = NULL; + +//static struct wake_lock g_ps_wlock; +struct elan_epl_data *epl_data; +static epl_raw_data gRawData; +static int dual_count; + +static const char ElanPsensorName[] = "proximity"; +static const char ElanALsensorName[] = "lightsensor-level"; + +static int psensor_mode_suspend = 0; + +#define LOG_TAG "[EPL2182] " +#define LOG_FUN(f) printk(KERN_INFO LOG_TAG"%s\n", __FUNCTION__) +#define LOG_INFO(fmt, args...) printk(KERN_INFO LOG_TAG fmt, ##args) +#define LOG_ERR(fmt, args...) printk(KERN_ERR LOG_TAG"%s %d : "fmt, __FUNCTION__, __LINE__, ##args) + +/* +//====================I2C write operation===============// +//regaddr: ELAN Register Address. +//bytecount: How many bytes to be written to register via i2c bus. +//txbyte: I2C bus transmit byte(s). Single byte(0X01) transmit only slave address. +//data: setting value. +// +// Example: If you want to write single byte to 0x1D register address, show below +// elan_sensor_I2C_Write(client,0x1D,0x01,0X02,0xff); +// +*/ +static int elan_sensor_I2C_Write(struct i2c_client *client, uint8_t regaddr, uint8_t bytecount, uint8_t txbyte, uint8_t data) +{ + uint8_t buffer[2]; + int ret = 0; + int retry,val; + struct elan_epl_data *epld = epl_data; + + buffer[0] = (regaddr<<3) | bytecount ; + buffer[1] = data; + + for(retry = 0; retry < I2C_RETRY_COUNT; retry++) + { + ret = i2c_master_send(client, buffer, txbyte); + + if (ret == txbyte) + { + break; + } + + val = gpio_get_value(epld->intr_pin); + + LOG_INFO("INTERRUPT GPIO val = %d\n", val); + + msleep(10); + } + + if(retry>=I2C_RETRY_COUNT) + { + LOG_ERR(KERN_ERR "i2c write retry over %d\n", I2C_RETRY_COUNT); + return -EINVAL; + } + + return ret; +} + +static int elan_sensor_I2C_Read(struct i2c_client *client) +{ + uint8_t buffer[RXBYTES]; + int ret = 0, i =0; + int retry,val; + struct elan_epl_data *epld = epl_data; + + for(retry = 0; retry < I2C_RETRY_COUNT; retry++) + { + ret = i2c_master_recv(client, buffer, RXBYTES); + + if (ret == RXBYTES) + break; + + val = gpio_get_value(epld->intr_pin); + + LOG_INFO("INTERRUPT GPIO val = %d\n", val); + + msleep(10); + } + + if(retry>=I2C_RETRY_COUNT) + { + LOG_ERR("i2c read retry over %d\n", I2C_RETRY_COUNT); + return -EINVAL; + } + + for(i=0; i<PACKAGE_SIZE; i++) + { + gRawData.raw_bytes[i] = buffer[i]; + } + + return ret; +} + +static void elan_sensor_restart_work(void) +{ + struct elan_epl_data *epld = epl_data; + cancel_delayed_work(&polling_work); + cancel_delayed_work(&report_polling_work); + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(10)); +} + +static int elan_sensor_psensor_enable(struct elan_epl_data *epld) +{ + int ret; + uint8_t regdata = 0; + struct i2c_client *client = epld->client; + + LOG_INFO("--- Proximity sensor Enable --- \n"); + + //disable_irq(epld->irq); + ret = elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_DISABLE); + + regdata = EPL_SENSING_8_TIME | EPL_PS_MODE | EPL_L_GAIN ; + regdata = regdata | (PS_INTERRUPT_MODE ? EPL_C_SENSING_MODE : EPL_S_SENSING_MODE); + ret = elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0X02,regdata); + + regdata = PS_INTT<<4 | EPL_PST_1_TIME | EPL_10BIT_ADC; + ret = elan_sensor_I2C_Write(client,REG_1,W_SINGLE_BYTE,0X02,regdata); + + set_psensor_intr_threshold(P_SENSOR_LTHD ,P_SENSOR_HTHD); +#ifdef PLSENSOR_ADAPTIVE + if (true == first_do_irq) { + msleep(PS_DELAY); + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + gRawData.ps_state= !((gRawData.raw_bytes[0]&0x04)>>2); + elan_sensor_I2C_Write(client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(client); + gRawData.ps_ch1_raw = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + P_SENSOR_LTHD = gRawData.ps_ch1_raw + MIN_SPACING - DEBOUNCE; + P_SENSOR_HTHD = gRawData.ps_ch1_raw + MIN_SPACING - DEBOUNCE; + set_psensor_intr_threshold(P_SENSOR_LTHD ,P_SENSOR_HTHD); + } +#endif + + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0X02,EPL_C_RESET); + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0x02,EPL_C_START_RUN); + +#if PS_INTERRUPT_MODE + if(epld->enable_lflag) + { + msleep(PS_DELAY); + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + gRawData.ps_state= !((gRawData.raw_bytes[0]&0x04)>>2); + + if(gRawData.ps_state!= gRawData.ps_int_state) + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_FRAME_ENABLE); + } + else + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_ACTIVE_LOW); + } + + } + else + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_ACTIVE_LOW); + } + //enable_irq(epld->irq); +#endif + + if (ret != 0x02) + { + LOG_INFO("P-sensor i2c err\n"); + } + + return ret; +} + +static int elan_sensor_lsensor_enable(struct elan_epl_data *epld) +{ + int ret; + uint8_t regdata = 0; + struct i2c_client *client = epld->client; + + LOG_INFO("--- ALS sensor Enable --- \n"); + + //disable_irq(epld->irq); + regdata = EPL_INT_DISABLE; + ret = elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02, regdata); + + regdata = EPL_S_SENSING_MODE | EPL_SENSING_8_TIME | EPL_ALS_MODE | EPL_AUTO_GAIN; + ret = elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0X02,regdata); + + regdata = ALS_INTT<<4 | EPL_PST_1_TIME | EPL_10BIT_ADC; + ret = elan_sensor_I2C_Write(client,REG_1,W_SINGLE_BYTE,0X02,regdata); + + ret = elan_sensor_I2C_Write(client,REG_10,W_SINGLE_BYTE,0x02, EPL_GO_MID); + ret = elan_sensor_I2C_Write(client,REG_11,W_SINGLE_BYTE,0x02, EPL_GO_LOW); + + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0X02,EPL_C_RESET); + ret = elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0x02,EPL_C_START_RUN); + + if(ret != 0x02) + { + LOG_INFO(" ALS-sensor i2c err\n"); + } + + return ret; + +} + +/* +//====================elan_epl_ps_poll_rawdata===============// +//polling method for proximity sensor detect. Report proximity sensor raw data. +//Report "ABS_DISTANCE" event to HAL layer. +//Variable "value" 0 and 1 to represent which distance from psensor to target(human's face..etc). +//value: 0 represent near. +//value: 1 represent far. +*/ +static int elan_epl_ps_poll_rawdata(void) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + u16 value = 0; + + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_LOCK); + + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + gRawData.ps_state= !((gRawData.raw_bytes[0]&0x04)>>2); + + elan_sensor_I2C_Write(client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(client); + gRawData.ps_ch1_raw = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + +#ifdef PLSENSOR_ADAPTIVE + value = gRawData.ps_ch1_raw; + /*threshold detect process*/ + if(0 == ps_max || 0 == ps_min) { + ps_min = value; + ps_max = value; + ps_threshold = ps_min + MIN_SPACING; + } + if(value > ps_max) { + ps_max = value; + ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min; + if(ps_threshold - ps_min < MIN_SPACING) + ps_threshold = ps_min + MIN_SPACING; + } else if(value < ps_min) { + ps_min = value; + ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min; + if(ps_threshold - ps_min < MIN_SPACING) + ps_threshold = ps_min + MIN_SPACING; + } + P_SENSOR_LTHD = ps_threshold - DEBOUNCE; + P_SENSOR_HTHD = ps_threshold + DEBOUNCE; + set_psensor_intr_threshold(P_SENSOR_LTHD ,P_SENSOR_HTHD); + /*threshold detect process end*/ + LOG_INFO("ps_min (%4d), ps_max (%4d), ps_threshold (%4d)\n", ps_min, ps_max, ps_threshold); + if (true == first_do_irq) + first_do_irq = false; +#endif + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_UNLOCK); + LOG_INFO("### ps_ch1_raw_data (%d), value(%d) ###\n", gRawData.ps_ch1_raw, gRawData.ps_state); + ps_data_changed = 1; + + input_report_abs(epld->ps_input_dev, ABS_DISTANCE, gRawData.ps_state); + input_sync(epld->ps_input_dev); + return 0; +} + +static void elan_epl_als_rawdata(void) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + uint32_t lux; + + elan_sensor_I2C_Write(client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(client); + gRawData.als_ch1_raw = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + + lux = (gRawData.als_ch1_raw* LUX_PER_COUNT) / 1000 * 15 / 100; + if(lux>20000) + lux=20000; + + LOG_INFO("------------------- ALS raw = %d, lux = %d\n\n", gRawData.als_ch1_raw, lux); + ls_data_changed = 1; +#if 1 + input_report_abs(epld->ps_input_dev, ABS_MISC, lux); + input_sync(epld->ps_input_dev); +#else + input_report_abs(epld->als_input_dev, ABS_MISC, lux); + input_sync(epld->als_input_dev); +#endif +} + +/* +//====================set_psensor_intr_threshold===============// +//low_thd: The value is psensor interrupt low threshold. +//high_thd: The value is psensor interrupt hihg threshold. +//When psensor rawdata > hihg_threshold, interrupt pin will be pulled low. +//After interrupt occur, psensor rawdata < low_threshold, interrupt pin will be pulled high. +*/ +static int set_psensor_intr_threshold(uint16_t low_thd, uint16_t high_thd) +{ + int ret = 0; + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + + uint8_t high_msb ,high_lsb, low_msb, low_lsb; + + high_msb = (uint8_t) (high_thd >> 8); + high_lsb = (uint8_t) (high_thd & 0x00ff); + low_msb = (uint8_t) (low_thd >> 8); + low_lsb = (uint8_t) (low_thd & 0x00ff); + + elan_sensor_I2C_Write(client,REG_2,W_SINGLE_BYTE,0x02,high_lsb); + elan_sensor_I2C_Write(client,REG_3,W_SINGLE_BYTE,0x02,high_msb); + elan_sensor_I2C_Write(client,REG_4,W_SINGLE_BYTE,0x02,low_lsb); + elan_sensor_I2C_Write(client,REG_5,W_SINGLE_BYTE,0x02,low_msb); + + return ret; +} + +#if PS_INTERRUPT_MODE +static void epl_sensor_irq_do_work(struct work_struct *work) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + int mode = 0; + LOG_FUN(); + + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_LOCK); + + elan_sensor_I2C_Write(client,REG_13,R_SINGLE_BYTE,0x01,0); + elan_sensor_I2C_Read(client); + mode = gRawData.raw_bytes[0]&(3<<4); + + // 0x10 is ps mode + if(mode==CMC_MODE_PS && epld->enable_pflag) + { + gRawData.ps_int_state= !((gRawData.raw_bytes[0]&0x04)>>2); + elan_epl_ps_poll_rawdata(); + } + else + { + LOG_INFO("interrupt in als\n"); + } + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_ACTIVE_LOW); + elan_sensor_I2C_Write(client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_UNLOCK); + enable_irq(epld->irq); +} + +static irqreturn_t elan_sensor_irq_handler(int irqNo, void *handle) +{ + struct elan_epl_data *epld = (struct elan_epl_data*)handle; + + disable_irq_nosync(epld->irq); + queue_work(epld->epl_wq, &epl_sensor_irq_work); + + return IRQ_HANDLED; +} +#endif + +static void report_polling_do_work(struct work_struct *work) +{ + struct elan_epl_data *epld = epl_data; + + if(dual_count==CMC_MODE_PS) + { + elan_epl_ps_poll_rawdata(); + } + else if(dual_count==CMC_MODE_ALS) + { + elan_epl_als_rawdata(); + } + + if(epld->enable_pflag) + { + elan_sensor_psensor_enable(epld); + + if(PS_INTERRUPT_MODE==0) + { + dual_count=CMC_MODE_PS; // ps mode + queue_delayed_work(epld->epl_wq, &report_polling_work,msecs_to_jiffies(PS_POLLING_RATE)); + } + } +} + +static void polling_do_work(struct work_struct *work) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + + bool isInterleaving = epld->enable_pflag==1 && epld->enable_lflag==1; + bool isAlsOnly = epld->enable_pflag==0 && epld->enable_lflag==1; + bool isPsOnly = epld->enable_pflag==1 && epld->enable_lflag==0; + + cancel_delayed_work(&polling_work); + cancel_delayed_work(&report_polling_work); + + LOG_INFO("enable_pflag = %d, enable_lflag = %d\n", epld->enable_pflag, epld->enable_lflag); + + if(isAlsOnly || isInterleaving) + { + elan_sensor_lsensor_enable(epld); + dual_count = CMC_MODE_ALS; + + queue_delayed_work(epld->epl_wq, &report_polling_work,msecs_to_jiffies(ALS_DELAY)); + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(ALS_POLLING_RATE)); + } + else if(isPsOnly) + { + elan_sensor_psensor_enable(epld); + dual_count = CMC_MODE_PS; + + if(PS_INTERRUPT_MODE) + { + // do nothing + } + else + { + queue_delayed_work(epld->epl_wq, &report_polling_work,msecs_to_jiffies(PS_DELAY)); + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(PS_POLLING_RATE)); + } + } + else + { + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_DISABLE); + elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0X02,EPL_S_SENSING_MODE); + } +} + +#if 0 +static ssize_t elan_ls_operationmode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + uint16_t mode=0; + struct elan_epl_data *epld = epl_data; + LOG_FUN(); + + sscanf(buf, "%hu",&mode); + LOG_INFO("==>[operation mode]=%d\n", mode); + + if(mode == 0) + { + epld->enable_lflag = 0; + epld->enable_pflag = 0; + } + else if(mode == 1) + { + epld->enable_lflag = 1; + epld->enable_pflag = 0; + } + else if(mode == 2) + { + epld->enable_lflag = 0; + epld->enable_pflag = 1; + } + else if(mode == 3) + { + epld->enable_lflag = 1; + epld->enable_pflag = 1; + } + else + { + LOG_INFO("0: none\n1: als only\n2: ps only\n3: interleaving"); + } + + elan_sensor_restart_work(); + return count; +} + +static ssize_t elan_ls_operationmode_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + long *tmp = (long*)buf; + uint16_t mode =0; + LOG_FUN(); + + if( epld->enable_pflag==0 && epld->enable_lflag==0) + { + mode = 0; + } + else if( epld->enable_pflag==0 && epld->enable_lflag==1) + { + mode = 1; + } + else if( epld->enable_pflag==1 && epld->enable_lflag==0) + { + mode = 2; + } + else if( epld->enable_pflag==1 && epld->enable_lflag==1) + { + mode = 3; + } + tmp[0] = mode; + return sprintf(buf, "%d \n", mode); +} + +static ssize_t elan_ls_status_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + u16 ch1; + + if(!epl_data) + { + LOG_INFO("epl_data is null!!\n"); + return 0; + } + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_LOCK); + + elan_sensor_I2C_Write(epld->client,REG_16,R_TWO_BYTE,0x01,0x00); + elan_sensor_I2C_Read(epld->client); + ch1 = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + LOG_INFO("ch1 raw_data = %d\n", ch1); + + elan_sensor_I2C_Write(epld->client,REG_7,W_SINGLE_BYTE,0x02,EPL_DATA_UNLOCK); + + return sprintf(buf, "%d\n", ch1); +} + +static DEVICE_ATTR(elan_ls_operationmode, S_IROTH|S_IWOTH, elan_ls_operationmode_show,elan_ls_operationmode_store); +static DEVICE_ATTR(elan_ls_status, S_IROTH|S_IWOTH, elan_ls_status_show,NULL); + +static struct attribute *ets_attributes[] = +{ + &dev_attr_elan_ls_operationmode.attr, + &dev_attr_elan_ls_status.attr, + NULL, +}; + +static struct attribute_group ets_attr_group = +{ + .attrs = ets_attributes, +}; +#endif + +#if 0 //ices add +static int elan_als_open(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + if (epld->als_opened) + { + return -EBUSY; + } + epld->als_opened = 1; + + return 0; +} + +static int elan_als_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct elan_epl_data *epld = epl_data; + int buf[1]; + if(epld->read_flag ==1) + { + buf[0] = gRawData.als_ch1_raw; + if(copy_to_user(buffer, &buf , sizeof(buf))) + return 0; + epld->read_flag = 0; + return 12; + } + else + { + return 0; + } +} + +static int elan_als_release(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + epld->als_opened = 0; + + return 0; +} +#if 1 +static long elan_als_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int flag; + unsigned long buf[1]; + struct elan_epl_data *epld = epl_data; + + void __user *argp = (void __user *)arg; + + LOG_INFO("als io ctrl cmd %d\n", _IOC_NR(cmd)); + + switch(cmd) + { + case ELAN_EPL6800_IOCTL_GET_LFLAG: + + LOG_INFO("elan ambient-light IOCTL Sensor get lflag \n"); + flag = epld->enable_lflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + + LOG_INFO("elan ambient-light Sensor get lflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_ENABLE_LFLAG: + + LOG_INFO("elan ambient-light IOCTL Sensor set lflag \n"); + if (copy_from_user(&flag, argp, 1)) + return -EFAULT; + LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + if(flag) { + flag = 1; + }else { + flag = 0; + } + //if (flag < 0 || flag > 1) + // return -EINVAL; + + epld->enable_lflag = flag; + elan_sensor_restart_work(); + + //LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_GETDATA: + buf[0] = (unsigned long)gRawData.als_ch1_raw; + if(copy_to_user(argp, &buf , 2)) + return -EFAULT; + + break; + + default: + LOG_ERR("invalid cmd %d\n", _IOC_NR(cmd)); + return -EINVAL; + } + + return 0; + +} +#endif +static unsigned int elan_als_poll(struct file *fp, poll_table * wait) +{ + if(ls_data_changed){ + //pr_info("%s, ls_data_changed 1st, ls_data = 0x%x\n", __func__, ls_data); + ls_data_changed = 0; + //mutex_unlock(&stk3x1x_mutex); + return POLLIN | POLLRDNORM; + } + poll_wait(fp, &ls_waitqueue, wait); + //mutex_unlock(&stk3x1x_mutex); + return 0; +} + +static struct file_operations elan_als_fops = +{ + .owner = THIS_MODULE, + .open = elan_als_open, + .read = elan_als_read, + .release = elan_als_release, + .unlocked_ioctl = elan_als_ioctl, + .poll = elan_als_poll, +}; + +static struct miscdevice elan_als_device = +{ + .minor = MISC_DYNAMIC_MINOR, + .name = "elan_als", + .fops = &elan_als_fops +}; +#endif //ices end + +static int elan_ps_open(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + if (epld->ps_opened) + return -EBUSY; + + epld->ps_opened = 1; + + return 0; +} + +static int elan_ps_release(struct inode *inode, struct file *file) +{ + struct elan_epl_data *epld = epl_data; + + LOG_FUN(); + + epld->ps_opened = 0; + + psensor_mode_suspend = 0; + + return 0; +} + +static int epl2182_read_chip_info(struct i2c_client *client, char *buf) +{ + if(NULL == buf) { + return -1; + } + if(NULL == client) { + *buf = 0; + return -2; + } + + sprintf(buf, "EPL2182"); + printk("[EPL2182] epl2182_read_chip_info %s\n",buf); + return 0; +} + +static long elan_ps_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int flag, err; + unsigned long buf; + char strbuf[256]; + struct elan_epl_data *epld = epl_data; + void __user *argp = (void __user *)arg; + + LOG_INFO("ps io ctrl cmd %d\n", _IOC_NR(cmd)); + //ioctl message handle must define by android sensor library (case by case) + switch(cmd) + { + case ELAN_EPL6800_IOCTL_GET_PFLAG: + flag = epld->enable_pflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan Proximity Sensor get pflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_ENABLE_PFLAG: + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan Proximity Sensor set pflag %d\n",flag); + if(flag) { + flag = 1; + }else { + flag = 0; + } + epld->enable_pflag = flag; + elan_sensor_restart_work(); + break; + + case ELAN_EPL6800_IOCTL_GET_CHIPINFO: + err = epl2182_read_chip_info(this_client, strbuf); + if(err < 0) + return -EFAULT; + if(copy_to_user(argp, strbuf, strlen(strbuf)+1)) + return -EFAULT; + break; + + case ELAN_EPL6800_IOCTL_GET_LFLAG: + flag = epld->enable_lflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan ambient-light Sensor get lflag %d\n",flag); + break; + + case ELAN_EPL6800_IOCTL_ENABLE_LFLAG: + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + if(flag) { + flag = 1; + }else { + flag = 0; + } + epld->enable_lflag = flag; + elan_sensor_restart_work(); + break; + + case ELAN_EPL6800_IOCTL_GETDATA: + buf = (unsigned long)gRawData.als_ch1_raw; + if(copy_to_user(argp, &buf , sizeof(buf))) + return -EFAULT; + LOG_INFO("elan als Sensor get data (%lu)\n",buf); + break; + + default: + LOG_ERR("invalid cmd %d\n", _IOC_NR(cmd)); + return -EINVAL; + } + + return 0; +} + +static unsigned int elan_ps_poll(struct file *fp, poll_table * wait) +{ + if(ps_data_changed) { + ps_data_changed = 0; + return POLLIN | POLLRDNORM; + } + poll_wait(fp, &ps_waitqueue, wait); + return 0; +} + + +static struct file_operations elan_ps_fops = +{ + .owner = THIS_MODULE, + .open = elan_ps_open, + .release = elan_ps_release, + .unlocked_ioctl = elan_ps_ioctl, + .poll = elan_ps_poll, +}; + +static struct miscdevice elan_ps_device = +{ + .minor = MISC_DYNAMIC_MINOR, + .name = EPL2182_PLS_DEVICE, + .fops = &elan_ps_fops +}; + +static int initial_sensor(struct elan_epl_data *epld) +{ + struct i2c_client *client = epld->client; + + int ret = 0; + + LOG_INFO("initial_sensor enter!\n"); + + ret = elan_sensor_I2C_Read(client); + + if(ret < 0) + return -EINVAL; + + elan_sensor_I2C_Write(client,REG_0,W_SINGLE_BYTE,0x02, EPL_S_SENSING_MODE); + elan_sensor_I2C_Write(client,REG_9,W_SINGLE_BYTE,0x02,EPL_INT_DISABLE); + set_psensor_intr_threshold(P_SENSOR_LTHD , P_SENSOR_HTHD); + + msleep(2); + + epld->enable_lflag = 0; + epld->enable_pflag = 0; + + return ret; +} +#if 0 //ices add +/*----------------------------------------------------------------------------*/ +static ssize_t light_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + printk("%s: ALS_status=%d\n", __func__, epld->enable_lflag); + return sprintf(buf, "%d\n", epld->enable_lflag); +} + +static ssize_t light_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct elan_epl_data *epld = epl_data; + + LOG_INFO("light_enable_store: enable=%s \n", buf); + + if (sysfs_streq(buf, "1")) + epld->enable_lflag= 1; + else if (sysfs_streq(buf, "0")) + epld->enable_lflag= 0; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return 0; + } + + elan_sensor_restart_work(); + return size; +} + +/*----------------------------------------------------------------------------*/ +static struct device_attribute dev_attr_light_enable = +__ATTR(enable, S_IRWXUGO, + light_enable_show, light_enable_store); + +static struct attribute *light_sysfs_attrs[] = { + &dev_attr_light_enable.attr, + NULL +}; + +static struct attribute_group light_attribute_group = { + .attrs = light_sysfs_attrs, +}; +/*----------------------------------------------------------------------------*/ + +static int lightsensor_setup(struct elan_epl_data *epld) +{ + int err = 0; + LOG_INFO("lightsensor_setup enter.\n"); + + epld->als_input_dev = input_allocate_device(); + if (!epld->als_input_dev) + { + LOG_ERR( "could not allocate ls input device\n"); + return -ENOMEM; + } + epld->als_input_dev->name = ElanALsensorName; + set_bit(EV_ABS, epld->als_input_dev->evbit); + input_set_abs_params(epld->als_input_dev, ABS_MISC, 0, 9, 0, 0); + + err = input_register_device(epld->als_input_dev); + if (err < 0) + { + LOG_ERR("can not register ls input device\n"); + goto err_free_ls_input_device; + } +#if 0 //ices add + err = misc_register(&elan_als_device); + if (err < 0) + { + LOG_ERR("can not register ls misc device\n"); + goto err_unregister_ls_input_device; + } +#endif //ices end + err = sysfs_create_group(&epld->als_input_dev->dev.kobj, &light_attribute_group); + + if (err) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_free_ls_input_device; + } + + return err; + +err_unregister_ls_input_device: + input_unregister_device(epld->als_input_dev); +err_free_ls_input_device: + input_free_device(epld->als_input_dev); + return err; +} +#endif +/*----------------------------------------------------------------------------*/ +static ssize_t proximity_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct elan_epl_data *epld = epl_data; + printk("%s: PS status=%d\n", __func__, epld->enable_pflag); + LOG_INFO("epl2182_setup_psensor enter.\n"); + return sprintf(buf, "%d\n", epld->enable_pflag); +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static ssize_t proximity_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + struct elan_epl_data *epld = epl_data; + + LOG_INFO("proximity_enable_store: enable=%s \n", buf); + if (sysfs_streq(buf, "1")) + epld->enable_pflag =1; + else if (sysfs_streq(buf, "0")) + epld->enable_pflag =0; + else { + pr_err("%s: invalid value %d\n", __func__, *buf); + return 0; + } + + elan_sensor_restart_work(); + return size; +} +/*----------------------------------------------------------------------------*/ +static struct device_attribute dev_attr_ps_enable = +__ATTR(enable, S_IRWXUGO, + proximity_enable_show, proximity_enable_store); + +static struct attribute *proximity_sysfs_attrs[] = { + &dev_attr_ps_enable.attr, + NULL +}; + +static struct attribute_group proximity_attribute_group = { + .attrs = proximity_sysfs_attrs, +}; +/*----------------------------------------------------------------------------*/ + +static int psensor_setup(struct elan_epl_data *epld) +{ + int err = 0; + LOG_INFO("psensor_setup enter.\n"); + + epld->ps_input_dev = input_allocate_device(); + if (!epld->ps_input_dev) + { + LOG_ERR("could not allocate ps input device\n"); + return -ENOMEM; + } + epld->ps_input_dev->name = ElanPsensorName; + + set_bit(EV_ABS, epld->ps_input_dev->evbit); + input_set_abs_params(epld->ps_input_dev, ABS_DISTANCE, 0, 1, 0, 0); +#if 1 + set_bit(EV_ABS, epld->ps_input_dev->evbit); + input_set_abs_params(epld->ps_input_dev, ABS_MISC, 0, 9, 0, 0); +#endif + + err = input_register_device(epld->ps_input_dev); + if (err < 0) + { + LOG_ERR("could not register ps input device\n"); + goto err_free_ps_input_device; + } + + err = misc_register(&elan_ps_device); + if (err < 0) + { + LOG_ERR("could not register ps misc device\n"); + goto err_unregister_ps_input_device; + } + + err = sysfs_create_group(&epld->ps_input_dev->dev.kobj, &proximity_attribute_group); + if (err) { + pr_err("%s: PS could not create sysfs group\n", __func__); + goto err_free_ps_input_device; + } + + return err; + +err_unregister_ps_input_device: + input_unregister_device(epld->ps_input_dev); +err_free_ps_input_device: + input_free_device(epld->ps_input_dev); + return err; +} + +#if PS_INTERRUPT_MODE +static int setup_interrupt(struct elan_epl_data *epld) +{ + struct i2c_client *client = epld->client; + struct elan_epl_platform_data *pdata = client->dev.platform_data; + + int err = 0; + msleep(5); +#if 1 + err = gpio_request(pdata->irq_gpio_number, "Elan EPL IRQ"); + if (err) + { + LOG_ERR("gpio pin request fail (%d)\n", err); + goto initial_fail; + } + else + { + + gpio_direction_input(pdata->irq_gpio_number); + + /*get irq*/ + client->irq = gpio_to_irq(pdata->irq_gpio_number); + epld->irq = client->irq; + + printk("IRQ number is %d\n", client->irq); + + } +#endif + err = request_irq(epld->irq,elan_sensor_irq_handler, IRQF_TRIGGER_FALLING | IRQF_NO_SUSPEND , + client->dev.driver->name, epld); + if(err <0) + { + LOG_ERR("request irq pin %d fail for gpio\n",err); + goto fail_free_intr_pin; + } + + return err; + +initial_fail: +fail_free_intr_pin: + gpio_free(epld->intr_pin); + return err; +} +#endif + +#ifdef CONFIG_SUSPEND +static int elan_sensor_suspend(struct i2c_client *client, pm_message_t mesg) +{ + LOG_FUN(); + + if(epl_data->enable_pflag==0 ) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_DOWN); + cancel_delayed_work(&polling_work); + } + return 0; +} + +static int elan_sensor_resume(struct i2c_client *client) +{ + struct elan_epl_data *epld = epl_data; + LOG_FUN(); + + if(epld->enable_pflag | epld->enable_lflag) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_UP); + } + + if(epld->enable_pflag) + { + elan_sensor_restart_work(); + } + return 0; +} +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void elan_sensor_early_suspend(struct early_suspend *h) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + LOG_FUN(); + + if( epld->enable_pflag==0) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_DOWN); + cancel_delayed_work(&polling_work); + } + + psensor_mode_suspend = 1; +} + +static void elan_sensor_late_resume(struct early_suspend *h) +{ + struct elan_epl_data *epld = epl_data; + struct i2c_client *client = epld->client; + + LOG_FUN(); + + if(epld->enable_pflag | epld->enable_lflag) + { + elan_sensor_I2C_Write(client,REG_7, W_SINGLE_BYTE, 0x02, EPL_C_P_UP); + } + + if(epld->enable_pflag || epld->enable_lflag) + { + elan_sensor_restart_work(); + } + psensor_mode_suspend = 0; +} +#endif + +static int elan_sensor_probe(struct i2c_client *client,const struct i2c_device_id *id) +{ + int err = 0; + int chip_id = 0; + struct elan_epl_data *epld ; + struct elan_epl_platform_data *pdata = client->dev.platform_data; + //static struct platform_device *sensor_dev; + struct device_node *np = client->dev.of_node; + LOG_INFO("elan sensor probe enter.\n"); +#ifdef CONFIG_OF + if (np && !pdata){ + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Could not allocate struct elan_epl_platform_data"); + goto exit_allocate_pdata_failed; + } + pdata->irq_gpio_number = of_get_gpio(np, 0); + if(pdata->irq_gpio_number < 0){ + dev_err(&client->dev, "fail to get irq_gpio_number\n"); + kfree(pdata); + goto exit_irq_gpio_read_fail; + } + client->dev.platform_data = pdata; + } +#endif + + epld = kzalloc(sizeof(struct elan_epl_data), GFP_KERNEL); + if (!epld) { + err = -ENOMEM; + LOG_ERR("kzalloc elan_epl_data failed!\n"); + goto exit_kzalloc_epld_failed; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + { + dev_err(&client->dev,"No supported i2c func what we need?!!\n"); + err = -ENOTSUPP; + goto i2c_fail; + } + chip_id = i2c_smbus_read_byte_data(client, 0x00); + if (chip_id < 0) + { + dev_err(&client->dev,"read chip id REG 0x00 failed\n"); + err = -ENODEV; + goto exit_read_chipid_failed; + } + LOG_INFO("chip id REG 0x00 value = %8x\n", i2c_smbus_read_byte_data(client, 0x00)); + LOG_INFO("chip id REG 0x01 value = %8x\n", i2c_smbus_read_byte_data(client, 0x08)); + LOG_INFO("chip id REG 0x02 value = %8x\n", i2c_smbus_read_byte_data(client, 0x10)); + LOG_INFO("chip id REG 0x03 value = %8x\n", i2c_smbus_read_byte_data(client, 0x18)); + LOG_INFO("chip id REG 0x04 value = %8x\n", i2c_smbus_read_byte_data(client, 0x20)); + LOG_INFO("chip id REG 0x05 value = %8x\n", i2c_smbus_read_byte_data(client, 0x28)); + LOG_INFO("chip id REG 0x06 value = %8x\n", i2c_smbus_read_byte_data(client, 0x30)); + LOG_INFO("chip id REG 0x07 value = %8x\n", i2c_smbus_read_byte_data(client, 0x38)); + LOG_INFO("chip id REG 0x09 value = %8x\n", i2c_smbus_read_byte_data(client, 0x48)); + LOG_INFO("chip id REG 0x0D value = %8x\n", i2c_smbus_read_byte_data(client, 0x68)); + LOG_INFO("chip id REG 0x0E value = %8x\n", i2c_smbus_read_byte_data(client, 0x70)); + LOG_INFO("chip id REG 0x0F value = %8x\n", i2c_smbus_read_byte_data(client, 0x71)); + LOG_INFO("chip id REG 0x10 value = %8x\n", i2c_smbus_read_byte_data(client, 0x80)); + LOG_INFO("chip id REG 0x11 value = %8x\n", i2c_smbus_read_byte_data(client, 0x88)); + LOG_INFO("chip id REG 0x13 value = %8x\n", i2c_smbus_read_byte_data(client, 0x98)); + + epld->client = client; + this_client = client; + epld->irq = client->irq; + i2c_set_clientdata(client, epld); + + epl_data = epld; + + epld->epl_wq = create_singlethread_workqueue(EPL2182_PLS_DEVICE); + if (!epld->epl_wq) + { + LOG_ERR("can't create workqueue\n"); + err = -ENOMEM; + goto err_create_singlethread_workqueue; + } +#if 0 //ices add + err = lightsensor_setup(epld); + if (err < 0) + { + LOG_ERR("lightsensor_setup error!!\n"); + goto err_lightsensor_setup; + } +#endif + err = psensor_setup(epld); + if (err < 0) + { + LOG_ERR("psensor_setup error!!\n"); + goto err_psensor_setup; + } + + err = initial_sensor(epld); + if (err < 0) + { + LOG_ERR("fail to initial sensor (%d)\n", err); + goto err_sensor_setup; + } + +#if PS_INTERRUPT_MODE + err = setup_interrupt(epld); + if (err < 0) + { + LOG_ERR("setup error!\n"); + goto err_sensor_setup; + } +#endif + +#ifdef CONFIG_HAS_EARLYSUSPEND + epld->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + epld->early_suspend.suspend = elan_sensor_early_suspend; + epld->early_suspend.resume = elan_sensor_late_resume; + register_early_suspend(&epld->early_suspend); +#endif + +#if 0 + wake_lock_init(&g_ps_wlock, WAKE_LOCK_SUSPEND, "ps_wakelock"); + + sensor_dev = platform_device_register_simple("elan_alsps", -1, NULL, 0); + if (IS_ERR(sensor_dev)) + { + printk ("sensor_dev_init: error\n"); + goto err_fail; + } + + err = sysfs_create_group(&sensor_dev->dev.kobj, &ets_attr_group); + if (err !=0) + { + dev_err(&client->dev,"%s:create sysfs group error", __func__); + goto err_fail; + } +#endif + + LOG_INFO("sensor probe success.\n"); + + return err; + +err_fail: + //input_unregister_device(epld->als_input_dev); + input_unregister_device(epld->ps_input_dev); + //input_free_device(epld->als_input_dev); + input_free_device(epld->ps_input_dev); +//err_lightsensor_setup: +err_psensor_setup: +err_sensor_setup: + destroy_workqueue(epld->epl_wq); + misc_deregister(&elan_ps_device); +// misc_deregister(&elan_als_device); //ices add +err_create_singlethread_workqueue: +exit_read_chipid_failed: +i2c_fail: +//err_platform_data_null: + kfree(epld); +exit_kzalloc_epld_failed: +#ifdef CONFIG_OF +exit_irq_gpio_read_fail: +exit_allocate_pdata_failed: +#endif + return err; +} + +static int elan_sensor_remove(struct i2c_client *client) +{ + struct elan_epl_data *epld = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s: enter.\n", __func__); + + unregister_early_suspend(&epld->early_suspend); + //input_unregister_device(epld->als_input_dev); + input_unregister_device(epld->ps_input_dev); + //input_free_device(epld->als_input_dev); + input_free_device(epld->ps_input_dev); + misc_deregister(&elan_ps_device); +// misc_deregister(&elan_als_device); //ices add + free_irq(epld->irq,epld); + destroy_workqueue(epld->epl_wq); + kfree(epld); + return 0; +} + +static const struct i2c_device_id elan_sensor_id[] = +{ + { EPL2182_PLS_DEVICE, 0 }, + { } +}; + +static const struct of_device_id epl2182_of_match[] = { + { .compatible = "ELAN,epl2182_pls", }, + {} +}; +MODULE_DEVICE_TABLE(of, epl2182_of_match); + +static struct i2c_driver elan_sensor_driver = +{ + .probe = elan_sensor_probe, + .remove = elan_sensor_remove, + .id_table = elan_sensor_id, + .driver = { + .name = EPL2182_PLS_DEVICE, + .owner = THIS_MODULE, + .of_match_table = epl2182_of_match, + }, +#ifdef CONFIG_SUSPEND + .suspend = elan_sensor_suspend, + .resume = elan_sensor_resume, +#endif +}; + +static int __init elan_sensor_init(void) +{ + return i2c_add_driver(&elan_sensor_driver); +} + +static void __exit elan_sensor_exit(void) +{ + i2c_del_driver(&elan_sensor_driver); +} + +module_init(elan_sensor_init); +module_exit(elan_sensor_exit); + +MODULE_AUTHOR("Renato Pan <renato.pan@eminent-tek.com>"); +MODULE_DESCRIPTION("ELAN epl2182 driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/misc/epl259x.c b/drivers/input/misc/epl259x.c new file mode 100644 index 00000000..81bcd79a --- /dev/null +++ b/drivers/input/misc/epl259x.c @@ -0,0 +1,4645 @@ +/* drivers/i2c/chips/epl259x.c - light and proxmity sensors driver + * Copyright (C) 2014 ELAN Corporation. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + + +#include <linux/hrtimer.h> +#include <linux/timer.h> +#include <linux/delay.h> +#if defined(CONFIG_HAS_EARLYSUSPEND) +#include <linux/earlysuspend.h> +#endif +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <asm/uaccess.h> +//#include <asm/mach-types.h> +#include <asm/setup.h> +#include <linux/wakelock.h> +#include <linux/jiffies.h> +#include <linux/i2c/epl259x.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> +#include <linux/regulator/consumer.h> +#include <linux/platform_device.h> + +/****************************************************************************** +* configuration +*******************************************************************************/ +#define ALS_POLLING_MODE 1 // 1 is polling mode, 0 is interrupt mode +#define PS_POLLING_MODE 0 // 1 is polling mode, 0 is interrupt mode + +#define ALS_LOW_THRESHOLD 1000 +#define ALS_HIGH_THRESHOLD 3000 + +#define PS_LOW_THRESHOLD 3000 +#define PS_HIGH_THRESHOLD 4000 + +#define LUX_PER_COUNT 700 + +//platform select +#define S5PV210 0 +#define SPREAD 1 +#define QCOM 0 +#define LEADCORE 0 +#define MARVELL 0 + +//#define ELAN_INT_PIN 140 /*Interrupt pin setting*/ + +//debug message +#define COMMON_DEBUG 0 +#define ALS_DEBUG 0 +#define PS_DEBUG 1 +#define SHOW_DBG 0 + +#define ALS_DYN_INTT 0 +#define PS_DYN_K 0 +#define PS_DYN_K_STR 0 +#define HS_ENABLE 0 +#define PS_GES 0 + +#define ALS_LEVEL 16 +static int polling_time = 200; +static struct i2c_client *this_client = NULL; +static int als_level[] = {20, 45, 70, 90, 150, 300, 500, 700, 1150, 2250, 4500, 8000, 15000, 30000, 50000}; +static int als_value[] = {10, 30, 60, 80, 100, 200, 400, 600, 800, 1500, 3000, 6000, 10000, 20000, 40000, 60000}; + +#if ALS_DYN_INTT +//Dynamic INTT +int dynamic_intt_idx; +int dynamic_intt_init_idx = 1; //initial dynamic_intt_idx +int c_gain; +int dynamic_intt_lux = 0; + +uint16_t dynamic_intt_high_thr; +uint16_t dynamic_intt_low_thr; +uint32_t dynamic_intt_max_lux = 12000; +uint32_t dynamic_intt_min_lux = 0; +uint32_t dynamic_intt_min_unit = 1000; + +static int als_dynamic_intt_intt[] = {EPL_ALS_INTT_8192, EPL_ALS_INTT_64}; +static int als_dynamic_intt_value[] = {8192, 64}; +static int als_dynamic_intt_gain[] = {EPL_GAIN_MID, EPL_GAIN_MID}; +static int als_dynamic_intt_high_thr[] = {60000, 53000}; +static int als_dynamic_intt_low_thr[] = {200, 300}; +static int als_dynamic_intt_intt_num = sizeof(als_dynamic_intt_value)/sizeof(int); +#endif + +#if ALS_DYN_INTT +typedef enum +{ + CMC_BIT_LSRC_NON = 0x0, + CMC_BIT_LSRC_SCALE = 0x1, + CMC_BIT_LSRC_SLOPE = 0x2, + CMC_BIT_LSRC_BOTH = 0x3, +} CMC_LSRC_REPORT_TYPE; +#endif + +#if PS_DYN_K +static int dynk_polling_delay = 200; +int dynk_min_ps_raw_data; +int dynk_max_ir_data; + +u32 dynk_thd_low = 0; +u32 dynk_thd_high = 0; + +int dynk_low_offset; +int dynk_high_offset; + +bool dynk_change_flag = false; +u16 dynk_change_thd_max; +u16 dynk_thd_offset; + +u8 dynk_last_status = 0; + +#if PS_DYN_K_STR +bool dynk_enhance_flag = true; +u8 dynk_enhance_integration_time; +u8 dynk_enhance_gain; +u8 dynk_enhance_adc; +u16 dynk_enhance_ch0; +u16 dynk_enhance_ch1; +u16 dynk_enhance_max_ch0; +#endif + +#endif + +#if HS_ENABLE +static struct mutex hs_sensor_mutex; +static bool hs_enable_flag = false; +#endif + +#if PS_GES +static bool ps_ges_enable_flag = false; +u16 ges_threshold_low = 1000; +u16 ges_threshold_high = 1500; +#define KEYCODE_LEFT KEY_LEFTALT +bool ps_ges_suspend_flag = false; +#endif + +bool polling_flag = true; +bool eint_flag = true; + +//ps calibration file location +static const char ps_cal_file[]="/data/data/com.eminent.ps.calibration/ps.dat"; + +//als calibration file location +static const char als_cal_file[]="/data/data/com.eminent.ps.calibration/als.dat"; + +static int PS_h_offset = 2000; +static int PS_l_offset = 1000; +static int PS_MAX_XTALK = 30000; + +int als_frame_time = 0; +int ps_frame_time = 0; +/****************************************************************************** +*******************************************************************************/ + +#define TXBYTES 2 +#define RXBYTES 2 + +#define PACKAGE_SIZE 8 +#define I2C_RETRY_COUNT 10 +#define EPL_DEV_NAME "epl2182_pls" +#define DRIVER_VERSION "1.0.3" +//struct timeval ges_enable_time; + +//int ges_frame_count; + +typedef enum +{ + CMC_BIT_RAW = 0x0, + CMC_BIT_PRE_COUNT = 0x1, + CMC_BIT_DYN_INT = 0x2, + CMC_BIT_DEF_LIGHT = 0x4, + CMC_BIT_TABLE = 0x8, +} CMC_ALS_REPORT_TYPE; + +typedef struct _epl_raw_data +{ + u8 raw_bytes[PACKAGE_SIZE]; + u16 renvo; +} epl_raw_data; + +struct epl_sensor_priv +{ + struct i2c_client *client; + struct input_dev *als_input_dev; + struct input_dev *ps_input_dev; + struct workqueue_struct *epl_wq; + struct delayed_work eint_work; +#if defined(CONFIG_HAS_EARLYSUSPEND) + struct early_suspend early_suspend; +#endif + int intr_pin; + int (*power)(int on); + + int ps_opened; + int als_opened; + + int als_suspend; + int ps_suspend; + + int lux_per_count; + + int enable_pflag; + int enable_lflag; +#if HS_ENABLE + int enable_hflag; + int hs_suspend; +#endif +#if PS_GES + struct input_dev *gs_input_dev; + int enable_gflag; + int ges_suspend; +#endif + int read_flag; + int irq; + spinlock_t lock; + +#if ALS_DYN_INTT + uint32_t ratio; + uint32_t last_ratio; + int c_gain_h; // fluorescent (C1) + int c_gain_l; // incandescent (C2) + uint32_t lsource_thd_high; //different light source boundary (N) fluorescent (C1) + uint32_t lsource_thd_low; //different light source boundary (N) incandescent (C2) +#endif + + /*data*/ + u16 als_level_num; + u16 als_value_num; + u32 als_level[ALS_LEVEL-1]; + u32 als_value[ALS_LEVEL]; +} ; + +static struct platform_device *sensor_dev; +struct epl_sensor_priv *epl_sensor_obj; +static epl_optical_sensor epl_sensor; +int i2c_max_count=8; + +static epl_raw_data gRawData; + +static struct wake_lock ps_lock; +static struct mutex sensor_mutex; + +#if S5PV210 +static const char ElanPsensorName[]="proximity"; +static const char ElanALsensorName[]="lightsensor-level"; +#elif SPREAD +static const char ElanPsensorName[] = "proximity"; +#elif QCOM || LEADCORE +static const char ElanPsensorName[] = "proximity"; +static const char ElanALsensorName[] = "light"; +#elif MARVELL +static const char ElanPsensorName[] = "alps_pxy"; +#endif + +#define LOG_TAG "[EPL259x] " +#define LOG_FUN(f) printk(KERN_INFO LOG_TAG"%s\n", __FUNCTION__) +#define LOG_INFO(fmt, args...) printk(KERN_INFO LOG_TAG fmt, ##args) +#define LOG_ERR(fmt, args...) printk(KERN_ERR LOG_TAG"%s %d : "fmt, __FUNCTION__, __LINE__, ##args) + + +void epl_sensor_update_mode(struct i2c_client *client); +int epl_sensor_read_als_status(struct i2c_client *client); +static int epl_sensor_setup_interrupt(struct epl_sensor_priv *epld); +static int ps_sensing_time(int intt, int adc, int cycle); +static int als_sensing_time(int intt, int adc, int cycle); + +static void epl_sensor_eint_work(struct work_struct *work); +//static DECLARE_WORK(epl_sensor_irq_work, epl_sensor_eint_work); + +static void epl_sensor_polling_work(struct work_struct *work); +static DECLARE_DELAYED_WORK(polling_work, epl_sensor_polling_work); + +#if PS_DYN_K +void epl_sensor_dynk_thd_polling_work(struct work_struct *work); +void epl_sensor_restart_dynk_polling(void); +static DECLARE_DELAYED_WORK(dynk_thd_polling_work, epl_sensor_dynk_thd_polling_work); +#endif + + + + +/* +//====================I2C write operation===============// +*/ +static int epl_sensor_I2C_Write_Cmd(struct i2c_client *client, uint8_t regaddr, uint8_t data, uint8_t txbyte) +{ + uint8_t buffer[2]; + int ret = 0; + int retry; + + buffer[0] = regaddr ; + buffer[1] = data; + + for(retry = 0; retry < I2C_RETRY_COUNT; retry++) + { + ret = i2c_master_send(client, buffer, txbyte); + + if (ret == txbyte) + { + break; + } + + LOG_ERR("i2c write error,TXBYTES %d\n",ret); + mdelay(10); + } + + + if(retry>=I2C_RETRY_COUNT) + { + LOG_ERR("i2c write retry over %d\n", I2C_RETRY_COUNT); + return -EINVAL; + } + + return ret; +} + +static int epl_sensor_I2C_Write(struct i2c_client *client, uint8_t regaddr, uint8_t data) +{ + int ret = 0; + ret = epl_sensor_I2C_Write_Cmd(client, regaddr, data, 0x02); + return ret; + return 0; +} +/*----------------------------------------------------------------------------*/ +static int epl_sensor_I2C_Read(struct i2c_client *client, uint8_t regaddr, uint8_t bytecount) +{ + + int ret = 0; + + + int retry; + int read_count=0, rx_count=0; + + while(bytecount>0) + { + epl_sensor_I2C_Write_Cmd(client, regaddr+read_count, 0x00, 0x01); + + for(retry = 0; retry < I2C_RETRY_COUNT; retry++) + { + rx_count = bytecount>i2c_max_count?i2c_max_count:bytecount; + ret = i2c_master_recv(client, &gRawData.raw_bytes[read_count], rx_count); + + if (ret == rx_count) + break; + + LOG_ERR("i2c read error,RXBYTES %d\r\n",ret); + mdelay(10); + } + + if(retry>=I2C_RETRY_COUNT) + { + LOG_ERR("i2c read retry over %d\n", I2C_RETRY_COUNT); + return -EINVAL; + } + bytecount-=rx_count; + read_count+=rx_count; + } + + return ret; +} + +/*----------------------------------------------------------------------------*/ +static void epl_sensor_restart_polling(void) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + cancel_delayed_work(&polling_work); + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(polling_time)); + +} +/*----------------------------------------------------------------------------*/ + +#if PS_GES +static void epl_sensor_notify_event(void) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + struct input_dev *idev = epld->gs_input_dev; + + LOG_INFO(" --> LEFT\n\n"); + input_report_key(idev, KEYCODE_LEFT, 1); + input_report_key(idev, KEYCODE_LEFT, 0); + input_sync(idev); +} +#endif + +/*----------------------------------------------------------------------------*/ +static void epl_sensor_report_lux(int repott_lux) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_INFO("------------------- ALS raw = %d, lux = %d\n\n",epl_sensor.als.data.channels[1], repott_lux); +#if SPREAD || MARVELL + input_report_abs(epld->ps_input_dev, ABS_MISC, repott_lux); + input_sync(epld->ps_input_dev); +#else + input_report_abs(epld->als_input_dev, ABS_MISC, repott_lux); + input_sync(epld->als_input_dev); +#endif +} +/*----------------------------------------------------------------------------*/ +#if ALS_DYN_INTT +long raw_convert_to_lux(u16 raw_data) +{ + long lux = 0; + + lux = raw_data * (c_gain / als_dynamic_intt_value[dynamic_intt_idx]); +#if ALS_DEBUG + LOG_INFO("[%s]:raw_data=%d, lux=%ld\r\n", __func__, raw_data, lux); +#endif + + if(lux >= (dynamic_intt_max_lux * dynamic_intt_min_unit)){ +#if ALS_DEBUG + LOG_INFO("[%s]:raw_convert_to_lux: change max lux\r\n", __func__); +#endif + lux = dynamic_intt_max_lux * dynamic_intt_min_unit; + } + else if(lux <= (dynamic_intt_min_lux*dynamic_intt_min_unit)){ +#if ALS_DEBUG + LOG_INFO("[%s]:raw_convert_to_lux: change min lux\r\n", __func__); +#endif + lux = dynamic_intt_min_lux * dynamic_intt_min_unit; + } + + return lux; +} +#endif +/*----------------------------------------------------------------------------*/ +static int epl_sensor_get_als_value(struct epl_sensor_priv *obj, u16 als) +{ + int idx; + int invalid = 0; +#if ALS_DYN_INTT + long now_lux=0, lux_tmp=0; + bool change_flag = false; +#endif + switch(epl_sensor.als.report_type) + { + case CMC_BIT_RAW: + return als; + break; + + case CMC_BIT_PRE_COUNT: + return (als * epl_sensor.als.factory.lux_per_count)/1000; + break; + + case CMC_BIT_TABLE: + for(idx = 0; idx < obj->als_level_num; idx++) + { + if(als < als_level[idx]) + { + break; + } + } + + if(idx >= obj->als_value_num) + { + LOG_ERR("exceed range\n"); + idx = obj->als_value_num - 1; + } + + if(!invalid) + { + LOG_INFO("ALS: %05d => %05d\n", als, als_value[idx]); + return als_value[idx]; + } + else + { + LOG_ERR("ALS: %05d => %05d (-1)\n", als, als_value[idx]); + return als; + } + break; +#if ALS_DYN_INTT + case CMC_BIT_DYN_INT: + + if(epl_sensor.als.lsrc_type != CMC_BIT_LSRC_NON) + { + long luxratio = 0; + epl_sensor_read_als_status(obj->client); + + if (epl_sensor.als.data.channels[0] == 0) + { + epl_sensor.als.data.channels[0] = 1; + LOG_ERR("[%s]:read ch0 data is 0 \r\n", __func__); + } + + luxratio = (long)((als*dynamic_intt_min_unit) / epl_sensor.als.data.channels[0]); //lux ratio (A=CH1/CH0) + + obj->ratio = luxratio; + if((epl_sensor.als.saturation >> 5) == 0) + { + if(epl_sensor.als.lsrc_type == CMC_BIT_LSRC_SCALE || epl_sensor.als.lsrc_type == CMC_BIT_LSRC_BOTH) + { + if(obj->ratio == 0){ + obj->last_ratio = luxratio; + } + else{ + obj->last_ratio = (luxratio + obj->last_ratio*9) / 10; + } + + if (obj->last_ratio >= obj->lsource_thd_high) //fluorescent (C1) + { + c_gain = obj->c_gain_h; + } + else if (obj->last_ratio <= obj->lsource_thd_low) //incandescent (C2) + { + c_gain = obj->c_gain_l; + } + else if(epl_sensor.als.lsrc_type == CMC_BIT_LSRC_BOTH) + { + int a = 0, b = 0, c = 0; + a = (obj->c_gain_h - obj->c_gain_l) * dynamic_intt_min_unit / (obj->lsource_thd_high - obj->lsource_thd_low); + b = (obj->c_gain_h) - ((a * obj->lsource_thd_high)/dynamic_intt_min_unit ); + c = ((a * obj->last_ratio)/dynamic_intt_min_unit) + b; + if(c > obj->c_gain_h) + c_gain = obj->c_gain_h; + else if (c < obj->c_gain_l) + c_gain = obj->c_gain_l; + else + c_gain = c; + } + } + else if(epl_sensor.als.lsrc_type == CMC_BIT_LSRC_SLOPE) + { + if (luxratio >= obj->lsource_thd_high) //fluorescent (C1) + { + c_gain = obj->c_gain_h; + } + else if (luxratio <= obj->lsource_thd_low) //incandescent (C2) + { + c_gain = obj->c_gain_l; + } + else{ /*mix*/ + int a = 0, b = 0, c = 0; + a = (obj->c_gain_h - obj->c_gain_l) * dynamic_intt_min_unit / (obj->lsource_thd_high - obj->lsource_thd_low); + b = (obj->c_gain_h) - ((a * obj->lsource_thd_high)/dynamic_intt_min_unit ); + c = ((a * luxratio)/dynamic_intt_min_unit) + b; + if(c > obj->c_gain_h) + c_gain = obj->c_gain_h; + else if (c < obj->c_gain_l) + c_gain = obj->c_gain_l; + else + c_gain = c; + } + } + LOG_INFO("[%s]:ch0=%d, ch1=%d, c_gain=%d, obj->ratio=%d, obj->last_ratio=%d \r\n\n", + __func__,epl_sensor.als.data.channels[0], als, c_gain, obj->ratio, obj->last_ratio); + } + else + { + LOG_INFO("[%s]: ALS saturation(%d) \r\n", __func__, (epl_sensor.als.saturation >> 5)); + } + } + +#if ALS_DEBUG + LOG_INFO("[%s]: dynamic_intt_idx=%d, als_dynamic_intt_value=%d, dynamic_intt_gain=%d, als=%d \r\n", + __func__, dynamic_intt_idx, als_dynamic_intt_value[dynamic_intt_idx], als_dynamic_intt_gain[dynamic_intt_idx], als); +#endif + + if(als > dynamic_intt_high_thr) + { + if(dynamic_intt_idx == (als_dynamic_intt_intt_num - 1)){ + als = dynamic_intt_high_thr; + lux_tmp = raw_convert_to_lux(als); +#if ALS_DEBUG + LOG_INFO(">>>>>>>>>>>>>>>>>>>>>>>> INTT_MAX_LUX\r\n"); +#endif + } + else{ + change_flag = true; + als = dynamic_intt_high_thr; + lux_tmp = raw_convert_to_lux(als); + dynamic_intt_idx++; +#if ALS_DEBUG + LOG_INFO(">>>>>>>>>>>>>>>>>>>>>>>>change INTT high: %d, raw: %d \r\n", dynamic_intt_idx, als); +#endif + } + } + else if(als < dynamic_intt_low_thr) + { + if(dynamic_intt_idx == 0){ + //als = dynamic_intt_low_thr; + lux_tmp = raw_convert_to_lux(als); +#if ALS_DEBUG + LOG_INFO(">>>>>>>>>>>>>>>>>>>>>>>> INTT_MIN_LUX\r\n"); +#endif + } + else{ + change_flag = true; + als = dynamic_intt_low_thr; + lux_tmp = raw_convert_to_lux(als); + dynamic_intt_idx--; +#if ALS_DEBUG + LOG_INFO(">>>>>>>>>>>>>>>>>>>>>>>>change INTT low: %d, raw: %d \r\n", dynamic_intt_idx, als); +#endif + } + } + else + { + lux_tmp = raw_convert_to_lux(als); + } + + now_lux = lux_tmp; + dynamic_intt_lux = now_lux/dynamic_intt_min_unit; + + epl_sensor_report_lux(dynamic_intt_lux); + + if(change_flag == true) + { + epl_sensor.als.integration_time = als_dynamic_intt_intt[dynamic_intt_idx]; + epl_sensor.als.gain = als_dynamic_intt_gain[dynamic_intt_idx]; + dynamic_intt_high_thr = als_dynamic_intt_high_thr[dynamic_intt_idx]; + dynamic_intt_low_thr = als_dynamic_intt_low_thr[dynamic_intt_idx]; + epl_sensor_update_mode(obj->client); + change_flag = false; + } + return dynamic_intt_lux; + + break; +#endif + + } + return 0; +} +/*----------------------------------------------------------------------------*/ + +int epl_sensor_read_als(struct i2c_client *client) +{ + struct epl_sensor_priv *epld = i2c_get_clientdata(client); + + if(client == NULL) + { + LOG_ERR("CLIENT CANN'T EQUL NULL\n"); + return -1; + } + + epl_sensor_I2C_Read(epld->client, 0x13, 4); +#if PS_DYN_K && PS_DYN_K_STR + if(dynk_enhance_flag == false) + { + dynk_enhance_ch0 = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + dynk_enhance_ch1 = (gRawData.raw_bytes[3]<<8) | gRawData.raw_bytes[2]; + + LOG_INFO("read dynk_enhance_ch0 = %d\n", dynk_enhance_ch0); + LOG_INFO("read dynk_enhance_ch1 = %d\n", dynk_enhance_ch1); + } + else +#endif + { + epl_sensor.als.data.channels[0] = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + epl_sensor.als.data.channels[1] = (gRawData.raw_bytes[3]<<8) | gRawData.raw_bytes[2]; + +#if ALS_DEBUG + LOG_INFO("read als channel 0 = %d\n", epl_sensor.als.data.channels[0]); + LOG_INFO("read als channel 1 = %d\n", epl_sensor.als.data.channels[1]); +#endif + } + + + if(epl_sensor.wait == EPL_WAIT_SINGLE) + epl_sensor_I2C_Write(epld->client,0x11, epl_sensor.power | epl_sensor.reset); + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static void epl_sensor_report_ps_status(void) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + + LOG_INFO("------------------- epl_sensor.ps.data.data=%d, value=%d \n\n", epl_sensor.ps.data.data, epl_sensor.ps.compare_low >> 3); + + input_report_abs(epld->ps_input_dev, ABS_DISTANCE, epl_sensor.ps.compare_low >> 3); + input_sync(epld->ps_input_dev); +} + +int epl_sensor_read_ps(struct i2c_client *client) +{ + struct epl_sensor_priv *epld = i2c_get_clientdata(client); + if(client == NULL) + { + LOG_ERR("CLIENT CANN'T EQUL NULL\n"); + return -1; + } + + epl_sensor_I2C_Read(epld->client,0x1c, 4); + + epl_sensor.ps.data.ir_data = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + epl_sensor.ps.data.data = (gRawData.raw_bytes[3]<<8) | gRawData.raw_bytes[2]; + + LOG_INFO("[%s] data = %d\n", __FUNCTION__, epl_sensor.ps.data.data); + LOG_INFO("[%s] ir data = %d\n", __FUNCTION__, epl_sensor.ps.data.ir_data); + + if(epl_sensor.wait == EPL_WAIT_SINGLE) + epl_sensor_I2C_Write(epld->client, 0x11, epl_sensor.power | epl_sensor.reset); + + return 0; +} + +int epl_sensor_read_ps_status(struct i2c_client *client) +{ + u8 buf; +#if PS_GES + struct epl_sensor_priv *obj = epl_sensor_obj; + u8 new_ps_state; + u8 ges_saturation; + bool enable_ges = obj->enable_gflag==1 && obj->ges_suspend==0; +#endif + if(client == NULL) + { + LOG_ERR("CLIENT CANN'T EQUL NULL\n"); + return -1; + } + + epl_sensor_I2C_Read(client, 0x1b, 1); + + buf = gRawData.raw_bytes[0]; + +#if PS_GES + ges_saturation = (buf & 0x20); + new_ps_state = (buf & 0x08) >> 3; + LOG_INFO("[%s]:new_ps_state=%d, ps_ges_suspend_flag=%d \r\n", __func__, new_ps_state, ps_ges_suspend_flag); + if(enable_ges == 1 && epl_sensor.ges.polling_mode == 1) + { + if( new_ps_state == 0 && (epl_sensor.ps.compare_low>>3 == 1) && ps_ges_suspend_flag == false && ges_saturation == 0) + epl_sensor_notify_event(); + } + else if(enable_ges == 1 && epl_sensor.ges.polling_mode == 0 && ps_ges_suspend_flag == false && ges_saturation == 0) + { + if(new_ps_state == 0) + epl_sensor_notify_event(); + } +#endif + + epl_sensor.ps.saturation = (buf & 0x20); + epl_sensor.ps.compare_high = (buf & 0x10); + epl_sensor.ps.compare_low = (buf & 0x08); + epl_sensor.ps.interrupt_flag = (buf & 0x04); + epl_sensor.ps.compare_reset = (buf & 0x02); + epl_sensor.ps.lock= (buf & 0x01); +#if PS_DEBUG + LOG_INFO("ps: ~~~~ PS ~~~~~ \n"); + LOG_INFO("ps: buf = 0x%x\n", buf); + LOG_INFO("ps: sat = 0x%x\n", epl_sensor.ps.saturation); + LOG_INFO("ps: cmp h = 0x%x, l = 0x%x\n", epl_sensor.ps.compare_high, epl_sensor.ps.compare_low); + LOG_INFO("ps: int_flag = 0x%x\n",epl_sensor.ps.interrupt_flag); + LOG_INFO("ps: cmp_rstn = 0x%x, lock = %x\n", epl_sensor.ps.compare_reset, epl_sensor.ps.lock); +#endif + return 0; +} + +/*----------------------------------------------------------------------------*/ + +static int set_psensor_intr_threshold(uint16_t low_thd, uint16_t high_thd) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + struct i2c_client *client = epld->client; + uint8_t high_msb ,high_lsb, low_msb, low_lsb; + + + high_msb = (uint8_t) (high_thd >> 8); + high_lsb = (uint8_t) (high_thd & 0x00ff); + low_msb = (uint8_t) (low_thd >> 8); + low_lsb = (uint8_t) (low_thd & 0x00ff); + + LOG_INFO("%s: low_thd = %d, high_thd = %d \n",__FUNCTION__, low_thd, high_thd); + + epl_sensor_I2C_Write(client,0x0c,low_lsb); + epl_sensor_I2C_Write(client,0x0d,low_msb); + epl_sensor_I2C_Write(client,0x0e,high_lsb); + epl_sensor_I2C_Write(client,0x0f,high_msb); + + return 0; +} + +static int set_lsensor_intr_threshold(uint16_t low_thd, uint16_t high_thd) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + struct i2c_client *client = epld->client; + uint8_t high_msb ,high_lsb, low_msb, low_lsb; + + high_msb = (uint8_t) (high_thd >> 8); + high_lsb = (uint8_t) (high_thd & 0x00ff); + low_msb = (uint8_t) (low_thd >> 8); + low_lsb = (uint8_t) (low_thd & 0x00ff); + + epl_sensor_I2C_Write(client,0x08,low_lsb); + epl_sensor_I2C_Write(client,0x09,low_msb); + epl_sensor_I2C_Write(client,0x0a,high_lsb); + epl_sensor_I2C_Write(client,0x0b,high_msb); + + LOG_INFO("%s: low_thd = %d, high_thd = %d \n",__FUNCTION__, low_thd, high_thd); + + return 0; +} + +int epl_sensor_read_als_status(struct i2c_client *client) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + u8 buf; + + if(client == NULL) + { + LOG_ERR("CLIENT CANN'T EQUL NULL\n"); + return -1; + } + + epl_sensor_I2C_Read(epld->client, 0x12, 1); + + buf = gRawData.raw_bytes[0]; + + epl_sensor.als.saturation = (buf & 0x20); + epl_sensor.als.compare_high = (buf & 0x10); + epl_sensor.als.compare_low = (buf & 0x08); + epl_sensor.als.interrupt_flag = (buf & 0x04); + epl_sensor.als.compare_reset = (buf & 0x02); + epl_sensor.als.lock= (buf & 0x01); +#if ALS_DEBUG + LOG_INFO("als: ~~~~ ALS ~~~~~ \n"); + LOG_INFO("als: buf = 0x%x\n", buf); + LOG_INFO("als: sat = 0x%x\n", epl_sensor.als.saturation); + LOG_INFO("als: cmp h = 0x%x, l = 0x%x\n", epl_sensor.als.compare_high, epl_sensor.als.compare_low); + LOG_INFO("als: int_flag = 0x%x\n",epl_sensor.als.interrupt_flag); + LOG_INFO("als: cmp_rstn = 0x%x, lock = 0x%x\n", epl_sensor.als.compare_reset, epl_sensor.als.lock); +#endif + return 0; +} + +static int write_factory_calibration(struct epl_sensor_priv *epl_data, char* ps_data, int ps_cal_len) +{ + struct file *fp_cal; + + mm_segment_t fs; + loff_t pos; + + LOG_FUN(); + pos = 0; + + fp_cal = filp_open(ps_cal_file, O_CREAT|O_RDWR|O_TRUNC, 0755/*S_IRWXU*/); + if (IS_ERR(fp_cal)) + { + LOG_ERR("[ELAN]create file_h error\n"); + return -1; + } + + fs = get_fs(); + set_fs(KERNEL_DS); + + vfs_write(fp_cal, ps_data, ps_cal_len, &pos); + + filp_close(fp_cal, NULL); + + set_fs(fs); + + return 0; +} + +static bool read_factory_calibration(void) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + struct file *fp; + mm_segment_t fs; + loff_t pos; + char buffer[100]= {0}; + if(epl_sensor.ps.factory.calibration_enable && !epl_sensor.ps.factory.calibrated) + { + fp = filp_open(ps_cal_file, O_RDWR, S_IRUSR); + + if (IS_ERR(fp)) + { + LOG_ERR("NO PS calibration file(%d)\n", (int)IS_ERR(fp)); + epl_sensor.ps.factory.calibration_enable = false; + } + else + { + int ps_cancelation = 0, ps_hthr = 0, ps_lthr = 0; + pos = 0; + fs = get_fs(); + set_fs(KERNEL_DS); + vfs_read(fp, buffer, sizeof(buffer), &pos); + filp_close(fp, NULL); + + sscanf(buffer, "%d,%d,%d", &ps_cancelation, &ps_hthr, &ps_lthr); + epl_sensor.ps.factory.cancelation = ps_cancelation; + epl_sensor.ps.factory.high_threshold = ps_hthr; + epl_sensor.ps.factory.low_threshold = ps_lthr; + set_fs(fs); + + epl_sensor.ps.high_threshold = epl_sensor.ps.factory.high_threshold; + epl_sensor.ps.low_threshold = epl_sensor.ps.factory.low_threshold; + epl_sensor.ps.cancelation = epl_sensor.ps.factory.cancelation; + } + + epl_sensor_I2C_Write(epld->client,0x22, (u8)(epl_sensor.ps.cancelation& 0xff)); + epl_sensor_I2C_Write(epld->client,0x23, (u8)((epl_sensor.ps.cancelation & 0xff00) >> 8)); + set_psensor_intr_threshold(epl_sensor.ps.low_threshold, epl_sensor.ps.high_threshold); + + epl_sensor.ps.factory.calibrated = true; + } + + if(epl_sensor.als.factory.calibration_enable && !epl_sensor.als.factory.calibrated) + { + fp = filp_open(als_cal_file, O_RDONLY, S_IRUSR); + if (IS_ERR(fp)) + { + LOG_ERR("NO ALS calibration file(%d)\n", (int)IS_ERR(fp)); + epl_sensor.als.factory.calibration_enable = false; + } + else + { + int als_lux_per_count = 0; + pos = 0; + fs = get_fs(); + set_fs(KERNEL_DS); + vfs_read(fp, buffer, sizeof(buffer), &pos); + filp_close(fp, NULL); + + sscanf(buffer, "%d", &als_lux_per_count); + epl_sensor.als.factory.lux_per_count = als_lux_per_count; + set_fs(fs); + } + epl_sensor.als.factory.calibrated = true; + } + return true; +} + +static int epl_run_ps_calibration(struct epl_sensor_priv *epl_data) +{ + struct epl_sensor_priv *epld = epl_data; + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; + u16 ch1=0; + u32 ch1_all=0; + int count =5, i; + int ps_hthr=0, ps_lthr=0, ps_cancelation=0, ps_cal_len = 0; + char ps_calibration[20]; + + + if(PS_MAX_XTALK < 0) + { + LOG_ERR("[%s]:Failed: PS_MAX_XTALK < 0 \r\n", __func__); + return -EINVAL; + } + + if(enable_ps == 0) + { + epld->enable_pflag = 1; + epl_sensor_update_mode(epld->client); + } + + polling_flag = false; + + for(i=0; i<count; i++) + { + msleep(50); + switch(epl_sensor.mode) + { + case EPL_MODE_PS: + case EPL_MODE_ALS_PS: + if(enable_ps == true && polling_flag == true && eint_flag == true && epl_sensor.ps.polling_mode == 0) + epl_sensor_read_ps(epld->client); + ch1 = epl_sensor.ps.data.data; + break; + } + + ch1_all = ch1_all + ch1; + if(epl_sensor.wait == EPL_WAIT_SINGLE) + epl_sensor_I2C_Write(epld->client,0x11, epl_sensor.power | epl_sensor.reset); + } + + + ch1 = (u16)(ch1_all/count); + + if(ch1 > PS_MAX_XTALK) + { + LOG_ERR("[%s]:Failed: ch1 > max_xtalk(%d) \r\n", __func__, ch1); + return -EINVAL; + } + else if(ch1 <= 0) + { + LOG_ERR("[%s]:Failed: ch1 = 0\r\n", __func__); + return -EINVAL; + } + + ps_hthr = ch1 + PS_h_offset; + ps_lthr = ch1 + PS_l_offset; + + ps_cal_len = sprintf(ps_calibration, "%d,%d,%d", ps_cancelation, ps_hthr, ps_lthr); + + if(write_factory_calibration(epld, ps_calibration, ps_cal_len) < 0) + { + LOG_ERR("[%s] create file error \n", __func__); + return -EINVAL; + } + + epl_sensor.ps.low_threshold = ps_lthr; + epl_sensor.ps.high_threshold = ps_hthr; + set_psensor_intr_threshold(epl_sensor.ps.low_threshold, epl_sensor.ps.high_threshold); + + LOG_INFO("[%s]: ch1 = %d\n", __func__, ch1); + + polling_flag = true; + epl_sensor_restart_polling(); + return ch1; +} + +/* +//====================write global variable===============// +*/ +static void write_global_variable(struct i2c_client *client) +{ + u8 buf; +#if HS_ENABLE + struct epl_sensor_priv *obj = epl_sensor_obj; + bool enable_hs = obj->enable_hflag==1 && obj->hs_suspend==0; +#endif +#if PS_GES + bool enable_ges = obj->enable_gflag==1 && obj->ges_suspend==0; +#endif + //wake up chip + buf = epl_sensor.reset | epl_sensor.power; + epl_sensor_I2C_Write(client,0x11, buf); + + /* read revno*/ + epl_sensor_I2C_Read(client, 0x20, 2); + epl_sensor.revno = gRawData.raw_bytes[0] | gRawData.raw_bytes[1] << 8; + + /*chip refrash*/ + epl_sensor_I2C_Write(client, 0xfd, 0x8e); + epl_sensor_I2C_Write(client, 0xfe, 0x22); + epl_sensor_I2C_Write(client, 0xfe, 0x02); + epl_sensor_I2C_Write(client, 0xfd, 0x00); + + epl_sensor_I2C_Write(client, 0xfc, EPL_A_D | EPL_NORMAL| EPL_GFIN_ENABLE | EPL_VOS_ENABLE | EPL_DOC_ON); + +#if HS_ENABLE + if(enable_hs) + { + epl_sensor.mode = EPL_MODE_PS; + epl_sensor_I2C_Write(obj->client, 0x00, epl_sensor.wait | EPL_MODE_IDLE); + + /*hs setting*/ + buf = epl_sensor.hs.integration_time | epl_sensor.hs.gain; + epl_sensor_I2C_Write(client, 0x03, buf); + + buf = epl_sensor.hs.adc | epl_sensor.hs.cycle; + epl_sensor_I2C_Write(client, 0x04, buf); + + buf = epl_sensor.hs.ir_on_control | epl_sensor.hs.ir_mode | epl_sensor.hs.ir_driver; + epl_sensor_I2C_Write(client, 0x05, buf); + + buf = epl_sensor.hs.compare_reset | epl_sensor.hs.lock; + + epl_sensor_I2C_Write(client, 0x1b, buf); + } +#if !PS_GES /*PS_GES*/ + else +#elif PS_GES + else if(enable_ges) +#endif /*PS_GES*/ +#endif + +#if PS_GES + +#if !HS_ENABLE /*HS_ENABLE*/ + if(enable_ges) +#endif /*HS_ENABLE*/ + { + /*ges setting*/ + buf = epl_sensor.ges.integration_time | epl_sensor.ges.gain; + epl_sensor_I2C_Write(client, 0x03, buf); + + buf = epl_sensor.ges.adc | epl_sensor.ges.cycle; + epl_sensor_I2C_Write(client, 0x04, buf); + + buf = epl_sensor.ges.ir_on_control | epl_sensor.ges.ir_mode | epl_sensor.ges.ir_drive; + epl_sensor_I2C_Write(client, 0x05, buf); + + buf = epl_sensor.interrupt_control | epl_sensor.ges.persist |epl_sensor.ges.interrupt_type; + epl_sensor_I2C_Write(client, 0x06, buf); + + buf = epl_sensor.ges.compare_reset | epl_sensor.ges.lock; + epl_sensor_I2C_Write(client, 0x1b, buf); + + epl_sensor_I2C_Write(client, 0x22, (u8)(epl_sensor.ges.cancelation& 0xff)); + epl_sensor_I2C_Write(client, 0x23, (u8)((epl_sensor.ges.cancelation & 0xff00) >> 8)); + set_psensor_intr_threshold(epl_sensor.ges.low_threshold, epl_sensor.ges.high_threshold); + } + else +#endif + { + /*ps setting*/ + buf = epl_sensor.ps.integration_time | epl_sensor.ps.gain; + epl_sensor_I2C_Write(client,0x03, buf); + + buf = epl_sensor.ps.adc | epl_sensor.ps.cycle; + epl_sensor_I2C_Write(client,0x04, buf); + + buf = epl_sensor.ps.ir_on_control | epl_sensor.ps.ir_mode | epl_sensor.ps.ir_drive; + epl_sensor_I2C_Write(client,0x05, buf); + + buf = epl_sensor.interrupt_control | epl_sensor.ps.persist |epl_sensor.ps.interrupt_type; + epl_sensor_I2C_Write(client,0x06, buf); + + buf = epl_sensor.ps.compare_reset | epl_sensor.ps.lock; + epl_sensor_I2C_Write(client,0x1b, buf); + + epl_sensor_I2C_Write(client,0x22, (u8)(epl_sensor.ps.cancelation& 0xff)); + epl_sensor_I2C_Write(client,0x23, (u8)((epl_sensor.ps.cancelation & 0xff0) >> 8)); + set_psensor_intr_threshold(epl_sensor.ps.low_threshold, epl_sensor.ps.high_threshold); + + /*als setting*/ + buf = epl_sensor.als.integration_time | epl_sensor.als.gain; + epl_sensor_I2C_Write(client,0x01, buf); + + buf = epl_sensor.als.adc | epl_sensor.als.cycle; + epl_sensor_I2C_Write(client,0x02, buf); + + buf = epl_sensor.als.interrupt_channel_select | epl_sensor.als.persist | epl_sensor.als.interrupt_type; + epl_sensor_I2C_Write(client,0x07, buf); + + buf = epl_sensor.als.compare_reset | epl_sensor.als.lock; + epl_sensor_I2C_Write(client,0x12, buf); + + set_lsensor_intr_threshold(epl_sensor.als.low_threshold, epl_sensor.als.high_threshold); + } + //set mode and wait + buf = epl_sensor.wait | epl_sensor.mode; + epl_sensor_I2C_Write(client,0x00, buf); +} + +static void set_als_ps_intr_type(struct i2c_client *client, bool ps_polling, bool als_polling) +{ + + //set als / ps interrupt control mode and trigger type + switch((ps_polling << 1) | als_polling) + { + case 0: // ps and als interrupt + epl_sensor.interrupt_control = EPL_INT_CTRL_ALS_OR_PS; + epl_sensor.als.interrupt_type = EPL_INTTY_ACTIVE; + epl_sensor.ps.interrupt_type = EPL_INTTY_ACTIVE; + break; + + case 1: //ps interrupt and als polling + epl_sensor.interrupt_control = EPL_INT_CTRL_PS; + epl_sensor.als.interrupt_type = EPL_INTTY_DISABLE; + epl_sensor.ps.interrupt_type = EPL_INTTY_ACTIVE; + break; + + case 2: // ps polling and als interrupt + epl_sensor.interrupt_control = EPL_INT_CTRL_ALS; + epl_sensor.als.interrupt_type = EPL_INTTY_ACTIVE; + epl_sensor.ps.interrupt_type = EPL_INTTY_DISABLE; + break; + + case 3: //ps and als polling + epl_sensor.interrupt_control = EPL_INT_CTRL_ALS_OR_PS; + epl_sensor.als.interrupt_type = EPL_INTTY_DISABLE; + epl_sensor.ps.interrupt_type = EPL_INTTY_DISABLE; + break; + } +} + +//====================initial global variable===============// +static void initial_global_variable(struct i2c_client *client, struct epl_sensor_priv *obj) +{ + + //general setting + epl_sensor.power = EPL_POWER_ON; + epl_sensor.reset = EPL_RESETN_RUN; + epl_sensor.mode = EPL_MODE_IDLE; + epl_sensor.wait = EPL_WAIT_20_MS; + epl_sensor.osc_sel = EPL_OSC_SEL_1MHZ; + + //als setting + epl_sensor.als.polling_mode = ALS_POLLING_MODE; + epl_sensor.als.integration_time = EPL_ALS_INTT_64; + epl_sensor.als.gain = EPL_GAIN_MID; + epl_sensor.als.adc = EPL_PSALS_ADC_13; + epl_sensor.als.cycle = EPL_CYCLE_16; + epl_sensor.als.interrupt_channel_select = EPL_ALS_INT_CHSEL_1; + epl_sensor.als.persist = EPL_PERIST_1; + epl_sensor.als.compare_reset = EPL_CMP_RESET; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor.als.report_type = CMC_BIT_RAW; //CMC_BIT_DYN_INT + epl_sensor.als.high_threshold = ALS_HIGH_THRESHOLD; + epl_sensor.als.low_threshold = ALS_LOW_THRESHOLD; + //als factory + epl_sensor.als.factory.calibration_enable = false; + epl_sensor.als.factory.calibrated = false; + epl_sensor.als.factory.lux_per_count = LUX_PER_COUNT; + +#if ALS_DYN_INTT + + epl_sensor.als.lsrc_type = CMC_BIT_LSRC_NON; + + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT) + { + dynamic_intt_idx = dynamic_intt_init_idx; + epl_sensor.als.integration_time = als_dynamic_intt_intt[dynamic_intt_idx]; + epl_sensor.als.gain = als_dynamic_intt_gain[dynamic_intt_idx]; + dynamic_intt_high_thr = als_dynamic_intt_high_thr[dynamic_intt_idx]; + dynamic_intt_low_thr = als_dynamic_intt_low_thr[dynamic_intt_idx]; + } + + c_gain = 21000; // 21000/1000=21 + + obj->lsource_thd_high = 1900; //different light source boundary (N) (1.9) + obj->lsource_thd_low = 1500; //1.5 + obj->c_gain_h = 21000; //fluorescent (C1) (21) + obj->c_gain_l = 15000; //incandescent (C2) (15) + +#endif + //ps setting + epl_sensor.ps.polling_mode = PS_POLLING_MODE; + epl_sensor.ps.integration_time = EPL_PS_INTT_80; + epl_sensor.ps.gain = EPL_GAIN_MID; +#if PS_DYN_K + dynk_min_ps_raw_data = 0xffff; //min raw data + dynk_max_ir_data = 50000; //max ch0 + dynk_low_offset = 500; //low thd offset + dynk_high_offset = 800; //high thd offset + dynk_change_thd_max = 30000; //change thd condition + dynk_thd_offset = 500; //change thd offset +#if PS_DYN_K_STR + dynk_enhance_max_ch0 = 50000; //enhance max ch0 + dynk_enhance_integration_time = EPL_ALS_INTT_64; + dynk_enhance_gain = EPL_GAIN_LOW; + dynk_enhance_adc = EPL_PSALS_ADC_12; +#endif +#endif + epl_sensor.ps.adc = EPL_PSALS_ADC_12; + epl_sensor.ps.cycle = EPL_CYCLE_32; + epl_sensor.ps.persist = EPL_PERIST_1; + epl_sensor.ps.ir_on_control = EPL_IR_ON_CTRL_ON; + epl_sensor.ps.ir_mode = EPL_IR_MODE_CURRENT; + epl_sensor.ps.ir_drive = EPL_IR_DRIVE_100; + epl_sensor.ps.compare_reset = EPL_CMP_RESET; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor.ps.high_threshold = PS_HIGH_THRESHOLD; + epl_sensor.ps.low_threshold = PS_LOW_THRESHOLD; + //ps factory + epl_sensor.ps.factory.calibration_enable = false; + epl_sensor.ps.factory.calibrated = false; + epl_sensor.ps.factory.cancelation= 0; + +#if HS_ENABLE + //hs setting + epl_sensor.hs.integration_time = EPL_PS_INTT_80; + epl_sensor.hs.integration_time_max = EPL_PS_INTT_272; + epl_sensor.hs.integration_time_min = EPL_PS_INTT_32; + epl_sensor.hs.gain = EPL_GAIN_LOW; + epl_sensor.hs.adc = EPL_PSALS_ADC_11; + epl_sensor.hs.cycle = EPL_CYCLE_4; + epl_sensor.hs.ir_on_control = EPL_IR_ON_CTRL_ON; + epl_sensor.hs.ir_mode = EPL_IR_MODE_CURRENT; + epl_sensor.hs.ir_driver = EPL_IR_DRIVE_200; + epl_sensor.hs.compare_reset = EPL_CMP_RESET; + epl_sensor.hs.lock = EPL_UN_LOCK; + epl_sensor.hs.low_threshold = 6400; + epl_sensor.hs.mid_threshold = 25600; + epl_sensor.hs.high_threshold = 60800; +#endif + +#if PS_GES + //ps setting + epl_sensor.ges.polling_mode = PS_POLLING_MODE; + epl_sensor.ges.integration_time = EPL_PS_INTT_80; + epl_sensor.ges.gain = EPL_GAIN_LOW; + epl_sensor.ges.adc = EPL_PSALS_ADC_12; + epl_sensor.ges.cycle = EPL_CYCLE_2; + epl_sensor.ges.persist = EPL_PERIST_1; + epl_sensor.ges.ir_on_control = EPL_IR_ON_CTRL_ON; + epl_sensor.ges.ir_mode = EPL_IR_MODE_CURRENT; + epl_sensor.ges.ir_drive = EPL_IR_DRIVE_200; + epl_sensor.ges.compare_reset = EPL_CMP_RESET; + epl_sensor.ges.lock = EPL_UN_LOCK; + epl_sensor.ges.high_threshold = ges_threshold_high; + epl_sensor.ges.low_threshold = ges_threshold_low; +#endif + + set_als_ps_intr_type(client, epl_sensor.ps.polling_mode, epl_sensor.als.polling_mode); + //write setting to sensor + write_global_variable(client); +} + +#if HS_ENABLE +int epl_sensor_read_hs(struct i2c_client *client) +{ + u8 buf; + + if(client == NULL) + { + LOG_ERR("CLIENT CANN'T EQUL NULL\n"); + return -1; + } + + mutex_lock(&hs_sensor_mutex); + epl_sensor_I2C_Read(client,0x1e, 2); + epl_sensor.hs.raw = (gRawData.raw_bytes[1]<<8) | gRawData.raw_bytes[0]; + LOG_INFO("epl_sensor.hs.raw=%d \r\n", epl_sensor.hs.raw); + if(epl_sensor.hs.dynamic_intt == true && epl_sensor.hs.raw>epl_sensor.hs.high_threshold && epl_sensor.hs.integration_time > epl_sensor.hs.integration_time_min) + { + epl_sensor.hs.integration_time -= 4; + buf = epl_sensor.hs.integration_time | epl_sensor.hs.gain; + epl_sensor_I2C_Write(client, 0x03, buf); + } + else if(epl_sensor.hs.dynamic_intt == true && epl_sensor.hs.raw>epl_sensor.hs.low_threshold && epl_sensor.hs.raw <epl_sensor.hs.mid_threshold && epl_sensor.hs.integration_time < epl_sensor.hs.integration_time_max) + { + epl_sensor.hs.integration_time += 4; + buf = epl_sensor.hs.integration_time | epl_sensor.hs.gain; + epl_sensor_I2C_Write(client, 0x03, buf); + } + + mutex_unlock(&hs_sensor_mutex); + + if(epl_sensor.hs.raws_count<200) + { + epl_sensor.hs.raws[epl_sensor.hs.raws_count] = epl_sensor.hs.raw; + epl_sensor.hs.raws_count++; + } + + return 0; +} +#endif + +#if PS_DYN_K +void epl_sensor_reset_dynk_thd(u8 last_status, u8 now_status) +{ + if(last_status==0 && now_status==1) + { +#if PS_DYN_K_STR + if( (epl_sensor.ps.saturation == 0) && (epl_sensor.ps.data.ir_data < dynk_max_ir_data) && (dynk_enhance_ch0 < dynk_enhance_max_ch0) ) +#else + if( (epl_sensor.ps.saturation == 0)&&(epl_sensor.ps.data.ir_data < dynk_max_ir_data) ) +#endif + { + dynk_min_ps_raw_data = epl_sensor.ps.data.data; + } + + dynk_thd_low = dynk_min_ps_raw_data + dynk_low_offset; + dynk_thd_high = dynk_min_ps_raw_data + dynk_high_offset; + + if(dynk_thd_low>65534) + dynk_thd_low = 65534; + if(dynk_thd_high>65535) + dynk_thd_high = 65535; +#if PS_DEBUG + LOG_INFO("[%s]:restart dynk ps raw = %d, min = %d, ir_data = %d\n", __func__, epl_sensor.ps.data.data, dynk_min_ps_raw_data, epl_sensor.ps.data.ir_data); + LOG_INFO("[%s]:restart dynk thre_l = %ld, thre_h = %ld\n", __func__, (long)dynk_thd_low, (long)dynk_thd_high); +#endif + eint_flag = false; + set_psensor_intr_threshold((u16)dynk_thd_low, (u16)dynk_thd_high); + eint_flag = true; + } + else if(last_status==1 && now_status==0) + { + dynk_change_flag = true; + } +} +#if PS_DYN_K_STR +void epl_sensor_enhance_enable(struct i2c_client *client, bool enable) +{ + bool cmp_flag = false; + u8 buf, now_cmp_l; + int enh_time=0, ps_time=0, total_time=0; + ps_time = ps_sensing_time(epl_sensor.ps.integration_time, epl_sensor.ps.adc, epl_sensor.ps.cycle); + + + //mutex_lock(&sensor_mutex); + polling_flag = false; + + + epl_sensor_I2C_Read(client, 0x1b, 1); + buf = gRawData.raw_bytes[0]; + epl_sensor.ps.compare_high = (buf & 0x10); + now_cmp_l = (buf & 0x08); + + if(now_cmp_l != epl_sensor.ps.compare_low) + { + LOG_INFO("[%s]: buf=0x%x, now_cmp_l=0x%x, epl_sensor.ps.compare_low=0x%x \r\n", __func__, buf, now_cmp_l, epl_sensor.ps.compare_low); + //PS unlock and reset + epl_sensor.ps.compare_reset = EPL_CMP_RESET; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(client, 0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + + cmp_flag = true; + } + + epl_sensor.ps.interrupt_flag = EPL_INT_CLEAR; + epl_sensor.als.interrupt_flag = EPL_INT_CLEAR; + + epl_sensor_I2C_Write(client, 0x00, epl_sensor.wait | EPL_MODE_IDLE); + + if(enable == true){ + enh_time = als_sensing_time(dynk_enhance_integration_time, dynk_enhance_adc, epl_sensor.als.cycle) / 2; + epl_sensor_I2C_Write(client, 0x01, dynk_enhance_integration_time | dynk_enhance_gain); + epl_sensor_I2C_Write(client, 0x02, dynk_enhance_adc | epl_sensor.als.cycle); + epl_sensor_I2C_Write(client, 0xfc, EPL_A_D | EPL_NORMAL| EPL_GFIN_ENABLE | EPL_VOS_ENABLE | EPL_DOC_OFF); + } + else + { + enh_time = als_sensing_time(epl_sensor.als.integration_time, epl_sensor.als.adc, epl_sensor.als.cycle); + epl_sensor_I2C_Write(client, 0x01, epl_sensor.als.integration_time | epl_sensor.als.gain); + epl_sensor_I2C_Write(client, 0x02, epl_sensor.als.adc | epl_sensor.als.cycle); + epl_sensor_I2C_Write(client, 0xfc, EPL_A_D | EPL_NORMAL| EPL_GFIN_ENABLE | EPL_VOS_ENABLE | EPL_DOC_ON); + } + + als_frame_time = enh_time; + ps_frame_time = ps_time; + + epl_sensor_I2C_Write(client, 0x00, epl_sensor.wait | epl_sensor.mode); + if(cmp_flag == true) + { + //PS unlock and run + epl_sensor.ps.compare_reset = EPL_CMP_RUN; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(client, 0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + + cmp_flag = false; + } + //mutex_unlock(&sensor_mutex); + + total_time = ps_time+enh_time+wait_value[epl_sensor.wait>>4]; + if((2*total_time) >= dynk_polling_delay) + { + dynk_polling_delay = 2*total_time+50; + LOG_INFO("[%s]: dynk_polling_delay=%d \r\n", __func__, dynk_polling_delay); + } + + msleep(total_time); + LOG_INFO("[%s] PS+ALS(%dms)\r\n", __func__, total_time); + + //mutex_lock(&sensor_mutex); + if(enable == true) + { + epl_sensor_read_als(client); + } + + if(epl_sensor.ps.interrupt_flag == EPL_INT_TRIGGER) + { + //PS unlock and run + epl_sensor.ps.compare_reset = EPL_CMP_RUN; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(client, 0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + } + polling_flag = true; + //mutex_unlock(&sensor_mutex); + +} +#endif + +void epl_sensor_restart_dynk_polling(void) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + cancel_delayed_work(&dynk_thd_polling_work); + queue_delayed_work(epld->epl_wq, &dynk_thd_polling_work,msecs_to_jiffies(dynk_polling_delay)); +} + +void epl_sensor_dynk_thd_polling_work(struct work_struct *work) +{ + struct epl_sensor_priv *obj = epl_sensor_obj; + + bool enable_ps = obj->enable_pflag==1 && obj->ps_suspend==0; +#if PS_DEBUG + bool enable_als = obj->enable_lflag==1 && obj->als_suspend==0; + LOG_INFO("[%s]:als / ps enable: %d / %d\n", __func__,enable_als, enable_ps); +#endif + + if(enable_ps == true) + { +#if PS_DYN_K_STR + dynk_enhance_flag = false; + if(polling_flag == true && eint_flag == true) + { + epl_sensor_enhance_enable(obj->client, true); + } +#endif + if(polling_flag == true && eint_flag == true && epl_sensor.ps.polling_mode == 0) + { + mutex_lock(&sensor_mutex); + epl_sensor_read_ps_status(obj->client); + epl_sensor_read_ps(obj->client); + mutex_unlock(&sensor_mutex); + } +#if PS_DYN_K_STR + if( (dynk_min_ps_raw_data > epl_sensor.ps.data.data) + && (epl_sensor.ps.saturation == 0) + && (epl_sensor.ps.data.ir_data < dynk_max_ir_data) + && (dynk_enhance_ch0 < dynk_enhance_max_ch0) ) +#else + if( (dynk_min_ps_raw_data > epl_sensor.ps.data.data) + && (epl_sensor.ps.saturation == 0) + && (epl_sensor.ps.data.ir_data < dynk_max_ir_data) ) +#endif + { + dynk_min_ps_raw_data = epl_sensor.ps.data.data; + dynk_thd_low = dynk_min_ps_raw_data + dynk_low_offset; + dynk_thd_high = dynk_min_ps_raw_data + dynk_high_offset; + + if(dynk_thd_low>65534) + dynk_thd_low = 65534; + if(dynk_thd_high>65535) + dynk_thd_high = 65535; +#if PS_DEBUG + LOG_INFO("[%s]:dyn ps raw = %d, min = %d, ir_data = %d\n", __func__, epl_sensor.ps.data.data, dynk_min_ps_raw_data, epl_sensor.ps.data.ir_data); +#endif + eint_flag = false; + mutex_lock(&sensor_mutex); + set_psensor_intr_threshold((u16)dynk_thd_low, (u16)dynk_thd_high); + mutex_unlock(&sensor_mutex); + eint_flag = true; +#if PS_DEBUG + LOG_INFO("[%s]:dyn k thre_l = %ld, thre_h = %ld\n", __func__, (long)dynk_thd_low, (long)dynk_thd_high); +#endif + } + else if(dynk_change_flag==true && (epl_sensor.ps.data.data>dynk_change_thd_max) && ( (epl_sensor.ps.compare_low >> 3)==0 )) + { + dynk_change_flag = false; + dynk_thd_low += dynk_thd_offset; + dynk_thd_high += dynk_thd_offset; + + if(dynk_thd_low>65534) + dynk_thd_low = 65534; + if(dynk_thd_high>65535) + dynk_thd_high = 65535; + + eint_flag = false; + mutex_lock(&sensor_mutex); + set_psensor_intr_threshold((u16)dynk_thd_low, (u16)dynk_thd_high); + mutex_unlock(&sensor_mutex); + eint_flag = true; +#if PS_DEBUG + LOG_INFO("[%s]: epl_sensor.ps.data.data=%d, L/H=%ld/%ld \r\n", __func__, epl_sensor.ps.data.data, (long)dynk_thd_low, (long)dynk_thd_high); +#endif + } +#if PS_DYN_K_STR + if(polling_flag == true && eint_flag == true) + { + epl_sensor_enhance_enable(obj->client, false); + } + dynk_enhance_flag = true; +#endif + queue_delayed_work(obj->epl_wq, &dynk_thd_polling_work,msecs_to_jiffies(dynk_polling_delay)); + } +} +#endif + +/************************************************************************/ +//for 3637 +static int als_sensing_time(int intt, int adc, int cycle) +{ + long sensing_us_time; + int sensing_ms_time; + int als_intt, als_adc, als_cycle; + + als_intt = als_intt_value[intt>>2]; + als_adc = adc_value[adc>>3]; + als_cycle = cycle_value[cycle]; +#if COMMON_DEBUG + LOG_INFO("ALS: INTT=%d, ADC=%d, Cycle=%d \r\n", als_intt, als_adc, als_cycle); +#endif + + sensing_us_time = (als_intt + als_adc*2*2) * 2 * als_cycle; + sensing_ms_time = sensing_us_time / 1000; + +#if COMMON_DEBUG + LOG_INFO("[%s]: sensing=%d ms \r\n", __func__, sensing_ms_time); +#endif + return (sensing_ms_time + 5); +} + +static int ps_sensing_time(int intt, int adc, int cycle) +{ + long sensing_us_time; + int sensing_ms_time; + int ps_intt, ps_adc, ps_cycle; + + ps_intt = ps_intt_value[intt>>2]; + ps_adc = adc_value[adc>>3]; + ps_cycle = cycle_value[cycle]; +#if COMMON_DEBUG + LOG_INFO("PS: INTT=%d, ADC=%d, Cycle=%d \r\n", ps_intt, ps_adc, ps_cycle); +#endif + + sensing_us_time = (ps_intt*3 + ps_adc*2*3) * ps_cycle; + sensing_ms_time = sensing_us_time / 1000; +#if COMMON_DEBUG + LOG_INFO("[%s]: sensing=%d ms\r\n", __func__, sensing_ms_time); +#endif + + + return (sensing_ms_time + 5); +} + +static int epl_sensor_get_wait_time(int ps_time, int als_time) +{ + int wait_idx = 0; + int wait_time = 0; + + wait_time = als_time - ps_time; + if(wait_time < 0){ + wait_time = 0; + } +#if COMMON_DEBUG + LOG_INFO("[%s]: wait_len = %d \r\n", __func__, wait_len); +#endif + for(wait_idx = 0; wait_idx < wait_len; wait_idx++) + { + if(wait_time < wait_value[wait_idx]) + { + break; + } + } + if(wait_idx >= wait_len){ + wait_idx = wait_len - 1; + } + +#if COMMON_DEBUG + LOG_INFO("[%s]: wait_idx = %d, wait = %dms \r\n", __func__, wait_idx, wait_value[wait_idx]); +#endif + return (wait_idx << 4); +} +/************************************************************************/ + +void epl_sensor_update_mode(struct i2c_client *client) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int als_time = 0, ps_time = 0; + + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; + bool enable_als = epld->enable_lflag==1 && epld->als_suspend==0; +#if HS_ENABLE + bool enable_hs = epld->enable_hflag==1 && epld->hs_suspend==0; +#endif +#if PS_GES + bool enable_ges = epld->enable_gflag==1 && epld->ges_suspend==0; +#endif + polling_flag = false; + als_time = als_sensing_time(epl_sensor.als.integration_time, epl_sensor.als.adc, epl_sensor.als.cycle); + ps_time = ps_sensing_time(epl_sensor.ps.integration_time, epl_sensor.ps.adc, epl_sensor.ps.cycle); + + als_frame_time = als_time; + ps_frame_time = ps_time; + +#if HS_ENABLE + if(enable_hs) + { + LOG_INFO("[%s]: HS mode \r\n", __func__); + epl_sensor_restart_polling(); + } + else +#endif + { + //PS unlock and reset + epl_sensor.ps.compare_reset = EPL_CMP_RESET; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client, 0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + + //ALS unlock and reset + epl_sensor.als.compare_reset = EPL_CMP_RESET; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client, 0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + + + //epl_sensor_I2C_Write(epld->client, 0x11, EPL_RESETN_RESET | EPL_POWER_OFF); + epl_sensor.ps.interrupt_flag = EPL_INT_CLEAR; + epl_sensor.als.interrupt_flag = EPL_INT_CLEAR; + +#if PS_GES + LOG_INFO("mode selection =0x%x\n", (enable_ps|enable_ges) | (enable_als << 1)); +#else + LOG_INFO("mode selection =0x%x\n", enable_ps | (enable_als << 1)); +#endif + +#if PS_GES + //**** mode selection **** + switch((enable_als << 1) | (enable_ps|enable_ges)) +#else + //**** mode selection **** + switch((enable_als << 1) | enable_ps) +#endif + { + case 0: //disable all + epl_sensor.mode = EPL_MODE_IDLE; + break; + + case 1: //als = 0, ps = 1 +#if PS_DYN_K && PS_DYN_K_STR + epl_sensor.mode = EPL_MODE_ALS_PS; +#else + epl_sensor.mode = EPL_MODE_PS; +#endif + break; + + case 2: //als = 1, ps = 0 + epl_sensor.mode = EPL_MODE_ALS; + break; + + case 3: //als = 1, ps = 1 + epl_sensor.mode = EPL_MODE_ALS_PS; + break; + } + + //**** write setting **** + // step 1. set sensor at idle mode + // step 2. set sensor at operation mode + // step 3. delay sensing time + // step 4. unlock and run als / ps status + epl_sensor_I2C_Write(epld->client, 0x00, epl_sensor.wait | EPL_MODE_IDLE); + // initial factory calibration variable + read_factory_calibration(); + + epl_sensor_I2C_Write(epld->client, 0x02, epl_sensor.als.adc | epl_sensor.als.cycle); + set_als_ps_intr_type(epld->client, epl_sensor.ps.polling_mode, epl_sensor.als.polling_mode); + + epl_sensor_I2C_Write(epld->client, 0x06, epl_sensor.interrupt_control | epl_sensor.ps.persist |epl_sensor.ps.interrupt_type); + + epl_sensor_I2C_Write(epld->client, 0x07, epl_sensor.als.interrupt_channel_select | epl_sensor.als.persist | epl_sensor.als.interrupt_type); + +#if ALS_DYN_INTT + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT){ + epl_sensor_I2C_Write(client, 0x01, epl_sensor.als.integration_time | epl_sensor.als.gain); + } +#endif + + if(epl_sensor.mode == EPL_MODE_ALS_PS && epl_sensor.als.polling_mode == 0 && epl_sensor.ps.polling_mode == 0){ + int wait = 0; + wait = epl_sensor_get_wait_time(ps_time, als_time); + epl_sensor_I2C_Write(epld->client, 0x00, wait | epl_sensor.mode); + } + else{ +#if PS_GES + if(enable_ges == 1 && enable_ps == 0) + { + set_psensor_intr_threshold(epl_sensor.ges.low_threshold, epl_sensor.ges.high_threshold); + epl_sensor_I2C_Write(client, 0x00, EPL_WAIT_2_MS | epl_sensor.mode); + } + else +#endif + epl_sensor_I2C_Write(epld->client, 0x00, epl_sensor.wait | epl_sensor.mode); + } + + //ALS unlock and run + epl_sensor.als.compare_reset = EPL_CMP_RUN; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client, 0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + + //PS unlock and run + epl_sensor.ps.compare_reset = EPL_CMP_RUN; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client, 0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + + //epl_sensor_I2C_Write(epld->client, 0x11, epl_sensor.reset | epl_sensor.power); + +#if COMMON_DEBUG + //**** check setting **** + if(enable_ps == 1) + { + LOG_INFO("[%s] PS:low_thd = %d, high_thd = %d \n",__func__, epl_sensor.ps.low_threshold, epl_sensor.ps.high_threshold); + } + + if(enable_als == 1 && epl_sensor.als.polling_mode == 0) + { + LOG_INFO("[%s] ALS:low_thd = %d, high_thd = %d \n",__func__, epl_sensor.als.low_threshold, epl_sensor.als.high_threshold); + } +#if PS_GES + if(enable_ges) + { + LOG_INFO("[%s] GES:low_thd = %d, high_thd = %d \n",__func__, epl_sensor.ges.low_threshold, epl_sensor.ges.high_threshold); + + } +#endif + + LOG_INFO("[%s] reg0x00= 0x%x \n", __func__, epl_sensor.wait | epl_sensor.mode); + LOG_INFO("[%s] reg0x07= 0x%x \n", __func__, epl_sensor.als.interrupt_channel_select | epl_sensor.als.persist | epl_sensor.als.interrupt_type); + LOG_INFO("[%s] reg0x06= 0x%x \n", __func__, epl_sensor.interrupt_control | epl_sensor.ps.persist |epl_sensor.ps.interrupt_type); + LOG_INFO("[%s] reg0x11= 0x%x \n", __func__, epl_sensor.power | epl_sensor.reset); + LOG_INFO("[%s] reg0x12= 0x%x \n", __func__, epl_sensor.als.compare_reset | epl_sensor.als.lock); + LOG_INFO("[%s] reg0x1b= 0x%x \n", __func__, epl_sensor.ps.compare_reset | epl_sensor.ps.lock); +#endif + + if(epl_sensor.mode == EPL_MODE_PS) + { + msleep(ps_time); + LOG_INFO("[%s] PS only(%dms)\r\n", __func__, ps_time); + } + else if (epl_sensor.mode == EPL_MODE_ALS) + { + msleep(als_time); + LOG_INFO("[%s] ALS only(%dms)\r\n", __func__, als_time); + } + else if (epl_sensor.mode == EPL_MODE_ALS_PS) + { + msleep(ps_time+als_time+wait_value[epl_sensor.wait>>4]); + LOG_INFO("[%s] PS+ALS(%dms)\r\n", __func__, ps_time+als_time+wait_value[epl_sensor.wait>>4]); + } + + if(epl_sensor.ps.interrupt_flag == EPL_INT_TRIGGER) + { + //PS unlock and run + epl_sensor.ps.compare_reset = EPL_CMP_RUN; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client, 0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + } + + if(epl_sensor.als.interrupt_flag == EPL_INT_TRIGGER) + { + //ALS unlock and run + epl_sensor.als.compare_reset = EPL_CMP_RUN; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client, 0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + } + +#if PS_GES + if((enable_als==1 && epl_sensor.als.polling_mode==1) || (enable_ps==1 && epl_sensor.ps.polling_mode==1) || (enable_ges == 1 && epl_sensor.ges.polling_mode==1)) +#else + if((enable_als==1 && epl_sensor.als.polling_mode==1) || (enable_ps==1 && epl_sensor.ps.polling_mode==1)) +#endif + { + epl_sensor_restart_polling(); + } + +#if PS_DYN_K + if(enable_ps == 1) + { + epl_sensor_restart_dynk_polling(); + } +#endif + } + polling_flag = true; + +} + +/*----------------------------------------------------------------------------*/ +static void epl_sensor_polling_work(struct work_struct *work) +{ + + struct epl_sensor_priv *epld = epl_sensor_obj; + struct i2c_client *client = epld->client; + + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; + bool enable_als = epld->enable_lflag==1 && epld->als_suspend==0; +#if HS_ENABLE + bool enable_hs = epld->enable_hflag==1 && epld->hs_suspend==0; + +#endif +#if PS_GES + bool enable_ges = epld->enable_gflag==1 && epld->ges_suspend==0; +#endif + +#if COMMON_DEBUG +#if HS_ENABLE && PS_GES + LOG_INFO("enable_pflag=%d, enable_lflag=%d, enable_hs=%d, enable_ges=%d \n", enable_ps, enable_als, enable_hs, enable_ges); +#elif HS_ENABLE + LOG_INFO("enable_pflag=%d, enable_lflag=%d, enable_hs=%d\n", enable_ps, enable_als, enable_hs); +#elif PS_GES + LOG_INFO("enable_pflag=%d, enable_lflag=%d, enable_ges=%d\n", enable_ps, enable_als, enable_ges); +#else + LOG_INFO("enable_pflag = %d, enable_lflag = %d \n", enable_ps, enable_als); +#endif +#endif + +#if PS_DYN_K && PS_DYN_K_STR + if(polling_flag == false && eint_flag == false && dynk_enhance_flag == false) +#else + if(polling_flag == false && eint_flag == false) +#endif + { +#if PS_DYN_K && PS_DYN_K_STR + LOG_INFO("[%s]: epl_sensor_update_mode(%d) or eint_work(%d) is running, dynk_enhance_flag=%d !\r\n", __func__, polling_flag, eint_flag, dynk_enhance_flag); +#else + LOG_INFO("[%s]: epl_sensor_update_mode(%d) or eint_work(%d) is running !\r\n", __func__, polling_flag, eint_flag); +#endif + return; + } + + cancel_delayed_work(&polling_work); + + if( (enable_als && epl_sensor.als.polling_mode == 1) || (enable_ps && epl_sensor.ps.polling_mode == 1) ) + { + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(polling_time)); + } + +#if HS_ENABLE + if (enable_hs) + { + queue_delayed_work(epld->epl_wq, &polling_work,msecs_to_jiffies(20)); + epl_sensor_read_hs(client); + } +#if PS_GES /*PS_GES*/ + else if (enable_ges && epl_sensor.ges.polling_mode==1) +#endif /*PS_GES*/ +#endif + +#if PS_GES +#if !HS_ENABLE /*HS_ENABLE*/ + if (enable_ges && epl_sensor.ges.polling_mode==1) +#endif /*HS_ENABLE*/ + { + queue_delayed_work(epld->epl_wq, &polling_work, msecs_to_jiffies(polling_time)); + epl_sensor_read_ps(epld->client); + epl_sensor_read_ps_status(epld->client); + } +#endif + + if(enable_als && epl_sensor.als.polling_mode == 1) + { + int report_lux = 0; +#if PS_DYN_K && PS_DYN_K_STR + if(polling_flag == true && eint_flag == true && dynk_enhance_flag == true) +#else + if(polling_flag == true && eint_flag == true) +#endif + { + mutex_lock(&sensor_mutex); + //epl_sensor_read_als_status(client); // for test + epl_sensor_read_als(client); + mutex_unlock(&sensor_mutex); + report_lux = epl_sensor_get_als_value(epld, epl_sensor.als.data.channels[1]); + if(epl_sensor.als.report_type != CMC_BIT_DYN_INT){ + epl_sensor_report_lux(report_lux); + } + } + } + + if(enable_ps && epl_sensor.ps.polling_mode == 1) + { +#if PS_DYN_K && PS_DYN_K_STR + if(polling_flag == true && eint_flag == true && dynk_enhance_flag == true) +#else + if(polling_flag == true && eint_flag == true) +#endif + { + mutex_lock(&sensor_mutex); + epl_sensor_read_ps_status(client); + epl_sensor_read_ps(client); + mutex_unlock(&sensor_mutex); +#if PS_DYN_K + epl_sensor_reset_dynk_thd(dynk_last_status, (epl_sensor.ps.compare_low >> 3)); + dynk_last_status = (epl_sensor.ps.compare_low >> 3); +#endif + epl_sensor_report_ps_status(); + } + } +#if HS_ENABLE && PS_GES + if(enable_als==false && enable_ps==false && enable_hs==false && enable_ges==false) +#elif HS_ENABLE + if(enable_als==false && enable_ps==false && enable_hs==false) +#elif PS_GES + if(enable_als==false && enable_ps==false && enable_ges==false) +#else + if(enable_als==false && enable_ps==false) +#endif + { + cancel_delayed_work(&polling_work); + LOG_INFO("disable sensor\n"); + } + +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static irqreturn_t epl_sensor_eint_func(int irqNo, void *handle) +{ + struct epl_sensor_priv *epld = (struct epl_sensor_priv*)handle; + disable_irq_nosync(epld->irq); + //queue_work(epld->epl_wq, &epl_sensor_irq_work); + schedule_delayed_work(&epld->eint_work, 0); + + return IRQ_HANDLED; +} +/*----------------------------------------------------------------------------*/ +static void epl_sensor_intr_als_report_lux(void) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + + int report_lux = 0; + epl_sensor_read_als(epld->client); + + report_lux = epl_sensor_get_als_value(epld, epl_sensor.als.data.channels[1]); + epl_sensor_report_lux(report_lux); + + epl_sensor.als.compare_reset = EPL_CMP_RESET; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + + //set dynamic threshold + if(epl_sensor.als.compare_high >> 4) + { + epl_sensor.als.high_threshold = epl_sensor.als.high_threshold + 250; + epl_sensor.als.low_threshold = epl_sensor.als.low_threshold + 250; + + if (epl_sensor.als.high_threshold > 60000) + { + epl_sensor.als.high_threshold = epl_sensor.als.high_threshold -250; + epl_sensor.als.low_threshold = epl_sensor.als.low_threshold - 250; + } + } + if(epl_sensor.als.compare_low>> 3) + { + epl_sensor.als.high_threshold = epl_sensor.als.high_threshold -250; + epl_sensor.als.low_threshold = epl_sensor.als.low_threshold - 250; + + if (epl_sensor.als.high_threshold < 250) + { + epl_sensor.als.high_threshold = epl_sensor.als.high_threshold + 250; + epl_sensor.als.low_threshold = epl_sensor.als.low_threshold + 250; + } + } + + if(epl_sensor.als.high_threshold < epl_sensor.als.low_threshold) + { + LOG_INFO("[%s]:recover default setting \r\n", __FUNCTION__); + epl_sensor.als.high_threshold = ALS_HIGH_THRESHOLD; + epl_sensor.als.low_threshold = ALS_LOW_THRESHOLD; + } + + //write new threshold + set_lsensor_intr_threshold(epl_sensor.als.low_threshold, epl_sensor.als.high_threshold); +} +/*----------------------------------------------------------------------------*/ +static void epl_sensor_eint_work(struct work_struct *work) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_FUN(); + + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; + bool enable_als = epld->enable_lflag==1 && epld->als_suspend==0; +#if PS_GES + bool enable_ges = epld->enable_gflag==1 && epld->ges_suspend==0; +#endif + eint_flag = false; + mutex_lock(&sensor_mutex); + +#if PS_GES + if(enable_ges && epl_sensor.ges.polling_mode == 0) + { + epl_sensor_read_ps_status(epld->client); + if(polling_flag == true) + { + //GES unlock and run + epl_sensor.ges.compare_reset = EPL_CMP_RUN; + epl_sensor.ges.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x1b, epl_sensor.ges.compare_reset | epl_sensor.ges.lock); + } + } +#endif + if(enable_ps && epl_sensor.ps.polling_mode == 0) + { + epl_sensor_read_ps_status(epld->client); + if(epl_sensor.ps.interrupt_flag == EPL_INT_TRIGGER) + { +#if PS_DEBUG || PS_DYN_K + epl_sensor_read_ps(epld->client); +#endif + +#if PS_DYN_K + epl_sensor_reset_dynk_thd(dynk_last_status, (epl_sensor.ps.compare_low >> 3)); + dynk_last_status = (epl_sensor.ps.compare_low >> 3); +#endif + epl_sensor_report_ps_status(); + + if(polling_flag == true) + { + //PS unlock and run + epl_sensor.ps.compare_reset = EPL_CMP_RUN; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + } + + } + } + if(enable_als && epl_sensor.als.polling_mode == 0) + { + epl_sensor_read_als_status(epld->client); + if(epl_sensor.als.interrupt_flag == EPL_INT_TRIGGER) + { + epl_sensor_intr_als_report_lux(); + if(polling_flag == true) + { + //ALS unlock and run + epl_sensor.als.compare_reset = EPL_CMP_RUN; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + } + } + } + + //enable_irq(epld->irq); + mutex_unlock(&sensor_mutex); + eint_flag = true; + enable_irq(epld->irq); +} +/*----------------------------------------------------------------------------*/ +static int epl_sensor_setup_interrupt(struct epl_sensor_priv *epld) +{ + struct i2c_client *client = epld->client; + int err = 0; +#if QCOM + unsigned int irq_gpio; + unsigned int irq_gpio_flags; + struct device_node *np = client->dev.of_node; +#endif + msleep(5); +#if S5PV210 + err = gpio_request(S5PV210_GPH0(1), "Elan EPL IRQ"); + if (err) + { + LOG_ERR("gpio pin request fail (%d)\n", err); + goto initial_fail; + } + else + { + LOG_INFO("----- Samsung gpio config success -----\n"); + s3c_gpio_cfgpin(S5PV210_GPH0(1),S3C_GPIO_SFN(0x0F)/*(S5PV210_GPH0_1_EXT_INT30_1) */); + s3c_gpio_setpull(S5PV210_GPH0(1),S3C_GPIO_PULL_UP); + + } +#elif SPREAD + //epld->intr_pin = ELAN_INT_PIN; /*need setting*/ + err = gpio_request(epld->intr_pin, "Elan EPL IRQ"); + if (err) + { + LOG_ERR("gpio pin request fail (%d)\n", err); + goto initial_fail; + }else{ + gpio_direction_input(epld->intr_pin); + /*get irq*/ + client->irq = gpio_to_irq(epld->intr_pin); + epld->irq = client->irq; + LOG_INFO("IRQ number is %d\n", client->irq); + } +#elif QCOM + irq_gpio = of_get_named_gpio_flags(np, "epl,irq-gpio", 0, &irq_gpio_flags); + //irq_gpio = ELAN_INT_PIN; + epld->intr_pin = irq_gpio; + if (epld->intr_pin < 0) { + goto initial_fail; + } + + if (gpio_is_valid(epld->intr_pin)) { + err = gpio_request(epld->intr_pin, "epl_irq_gpio"); + if (err) { + LOG_ERR( "irq gpio request failed"); + goto initial_fail; + } + + err = gpio_direction_input(epld->intr_pin); + if (err) { + LOG_ERR("set_direction for irq gpio failed\n"); + goto initial_fail; + } + } +#elif LEADCORE + //epld->intr_pin = ELAN_INT_PIN; /*need setting*/ + epld->intr_pin = irq_to_gpio(client->irq); /*need confirm*/ + + err = gpio_request(epld->intr_pin, "epl irq"); /*ep12182 irq*/ + //err = gpio_request(epld->intr_pin, "ep12182 irq"); + if (err < 0){ + LOG_ERR("%s:Gpio request failed! \r\n", __func__); + goto initial_fail; + } + gpio_direction_input(epld->intr_pin); + client->irq = gpio_to_irq(epld->intr_pin); +#elif MARVELL + + epld->intr_pin = ELAN_INT_PIN; /*need setting*/ + if(client->irq <= 0) + { + LOG_ERR("client->irq(%d) Failed \r\n", client->irq); + goto initial_fail; + } +#endif + err = request_irq(epld->irq,epl_sensor_eint_func, IRQF_TRIGGER_FALLING, + "epl2182_pls", epld); + if(err <0) + { + LOG_ERR("request irq pin %d fail for gpio\n",err); + goto fail_free_intr_pin; + } + + return err; + +initial_fail: +fail_free_intr_pin: + gpio_free(epld->intr_pin); + //free_irq(epld->irq, epld); + return err; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ + +static ssize_t epl_sensor_show_reg(struct device *dev, struct device_attribute *attr, char *buf) +{ + + ssize_t len = 0; + struct i2c_client *client = epl_sensor_obj->client; + + if(!epl_sensor_obj) + { + LOG_ERR("epl_obj is null!!\n"); + return 0; + } + + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x00 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x00)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x01 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x01)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x02 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x02)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x03 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x03)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x04 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x04)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x05 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x05)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x06 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x06)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x07 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x07)); + if(epl_sensor.als.polling_mode == 0) + { + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x08 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x08)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x09 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x09)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x0A value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x0A)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x0B value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x0B)); + } + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x0C value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x0C)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x0D value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x0D)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x0E value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x0E)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x0F value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x0F)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x11 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x11)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x12 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x12)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x1B value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x1B)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x22 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x22)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x23 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x23)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x24 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x24)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0x25 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x25)); + len += snprintf(buf+len, PAGE_SIZE-len, "chip id REG 0xFC value = 0x%x\n", i2c_smbus_read_byte_data(client, 0xFC)); + + return len; + +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_show_status(struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + struct epl_sensor_priv *epld = epl_sensor_obj; + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; + bool enable_als = epld->enable_lflag==1 && epld->als_suspend==0; +#if HS_ENABLE + bool enable_hs = epld->enable_hflag==1 && epld->hs_suspend==0; +#endif + + if(!epl_sensor_obj) + { + LOG_ERR("epl_sensor_obj is null!!\n"); + return 0; + } + + len += snprintf(buf+len, PAGE_SIZE-len, "chip is %s, ver is %s \n", EPL_DEV_NAME, DRIVER_VERSION); + len += snprintf(buf+len, PAGE_SIZE-len, "als/ps polling is %d-%d\n", epl_sensor.als.polling_mode, epl_sensor.ps.polling_mode); + len += snprintf(buf+len, PAGE_SIZE-len, "wait = %d, mode = %d\n",epl_sensor.wait >> 4, epl_sensor.mode); + len += snprintf(buf+len, PAGE_SIZE-len, "interrupt control = %d\n", epl_sensor.interrupt_control >> 4); + len += snprintf(buf+len, PAGE_SIZE-len, "frame time ps=%dms, als=%dms\n", ps_frame_time, als_frame_time); +#if HS_ENABLE + if(enable_hs) + { + len += snprintf(buf+len, PAGE_SIZE-len, "hs adc= %d\n", epl_sensor.hs.adc>>3); + len += snprintf(buf+len, PAGE_SIZE-len, "hs int_time= %d\n", epl_sensor.hs.integration_time>>2); + len += snprintf(buf+len, PAGE_SIZE-len, "hs cycle= %d\n", epl_sensor.hs.cycle); + len += snprintf(buf+len, PAGE_SIZE-len, "hs gain= %d\n", epl_sensor.hs.gain); + len += snprintf(buf+len, PAGE_SIZE-len, "hs ch1 raw= %d\n", epl_sensor.hs.raw); + } +#endif + if(enable_ps) + { + len += snprintf(buf+len, PAGE_SIZE-len, "PS: \n"); + len += snprintf(buf+len, PAGE_SIZE-len, "INTEG = %d, gain = %d\n", epl_sensor.ps.integration_time >> 2, epl_sensor.ps.gain); + len += snprintf(buf+len, PAGE_SIZE-len, "ADC = %d, cycle = %d, ir drive = %d\n", epl_sensor.ps.adc >> 3, epl_sensor.ps.cycle, epl_sensor.ps.ir_drive); + len += snprintf(buf+len, PAGE_SIZE-len, "saturation = %d, int flag = %d\n", epl_sensor.ps.saturation >> 5, epl_sensor.ps.interrupt_flag >> 2); + len += snprintf(buf+len, PAGE_SIZE-len, "Thr(L/H) = (%d/%d)\n", epl_sensor.ps.low_threshold, epl_sensor.ps.high_threshold); +#if PS_DYN_K +#if PS_DYN_K_STR + len += snprintf(buf+len, PAGE_SIZE-len, "Dyn enhance ch0 = %d \n", dynk_enhance_ch0); +#endif + len += snprintf(buf+len, PAGE_SIZE-len, "Dyn thr(L/H) = (%ld/%ld)\n", (long)dynk_thd_low, (long)dynk_thd_high); +#endif + len += snprintf(buf+len, PAGE_SIZE-len, "pals data = %d, data = %d\n", epl_sensor.ps.data.ir_data, epl_sensor.ps.data.data); + } + if(enable_als) + { + len += snprintf(buf+len, PAGE_SIZE-len, "ALS: \n"); + len += snprintf(buf+len, PAGE_SIZE-len, "INTEG = %d, gain = %d\n", epl_sensor.als.integration_time >> 2, epl_sensor.als.gain); + len += snprintf(buf+len, PAGE_SIZE-len, "ADC = %d, cycle = %d\n", epl_sensor.als.adc >> 3, epl_sensor.als.cycle); +#if ALS_DYN_INTT + if(epl_sensor.als.lsrc_type != CMC_BIT_LSRC_NON) + { + len += snprintf(buf+len, PAGE_SIZE-len, "lsource_thd_low=%d, lsource_thd_high=%d \n", epld->lsource_thd_low, epld->lsource_thd_high); + len += snprintf(buf+len, PAGE_SIZE-len, "saturation = %d\n", epl_sensor.als.saturation >> 5); + + if(epl_sensor.als.lsrc_type == CMC_BIT_LSRC_SCALE || epl_sensor.als.lsrc_type == CMC_BIT_LSRC_BOTH) + { + len += snprintf(buf+len, PAGE_SIZE-len, "real_ratio = %d\n", epld->ratio); + len += snprintf(buf+len, PAGE_SIZE-len, "use_ratio = %d\n", epld->last_ratio); + } + else if(epl_sensor.als.lsrc_type == CMC_BIT_LSRC_SLOPE) + { + len += snprintf(buf+len, PAGE_SIZE-len, "ratio = %d\n", epld->ratio); + } + } + + len += snprintf(buf+len, PAGE_SIZE-len, "c_gain = %d\n", c_gain); + len += snprintf(buf+len, PAGE_SIZE-len, "dynamic_intt_lux = %d\n", dynamic_intt_lux); +#endif + if(epl_sensor.als.polling_mode == 0) + len += snprintf(buf+len, PAGE_SIZE-len, "Thr(L/H) = (%d/%d)\n", epl_sensor.als.low_threshold, epl_sensor.als.high_threshold); + len += snprintf(buf+len, PAGE_SIZE-len, "ch0 = %d, ch1 = %d\n", epl_sensor.als.data.channels[0], epl_sensor.als.data.channels[1]); + } + + return len; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_als_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + uint16_t mode=0; + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_FUN(); + + + sscanf(buf, "%hu",&mode); + if(epld->enable_lflag != mode) + { +#if ALS_DYN_INTT + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT) + { + dynamic_intt_idx = dynamic_intt_init_idx; + epl_sensor.als.integration_time = als_dynamic_intt_intt[dynamic_intt_idx]; + epl_sensor.als.gain = als_dynamic_intt_gain[dynamic_intt_idx]; + dynamic_intt_high_thr = als_dynamic_intt_high_thr[dynamic_intt_idx]; + dynamic_intt_low_thr = als_dynamic_intt_low_thr[dynamic_intt_idx]; + } +#endif + epld->enable_lflag = mode; + + epl_sensor_update_mode(epld->client); + } + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_ps_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + uint16_t mode=0; + struct epl_sensor_priv *epld = epl_sensor_obj; +#if HS_ENABLE + bool enable_hs = epld->enable_hflag==1 && epld->hs_suspend==0; +#endif +#if PS_GES + bool enable_ges = epld->enable_gflag==1 && epld->ges_suspend==0; +#endif + LOG_FUN(); + + sscanf(buf, "%hu",&mode); +#if HS_ENABLE + if(enable_hs == 1 && mode == 1) + { + epld->enable_hflag = 0; + if(hs_enable_flag == true) + { + epld->enable_lflag = 1; + hs_enable_flag = false; + } + write_global_variable(epld->client); + LOG_INFO("[%s] Disable HS and recover ps setting \r\n", __func__); + } +#endif +#if PS_GES + if(enable_ges == 1 && mode == 1) + { + epld->enable_gflag = 0; + write_global_variable(epld->client); + ps_ges_enable_flag = true; + LOG_INFO("[%s] Disable GES and recover ps setting \r\n", __func__); + } + else if (ps_ges_enable_flag == true && mode == 0) + { + epld->enable_gflag = 1; + write_global_variable(epld->client); + ps_ges_enable_flag = false; + LOG_INFO("[%s] enable GES and recover ges setting \r\n", __func__); + } +#endif + if(epld->enable_pflag != mode) + { + epld->enable_pflag = mode; + if(mode) + { + wake_lock(&ps_lock); +#if PS_DYN_K + dynk_min_ps_raw_data = 0xffff; + dynk_change_flag = false; +#if PS_DYN_K_STR + dynk_enhance_flag = false; +#endif +#endif + } + else + { +#if PS_DYN_K + cancel_delayed_work(&dynk_thd_polling_work); +#endif + wake_unlock(&ps_lock); + } + + epl_sensor_update_mode(epld->client); + } + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_show_cal_raw(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + u16 ch1 = 0; + u32 ch1_all=0; + int count =5; + int i; + ssize_t len = 0; +#if !PS_DYN_K + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; +#endif + + if(!epl_sensor_obj) + { + LOG_ERR("epl_sensor_obj is null!!\n"); + return 0; + } + + + + for(i=0; i<count; i++) + { + switch(epl_sensor.mode) + { + msleep(50); +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: +#endif +#if !PS_DYN_K + if(enable_ps == true && polling_flag == true && eint_flag == true && epl_sensor.ps.polling_mode == 0) + epl_sensor_read_ps(epld->client); +#endif + ch1 = epl_sensor.ps.data.data; + break; + + case EPL_MODE_ALS: + if(epl_sensor.als.polling_mode == 0) + epl_sensor_read_als(epld->client); + ch1 = epl_sensor.als.data.channels[1]; + break; + } + + ch1_all = ch1_all + ch1; + if(epl_sensor.wait == EPL_WAIT_SINGLE) + epl_sensor_I2C_Write(epld->client,0x11, epl_sensor.power | epl_sensor.reset); + } + + ch1 = (u16)(ch1_all/count); + + LOG_INFO("cal_raw = %d \r\n" , ch1); + + len += snprintf(buf + len, PAGE_SIZE - len, "%d \r\n", ch1); + + return len; +} + + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_threshold(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int hthr = 0, lthr = 0; + if(!epld) + { + LOG_ERR("epl_sensor_obj is null!!\n"); + return 0; + } + + switch(epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: +#endif + sscanf(buf, "%d,%d", <hr, &hthr); + epl_sensor.ps.low_threshold = lthr; + epl_sensor.ps.high_threshold = hthr; + set_psensor_intr_threshold(epl_sensor.ps.low_threshold, epl_sensor.ps.high_threshold); + break; + + case EPL_MODE_ALS: + sscanf(buf, "%d,%d", <hr, &hthr); + epl_sensor.als.low_threshold = lthr; + epl_sensor.als.high_threshold = hthr; + set_lsensor_intr_threshold(epl_sensor.als.low_threshold, epl_sensor.als.high_threshold); + break; + + } + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_wait_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int val; + sscanf(buf, "%d",&val); + + epl_sensor.wait = (val & 0xf) << 4; + + return count; +} +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int value = 0; + LOG_FUN(); + + sscanf(buf, "%d", &value); + + value = value & 0x03; + + switch (epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: //ps +#endif + epl_sensor.ps.gain = value; + epl_sensor_I2C_Write(epld->client, 0x03, epl_sensor.ps.integration_time | epl_sensor.ps.gain); + break; + + case EPL_MODE_ALS: //als + epl_sensor.als.gain = value; + epl_sensor_I2C_Write(epld->client, 0x01, epl_sensor.als.integration_time | epl_sensor.als.gain); + break; + } + + epl_sensor_update_mode(epld->client); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int value=0; + + LOG_FUN(); + + epld->enable_pflag = 0; + epld->enable_lflag = 0; + + sscanf(buf, "%d",&value); + + switch (value) + { + case 0: + epl_sensor.mode = EPL_MODE_IDLE; + break; + + case 1: + epld->enable_lflag = 1; + epl_sensor.mode = EPL_MODE_ALS; + break; + + case 2: + epld->enable_pflag = 1; + epl_sensor.mode = EPL_MODE_PS; + break; + + case 3: + epld->enable_lflag = 1; + epld->enable_pflag = 1; + epl_sensor.mode = EPL_MODE_ALS_PS; + break; + } + + epl_sensor_update_mode(epld->client); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_ir_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + sscanf(buf, "%d", &value); + + switch (epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: //ps +#endif + switch(value) + { + case 0: + epl_sensor.ps.ir_mode = EPL_IR_MODE_CURRENT; + break; + + case 1: + epl_sensor.ps.ir_mode = EPL_IR_MODE_VOLTAGE; + break; + } + + epl_sensor_I2C_Write(epld->client,0x05, epl_sensor.ps.ir_on_control | epl_sensor.ps.ir_mode | epl_sensor.ps.ir_drive); + break; + } + + epl_sensor_I2C_Write(epld->client,0x00,epl_sensor.wait | epl_sensor.mode); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_ir_contrl(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + uint8_t data; + + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + sscanf(buf, "%d",&value); + + switch (epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: //ps +#endif + switch(value) + { + case 0: + epl_sensor.ps.ir_on_control = EPL_IR_ON_CTRL_OFF; + break; + + case 1: + epl_sensor.ps.ir_on_control = EPL_IR_ON_CTRL_ON; + break; + } + + data = epl_sensor.ps.ir_on_control | epl_sensor.ps.ir_mode | epl_sensor.ps.ir_drive; + LOG_INFO("[%s]: 0x05 = 0x%x\n", __FUNCTION__, data); + + epl_sensor_I2C_Write(epld->client,0x05, epl_sensor.ps.ir_on_control | epl_sensor.ps.ir_mode | epl_sensor.ps.ir_drive); + break; + } + + epl_sensor_I2C_Write(epld->client,0x00,epl_sensor.wait | epl_sensor.mode); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_ir_drive(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + sscanf(buf, "%d", &value); + + switch(epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: +#endif + epl_sensor.ps.ir_drive = (value & 0x03); + epl_sensor_I2C_Write(epld->client,0x05, epl_sensor.ps.ir_on_control | epl_sensor.ps.ir_mode | epl_sensor.ps.ir_drive); + break; + } + + epl_sensor_I2C_Write(epld->client,0x00,epl_sensor.wait | epl_sensor.mode); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_interrupt_type(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + sscanf(buf, "%d",&value); + + switch (epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: //ps +#endif + if(!epl_sensor.ps.polling_mode) + { + epl_sensor.ps.interrupt_type = value & 0x03; + epl_sensor_I2C_Write(epld->client,0x06, epl_sensor.interrupt_control | epl_sensor.ps.persist |epl_sensor.ps.interrupt_type); + LOG_INFO("[%s]: 0x06 = 0x%x\n", __FUNCTION__, epl_sensor.interrupt_control | epl_sensor.ps.persist |epl_sensor.ps.interrupt_type); + } + break; + + case EPL_MODE_ALS: //als + if(!epl_sensor.als.polling_mode) + { + epl_sensor.als.interrupt_type = value & 0x03; + epl_sensor_I2C_Write(epld->client,0x07, epl_sensor.als.interrupt_channel_select | epl_sensor.als.persist | epl_sensor.als.interrupt_type); + LOG_INFO("[%s]: 0x07 = 0x%x\n", __FUNCTION__, epl_sensor.als.interrupt_channel_select | epl_sensor.als.persist | epl_sensor.als.interrupt_type); + } + break; + } + + return count; +} + +/*----------------------------------------------------------------------------*/ + +static ssize_t epl_sensor_store_ps_polling_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int polling_mode = 0; + + + sscanf(buf, "%d",&polling_mode); + epl_sensor.ps.polling_mode = polling_mode; +#if PS_GES + epl_sensor.ges.polling_mode = polling_mode; +#endif + + epl_sensor_update_mode(epld->client); + + return count; +} + +static ssize_t epl_sensor_store_als_polling_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int polling_mode = 0; + + sscanf(buf, "%d",&polling_mode); + epl_sensor.als.polling_mode = polling_mode; + + epl_sensor_update_mode(epld->client); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_integration(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + sscanf(buf, "%d",&value); + + switch (epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: //ps +#endif + epl_sensor.ps.integration_time = (value & 0xf) << 2; + epl_sensor_I2C_Write(epld->client,0x03, epl_sensor.ps.integration_time | epl_sensor.ps.gain); + epl_sensor_I2C_Read(epld->client, 0x03, 1); + LOG_INFO("[%s]: 0x03 = 0x%x (0x%x)\n", __FUNCTION__, epl_sensor.ps.integration_time | epl_sensor.ps.gain, gRawData.raw_bytes[0]); + break; + + case EPL_MODE_ALS: //als + epl_sensor.als.integration_time = (value & 0xf) << 2; + epl_sensor_I2C_Write(epld->client,0x01, epl_sensor.als.integration_time | epl_sensor.als.gain); + epl_sensor_I2C_Read(epld->client, 0x01, 1); + LOG_INFO("[%s]: 0x01 = 0x%x (0x%x)\n", __FUNCTION__, epl_sensor.als.integration_time | epl_sensor.als.gain, gRawData.raw_bytes[0]); + break; + } + + epl_sensor_update_mode(epld->client); + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_adc(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + sscanf(buf, "%d",&value); + + switch (epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: //ps +#endif + epl_sensor.ps.adc = (value & 0x3) << 3; + epl_sensor_I2C_Write(epld->client,0x04, epl_sensor.ps.adc | epl_sensor.ps.cycle); + epl_sensor_I2C_Read(epld->client, 0x04, 1); + LOG_INFO("[%s]:0x04 = 0x%x (0x%x)\n", __FUNCTION__, epl_sensor.ps.adc | epl_sensor.ps.cycle, gRawData.raw_bytes[0]); + break; + + case EPL_MODE_ALS: //als + epl_sensor.als.adc = (value & 0x3) << 3; + epl_sensor_I2C_Write(epld->client,0x02, epl_sensor.als.adc | epl_sensor.als.cycle); + epl_sensor_I2C_Read(epld->client, 0x02, 1); + LOG_INFO("[%s]:0x02 = 0x%x (0x%x)\n", __FUNCTION__, epl_sensor.als.adc | epl_sensor.als.cycle, gRawData.raw_bytes[0]); + break; + } + + epl_sensor_update_mode(epld->client); + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_cycle(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + sscanf(buf, "%d",&value); + + switch (epl_sensor.mode) + { +#if PS_DYN_K_STR + case EPL_MODE_ALS_PS: +#else + case EPL_MODE_PS: //ps +#endif + epl_sensor.ps.cycle = (value & 0x7); + epl_sensor_I2C_Write(epld->client,0x04, epl_sensor.ps.adc | epl_sensor.ps.cycle); + LOG_INFO("[%s]:0x04 = 0x%x (0x%x)\n", __FUNCTION__, epl_sensor.ps.adc | epl_sensor.ps.cycle, gRawData.raw_bytes[0]); + break; + + case EPL_MODE_ALS: //als + epl_sensor.als.cycle = (value & 0x7); + epl_sensor_I2C_Write(epld->client,0x02, epl_sensor.als.adc | epl_sensor.als.cycle); + LOG_INFO("[%s]:0x02 = 0x%x (0x%x)\n", __FUNCTION__, epl_sensor.als.adc | epl_sensor.als.cycle, gRawData.raw_bytes[0]); + break; + } + + epl_sensor_update_mode(epld->client); + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_als_report_type(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int value=0; + + LOG_FUN(); + + sscanf(buf, "%d", &value); + epl_sensor.als.report_type = value & 0xf; + + return count; +} +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_store_ps_w_calfile(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int ps_hthr=0, ps_lthr=0, ps_cancelation=0; + int ps_cal_len = 0; + char ps_calibration[20]; + LOG_FUN(); + + if(!epl_sensor_obj) + { + LOG_ERR("epl_obj is null!!\n"); + return 0; + } + sscanf(buf, "%d,%d,%d",&ps_cancelation, &ps_hthr, &ps_lthr); + + ps_cal_len = sprintf(ps_calibration, "%d,%d,%d", ps_cancelation, ps_hthr, ps_lthr); + + write_factory_calibration(epld, ps_calibration, ps_cal_len); + return count; +} +/*----------------------------------------------------------------------------*/ + +static ssize_t epl_sensor_store_reg_write(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int reg; + int data; + LOG_FUN(); + + sscanf(buf, "%x,%x",®, &data); + + LOG_INFO("[%s]: reg=0x%x, data=0x%x", __func__, reg, data); + epl_sensor_I2C_Write(epld->client, reg, data); + + return count; +} + +static ssize_t epl_sensor_store_unlock(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int mode; + LOG_FUN(); + + sscanf(buf, "%d",&mode); + + LOG_INFO("mode = %d \r\n", mode); + switch (mode) + { + case 0: //ps + //PS unlock and run + epl_sensor.ps.compare_reset = EPL_CMP_RUN; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + break; + + case 1: //als + //ALS unlock and run + epl_sensor.als.compare_reset = EPL_CMP_RUN; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + break; + + case 2: //als unlock and reset + epl_sensor.als.compare_reset = EPL_CMP_RESET; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + break; + + case 3: //ps+als + //PS unlock and run + epl_sensor.ps.compare_reset = EPL_CMP_RUN; + epl_sensor.ps.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x1b, epl_sensor.ps.compare_reset |epl_sensor.ps.lock); + + //ALS unlock and run + epl_sensor.als.compare_reset = EPL_CMP_RUN; + epl_sensor.als.lock = EPL_UN_LOCK; + epl_sensor_I2C_Write(epld->client,0x12, epl_sensor.als.compare_reset | epl_sensor.als.lock); + break; + } + /*double check PS or ALS lock*/ + + + return count; +} + +static ssize_t epl_sensor_store_als_ch_sel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int ch_sel; + LOG_FUN(); + + sscanf(buf, "%d",&ch_sel); + + LOG_INFO("channel selection = %d \r\n", ch_sel); + switch (ch_sel) + { + case 0: //ch0 + epl_sensor.als.interrupt_channel_select = EPL_ALS_INT_CHSEL_0; + break; + + case 1: //ch1 + epl_sensor.als.interrupt_channel_select = EPL_ALS_INT_CHSEL_1; + break; + } + epl_sensor_I2C_Write(epld->client,0x07, epl_sensor.als.interrupt_channel_select | epl_sensor.als.persist | epl_sensor.als.interrupt_type); + + epl_sensor_update_mode(epld->client); + + return count; +} + +static ssize_t epl_sensor_store_ps_cancelation(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int cancelation; + LOG_FUN(); + + sscanf(buf, "%d",&cancelation); + + epl_sensor.ps.cancelation = cancelation; + + LOG_INFO("epl_sensor.ps.cancelation = %d \r\n", epl_sensor.ps.cancelation); + + epl_sensor_I2C_Write(epld->client,0x22, (u8)(epl_sensor.ps.cancelation& 0xff)); + epl_sensor_I2C_Write(epld->client,0x23, (u8)((epl_sensor.ps.cancelation & 0xff00) >> 8)); + + return count; +} + +static ssize_t epl_sensor_show_ps_polling(struct device *dev, struct device_attribute *attr, char *buf) +{ + u16 *tmp = (u16*)buf; + tmp[0]= epl_sensor.ps.polling_mode; + return 2; +} + +static ssize_t epl_sensor_show_als_polling(struct device *dev, struct device_attribute *attr, char *buf) +{ + u16 *tmp = (u16*)buf; + tmp[0]= epl_sensor.als.polling_mode; + return 2; +} + +static ssize_t epl_sensor_show_ps_run_cali(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + ssize_t len = 0; + int ret; + + LOG_FUN(); + + ret = epl_run_ps_calibration(epld); + + len += snprintf(buf+len, PAGE_SIZE-len, "ret = %d\r\n", ret); + + return len; +} + +static ssize_t epl_sensor_show_pdata(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + ssize_t len = 0; + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; + LOG_FUN(); + + if(enable_ps == true && polling_flag == true && eint_flag == true && epl_sensor.ps.polling_mode == 0) + { + mutex_lock(&sensor_mutex); + epl_sensor_read_ps(epld->client); + mutex_unlock(&sensor_mutex); + } + LOG_INFO("[%s]: epl_sensor.ps.data.data = %d \r\n", __func__, epl_sensor.ps.data.data); + len += snprintf(buf + len, PAGE_SIZE - len, "%d", epl_sensor.ps.data.data); + return len; + +} + +static ssize_t epl_sensor_show_als_data(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + ssize_t len = 0; + bool enable_als = epld->enable_lflag==1 && epld->als_suspend==0; + LOG_FUN(); + +#if ALS_DYN_INTT + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT) + { + LOG_INFO("[%s]: dynamic_intt_lux = %d \r\n", __func__, dynamic_intt_lux); + len += snprintf(buf + len, PAGE_SIZE - len, "%d", dynamic_intt_lux); + } + else +#endif + { + if(enable_als == true && polling_flag == true && eint_flag == true && epl_sensor.als.polling_mode == 0) + { + mutex_lock(&sensor_mutex); + epl_sensor_read_als(epld->client); + mutex_unlock(&sensor_mutex); + } + len += snprintf(buf + len, PAGE_SIZE - len, "%d", epl_sensor.als.data.channels[1]); + } + + return len; +} + +#if ALS_DYN_INTT +static ssize_t epl_sensor_store_c_gain(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int c_h,c_l; + LOG_FUN(); + + if(epl_sensor.als.lsrc_type == CMC_BIT_LSRC_NON) + { + sscanf(buf, "%d",&c_h); + c_gain = c_h; + LOG_INFO("c_gain = %d \r\n", c_gain); + } + else + { + sscanf(buf, "%d,%d",&c_l, &c_h); + epld->c_gain_h = c_h; + epld->c_gain_l = c_l; + } + + return count; +} + +static ssize_t epl_sensor_store_lsrc_type(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int type; + LOG_FUN(); + + sscanf(buf, "%d",&type); + + epl_sensor.als.lsrc_type = type; + + LOG_INFO("epl_sensor.als.lsrc_type = %d \r\n", epl_sensor.als.lsrc_type); + + return count; +} + +static ssize_t epl_sensor_store_lsrc_thd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int lsrc_thrl, lsrc_thrh; + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_FUN(); + + sscanf(buf, "%d,%d",&lsrc_thrl, &lsrc_thrh); + + epld->lsource_thd_low = lsrc_thrl; + epld->lsource_thd_high = lsrc_thrh; + + LOG_INFO("lsource_thd=(%d,%d) \r\n", epld->lsource_thd_low, epld->lsource_thd_high); + + return count; +} + +#endif + +#if PS_DYN_K +static ssize_t epl_sensor_store_dyn_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int dyn_h,dyn_l; + LOG_FUN(); + + sscanf(buf, "%d,%d",&dyn_l, &dyn_h); + + dynk_low_offset = dyn_l; + dynk_high_offset = dyn_h; + + return count; +} + +static ssize_t epl_sensor_store_dyn_thd_offset(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int thd_offset; + LOG_FUN(); + + sscanf(buf, "%d",&thd_offset); + dynk_thd_offset = thd_offset; + + return count; +} + +static ssize_t epl_sensor_store_dyn_change_thd_max(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int thd_max; + LOG_FUN(); + + sscanf(buf, "%d",&thd_max); + dynk_change_thd_max = thd_max; + + return count; +} + +static ssize_t epl_sensor_store_dyn_max_ir_data(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int max_ir_data; + LOG_FUN(); + + sscanf(buf, "%d",&max_ir_data); + dynk_max_ir_data = max_ir_data; + + return count; +} + +#if PS_DYN_K_STR +static ssize_t epl_sensor_store_dyn_enhance_max_ch0(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + //struct epl_sensor_priv *epld = epl_sensor_obj; + int max_ch0; + LOG_FUN(); + + sscanf(buf, "%d",&max_ch0); + dynk_enhance_max_ch0 = max_ch0; + + return count; +} + +static ssize_t epl_sensor_store_dyn_enhance_gain_intt(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + //struct epl_sensor_priv *epld = epl_sensor_obj; + int gain, intt; + LOG_FUN(); + + sscanf(buf, "%d,%d",&gain, &intt); + + dynk_enhance_integration_time = intt; + dynk_enhance_gain = gain; + + return count; +} +#endif + +#endif + +#if HS_ENABLE +static ssize_t epl_sensor_show_renvo(struct device *dev, struct device_attribute *attr, char *buf) +{ + ssize_t len = 0; + + LOG_FUN(); + LOG_INFO("gRawData.renvo=0x%x \r\n", epl_sensor.revno); + + len += snprintf(buf+len, PAGE_SIZE-len, "%x", epl_sensor.revno); + + return len; +} + +static ssize_t epl_sensor_store_hs_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + uint16_t mode=0; + struct epl_sensor_priv *obj = epl_sensor_obj; + bool enable_ps = obj->enable_pflag==1 && obj->ps_suspend==0; + bool enable_als = obj->enable_lflag==1 && obj->als_suspend==0; + LOG_FUN(); + + sscanf(buf, "%hu",&mode); + + if(enable_ps == 0) + { + if(mode > 0) + { + if(enable_als == 1) + { + obj->enable_lflag = 0; + hs_enable_flag = true; + } + epl_sensor.hs.integration_time = epl_sensor.hs.integration_time_max; + epl_sensor.hs.raws_count=0; + obj->enable_hflag = 1; + + if(mode == 2) + { + epl_sensor.hs.dynamic_intt = false; + } + else + { + epl_sensor.hs.dynamic_intt = true; + } + } + else + { + obj->enable_hflag = 0; + if(hs_enable_flag == true) + { + obj->enable_lflag = 1; + hs_enable_flag = false; + } + + } + write_global_variable(obj->client); + epl_sensor_update_mode(obj->client); + } + + return count; +} + +static ssize_t epl_sensor_store_hs_int_time(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct epl_sensor_priv *obj = epl_sensor_obj; + int value; + u8 intt_buf; + + sscanf(buf, "%d",&value); + + mutex_lock(&hs_sensor_mutex); + epl_sensor.hs.integration_time = value<<2; + intt_buf = epl_sensor.hs.integration_time | epl_sensor.hs.gain; + epl_sensor_I2C_Write(obj->client, 0x03, intt_buf); + mutex_unlock(&hs_sensor_mutex); + + return count; +} + +/*----------------------------------------------------------------------------*/ +static ssize_t epl_sensor_show_hs_raws(struct device *dev, struct device_attribute *attr, char *buf) +{ + u16 *tmp = (u16*)buf; + int byte_count=2+epl_sensor.hs.raws_count*2; + int i=0; + LOG_FUN(); + mutex_lock(&hs_sensor_mutex); + tmp[0]= epl_sensor.hs.raws_count; + + for(i=0; i<epl_sensor.hs.raws_count; i++){ + tmp[i+1] = epl_sensor.hs.raws[i]; + } + + epl_sensor.hs.raws_count=0; + mutex_unlock(&hs_sensor_mutex); + + return byte_count; +} +#endif + +#if PS_GES +static ssize_t epl_sensor_store_ges_enable(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int ges_enable=0; + struct epl_sensor_priv *obj = epl_sensor_obj; + struct i2c_client *client = obj->client; + bool enable_ps = obj->enable_pflag==1 && obj->ps_suspend==0; + LOG_FUN(); + + sscanf(buf, "%d",&ges_enable); + LOG_INFO("[%s]: enable_ps=%d, ges_enable=%d \r\n", __func__, enable_ps, ges_enable); + + if(enable_ps == 0) + { + if(ges_enable == 1) + { + obj->enable_gflag = 1; + } + else + { + obj->enable_gflag = 0; + } + } + + write_global_variable(obj->client); + epl_sensor_update_mode(client); + return count; +} + +static ssize_t epl_sensor_store_ges_polling_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int ges_mode=0; + struct epl_sensor_priv *obj = epl_sensor_obj; + struct i2c_client *client = obj->client; + + LOG_FUN(); + sscanf(buf, "%d",&ges_mode); + LOG_INFO("[%s]: ges_mode=%d \r\n", __func__, ges_mode); + + epl_sensor.ges.polling_mode = ges_mode; + epl_sensor.ps.polling_mode = ges_mode; + + epl_sensor_update_mode(client); + return count; +} + +static ssize_t epl_sensor_store_ges_thd(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + int ges_l=0, ges_h=0; + struct epl_sensor_priv *obj = epl_sensor_obj; + struct i2c_client *client = obj->client; + + LOG_FUN(); + sscanf(buf, "%d,%d",&ges_l, &ges_h); + epl_sensor.ges.low_threshold = ges_l; + epl_sensor.ges.high_threshold = ges_h; + LOG_INFO("[%s]: ges_thd=%d,%d \r\n", __func__, epl_sensor.ges.low_threshold, epl_sensor.ges.high_threshold); + + write_global_variable(obj->client); + epl_sensor_update_mode(client); + return count; +} + +#endif +/*----------------------------------------------------------------------------*/ + +static DEVICE_ATTR(elan_status, S_IROTH | S_IWOTH, epl_sensor_show_status, NULL ); +static DEVICE_ATTR(elan_reg, S_IROTH | S_IWOTH, epl_sensor_show_reg, NULL ); +static DEVICE_ATTR(mode, S_IROTH | S_IWOTH, NULL, epl_sensor_store_mode ); +static DEVICE_ATTR(wait_time, S_IROTH | S_IWOTH, NULL, epl_sensor_store_wait_time ); +static DEVICE_ATTR(set_threshold, S_IROTH | S_IWOTH, NULL, epl_sensor_store_threshold ); +static DEVICE_ATTR(cal_raw, S_IROTH | S_IWOTH, epl_sensor_show_cal_raw, NULL ); +static DEVICE_ATTR(als_enable, S_IROTH | S_IWOTH, NULL, epl_sensor_store_als_enable ); +static DEVICE_ATTR(als_report_type, S_IROTH | S_IWOTH, NULL, epl_sensor_store_als_report_type ); +static DEVICE_ATTR(ps_enable, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ps_enable ); +static DEVICE_ATTR(ps_polling_mode, S_IROTH | S_IWOTH, epl_sensor_show_ps_polling, epl_sensor_store_ps_polling_mode ); +static DEVICE_ATTR(als_polling_mode, S_IROTH | S_IWOTH, epl_sensor_show_als_polling, epl_sensor_store_als_polling_mode ); +static DEVICE_ATTR(gain, S_IROTH | S_IWOTH, NULL, epl_sensor_store_gain ); +static DEVICE_ATTR(ir_mode, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ir_mode ); +static DEVICE_ATTR(ir_drive, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ir_drive ); +static DEVICE_ATTR(ir_on, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ir_contrl ); +static DEVICE_ATTR(interrupt_type, S_IROTH | S_IWOTH, NULL, epl_sensor_store_interrupt_type ); +static DEVICE_ATTR(integration, S_IROTH | S_IWOTH, NULL, epl_sensor_store_integration ); +static DEVICE_ATTR(adc, S_IROTH | S_IWOTH, NULL, epl_sensor_store_adc ); +static DEVICE_ATTR(cycle, S_IROTH | S_IWOTH, NULL, epl_sensor_store_cycle ); +static DEVICE_ATTR(ps_w_calfile, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ps_w_calfile ); +static DEVICE_ATTR(i2c_w, S_IROTH | S_IWOTH, NULL, epl_sensor_store_reg_write ); +static DEVICE_ATTR(unlock, S_IROTH | S_IWOTH, NULL, epl_sensor_store_unlock ); +static DEVICE_ATTR(als_ch, S_IROTH | S_IWOTH, NULL, epl_sensor_store_als_ch_sel ); +static DEVICE_ATTR(ps_cancel, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ps_cancelation ); +static DEVICE_ATTR(run_ps_cali, S_IROTH | S_IWOTH, epl_sensor_show_ps_run_cali, NULL ); +static DEVICE_ATTR(pdata, S_IROTH | S_IWOTH, epl_sensor_show_pdata, NULL ); +static DEVICE_ATTR(als_data, S_IROTH | S_IWOTH, epl_sensor_show_als_data, NULL ); +#if ALS_DYN_INTT +static DEVICE_ATTR(als_dyn_c_gain, S_IROTH | S_IWOTH, NULL, epl_sensor_store_c_gain); +static DEVICE_ATTR(als_dyn_lsrc_type, S_IROTH | S_IWOTH, NULL, epl_sensor_store_lsrc_type); +static DEVICE_ATTR(als_dyn_lsrc_thd, S_IROTH | S_IWOTH, NULL, epl_sensor_store_lsrc_thd); +#endif +#if PS_DYN_K +static DEVICE_ATTR(dyn_offset, S_IROTH | S_IWOTH, NULL, epl_sensor_store_dyn_offset); +static DEVICE_ATTR(dyn_thd_offset, S_IROTH | S_IWOTH, NULL, epl_sensor_store_dyn_thd_offset); +static DEVICE_ATTR(dyn_change_max, S_IROTH | S_IWOTH, NULL, epl_sensor_store_dyn_change_thd_max); +static DEVICE_ATTR(dyn_max_ir_data, S_IROTH | S_IWOTH, NULL, epl_sensor_store_dyn_max_ir_data ); +#if PS_DYN_K_STR +static DEVICE_ATTR(dyn_enh_max_ch0, S_IROTH | S_IWOTH, NULL, epl_sensor_store_dyn_enhance_max_ch0 ); +static DEVICE_ATTR(dyn_enh_gain_intt, S_IROTH | S_IWOTH, NULL, epl_sensor_store_dyn_enhance_gain_intt ); +#endif +#endif +#if HS_ENABLE +static DEVICE_ATTR(elan_renvo, S_IROTH | S_IWOTH, epl_sensor_show_renvo, NULL ); +static DEVICE_ATTR(hs_enable, S_IROTH | S_IWOTH, NULL, epl_sensor_store_hs_enable ); +static DEVICE_ATTR(hs_int_time, S_IROTH | S_IWOTH, NULL, epl_sensor_store_hs_int_time ); +static DEVICE_ATTR(hs_raws, S_IROTH | S_IWOTH, epl_sensor_show_hs_raws, NULL ); +#endif +#if PS_GES +static DEVICE_ATTR(ges_enable, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ges_enable ); +static DEVICE_ATTR(ges_polling_mode, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ges_polling_mode ); +static DEVICE_ATTR(ges_thd, S_IROTH | S_IWOTH, NULL, epl_sensor_store_ges_thd ); +#endif +/*----------------------------------------------------------------------------*/ +static struct attribute *epl_sensor_attr_list[] = +{ + &dev_attr_elan_status.attr, + &dev_attr_elan_reg.attr, + &dev_attr_als_enable.attr, + &dev_attr_ps_enable.attr, + &dev_attr_cal_raw.attr, + &dev_attr_set_threshold.attr, + &dev_attr_wait_time.attr, + &dev_attr_gain.attr, + &dev_attr_mode.attr, + &dev_attr_ir_mode.attr, + &dev_attr_ir_drive.attr, + &dev_attr_ir_on.attr, + &dev_attr_interrupt_type.attr, + &dev_attr_integration.attr, + &dev_attr_adc.attr, + &dev_attr_cycle.attr, + &dev_attr_als_report_type.attr, + &dev_attr_ps_polling_mode.attr, + &dev_attr_als_polling_mode.attr, + &dev_attr_ps_w_calfile.attr, + &dev_attr_i2c_w.attr, + &dev_attr_unlock.attr, + &dev_attr_als_ch.attr, + &dev_attr_ps_cancel.attr, + &dev_attr_run_ps_cali.attr, + &dev_attr_pdata.attr, + &dev_attr_als_data.attr, +#if ALS_DYN_INTT + &dev_attr_als_dyn_c_gain.attr, + &dev_attr_als_dyn_lsrc_type.attr, + &dev_attr_als_dyn_lsrc_thd.attr, +#endif +#if PS_DYN_K + &dev_attr_dyn_offset.attr, + &dev_attr_dyn_thd_offset.attr, + &dev_attr_dyn_change_max.attr, + &dev_attr_dyn_max_ir_data.attr, +#if PS_DYN_K_STR + &dev_attr_dyn_enh_max_ch0.attr, + &dev_attr_dyn_enh_gain_intt.attr, +#endif +#endif +#if HS_ENABLE + &dev_attr_elan_renvo.attr, + &dev_attr_hs_enable.attr, + &dev_attr_hs_int_time.attr, + &dev_attr_hs_raws.attr, +#endif +#if PS_GES + &dev_attr_ges_enable.attr, + &dev_attr_ges_polling_mode.attr, + &dev_attr_ges_thd.attr, +#endif +}; +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static struct attribute_group epl_sensor_attr_group = +{ + .attrs = epl_sensor_attr_list, +}; +/*----------------------------------------------------------------------------*/ +#if !(SPREAD || MARVELL)/*SPREAD MARVELL start.....*/ +/*----------------------------------------------------------------------------*/ +static int epl_sensor_als_open(struct inode *inode, struct file *file) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + if (epld->als_opened) + { + return -EBUSY; + } + epld->als_opened = 1; + + return 0; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static int epl_sensor_als_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + int buf[1]; + if(epld->read_flag ==1) + { + buf[0] = epl_sensor.als.data.channels[1]; + if(copy_to_user(buffer, &buf , sizeof(buf))) + return 0; + epld->read_flag = 0; + return 12; + } + else + { + return 0; + } +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static int epl_sensor_als_release(struct inode *inode, struct file *file) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + epld->als_opened = 0; + + return 0; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static long epl_sensor_als_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int flag; + unsigned long buf[1]; + struct epl_sensor_priv *epld = epl_sensor_obj; + bool enable_als = epld->enable_lflag==1 && epld->als_suspend==0; + void __user *argp = (void __user *)arg; + + LOG_INFO("als io ctrl cmd %d\n", _IOC_NR(cmd)); + + switch(cmd) + { + case ELAN_EPL8800_IOCTL_GET_LFLAG: + + LOG_INFO("elan ambient-light IOCTL Sensor get lflag \n"); + flag = epld->enable_lflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + + LOG_INFO("elan ambient-light Sensor get lflag %d\n",flag); + break; + + case ELAN_EPL8800_IOCTL_ENABLE_LFLAG: +#if LEADCORE + case LIGHT_SET_ENALBE: +#endif + LOG_INFO("elan ambient-light IOCTL Sensor set lflag \n"); + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + if (flag < 0 || flag > 1) + return -EINVAL; + + if(epld->enable_lflag != flag) + { +#if ALS_DYN_INTT + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT) + { + dynamic_intt_idx = dynamic_intt_init_idx; + epl_sensor.als.integration_time = als_dynamic_intt_intt[dynamic_intt_idx]; + epl_sensor.als.gain = als_dynamic_intt_gain[dynamic_intt_idx]; + dynamic_intt_high_thr = als_dynamic_intt_high_thr[dynamic_intt_idx]; + dynamic_intt_low_thr = als_dynamic_intt_low_thr[dynamic_intt_idx]; + } +#endif + epld->enable_lflag = flag; + + epl_sensor_update_mode(epld->client); + } + + LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + break; + + case ELAN_EPL8800_IOCTL_GETDATA: + if(enable_als == 0) + { + epld->enable_lflag = 1; + epl_sensor_update_mode(epld->client); + msleep(30); + } + + if(enable_als == true && polling_flag == true && eint_flag == true && epl_sensor.als.polling_mode == 0) + { + mutex_lock(&sensor_mutex); + epl_sensor_read_als(epld->client); + mutex_unlock(&sensor_mutex); + } +#if ALS_DYN_INTT + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT) + { + buf[0] = dynamic_intt_lux; + LOG_INFO("[%s]: als dynamic_intt_lux = %d \r\n", __func__, dynamic_intt_lux); + } + else +#else + { + buf[0] = epl_sensor.als.data.channels[1]; + LOG_INFO("[%s]: epl_sensor.als.data.channels[1] = %d \r\n", __func__, epl_sensor.als.data.channels[1]); + } +#endif + + if(copy_to_user(argp, &buf , sizeof(buf))) + return -EFAULT; + + break; +#if LEADCORE + case LIGHT_SET_DELAY: + + if (arg > LIGHT_MAX_DELAY) + arg = LIGHT_MAX_DELAY; + else if (arg < LIGHT_MIN_DELAY) + arg = LIGHT_MIN_DELAY; + LOG_INFO("LIGHT_SET_DELAY--%d\r\n",(int)arg); + polling_time = arg; + break; +#endif + default: + LOG_ERR("invalid cmd %d\n", _IOC_NR(cmd)); + return -EINVAL; + } + + return 0; + + +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static struct file_operations epl_sensor_als_fops = +{ + .owner = THIS_MODULE, + .open = epl_sensor_als_open, + .read = epl_sensor_als_read, + .release = epl_sensor_als_release, + .unlocked_ioctl = epl_sensor_als_ioctl +}; +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static struct miscdevice epl_sensor_als_device = +{ + .minor = MISC_DYNAMIC_MINOR, +#if LEADCORE + .name = "light", +#else + .name = "elan_als", +#endif + .fops = &epl_sensor_als_fops +}; +/*----------------------------------------------------------------------------*/ +#endif /*SPREAD MARVELL end.........*/ + +/*----------------------------------------------------------------------------*/ +static int epl_sensor_ps_open(struct inode *inode, struct file *file) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + if (epld->ps_opened) + return -EBUSY; + + epld->ps_opened = 1; + + return 0; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static int epl_sensor_ps_release(struct inode *inode, struct file *file) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + + LOG_FUN(); + + epld->ps_opened = 0; + + return 0; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ + +static int epl259x_read_chip_info(struct i2c_client *client, char *buf) +{ + if(NULL == buf) { + return -1; + } + if(NULL == client) { + *buf = 0; + return -2; + } + + sprintf(buf, "EPL2182"); + printk("[EPL259x] epl259x_read_chip_info %s\n",buf); + return 0; +} + +static long epl_sensor_ps_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + int value; + int flag,err =0; + char strbuf[100]; + struct epl_sensor_priv *epld = epl_sensor_obj; + bool enable_ps = epld->enable_pflag==1 && epld->ps_suspend==0; +#if HS_ENABLE + bool enable_hs = epld->enable_hflag==1 && epld->hs_suspend==0; +#endif +#if PS_GES + bool enable_ges = epld->enable_gflag==1 && epld->ges_suspend==0; +#endif + void __user *argp = (void __user *)arg; + + LOG_INFO("ps io ctrl cmd %d\n", _IOC_NR(cmd)); + + //ioctl message handle must define by android sensor library (case by case) + switch(cmd) + { + case ELAN_EPL8800_IOCTL_GET_PFLAG: +#if MARVELL + case LTR_IOCTL_GET_PFLAG: +#endif + LOG_INFO("elan Proximity Sensor IOCTL get pflag \n"); + flag = epld->enable_pflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + + LOG_INFO("elan Proximity Sensor get pflag %d\n",flag); + break; + + case ELAN_EPL8800_IOCTL_ENABLE_PFLAG: +#if LEADCORE + case PROXIMITY_SET_ENALBE: +#elif MARVELL + case LTR_IOCTL_SET_PFLAG: +#endif + LOG_INFO("elan Proximity IOCTL Sensor set pflag \n"); + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + if (flag < 0 || flag > 1) + return -EINVAL; +#if HS_ENABLE + if(enable_hs == 1 && flag == 1) + { + epld->enable_hflag = 0; + if(hs_enable_flag == true) + { + epld->enable_lflag = 1; + hs_enable_flag = false; + } + write_global_variable(epld->client); + LOG_INFO("[%s] Disable HS and recover ps setting \r\n", __func__); + } +#endif +#if PS_GES + if(enable_ges == 1 && flag == 1) + { + epld->enable_gflag = 0; + write_global_variable(epld->client); + ps_ges_enable_flag = true; + LOG_INFO("[%s] Disable GES and recover ps setting \r\n", __func__); + } + else if (ps_ges_enable_flag == true && flag == 0) + { + epld->enable_gflag = 1; + write_global_variable(epld->client); + ps_ges_enable_flag = false; + LOG_INFO("[%s] enable GES and recover ges setting \r\n", __func__); + } +#endif + if(epld->enable_pflag != flag) + { + epld->enable_pflag = flag; + if(flag) + { + wake_lock(&ps_lock); +#if PS_DYN_K + dynk_min_ps_raw_data = 0xffff; + dynk_change_flag = false; +#if PS_DYN_K_STR + dynk_enhance_flag = false; +#endif +#endif + }else{ +#if PS_DYN_K + cancel_delayed_work(&dynk_thd_polling_work); +#endif + wake_unlock(&ps_lock); + } + epl_sensor_update_mode(epld->client); + } + + LOG_INFO("elan Proximity Sensor set pflag %d\n",flag); + break; + + case ELAN_EPL8800_IOCTL_GETDATA: + if(enable_ps == 0) + { + epld->enable_pflag = 1; + epl_sensor_update_mode(epld->client); + msleep(30); + } + + if(enable_ps == true && polling_flag == true && eint_flag == true && epl_sensor.ps.polling_mode == 0) + { + mutex_lock(&sensor_mutex); + epl_sensor_read_ps(epld->client); + mutex_unlock(&sensor_mutex); + } + + LOG_INFO("[%s]: epl_sensor.ps.data.data = %d \r\n", __func__, epl_sensor.ps.data.data); + + value = epl_sensor.ps.data.data; + if(copy_to_user(argp, &value , sizeof(value))) + return -EFAULT; + + LOG_INFO("elan proximity Sensor get data (%d) \n",value); + break; +#if LEADCORE + case PROXIMITY_SET_DELAY: + if (arg > PROXIMITY_MAX_DELAY) + arg = PROXIMITY_MAX_DELAY; + else if (arg < PROXIMITY_MIN_DELAY) + arg = PROXIMITY_MIN_DELAY; + LOG_INFO("PROXIMITY_SET_DELAY--%d\r\n",(int)arg); + polling_time = arg; + break; +#endif + +#if SPREAD || MARVELL /*SPREAD MARVELL start.....*/ + case ELAN_EPL8800_IOCTL_GET_LFLAG: +#if MARVELL + case LTR_IOCTL_GET_LFLAG: +#endif + LOG_INFO("elan ambient-light IOCTL Sensor get lflag \n"); + flag = epld->enable_lflag; + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + + LOG_INFO("elan ambient-light Sensor get lflag %d\n",flag); + break; + + case ELAN_EPL8800_IOCTL_GET_CHIPINFO: + err = epl259x_read_chip_info(this_client, strbuf); + if(err < 0) + return -EFAULT; + if(copy_to_user(argp, strbuf, strlen(strbuf)+1)) + return -EFAULT; + break; + + case ELAN_EPL8800_IOCTL_ENABLE_LFLAG: +#if MARVELL + case LTR_IOCTL_SET_LFLAG: +#endif + LOG_INFO("elan ambient-light IOCTL Sensor set lflag \n"); + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + if (flag < 0 || flag > 1) + return -EINVAL; + + if(epld->enable_lflag != flag) + { +#if ALS_DYN_INTT + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT) + { + dynamic_intt_idx = dynamic_intt_init_idx; + epl_sensor.als.integration_time = als_dynamic_intt_intt[dynamic_intt_idx]; + epl_sensor.als.gain = als_dynamic_intt_gain[dynamic_intt_idx]; + dynamic_intt_high_thr = als_dynamic_intt_high_thr[dynamic_intt_idx]; + dynamic_intt_low_thr = als_dynamic_intt_low_thr[dynamic_intt_idx]; + } +#endif + epld->enable_lflag = flag; + + epl_sensor_update_mode(epld->client); + } + + LOG_INFO("elan ambient-light Sensor set lflag %d\n",flag); + break; +#if 0 + case ELAN_EPL8800_IOCTL_GETDATA: + if(enable_als == 0) + { + epld->enable_lflag = 1; + epl_sensor_update_mode(epld->client); + msleep(30); + } + + if(enable_als == true && polling_flag == true && eint_flag == true && epl_sensor.als.polling_mode == 0) + { + mutex_lock(&sensor_mutex); + epl_sensor_read_als(epld->client); + mutex_unlock(&sensor_mutex); + } +#if ALS_DYN_INTT + if(epl_sensor.als.report_type == CMC_BIT_DYN_INT) + { + buf[0] = dynamic_intt_lux; + LOG_INFO("[%s]: als dynamic_intt_lux = %d \r\n", __func__, dynamic_intt_lux); + } + else +#else + { + buf[0] = epl_sensor.als.data.channels[1]; + LOG_INFO("[%s]: epl_sensor.als.data.channels[1] = %d \r\n", __func__, epl_sensor.als.data.channels[1]); + } +#endif + + if(copy_to_user(argp, &buf , sizeof(buf))) + return -EFAULT; + + break; +#endif +#endif /*SPREAD MARVELL end......*/ + default: + LOG_ERR("invalid cmd %d\n", _IOC_NR(cmd)); + return -EINVAL; + } + + return 0; + +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static struct file_operations epl_sensor_ps_fops = +{ + .owner = THIS_MODULE, + .open = epl_sensor_ps_open, + .release = epl_sensor_ps_release, + .unlocked_ioctl = epl_sensor_ps_ioctl +}; +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static struct miscdevice epl_sensor_ps_device = +{ + .minor = MISC_DYNAMIC_MINOR, +#if LEADCORE + .name = "proximity", +#elif MARVELL + .name = "alps_pxy", +#elif SPREAD + .name = "epl2182_pls", +#endif + .fops = &epl_sensor_ps_fops +}; + +#if !(SPREAD || MARVELL) /*SPREAD MARVELL start.....*/ +/*----------------------------------------------------------------------------*/ +static ssize_t light_enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_INFO("%s: ALS_status=%d\n", __func__, epld->enable_lflag); + return sprintf(buf, "%d\n", epld->enable_lflag); +} + +static ssize_t light_enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + uint16_t als_enable = 0; + LOG_INFO("light_enable_store: enable=%s \n", buf); + + sscanf(buf, "%hu",&als_enable); + + if(epld->enable_lflag != als_enable) + { + epld->enable_lflag = als_enable; + + epl_sensor_update_mode(epld->client); + } + + return size; +} +/*----------------------------------------------------------------------------*/ +static struct device_attribute dev_attr_light_enable = +__ATTR(enable, S_IRWXUGO, + light_enable_show, light_enable_store); + +static struct attribute *light_sysfs_attrs[] = { + &dev_attr_light_enable.attr, + NULL +}; + +static struct attribute_group light_attribute_group = { + .attrs = light_sysfs_attrs, +}; +/*----------------------------------------------------------------------------*/ +static int epl_sensor_setup_lsensor(struct epl_sensor_priv *epld) +{ + int err = 0; + LOG_INFO("epl_sensor_setup_lsensor enter.\n"); + + epld->als_input_dev = input_allocate_device(); + if (!epld->als_input_dev) + { + LOG_ERR( "could not allocate ls input device\n"); + return -ENOMEM; + } + epld->als_input_dev->name = ElanALsensorName; + set_bit(EV_ABS, epld->als_input_dev->evbit); + input_set_abs_params(epld->als_input_dev, ABS_MISC, 0, 9, 0, 0); + + err = input_register_device(epld->als_input_dev); + if (err < 0) + { + LOG_ERR("can not register ls input device\n"); + goto err_free_ls_input_device; + } + + err = misc_register(&epl_sensor_als_device); + if (err < 0) + { + LOG_ERR("can not register ls misc device\n"); + goto err_unregister_ls_input_device; + } + + err = sysfs_create_group(&epld->als_input_dev->dev.kobj, &light_attribute_group); + + if (err) { + pr_err("%s: could not create sysfs group\n", __func__); + goto err_free_ls_input_device; + } + return err; + + +err_unregister_ls_input_device: + input_unregister_device(epld->als_input_dev); +err_free_ls_input_device: + input_free_device(epld->als_input_dev); + return err; +} +#endif /*SPREAD MARVELL end.....*/ +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static ssize_t proximity_enable_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_INFO("%s: PS status=%d\n", __func__, epld->enable_pflag); + return sprintf(buf, "%d\n", epld->enable_pflag); +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static ssize_t proximity_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + uint16_t ps_enable = 0; +#if HS_ENABLE + bool enable_hs = epld->enable_hflag==1 && epld->hs_suspend==0; +#endif +#if PS_GES + bool enable_ges = epld->enable_gflag==1 && epld->ges_suspend==0; +#endif + LOG_INFO("proximity_enable_store: enable=%s \n", buf); + + sscanf(buf, "%hu",&ps_enable); + +#if HS_ENABLE + if(enable_hs == 1 && ps_enable == 1) + { + epld->enable_hflag = 0; + if(hs_enable_flag == true) + { + epld->enable_lflag = 1; + hs_enable_flag = false; + } + write_global_variable(epld->client); + LOG_INFO("[%s] Disable HS and recover ps setting \r\n", __func__); + } +#endif +#if PS_GES + if(enable_ges == 1 && ps_enable == 1) + { + epld->enable_gflag = 0; + write_global_variable(epld->client); + ps_ges_enable_flag = true; + LOG_INFO("[%s] Disable GES and recover ps setting \r\n", __func__); + } + else if (ps_ges_enable_flag == true && ps_enable == 0) + { + epld->enable_gflag = 1; + write_global_variable(epld->client); + ps_ges_enable_flag = false; + LOG_INFO("[%s] enable GES and recover ges setting \r\n", __func__); + } +#endif + if(epld->enable_pflag != ps_enable) + { + epld->enable_pflag = ps_enable; + if(ps_enable) + { + wake_lock(&ps_lock); +#if PS_DYN_K + dynk_min_ps_raw_data = 0xffff; + dynk_change_flag = false; +#if PS_DYN_K_STR + dynk_enhance_flag = false; +#endif +#endif + } + else + { +#if PS_DYN_K + cancel_delayed_work(&dynk_thd_polling_work); +#endif + wake_unlock(&ps_lock); + } + + epl_sensor_update_mode(epld->client); + } + + return size; +} +/*----------------------------------------------------------------------------*/ +static struct device_attribute dev_attr_psensor_enable = +__ATTR(enable, S_IRWXUGO, + proximity_enable_show, proximity_enable_store); + +static struct attribute *proximity_sysfs_attrs[] = { + &dev_attr_psensor_enable.attr, + NULL +}; + +static struct attribute_group proximity_attribute_group = { + .attrs = proximity_sysfs_attrs, +}; +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static int epl_sensor_setup_psensor(struct epl_sensor_priv *epld) +{ + int err = 0; + LOG_INFO("epl_sensor_setup_psensor enter.\n"); + + + epld->ps_input_dev = input_allocate_device(); + if (!epld->ps_input_dev) + { + LOG_ERR("could not allocate ps input device\n"); + return -ENOMEM; + } + epld->ps_input_dev->name = ElanPsensorName; + + set_bit(EV_ABS, epld->ps_input_dev->evbit); + input_set_abs_params(epld->ps_input_dev, ABS_DISTANCE, 0, 1, 0, 0); +#if 1 + set_bit(EV_ABS, epld->ps_input_dev->evbit); + input_set_abs_params(epld->ps_input_dev, ABS_MISC, 0, 9, 0, 0); +#endif + + err = input_register_device(epld->ps_input_dev); + if (err < 0) + { + LOG_ERR("could not register ps input device\n"); + goto err_free_ps_input_device; + } + + err = misc_register(&epl_sensor_ps_device); + if (err < 0) + { + LOG_ERR("could not register ps misc device\n"); + goto err_unregister_ps_input_device; + } + + err = sysfs_create_group(&epld->ps_input_dev->dev.kobj, &proximity_attribute_group); + + if (err) { + pr_err("%s: PS could not create sysfs group\n", __func__); + goto err_free_ps_input_device; + } + + return err; + +err_unregister_ps_input_device: + input_unregister_device(epld->ps_input_dev); +err_free_ps_input_device: + input_free_device(epld->ps_input_dev); + return err; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +#ifdef CONFIG_SUSPEND +static int epl_sensor_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_FUN(); + + epld->als_suspend=1; +#if HS_ENABLE + epld->hs_suspend=1; +#endif +#if PS_GES + epld->ges_suspend = 1; + ps_ges_suspend_flag = true; +#endif + if(epld->enable_pflag == 1){ + //epld->ps_suspend=0; + LOG_INFO("[%s]: ps enable \r\n", __func__); + } + else{ + //epld->ps_suspend=1; + LOG_INFO("[%s]: ps disable \r\n", __func__); + epl_sensor_update_mode(epld->client); + } + + return 0; +} + +static int epl_sensor_resume(struct i2c_client *client) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_FUN(); + + epld->als_suspend=0; + epld->ps_suspend=0; +#if HS_ENABLE + epld->hs_suspend=0; +#endif +#if PS_GES + epld->ges_suspend = 0; +#endif + if(epld->enable_pflag == 1){ + //epld->ps_suspend=0; + LOG_INFO("[%s]: ps enable \r\n", __func__); + epl_sensor_restart_polling(); + } + else{ + //epld->ps_suspend=1; + LOG_INFO("[%s]: ps disable \r\n", __func__); + epl_sensor_update_mode(epld->client); + } + +#if !defined(CONFIG_HAS_EARLYSUSPEND) && PS_GES + ps_ges_suspend_flag = false; +#endif + return 0; +} +#endif + +/*----------------------------------------------------------------------------*/ +#if defined(CONFIG_HAS_EARLYSUSPEND) +static void epl_sensor_early_suspend(struct early_suspend *h) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_FUN(); + + epld->als_suspend=1; +#if HS_ENABLE + epld->hs_suspend=1; +#endif +#if PS_GES + epld->ges_suspend = 1; + ps_ges_suspend_flag = true; +#endif + if(epld->enable_pflag == 1) + { + //epld->ps_suspend=0; + LOG_INFO("[%s]: ps enable \r\n", __func__); + } + else + { + //epld->ps_suspend=1; + LOG_INFO("[%s]: ps disable \r\n", __func__); + epl_sensor_update_mode(epld->client); + } + +} + +static void epl_sensor_late_resume(struct early_suspend *h) +{ + struct epl_sensor_priv *epld = epl_sensor_obj; + LOG_FUN(); + + epld->als_suspend=0; + epld->ps_suspend=0; +#if HS_ENABLE + epld->hs_suspend=0; +#endif +#if PS_GES + epld->ges_suspend = 0; +#endif + if(epld->enable_pflag == 1){ + //epld->ps_suspend=0; + LOG_INFO("[%s]: ps enable \r\n", __func__); + epl_sensor_restart_polling(); + } + else{ + //epld->ps_suspend=1; + LOG_INFO("[%s]: ps disable \r\n", __func__); + epl_sensor_update_mode(epld->client); + } +#if PS_GES + ps_ges_suspend_flag = false; +#endif +} +#endif +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static int epl_sensor_probe(struct i2c_client *client,const struct i2c_device_id *id) +{ + int err = 0; + struct epl_sensor_priv *epld ; + struct elan_epl_platform_data *pdata = client->dev.platform_data; + struct device_node *np = client->dev.of_node; + LOG_INFO("elan sensor probe enter.\n"); + +#ifdef CONFIG_OF + if (np && !pdata){ + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Could not allocate struct elan_epl_platform_data"); + goto exit_allocate_pdata_failed; + } + pdata->irq_gpio_number = of_get_gpio(np, 0); + if(pdata->irq_gpio_number < 0){ + dev_err(&client->dev, "fail to get irq_gpio_number\n"); + kfree(pdata); + goto exit_irq_gpio_read_fail; + } + client->dev.platform_data = pdata; + } +#endif + + epld = kzalloc(sizeof(struct epl_sensor_priv), GFP_KERNEL); + if (!epld) + return -ENOMEM; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) + { + dev_err(&client->dev,"No supported i2c func what we need?!!\n"); + err = -ENOTSUPP; + goto i2c_fail; + } + LOG_INFO("chip id REG 0x00 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x00)); + LOG_INFO("chip id REG 0x01 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x01)); + LOG_INFO("chip id REG 0x02 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x02)); + LOG_INFO("chip id REG 0x03 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x03)); + LOG_INFO("chip id REG 0x04 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x04)); + LOG_INFO("chip id REG 0x05 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x05)); + LOG_INFO("chip id REG 0x06 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x06)); + LOG_INFO("chip id REG 0x07 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x07)); + LOG_INFO("chip id REG 0x11 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x11)); + LOG_INFO("chip id REG 0x12 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x12)); + LOG_INFO("chip id REG 0x1B value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x1B)); + LOG_INFO("chip id REG 0x20 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x20)); + LOG_INFO("chip id REG 0x21 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x21)); + LOG_INFO("chip id REG 0x24 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x24)); + LOG_INFO("chip id REG 0x25 value = 0x%x\n", i2c_smbus_read_byte_data(client, 0x25)); + + if((i2c_smbus_read_byte_data(client, 0x21)) != 0x81){ + LOG_INFO("elan ALS/PS sensor is failed. \n"); + goto i2c_fail; + } + + epld->als_level_num = sizeof(epld->als_level)/sizeof(epld->als_level[0]); + epld->als_value_num = sizeof(epld->als_value)/sizeof(epld->als_value[0]); + BUG_ON(sizeof(epld->als_level) != sizeof(als_level)); + memcpy(epld->als_level, als_level, sizeof(epld->als_level)); + BUG_ON(sizeof(epld->als_value) != sizeof(als_value)); + memcpy(epld->als_value, als_value, sizeof(epld->als_value)); + + epld->client = client; + this_client = client; + epld->intr_pin= pdata->irq_gpio_number; + +#if HS_ENABLE + epld->hs_suspend = 0; + mutex_init(&hs_sensor_mutex); +#endif +#if PS_GES + epld->ges_suspend = 0; + epld->gs_input_dev = input_allocate_device(); + set_bit(EV_KEY, epld->gs_input_dev->evbit); + set_bit(EV_REL, epld->gs_input_dev->evbit); + set_bit(EV_ABS, epld->gs_input_dev->evbit); + epld->gs_input_dev->evbit[0] |= BIT_MASK(EV_REP); + epld->gs_input_dev->keycodemax = 500; + epld->gs_input_dev->name ="elan_gesture"; + epld->gs_input_dev->keybit[BIT_WORD(KEYCODE_LEFT)] |= BIT_MASK(KEYCODE_LEFT); + if (input_register_device(epld->gs_input_dev)) + LOG_ERR("register input error\n"); +#endif + i2c_set_clientdata(client, epld); + + epl_sensor_obj = epld; + + INIT_DELAYED_WORK(&epld->eint_work, epl_sensor_eint_work); + + epld->epl_wq = create_workqueue("epl2182_pls"); //create_singlethread_workqueue + if (!epld->epl_wq) + { + LOG_ERR("can't create workqueue\n"); + err = -ENOMEM; + goto err_create_singlethread_workqueue; + } + + mutex_init(&sensor_mutex); + + //initial global variable and write to senosr + initial_global_variable(client, epld); +#if !(SPREAD || MARVELL) + err = epl_sensor_setup_lsensor(epld); + if (err < 0) + { + LOG_ERR("epl_sensor_setup_lsensor error!!\n"); + goto err_lightsensor_setup; + } +#endif + + err = epl_sensor_setup_psensor(epld); + if (err < 0) + { + LOG_ERR("epl_sensor_setup_psensor error!!\n"); + goto err_psensor_setup; + } + + + if (epl_sensor.als.polling_mode==0 || epl_sensor.ps.polling_mode==0) + { + err = epl_sensor_setup_interrupt(epld); + if (err < 0) + { + LOG_ERR("setup error!\n"); + goto err_sensor_setup; + } + } + //disable_irq(epld->irq); //ices add + +#ifdef CONFIG_SUSPEND + +#if defined(CONFIG_HAS_EARLYSUSPEND) + epld->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1; + epld->early_suspend.suspend = epl_sensor_early_suspend; + epld->early_suspend.resume = epl_sensor_late_resume; + register_early_suspend(&epld->early_suspend); +#endif + +#endif + + wake_lock_init(&ps_lock, WAKE_LOCK_SUSPEND, "ps wakelock"); +#if 0 + sensor_dev = platform_device_register_simple("elan_alsps", -1, NULL, 0); + if (IS_ERR(sensor_dev)) + { + printk ("sensor_dev_init: error\n"); + goto err_fail; + } + + + err = sysfs_create_group(&sensor_dev->dev.kobj, &epl_sensor_attr_group); + if (err !=0) + { + dev_err(&client->dev,"%s:create sysfs group error", __func__); + goto err_fail; + } +#endif + LOG_INFO("sensor probe success.\n"); + return err; + +err_fail: + input_unregister_device(epld->als_input_dev); + input_unregister_device(epld->ps_input_dev); + input_free_device(epld->als_input_dev); + input_free_device(epld->ps_input_dev); +#if !(SPREAD || MARVELL) +err_lightsensor_setup: +#endif +err_psensor_setup: +err_sensor_setup: + destroy_workqueue(epld->epl_wq); + misc_deregister(&epl_sensor_ps_device); +#if !(SPREAD || MARVELL) + misc_deregister(&epl_sensor_als_device); +#endif +err_create_singlethread_workqueue: +i2c_fail: + kfree(epld); +#ifdef CONFIG_OF +exit_irq_gpio_read_fail: +exit_allocate_pdata_failed: +#endif + + return err; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static int epl_sensor_remove(struct i2c_client *client) +{ + struct epl_sensor_priv *epld = i2c_get_clientdata(client); + + dev_dbg(&client->dev, "%s: enter.\n", __func__); +#if defined(CONFIG_HAS_EARLYSUSPEND) + unregister_early_suspend(&epld->early_suspend); +#endif + sysfs_remove_group(&sensor_dev->dev.kobj, &epl_sensor_attr_group); + platform_device_unregister(sensor_dev); + input_unregister_device(epld->als_input_dev); + input_unregister_device(epld->ps_input_dev); +#if !(SPREAD || MARVELL) + input_free_device(epld->als_input_dev); +#endif + input_free_device(epld->ps_input_dev); + misc_deregister(&epl_sensor_ps_device); +#if !(SPREAD || MARVELL) + misc_deregister(&epl_sensor_als_device); +#endif + free_irq(epld->irq,epld); + destroy_workqueue(epld->epl_wq); + kfree(epld); + return 0; +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static const struct i2c_device_id epl_sensor_id[] = +{ + { EPL_DEV_NAME, 0 }, + {} +}; + +#if SPREAD +static struct of_device_id epl_match_table[] = { + { .compatible = "ELAN,epl259x_pls", }, + {} +}; +#endif +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +static struct i2c_driver epl_sensor_driver = +{ + .probe = epl_sensor_probe, + .remove = epl_sensor_remove, + .id_table = epl_sensor_id, + .driver = { + .name = EPL_DEV_NAME, + .owner = THIS_MODULE, +#if SPREAD + .of_match_table =epl_match_table, +#endif + }, +#ifdef CONFIG_SUSPEND + //.suspend = epl_sensor_suspend, + //.resume = epl_sensor_resume, +#endif +}; + +static int __init epl_sensor_init(void) +{ + return i2c_add_driver(&epl_sensor_driver); +} + +static void __exit epl_sensor_exit(void) +{ + i2c_del_driver(&epl_sensor_driver); +} +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +module_init(epl_sensor_init); +module_exit(epl_sensor_exit); +/*----------------------------------------------------------------------------*/ + +/*----------------------------------------------------------------------------*/ +MODULE_AUTHOR("Renato Pan <renato.pan@eminent-tek.com>"); +MODULE_DESCRIPTION("ELAN epl259x driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/ewtsa.c b/drivers/input/misc/ewtsa.c new file mode 100644 index 00000000..bdb0b973 --- /dev/null +++ b/drivers/input/misc/ewtsa.c @@ -0,0 +1,2763 @@ +/* + * linux/drivers/input/misc/ewtsa.c + * + * Copyright 2010-2011 Panasonic Electronic Devices. All Rights Reserved. + */ + +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +/*include files*/ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/regulator/consumer.h> +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/spinlock.h> + +/* configurable */ +#define GYRO_MOUNT_SWAP_XY 0 /* swap X, Y */ +#define GYRO_MOUNT_REVERSE_X 0 /* reverse X */ +#define GYRO_MOUNT_REVERSE_Y 0 /* reverse Y */ +#define GYRO_MOUNT_REVERSE_Z 0 /* reverse Z */ +#define GYRO_VER3_ES1 0 +#define GYRO_VER3_ES3 0 +#define GYRO_VER3_ES4 1 +#define GYRO_REGISTER_MAP2 0 +#define GYRO_SPI_INTERFACE 0 + +#define USE_GPIO_DRDY_PIN 0 +#define USE_GPIO_DIAG_PIN 0 +#define USE_GPIO_WINDOW_PIN 0 + +/* macro defines */ +#define CHIP_ID 0x68 +#define DEVICE_NAME "ewtsa" +#define EWTSA_ON 1 +#define EWTSA_OFF 0 +#define WINDOW_PIN 14 +#define DRDY_PIN 12 +#define DIAG_PIN 11 +#define DIAG_ENABLE 0 +#define DIAG_DISABLE 1 +#define MAX_VALUE 32768 + +/* ewtsa_delay parameter */ +#define DELAY_THRES_MIN 1 +#define DELAY_THRES_1 4 +#define DELAY_THRES_2 9 /* msec x 90% */ +#define DELAY_THRES_3 18 +#define DELAY_THRES_4 45 +#define DELAY_THRES_5 90 +#define DELAY_THRES_6 128 +#define DELAY_THRES_MAX 255 +#define DELAY_DLPF_0 0 +#define DELAY_DLPF_1 1 +#define DELAY_DLPF_2 2 +#define DELAY_DLPF_3 3 +#define DELAY_DLPF_4 4 +#define DELAY_DLPF_5 5 +#define DELAY_DLPF_6 6 +#define DELAY_INTMIN_THRES 9 +#define DELAY_INT8K_THRES 79 +#define DELAY_THRES_FIFO 150 + +/* ewtsa_range parameter */ +#define DYNRANGE_250 0x00 +#define DYNRANGE_500 0x01 +#define DYNRANGE_1000 0x02 +#define DYNRANGE_2000 0x03 + +#define DATA_RATE_1 0x01 +#define DATA_RATE_5 0x05 +#define DATA_RATE_8 0x08 +#define DATA_RATE_10 0x0A + +/* ewtsa_sleep parameter */ +#define SLEEP_OFF 0 +#define SLEEP_ON 1 +#define SLEEP_ALLRESET 2 + +/* ewtsa_calib parameter */ +#define BOOST_TIME_500MS 0 +#define BOOST_TIME_300MS 1 +#define BOOST_TIME_200MS 2 +#define BOOST_TIME_100MS 3 +#define BOOST_FS_48KHZ 0 +#define BOOST_FS_16KHZ 1 +#define HPF_001HZ 0 +#define HPF_002HZ 1 +#define MASK_CLIP 0 +#define MASK_NON_CLIP 1 +#define THRESHOLD_ON 0 +#define THRESHOLD_OFF 1 +#define LAG_NON 0 +#define LAG_5SMP 1 +#define LAG_10SMP 2 +#define LAG_20SMP 3 +#define W1_THRES_LEVEL_2DPS 0 +#define W1_THRES_LEVEL_5DPS 1 +#define W1_THRES_LEVEL_10DPS 2 +#define W1_THRES_LEVEL_15DPS 3 +#define REFERENCE_WAIT 16 +#define W1_THRES_CENTER_COEF 25000 +#define CALIB_FS_62_5HZ 0 +#define CALIB_FS_31_25HZ 1 + +/* ewtsa_configuration parameter */ +#define BYPASS_MODE 0 +#define FIFO_MODE 1 +#define FIFO_MAX 32 +#define FIFO_MIN_DELAY 5 +#define MAP_NAME_SIZE 4 +#define INTERFACE_NAME_SIZE 3 +#define CMD_SIZE_3 3 +#define CMD_SIZE_4 4 +#define THRES_CENTER_MIN 0 +#define THRES_CENTER_MAX 359 +#define THRES_CENTER_INIT_X 160 +#define THRES_CENTER_INIT_Y 160 +#define THRES_CENTER_INIT_Z 160 + +/* EWTSA_system_restart parameter */ +#define RST_REG_INIT_OFF 0x00 +#define RST_REG_INIT_RANGE 0x01 +#define RST_REG_INIT_DELAY 0x02 +#define RST_REG_INIT_ALL 0xFF + +/* event mode */ +#define EWTSA_POLLING_MODE 0 +#define EWTSA_INTERUPT_MODE 1 + +/* ewtsa register address */ +#define REG_SLAD 0x00 +#define REG_FIFO_DRDY 0x10 +#define REG_FIFO_CTRL1 0x13 +#define REG_FIFO_CTRL2 0x14 +#define REG_DATA_RATE 0x15 +#define REG_FS_DLPF_CTRL 0x16 +#define REG_INT_CTRL 0x17 +#define REG_CTRL_REG2 0x18 +#define REG_CTRL_REG3 0x19 +#define REG_INT_STAT 0x1A +#define REG_TEMP_H 0x1B +#define REG_TEMP_L 0x1C +#define REG_OUTX_H 0x1D +#define REG_OUTX_L 0x1E +#define REG_OUTY_H 0x1F +#define REG_OUTY_L 0x20 +#define REG_OUTZ_H 0x21 +#define REG_OUTZ_L 0x22 +#define REG_CTRL_REG5 0x24 +#define REG_REFERENCE 0x25 +#define REG_STATUS_REG 0x27 +#define REG_DIGITAL_CAL 0x29 +#define REG_DIAG_CTRL 0x2B +#define REG_FIFO_DM 0x2F +#define REG_DIAG_MONI 0x39 +#define REG_SLEEP_CTRL 0x3E +#define REG_W1THX_H 0x46 +#define REG_W1THX_L 0x47 +#define REG_W1THY_H 0x48 +#define REG_W1THY_L 0x49 +#define REG_W1THZ_H 0x4A +#define REG_W1THZ_L 0x4B +#define REG_SOC_CNT1 0x4C +#define REG_SOC_CNT2 0x4D +#define REG_SLFSLP_CNT 0x4F +#define REG_SPITYPE 0x60 +#define REG_MBURST_ALL 0xFF + +/* ewtsa register map2 address */ +#define REG2_CTRL_REG1 0x20 + +/* ewtsa register param */ +#define INT_CTRL_WTMON_ENABLE 0x0D +#define INT_CTRL_WTMON_DISABLE 0x00 +#define DIGITAL_CAL_ENABLE 0x00 +#define DIGITAL_CAL_DISABLE 0x01 +#define SLEEP_CTRL_ACTIVATE 0x44 +#define SLEEP_CTRL_SLEEP 0x04 +#define SLEEP_CTRL_ALLRSTSLEEP 0x00 +#define SLEEP_CTRL_LOGIC_RST 0x80 +#define SOC_CNT1_BOOST_ON 0x01 +#define SOC_CNT1_WIN_DIS 0x70 +#define SOC_CNT2_SOCCLK_ON 0x40 +#define SOC_CNT2_SOCCLK_OFF 0x00 +#define INT_CTRL_INT_ENABLE 0x01 +#define INT_CTRL_INT_DISABLE 0x00 +#define INT_CTRL_INT_WTMON_EN 0x08 +#define CTRL_REG2_REF_MODE 0x10 +#define CTRL_REG2_NOR_MODE 0x60 +#define CTRL_REG2_HPCF 0x09 +#define CTRL_REG5_FIFO_EN 0x53 +#define CTRL_REG5_INIT 0x13 +#define FIFO_DRDY_DITH_EN 0x02 +#define FIFO_DRDY_INIT 0x17 +#define CTRL_REG3_WTM_DIS 0x00 +#define CTRL_REG3_WTM_EN 0x04 +#define STATUS_REG_OVERRUN 0xF0 +#define FIFO_CTRL1_CLEAR 0x02 + +/* ewtsa register map2 param */ +#define CTRL_REG1_SLEEP 0x07 +#define CTRL_REG1_ACTIVATE 0x0F + +/* ewtsa interrupt control */ +#define EWSTA_INT_CLEAR 0x00 +#define EWSTA_INT_SKIP 0x01 + +/* EWTSA_set_calibration param */ +#define EWTSA_RANGE_0 250 +#define EWTSA_RANGE_1 500 +#define EWTSA_RANGE_2 1000 +#define EWTSA_RANGE_3 2000 +#define EWTSA_BOOST_TIME_0 500 +#define EWTSA_BOOST_TIME_1 300 +#define EWTSA_BOOST_TIME_2 200 +#define EWTSA_BOOST_TIME_3 100 +#define EWTSA_W1_THRES_LV_0 2 +#define EWTSA_W1_THRES_LV_1 5 +#define EWTSA_W1_THRES_LV_2 10 +#define EWTSA_W1_THRES_LV_3 15 + +/* init param */ +#define SLEEP_INIT_VAL SLEEP_ALLRESET +#define FIFO_TYPE_INIT_VAL BYPASS_MODE +#define DELAY_INIT_VAL DELAY_THRES_1 +#define RANGE_INIT_VAL DYNRANGE_1000 +#define DLPF_INIT_VAL DELAY_DLPF_2 +#define CALIB_FUNC_INIT_VAL EWTSA_ON +#if GYRO_VER3_ES4 +#define CALIB_BTIM_INIT_VAL BOOST_TIME_100MS +#else +#define CALIB_BTIM_INIT_VAL BOOST_TIME_500MS +#endif +#define CALIB_FS_INIT_VAL CALIB_FS_62_5HZ +#define CALIB_RST_INIT_VAL EWTSA_ON +#define CALIB_BFS_INIT_VAL BOOST_FS_48KHZ +#define CALIB_CLK_INIT_VAL EWTSA_ON +#define CALIB_HPF_INIT_VAL HPF_002HZ +#if GYRO_VER3_ES4 +#define CALIB_LAG_INIT_VAL LAG_5SMP +#else +#define CALIB_LAG_INIT_VAL LAG_10SMP +#endif +#define W1_FUNC_INIT_VAL THRESHOLD_ON +#define W2_FUNC_INIT_VAL THRESHOLD_OFF +#if GYRO_VER3_ES4 +#define W1_LV_INIT_VAL W1_THRES_LEVEL_5DPS +#define W1_MASK_INIT_VAL MASK_CLIP +#else +#define W1_LV_INIT_VAL W1_THRES_LEVEL_15DPS +#define W1_MASK_INIT_VAL MASK_NON_CLIP +#endif + +/* declarations */ +struct ewtsa_t { + struct i2c_client *client; + struct input_dev *input_dev; + struct workqueue_struct *workqueue; + struct delayed_work input_work; + struct mutex mlock; + spinlock_t slock; + unsigned long delay; + signed short fifo_lastdata[4]; + unsigned short w1_thres_x; + unsigned short w1_thres_y; + unsigned short w1_thres_z; + unsigned char event; + unsigned char range; + unsigned char sleep; + unsigned char dlpf; + unsigned char calib; + unsigned char calib_fs; + unsigned char calib_rst; + unsigned char calib_boost_tm; + unsigned char calib_boost_fs; + unsigned char fifo_mode; + unsigned char fifo_num; + unsigned char fifo_data_flag; + unsigned char reset_param; + unsigned char int_skip; + unsigned char calib_alway; + unsigned char hpf; + unsigned char w1_datamask; + unsigned char w1_thres_en; + unsigned char w2_thres_en; + unsigned char offset_lag; + unsigned char w1_thres_level; + unsigned char reserved; +}; + +static int __init init_ewtsa(void); +static void __exit exit_ewtsa(void); +static ssize_t ewtsa_sleep_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t ewtsa_sleep_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); +static ssize_t ewtsa_delay_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t ewtsa_delay_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); +static ssize_t ewtsa_range_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t ewtsa_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); +static ssize_t ewtsa_calib_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t ewtsa_calib_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); +static ssize_t ewtsa_config_show(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t ewtsa_config_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); +static int EWTSA_probe(struct i2c_client *client, const struct i2c_device_id *id); +static int EWTSA_remove(struct i2c_client *client); +static int EWTSA_suspend(struct i2c_client *client, pm_message_t mesg); +static int EWTSA_resume(struct i2c_client *client); +static void EWTSA_report_abs(struct ewtsa_t *ewt); +static void EWTSA_dev_poll(struct work_struct *work); +static void EWTSA_input_work_func(struct work_struct *work); +#if USE_GPIO_DRDY_PIN +static irqreturn_t EWTSA_interrupt(int irq, void *dev_id); +#endif +static void EWTSA_system_restart(struct ewtsa_t *ewtsa); +static void EWTSA_set_calibration(struct ewtsa_t *ewtsa); + +static struct device_attribute ewtsa_sleep_attr = { + .attr = { + .name = "ewtsa_sleep", + .mode = S_IRUGO | S_IWUSR, + }, + .show = ewtsa_sleep_show, + .store = ewtsa_sleep_store, +}; + +static struct device_attribute ewtsa_delay_attr = { + .attr = { + .name = "ewtsa_delay", + .mode = S_IRUGO | S_IWUSR, + }, + .show = ewtsa_delay_show, + .store = ewtsa_delay_store, +}; + +static struct device_attribute ewtsa_range_attr = { + .attr = { + .name = "ewtsa_range", + .mode = S_IRUGO | S_IWUSR, + }, + .show = ewtsa_range_show, + .store = ewtsa_range_store, +}; + +static struct device_attribute ewtsa_calib_attr = { + .attr = { + .name = "ewtsa_calib", + .mode = S_IRUGO | S_IWUSR, + }, + .show = ewtsa_calib_show, + .store = ewtsa_calib_store, +}; + +static struct device_attribute ewtsa_config_attr = { + .attr = { + .name = "ewtsa_configuration", + .mode = S_IRUGO | S_IWUSR, + }, + .show = ewtsa_config_show, + .store = ewtsa_config_store, +}; + +static const struct i2c_device_id ewtsa_id[] = { + {"ewtsa", 0}, + {{0}, 0} +}; + +MODULE_DEVICE_TABLE(i2c, ewtsa_id); + +static struct i2c_driver i2c_ewtsa_driver = { + .driver = { + .name = "ewtsa", + }, + .probe = EWTSA_probe, + .remove = EWTSA_remove, + .suspend = EWTSA_suspend, + .resume = EWTSA_resume, + .id_table = ewtsa_id, +}; + +/* show the sleep mode */ +static ssize_t ewtsa_sleep_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + struct ewtsa_t *ewtsa; +#if !GYRO_VER3_ES1 + int ret; + unsigned char val; +#endif /* GYRO_VER3_ES1 */ + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* output buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + +#if GYRO_VER3_ES1 + return sprintf(buf, "%d\n", ewtsa->sleep); +#else /* GYRO_VER3_ES1 */ + ret = i2c_smbus_read_byte_data(ewtsa->client, REG_SLEEP_CTRL); + if (ret < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL read err(ret = %d)\n", ret); + return ret; + } + val = (unsigned char)ret; + + /* REG_SLEEP_CTRL register value check */ + if ((val & SLEEP_CTRL_ACTIVATE) == SLEEP_CTRL_ACTIVATE) { + val = SLEEP_OFF; + } + else if ((val & SLEEP_CTRL_SLEEP) == SLEEP_CTRL_SLEEP) { + val = SLEEP_ON; + } + else { + val = SLEEP_ALLRESET; + } + + return sprintf(buf, "%d\n", val); +#endif /* GYRO_VER3_ES1 */ +} + +/* Set the sleep mode */ +static ssize_t ewtsa_sleep_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + u8 reg; + struct i2c_client *client; + struct ewtsa_t *ewtsa; + unsigned char val; + unsigned char before_sleep; + s32 ret; +#if GYRO_VER3_ES1 || USE_GPIO_DRDY_PIN + int err; +#endif /* GYRO_VER3_ES1 */ + long chk_param; + unsigned long l_flags = 0; +#if GYRO_VER3_ES1 + unsigned char map2_flag = EWTSA_OFF; +#endif /* GYRO_VER3_ES1 */ + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* input buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + + chk_param = simple_strtol(buf, NULL, 10); + if ((chk_param < SLEEP_OFF) || (chk_param >SLEEP_ALLRESET)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", chk_param); + return -EINVAL; + } + val = (unsigned char)chk_param; + + dev_info(&ewtsa->client->dev, "set SLEEP = %d\n", val); + +#if GYRO_VER3_ES1 + /* if current status is same */ + if (ewtsa->sleep == val) { + return count; + } +#else /* GYRO_VER3_ES1 */ + ret = i2c_smbus_read_byte_data(ewtsa->client, REG_SLEEP_CTRL); + if (ret < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL read err(ret = %d)\n", ret); + return ret; + } + reg = (u8)ret; + + /* REG_SLEEP_CTRL register value check */ + if ((reg & SLEEP_CTRL_ACTIVATE) == SLEEP_CTRL_ACTIVATE) { + reg = SLEEP_OFF; + } + else if ((reg & SLEEP_CTRL_SLEEP) == SLEEP_CTRL_SLEEP) { + reg = SLEEP_ON; + } + else { + reg = SLEEP_ALLRESET; + } + + /* if current status is same */ + if (reg == val) { + return count; + } +#endif /* GYRO_VER3_ES1 */ + + before_sleep = ewtsa->sleep; + switch(val) + { + case SLEEP_OFF: +#if GYRO_VER3_ES1 + if (ewtsa->sleep == SLEEP_ON) { + reg = CTRL_REG1_ACTIVATE; + map2_flag = EWTSA_ON; + } + else { + reg = SLEEP_CTRL_ACTIVATE; + } +#else /* GYRO_VER3_ES1 */ + reg = SLEEP_CTRL_ACTIVATE; +#endif /* GYRO_VER3_ES1 */ + break; + case SLEEP_ON: + if (ewtsa->sleep == SLEEP_ALLRESET) { + return count; + } + reg = SLEEP_CTRL_SLEEP; + ewtsa->calib_rst = EWTSA_ON; + break; + default: /* SLEEP_ALLRESET */ + reg = SLEEP_CTRL_ALLRSTSLEEP; + ewtsa->calib_rst = EWTSA_ON; + ewtsa->reset_param = RST_REG_INIT_ALL; + break; + } + + if (ewtsa->event != EWTSA_POLLING_MODE) { + /* disable interrupt */ + ret = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, INT_CTRL_INT_DISABLE); + if (ret < 0) + { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", INT_CTRL_INT_DISABLE, ret); + return ret; + } + } + + dev_info(&ewtsa->client->dev, "set reg = 0x%02x\n", reg); +#if GYRO_VER3_ES1 + if (map2_flag == EWTSA_ON) { + ret = i2c_smbus_write_byte_data(ewtsa->client, REG2_CTRL_REG1, reg); + } + else { + ret = i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, reg); + } +#else /* GYRO_VER3_ES1 */ + ret = i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, reg); +#endif /* GYRO_VER3_ES1 */ + if (ret < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL write err(data = 0x%02x, ret = %d)\n", reg, ret); + return ret; + } + ewtsa->sleep = val; + + if (val != SLEEP_OFF) { + if (ewtsa->event == EWTSA_POLLING_MODE) { + cancel_delayed_work(&ewtsa->input_work); + } + } + else { + ret = i2c_smbus_write_byte_data(ewtsa->client, REG_DIAG_CTRL, DIAG_ENABLE); + if (ret < 0) { + dev_err(&ewtsa->client->dev, "REG_DIAG_CTRL write err(data = 0x%02x, ret = %d)\n", DIAG_ENABLE, ret); + return ret; + } + if (ewtsa->event == EWTSA_POLLING_MODE) { + ret = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, INT_CTRL_WTMON_DISABLE); /* No INT */ + if (ret < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", INT_CTRL_WTMON_DISABLE, ret); + return ret; + } + } + + if (ewtsa->calib_rst == EWTSA_ON) { + EWTSA_system_restart(ewtsa); + } + if (ewtsa->event == EWTSA_POLLING_MODE) { + /* polling timer start */ + queue_delayed_work(ewtsa->workqueue, + &ewtsa->input_work, + msecs_to_jiffies(ewtsa->delay)); + } + } + + if (ewtsa->event == EWTSA_POLLING_MODE) { + mutex_lock(&ewtsa->mlock); + input_report_abs(ewtsa->input_dev, ABS_RY, -1); + input_sync(ewtsa->input_dev); + mutex_unlock(&ewtsa->mlock); + } + else { + spin_lock_irqsave(&ewtsa->slock, l_flags); + input_report_abs(ewtsa->input_dev, ABS_RY, -1); + input_sync(ewtsa->input_dev); + spin_unlock_irqrestore(&ewtsa->slock, l_flags); + + if (val == SLEEP_OFF) { + /* enable interrupt */ +#if USE_GPIO_DRDY_PIN + err = gpio_request(DRDY_PIN, "DRDY"); + err += gpio_direction_input(DRDY_PIN); + err += request_threaded_irq(gpio_to_irq(DRDY_PIN), NULL, EWTSA_interrupt, IRQF_TRIGGER_RISING, DEVICE_NAME, ewtsa); + if (err != 0) { + dev_err(&ewtsa->client->dev, "request_irq err(%d)\n", err); + return err; + } +#endif + + ewtsa->fifo_data_flag = EWTSA_OFF; + memset(ewtsa->fifo_lastdata, 0x00, sizeof(ewtsa->fifo_lastdata)); + ewtsa->int_skip = EWSTA_INT_SKIP; + if (ewtsa->fifo_mode == BYPASS_MODE) { + reg = (u8)INT_CTRL_INT_ENABLE; + } + else { + reg = (u8)(INT_CTRL_INT_ENABLE | INT_CTRL_INT_WTMON_EN); + } + ret = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, reg); + if (ret < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", reg, ret); + return ret; + } + } + else + { + if (before_sleep == SLEEP_OFF) { +#if USE_GPIO_DRDY_PIN + free_irq(gpio_to_irq(DRDY_PIN), ewtsa); + gpio_free(DRDY_PIN); +#endif + } + } + } + + return count; +} + +/* Show the LPF settings */ +static ssize_t ewtsa_delay_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + struct ewtsa_t *ewtsa; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* output buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + + return sprintf(buf, "%ld\n", ewtsa->delay); +} + +/* Set the LPF settings*/ +static ssize_t ewtsa_delay_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client; + struct ewtsa_t *ewtsa; + unsigned long delay; + unsigned char val; + unsigned char number; + u8 smpl; + int err; + unsigned char reg; + unsigned char fifo_type; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* input buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + err = strict_strtoul(buf, 0, &delay); + if (err != 0) { + dev_err(&ewtsa->client->dev, "strict_strtoul err(ret = %d)\n", err); + return err; + } + + if (delay == ewtsa->delay) { + return count; + } + + if ((delay < DELAY_THRES_MIN) || (delay > DELAY_THRES_MAX)) { + dev_err(&ewtsa->client->dev, "param err(ret = %ld)\n", delay); + return -EINVAL; + } + + dev_info(&ewtsa->client->dev, "Delay = %ld\n", delay); + + if (ewtsa->sleep == SLEEP_OFF) { + if (ewtsa->event == EWTSA_POLLING_MODE) { + cancel_delayed_work(&ewtsa->input_work); + } + else { + /* disable interrupt */ + err = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, INT_CTRL_INT_DISABLE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", INT_CTRL_INT_DISABLE, err); + return err; + } + } + + if (ewtsa->event == EWTSA_POLLING_MODE) { + fifo_type = BYPASS_MODE; + } + else { + fifo_type = ewtsa->fifo_mode; + } + + if (fifo_type == BYPASS_MODE) { + if (delay < DELAY_THRES_4) { + if (delay >= DELAY_THRES_3) { + val = DELAY_DLPF_3; + } else { + val = DELAY_DLPF_2; + } + } else { + if (delay >= DELAY_THRES_6) { + val = DELAY_DLPF_6; + } else if (delay >= DELAY_THRES_5) { + val = DELAY_DLPF_5; + } else { + val = DELAY_DLPF_4; + } + } + } + else { + if (delay <= DELAY_THRES_FIFO) { + val = DELAY_DLPF_1; + } + else { + val = DELAY_DLPF_2; + } + } + + reg = (unsigned char)((unsigned char)(ewtsa->range << 3) | val ); + dev_info(&ewtsa->client->dev, "FS_DLPF = %2X\n", (unsigned int)reg); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FS_DLPF_CTRL, reg); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FS_DLPF_CTRL write err(data = 0x%02x, ret = %d)\n", reg, err); + return err; + } + ewtsa->dlpf = val; + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG5, CTRL_REG5_INIT); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG5 write err(data = 0x%02x, ret = %d)\n", CTRL_REG5_INIT, err); + return err; + } + + if (fifo_type != BYPASS_MODE) { + reg = FIFO_DRDY_DITH_EN; + } + else { + dev_info(&ewtsa->client->dev, "FIFO_CTR = %2X\n", fifo_type); + reg = (unsigned char)(fifo_type << 5); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_CTRL2, reg); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_CTRL2 write err(data = 0x%02x, ret = %d)\n", reg, err); + return err; + } + reg = FIFO_DRDY_INIT; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_DRDY, reg); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_DRDY write err(data = 0x%02x, ret = %d)\n", reg, err); + return err; + } + + if (ewtsa->event == EWTSA_POLLING_MODE) { + smpl = DATA_RATE_1 - 1; /* SMPL = 0 */ + dev_info(&ewtsa->client->dev, "SMPL+1 = %2X\n", smpl); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_DATA_RATE, smpl); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_DATA_RATE write err(data = 0x%02x, ret = %d)\n", smpl, err); + return err; + } + ewtsa->delay = delay; + ewtsa->reset_param &= (unsigned char)~RST_REG_INIT_DELAY; + EWTSA_system_restart(ewtsa); + + queue_delayed_work(ewtsa->workqueue, + &ewtsa->input_work, + msecs_to_jiffies(ewtsa->delay)); + } + else { + if (fifo_type == BYPASS_MODE) { + if (delay <= DELAY_THRES_2) { + smpl = DELAY_INTMIN_THRES; + } + else { + smpl = (unsigned char)(delay - 1); + } + ewtsa->fifo_num = 1; + } + else { + if (delay <= DELAY_THRES_FIFO) { + if (delay > FIFO_MIN_DELAY) { + number = (unsigned char)(delay / DATA_RATE_5); + if ((delay % DATA_RATE_5) != 0) { + number++; + } + } + else { + number = 2; + } + smpl = DATA_RATE_5 - 1; /* SMPL = 4 */ + } + else { + number = (unsigned char)(delay / DATA_RATE_10); + if ((delay % DATA_RATE_10) != 0) { + number++; + } + smpl = DATA_RATE_10 - 1; /* SMPL = 9 */ + } + + reg = (unsigned char)(fifo_type << 5); + reg |= number; + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_CTRL2, reg); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_CTRL2 write err(data = 0x%02x, ret = %d)\n", reg, err); + return err; + } + ewtsa->fifo_num = number; + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG3, CTRL_REG3_WTM_EN); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG3 write err(data = 0x%02x, ret = %d)\n", CTRL_REG3_WTM_EN, err); + return err; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG5, CTRL_REG5_FIFO_EN); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG5 write err(data = 0x%02x, ret = %d)\n", CTRL_REG5_FIFO_EN, err); + return err; + } + } + dev_info(&ewtsa->client->dev, "SMPL+1 = %2X\n", smpl); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_DATA_RATE, smpl); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_DATA_RATE write err(data = 0x%02x, ret = %d)\n", smpl, err); + return err; + } + ewtsa->delay = delay; + ewtsa->reset_param &= (unsigned char)~RST_REG_INIT_DELAY; + EWTSA_system_restart(ewtsa); + + /* enable interrupt */ + ewtsa->fifo_data_flag = EWTSA_OFF; + memset(ewtsa->fifo_lastdata, 0x00, sizeof(ewtsa->fifo_lastdata)); + ewtsa->int_skip = EWSTA_INT_SKIP; + if (fifo_type == BYPASS_MODE) { + reg = (u8)INT_CTRL_INT_ENABLE; + } + else { + reg = (u8)(INT_CTRL_INT_ENABLE | INT_CTRL_INT_WTMON_EN); + } + err = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, reg); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", reg, err); + return err; + } + } + } + else { + ewtsa->delay = delay; + ewtsa->calib_rst = EWTSA_ON; + ewtsa->reset_param |= RST_REG_INIT_DELAY; + } + + return count; +} + +/* Show the FS settings */ +static ssize_t ewtsa_range_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + struct ewtsa_t *ewtsa ; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* output buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + + return sprintf(buf, "%d\n", ewtsa->range); +} + +/* Set the FS settings*/ +static ssize_t ewtsa_range_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client; + struct ewtsa_t *ewtsa; + unsigned char val; + s32 err; + unsigned char rng; + u8 reg; + long chk_param; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* input buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + chk_param = simple_strtol(buf, NULL, 10); + if ((chk_param < DYNRANGE_250) || (chk_param > DYNRANGE_2000)) { + dev_info(&ewtsa->client->dev, "invalid value = %ld\n", chk_param); + return -EINVAL; + } + val = (unsigned char)chk_param; + + if (val == ewtsa->range) { + return count; + } + + dev_info(&ewtsa->client->dev, "FS = %d\n", (int)val); + if (ewtsa->sleep == SLEEP_OFF) { + if (ewtsa->event != EWTSA_POLLING_MODE) { + /* disable interrupt */ + err = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, INT_CTRL_INT_DISABLE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", INT_CTRL_INT_DISABLE, err); + return err; + } + } + rng = (unsigned char)((unsigned char)(val << 3) | ewtsa->dlpf); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FS_DLPF_CTRL, rng); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FS_DLPF_CTRL write err(data = 0x%02x, ret = %d)\n", rng, err); + return err; + } + ewtsa->range = val; + ewtsa->reset_param &= (unsigned char)~RST_REG_INIT_RANGE; + if (ewtsa->event == EWTSA_POLLING_MODE) { + cancel_delayed_work(&ewtsa->input_work); + EWTSA_system_restart(ewtsa); + queue_delayed_work(ewtsa->workqueue, + &ewtsa->input_work, + msecs_to_jiffies(ewtsa->delay)); + } + else { + EWTSA_system_restart(ewtsa); + + /* enable interrupt */ + ewtsa->fifo_data_flag = EWTSA_OFF; + memset(ewtsa->fifo_lastdata, 0x00, sizeof(ewtsa->fifo_lastdata)); + ewtsa->int_skip = EWSTA_INT_SKIP; + if (ewtsa->fifo_mode == BYPASS_MODE) { + reg = (u8)INT_CTRL_INT_ENABLE; + } + else { + reg = (u8)(INT_CTRL_INT_ENABLE | INT_CTRL_INT_WTMON_EN); + } + err = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, reg); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", reg, err); + return err; + } + } + } + else { + ewtsa->range = val; + ewtsa->calib_rst = EWTSA_ON; + ewtsa->reset_param |= RST_REG_INIT_RANGE; + } + + return count; +} + +/* Show the SELF O C settings */ +static ssize_t ewtsa_calib_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *client; + struct ewtsa_t *ewtsa; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* output buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + + return sprintf(buf, "%d\n", ewtsa->calib); +} + +/* Set the SELF O C settings*/ +static ssize_t ewtsa_calib_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *client; + struct ewtsa_t *ewtsa; + unsigned char val; + s32 err; + u8 reg; + long chk_param; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + client = to_i2c_client(dev); + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + /* input buffer check */ + if (buf == NULL) { + dev_err(&client->dev, "buf pointer null!\n"); + return -EIO; + } + chk_param = simple_strtol(buf, NULL, 10); + if ((chk_param != EWTSA_ON) && (chk_param != EWTSA_OFF)) { + dev_info(&ewtsa->client->dev, "invalid value = %ld\n", chk_param); + return -EINVAL; + } + val = (unsigned char)chk_param; + + dev_info(&ewtsa->client->dev, "SELF_O_C = %d(now = %d)\n", (int)val, ewtsa->calib); + + if (ewtsa->calib == val) { + return count; + } + + ewtsa->calib = val; + if (ewtsa->sleep == SLEEP_OFF) { + if (ewtsa->event == EWTSA_POLLING_MODE) { + cancel_delayed_work(&ewtsa->input_work); + EWTSA_system_restart(ewtsa); + queue_delayed_work(ewtsa->workqueue, + &ewtsa->input_work, + msecs_to_jiffies(ewtsa->delay)); + } + else { + /* disable interrupt */ + err = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, INT_CTRL_INT_DISABLE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", INT_CTRL_INT_DISABLE, err); + return err; + } + + EWTSA_system_restart(ewtsa); + + /* enable interrupt */ + ewtsa->fifo_data_flag = EWTSA_OFF; + memset(ewtsa->fifo_lastdata, 0x00, sizeof(ewtsa->fifo_lastdata)); + ewtsa->int_skip = EWSTA_INT_SKIP; + if (ewtsa->fifo_mode == BYPASS_MODE) { + reg = (u8)INT_CTRL_INT_ENABLE; + } + else { + reg = (u8)(INT_CTRL_INT_ENABLE | INT_CTRL_INT_WTMON_EN); + } + err = i2c_smbus_write_byte_data(ewtsa->client, REG_INT_CTRL, reg); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_INT_CTRL write err(data = 0x%02x, ret = %d)\n", reg, err); + return err; + } + } + } + else { + ewtsa->calib_rst = EWTSA_ON; + } + + return count; +} + +/* Show the gyro configurations */ +static ssize_t ewtsa_config_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct i2c_client *l_client; + struct ewtsa_t *ewtsa; + int ret; + ssize_t ret_len = 0; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + l_client = to_i2c_client(dev); + + if (l_client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(l_client); + + /* output buffer check */ + if (buf == NULL) { + dev_err(&ewtsa->client->dev, "buf pointer null!\n"); + return -EIO; + } + +#if GYRO_REGISTER_MAP2 + ret = sprintf(buf+ret_len, "MAP2;"); +#else /* GYRO_REGISTER_MAP2 */ + ret = sprintf(buf+ret_len, "MAP1;"); +#endif /* GYRO_REGISTER_MAP2 */ + if (ret >= 0) { + ret_len += ret; + } + +#if GYRO_SPI_INTERFACE + ret = sprintf(buf+ret_len, "SPI;"); +#else /* GYRO_SPI_INTERFACE */ + ret = sprintf(buf+ret_len, "I2C;"); +#endif /* GYRO_SPI_INTERFACE */ + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "SLP=%d;", ewtsa->sleep); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "DLY=%ld;", ewtsa->delay); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "RNG=%d;", ewtsa->range); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "SOC=%d;", ewtsa->calib); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "SBT=%d;", ewtsa->calib_boost_tm); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "HPF=%d;", ewtsa->hpf); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "SFR=%d;", ewtsa->calib_fs); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "EVE=%d;", ewtsa->event); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "FIFO=%d;", ewtsa->fifo_mode); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "SAA=%d;", ewtsa->calib_alway); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "W1CX=%d;", ewtsa->w1_thres_x); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "W1CY=%d;", ewtsa->w1_thres_y); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "W1CZ=%d;", ewtsa->w1_thres_z); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "BFR=%d;", ewtsa->calib_boost_fs); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "OTL=%d;", ewtsa->offset_lag); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "W1L=%d;", ewtsa->w1_thres_level); + if (ret >= 0) { + ret_len += ret; + } + + ret = sprintf(buf+ret_len, "W1DM=%d;", ewtsa->w1_datamask); + if (ret >= 0) { + ret_len += ret; + } + + return ret_len; +} + +/* Set the gyro configurations */ +static ssize_t ewtsa_config_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct i2c_client *l_client; + struct ewtsa_t *ewtsa; + char val_char[4] = {0}; + char sleep_val[4] = {0}; + unsigned char cmd[5] = {0}; + unsigned char cmd_size = 0; + unsigned char val_size = 0; + unsigned char sleep_flag = EWTSA_OFF; + unsigned char sleep_mode = SLEEP_ALLRESET; + unsigned char fifo_tmp; + unsigned char event_tmp; + int loop; + int size_err; + unsigned long val = 0; + size_t chk_cnt = 0; + ssize_t sleep_ret; + + if (dev == NULL) { + printk(KERN_INFO "struct device null pointer\n"); + return -EIO; + } + l_client = to_i2c_client(dev); + + if (l_client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(l_client); + + /* input buffer check */ + if (buf == NULL) { + dev_err(&ewtsa->client->dev, "buf pointer null!\n"); + return -EIO; + } + + fifo_tmp = ewtsa->fifo_mode; + event_tmp = ewtsa->event; + + if (strncmp(buf, "MAP", 3) == 0) { + /* get register map information */ +#if GYRO_REGISTER_MAP2 + if (strncmp(buf, "MAP2", MAP_NAME_SIZE) != 0) { + dev_err(&ewtsa->client->dev, "register map unmatch!\n"); + return -EINVAL; + } +#else /* GYRO_REGISTER_MAP2 */ + if (strncmp(buf, "MAP1", MAP_NAME_SIZE) != 0) { + dev_err(&ewtsa->client->dev, "register map unmatch!\n"); + return -EINVAL; + } +#endif /* GYRO_REGISTER_MAP2 */ + buf += MAP_NAME_SIZE + 1; + chk_cnt += MAP_NAME_SIZE + 1; + + /* get communication interface */ +#if GYRO_SPI_INTERFACE + if (strncmp(buf, "SPI", INTERFACE_NAME_SIZE) != 0) { + dev_err(&ewtsa->client->dev, "communication interface unmatch!\n"); + return -EINVAL; + } +#else /* GYRO_SPI_INTERFACE */ + if (strncmp(buf, "I2C", INTERFACE_NAME_SIZE) != 0) { + dev_err(&ewtsa->client->dev, "communication interface unmatch!\n"); + return -EINVAL; + } +#endif /* GYRO_SPI_INTERFACE */ + buf += INTERFACE_NAME_SIZE + 1; + chk_cnt += INTERFACE_NAME_SIZE + 1; + } + + while (chk_cnt < count) { + memset(cmd, 0x00, sizeof(cmd)); + memset(val_char, 0x00, sizeof(val_char)); + size_err = 0; + + for (loop = 0; (chk_cnt + loop) < count; loop++) { + if (*(buf + loop) == '=') { + break; + } + + if (*(buf + loop) == ';') { + dev_err(&ewtsa->client->dev, "command format err\n"); + return -EINVAL; + } + } + + if ((chk_cnt + loop) >= count) { + break; + } + + cmd_size = (unsigned char)loop; + if (cmd_size == 0) { + dev_err(&ewtsa->client->dev, "command nothing\n"); + return -EINVAL; + } + + /* command size over? */ + if (loop > (sizeof(cmd) - 1)) { + size_err = EWTSA_ON; + } + else { + memcpy(cmd, buf, cmd_size); + } + buf += cmd_size + 1; + chk_cnt += cmd_size + 1; + + for (loop = 0; (chk_cnt + loop) < count; loop++) { + if (*(buf + loop) == ';') { + break; + } + } + + if ((chk_cnt + loop) >= count) { + break; + } + + val_size = (unsigned char)loop; + if (val_size == 0) { + dev_err(&ewtsa->client->dev, "value nothing(cmd = %s)\n", cmd); + return -EINVAL; + } + + if (size_err != 0) { + /* if command size is over, value ignore and next command check. */ + buf += val_size + 1; + chk_cnt += val_size + 1; + continue; + } + else if (loop > (sizeof(val_char) - 1)) { + /* if value size is over, return format err. */ + size_err = EWTSA_ON; + } + else { + memcpy(val_char, buf, val_size); + size_err = strict_strtoul(val_char, 10, &val); + } + + if (size_err != 0) { + dev_err(&ewtsa->client->dev, "value format err(cmd = %s)\n", cmd); + return -EINVAL; + } + buf += val_size + 1; + chk_cnt += val_size + 1; + + if (cmd_size == CMD_SIZE_3) { + if (strncmp((char*)cmd, "SLP", CMD_SIZE_3) == 0) { + sleep_flag = EWTSA_ON; + memcpy(sleep_val, val_char, sizeof(sleep_val)); + if (val == 0) { + sleep_mode = SLEEP_OFF; + } + } + else if (strncmp((char*)cmd, "DLY", CMD_SIZE_3) == 0) { + if ((val < DELAY_THRES_MIN) || (val > DELAY_THRES_MAX)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + + ewtsa->delay = val; + ewtsa->calib_rst = EWTSA_ON; + ewtsa->reset_param |= RST_REG_INIT_DELAY; + } + else if (strncmp((char*)cmd, "RNG", CMD_SIZE_3) == 0) { + if (val > DYNRANGE_2000) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->range = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + ewtsa->reset_param |= RST_REG_INIT_RANGE; + } + else if (strncmp((char*)cmd, "SOC", CMD_SIZE_3) == 0) { + if ((val != EWTSA_ON) && (val != EWTSA_OFF)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->calib = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "SBT", CMD_SIZE_3) == 0) { + if (val > BOOST_TIME_100MS) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->calib_boost_tm = (unsigned char)val; + } + else if (strncmp((char*)cmd, "HPF", CMD_SIZE_3) == 0) { + if ((val != HPF_001HZ) && (val != HPF_002HZ)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->hpf = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "SFR", CMD_SIZE_3) == 0) { + if ((val != CALIB_FS_62_5HZ) && (val != CALIB_FS_31_25HZ)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->calib_fs = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "EVE", CMD_SIZE_3) == 0) { + if ((val != EWTSA_POLLING_MODE) && (val != EWTSA_INTERUPT_MODE)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } +#if USE_GPIO_DRDY_PIN + event_tmp = (unsigned char)val; +#else + event_tmp = (unsigned char)EWTSA_POLLING_MODE; +#endif + ewtsa->calib_rst = EWTSA_ON; + ewtsa->reset_param = RST_REG_INIT_ALL; + } + else if (strncmp((char*)cmd, "SAA", CMD_SIZE_3) == 0) { + if ((val != EWTSA_ON) && (val != EWTSA_OFF)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->calib_alway = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "BFR", CMD_SIZE_3) == 0) { + if ((val != BOOST_FS_48KHZ) && (val != BOOST_FS_16KHZ)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->calib_boost_fs = (unsigned char)val; + } + else if (strncmp((char*)cmd, "OTL", CMD_SIZE_3) == 0) { + if (val > LAG_20SMP) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->offset_lag = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "W1L", CMD_SIZE_3) == 0) { + if (val > W1_THRES_LEVEL_15DPS) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->w1_thres_level = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + } + else { + dev_info(&ewtsa->client->dev, "unknown command(%s)\n", cmd); + } + } + else if (cmd_size == CMD_SIZE_4) { + if (strncmp((char*)cmd, "FIFO", CMD_SIZE_4) == 0) { + if ((val != BYPASS_MODE) && (val != FIFO_MODE)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + fifo_tmp = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + ewtsa->reset_param = RST_REG_INIT_ALL; + } + else if (strncmp((char*)cmd, "W1CX", CMD_SIZE_4) == 0) { + if (val > THRES_CENTER_MAX) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->w1_thres_x = (unsigned short)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "W1CY", CMD_SIZE_4) == 0) { + if (val > THRES_CENTER_MAX) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->w1_thres_y = (unsigned short)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "W1CZ", CMD_SIZE_4) == 0) { + if (val > THRES_CENTER_MAX) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->w1_thres_z = (unsigned short)val; + ewtsa->calib_rst = EWTSA_ON; + } + else if (strncmp((char*)cmd, "W1DM", CMD_SIZE_4) == 0) { + if ((val != MASK_CLIP) && (val != MASK_NON_CLIP)) { + dev_err(&ewtsa->client->dev, "invalid value = %ld\n", val); + return -EINVAL; + } + ewtsa->w1_datamask = (unsigned char)val; + ewtsa->calib_rst = EWTSA_ON; + } + else { + dev_info(&ewtsa->client->dev, "unknown command(%s)\n", cmd); + } + } + else { + dev_info(&ewtsa->client->dev, "unknown command(%s)\n", cmd); + } + } + + if (sleep_flag == EWTSA_ON) { + if (sleep_mode == SLEEP_OFF) { + /* all reset sleep mode */ + val_char[0] = '2'; + val_char[1] = '\0'; + sleep_ret = ewtsa_sleep_store(dev, attr, val_char, 1); + if (sleep_ret != 1) { + dev_err(&ewtsa->client->dev, "ewtsa_sleep_store err(%d)\n", sleep_ret); + return sleep_ret; + } + ewtsa->event = event_tmp; + ewtsa->fifo_mode = fifo_tmp; + } + sleep_ret = ewtsa_sleep_store(dev, attr, sleep_val, sizeof(sleep_val)); + if (sleep_ret != sizeof(sleep_val)) { + dev_err(&ewtsa->client->dev, "ewtsa_sleep_store err(%d)\n", sleep_ret); + return sleep_ret; + } + ewtsa->event = event_tmp; + ewtsa->fifo_mode = fifo_tmp; + } + else { + if (ewtsa->calib_rst == EWTSA_ON) { + sleep_mode = ewtsa->sleep; + + /* all reset sleep mode */ + val_char[0] = '2'; + val_char[1] = '\0'; + sleep_ret = ewtsa_sleep_store(dev, attr, val_char, 1); + if (sleep_ret != 1) { + dev_err(&ewtsa->client->dev, "ewtsa_sleep_store err(%d)\n", sleep_ret); + return sleep_ret; + } + + ewtsa->event = event_tmp; + ewtsa->fifo_mode = fifo_tmp; + + if (sleep_mode == SLEEP_OFF) { + /* sleep off mode */ + val_char[0] = '0'; + val_char[1] = '\0'; + sleep_ret = ewtsa_sleep_store(dev, attr, val_char, 1); + if (sleep_ret != 1) { + dev_err(&ewtsa->client->dev, "ewtsa_sleep_store err(%d)\n", sleep_ret); + return sleep_ret; + } + } + } + } + + return count; +} + +/* calibration & register settings */ +static void EWTSA_system_restart(struct ewtsa_t *ewtsa) +{ + unsigned char val; + unsigned char smpl; + unsigned char number; + unsigned char fifo_type; + s32 err; +#if GYRO_VER3_ES1 + signed long ret; + signed long timeout; +#endif + + if (ewtsa == NULL) { + printk(KERN_INFO "EWTSA_system_restart:ewtsa null pointer\n"); + return; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_DIGITAL_CAL, DIGITAL_CAL_DISABLE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_DIGITAL_CAL write err(data = 0x%02x, ret = %d)\n", DIGITAL_CAL_DISABLE, err); + return; + } + +#if GYRO_VER3_ES1 + err = i2c_smbus_write_byte_data(ewtsa->client, REG_REFERENCE, 0x00); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_REFERENCE write err(data = 0x00, ret = %d)\n", err); + return; + } + + val = (unsigned char)(CTRL_REG2_REF_MODE | CTRL_REG2_HPCF); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + err = i2c_smbus_read_byte_data(ewtsa->client, REG_REFERENCE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_REFERENCE read err(ret = %d)\n", err); + return; + } + + val = SOC_CNT2_SOCCLK_ON; + val |= (unsigned char)(CALIB_FS_62_5HZ << 7); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + /* Wait reference mode setting */ + ret = (signed long)((REFERENCE_WAIT * HZ)/1000); + ret += (signed long)(1000 / HZ); + while (ret > 0) { + timeout = ret; + set_current_state(TASK_UNINTERRUPTIBLE); + ret = schedule_timeout(timeout); + } + + val = SOC_CNT2_SOCCLK_OFF; + val |= (unsigned char)(CALIB_FS_62_5HZ << 7); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(CTRL_REG2_NOR_MODE | CTRL_REG2_HPCF); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } +#endif /* GYRO_VER3_ES1 */ + + if (ewtsa->event == EWTSA_POLLING_MODE) { + fifo_type = BYPASS_MODE; + } + else { + fifo_type = ewtsa->fifo_mode; + } + + if ((ewtsa->reset_param & RST_REG_INIT_ALL) != RST_REG_INIT_OFF) { + if (fifo_type == BYPASS_MODE) { + if (ewtsa->delay < DELAY_THRES_4) { + if (ewtsa->delay >= DELAY_THRES_3) { + ewtsa->dlpf = DELAY_DLPF_3; + } else { + ewtsa->dlpf = DELAY_DLPF_2; + } + } else { + if (ewtsa->delay >= DELAY_THRES_6) { + ewtsa->dlpf = DELAY_DLPF_6; + } else if (ewtsa->delay >= DELAY_THRES_5) { + ewtsa->dlpf = DELAY_DLPF_5; + } else { + ewtsa->dlpf = DELAY_DLPF_4; + } + } + } + else { + if (ewtsa->delay <= DELAY_THRES_FIFO) { + ewtsa->dlpf = DELAY_DLPF_1; + } + else { + ewtsa->dlpf = DELAY_DLPF_2; + } + } + val = (unsigned char)((unsigned char)(ewtsa->range << 3) | ewtsa->dlpf); + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FS_DLPF_CTRL, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FS_DLPF_CTRL write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + } + + if ((ewtsa->reset_param & RST_REG_INIT_DELAY) != RST_REG_INIT_OFF) { + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG5, CTRL_REG5_INIT); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG5 write err(data = 0x%02x, ret = %d)\n", CTRL_REG5_INIT, err); + return; + } + + if (fifo_type != BYPASS_MODE) { + val = FIFO_DRDY_DITH_EN; + } + else { + val = (unsigned char)(fifo_type << 5); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_CTRL2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_CTRL2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + val = FIFO_DRDY_INIT; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_DRDY, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_DRDY write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + if (fifo_type == BYPASS_MODE) { + if (ewtsa->event == EWTSA_POLLING_MODE) { + smpl = DATA_RATE_1 - 1; /* SMPL = 0 */ + } + else { + if (ewtsa->delay <= DELAY_THRES_2) { + smpl = DELAY_INTMIN_THRES; + } + else { + smpl = (unsigned char)(ewtsa->delay - 1); + } + } + ewtsa->fifo_num = 1; + } + else { + if (ewtsa->delay <= DELAY_THRES_FIFO) { + if (ewtsa->delay > FIFO_MIN_DELAY) { + number = (unsigned char)(ewtsa->delay / DATA_RATE_5); + if ((ewtsa->delay % DATA_RATE_5) != 0) { + number++; + } + } + else { + number = 2; + } + smpl = DATA_RATE_5 - 1; /* SMPL = 4 */ + } + else { + number = (unsigned char)(ewtsa->delay / DATA_RATE_10); + if ((ewtsa->delay % DATA_RATE_10) != 0) { + number++; + } + smpl = DATA_RATE_10 - 1; /* SMPL = 9 */ + } + + val = (unsigned char)(fifo_type << 5); + val |= number; + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_CTRL2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_CTRL2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + ewtsa->fifo_num = number; + + val = CTRL_REG3_WTM_EN; + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG3, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG3 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG5, CTRL_REG5_FIFO_EN); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG5 write err(data = 0x%02x, ret = %d)\n", CTRL_REG5_FIFO_EN, err); + return; + } + } + err = i2c_smbus_write_byte_data(ewtsa->client, REG_DATA_RATE, smpl); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_DATA_RATE write err(data = 0x%02x, ret = %d)\n", smpl, err); + return; + } + } + ewtsa->reset_param = RST_REG_INIT_OFF; + + if (ewtsa->calib == EWTSA_ON) { + EWTSA_set_calibration(ewtsa); + } + else { + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, SLEEP_CTRL_SLEEP); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL write err(data = 0x%02x, ret = %d)\n", SLEEP_CTRL_SLEEP, err); + return; + } + +#if GYRO_VER3_ES1 + err = i2c_smbus_write_byte_data(ewtsa->client, REG2_CTRL_REG1, CTRL_REG1_ACTIVATE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG2_CTRL_REG1 write err(data = 0x%02x, ret = %d)\n", CTRL_REG1_ACTIVATE, err); + return; + } +#else /* GYRO_VER3_ES1 */ + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, SLEEP_CTRL_ACTIVATE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL write err(data = 0x%02x, ret = %d)\n", SLEEP_CTRL_ACTIVATE, err); + return; + } +#endif /* GYRO_VER3_ES1 */ + + /* FIFO clear */ + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_CTRL1, FIFO_CTRL1_CLEAR); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_CTRL1 write err(data = 0x02, ret = %d)\n", err); + return; + } + } + + ewtsa->calib_rst = EWTSA_OFF; +} + +static void EWTSA_set_calibration(struct ewtsa_t *ewtsa) +{ + unsigned char val; + s32 err; + signed long ret; + signed long l_timeout; + unsigned long center; + static const unsigned short ewtsa_boost_time[4] = { + EWTSA_BOOST_TIME_0, EWTSA_BOOST_TIME_1, EWTSA_BOOST_TIME_2, EWTSA_BOOST_TIME_3 + }; + static const unsigned char ewtsa_w1_threshold_level[4] = { + EWTSA_W1_THRES_LV_0, EWTSA_W1_THRES_LV_1, EWTSA_W1_THRES_LV_2, EWTSA_W1_THRES_LV_3 + }; + static const unsigned short ewtsa_dynamic_range[4] = { + EWTSA_RANGE_0, EWTSA_RANGE_1, EWTSA_RANGE_2, EWTSA_RANGE_3 + }; + + if (ewtsa == NULL) { + printk(KERN_INFO "EWTSA_system_restart:ewtsa null pointer\n"); + return; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT1, SOC_CNT1_WIN_DIS | SOC_CNT1_BOOST_ON); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT1 write err(data = 0x%02x, ret = %d)\n", SOC_CNT1_WIN_DIS | SOC_CNT1_BOOST_ON, err); + return; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, SLEEP_CTRL_SLEEP); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL write err(data = 0x%02x, ret = %d)\n", SLEEP_CTRL_SLEEP, err); + return; + } + +#if GYRO_VER3_ES1 + err = i2c_smbus_write_byte_data(ewtsa->client, REG2_CTRL_REG1, CTRL_REG1_ACTIVATE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG2_CTRL_REG1 write err(data = 0x%02x, ret = %d)\n", CTRL_REG1_ACTIVATE, err); + return; + } +#else /* GYRO_VER3_ES1 */ + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, SLEEP_CTRL_ACTIVATE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL write err(data = 0x%02x, ret = %d)\n", SLEEP_CTRL_ACTIVATE, err); + return; + } +#endif /* GYRO_VER3_ES1 */ + + if (ewtsa_w1_threshold_level[ewtsa->w1_thres_level] > ewtsa->w1_thres_x) { + center = ewtsa_w1_threshold_level[ewtsa->w1_thres_level] + 1; + } + else { + center = ewtsa->w1_thres_x; + } + center *= W1_THRES_CENTER_COEF; + center /= ewtsa_dynamic_range[ewtsa->range]; + val = (unsigned char)((0xFF00 & center) >> 8); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_REFERENCE, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_REFERENCE write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(CTRL_REG2_REF_MODE | CTRL_REG2_HPCF); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = SOC_CNT2_SOCCLK_ON; + val |= (unsigned char)(CALIB_FS_62_5HZ << 7); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + /* Wait reference mode setting */ + ret = (signed long)((REFERENCE_WAIT * HZ)/1000); + ret += (signed long)(1000 / HZ); + while (ret > 0) { + l_timeout = ret; + set_current_state(TASK_UNINTERRUPTIBLE); + ret = schedule_timeout(l_timeout); + } + + val = SOC_CNT2_SOCCLK_OFF; + val |= (unsigned char)(CALIB_FS_62_5HZ << 7); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(CTRL_REG2_NOR_MODE | CTRL_REG2_HPCF); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_CTRL_REG2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_CTRL_REG2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } +#if GYRO_VER3_ES3 || GYRO_VER3_ES4 //2012.01.25 for ES3 & ES4 + val = (unsigned char)(0x40 | SOC_CNT1_BOOST_ON); +#else + val = (unsigned char)((unsigned char)(ewtsa->w1_datamask << 6) | SOC_CNT1_BOOST_ON); +#endif + val |= (unsigned char)(ewtsa->w2_thres_en << 5); + val |= (unsigned char)(ewtsa->w1_thres_en << 4); + val |= (unsigned char)(ewtsa->calib_boost_tm << 2); + val |= (unsigned char)(ewtsa->calib_boost_fs << 1); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT1, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT1 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(SOC_CNT2_SOCCLK_ON | ewtsa->w1_thres_level); + val |= (unsigned char)(ewtsa->calib_fs << 7); + val |= (unsigned char)(ewtsa->offset_lag << 4); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(ewtsa->hpf << 7); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SLFSLP_CNT, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SLFSLP_CNT write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + if (ewtsa_w1_threshold_level[ewtsa->w1_thres_level] > ewtsa->w1_thres_x) { + center = ewtsa_w1_threshold_level[ewtsa->w1_thres_level] + 1; + } + else { + center = ewtsa->w1_thres_x; + } + center *= W1_THRES_CENTER_COEF; + center /= ewtsa_dynamic_range[ewtsa->range]; + val = (unsigned char)((0xFF00 & center) >> 8); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_W1THX_H, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_W1THX_H write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(0xFF & center); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_W1THX_L, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_W1THX_L write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + if (ewtsa_w1_threshold_level[ewtsa->w1_thres_level] > ewtsa->w1_thres_y) { + center = ewtsa_w1_threshold_level[ewtsa->w1_thres_level] + 1; + } + else { + center = ewtsa->w1_thres_y; + } + center *= W1_THRES_CENTER_COEF; + center /= ewtsa_dynamic_range[ewtsa->range]; + val = (unsigned char)((0xFF00 & center) >> 8); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_W1THY_H, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_W1THY_H write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(0xFF & center); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_W1THY_L, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_W1THY_L write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + if (ewtsa_w1_threshold_level[ewtsa->w1_thres_level] > ewtsa->w1_thres_z) { + center = ewtsa_w1_threshold_level[ewtsa->w1_thres_level] + 1; + } + else { + center = ewtsa->w1_thres_z; + } + center *= W1_THRES_CENTER_COEF; + center /= ewtsa_dynamic_range[ewtsa->range]; + val = (unsigned char)((0xFF00 & center) >> 8); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_W1THZ_H, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_W1THZ_H write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + val = (unsigned char)(0xFF & center); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_W1THZ_L, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_W1THZ_L write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + err = i2c_smbus_write_byte_data(ewtsa->client, REG_DIGITAL_CAL, DIGITAL_CAL_ENABLE); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_DIGITAL_CAL write err(data = 0x%02x, ret = %d)\n", DIGITAL_CAL_ENABLE, err); + return; + } + + /* Wait calibration boost */ + ret = (signed long)((ewtsa_boost_time[ewtsa->calib_boost_tm] * HZ)/1000); + ret += (signed long)(1000 / HZ); + while (ret > 0) { + l_timeout = ret; + set_current_state(TASK_UNINTERRUPTIBLE); + ret = schedule_timeout(l_timeout); + } + + /* boost stop */ + val = (unsigned char)(ewtsa->w1_datamask << 6); + val |= (unsigned char)(ewtsa->w2_thres_en << 5); + val |= (unsigned char)(ewtsa->w1_thres_en << 4); + val |= (unsigned char)(ewtsa->calib_boost_tm << 2); + val |= (unsigned char)(ewtsa->calib_boost_fs << 1); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT1, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT1 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + + if ((ewtsa->calib_alway != EWTSA_ON) || + ((ewtsa->w1_thres_en == THRESHOLD_OFF) && (ewtsa->w2_thres_en == THRESHOLD_OFF))) { + val = (unsigned char)(SOC_CNT2_SOCCLK_OFF | ewtsa->w1_thres_level); + err = i2c_smbus_write_byte_data(ewtsa->client, REG_SOC_CNT2, val); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_SOC_CNT2 write err(data = 0x%02x, ret = %d)\n", val, err); + return; + } + } + + /* FIFO clear */ + err = i2c_smbus_write_byte_data(ewtsa->client, REG_FIFO_CTRL1, FIFO_CTRL1_CLEAR); + if (err < 0) { + dev_err(&ewtsa->client->dev, "REG_FIFO_CTRL1 write err(data = 0x02, ret = %d)\n", err); + return; + } + return; +} + +/* Read the angular data from the registers */ +static void EWTSA_report_abs(struct ewtsa_t *ewt) +{ + unsigned short x = 0, y = 0, z = 0, temp = 0; + s16 send_x, send_y, send_z, send_temp; + unsigned char reg = REG_MBURST_ALL; + unsigned char reg2[32][8]; + int err; + struct i2c_msg msg[2]; +#if GYRO_MOUNT_SWAP_XY + s16 tmp; +#endif /* GYRO_MOUNT_SWAP_XY */ + unsigned char diag_stat = 0; + unsigned char loop; + unsigned long l_flags = 0; + signed long cal_tmp[4] = {0}; + unsigned char get_num = 0; + unsigned char read_num = 0; + + if (ewt == NULL) { + printk(KERN_INFO "struct ewtsa_t null pointer\n"); + return; + } + + if (ewt->event == EWTSA_POLLING_MODE) { + mutex_lock(&ewt->mlock); + } + else { + if (ewt->fifo_mode == FIFO_MODE) { + err = i2c_smbus_read_byte_data(ewt->client, REG_FIFO_DM); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_FIFO_DM read err(%d)\n", err); + err = 0; + } + read_num = (unsigned char)err; + if (((unsigned char)(read_num & 0x20) >> 5) == 1) { + /* data nothing */ + err = i2c_smbus_read_byte_data(ewt->client, REG_INT_STAT); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_INT_STAT read err(%d)\n", err); + } + return; + } + read_num &= 0x1F; + + if (ewt->fifo_num > read_num) { + err = i2c_smbus_read_byte_data(ewt->client, REG_INT_STAT); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_INT_STAT read err(%d)\n", err); + } + return; + } + } + } + + msg[0].addr = ewt->client->addr; + msg[0].flags = 0; + msg[0].len = 1; + msg[0].buf = ® + + msg[1].addr = ewt->client->addr; + msg[1].flags = I2C_M_RD; + msg[1].len = (unsigned short)(8 * ewt->fifo_num); + msg[1].buf = ®2[0][0]; + + err = i2c_transfer(ewt->client->adapter, msg, 2); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_MBURST_ALL read err(%d)\n", err); + memset(reg2, 0x00, sizeof(reg2)); + } + + for (loop = 0; loop < ewt->fifo_num; loop++) { + x = (unsigned short)(0xFF & reg2[loop][3]); + x |= (unsigned short)(0xFF00 & (unsigned short)(reg2[loop][2] << 8)); + y = (unsigned short)(0xFF & reg2[loop][5]); + y |= (unsigned short)(0xFF00 & (unsigned short)(reg2[loop][4] << 8)); + z = (unsigned short)(0xFF & reg2[loop][7]); + z |= (unsigned short)(0xFF00 & (unsigned short)(reg2[loop][6] << 8)); + temp = (unsigned short)(0xFF & reg2[loop][1]); + temp |= (unsigned short)(0xFF00 & (unsigned short)(reg2[loop][0] << 8)); + + cal_tmp[0] += (signed short)x; + cal_tmp[1] += (signed short)y; + cal_tmp[2] += (signed short)z; + cal_tmp[3] += (signed short)temp; + get_num++; + } + + err = i2c_smbus_read_byte_data(ewt->client, REG_STATUS_REG); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_STATUS_REG read err(%d)\n", err); + err = 0; + } + reg = (unsigned char)err; + if ((reg & STATUS_REG_OVERRUN) != 0) { + err = i2c_smbus_write_byte_data(ewt->client, REG_FIFO_CTRL1, FIFO_CTRL1_CLEAR); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_FIFO_CTRL1 read err(%d)\n", err); + } + } + + if (ewt->int_skip == EWSTA_INT_SKIP) { + ewt->int_skip = EWSTA_INT_CLEAR; + err = i2c_smbus_read_byte_data(ewt->client, REG_INT_STAT); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_INT_STAT read err(%d)\n", err); + } + return; + } + + if (get_num != 0) { + if (ewt->fifo_data_flag == EWTSA_ON) { + cal_tmp[0] += ewt->fifo_lastdata[0]; + cal_tmp[1] += ewt->fifo_lastdata[1]; + cal_tmp[2] += ewt->fifo_lastdata[2]; + cal_tmp[3] += ewt->fifo_lastdata[3]; + get_num++; + } + + /* fifo mode only */ + if (get_num > 1) { + ewt->fifo_lastdata[0] = (signed short)x; + ewt->fifo_lastdata[1] = (signed short)y; + ewt->fifo_lastdata[2] = (signed short)z; + ewt->fifo_lastdata[3] = (signed short)temp; + ewt->fifo_data_flag = EWTSA_ON; + } + + send_x = (signed short)(cal_tmp[0] / get_num); + send_y = (signed short)(cal_tmp[1] / get_num); + send_z = (signed short)(cal_tmp[2] / get_num); + send_temp = (signed short)(cal_tmp[3] / get_num); + } + else { + ewt->fifo_lastdata[0] = 0; + ewt->fifo_lastdata[1] = 0; + ewt->fifo_lastdata[2] = 0; + ewt->fifo_lastdata[3] = 0; + ewt->fifo_data_flag = EWTSA_OFF; + + send_x = 0; + send_y = 0; + send_z = 0; + send_temp = 0; + err = i2c_smbus_write_byte_data(ewt->client, REG_FIFO_CTRL1, FIFO_CTRL1_CLEAR); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_FIFO_CTRL1 read err(%d)\n", err); + } + } + +#if GYRO_MOUNT_SWAP_XY + tmp = send_x; + send_x = send_y; + send_y = tmp; +#endif /* GYRO_MOUNT_SWAP_XY */ +#if GYRO_MOUNT_REVERSE_X + send_x *= -1; +#endif /* GYRO_MOUNT_REVERSE_X */ +#if GYRO_MOUNT_REVERSE_Y + send_y *= -1; +#endif /* GYRO_MOUNT_REVERSE_Y */ +#if GYRO_MOUNT_REVERSE_Z + send_z *= -1; +#endif /* GYRO_MOUNT_REVERSE_Z */ + + if (ewt->event != EWTSA_POLLING_MODE) { + err = i2c_smbus_read_byte_data(ewt->client, REG_INT_STAT); + if (err < 0) { + dev_err(&ewt->client->dev, "REG_INT_STAT read err(%d)\n", err); + err = 0; + } + diag_stat = (unsigned char)err; + spin_lock_irqsave(&ewt->slock, l_flags); + } + + input_report_abs(ewt->input_dev, ABS_X, send_x); + input_report_abs(ewt->input_dev, ABS_Y, send_y); + input_report_abs(ewt->input_dev, ABS_Z, send_z); + input_report_abs(ewt->input_dev, ABS_RX, send_temp); + +#if USE_GPIO_DIAG_PIN + if (ewt->event == EWTSA_POLLING_MODE) { + input_report_abs(ewt->input_dev, ABS_RY, gpio_get_value(DIAG_PIN)); + } + else { + diag_stat = (unsigned char)((unsigned char)(diag_stat & 0x04) >> 2); + input_report_abs(ewt->input_dev, ABS_RY, diag_stat); + } +#else + diag_stat = (unsigned char)((unsigned char)(diag_stat & 0x04) >> 2); + input_report_abs(ewt->input_dev, ABS_RY, diag_stat); +#endif + input_sync(ewt->input_dev); + + if (ewt->event == EWTSA_POLLING_MODE) { + mutex_unlock(&ewt->mlock); + } + else { + spin_unlock_irqrestore(&ewt->slock, l_flags); + } +} + +static void EWTSA_dev_poll(struct work_struct *work) +{ + struct ewtsa_t *ewt; + + if (work == NULL) { + printk(KERN_INFO "struct work_struct null pointer\n"); + return; + } + ewt = container_of(work, struct ewtsa_t, input_work.work); + + if (ewt->sleep == SLEEP_OFF) { + EWTSA_report_abs(ewt); + } +} + +/* Interrupt handler */ +#if USE_GPIO_DRDY_PIN +static irqreturn_t EWTSA_interrupt(int irq, void *dev_id) +{ + struct ewtsa_t *ewtsa = dev_id; + + if (ewtsa == NULL) { + printk(KERN_INFO "struct ewtsa_t null pointer\n"); + return IRQ_RETVAL(1); + } + + if (irq == gpio_to_irq(DRDY_PIN)) { + if (ewtsa->sleep != SLEEP_OFF) { + return IRQ_RETVAL(1); + } + EWTSA_report_abs(ewtsa); + } + return IRQ_RETVAL(1); +} +#endif + +static void EWTSA_input_work_func(struct work_struct *work) +{ + struct ewtsa_t *ewt; + volatile unsigned long start_jiff = jiffies, delay_jiff, next_jiff; + + if (work == NULL) { + printk(KERN_INFO "struct work_struct null pointer\n"); + return; + } + ewt = container_of(work, struct ewtsa_t, input_work.work); + + if (ewt->sleep == SLEEP_OFF) { + EWTSA_dev_poll(work); + + /* adjust polling interval */ + delay_jiff = jiffies; + if (start_jiff > delay_jiff) { /* jiffies overflow */ + delay_jiff += 0xFFFFFFFFUL - start_jiff; + } + else { + delay_jiff -= start_jiff; + } + next_jiff = msecs_to_jiffies(ewt->delay); + /* for delay_jiff > next_jiff */ + delay_jiff %= next_jiff; + next_jiff -= delay_jiff; + + queue_delayed_work(ewt->workqueue, + &ewt->input_work, + next_jiff); + } +} + +/* Suspend function to register device */ +static int EWTSA_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct ewtsa_t *ewt; + s32 ret; + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewt = i2c_get_clientdata(client); + + if (ewt->event == EWTSA_POLLING_MODE) { + cancel_delayed_work(&ewt->input_work); + } + else { + /* disable interrupt */ + ret = i2c_smbus_write_byte_data(ewt->client, REG_INT_CTRL, INT_CTRL_INT_DISABLE); + if (ret < 0) { + dev_err(&ewt->client->dev, "REG_INT_CTRL write err(ret = %d)\n", ret); + } + + if (ewt->sleep == SLEEP_OFF) { +#if USE_GPIO_DRDY_PIN + free_irq(gpio_to_irq(DRDY_PIN), ewt); + /* keep sleep status for resume */ +#endif + } + else { + ewt->sleep = SLEEP_ALLRESET; + } + } +#if USE_GPIO_WINDOW_PIN + gpio_free(WINDOW_PIN); +#endif +#if USE_GPIO_DRDY_PIN + gpio_free(DRDY_PIN); +#endif +#if USE_GPIO_DIAG_PIN + gpio_free(DIAG_PIN); +#endif + ewt->calib_rst = EWTSA_ON; + ewt->reset_param = RST_REG_INIT_ALL; + ret = i2c_smbus_write_byte_data(ewt->client, REG_SLEEP_CTRL, SLEEP_CTRL_ALLRSTSLEEP); + if (ret < 0) { + dev_err(&ewt->client->dev, "REG_SLEEP_CTRL write err(ret = %d)\n", ret); + } + return 0; +} + +/* Resume function to register device */ +static int EWTSA_resume(struct i2c_client *client) +{ + struct ewtsa_t *ewt; + int ret; + + if (client == NULL) { + printk(KERN_INFO "struct i2c_client null pointer\n"); + return -EIO; + } + ewt = i2c_get_clientdata(client); + +#if USE_GPIO_WINDOW_PIN + ret = gpio_request(WINDOW_PIN, "WINDOW"); + ret += gpio_direction_input(WINDOW_PIN); +#else + ret = 0; +#endif +#if USE_GPIO_DIAG_PIN + ret += gpio_request(DIAG_PIN, "DIAG"); + ret += gpio_direction_input(DIAG_PIN); +#endif + if ((ewt->sleep != SLEEP_OFF) || (ret != 0)) { + return 0; + } + +#if GYRO_VER3_ES1 + ret = i2c_smbus_write_byte_data(ewt->client, REG2_CTRL_REG1, CTRL_REG1_ACTIVATE); +#else /* GYRO_VER3_ES1 */ + ret = i2c_smbus_write_byte_data(ewt->client, REG_SLEEP_CTRL, SLEEP_CTRL_ACTIVATE); +#endif /* GYRO_VER3_ES1 */ + + if (ret >= 0) { + EWTSA_system_restart(ewt); + + if (ewt->event == EWTSA_POLLING_MODE) { + queue_delayed_work(ewt->workqueue, + &ewt->input_work, + msecs_to_jiffies(ewt->delay)); + } + else { + /* enable interrupt */ +#if USE_GPIO_DRDY_PIN + ret = gpio_request(DRDY_PIN, "DRDY"); + ret += gpio_direction_input(DRDY_PIN); + ret += request_threaded_irq(gpio_to_irq(DRDY_PIN), NULL, EWTSA_interrupt, IRQF_TRIGGER_RISING, DEVICE_NAME, ewt); + + if (ret == 0) { + ewt->int_skip = EWSTA_INT_SKIP; + if (ewt->fifo_mode == BYPASS_MODE) { + ret = i2c_smbus_write_byte_data(ewt->client, REG_INT_CTRL, INT_CTRL_INT_ENABLE); + } + else { + ret = i2c_smbus_write_byte_data(ewt->client, REG_INT_CTRL, INT_CTRL_INT_ENABLE | INT_CTRL_INT_WTMON_EN); + } + if (ret < 0) { + dev_err(&ewt->client->dev, "REG_INT_CTRL write err(ret = %d)\n", ret); + } + } +#endif + } + } + else { + dev_err(&ewt->client->dev, "REG_SLEEP_CTRL write err(ret = %d)\n", ret); + } + + return 0; +} + +/* Probe function to register device */ +static int EWTSA_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret; + struct ewtsa_t *ewtsa; + + if (client == NULL) { + printk(KERN_INFO "EWTSA_probe:client null pointer\n"); + return -EIO; + } + + ewtsa = kzalloc(sizeof(*ewtsa), GFP_KERNEL); + if (ewtsa == NULL) { + dev_err(&client->dev, "unable to allocate memory!\n"); + return -ENOMEM; + } + + ewtsa->workqueue = create_workqueue("ewtsa_workqueue"); + if (ewtsa->workqueue == NULL) { + dev_err(&client->dev, "Unable to create workqueue!\n"); + kfree(ewtsa); + return -ENOMEM; + } + + ewtsa->client = client; + i2c_set_clientdata(client, ewtsa); + + ewtsa->sleep = SLEEP_INIT_VAL; /* sleep mode */ + ewtsa->delay = DELAY_INIT_VAL; /* information interval */ + ewtsa->range = RANGE_INIT_VAL; /* dynamic range */ + ewtsa->dlpf = DLPF_INIT_VAL; /* dlpf */ + ewtsa->calib = CALIB_FUNC_INIT_VAL; /* calibration function */ + ewtsa->calib_boost_tm = CALIB_BTIM_INIT_VAL; /* boost time[ms] */ + ewtsa->calib_fs = CALIB_FS_INIT_VAL; /* offset renewal rate */ + ewtsa->calib_rst = CALIB_RST_INIT_VAL; /* calib restart flag */ + ewtsa->fifo_mode = FIFO_TYPE_INIT_VAL; /* fifo type */ + ewtsa->reset_param = RST_REG_INIT_ALL; /* register setting flag */ +#if USE_GPIO_DRDY_PIN + ewtsa->event = EWTSA_INTERUPT_MODE; /* data detect method */ +#else + ewtsa->event = EWTSA_POLLING_MODE; /* data detect method */ +#endif + ewtsa->calib_boost_fs = CALIB_BFS_INIT_VAL; /* calibration boost fs */ + ewtsa->int_skip = EWSTA_INT_CLEAR; /* first data flag */ + ewtsa->calib_alway = CALIB_CLK_INIT_VAL; /* socclk flag */ + ewtsa->hpf = CALIB_HPF_INIT_VAL; /* hpf */ + ewtsa->w1_thres_x = THRES_CENTER_INIT_X; /* threshold center val */ + ewtsa->w1_thres_y = THRES_CENTER_INIT_Y; /* threshold center val */ + ewtsa->w1_thres_z = THRES_CENTER_INIT_Z; /* threshold center val */ + ewtsa->w1_datamask = W1_MASK_INIT_VAL; /* window1 datamask set */ + ewtsa->w1_thres_en = W1_FUNC_INIT_VAL; /* window1 function flag */ + ewtsa->w2_thres_en = W2_FUNC_INIT_VAL; /* window2 function flag */ + ewtsa->offset_lag = CALIB_LAG_INIT_VAL; /* offset renewal lag */ + ewtsa->w1_thres_level = W1_LV_INIT_VAL; /* window1 threshold lv */ + ewtsa->fifo_data_flag = EWTSA_OFF; /* last data flag */ + ewtsa->fifo_num = 0; /* fifo number */ + memset(ewtsa->fifo_lastdata, 0x00, sizeof(ewtsa->fifo_lastdata)); + + /*create device file in sysfs as user interface */ + ret = device_create_file(&client->dev, &ewtsa_sleep_attr); + if (ret != 0) { + dev_err(&client->dev, "create device(sleep_attr) file failed!\n"); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return ret; + } + ret = device_create_file(&client->dev, &ewtsa_delay_attr); + if (ret != 0) { + dev_err(&client->dev, "create device(delay_attr) file failed!\n"); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return ret; + } + ret = device_create_file(&client->dev, &ewtsa_range_attr); + if (ret != 0) { + dev_err(&client->dev, "create device(range_attr) file failed!\n"); + device_remove_file(&client->dev, &ewtsa_delay_attr); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return ret; + } + ret = device_create_file(&client->dev, &ewtsa_calib_attr); + if (ret != 0) { + dev_err(&client->dev, "create device(calib_attr) file failed!\n"); + device_remove_file(&client->dev, &ewtsa_range_attr); + device_remove_file(&client->dev, &ewtsa_delay_attr); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return ret; + } + ret = device_create_file(&client->dev, &ewtsa_config_attr); + if (ret != 0) { + dev_err(&client->dev, "create device(calib_attr) file failed!\n"); + device_remove_file(&client->dev, &ewtsa_calib_attr); + device_remove_file(&client->dev, &ewtsa_range_attr); + device_remove_file(&client->dev, &ewtsa_delay_attr); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return ret; + } + + /*input poll device register */ + ewtsa->input_dev = input_allocate_device(); + if (ewtsa->input_dev == NULL) { + dev_err(&client->dev, "alloc input device failed!\n"); + device_remove_file(&client->dev, &ewtsa_config_attr); + device_remove_file(&client->dev, &ewtsa_calib_attr); + device_remove_file(&client->dev, &ewtsa_range_attr); + device_remove_file(&client->dev, &ewtsa_delay_attr); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return -ENOMEM; + } + ewtsa->input_dev->name = DEVICE_NAME; + ewtsa->input_dev->id.bustype = BUS_I2C; + ewtsa->input_dev->evbit[0] = BIT_MASK(EV_ABS); + + mutex_init(&ewtsa->mlock); + spin_lock_init(&ewtsa->slock); + + input_set_abs_params(ewtsa->input_dev, ABS_X, -MAX_VALUE, MAX_VALUE, 0, 0); + input_set_abs_params(ewtsa->input_dev, ABS_Y, -MAX_VALUE, MAX_VALUE, 0, 0); + input_set_abs_params(ewtsa->input_dev, ABS_Z, -MAX_VALUE, MAX_VALUE, 0, 0); + input_set_abs_params(ewtsa->input_dev, ABS_RX, -200, 200, 0, 0); + input_set_abs_params(ewtsa->input_dev, ABS_RY, -200, 200, 0, 0); + + ret = input_register_device(ewtsa->input_dev); + if (ret != 0) { + dev_err(&client->dev, "register poll device failed!\n"); + input_free_device(ewtsa->input_dev); + device_remove_file(&client->dev, &ewtsa_config_attr); + device_remove_file(&client->dev, &ewtsa_calib_attr); + device_remove_file(&client->dev, &ewtsa_range_attr); + device_remove_file(&client->dev, &ewtsa_delay_attr); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return ret; + } + + /* GPIO configuration */ + ret = i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, SLEEP_CTRL_ALLRSTSLEEP); + if (ret < 0) { + dev_err(&ewtsa->client->dev, "REG_SLEEP_CTRL write err(data = 0x00, ret = %d)\n", ret); + } +#if USE_GPIO_WINDOW_PIN + ret = gpio_request(WINDOW_PIN, "WINDOW"); + ret += gpio_direction_input(WINDOW_PIN); +#else + ret = 0; +#endif +#if USE_GPIO_DIAG_PIN + ret += gpio_request(DIAG_PIN, "DIAG"); + ret += gpio_direction_input(DIAG_PIN); +#endif + if (ret != 0) { + dev_err(&client->dev, "gpio_request failed!\n"); +#if USE_GPIO_WINDOW_PIN + gpio_free(WINDOW_PIN); +#endif +#if USE_GPIO_DIAG_PIN + gpio_free(DIAG_PIN); +#endif + input_unregister_device(ewtsa->input_dev); + input_free_device(ewtsa->input_dev); + device_remove_file(&client->dev, &ewtsa_config_attr); + device_remove_file(&client->dev, &ewtsa_calib_attr); + device_remove_file(&client->dev, &ewtsa_range_attr); + device_remove_file(&client->dev, &ewtsa_delay_attr); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + destroy_workqueue(ewtsa->workqueue); + kfree(ewtsa); + return ret; + } + + INIT_DELAYED_WORK(&ewtsa->input_work, EWTSA_input_work_func); + + dev_info(&client->dev, "ewtsa device is probed successfully.\n"); + return 0; +} + +/* Remove function to unregister the device */ +static int EWTSA_remove(struct i2c_client *client) +{ + struct ewtsa_t *ewtsa; + + if (client == NULL) { + printk(KERN_INFO "client pointer null!\n"); + return -EIO; + } + ewtsa = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(ewtsa->client, REG_SLEEP_CTRL, SLEEP_CTRL_ALLRSTSLEEP); +#if USE_GPIO_DRDY_PIN + if (ewtsa->sleep == SLEEP_OFF) { + free_irq(gpio_to_irq(DRDY_PIN), ewtsa); + gpio_free(DRDY_PIN); + } +#endif +#if USE_GPIO_WINDOW_PIN + gpio_free(WINDOW_PIN); +#endif +#if USE_GPIO_DIAG_PIN + gpio_free(DIAG_PIN); +#endif + + input_unregister_device(ewtsa->input_dev); + input_free_device(ewtsa->input_dev); + destroy_workqueue(ewtsa->workqueue); + device_remove_file(&client->dev, &ewtsa_sleep_attr); + device_remove_file(&client->dev, &ewtsa_delay_attr); + device_remove_file(&client->dev, &ewtsa_range_attr); + device_remove_file(&client->dev, &ewtsa_calib_attr); + device_remove_file(&client->dev, &ewtsa_config_attr); + kfree(ewtsa); + dev_info(&client->dev, "ewtsa device is removed successfully.\n"); + return 0; +} + + +static int __init init_ewtsa(void) +{ + return i2c_add_driver(&i2c_ewtsa_driver); +} + +static void __exit exit_ewtsa(void) +{ + i2c_del_driver(&i2c_ewtsa_driver); +} + +module_init(init_ewtsa); +module_exit(exit_ewtsa); + +MODULE_AUTHOR("Panasonic Electronic Devices"); +MODULE_DESCRIPTION("Gyro sensor driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/gp2ap002a00f.c b/drivers/input/misc/gp2ap002a00f.c new file mode 100644 index 00000000..fe30bd0f --- /dev/null +++ b/drivers/input/misc/gp2ap002a00f.c @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2011 Sony Ericsson Mobile Communications Inc. + * + * Author: Courtney Cavin <courtney.cavin@sonyericsson.com> + * Prepared for up-stream by: Oskar Andero <oskar.andero@sonyericsson.com> + * + * 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. + */ + +#include <linux/i2c.h> +#include <linux/irq.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/gpio.h> +#include <linux/delay.h> +#include <linux/input/gp2ap002a00f.h> + +struct gp2a_data { + struct input_dev *input; + const struct gp2a_platform_data *pdata; + struct i2c_client *i2c_client; +}; + +enum gp2a_addr { + GP2A_ADDR_PROX = 0x0, + GP2A_ADDR_GAIN = 0x1, + GP2A_ADDR_HYS = 0x2, + GP2A_ADDR_CYCLE = 0x3, + GP2A_ADDR_OPMOD = 0x4, + GP2A_ADDR_CON = 0x6 +}; + +enum gp2a_controls { + /* Software Shutdown control: 0 = shutdown, 1 = normal operation */ + GP2A_CTRL_SSD = 0x01 +}; + +static int gp2a_report(struct gp2a_data *dt) +{ + int vo = gpio_get_value(dt->pdata->vout_gpio); + + input_report_switch(dt->input, SW_FRONT_PROXIMITY, !vo); + input_sync(dt->input); + + return 0; +} + +static irqreturn_t gp2a_irq(int irq, void *handle) +{ + struct gp2a_data *dt = handle; + + gp2a_report(dt); + + return IRQ_HANDLED; +} + +static int gp2a_enable(struct gp2a_data *dt) +{ + return i2c_smbus_write_byte_data(dt->i2c_client, GP2A_ADDR_OPMOD, + GP2A_CTRL_SSD); +} + +static int gp2a_disable(struct gp2a_data *dt) +{ + return i2c_smbus_write_byte_data(dt->i2c_client, GP2A_ADDR_OPMOD, + 0x00); +} + +static int gp2a_device_open(struct input_dev *dev) +{ + struct gp2a_data *dt = input_get_drvdata(dev); + int error; + + error = gp2a_enable(dt); + if (error < 0) { + dev_err(&dt->i2c_client->dev, + "unable to activate, err %d\n", error); + return error; + } + + gp2a_report(dt); + + return 0; +} + +static void gp2a_device_close(struct input_dev *dev) +{ + struct gp2a_data *dt = input_get_drvdata(dev); + int error; + + error = gp2a_disable(dt); + if (error < 0) + dev_err(&dt->i2c_client->dev, + "unable to deactivate, err %d\n", error); +} + +static int gp2a_initialize(struct gp2a_data *dt) +{ + int error; + + error = i2c_smbus_write_byte_data(dt->i2c_client, GP2A_ADDR_GAIN, + 0x08); + if (error < 0) + return error; + + error = i2c_smbus_write_byte_data(dt->i2c_client, GP2A_ADDR_HYS, + 0xc2); + if (error < 0) + return error; + + error = i2c_smbus_write_byte_data(dt->i2c_client, GP2A_ADDR_CYCLE, + 0x04); + if (error < 0) + return error; + + error = gp2a_disable(dt); + + return error; +} + +static int gp2a_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct gp2a_platform_data *pdata = client->dev.platform_data; + struct gp2a_data *dt; + int error; + + if (!pdata) + return -EINVAL; + + if (pdata->hw_setup) { + error = pdata->hw_setup(client); + if (error < 0) + return error; + } + + error = gpio_request_one(pdata->vout_gpio, GPIOF_IN, GP2A_I2C_NAME); + if (error) + goto err_hw_shutdown; + + dt = kzalloc(sizeof(struct gp2a_data), GFP_KERNEL); + if (!dt) { + error = -ENOMEM; + goto err_free_gpio; + } + + dt->pdata = pdata; + dt->i2c_client = client; + + error = gp2a_initialize(dt); + if (error < 0) + goto err_free_mem; + + dt->input = input_allocate_device(); + if (!dt->input) { + error = -ENOMEM; + goto err_free_mem; + } + + input_set_drvdata(dt->input, dt); + + dt->input->open = gp2a_device_open; + dt->input->close = gp2a_device_close; + dt->input->name = GP2A_I2C_NAME; + dt->input->id.bustype = BUS_I2C; + dt->input->dev.parent = &client->dev; + + input_set_capability(dt->input, EV_SW, SW_FRONT_PROXIMITY); + + error = request_threaded_irq(client->irq, NULL, gp2a_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + GP2A_I2C_NAME, dt); + if (error) { + dev_err(&client->dev, "irq request failed\n"); + goto err_free_input_dev; + } + + error = input_register_device(dt->input); + if (error) { + dev_err(&client->dev, "device registration failed\n"); + goto err_free_irq; + } + + device_init_wakeup(&client->dev, pdata->wakeup); + i2c_set_clientdata(client, dt); + + return 0; + +err_free_irq: + free_irq(client->irq, dt); +err_free_input_dev: + input_free_device(dt->input); +err_free_mem: + kfree(dt); +err_free_gpio: + gpio_free(pdata->vout_gpio); +err_hw_shutdown: + if (pdata->hw_shutdown) + pdata->hw_shutdown(client); + return error; +} + +static int gp2a_remove(struct i2c_client *client) +{ + struct gp2a_data *dt = i2c_get_clientdata(client); + const struct gp2a_platform_data *pdata = dt->pdata; + + device_init_wakeup(&client->dev, false); + + free_irq(client->irq, dt); + + input_unregister_device(dt->input); + kfree(dt); + + gpio_free(pdata->vout_gpio); + + if (pdata->hw_shutdown) + pdata->hw_shutdown(client); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int gp2a_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *dt = i2c_get_clientdata(client); + int retval = 0; + + if (device_may_wakeup(&client->dev)) { + enable_irq_wake(client->irq); + } else { + mutex_lock(&dt->input->mutex); + if (dt->input->users) + retval = gp2a_disable(dt); + mutex_unlock(&dt->input->mutex); + } + + return retval; +} + +static int gp2a_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct gp2a_data *dt = i2c_get_clientdata(client); + int retval = 0; + + if (device_may_wakeup(&client->dev)) { + disable_irq_wake(client->irq); + } else { + mutex_lock(&dt->input->mutex); + if (dt->input->users) + retval = gp2a_enable(dt); + mutex_unlock(&dt->input->mutex); + } + + return retval; +} +#endif + +static SIMPLE_DEV_PM_OPS(gp2a_pm, gp2a_suspend, gp2a_resume); + +static const struct i2c_device_id gp2a_i2c_id[] = { + { GP2A_I2C_NAME, 0 }, + { } +}; + +static struct i2c_driver gp2a_i2c_driver = { + .driver = { + .name = GP2A_I2C_NAME, + .owner = THIS_MODULE, + .pm = &gp2a_pm, + }, + .probe = gp2a_probe, + .remove = gp2a_remove, + .id_table = gp2a_i2c_id, +}; + +module_i2c_driver(gp2a_i2c_driver); + +MODULE_AUTHOR("Courtney Cavin <courtney.cavin@sonyericsson.com>"); +MODULE_DESCRIPTION("Sharp GP2AP002A00F I2C Proximity/Opto sensor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/misc/gpio_axis.c b/drivers/input/misc/gpio_axis.c new file mode 100644 index 00000000..0acf4a57 --- /dev/null +++ b/drivers/input/misc/gpio_axis.c @@ -0,0 +1,192 @@ +/* drivers/input/misc/gpio_axis.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> +#include <linux/interrupt.h> +#include <linux/slab.h> + +struct gpio_axis_state { + struct gpio_event_input_devs *input_devs; + struct gpio_event_axis_info *info; + uint32_t pos; +}; + +uint16_t gpio_axis_4bit_gray_map_table[] = { + [0x0] = 0x0, [0x1] = 0x1, /* 0000 0001 */ + [0x3] = 0x2, [0x2] = 0x3, /* 0011 0010 */ + [0x6] = 0x4, [0x7] = 0x5, /* 0110 0111 */ + [0x5] = 0x6, [0x4] = 0x7, /* 0101 0100 */ + [0xc] = 0x8, [0xd] = 0x9, /* 1100 1101 */ + [0xf] = 0xa, [0xe] = 0xb, /* 1111 1110 */ + [0xa] = 0xc, [0xb] = 0xd, /* 1010 1011 */ + [0x9] = 0xe, [0x8] = 0xf, /* 1001 1000 */ +}; +uint16_t gpio_axis_4bit_gray_map(struct gpio_event_axis_info *info, uint16_t in) +{ + return gpio_axis_4bit_gray_map_table[in]; +} + +uint16_t gpio_axis_5bit_singletrack_map_table[] = { + [0x10] = 0x00, [0x14] = 0x01, [0x1c] = 0x02, /* 10000 10100 11100 */ + [0x1e] = 0x03, [0x1a] = 0x04, [0x18] = 0x05, /* 11110 11010 11000 */ + [0x08] = 0x06, [0x0a] = 0x07, [0x0e] = 0x08, /* 01000 01010 01110 */ + [0x0f] = 0x09, [0x0d] = 0x0a, [0x0c] = 0x0b, /* 01111 01101 01100 */ + [0x04] = 0x0c, [0x05] = 0x0d, [0x07] = 0x0e, /* 00100 00101 00111 */ + [0x17] = 0x0f, [0x16] = 0x10, [0x06] = 0x11, /* 10111 10110 00110 */ + [0x02] = 0x12, [0x12] = 0x13, [0x13] = 0x14, /* 00010 10010 10011 */ + [0x1b] = 0x15, [0x0b] = 0x16, [0x03] = 0x17, /* 11011 01011 00011 */ + [0x01] = 0x18, [0x09] = 0x19, [0x19] = 0x1a, /* 00001 01001 11001 */ + [0x1d] = 0x1b, [0x15] = 0x1c, [0x11] = 0x1d, /* 11101 10101 10001 */ +}; +uint16_t gpio_axis_5bit_singletrack_map( + struct gpio_event_axis_info *info, uint16_t in) +{ + return gpio_axis_5bit_singletrack_map_table[in]; +} + +static void gpio_event_update_axis(struct gpio_axis_state *as, int report) +{ + struct gpio_event_axis_info *ai = as->info; + int i; + int change; + uint16_t state = 0; + uint16_t pos; + uint16_t old_pos = as->pos; + for (i = ai->count - 1; i >= 0; i--) + state = (state << 1) | gpio_get_value(ai->gpio[i]); + pos = ai->map(ai, state); + if (ai->flags & GPIOEAF_PRINT_RAW) + pr_info("axis %d-%d raw %x, pos %d -> %d\n", + ai->type, ai->code, state, old_pos, pos); + if (report && pos != old_pos) { + if (ai->type == EV_REL) { + change = (ai->decoded_size + pos - old_pos) % + ai->decoded_size; + if (change > ai->decoded_size / 2) + change -= ai->decoded_size; + if (change == ai->decoded_size / 2) { + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d unknown direction, " + "pos %d -> %d\n", ai->type, + ai->code, old_pos, pos); + change = 0; /* no closest direction */ + } + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d change %d\n", + ai->type, ai->code, change); + input_report_rel(as->input_devs->dev[ai->dev], + ai->code, change); + } else { + if (ai->flags & GPIOEAF_PRINT_EVENT) + pr_info("axis %d-%d now %d\n", + ai->type, ai->code, pos); + input_event(as->input_devs->dev[ai->dev], + ai->type, ai->code, pos); + } + input_sync(as->input_devs->dev[ai->dev]); + } + as->pos = pos; +} + +static irqreturn_t gpio_axis_irq_handler(int irq, void *dev_id) +{ + struct gpio_axis_state *as = dev_id; + gpio_event_update_axis(as, 1); + return IRQ_HANDLED; +} + +int gpio_event_axis_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int ret; + int i; + int irq; + struct gpio_event_axis_info *ai; + struct gpio_axis_state *as; + + ai = container_of(info, struct gpio_event_axis_info, info); + if (func == GPIO_EVENT_FUNC_SUSPEND) { + for (i = 0; i < ai->count; i++) + disable_irq(gpio_to_irq(ai->gpio[i])); + return 0; + } + if (func == GPIO_EVENT_FUNC_RESUME) { + for (i = 0; i < ai->count; i++) + enable_irq(gpio_to_irq(ai->gpio[i])); + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + *data = as = kmalloc(sizeof(*as), GFP_KERNEL); + if (as == NULL) { + ret = -ENOMEM; + goto err_alloc_axis_state_failed; + } + as->input_devs = input_devs; + as->info = ai; + if (ai->dev >= input_devs->count) { + pr_err("gpio_event_axis: bad device index %d >= %d " + "for %d:%d\n", ai->dev, input_devs->count, + ai->type, ai->code); + ret = -EINVAL; + goto err_bad_device_index; + } + + input_set_capability(input_devs->dev[ai->dev], + ai->type, ai->code); + if (ai->type == EV_ABS) { + input_set_abs_params(input_devs->dev[ai->dev], ai->code, + 0, ai->decoded_size - 1, 0, 0); + } + for (i = 0; i < ai->count; i++) { + ret = gpio_request(ai->gpio[i], "gpio_event_axis"); + if (ret < 0) + goto err_request_gpio_failed; + ret = gpio_direction_input(ai->gpio[i]); + if (ret < 0) + goto err_gpio_direction_input_failed; + ret = irq = gpio_to_irq(ai->gpio[i]); + if (ret < 0) + goto err_get_irq_num_failed; + ret = request_irq(irq, gpio_axis_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "gpio_event_axis", as); + if (ret < 0) + goto err_request_irq_failed; + } + gpio_event_update_axis(as, 0); + return 0; + } + + ret = 0; + as = *data; + for (i = ai->count - 1; i >= 0; i--) { + free_irq(gpio_to_irq(ai->gpio[i]), as); +err_request_irq_failed: +err_get_irq_num_failed: +err_gpio_direction_input_failed: + gpio_free(ai->gpio[i]); +err_request_gpio_failed: + ; + } +err_bad_device_index: + kfree(as); + *data = NULL; +err_alloc_axis_state_failed: + return ret; +} diff --git a/drivers/input/misc/gpio_event.c b/drivers/input/misc/gpio_event.c new file mode 100644 index 00000000..90f07eba --- /dev/null +++ b/drivers/input/misc/gpio_event.c @@ -0,0 +1,228 @@ +/* drivers/input/misc/gpio_event.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/gpio_event.h> +#include <linux/hrtimer.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct gpio_event { + struct gpio_event_input_devs *input_devs; + const struct gpio_event_platform_data *info; + void *state[0]; +}; + +static int gpio_input_event( + struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + int i; + int devnr; + int ret = 0; + int tmp_ret; + struct gpio_event_info **ii; + struct gpio_event *ip = input_get_drvdata(dev); + + for (devnr = 0; devnr < ip->input_devs->count; devnr++) + if (ip->input_devs->dev[devnr] == dev) + break; + if (devnr == ip->input_devs->count) { + pr_err("gpio_input_event: unknown device %p\n", dev); + return -EIO; + } + + for (i = 0, ii = ip->info->info; i < ip->info->info_count; i++, ii++) { + if ((*ii)->event) { + tmp_ret = (*ii)->event(ip->input_devs, *ii, + &ip->state[i], + devnr, type, code, value); + if (tmp_ret) + ret = tmp_ret; + } + } + return ret; +} + +static int gpio_event_call_all_func(struct gpio_event *ip, int func) +{ + int i; + int ret; + struct gpio_event_info **ii; + + if (func == GPIO_EVENT_FUNC_INIT || func == GPIO_EVENT_FUNC_RESUME) { + ii = ip->info->info; + for (i = 0; i < ip->info->info_count; i++, ii++) { + if ((*ii)->func == NULL) { + ret = -ENODEV; + pr_err("gpio_event_probe: Incomplete pdata, " + "no function\n"); + goto err_no_func; + } + if (func == GPIO_EVENT_FUNC_RESUME && (*ii)->no_suspend) + continue; + ret = (*ii)->func(ip->input_devs, *ii, &ip->state[i], + func); + if (ret) { + pr_err("gpio_event_probe: function failed\n"); + goto err_func_failed; + } + } + return 0; + } + + ret = 0; + i = ip->info->info_count; + ii = ip->info->info + i; + while (i > 0) { + i--; + ii--; + if ((func & ~1) == GPIO_EVENT_FUNC_SUSPEND && (*ii)->no_suspend) + continue; + (*ii)->func(ip->input_devs, *ii, &ip->state[i], func & ~1); +err_func_failed: +err_no_func: + ; + } + return ret; +} + +static void __maybe_unused gpio_event_suspend(struct gpio_event *ip) +{ + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_SUSPEND); + if (ip->info->power) + ip->info->power(ip->info, 0); +} + +static void __maybe_unused gpio_event_resume(struct gpio_event *ip) +{ + if (ip->info->power) + ip->info->power(ip->info, 1); + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_RESUME); +} + +static int gpio_event_probe(struct platform_device *pdev) +{ + int err; + struct gpio_event *ip; + struct gpio_event_platform_data *event_info; + int dev_count = 1; + int i; + int registered = 0; + + event_info = pdev->dev.platform_data; + if (event_info == NULL) { + pr_err("gpio_event_probe: No pdata\n"); + return -ENODEV; + } + if ((!event_info->name && !event_info->names[0]) || + !event_info->info || !event_info->info_count) { + pr_err("gpio_event_probe: Incomplete pdata\n"); + return -ENODEV; + } + if (!event_info->name) + while (event_info->names[dev_count]) + dev_count++; + ip = kzalloc(sizeof(*ip) + + sizeof(ip->state[0]) * event_info->info_count + + sizeof(*ip->input_devs) + + sizeof(ip->input_devs->dev[0]) * dev_count, GFP_KERNEL); + if (ip == NULL) { + err = -ENOMEM; + pr_err("gpio_event_probe: Failed to allocate private data\n"); + goto err_kp_alloc_failed; + } + ip->input_devs = (void*)&ip->state[event_info->info_count]; + platform_set_drvdata(pdev, ip); + + for (i = 0; i < dev_count; i++) { + struct input_dev *input_dev = input_allocate_device(); + if (input_dev == NULL) { + err = -ENOMEM; + pr_err("gpio_event_probe: " + "Failed to allocate input device\n"); + goto err_input_dev_alloc_failed; + } + input_set_drvdata(input_dev, ip); + input_dev->name = event_info->name ? + event_info->name : event_info->names[i]; + input_dev->event = gpio_input_event; + ip->input_devs->dev[i] = input_dev; + } + ip->input_devs->count = dev_count; + ip->info = event_info; + if (event_info->power) + ip->info->power(ip->info, 1); + + err = gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_INIT); + if (err) + goto err_call_all_func_failed; + + for (i = 0; i < dev_count; i++) { + err = input_register_device(ip->input_devs->dev[i]); + if (err) { + pr_err("gpio_event_probe: Unable to register %s " + "input device\n", ip->input_devs->dev[i]->name); + goto err_input_register_device_failed; + } + registered++; + } + + return 0; + +err_input_register_device_failed: + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT); +err_call_all_func_failed: + if (event_info->power) + ip->info->power(ip->info, 0); + for (i = 0; i < registered; i++) + input_unregister_device(ip->input_devs->dev[i]); + for (i = dev_count - 1; i >= registered; i--) { + input_free_device(ip->input_devs->dev[i]); +err_input_dev_alloc_failed: + ; + } + kfree(ip); +err_kp_alloc_failed: + return err; +} + +static int gpio_event_remove(struct platform_device *pdev) +{ + struct gpio_event *ip = platform_get_drvdata(pdev); + int i; + + gpio_event_call_all_func(ip, GPIO_EVENT_FUNC_UNINIT); + if (ip->info->power) + ip->info->power(ip->info, 0); + for (i = 0; i < ip->input_devs->count; i++) + input_unregister_device(ip->input_devs->dev[i]); + kfree(ip); + return 0; +} + +static struct platform_driver gpio_event_driver = { + .probe = gpio_event_probe, + .remove = gpio_event_remove, + .driver = { + .name = GPIO_EVENT_DEV_NAME, + }, +}; + +module_platform_driver(gpio_event_driver); + +MODULE_DESCRIPTION("GPIO Event Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/input/misc/gpio_input.c b/drivers/input/misc/gpio_input.c new file mode 100644 index 00000000..eefd0272 --- /dev/null +++ b/drivers/input/misc/gpio_input.c @@ -0,0 +1,390 @@ +/* drivers/input/misc/gpio_input.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> +#include <linux/hrtimer.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/pm_wakeup.h> + +enum { + DEBOUNCE_UNSTABLE = BIT(0), /* Got irq, while debouncing */ + DEBOUNCE_PRESSED = BIT(1), + DEBOUNCE_NOTPRESSED = BIT(2), + DEBOUNCE_WAIT_IRQ = BIT(3), /* Stable irq state */ + DEBOUNCE_POLL = BIT(4), /* Stable polling state */ + + DEBOUNCE_UNKNOWN = + DEBOUNCE_PRESSED | DEBOUNCE_NOTPRESSED, +}; + +struct gpio_key_state { + struct gpio_input_state *ds; + uint8_t debounce; +}; + +struct gpio_input_state { + struct gpio_event_input_devs *input_devs; + const struct gpio_event_input_info *info; + struct hrtimer timer; + int use_irq; + int debounce_count; + spinlock_t irq_lock; + struct wakeup_source *ws; + struct gpio_key_state key_state[0]; +}; + +static enum hrtimer_restart gpio_event_input_timer_func(struct hrtimer *timer) +{ + int i; + int pressed; + struct gpio_input_state *ds = + container_of(timer, struct gpio_input_state, timer); + unsigned gpio_flags = ds->info->flags; + unsigned npolarity; + int nkeys = ds->info->keymap_size; + const struct gpio_event_direct_entry *key_entry; + struct gpio_key_state *key_state; + unsigned long irqflags; + uint8_t debounce; + bool sync_needed; + +#if 0 + key_entry = kp->keys_info->keymap; + key_state = kp->key_state; + for (i = 0; i < nkeys; i++, key_entry++, key_state++) + pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, + gpio_read_detect_status(key_entry->gpio)); +#endif + key_entry = ds->info->keymap; + key_state = ds->key_state; + sync_needed = false; + spin_lock_irqsave(&ds->irq_lock, irqflags); + for (i = 0; i < nkeys; i++, key_entry++, key_state++) { + debounce = key_state->debounce; + if (debounce & DEBOUNCE_WAIT_IRQ) + continue; + if (key_state->debounce & DEBOUNCE_UNSTABLE) { + debounce = key_state->debounce = DEBOUNCE_UNKNOWN; + enable_irq(gpio_to_irq(key_entry->gpio)); + if (gpio_flags & GPIOEDF_PRINT_KEY_UNSTABLE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) continue debounce\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + } + npolarity = !(gpio_flags & GPIOEDF_ACTIVE_HIGH); + pressed = gpio_get_value(key_entry->gpio) ^ npolarity; + if (debounce & DEBOUNCE_POLL) { + if (pressed == !(debounce & DEBOUNCE_PRESSED)) { + ds->debounce_count++; + key_state->debounce = DEBOUNCE_UNKNOWN; + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-" + "%x, %d (%d) start debounce\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + } + continue; + } + if (pressed && (debounce & DEBOUNCE_NOTPRESSED)) { + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) debounce pressed 1\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + key_state->debounce = DEBOUNCE_PRESSED; + continue; + } + if (!pressed && (debounce & DEBOUNCE_PRESSED)) { + if (gpio_flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_keys_scan_keys: key %x-%x, %d " + "(%d) debounce pressed 0\n", + ds->info->type, key_entry->code, + i, key_entry->gpio); + key_state->debounce = DEBOUNCE_NOTPRESSED; + continue; + } + /* key is stable */ + ds->debounce_count--; + if (ds->use_irq) + key_state->debounce |= DEBOUNCE_WAIT_IRQ; + else + key_state->debounce |= DEBOUNCE_POLL; + if (gpio_flags & GPIOEDF_PRINT_KEYS) + pr_info("gpio_keys_scan_keys: key %x-%x, %d (%d) " + "changed to %d\n", ds->info->type, + key_entry->code, i, key_entry->gpio, pressed); + input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, + key_entry->code, pressed); + sync_needed = true; + } + if (sync_needed) { + for (i = 0; i < ds->input_devs->count; i++) + input_sync(ds->input_devs->dev[i]); + } + +#if 0 + key_entry = kp->keys_info->keymap; + key_state = kp->key_state; + for (i = 0; i < nkeys; i++, key_entry++, key_state++) { + pr_info("gpio_read_detect_status %d %d\n", key_entry->gpio, + gpio_read_detect_status(key_entry->gpio)); + } +#endif + + if (ds->debounce_count) + hrtimer_start(timer, ds->info->debounce_time, HRTIMER_MODE_REL); + else if (!ds->use_irq) + hrtimer_start(timer, ds->info->poll_time, HRTIMER_MODE_REL); + else + __pm_relax(ds->ws); + + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + + return HRTIMER_NORESTART; +} + +static irqreturn_t gpio_event_input_irq_handler(int irq, void *dev_id) +{ + struct gpio_key_state *ks = dev_id; + struct gpio_input_state *ds = ks->ds; + int keymap_index = ks - ds->key_state; + const struct gpio_event_direct_entry *key_entry; + unsigned long irqflags; + int pressed; + + if (!ds->use_irq) + return IRQ_HANDLED; + + key_entry = &ds->info->keymap[keymap_index]; + + if (ds->info->debounce_time.tv64) { + spin_lock_irqsave(&ds->irq_lock, irqflags); + if (ks->debounce & DEBOUNCE_WAIT_IRQ) { + ks->debounce = DEBOUNCE_UNKNOWN; + if (ds->debounce_count++ == 0) { + __pm_stay_awake(ds->ws); + hrtimer_start( + &ds->timer, ds->info->debounce_time, + HRTIMER_MODE_REL); + } + if (ds->info->flags & GPIOEDF_PRINT_KEY_DEBOUNCE) + pr_info("gpio_event_input_irq_handler: " + "key %x-%x, %d (%d) start debounce\n", + ds->info->type, key_entry->code, + keymap_index, key_entry->gpio); + } else { + disable_irq_nosync(irq); + ks->debounce = DEBOUNCE_UNSTABLE; + } + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + } else { + pressed = gpio_get_value(key_entry->gpio) ^ + !(ds->info->flags & GPIOEDF_ACTIVE_HIGH); + if (ds->info->flags & GPIOEDF_PRINT_KEYS) + pr_info("gpio_event_input_irq_handler: key %x-%x, %d " + "(%d) changed to %d\n", + ds->info->type, key_entry->code, keymap_index, + key_entry->gpio, pressed); + input_event(ds->input_devs->dev[key_entry->dev], ds->info->type, + key_entry->code, pressed); + input_sync(ds->input_devs->dev[key_entry->dev]); + } + return IRQ_HANDLED; +} + +static int gpio_event_input_request_irqs(struct gpio_input_state *ds) +{ + int i; + int err; + unsigned int irq; + unsigned long req_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING; + + for (i = 0; i < ds->info->keymap_size; i++) { + err = irq = gpio_to_irq(ds->info->keymap[i].gpio); + if (err < 0) + goto err_gpio_get_irq_num_failed; + err = request_irq(irq, gpio_event_input_irq_handler, + req_flags, "gpio_keys", &ds->key_state[i]); + if (err) { + pr_err("gpio_event_input_request_irqs: request_irq " + "failed for input %d, irq %d\n", + ds->info->keymap[i].gpio, irq); + goto err_request_irq_failed; + } + if (ds->info->info.no_suspend) { + err = enable_irq_wake(irq); + if (err) { + pr_err("gpio_event_input_request_irqs: " + "enable_irq_wake failed for input %d, " + "irq %d\n", + ds->info->keymap[i].gpio, irq); + goto err_enable_irq_wake_failed; + } + } + } + return 0; + + for (i = ds->info->keymap_size - 1; i >= 0; i--) { + irq = gpio_to_irq(ds->info->keymap[i].gpio); + if (ds->info->info.no_suspend) + disable_irq_wake(irq); +err_enable_irq_wake_failed: + free_irq(irq, &ds->key_state[i]); +err_request_irq_failed: +err_gpio_get_irq_num_failed: + ; + } + return err; +} + +int gpio_event_input_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int ret; + int i; + unsigned long irqflags; + struct gpio_event_input_info *di; + struct gpio_input_state *ds = *data; + char *wlname; + + di = container_of(info, struct gpio_event_input_info, info); + + if (func == GPIO_EVENT_FUNC_SUSPEND) { + if (ds->use_irq) + for (i = 0; i < di->keymap_size; i++) + disable_irq(gpio_to_irq(di->keymap[i].gpio)); + hrtimer_cancel(&ds->timer); + return 0; + } + if (func == GPIO_EVENT_FUNC_RESUME) { + spin_lock_irqsave(&ds->irq_lock, irqflags); + if (ds->use_irq) + for (i = 0; i < di->keymap_size; i++) + enable_irq(gpio_to_irq(di->keymap[i].gpio)); + hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + if (ktime_to_ns(di->poll_time) <= 0) + di->poll_time = ktime_set(0, 20 * NSEC_PER_MSEC); + + *data = ds = kzalloc(sizeof(*ds) + sizeof(ds->key_state[0]) * + di->keymap_size, GFP_KERNEL); + if (ds == NULL) { + ret = -ENOMEM; + pr_err("gpio_event_input_func: " + "Failed to allocate private data\n"); + goto err_ds_alloc_failed; + } + ds->debounce_count = di->keymap_size; + ds->input_devs = input_devs; + ds->info = di; + wlname = kasprintf(GFP_KERNEL, "gpio_input:%s%s", + input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : ""); + + ds->ws = wakeup_source_register(wlname); + kfree(wlname); + if (!ds->ws) { + ret = -ENOMEM; + pr_err("gpio_event_input_func: " + "Failed to allocate wakeup source\n"); + goto err_ws_failed; + } + + spin_lock_init(&ds->irq_lock); + + for (i = 0; i < di->keymap_size; i++) { + int dev = di->keymap[i].dev; + if (dev >= input_devs->count) { + pr_err("gpio_event_input_func: bad device " + "index %d >= %d for key code %d\n", + dev, input_devs->count, + di->keymap[i].code); + ret = -EINVAL; + goto err_bad_keymap; + } + input_set_capability(input_devs->dev[dev], di->type, + di->keymap[i].code); + ds->key_state[i].ds = ds; + ds->key_state[i].debounce = DEBOUNCE_UNKNOWN; + } + + for (i = 0; i < di->keymap_size; i++) { + ret = gpio_request(di->keymap[i].gpio, "gpio_kp_in"); + if (ret) { + pr_err("gpio_event_input_func: gpio_request " + "failed for %d\n", di->keymap[i].gpio); + goto err_gpio_request_failed; + } + ret = gpio_direction_input(di->keymap[i].gpio); + if (ret) { + pr_err("gpio_event_input_func: " + "gpio_direction_input failed for %d\n", + di->keymap[i].gpio); + goto err_gpio_configure_failed; + } + } + + ret = gpio_event_input_request_irqs(ds); + + spin_lock_irqsave(&ds->irq_lock, irqflags); + ds->use_irq = ret == 0; + + pr_info("GPIO Input Driver: Start gpio inputs for %s%s in %s " + "mode\n", input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : "", + ret == 0 ? "interrupt" : "polling"); + + hrtimer_init(&ds->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ds->timer.function = gpio_event_input_timer_func; + hrtimer_start(&ds->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + return 0; + } + + ret = 0; + spin_lock_irqsave(&ds->irq_lock, irqflags); + hrtimer_cancel(&ds->timer); + if (ds->use_irq) { + for (i = di->keymap_size - 1; i >= 0; i--) { + int irq = gpio_to_irq(di->keymap[i].gpio); + if (ds->info->info.no_suspend) + disable_irq_wake(irq); + free_irq(irq, &ds->key_state[i]); + } + } + spin_unlock_irqrestore(&ds->irq_lock, irqflags); + + for (i = di->keymap_size - 1; i >= 0; i--) { +err_gpio_configure_failed: + gpio_free(di->keymap[i].gpio); +err_gpio_request_failed: + ; + } +err_bad_keymap: + wakeup_source_unregister(ds->ws); +err_ws_failed: + kfree(ds); +err_ds_alloc_failed: + return ret; +} diff --git a/drivers/input/misc/gpio_matrix.c b/drivers/input/misc/gpio_matrix.c new file mode 100644 index 00000000..eaa9e89d --- /dev/null +++ b/drivers/input/misc/gpio_matrix.c @@ -0,0 +1,441 @@ +/* drivers/input/misc/gpio_matrix.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> +#include <linux/hrtimer.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/wakelock.h> + +struct gpio_kp { + struct gpio_event_input_devs *input_devs; + struct gpio_event_matrix_info *keypad_info; + struct hrtimer timer; + struct wake_lock wake_lock; + int current_output; + unsigned int use_irq:1; + unsigned int key_state_changed:1; + unsigned int last_key_state_changed:1; + unsigned int some_keys_pressed:2; + unsigned int disabled_irq:1; + unsigned long keys_pressed[0]; +}; + +static void clear_phantom_key(struct gpio_kp *kp, int out, int in) +{ + struct gpio_event_matrix_info *mi = kp->keypad_info; + int key_index = out * mi->ninputs + in; + unsigned short keyentry = mi->keymap[key_index]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + + if (!test_bit(keycode, kp->input_devs->dev[dev]->key)) { + if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS) + pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) " + "cleared\n", keycode, out, in, + mi->output_gpios[out], mi->input_gpios[in]); + __clear_bit(key_index, kp->keys_pressed); + } else { + if (mi->flags & GPIOKPF_PRINT_PHANTOM_KEYS) + pr_info("gpiomatrix: phantom key %x, %d-%d (%d-%d) " + "not cleared\n", keycode, out, in, + mi->output_gpios[out], mi->input_gpios[in]); + } +} + +static int restore_keys_for_input(struct gpio_kp *kp, int out, int in) +{ + int rv = 0; + int key_index; + + key_index = out * kp->keypad_info->ninputs + in; + while (out < kp->keypad_info->noutputs) { + if (test_bit(key_index, kp->keys_pressed)) { + rv = 1; + clear_phantom_key(kp, out, in); + } + key_index += kp->keypad_info->ninputs; + out++; + } + return rv; +} + +static void remove_phantom_keys(struct gpio_kp *kp) +{ + int out, in, inp; + int key_index; + + if (kp->some_keys_pressed < 3) + return; + + for (out = 0; out < kp->keypad_info->noutputs; out++) { + inp = -1; + key_index = out * kp->keypad_info->ninputs; + for (in = 0; in < kp->keypad_info->ninputs; in++, key_index++) { + if (test_bit(key_index, kp->keys_pressed)) { + if (inp == -1) { + inp = in; + continue; + } + if (inp >= 0) { + if (!restore_keys_for_input(kp, out + 1, + inp)) + break; + clear_phantom_key(kp, out, inp); + inp = -2; + } + restore_keys_for_input(kp, out, in); + } + } + } +} + +static void report_key(struct gpio_kp *kp, int key_index, int out, int in) +{ + struct gpio_event_matrix_info *mi = kp->keypad_info; + int pressed = test_bit(key_index, kp->keys_pressed); + unsigned short keyentry = mi->keymap[key_index]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + + if (pressed != test_bit(keycode, kp->input_devs->dev[dev]->key)) { + if (keycode == KEY_RESERVED) { + if (mi->flags & GPIOKPF_PRINT_UNMAPPED_KEYS) + pr_info("gpiomatrix: unmapped key, %d-%d " + "(%d-%d) changed to %d\n", + out, in, mi->output_gpios[out], + mi->input_gpios[in], pressed); + } else { + if (mi->flags & GPIOKPF_PRINT_MAPPED_KEYS) + pr_info("gpiomatrix: key %x, %d-%d (%d-%d) " + "changed to %d\n", keycode, + out, in, mi->output_gpios[out], + mi->input_gpios[in], pressed); + input_report_key(kp->input_devs->dev[dev], keycode, pressed); + } + } +} + +static void report_sync(struct gpio_kp *kp) +{ + int i; + + for (i = 0; i < kp->input_devs->count; i++) + input_sync(kp->input_devs->dev[i]); +} + +static enum hrtimer_restart gpio_keypad_timer_func(struct hrtimer *timer) +{ + int out, in; + int key_index; + int gpio; + struct gpio_kp *kp = container_of(timer, struct gpio_kp, timer); + struct gpio_event_matrix_info *mi = kp->keypad_info; + unsigned gpio_keypad_flags = mi->flags; + unsigned polarity = !!(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH); + + out = kp->current_output; + if (out == mi->noutputs) { + out = 0; + kp->last_key_state_changed = kp->key_state_changed; + kp->key_state_changed = 0; + kp->some_keys_pressed = 0; + } else { + key_index = out * mi->ninputs; + for (in = 0; in < mi->ninputs; in++, key_index++) { + gpio = mi->input_gpios[in]; + if (gpio_get_value(gpio) ^ !polarity) { + if (kp->some_keys_pressed < 3) + kp->some_keys_pressed++; + kp->key_state_changed |= !__test_and_set_bit( + key_index, kp->keys_pressed); + } else + kp->key_state_changed |= __test_and_clear_bit( + key_index, kp->keys_pressed); + } + gpio = mi->output_gpios[out]; + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(gpio, !polarity); + else + gpio_direction_input(gpio); + out++; + } + kp->current_output = out; + if (out < mi->noutputs) { + gpio = mi->output_gpios[out]; + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(gpio, polarity); + else + gpio_direction_output(gpio, polarity); + hrtimer_start(timer, mi->settle_time, HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + if (gpio_keypad_flags & GPIOKPF_DEBOUNCE) { + if (kp->key_state_changed) { + hrtimer_start(&kp->timer, mi->debounce_delay, + HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + kp->key_state_changed = kp->last_key_state_changed; + } + if (kp->key_state_changed) { + if (gpio_keypad_flags & GPIOKPF_REMOVE_SOME_PHANTOM_KEYS) + remove_phantom_keys(kp); + key_index = 0; + for (out = 0; out < mi->noutputs; out++) + for (in = 0; in < mi->ninputs; in++, key_index++) + report_key(kp, key_index, out, in); + report_sync(kp); + } + if (!kp->use_irq || kp->some_keys_pressed) { + hrtimer_start(timer, mi->poll_time, HRTIMER_MODE_REL); + return HRTIMER_NORESTART; + } + + /* No keys are pressed, reenable interrupt */ + for (out = 0; out < mi->noutputs; out++) { + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(mi->output_gpios[out], polarity); + else + gpio_direction_output(mi->output_gpios[out], polarity); + } + for (in = 0; in < mi->ninputs; in++) + enable_irq(gpio_to_irq(mi->input_gpios[in])); + wake_unlock(&kp->wake_lock); + return HRTIMER_NORESTART; +} + +static irqreturn_t gpio_keypad_irq_handler(int irq_in, void *dev_id) +{ + int i; + struct gpio_kp *kp = dev_id; + struct gpio_event_matrix_info *mi = kp->keypad_info; + unsigned gpio_keypad_flags = mi->flags; + + if (!kp->use_irq) { + /* ignore interrupt while registering the handler */ + kp->disabled_irq = 1; + disable_irq_nosync(irq_in); + return IRQ_HANDLED; + } + + for (i = 0; i < mi->ninputs; i++) + disable_irq_nosync(gpio_to_irq(mi->input_gpios[i])); + for (i = 0; i < mi->noutputs; i++) { + if (gpio_keypad_flags & GPIOKPF_DRIVE_INACTIVE) + gpio_set_value(mi->output_gpios[i], + !(gpio_keypad_flags & GPIOKPF_ACTIVE_HIGH)); + else + gpio_direction_input(mi->output_gpios[i]); + } + wake_lock(&kp->wake_lock); + hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + return IRQ_HANDLED; +} + +static int gpio_keypad_request_irqs(struct gpio_kp *kp) +{ + int i; + int err; + unsigned int irq; + unsigned long request_flags; + struct gpio_event_matrix_info *mi = kp->keypad_info; + + switch (mi->flags & (GPIOKPF_ACTIVE_HIGH|GPIOKPF_LEVEL_TRIGGERED_IRQ)) { + default: + request_flags = IRQF_TRIGGER_FALLING; + break; + case GPIOKPF_ACTIVE_HIGH: + request_flags = IRQF_TRIGGER_RISING; + break; + case GPIOKPF_LEVEL_TRIGGERED_IRQ: + request_flags = IRQF_TRIGGER_LOW; + break; + case GPIOKPF_LEVEL_TRIGGERED_IRQ | GPIOKPF_ACTIVE_HIGH: + request_flags = IRQF_TRIGGER_HIGH; + break; + } + + for (i = 0; i < mi->ninputs; i++) { + err = irq = gpio_to_irq(mi->input_gpios[i]); + if (err < 0) + goto err_gpio_get_irq_num_failed; + err = request_irq(irq, gpio_keypad_irq_handler, request_flags, + "gpio_kp", kp); + if (err) { + pr_err("gpiomatrix: request_irq failed for input %d, " + "irq %d\n", mi->input_gpios[i], irq); + goto err_request_irq_failed; + } + err = enable_irq_wake(irq); + if (err) { + pr_err("gpiomatrix: set_irq_wake failed for input %d, " + "irq %d\n", mi->input_gpios[i], irq); + } + disable_irq(irq); + if (kp->disabled_irq) { + kp->disabled_irq = 0; + enable_irq(irq); + } + } + return 0; + + for (i = mi->noutputs - 1; i >= 0; i--) { + free_irq(gpio_to_irq(mi->input_gpios[i]), kp); +err_request_irq_failed: +err_gpio_get_irq_num_failed: + ; + } + return err; +} + +int gpio_event_matrix_func(struct gpio_event_input_devs *input_devs, + struct gpio_event_info *info, void **data, int func) +{ + int i; + int err; + int key_count; + struct gpio_kp *kp; + struct gpio_event_matrix_info *mi; + + mi = container_of(info, struct gpio_event_matrix_info, info); + if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) { + /* TODO: disable scanning */ + return 0; + } + + if (func == GPIO_EVENT_FUNC_INIT) { + if (mi->keymap == NULL || + mi->input_gpios == NULL || + mi->output_gpios == NULL) { + err = -ENODEV; + pr_err("gpiomatrix: Incomplete pdata\n"); + goto err_invalid_platform_data; + } + key_count = mi->ninputs * mi->noutputs; + + *data = kp = kzalloc(sizeof(*kp) + sizeof(kp->keys_pressed[0]) * + BITS_TO_LONGS(key_count), GFP_KERNEL); + if (kp == NULL) { + err = -ENOMEM; + pr_err("gpiomatrix: Failed to allocate private data\n"); + goto err_kp_alloc_failed; + } + kp->input_devs = input_devs; + kp->keypad_info = mi; + for (i = 0; i < key_count; i++) { + unsigned short keyentry = mi->keymap[i]; + unsigned short keycode = keyentry & MATRIX_KEY_MASK; + unsigned short dev = keyentry >> MATRIX_CODE_BITS; + if (dev >= input_devs->count) { + pr_err("gpiomatrix: bad device index %d >= " + "%d for key code %d\n", + dev, input_devs->count, keycode); + err = -EINVAL; + goto err_bad_keymap; + } + if (keycode && keycode <= KEY_MAX) + input_set_capability(input_devs->dev[dev], + EV_KEY, keycode); + } + + for (i = 0; i < mi->noutputs; i++) { + err = gpio_request(mi->output_gpios[i], "gpio_kp_out"); + if (err) { + pr_err("gpiomatrix: gpio_request failed for " + "output %d\n", mi->output_gpios[i]); + goto err_request_output_gpio_failed; + } + if (gpio_cansleep(mi->output_gpios[i])) { + pr_err("gpiomatrix: unsupported output gpio %d," + " can sleep\n", mi->output_gpios[i]); + err = -EINVAL; + goto err_output_gpio_configure_failed; + } + if (mi->flags & GPIOKPF_DRIVE_INACTIVE) + err = gpio_direction_output(mi->output_gpios[i], + !(mi->flags & GPIOKPF_ACTIVE_HIGH)); + else + err = gpio_direction_input(mi->output_gpios[i]); + if (err) { + pr_err("gpiomatrix: gpio_configure failed for " + "output %d\n", mi->output_gpios[i]); + goto err_output_gpio_configure_failed; + } + } + for (i = 0; i < mi->ninputs; i++) { + err = gpio_request(mi->input_gpios[i], "gpio_kp_in"); + if (err) { + pr_err("gpiomatrix: gpio_request failed for " + "input %d\n", mi->input_gpios[i]); + goto err_request_input_gpio_failed; + } + err = gpio_direction_input(mi->input_gpios[i]); + if (err) { + pr_err("gpiomatrix: gpio_direction_input failed" + " for input %d\n", mi->input_gpios[i]); + goto err_gpio_direction_input_failed; + } + } + kp->current_output = mi->noutputs; + kp->key_state_changed = 1; + + hrtimer_init(&kp->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + kp->timer.function = gpio_keypad_timer_func; + wake_lock_init(&kp->wake_lock, WAKE_LOCK_SUSPEND, "gpio_kp"); + err = gpio_keypad_request_irqs(kp); + kp->use_irq = err == 0; + + pr_info("GPIO Matrix Keypad Driver: Start keypad matrix for " + "%s%s in %s mode\n", input_devs->dev[0]->name, + (input_devs->count > 1) ? "..." : "", + kp->use_irq ? "interrupt" : "polling"); + + if (kp->use_irq) + wake_lock(&kp->wake_lock); + hrtimer_start(&kp->timer, ktime_set(0, 0), HRTIMER_MODE_REL); + + return 0; + } + + err = 0; + kp = *data; + + if (kp->use_irq) + for (i = mi->noutputs - 1; i >= 0; i--) + free_irq(gpio_to_irq(mi->input_gpios[i]), kp); + + hrtimer_cancel(&kp->timer); + wake_lock_destroy(&kp->wake_lock); + for (i = mi->noutputs - 1; i >= 0; i--) { +err_gpio_direction_input_failed: + gpio_free(mi->input_gpios[i]); +err_request_input_gpio_failed: + ; + } + for (i = mi->noutputs - 1; i >= 0; i--) { +err_output_gpio_configure_failed: + gpio_free(mi->output_gpios[i]); +err_request_output_gpio_failed: + ; + } +err_bad_keymap: + kfree(kp); +err_kp_alloc_failed: +err_invalid_platform_data: + return err; +} diff --git a/drivers/input/misc/gpio_output.c b/drivers/input/misc/gpio_output.c new file mode 100644 index 00000000..2aac2fad --- /dev/null +++ b/drivers/input/misc/gpio_output.c @@ -0,0 +1,97 @@ +/* drivers/input/misc/gpio_output.c + * + * Copyright (C) 2007 Google, Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/gpio.h> +#include <linux/gpio_event.h> + +int gpio_event_output_event( + struct gpio_event_input_devs *input_devs, struct gpio_event_info *info, + void **data, unsigned int dev, unsigned int type, + unsigned int code, int value) +{ + int i; + struct gpio_event_output_info *oi; + oi = container_of(info, struct gpio_event_output_info, info); + if (type != oi->type) + return 0; + if (!(oi->flags & GPIOEDF_ACTIVE_HIGH)) + value = !value; + for (i = 0; i < oi->keymap_size; i++) + if (dev == oi->keymap[i].dev && code == oi->keymap[i].code) + gpio_set_value(oi->keymap[i].gpio, value); + return 0; +} + +int gpio_event_output_func( + struct gpio_event_input_devs *input_devs, struct gpio_event_info *info, + void **data, int func) +{ + int ret; + int i; + struct gpio_event_output_info *oi; + oi = container_of(info, struct gpio_event_output_info, info); + + if (func == GPIO_EVENT_FUNC_SUSPEND || func == GPIO_EVENT_FUNC_RESUME) + return 0; + + if (func == GPIO_EVENT_FUNC_INIT) { + int output_level = !(oi->flags & GPIOEDF_ACTIVE_HIGH); + + for (i = 0; i < oi->keymap_size; i++) { + int dev = oi->keymap[i].dev; + if (dev >= input_devs->count) { + pr_err("gpio_event_output_func: bad device " + "index %d >= %d for key code %d\n", + dev, input_devs->count, + oi->keymap[i].code); + ret = -EINVAL; + goto err_bad_keymap; + } + input_set_capability(input_devs->dev[dev], oi->type, + oi->keymap[i].code); + } + + for (i = 0; i < oi->keymap_size; i++) { + ret = gpio_request(oi->keymap[i].gpio, + "gpio_event_output"); + if (ret) { + pr_err("gpio_event_output_func: gpio_request " + "failed for %d\n", oi->keymap[i].gpio); + goto err_gpio_request_failed; + } + ret = gpio_direction_output(oi->keymap[i].gpio, + output_level); + if (ret) { + pr_err("gpio_event_output_func: " + "gpio_direction_output failed for %d\n", + oi->keymap[i].gpio); + goto err_gpio_direction_output_failed; + } + } + return 0; + } + + ret = 0; + for (i = oi->keymap_size - 1; i >= 0; i--) { +err_gpio_direction_output_failed: + gpio_free(oi->keymap[i].gpio); +err_gpio_request_failed: + ; + } +err_bad_keymap: + return ret; +} + diff --git a/drivers/input/misc/gpio_tilt_polled.c b/drivers/input/misc/gpio_tilt_polled.c new file mode 100644 index 00000000..da05cca8 --- /dev/null +++ b/drivers/input/misc/gpio_tilt_polled.c @@ -0,0 +1,213 @@ +/* + * Driver for tilt switches connected via GPIO lines + * not capable of generating interrupts + * + * Copyright (C) 2011 Heiko Stuebner <heiko@sntech.de> + * + * based on: drivers/input/keyboard/gpio_keys_polled.c + * + * Copyright (C) 2007-2010 Gabor Juhos <juhosg@openwrt.org> + * Copyright (C) 2010 Nuno Goncalves <nunojpg@gmail.com> + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/ioport.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/input/gpio_tilt.h> + +#define DRV_NAME "gpio-tilt-polled" + +struct gpio_tilt_polled_dev { + struct input_polled_dev *poll_dev; + struct device *dev; + const struct gpio_tilt_platform_data *pdata; + + int last_state; + + int threshold; + int count; +}; + +static void gpio_tilt_polled_poll(struct input_polled_dev *dev) +{ + struct gpio_tilt_polled_dev *tdev = dev->private; + const struct gpio_tilt_platform_data *pdata = tdev->pdata; + struct input_dev *input = dev->input; + struct gpio_tilt_state *tilt_state = NULL; + int state, i; + + if (tdev->count < tdev->threshold) { + tdev->count++; + } else { + state = 0; + for (i = 0; i < pdata->nr_gpios; i++) + state |= (!!gpio_get_value(pdata->gpios[i].gpio) << i); + + if (state != tdev->last_state) { + for (i = 0; i < pdata->nr_states; i++) + if (pdata->states[i].gpios == state) + tilt_state = &pdata->states[i]; + + if (tilt_state) { + for (i = 0; i < pdata->nr_axes; i++) + input_report_abs(input, + pdata->axes[i].axis, + tilt_state->axes[i]); + + input_sync(input); + } + + tdev->count = 0; + tdev->last_state = state; + } + } +} + +static void gpio_tilt_polled_open(struct input_polled_dev *dev) +{ + struct gpio_tilt_polled_dev *tdev = dev->private; + const struct gpio_tilt_platform_data *pdata = tdev->pdata; + + if (pdata->enable) + pdata->enable(tdev->dev); + + /* report initial state of the axes */ + tdev->last_state = -1; + tdev->count = tdev->threshold; + gpio_tilt_polled_poll(tdev->poll_dev); +} + +static void gpio_tilt_polled_close(struct input_polled_dev *dev) +{ + struct gpio_tilt_polled_dev *tdev = dev->private; + const struct gpio_tilt_platform_data *pdata = tdev->pdata; + + if (pdata->disable) + pdata->disable(tdev->dev); +} + +static int gpio_tilt_polled_probe(struct platform_device *pdev) +{ + const struct gpio_tilt_platform_data *pdata = pdev->dev.platform_data; + struct device *dev = &pdev->dev; + struct gpio_tilt_polled_dev *tdev; + struct input_polled_dev *poll_dev; + struct input_dev *input; + int error, i; + + if (!pdata || !pdata->poll_interval) + return -EINVAL; + + tdev = kzalloc(sizeof(struct gpio_tilt_polled_dev), GFP_KERNEL); + if (!tdev) { + dev_err(dev, "no memory for private data\n"); + return -ENOMEM; + } + + error = gpio_request_array(pdata->gpios, pdata->nr_gpios); + if (error) { + dev_err(dev, + "Could not request tilt GPIOs: %d\n", error); + goto err_free_tdev; + } + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) { + dev_err(dev, "no memory for polled device\n"); + error = -ENOMEM; + goto err_free_gpios; + } + + poll_dev->private = tdev; + poll_dev->poll = gpio_tilt_polled_poll; + poll_dev->poll_interval = pdata->poll_interval; + poll_dev->open = gpio_tilt_polled_open; + poll_dev->close = gpio_tilt_polled_close; + + input = poll_dev->input; + + input->name = pdev->name; + input->phys = DRV_NAME"/input0"; + input->dev.parent = &pdev->dev; + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + __set_bit(EV_ABS, input->evbit); + for (i = 0; i < pdata->nr_axes; i++) + input_set_abs_params(input, pdata->axes[i].axis, + pdata->axes[i].min, pdata->axes[i].max, + pdata->axes[i].fuzz, pdata->axes[i].flat); + + tdev->threshold = DIV_ROUND_UP(pdata->debounce_interval, + pdata->poll_interval); + + tdev->poll_dev = poll_dev; + tdev->dev = dev; + tdev->pdata = pdata; + + error = input_register_polled_device(poll_dev); + if (error) { + dev_err(dev, "unable to register polled device, err=%d\n", + error); + goto err_free_polldev; + } + + platform_set_drvdata(pdev, tdev); + + return 0; + +err_free_polldev: + input_free_polled_device(poll_dev); +err_free_gpios: + gpio_free_array(pdata->gpios, pdata->nr_gpios); +err_free_tdev: + kfree(tdev); + + return error; +} + +static int gpio_tilt_polled_remove(struct platform_device *pdev) +{ + struct gpio_tilt_polled_dev *tdev = platform_get_drvdata(pdev); + const struct gpio_tilt_platform_data *pdata = tdev->pdata; + + platform_set_drvdata(pdev, NULL); + + input_unregister_polled_device(tdev->poll_dev); + input_free_polled_device(tdev->poll_dev); + + gpio_free_array(pdata->gpios, pdata->nr_gpios); + + kfree(tdev); + + return 0; +} + +static struct platform_driver gpio_tilt_polled_driver = { + .probe = gpio_tilt_polled_probe, + .remove = gpio_tilt_polled_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(gpio_tilt_polled_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_DESCRIPTION("Polled GPIO tilt driver"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/input/misc/headset.c b/drivers/input/misc/headset.c new file mode 100644 index 00000000..9a46670e --- /dev/null +++ b/drivers/input/misc/headset.c @@ -0,0 +1,462 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <mach/gpio.h> +#include <linux/headset.h> +#include <mach/board.h> + +#ifndef HEADSET_DETECT_GPIO +#define HEADSET_DETECT_GPIO 165 +#endif +#ifndef HEADSET_BUTTON_GPIO +#define HEADSET_BUTTON_GPIO 164 +#endif + +#ifndef HEADSET_DETECT_GPIO_ACTIVE_LOW +#define HEADSET_DETECT_GPIO_ACTIVE_LOW 1 +#endif +#ifndef HEADSET_BUTTON_GPIO_ACTIVE_LOW +#define HEADSET_BUTTON_GPIO_ACTIVE_LOW 0 +#endif + +#ifndef HEADSET_DETECT_GPIO_DEBOUNCE_SW +#define HEADSET_DETECT_GPIO_DEBOUNCE_SW 1000 +#endif +#ifndef HEADSET_BUTTON_GPIO_DEBOUNCE_SW +#define HEADSET_BUTTON_GPIO_DEBOUNCE_SW 100 +#endif + +static enum hrtimer_restart report_headset_button_status(int active, struct _headset_gpio *hgp); +static enum hrtimer_restart report_headset_detect_status(int active, struct _headset_gpio *hgp); +static struct _headset headset = { + .sdev = { + .name = "h2w", + }, + .detect = { + .desc = "headset detect", + .active_low = HEADSET_DETECT_GPIO_ACTIVE_LOW, + .gpio = HEADSET_DETECT_GPIO, + .debounce = 0, + .debounce_sw = HEADSET_DETECT_GPIO_DEBOUNCE_SW, + .irq_enabled = 1, + .callback = report_headset_detect_status, + }, + .button = { + .desc = "headset button", + .active_low = HEADSET_BUTTON_GPIO_ACTIVE_LOW, + .gpio = HEADSET_BUTTON_GPIO, + .debounce = 0, + .debounce_sw = HEADSET_BUTTON_GPIO_DEBOUNCE_SW, + .irq_enabled = 1, + .callback = report_headset_button_status, + .timeout_ms = 800, /* 800ms for long button down */ + }, +}; + + +#ifndef headset_gpio_init +#define headset_gpio_init(gpio, desc) \ + do { \ + gpio_request(gpio, desc); \ + gpio_direction_input(gpio); \ + } while (0) +#endif + +#ifndef headset_gpio_free +#define headset_gpio_free(gpio) \ + gpio_free(gpio) +#endif + +#ifndef headset_gpio2irq_free +#define headset_gpio2irq_free(irq, args) { } +#endif + +#ifndef headset_gpio2irq +#define headset_gpio2irq(gpio) \ + gpio_to_irq(gpio) +#endif + +#ifndef headset_gpio_set_irq_type +#define headset_gpio_set_irq_type(irq, type) \ + irq_set_irq_type(irq, type) +#endif + +#ifndef headset_gpio_get_value +#define headset_gpio_get_value(gpio) \ + gpio_get_value(gpio) +#endif + +#ifndef headset_gpio_debounce +#define headset_gpio_debounce(gpio, ms) \ + gpio_set_debounce(gpio, ms) +#endif + +#ifndef headset_hook_detect +#define headset_hook_detect(status) { } +#endif + +#define HEADSET_DEBOUNCE_ROUND_UP(dw) \ + dw = (((dw ? dw : 1) + HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD - 1) / \ + HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD) * HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD; + +static struct _headset_keycap headset_key_capability[20] = { + { EV_KEY, KEY_MEDIA }, + { EV_KEY, KEY_END }, + { EV_KEY, KEY_RESERVED }, +}; + +static unsigned int (*headset_get_button_code_board_method)(int v); +static unsigned int (*headset_map_code2push_code_board_method)(unsigned int code, int push_type); +static __devinit int headset_button_probe(struct platform_device *pdev) +{ + struct _headset_button *headset_button = platform_get_drvdata(pdev); + headset_get_button_code_board_method = headset_button->headset_get_button_code_board_method; + headset_map_code2push_code_board_method = headset_button->headset_map_code2push_code_board_method; + memcpy(headset_key_capability, headset_button->cap, sizeof headset_button->cap); + return 0; +} + +static struct platform_driver headset_button_driver = { + .driver = { + .name = "headset-button", + .owner = THIS_MODULE, + }, + .probe = headset_button_probe, +}; + +static unsigned int headset_get_button_code(int v) +{ + unsigned int code; + if (headset_get_button_code_board_method) + code = headset_get_button_code_board_method(v); + else + code = KEY_MEDIA; + return code; +} + +static unsigned int headset_map_code2key_type(unsigned int code) +{ + unsigned int key_type = EV_KEY; + int i; + for(i = 0; headset_key_capability[i].key != KEY_RESERVED && + headset_key_capability[i].key != code && i < ARRY_SIZE(headset_key_capability); i++); + if (i < ARRY_SIZE(headset_key_capability) && + headset_key_capability[i].key == code) + key_type = headset_key_capability[i].type; + else + pr_err("headset not find code [0x%x]'s maping type\n", code); + return key_type; +} + +static unsigned int headset_map_code2push_code(unsigned int code, int push_type) +{ + if (headset_map_code2push_code_board_method) + return headset_map_code2push_code_board_method(code, push_type); + + switch (push_type) { + case HEADSET_BUTTON_DOWN_SHORT: + code = KEY_MEDIA; + break; + case HEADSET_BUTTON_DOWN_LONG: + code = KEY_END; + break; + } + + return code; +} + +static void headset_gpio_irq_enable(int enable, struct _headset_gpio *hgp); +#define HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD 50 /* 10 */ +static enum hrtimer_restart report_headset_button_status(int active, struct _headset_gpio *hgp) +{ + enum hrtimer_restart restart; + int button_push_type = HEADSET_BUTTON_DOWN_INVALID; + unsigned int key_type; + static int pre_code = KEY_RESERVED; + static int step = 0; + + if (active < 0) { + step = 0; + pre_code = KEY_RESERVED; + return HRTIMER_NORESTART; + } + if (active) { + restart = HRTIMER_RESTART; + if (++step > 3) + step = 0; + switch (step) { + case 1: + pre_code = headset_get_button_code(0); + break; + case 2: + button_push_type = HEADSET_BUTTON_DOWN_LONG; + break; + } + } else { + restart = HRTIMER_NORESTART; + if (step == 1) + button_push_type = HEADSET_BUTTON_DOWN_SHORT; + step = 0; + } + + if (button_push_type != HEADSET_BUTTON_DOWN_INVALID) { + unsigned int code = headset_map_code2push_code(pre_code, button_push_type); + key_type = headset_map_code2key_type(code); + switch (key_type) { + case EV_KEY: + input_event(hgp->parent->input, key_type, code, 1); + input_sync(hgp->parent->input); + input_event(hgp->parent->input, key_type, code, 0); + input_sync(hgp->parent->input); + pr_info("headset button-%d[%dms]\n", code, hgp->holded); + break; + default: + pr_err("headset not support key type [%d]\n", key_type); + } + } + return restart; +} + +static enum hrtimer_restart report_headset_detect_status(int active, struct _headset_gpio *hgp) +{ + struct _headset * ht = hgp->parent; + + if (active) { + headset_hook_detect(1); + ht->headphone = 0; + /* ht->headphone = ht->button.active_low ^ headset_gpio_get_value(ht->button.gpio); */ + if (ht->headphone) { + ht->type = BIT_HEADSET_NO_MIC; + queue_work(ht->switch_workqueue, &ht->switch_work); + pr_info("headphone plug in\n"); + } else { + ht->type = BIT_HEADSET_MIC; + queue_work(ht->switch_workqueue, &ht->switch_work); + pr_info("headset plug in\n"); + headset_gpio_set_irq_type(ht->button.irq, ht->button.irq_type_active); + headset_gpio_irq_enable(1, &ht->button); + } + } else { + headset_gpio_irq_enable(0, &ht->button); + ht->button.callback(-1, &ht->button); + headset_hook_detect(0); + if (ht->headphone) + pr_info("headphone plug out\n"); + else { + pr_info("headset plug out\n"); + } + ht->type = BIT_HEADSET_OUT; + queue_work(ht->switch_workqueue, &ht->switch_work); + } + /* use below code only when gpio irq misses state, because of the dithering */ + headset_gpio_set_irq_type(hgp->irq, active ? hgp->irq_type_inactive : hgp->irq_type_active); + return HRTIMER_NORESTART; +} + +static enum hrtimer_restart headset_gpio_timer_func(struct hrtimer *timer) +{ + enum hrtimer_restart restart = HRTIMER_RESTART; + struct _headset_gpio *hgp = + container_of(timer, struct _headset_gpio, timer); + int active = hgp->active_low ^ headset_gpio_get_value(hgp->gpio); /* hgp->active */ + int green_ch = (!active && &hgp->parent->detect == hgp); + if (active != hgp->active) { + pr_info("The value %s mismatch [%d:%d] at %dms!\n", + hgp->desc, active, hgp->active, hgp->holded); + hgp->holded = 0; + } + pr_debug("%s : %s %s green_ch[%d], holed=%d, debounce_sw=%d\n", __func__, + hgp->desc, active ? "active" : "inactive", green_ch, hgp->holded, hgp->debounce_sw); + hgp->holded += HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD; + if (hgp->holded >= hgp->debounce_sw || green_ch) { + if (hgp->holded == hgp->debounce_sw || \ + hgp->holded == hgp->timeout_ms || \ + green_ch) { + pr_debug("call headset gpio handler\n"); + restart = hgp->callback(active, hgp); + } else + pr_debug("gpio <%d> has kept active for %d ms\n", hgp->gpio, hgp->holded); + } + if (restart == HRTIMER_RESTART) + hrtimer_forward_now(timer, + ktime_set(HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD / 1000, + (HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD % 1000) * 1000000)); /* repeat timer */ + return restart; +} + +static irqreturn_t headset_gpio_irq_handler(int irq, void *dev) +{ + struct _headset_gpio *hgp = dev; + hrtimer_cancel(&hgp->timer); + hgp->active = hgp->active_low ^ headset_gpio_get_value(hgp->gpio); + headset_gpio_set_irq_type(hgp->irq, hgp->active ? hgp->irq_type_inactive : hgp->irq_type_active); + pr_debug("%s : %s %s\n", __func__, hgp->desc, hgp->active ? "active" : "inactive"); + hgp->holded = 0; + hrtimer_start(&hgp->timer, + ktime_set(HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD / 1000, + (HEADSET_GPIO_DEBOUNCE_SW_SAMPLE_PERIOD % 1000) * 1000000), + HRTIMER_MODE_REL); + return IRQ_HANDLED; +} + +static void headset_gpio_irq_enable(int enable, struct _headset_gpio *hgp) +{ + int action = 0; + if (enable) { + if (!hgp->irq_enabled) { + hrtimer_cancel(&hgp->timer); + hgp->irq_enabled = 1; + action = 1; + hgp->holded = 0; + enable_irq(hgp->irq); + } + } else { + if (hgp->irq_enabled) { + disable_irq(hgp->irq); + hrtimer_cancel(&hgp->timer); + hgp->irq_enabled = 0; + action = 1; + hgp->holded = 0; + } + } + pr_info("%s [ irq=%d ] --- %saction %s\n", __func__, hgp->irq_enabled, action ? "do " : "no ", hgp->desc); +} + +static void headset_switch_state(struct work_struct *work) +{ + struct _headset *ht; + int type; + + ht = container_of(work, struct _headset, switch_work); + type = ht->type; + switch_set_state(&headset.sdev, type); + pr_info("set headset state to %d\n", type); +} + +static int __init headset_init(void) +{ + int ret, i; + struct _headset *ht = &headset; + + ret = switch_dev_register(&ht->sdev); + if (ret < 0) { + pr_err("switch_dev_register failed!\n"); + return ret; + } + platform_driver_register(&headset_button_driver); + ht->input = input_allocate_device(); + if (ht->input == NULL) { + pr_err("switch_dev_register failed!\n"); + goto _switch_dev_register; + } + ht->input->name = "headset-keyboard"; + ht->input->id.bustype = BUS_HOST; + ht->input->id.vendor = 0x0001; + ht->input->id.product = 0x0001; + ht->input->id.version = 0x0100; + + for(i = 0; headset_key_capability[i].key != KEY_RESERVED; i++) { + __set_bit(headset_key_capability[i].type, ht->input->evbit); + input_set_capability(ht->input, headset_key_capability[i].type, headset_key_capability[i].key); + } + + if (input_register_device(ht->input)) + goto _switch_dev_register; + + headset_gpio_init(ht->detect.gpio, ht->detect.desc); + headset_gpio_init(ht->button.gpio, ht->button.desc); + + headset_gpio_debounce(ht->detect.gpio, ht->detect.debounce * 1000); + headset_gpio_debounce(ht->button.gpio, ht->button.debounce * 1000); + + hrtimer_init(&ht->button.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ht->button.timer.function = headset_gpio_timer_func; + HEADSET_DEBOUNCE_ROUND_UP(ht->button.debounce_sw); + HEADSET_DEBOUNCE_ROUND_UP(ht->button.timeout_ms); + ht->button.parent = ht; + ht->button.irq = headset_gpio2irq(ht->button.gpio); + ht->button.irq_type_active = ht->button.active_low ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH; + ht->button.irq_type_inactive = ht->button.active_low ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW; + ret = request_irq(ht->button.irq, headset_gpio_irq_handler, + ht->button.irq_type_active, ht->button.desc, &ht->button); + if (ret) { + pr_err("request_irq gpio %d's irq failed!\n", ht->button.gpio); + goto _gpio_request; + } + headset_gpio_irq_enable(0, &ht->button); + + hrtimer_init(&ht->detect.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ht->detect.timer.function = headset_gpio_timer_func; + HEADSET_DEBOUNCE_ROUND_UP(ht->detect.debounce_sw); + ht->detect.parent = ht; + ht->detect.irq = headset_gpio2irq(ht->detect.gpio); + ht->detect.irq_type_active = ht->detect.active_low ? IRQF_TRIGGER_LOW : IRQF_TRIGGER_HIGH; + ht->detect.irq_type_inactive = ht->detect.active_low ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW; + ret = request_irq(ht->detect.irq, headset_gpio_irq_handler, + ht->detect.irq_type_active, ht->detect.desc, &ht->detect); + if (ret) { + pr_err("request_irq gpio %d's irq failed!\n", ht->detect.gpio); + goto _headset_button_gpio_irq_handler; + } + + INIT_WORK(&ht->switch_work, headset_switch_state); + ht->switch_workqueue = create_singlethread_workqueue("headset_switch"); + + if (ht->switch_workqueue == NULL) { + pr_err("can't create headset switch workqueue\n"); + ret = -ENOMEM; + goto _headset_button_gpio_irq_handler; + } + + return 0; + + destroy_workqueue(ht->switch_workqueue); +_headset_button_gpio_irq_handler: + free_irq(ht->button.irq, &ht->button); + headset_gpio2irq_free(ht->button.irq, &ht->button); +_gpio_request: + headset_gpio_free(ht->detect.gpio); + headset_gpio_free(ht->button.gpio); + input_free_device(ht->input); +_switch_dev_register: + platform_driver_unregister(&headset_button_driver); + switch_dev_unregister(&ht->sdev); + return ret; +} +module_init(headset_init); + +static void __exit headset_exit(void) +{ + struct _headset *ht = &headset; + destroy_workqueue(ht->switch_workqueue); + headset_gpio_irq_enable(0, &ht->button); + headset_gpio_irq_enable(0, &ht->detect); + free_irq(ht->detect.irq, &ht->detect); + headset_gpio2irq_free(ht->detect.irq, &ht->detect); + free_irq(ht->button.irq, &ht->button); + headset_gpio2irq_free(ht->button.irq, &ht->button); + headset_gpio_free(ht->detect.gpio); + headset_gpio_free(ht->button.gpio); + input_free_device(ht->input); + platform_driver_unregister(&headset_button_driver); + switch_dev_unregister(&ht->sdev); +} +module_exit(headset_exit); + +MODULE_DESCRIPTION("headset & button detect driver"); +MODULE_AUTHOR("Luther Ge <luther.ge@spreadtrum.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/headset_sprd.c b/drivers/input/misc/headset_sprd.c new file mode 100644 index 00000000..db6d1f7d --- /dev/null +++ b/drivers/input/misc/headset_sprd.c @@ -0,0 +1,2377 @@ +/* + * Copyright (C) 2013 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <mach/gpio.h> +#include <linux/headset_sprd.h> +#include <soc/sprd/board.h> +#include <linux/input.h> + +#include <soc/sprd/adc.h> +#include <asm/io.h> +#include <soc/sprd/hardware.h> +#include <soc/sprd/adi.h> +#include <linux/module.h> +#include <linux/wakelock.h> + +#include <linux/regulator/consumer.h> +#include <soc/sprd/regulator.h> +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/arch_misc.h> +#include <linux/notifier.h> +#ifdef CONFIG_OF +#include <linux/slab.h> +#include <linux/of_device.h> +#endif + +//==================== debug ==================== +#define ENTER \ +do{ if(debug_level >= 1) printk(KERN_INFO "[SPRD_HEADSET_DBG][%d] func: %s line: %04d\n", adie_type, __func__, __LINE__); }while(0) + +#define PRINT_DBG(format,x...) \ +do{ if(debug_level >= 1) printk(KERN_INFO "[SPRD_HEADSET_DBG][%d] " format, adie_type, ## x); }while(0) + +#define PRINT_INFO(format,x...) \ +do{ printk(KERN_INFO "[SPRD_HEADSET_INFO][%d] " format, adie_type, ## x); }while(0) + +#define PRINT_WARN(format,x...) \ +do{ printk(KERN_INFO "[SPRD_HEADSET_WARN][%d] " format, adie_type, ## x); }while(0) + +#define PRINT_ERR(format,x...) \ +do{ printk(KERN_ERR "[SPRD_HEADSET_ERR][%d] func: %s line: %04d info: " format, adie_type, __func__, __LINE__, ## x); }while(0) +//==================== debug ==================== + +/********************************************** + * The micro ADPGAR_BYP_SELECT is used for avoiding the Bug#298417, + * please refer to Bug#298417 to confirm your configuration. + **********************************************/ +#define ADPGAR_BYP_SELECT + +#ifdef CONFIG_HEADSET_STS_POLLING_DISABLED +#undef SPRD_STS_POLLING_EN +#else +#define SPRD_STS_POLLING_EN +#endif + +#define PLUG_CONFIRM_COUNT (2) +#define NO_MIC_RETRY_COUNT (0) +#define ADC_READ_COUNT (5) +#define ADC_READ_LOOP (2) +#define ADC_GND (100) +#define SCI_ADC_GET_VALUE_COUNT (50) + +#define ARM_MF_REG (0x0110) +#define HEAD_INSERT2_EIC_EN (BIT(15)) +#define HEAD_INSERT_EIC_EN (BIT(14)) + +#define EIC3_DBNC_CTRL (0x4C) +#define DBNC_CNT3_VALUE (30) +#define DBNC_CNT3_SHIFT (0) +#define DBNC_CNT3_MASK (0xFFF << DBNC_CNT3_SHIFT) + +#define EIC5_DBNC_CTRL (0x54) +#define DBNC_CNT5_VALUE (100) +#define DBNC_CNT5_SHIFT (0) +#define DBNC_CNT5_MASK (0xFFF << DBNC_CNT5_SHIFT) + +#if (defined(CONFIG_ARCH_SCX15)) + #define ADIE_CHID_LOW 0x0134 + #define ADIE_CHID_HIGH 0x0138 +#else + #if (defined(CONFIG_ARCH_SCX35)) + #define ADIE_CHID_LOW 0x0108 + #define ADIE_CHID_HIGH 0x010C + #endif +#endif + +#if (defined(CONFIG_ARCH_SCX15)) + #define CLK_AUD_HID_EN (BIT(4)) + #define CLK_AUD_HBD_EN (BIT(3)) +#else + #if (defined(CONFIG_ARCH_SCX35)) + #define CLK_AUD_HID_EN (BIT(5)) + #define CLK_AUD_HBD_EN (BIT(4)) + #endif +#endif + +#define HEADMIC_DETECT_BASE (ANA_AUDCFGA_INT_BASE) +#define HEADMIC_DETECT_REG(X) (HEADMIC_DETECT_BASE + (X)) + +#define HDT_EN (BIT(5)) +#define AUD_EN (BIT(4)) + +#define HEADMIC_BUTTON_BASE (ANA_HDT_INT_BASE) +#define HEADMIC_BUTTON_REG(X) (HEADMIC_BUTTON_BASE + (X)) +#define HID_CFG0 (0x0080) +#define HID_CFG2 (0x0088) +#define HID_CFG3 (0x008C) +#define HID_CFG4 (0x0090) + +#define ANA_CFG0 (0x0040) +#define ANA_CFG1 (0x0044) +#define ANA_CFG2 (0x0048) +#define ANA_CFG5 (0x0064) +#define ANA_CFG20 (0x00A0) +#define ANA_STS0 (0x00C0) + +#define HEADMIC_DETECT_GLB_REG(X) (ANA_REGS_GLB_BASE + (X)) + +#define HID_TMR_T1T2_STEP_SHIFT (5) +#define HID_TMR_T0_SHIFT (0) +#define HID_TMR_T1_SHIFT (0) +#define HID_TMR_T2_SHIFT (0) + +#define HID_TMR_T1T2_STEP_MASK (0x1F << HID_TMR_T1T2_STEP_SHIFT) +#define HID_TMR_T0_MASK (0x1F << HID_TMR_T0_SHIFT) +#define HID_TMR_T1_MASK (0xFFFF << HID_TMR_T1_SHIFT) +#define HID_TMR_T2_MASK (0xFFFF << HID_TMR_T2_SHIFT) + +#define HID_TMR_T1T2_STEP_VAL (0x1) +#define HID_TMR_T0_VAL (0xF) +#define HID_TMR_T1_VAL (0xF) +#define HID_TMR_T2_VAL (0xF) + +#define AUDIO_ICM_PLUS_EN (BIT(2)) + +#define AUDIO_HEADMIC_SLEEP_EN (BIT(1)) +#define AUDIO_MICBIAS_HV_EN (BIT(2)) +#define AUDIO_MICBIAS_V_SHIFT (3) +#define AUDIO_MICBIAS_V_MASK (0x3 << AUDIO_MICBIAS_V_SHIFT) +#define AUDIO_MICBIAS_V_1P7_OR_2P2 (0) +#define AUDIO_MICBIAS_V_1P9_OR_2P4 (1) +#define AUDIO_MICBIAS_V_2P1_OR_2P7 (2) +#define AUDIO_MICBIAS_V_2P3_OR_3P0 (3) + +#define AUDIO_ADPGAR_BYP_SHIFT (10) +#define AUDIO_ADPGAR_BYP_MASK (0x3 << AUDIO_ADPGAR_BYP_SHIFT) +#define AUDIO_ADPGAR_BYP_NORMAL (0) +#define AUDIO_ADPGAR_BYP_ADC_PGAR1_2_ADCR (1) +#define AUDIO_ADPGAR_BYP_HEADMIC_2_ADCR (2) +#define AUDIO_ADPGAR_BYP_ALL_DISCONNECTED (3) + +#if (defined(CONFIG_ARCH_SCX15)) + #define AUDIO_HEAD_SDET_SHIFT (4) +#else + #if (defined(CONFIG_ARCH_SCX35)) + #define AUDIO_HEAD_SDET_SHIFT (5) + #endif +#endif +#define AUDIO_HEAD_SDET_MASK (0x3 << AUDIO_HEAD_SDET_SHIFT) +#define AUDIO_HEAD_SDET_2P1_OR_1P4 (3) +#define AUDIO_HEAD_SDET_2P3_OR_1P5 (2) +#define AUDIO_HEAD_SDET_2P5_OR_1P6 (1) +#define AUDIO_HEAD_SDET_2P7_OR_1P7 (0) + +#if (defined(CONFIG_ARCH_SCX15)) + #define AUDIO_HEAD_INS_VREF_SHIFT (7) +#else + #if (defined(CONFIG_ARCH_SCX35)) + #define AUDIO_HEAD_INS_VREF_SHIFT (8) + #endif +#endif +#define AUDIO_HEAD_INS_VREF_MASK (0x3 << AUDIO_HEAD_INS_VREF_SHIFT) +#define AUDIO_HEAD_INS_VREF_2P1_OR_1P4 (3) +#define AUDIO_HEAD_INS_VREF_2P3_OR_1P5 (2) +#define AUDIO_HEAD_INS_VREF_2P5_OR_1P6 (1) +#define AUDIO_HEAD_INS_VREF_2P7_OR_1P7 (0) + +#if (defined(CONFIG_ARCH_SCX15)) + #define AUDIO_HEAD_SBUT_SHIFT (0) +#else + #if (defined(CONFIG_ARCH_SCX35)) + #define AUDIO_HEAD_SBUT_SHIFT (1) + #endif +#endif +#define AUDIO_HEAD_SBUT_MASK (0xF << AUDIO_HEAD_SBUT_SHIFT) + +#define AUDIO_HEADMIC_ADC_SEL (BIT(13)) +#define AUDIO_HEAD_INS_HMBIAS_EN (BIT(11)) + +#if (defined(CONFIG_ARCH_SCX15)) + #define AUDIO_V2ADC_EN (BIT(15)) + #define AUDIO_HEAD_BUF_EN (BIT(14)) + #define AUDIO_HEAD2ADC_SEL_SHIFT (12) + #define AUDIO_HEAD2ADC_SEL_MASK (0x3 << AUDIO_HEAD2ADC_SEL_SHIFT) + #define AUDIO_HEAD2ADC_SEL_DISABLE (0) + #define AUDIO_HEAD2ADC_SEL_MIC_IN (1) + #define AUDIO_HEAD2ADC_SEL_L_INT (2) + #define AUDIO_HEAD2ADC_SEL_DRO_L (3) +#else + #if (defined(CONFIG_ARCH_SCX35)) + #define AUDIO_V2ADC_EN (BIT(14)) + #define AUDIO_HEAD_BUF_EN (BIT(15)) + #define AUDIO_HEAD2ADC_SEL_SHIFT (13) + #define AUDIO_HEAD2ADC_SEL_MASK (0x1 << AUDIO_HEAD2ADC_SEL_SHIFT) + #define AUDIO_HEAD2ADC_SEL_DISABLE (0) + #define AUDIO_HEAD2ADC_SEL_MIC_IN (1) + #define AUDIO_HEAD2ADC_SEL_L_INT (0) + #define AUDIO_HEAD2ADC_SEL_DRO_L (0) + #endif +#endif + +#define AUDIO_HEAD_BUTTON (BIT(7)) +#define AUDIO_HEAD_INSERT (BIT(6)) +#define AUDIO_HEAD_INSERT_2 (BIT(5)) + +#define ABS(x) (((x) < (0)) ? (-(x)) : (x)) +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) + +#define headset_reg_read(addr) \ + do { \ + sci_adi_read(addr); \ +} while(0) + +#define headset_reg_set_val(addr, val, mask, shift) \ + do { \ + uint32_t temp; \ + temp = sci_adi_read(addr); \ + temp = (temp & (~mask)) | ((val) << (shift)); \ + sci_adi_raw_write(addr, temp); \ + } while(0) + +#define headset_reg_clr_bit(addr, bit) \ + do { \ + uint32_t temp; \ + temp = sci_adi_read(addr); \ + temp = temp & (~bit); \ + sci_adi_raw_write(addr, temp); \ + } while(0) + +#define headset_reg_set_bit(addr, bit) \ + do { \ + uint32_t temp; \ + temp = sci_adi_read(addr); \ + temp = temp | bit; \ + sci_adi_raw_write(addr, temp); \ + } while(0) + +static inline uint32_t headset_reg_get_bit(uint32_t addr, uint32_t bit) +{ + uint32_t temp; + temp = sci_adi_read(addr); + temp = temp & bit; + return temp; +} + +typedef enum sprd_headset_type { + HEADSET_NORMAL, + HEADSET_NO_MIC, + HEADSET_NORTH_AMERICA, + HEADSET_APPLE, + HEADSET_CAMERA_POLE, //add by lgd + HEADSET_TYPE_ERR = -1, +} SPRD_HEADSET_TYPE; + +static struct delayed_work reg_dump_work; +static struct workqueue_struct *reg_dump_work_queue; + +/* < add by lgd */ +#ifdef CONFIG_CAMERA_POLE_SUPPORT +static struct delayed_work camera_pole_work; +static struct workqueue_struct *camera_pole_work_queue; +static int headset_ctl = 0; +#define CAMERA_POLE_CODE KEY_CAMERA +#endif +/* add by lgd > */ + +/***polling ana_sts0 to avoid the hardware defect***/ +#ifdef SPRD_STS_POLLING_EN +static struct delayed_work sts_check_work; +static struct workqueue_struct *sts_check_work_queue; +static int plug_state_class_g_on = 0; +static int sts_check_work_need_to_cancel = 1; +#endif +/***polling ana_sts0 to avoid the hardware defect***/ + +#ifdef ADPGAR_BYP_SELECT +static struct delayed_work adpgar_byp_select_work; +static struct workqueue_struct *adpgar_byp_select_work_queue; +#endif + +static DEFINE_SPINLOCK(irq_button_lock); +static DEFINE_SPINLOCK(irq_detect_lock); +static int detect_err_count = 0; +static int debug_level = 0; +static int adie_type = 100; +static int gpio_detect_value_last = 0; +static int gpio_button_value_last = 0; +static volatile int button_state_last = 0; //0==released, 1==pressed +static int current_key_code = KEY_RESERVED; +static int plug_state_last = 0; //if the hardware detected the headset is plug in, set plug_state_last = 1 +static struct wake_lock headset_detect_wakelock; +static struct wake_lock headset_button_wakelock; +static struct semaphore headset_sem; + + +static struct sprd_headset headset = { + .sdev = { + .name = "h2w", + }, +}; + +#ifdef CONFIG_HEADSET_AUXADC_CAL_SUPPORTED +static int adc_get_ideal(int adc_mic); +#endif +//======================== audio codec ======================== +static struct sprd_headset_power { + struct regulator *head_mic; + struct regulator *vcom_buf; + struct regulator *vbo; +} sprd_hts_power; + +static int sprd_headset_power_get(struct device *dev, + struct regulator **regu, const char *id) +{ + if (!*regu) { + *regu = regulator_get(dev, id); + if (IS_ERR(*regu)) { + pr_err("ERR:Failed to request %ld: %s\n", + PTR_ERR(*regu), id); + *regu = 0; + return PTR_ERR(*regu); + } + } + return 0; +} + +static int sprd_headset_power_init(struct device *dev) +{ + int ret = 0; + ret = + sprd_headset_power_get(dev, &sprd_hts_power.head_mic, + "HEADMICBIAS"); + if (ret || (sprd_hts_power.head_mic == NULL)) { + sprd_hts_power.head_mic = 0; + return ret; + } + regulator_set_voltage(sprd_hts_power.head_mic, 950000, 950000); + + ret = sprd_headset_power_get(dev, &sprd_hts_power.vcom_buf, "VCOM_BUF"); + if (ret) { + sprd_hts_power.vcom_buf = 0; + goto __err1; + } + + ret = sprd_headset_power_get(dev, &sprd_hts_power.vbo, "VBO"); + if (ret) { + sprd_hts_power.vbo = 0; + goto __err2; + } + if (sprd_hts_power.vbo != NULL) + regulator_enable(sprd_hts_power.vbo); + goto __ok; +__err2: + regulator_put(sprd_hts_power.vcom_buf); +__err1: + regulator_put(sprd_hts_power.head_mic); +__ok: + return ret; +} + +static void sprd_headset_power_deinit(void) +{ + regulator_put(sprd_hts_power.head_mic); + regulator_put(sprd_hts_power.vcom_buf); + regulator_put(sprd_hts_power.vbo); +} + +static int sprd_headset_audio_block_is_running(struct device *dev) +{ + return regulator_is_enabled(sprd_hts_power.vcom_buf) + || regulator_is_enabled(sprd_hts_power.vbo); +} + +static int sprd_headset_audio_headmic_sleep_disable(struct device *dev, int on) +{ + int ret = 0; + if (!sprd_hts_power.head_mic) { + return -1; + } + + if (on) { + ret = regulator_enable(sprd_hts_power.vbo); + ret = + regulator_set_mode(sprd_hts_power.head_mic, + REGULATOR_MODE_NORMAL); + } else { + ret = regulator_disable(sprd_hts_power.vbo); + if (sprd_headset_audio_block_is_running(dev)) { + ret = + regulator_set_mode(sprd_hts_power.head_mic, + REGULATOR_MODE_NORMAL); + } else { + ret = + regulator_set_mode(sprd_hts_power.head_mic, + REGULATOR_MODE_STANDBY); + } + } + return ret; +} + +static int sprd_headset_headmic_bias_control(struct device *dev, int on) +{ + int ret = 0; + if (!sprd_hts_power.head_mic) { + return -1; + } + if (on) { + ret = regulator_enable(sprd_hts_power.head_mic); + } else { + ret = regulator_disable(sprd_hts_power.head_mic); + } + if (!ret) { + /* Set HEADMIC_SLEEP when audio block closed */ + if (sprd_headset_audio_block_is_running(dev)) { + ret = + regulator_set_mode(sprd_hts_power.head_mic, + REGULATOR_MODE_NORMAL); + } else { + ret = + regulator_set_mode(sprd_hts_power.head_mic, + REGULATOR_MODE_STANDBY); + } + } + return ret; +} + +static BLOCKING_NOTIFIER_HEAD(hp_chain_list); +int hp_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&hp_chain_list, nb); +} +EXPORT_SYMBOL(hp_register_notifier); + +int hp_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&hp_chain_list, nb); +} +EXPORT_SYMBOL(hp_unregister_notifier); + +static int hp_notifier_call_chain(unsigned long val) +{ + return (blocking_notifier_call_chain(&hp_chain_list, val, NULL) + == NOTIFY_BAD) ? -EINVAL : 0; +} + +int get_hp_plug_state(void) +{ + return !!plug_state_last; +} +EXPORT_SYMBOL(get_hp_plug_state); +//======================== audio codec ======================== + +static int wrap_sci_adc_get_value(unsigned int channel, int scale) +{ + int count = 0; + int average = 0; + + while(count < SCI_ADC_GET_VALUE_COUNT) { + average += sci_adc_get_value(channel, scale); + count++; + } + average /= SCI_ADC_GET_VALUE_COUNT; + + return average; +} + +/* on = 0: open headmic detect circuit */ +static void headset_detect_circuit(unsigned on) +{ + if (on) { + headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD_INS_HMBIAS_EN); + } else { + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD_INS_HMBIAS_EN); + } +} + +static void headset_detect_clk_en(void) +{ + //address:0x4003_8800+0x00 + headset_reg_set_bit(HEADMIC_DETECT_GLB_REG(0x00), (HDT_EN | AUD_EN)); + //address:0x4003_8800+0x04 + headset_reg_set_bit(HEADMIC_DETECT_GLB_REG(0x04), (CLK_AUD_HID_EN | CLK_AUD_HBD_EN)); +} + +static void headset_detect_init(void) +{ + headset_detect_clk_en(); + /* set headset detect voltage */ + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD_SDET_2P5_OR_1P6, AUDIO_HEAD_SDET_MASK, AUDIO_HEAD_SDET_SHIFT); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD_INS_VREF_2P1_OR_1P4, AUDIO_HEAD_INS_VREF_MASK, AUDIO_HEAD_INS_VREF_SHIFT); + /*set headmicbias voltage*/ + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG0), AUDIO_MICBIAS_V_2P3_OR_3P0, AUDIO_MICBIAS_V_MASK, AUDIO_MICBIAS_V_SHIFT); + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_CFG0), AUDIO_MICBIAS_HV_EN); +} + +static void headmic_sleep_disable(struct device *dev, int on); + +static void headset_adc_en(int en) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + + if (en) { + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD_BUF_EN); + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_V2ADC_EN); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD2ADC_SEL_MIC_IN, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_CFG1), AUDIO_ICM_PLUS_EN); + headmic_sleep_disable(&ht->this_pdev, 1); + } else { + if(debug_level <= 2) { + headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD_BUF_EN); + headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_V2ADC_EN); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD2ADC_SEL_DISABLE, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + } + headmic_sleep_disable(&ht->this_pdev, 0); + } +} + +/* is_set = 1, headset_mic to AUXADC */ +static void set_adc_to_headmic(unsigned is_set) +{ + headset_adc_en(1); + + if (is_set) { + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD2ADC_SEL_MIC_IN, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + } else { + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), AUDIO_HEAD2ADC_SEL_L_INT, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + } +} + +static void headset_button_irq_threshold(int enable) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int audio_head_sbut = 0; + + audio_head_sbut = pdata->irq_threshold_buttont; + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(enable) { + if(HEADSET_CAMERA_POLE == ht->raw_type) + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), 11, AUDIO_HEAD_SBUT_MASK, AUDIO_HEAD_SBUT_SHIFT); + else + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), audio_head_sbut, AUDIO_HEAD_SBUT_MASK, AUDIO_HEAD_SBUT_SHIFT); + } + else + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), 0xF, AUDIO_HEAD_SBUT_MASK, AUDIO_HEAD_SBUT_SHIFT); + #else + + if (enable) + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), audio_head_sbut, AUDIO_HEAD_SBUT_MASK, AUDIO_HEAD_SBUT_SHIFT); + else + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG20), 0xF, AUDIO_HEAD_SBUT_MASK, AUDIO_HEAD_SBUT_SHIFT); + #endif + /* add by lgd > */ +} + +static void headset_micbias_polling_en(int en) +{ + if(en) { + //step 1: enable clk for register accessable + headset_detect_clk_en(); + //step 2: start polling & set timer + headset_reg_set_val(HEADMIC_BUTTON_REG(HID_CFG2), HID_TMR_T1T2_STEP_VAL, HID_TMR_T1T2_STEP_MASK, HID_TMR_T1T2_STEP_SHIFT);//step for T1 & T2 [9:5] + headset_reg_set_val(HEADMIC_BUTTON_REG(HID_CFG2), HID_TMR_T0_VAL, HID_TMR_T0_MASK, HID_TMR_T0_SHIFT);//T0 timer count [4:0] + headset_reg_set_val(HEADMIC_BUTTON_REG(HID_CFG3), HID_TMR_T1_VAL, HID_TMR_T1_MASK, HID_TMR_T1_SHIFT);//T1 timer count [15:0] + headset_reg_set_val(HEADMIC_BUTTON_REG(HID_CFG4), HID_TMR_T2_VAL, HID_TMR_T2_MASK, HID_TMR_T2_SHIFT);//T2 timer count [15:0] + //step 3: disable headmicbias + headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_CFG0), BIT(5)); + //headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_CFG0), BIT(1)); + PRINT_INFO("headmicbias polling enable\n"); + PRINT_DBG("ANA_CFG0(0x%08X) HID_CFG0(0x%08X) HID_CFG2(0x%08X) HID_CFG3(0x%08X) HID_CFG4(0x%08X)\n", + sci_adi_read(HEADMIC_DETECT_REG(ANA_CFG0)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG0)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG2)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG3)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG4))); + } else { + //step 1: enable headmicbias + //headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_CFG0), BIT(1)); + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_CFG0), BIT(5)); + //step 2: stop polling + PRINT_INFO("headmicbias polling disable\n"); + PRINT_DBG("ANA_CFG0(0x%08X) HID_CFG0(0x%08X) HID_CFG2(0x%08X) HID_CFG3(0x%08X) HID_CFG4(0x%08X)\n", + sci_adi_read(HEADMIC_DETECT_REG(ANA_CFG0)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG0)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG2)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG3)), + sci_adi_read(HEADMIC_BUTTON_REG(HID_CFG4))); + } +} + +static void headset_irq_button_enable(int enable, unsigned int irq) +{ + PRINT_INFO("headset_irq_button_enable irq = %d\n",irq); + unsigned long spin_lock_flags; + static int current_irq_state = 1;//irq is enabled after request_irq() + spin_lock_irqsave(&irq_button_lock, spin_lock_flags); + if (1 == enable) { + if (0 == current_irq_state) { + enable_irq(irq); + current_irq_state = 1; + } + } else { + if (1 == current_irq_state) { + disable_irq_nosync(irq); + current_irq_state = 0; + } + } + spin_unlock_irqrestore(&irq_button_lock, spin_lock_flags); + return; +} + +static void headset_irq_detect_enable(int enable, unsigned int irq) +{ + PRINT_INFO("headset_irq_detect_enable irq = %d\n",irq); + unsigned long spin_lock_flags; + static int current_irq_state = 1;//irq is enabled after request_irq() + spin_lock_irqsave(&irq_detect_lock, spin_lock_flags); + if (1 == enable) { + if (0 == current_irq_state) { + enable_irq(irq); + current_irq_state = 1; + } + } else { + if (1 == current_irq_state) { + disable_irq_nosync(irq); + current_irq_state = 0; + } + } + spin_unlock_irqrestore(&irq_detect_lock, spin_lock_flags); + return; +} + +static void headmic_sleep_disable(struct device *dev, int on) +{ + static int current_power_state = 0; + + if (1 == on) { + if (0 == current_power_state) { + sprd_headset_audio_headmic_sleep_disable(dev, 1); + current_power_state = 1; + } + } else { + if (1 == current_power_state) { + sprd_headset_audio_headmic_sleep_disable(dev, 0); + current_power_state = 0; + } + } + + return; +} + +static void headmicbias_power_on(struct device *dev, int on) +{ + static int current_power_state = 0; + struct sprd_headset *ht = &headset; + + if (1 == on) { + if (0 == current_power_state) { + if(NULL != ht->platform_data->external_headmicbias_power_on) + ht->platform_data->external_headmicbias_power_on(1); + sprd_headset_headmic_bias_control(dev, 1); + current_power_state = 1; + } + } else { + if (1 == current_power_state) { + if(NULL != ht->platform_data->external_headmicbias_power_on) + ht->platform_data->external_headmicbias_power_on(0); + sprd_headset_headmic_bias_control(dev, 0); + current_power_state = 0; + } + } + + return; +} + +static int ana_sts0_confirm(void) +{ + if(0 == headset_reg_get_bit(HEADMIC_DETECT_REG(ANA_STS0), AUDIO_HEAD_INSERT)) + return 0; + else + return 1; +} + +static int adc_compare(int x1, int x2) +{ + int delta = 0; + int max = 0; + + if((x1<100) && (x2<100)) + return 1; + + x1 = ((0==x1) ? 1 : (x1*100)); + x2 = ((0==x2) ? 1 : (x2*100)); + + delta = ABS(x1-x2); + max = MAX(x1, x2); + + if(delta < ((max*10)/100)) + return 1; + else + return 0; +} + +static int adc_get_average(int gpio_num, int gpio_value) +{ + int i = 0; + int j = 0; + int k = 0; + int adc_average = 0; + int success = 1; + int adc[ADC_READ_COUNT] = {0}; + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + + //================= debug ===================== + if(debug_level >= 3) { + int count = 0; + int adc_val = 0; + int gpio_button = 0; + int ana_cfg0 = 0; + int ana_cfg1 = 0; + int ana_cfg20 = 0; + int ana_sts0 = 0; + + while(count < 20) + { + count++; + adc_val = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 0); + gpio_button = gpio_get_value(pdata->gpio_button); + ana_cfg0 = sci_adi_read(HEADMIC_DETECT_REG(ANA_CFG0)); + ana_cfg1 = sci_adi_read(HEADMIC_DETECT_REG(ANA_CFG1)); + ana_cfg20 = sci_adi_read(HEADMIC_DETECT_REG(ANA_CFG20)); + ana_sts0 = sci_adi_read(HEADMIC_DETECT_REG(ANA_STS0)); + + PRINT_INFO("%2d: gpio_button=%d adc=%4d ana_cfg0=0x%08X ana_cfg1=0x%08X ana_cfg20=0x%08X ana_sts0=0x%08X\n", + count, gpio_button, adc_val, ana_cfg0, ana_cfg1, ana_cfg20, ana_sts0); + msleep(1); + } + } + //================= debug ===================== + + msleep(1); + if(0 == headset_reg_get_bit(HEADMIC_DETECT_REG(ANA_CFG0), AUDIO_HEADMIC_SLEEP_EN)) + PRINT_INFO("AUDIO_HEADMIC_SLEEP_EN is disabled\n"); + + for(i=0; i< ADC_READ_LOOP; i++) { + if(gpio_get_value(gpio_num) != gpio_value) { + PRINT_INFO("gpio value changed!!! the adc read operation aborted (step1)\n"); + return -1; + } + if(0 == ana_sts0_confirm()) { + PRINT_INFO("ana_sts0_confirm failed!!! the adc read operation aborted (step2)\n"); + return -1; + } + adc[0] = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 0); + for(j=0; j<ADC_READ_COUNT-1; j++) { + udelay(100); + if(gpio_get_value(gpio_num) != gpio_value) { + PRINT_INFO("gpio value changed!!! the adc read operation aborted (step3)\n"); + return -1; + } + if(0 == ana_sts0_confirm()) { + PRINT_INFO("ana_sts0_confirm failed!!! the adc read operation aborted (step4)\n"); + return -1; + } + adc[j+1] = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 0); + if(0 == adc_compare(adc[j], adc[j+1])) { + success = 0; + for(k=0; k<=j+1; k++) { + PRINT_DBG("adc[%d] = %d\n", k, adc[k]); + } + break; + } + } + if(1 == success) { + msleep(DBNC_CNT3_VALUE); + if(gpio_get_value(gpio_num) != gpio_value) { + PRINT_INFO("gpio value changed!!! the adc read operation aborted (step5)\n"); + return -1; + } + if(0 == ana_sts0_confirm()) { + PRINT_INFO("ana_sts0_confirm failed!!! the adc read operation aborted (step6)\n"); + return -1; + } + for(i=0; i<ADC_READ_COUNT; i++) { + adc_average += adc[i]; + PRINT_DBG("adc[%d] = %d\n", i, adc[i]); + } + adc_average = adc_average / ADC_READ_COUNT; + PRINT_DBG("adc_get_average success, adc_average = %d\n", adc_average); + return adc_average; + } + else if(i+1< ADC_READ_LOOP) { + PRINT_INFO("adc_get_average failed, retrying count = %d\n", i+1); + msleep(1); + } + } + + return -1; +} + +static int headset_irq_set_irq_type(unsigned int irq, unsigned int type) +{ + struct sprd_headset *ht = &headset; + struct irq_desc *irq_desc = NULL; + unsigned int irq_flags = 0; + int ret = -1; + + ret = irq_set_irq_type(irq, type); + irq_desc = irq_to_desc(irq); + irq_flags = irq_desc->action->flags; + + if(irq == ht->irq_button) { + if(IRQF_TRIGGER_HIGH == type) { + PRINT_DBG("IRQF_TRIGGER_HIGH is set for irq_button(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_button, irq_flags, ret); + } + else if(IRQF_TRIGGER_LOW == type) { + PRINT_DBG("IRQF_TRIGGER_LOW is set for irq_button(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_button, irq_flags, ret); + } + } + else if(irq == ht->irq_detect) { + if(IRQF_TRIGGER_HIGH == type) { + PRINT_DBG("IRQF_TRIGGER_HIGH is set for irq_detect(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_detect, irq_flags, ret); + } + else if(IRQF_TRIGGER_LOW == type) { + PRINT_DBG("IRQF_TRIGGER_LOW is set for irq_detect(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_detect, irq_flags, ret); + } + } + return 0; +} + +static int headset_button_valid(int gpio_detect_value_current) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int button_is_valid = 0; + + if(1 == pdata->irq_trigger_level_detect) { + if(1 == gpio_detect_value_current) + button_is_valid = 1; + else + button_is_valid = 0; + } else { + if(0 == gpio_detect_value_current) + button_is_valid = 1; + else + button_is_valid = 0; + } + + return button_is_valid; +} + +static int headset_gpio_2_button_state(int gpio_button_value_current) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int button_state_current = 0; + + if(1 == pdata->irq_trigger_level_button) { + if(1 == gpio_button_value_current) + button_state_current = 1; + else + button_state_current = 0; + } else { + if(0 == gpio_button_value_current) + button_state_current = 1; + else + button_state_current = 0; + } + + return button_state_current; //0==released, 1==pressed +} + +static int headset_plug_confirm_by_adc(int last_gpio_detect_value) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int adc_last = 0; + int adc_current = 0; + int count = 0; + int adc_read_interval = 10; + + msleep(adc_read_interval); + adc_last = adc_get_average(pdata->gpio_detect, last_gpio_detect_value); + if(-1 == adc_last) { + PRINT_INFO("headset_plug_confirm_by_adc failed!!!\n"); + return -1; + } + + while(count < PLUG_CONFIRM_COUNT) { + adc_current = adc_get_average(pdata->gpio_detect, last_gpio_detect_value); + if(-1 == adc_current) { + PRINT_INFO("headset_plug_confirm_by_adc failed!!!\n"); + return -1; + } + if(0 == adc_compare(adc_last, adc_current)) { + PRINT_INFO("headset_plug_confirm_by_adc failed!!!\n"); + return -1; + } + adc_last = adc_current; + count++; + msleep(adc_read_interval); + } + PRINT_INFO("headset_plug_confirm_by_adc success!!!\n"); + return adc_current; +} + +static SPRD_HEADSET_TYPE headset_type_detect(int last_gpio_detect_value) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int adc_mic_average = 0; + int adc_left_average = 0; +#ifdef CONFIG_HEADSET_AUXADC_CAL_SUPPORTED + int adc_mic_ideal = 0; + int adc_left_ideal = 0; +#endif + int no_mic_retry_count = NO_MIC_RETRY_COUNT; + unsigned int head_insert_2; + + ENTER; + + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + else + PRINT_INFO("automatic type switch is unsupported\n"); + + headset_button_irq_threshold(1); + headset_detect_init(); + headset_detect_circuit(1); + +no_mic_retry: + + //get adc value of left + if(EIC_AUD_HEAD_INST2 == pdata->gpio_detect) { + PRINT_INFO("HEADSET_DETECT_GPIO=%d, matched the reference phone, now getting adc value of left\n", pdata->gpio_detect); + set_adc_to_headmic(0); + msleep(50); + adc_left_average = headset_plug_confirm_by_adc(last_gpio_detect_value); + if(-1 == adc_left_average) + return HEADSET_TYPE_ERR; + } else { + PRINT_INFO("HEADSET_DETECT_GPIO=%d, NOT matched the reference phone(GPIO_%d), set adc_left_average to 0\n", + pdata->gpio_detect, EIC_AUD_HEAD_INST2); + adc_left_average = 0; + } + + //get adc value of mic + set_adc_to_headmic(1); + msleep(50); + adc_mic_average = headset_plug_confirm_by_adc(last_gpio_detect_value); + if(-1 == adc_mic_average) + return HEADSET_TYPE_ERR; +#ifdef CONFIG_HEADSET_AUXADC_CAL_SUPPORTED + adc_mic_ideal = adc_get_ideal(adc_mic_average); + adc_left_ideal = adc_get_ideal(adc_left_average); + + PRINT_INFO("adc_mic_average=%d, adc_mic_ideal=%d\n", adc_mic_average, adc_mic_ideal); + PRINT_INFO("adc_left_average=%d, adc_left_ideal=%d\n", adc_left_average, adc_left_ideal); + + if(adc_mic_ideal >= 0 && adc_left_ideal >= 0) { + adc_mic_average = adc_mic_ideal; + adc_left_average = adc_left_ideal; + } +#else + PRINT_INFO("adc_mic_average = %d\n", adc_mic_average); + PRINT_INFO("adc_left_average = %d\n", adc_left_average); +#endif + if((gpio_get_value(pdata->gpio_detect)) != last_gpio_detect_value) { + PRINT_INFO("software debance (gpio check)!!!(headset_type_detect)\n"); + return HEADSET_TYPE_ERR; + } + + if(0 == ana_sts0_confirm()) { + PRINT_INFO("software debance (ana_sts0_confirm)!!!(headset_type_detect)\n"); + return HEADSET_TYPE_ERR; + } + + head_insert_2 = headset_reg_get_bit(HEADMIC_DETECT_REG(ANA_STS0), AUDIO_HEAD_INSERT_2); + if((adc_mic_average < pdata->adc_threshold_3pole_detect)) { + if(0 != no_mic_retry_count) { + PRINT_INFO("no_mic_retry\n"); + no_mic_retry_count--; + goto no_mic_retry; + } + return HEADSET_NO_MIC; + } + else if((adc_left_average < ADC_GND) && (adc_mic_average > ADC_GND)) + return HEADSET_NORMAL; +/* < add by lgd */ +#ifdef CONFIG_CAMERA_POLE_SUPPORT + else if((adc_left_average > ADC_GND) && (adc_mic_average > ADC_GND)) { + if (ABS(adc_mic_average - adc_left_average) < ADC_GND && adc_left_average < 2300) + return HEADSET_NORTH_AMERICA; + else + return HEADSET_CAMERA_POLE; + } +#else + else if((adc_left_average > ADC_GND) && (adc_mic_average > ADC_GND) + && (ABS(adc_mic_average - adc_left_average) < ADC_GND)) + return HEADSET_NORTH_AMERICA; +#endif +/* add by lgd > */ + else { + PRINT_INFO("head_insert_2 = %d\n", head_insert_2); + return HEADSET_TYPE_ERR; + } + +} + +static void button_release_verify(void) +{ + struct sprd_headset *ht = &headset; + + if(1 == button_state_last) { + input_event(ht->input_dev, EV_KEY, current_key_code, 0); + input_sync(ht->input_dev); + button_state_last = 0; + PRINT_INFO("headset button released by force!!! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + } +} + +static void headset_button_work_func(struct work_struct *work) +{ + PRINT_INFO("headset_button_work_func enter into\n"); + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int gpio_button_value_current = 0; + int button_state_current = 0; + int adc_mic_average = 0; +#ifdef CONFIG_HEADSET_AUXADC_CAL_SUPPORTED + int adc_ideal = 0; +#endif + int i = 0; + + down(&headset_sem); + set_adc_to_headmic(1); + + ENTER; + + gpio_button_value_current = gpio_get_value(pdata->gpio_button); + if(gpio_button_value_current != gpio_button_value_last) { + PRINT_INFO("software debance (step 1: gpio check)!!!(headset_button_work_func)\n"); + #ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); + #endif + goto out; + } + + button_state_current = headset_gpio_2_button_state(gpio_button_value_current); + + if(1 == button_state_current) {//pressed! + if(ht->platform_data->nbuttons > 0) { + adc_mic_average = adc_get_average(pdata->gpio_button, gpio_button_value_last); + if(-1 == adc_mic_average) { + PRINT_INFO("software debance (step 3: adc check)!!!(headset_button_work_func)(pressed)\n"); + #ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); + #endif + goto out; + } +#ifdef CONFIG_HEADSET_AUXADC_CAL_SUPPORTED + adc_ideal = adc_get_ideal(adc_mic_average); + PRINT_INFO("adc_mic_average=%d, adc_ideal=%d\n", adc_mic_average, adc_ideal); + + if (adc_ideal >= 0) + adc_mic_average = adc_ideal; +#else + PRINT_INFO("adc_mic_average = %d\n", adc_mic_average); +#endif + for (i = 0; i < ht->platform_data->nbuttons; i++) { + PRINT_DBG("adc_min=%d, adc_max=%d, code = %d\n", + ht->platform_data->headset_buttons[i].adc_min, + ht->platform_data->headset_buttons[i].adc_max, + ht->platform_data->headset_buttons[i].code); + if (adc_mic_average >= ht->platform_data->headset_buttons[i].adc_min && + adc_mic_average < ht->platform_data->headset_buttons[i].adc_max) { + current_key_code = ht->platform_data->headset_buttons[i].code; + break; + } + current_key_code = KEY_RESERVED; + } + } + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(HEADSET_CAMERA_POLE == ht->raw_type) { + current_key_code = CAMERA_POLE_CODE; + } + #endif + /* add by lgd > */ + if(0 == button_state_last) { + input_event(ht->input_dev, EV_KEY, current_key_code, 1); + input_sync(ht->input_dev); + button_state_last = 1; + PRINT_INFO("headset button pressed! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + } else { + PRINT_ERR("headset button pressed already! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + #ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); + #endif + } + + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + + headset_irq_button_enable(1, ht->irq_button); + } else { //released! + if(1 == button_state_last) { + input_event(ht->input_dev, EV_KEY, current_key_code, 0); + input_sync(ht->input_dev); + button_state_last = 0; + PRINT_INFO("headset button released! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + } else + PRINT_ERR("headset button released already! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + + headset_irq_button_enable(1, ht->irq_button); + } +out: + headset_adc_en(0); + headset_irq_button_enable(1, ht->irq_button); + //wake_unlock(&headset_button_wakelock); + up(&headset_sem); + PRINT_INFO("headset_button_work_func go out\n"); + return; +} +static void headset_detect_work_func(struct work_struct *work) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + SPRD_HEADSET_TYPE headset_type; + int plug_state_current = 0; + int gpio_detect_value_current = 0; + + down(&headset_sem); + ENTER; + if (sprd_hts_power.head_mic == NULL) { + sprd_headset_power_init(&ht->this_pdev); + } + if (sprd_hts_power.head_mic == NULL ) { + PRINT_INFO("sprd_headset_power_init fail \n"); + goto out; + } + + if(0 == plug_state_last) { + if(adie_type >= 3) { + headmicbias_power_on(&ht->this_pdev, 1); + } + } + if(0 == plug_state_last) { + gpio_detect_value_current = gpio_get_value(pdata->gpio_detect); + PRINT_INFO("gpio_detect_value_current = %d, gpio_detect_value_last = %d, plug_state_last = %d\n", + gpio_detect_value_current, gpio_detect_value_last, plug_state_last); + + if(gpio_detect_value_current != gpio_detect_value_last) { + PRINT_INFO("software debance (step 1)!!!(headset_detect_work_func)\n"); + goto out; + } + + if(1 == pdata->irq_trigger_level_detect) { + if(1 == gpio_detect_value_current) + plug_state_current = 1; + else + plug_state_current = 0; + } else { + if(0 == gpio_detect_value_current) + plug_state_current = 1; + else + plug_state_current = 0; + } + } + else + plug_state_current = 0;//no debounce for plug out!!! + + if(1 == plug_state_current && 0 == plug_state_last) { + headset_type = headset_type_detect(gpio_detect_value_last); + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + ht->raw_type = headset_type; + #endif + /* add by lgd > */ + headset_adc_en(0); + switch (headset_type) { + case HEADSET_TYPE_ERR: + detect_err_count ++; + PRINT_INFO("headset_type = %d detect_err_count = %d(HEADSET_TYPE_ERR)\n", headset_type,detect_err_count); + goto out; + case HEADSET_NORTH_AMERICA: + PRINT_INFO("headset_type = %d (HEADSET_NORTH_AMERICA)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 1); + break; + case HEADSET_NORMAL: + PRINT_INFO("headset_type = %d (HEADSET_NORMAL)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + break; + case HEADSET_NO_MIC: + PRINT_INFO("headset_type = %d (HEADSET_NO_MIC)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + break; + case HEADSET_APPLE: + PRINT_INFO("headset_type = %d (HEADSET_APPLE)\n", headset_type); + PRINT_INFO("we have not yet implemented this in the code\n"); + break; + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + case HEADSET_CAMERA_POLE: + PRINT_INFO("headset_type = %d (HEADSET_CAMERA_POLE)\n", headset_type); + break; + #endif + /* add by lgd > */ + default: + PRINT_INFO("headset_type = %d (HEADSET_UNKNOWN)\n", headset_type); + break; + } + detect_err_count = 0; + if(headset_type == HEADSET_NO_MIC) + ht->headphone = 1; + else + ht->headphone = 0; + + if (ht->headphone) { + headset_button_irq_threshold(0); + headset_irq_button_enable(0, ht->irq_button); + + ht->type = BIT_HEADSET_NO_MIC; + hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + PRINT_INFO("headphone plug in (headset_detect_work_func)\n"); + } else { + headset_button_irq_threshold(1); + + if((HEADSET_NORTH_AMERICA == headset_type) && (0 == pdata->gpio_switch)) { + headset_irq_button_enable(0, ht->irq_button); + PRINT_INFO("HEADSET_NORTH_AMERICA is not supported by your hardware! so disable the button irq!\n"); + } else { + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + + headset_irq_button_enable(1, ht->irq_button); + } + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(HEADSET_CAMERA_POLE != ht->raw_type) { + #endif + /* add by lgd > */ + ht->type = BIT_HEADSET_MIC; + hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + } + #endif + /* add by lgd > */ + PRINT_INFO("headset plug in (headset_detect_work_func)\n"); + #ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(500)); + #endif + } + + /***polling ana_sts0 to avoid the hardware defect***/ +#ifdef SPRD_STS_POLLING_EN + if ((1 != adie_type) && (adie_type < 5)) { + plug_state_class_g_on = 1; + sts_check_work_need_to_cancel = 0; + queue_delayed_work(sts_check_work_queue, &sts_check_work, msecs_to_jiffies(1000)); + PRINT_INFO("start sts_check_work_queue\n"); + } + else + PRINT_INFO("sts_check_work_queue is no need to start\n"); +#else + PRINT_INFO("sts_check_work_queue is not used\n"); +#endif + /***polling ana_sts0 to avoid the hardware defect***/ + + plug_state_last = 1; + + if(1 == pdata->irq_trigger_level_detect) + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_LOW); + else + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_HIGH); + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(HEADSET_CAMERA_POLE != ht->raw_type) + #endif + /* add by lgd > */ + if (adie_type >= 5) { + headset_reg_set_bit(ANA_CTL_GLB_BASE + ARM_MF_REG, HEAD_INSERT_EIC_EN); + headset_reg_clr_bit(ANA_CTL_GLB_BASE + ARM_MF_REG, HEAD_INSERT2_EIC_EN); + PRINT_INFO("disable HEAD_INSERT2_EIC_EN, enable HEAD_INSERT_EIC_EN\n"); + } + + headset_irq_detect_enable(1, ht->irq_detect); + } else if(0 == plug_state_current && 1 == plug_state_last) { + + headset_irq_button_enable(0, ht->irq_button); + button_release_verify(); + + /***polling ana_sts0 to avoid the hardware defect***/ +#ifdef SPRD_STS_POLLING_EN + if ((1 != adie_type) && (adie_type < 5)) { + sts_check_work_need_to_cancel = 1; + if(0 == plug_state_class_g_on) { + PRINT_INFO("plug out already!!!\n"); + goto plug_out_already; + } + plug_state_class_g_on = 0; + } +#endif + /***polling ana_sts0 to avoid the hardware defect***/ + + if (ht->headphone) { + PRINT_INFO("headphone plug out (headset_detect_work_func)\n"); + } else { + PRINT_INFO("headset plug out (headset_detect_work_func)\n"); + } + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(HEADSET_CAMERA_POLE != ht->raw_type) { + #endif + /* add by lgd > */ + ht->type = BIT_HEADSET_OUT; + hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + } + #endif + /* add by lgd > */ +plug_out_already: + plug_state_last = 0; + + if(1 == pdata->irq_trigger_level_detect) + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_LOW); + + if (adie_type >= 5) { + headset_reg_set_bit(ANA_CTL_GLB_BASE + ARM_MF_REG, HEAD_INSERT2_EIC_EN); + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(0 == headset_ctl) { + #endif + /* add by lgd > */ + headset_reg_clr_bit(ANA_CTL_GLB_BASE + ARM_MF_REG, HEAD_INSERT_EIC_EN); + PRINT_INFO("disable HEAD_INSERT_EIC_EN, enable HEAD_INSERT2_EIC_EN\n"); + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + } + #endif + /* add by lgd > */ + } + + headset_irq_detect_enable(1, ht->irq_detect); + } else { + PRINT_INFO("irq_detect must be enabled anyway!!!\n"); + goto out; + } +out: + + if(0 == plug_state_last) { + if(adie_type >= 3) { + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(0 == headset_ctl) + #endif + /* add by lgd > */ + headmicbias_power_on(&ht->this_pdev, 0); + } + } + if(detect_err_count < 500) { + headset_irq_detect_enable(1, ht->irq_detect); + } + //wake_unlock(&headset_detect_wakelock); + up(&headset_sem); + return; +} + +/***polling ana_sts0 to avoid the hardware defect***/ +#ifdef SPRD_STS_POLLING_EN +static void headset_sts_check_func(struct work_struct *work) +{ + int ana_sts0 = 0; + int ana_sts0_debance = 0; + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + SPRD_HEADSET_TYPE headset_type; + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(HEADSET_CAMERA_POLE == ht->raw_type) { + PRINT_INFO("headset_sts_check_func is not implemented for camera pole\n"); + return; + } + #endif + /* add by lgd > */ + down(&headset_sem); + + ENTER; + + if(1 == sts_check_work_need_to_cancel) + goto out; + + ana_sts0_debance = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0);//arm base address:0x40038600 + msleep(100); + ana_sts0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0);//arm base address:0x40038600 + if((0x00000060 & ana_sts0) != (0x00000060 & ana_sts0_debance)) { + PRINT_INFO("software debance!!!(headset_sts_check_func)\n"); + goto out; + } + + if(((0x00000060 & ana_sts0) != 0x00000060) && (1 == plug_state_class_g_on)) { + + headset_irq_button_enable(0, ht->irq_button); + button_release_verify(); + + if (ht->headphone) { + PRINT_INFO("headphone plug out (headset_sts_check_func)\n"); + } else { + PRINT_INFO("headset plug out (headset_sts_check_func)\n"); + } + ht->type = BIT_HEADSET_OUT; + hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + plug_state_class_g_on = 0; + } + if(((0x00000060 & ana_sts0) == 0x00000060) && (0 == plug_state_class_g_on)) { + headset_type = headset_type_detect(gpio_get_value(pdata->gpio_detect)); + headset_adc_en(0); + switch (headset_type) { + case HEADSET_TYPE_ERR: + PRINT_INFO("headset_type = %d (HEADSET_TYPE_ERR)\n", headset_type); + goto out; + case HEADSET_NORTH_AMERICA: + PRINT_INFO("headset_type = %d (HEADSET_NORTH_AMERICA)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 1); + break; + case HEADSET_NORMAL: + PRINT_INFO("headset_type = %d (HEADSET_NORMAL)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + break; + case HEADSET_NO_MIC: + PRINT_INFO("headset_type = %d (HEADSET_NO_MIC)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + break; + case HEADSET_APPLE: + PRINT_INFO("headset_type = %d (HEADSET_APPLE)\n", headset_type); + PRINT_INFO("we have not yet implemented this in the code\n"); + break; + default: + PRINT_INFO("headset_type = %d (HEADSET_UNKNOWN)\n", headset_type); + break; + } + + if(headset_type == HEADSET_NO_MIC) + ht->headphone = 1; + else + ht->headphone = 0; + + if (ht->headphone) { + headset_button_irq_threshold(0); + headset_irq_button_enable(0, ht->irq_button); + + ht->type = BIT_HEADSET_NO_MIC; + hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + PRINT_INFO("headphone plug in (headset_sts_check_func)\n"); + } else { + headset_button_irq_threshold(1); + + if((HEADSET_NORTH_AMERICA == headset_type) && (0 == pdata->gpio_switch)) { + headset_irq_button_enable(0, ht->irq_button); + PRINT_INFO("HEADSET_NORTH_AMERICA is not supported by your hardware! so disable the button irq!\n"); + } else { + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + + headset_irq_button_enable(1, ht->irq_button); + } + + ht->type = BIT_HEADSET_MIC; + hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + PRINT_INFO("headset plug in (headset_sts_check_func)\n"); + } + plug_state_class_g_on = 1; + } +out: + if(0 == sts_check_work_need_to_cancel) + queue_delayed_work(sts_check_work_queue, &sts_check_work, msecs_to_jiffies(1000)); + else + PRINT_INFO("sts_check_work cancelled\n"); + + up(&headset_sem); + return; +} +#endif +/***polling ana_sts0 to avoid the hardware defect***/ + +#ifdef ADPGAR_BYP_SELECT +static void adpgar_byp_select_func(struct work_struct *work) +{ + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG5), AUDIO_ADPGAR_BYP_HEADMIC_2_ADCR, AUDIO_ADPGAR_BYP_MASK, AUDIO_ADPGAR_BYP_SHIFT); + PRINT_INFO("ANA_CFG5 = 0x%08X\n", sci_adi_read(HEADMIC_DETECT_REG(ANA_CFG5))); + msleep(100); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CFG5), AUDIO_ADPGAR_BYP_NORMAL, AUDIO_ADPGAR_BYP_MASK, AUDIO_ADPGAR_BYP_SHIFT); + PRINT_INFO("ANA_CFG5 = 0x%08X\n", sci_adi_read(HEADMIC_DETECT_REG(ANA_CFG5))); + PRINT_INFO("adpgar_byp_select_func\n"); +} +#endif + +static void reg_dump_func(struct work_struct *work) +{ + int adc_mic = 0; + + int gpio_detect = 0; + int gpio_button = 0; + + int ana_sts0 = 0; + int ana_cfg0 = 0; + int ana_cfg1 = 0; + int ana_cfg20 = 0; + + int hid_cfg0 = 0; + int hid_cfg2 = 0; + int hid_cfg3 = 0; + int hid_cfg4 = 0; + + int arm_module_en = 0; + int arm_clk_en = 0; + + adc_mic = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 0); + + gpio_detect = gpio_get_value(headset.platform_data->gpio_detect); + gpio_button = gpio_get_value(headset.platform_data->gpio_button); + + sci_adi_write(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_AUD_EN, BIT_ANA_AUD_EN);//arm base address:0x40038800 for register accessable + ana_cfg0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_CFG0);//arm base address:0x40038600 + ana_cfg1 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_CFG1);//arm base address:0x40038600 + ana_cfg20 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_CFG20);//arm base address:0x40038600 + ana_sts0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0);//arm base address:0x40038600 + + sci_adi_write(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_HDT_EN, BIT_ANA_HDT_EN);//arm base address:0x40038800 for register accessable + sci_adi_write(ANA_REG_GLB_ARM_CLK_EN, BIT_CLK_AUD_HID_EN, BIT_CLK_AUD_HID_EN);//arm base address:0x40038800 for register accessable + + hid_cfg2 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG2);//arm base address:0x40038700 + hid_cfg3 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG3);//arm base address:0x40038700 + hid_cfg4 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG4);//arm base address:0x40038700 + hid_cfg0 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG0);//arm base address:0x40038700 + + arm_module_en = sci_adi_read(ANA_REG_GLB_ARM_MODULE_EN); + arm_clk_en = sci_adi_read(ANA_REG_GLB_ARM_CLK_EN); + + PRINT_INFO("GPIO_%03d(det)=%d GPIO_%03d(but)=%d adc_mic=%d\n", + headset.platform_data->gpio_detect, gpio_detect, + headset.platform_data->gpio_button, gpio_button, + adc_mic); + PRINT_INFO("arm_module_en|arm_clk_en|ana_cfg0 |ana_cfg1 |ana_cfg20 |ana_sts0 |hid_cfg2 |hid_cfg3 |hid_cfg4 |hid_cfg0\n"); + PRINT_INFO("0x%08X |0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X\n", + arm_module_en, arm_clk_en, ana_cfg0, ana_cfg1, ana_cfg20, ana_sts0, hid_cfg2, hid_cfg3, hid_cfg4, hid_cfg0); +#if 0 + int i = 0; + int hbd[20] = {0}; + + for(i=0; i<20; i++) + hbd[i] = sci_adi_read(ANA_HDT_INT_BASE + (i*4)); + + PRINT_INFO(" hbd_cfg0 | hbd_cfg1 | hbd_cfg2 | hbd_cfg3 | hbd_cfg4 | hbd_cfg5 | hbd_cfg6 | hbd_cfg7 | hbd_cfg8 | hbd_cfg9\n"); + PRINT_INFO("0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X\n", + hbd[0], hbd[1], hbd[2], hbd[3], hbd[4], hbd[5], hbd[6], hbd[7], hbd[8], hbd[9]); + + PRINT_INFO("hbd_cfg10 |hbd_cfg11 |hbd_cfg12 |hbd_cfg13 |hbd_cfg14 |hbd_cfg15 |hbd_cfg16 | hbd_sts0 | hbd_sts1 | hbd_sts2\n"); + PRINT_INFO("0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X\n", + hbd[10], hbd[11], hbd[12], hbd[13], hbd[14], hbd[15], hbd[16], hbd[17], hbd[18], hbd[19]); +#endif + + if (debug_level >= 2) + queue_delayed_work(reg_dump_work_queue, ®_dump_work, msecs_to_jiffies(500)); + return; +} + +/* < add by lgd */ +#ifdef CONFIG_CAMERA_POLE_SUPPORT +static void camera_pole_func(struct work_struct *work) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + + down(&headset_sem); + + if(1 == headset_ctl) { + headmicbias_power_on(&ht->this_pdev, 1); + msleep(5); + headset_detect_circuit(1); + msleep(5); + headset_reg_set_bit(ANA_CTL_GLB_BASE + ARM_MF_REG, HEAD_INSERT_EIC_EN); + PRINT_INFO("enable headmic_in insert detect\n"); + } else { + if((HEADSET_CAMERA_POLE == ht->raw_type) || (0 == plug_state_last)) { + headset_reg_clr_bit(ANA_CTL_GLB_BASE + ARM_MF_REG, HEAD_INSERT_EIC_EN); + headmicbias_power_on(&ht->this_pdev, 0); + msleep(5); + headset_detect_circuit(0); + msleep(5); + PRINT_INFO("disable headmic_in insert detect\n"); + } + } + + up(&headset_sem); +} +#endif +/* add by lgd > */ + +/** + * some phone boards, when no headset plugin, the button headset_button_valid() + * will always return 0, so we need to disable headset_button irq in here + * otherwise button irq will always happen. + */ +volatile int headset_button_hardware_bug_fix_stage = 1; +static void headset_button_hardware_bug_fix(struct sprd_headset *ht) +{ + if (headset_button_hardware_bug_fix_stage) { + headset_irq_button_enable(0, ht->irq_button); // disable button irq before headset detected + pr_err("This phone board headset button circuit has bug, please fix the hardware\n"); + } +} + +static irqreturn_t headset_button_irq_handler(int irq, void *dev) +{ + PRINT_INFO("headset_button_irq_handler enter into\n"); + struct sprd_headset *ht = dev; + int button_state_current = 0; + int gpio_button_value_current = 0; + + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + if(HEADSET_CAMERA_POLE != ht->raw_type) + #endif + /* add by lgd > */ + + if(0 == headset_button_valid(gpio_get_value(ht->platform_data->gpio_detect))) { + PRINT_INFO("headset_button_irq_handler: button is invalid!!! IRQ_%d(GPIO_%d) = %d, ANA_STS0 = 0x%08X\n", + ht->irq_button, ht->platform_data->gpio_button, gpio_button_value_last, + sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0)); + headset_button_hardware_bug_fix(ht); + + if(0 == plug_state_last) + headset_irq_button_enable(0, ht->irq_button); + + return IRQ_HANDLED; + } + + gpio_button_value_current = gpio_get_value(ht->platform_data->gpio_button); + button_state_current = headset_gpio_2_button_state(gpio_button_value_current); + #ifdef ADPGAR_BYP_SELECT + if(0 == button_state_current) { + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); + } + #endif + if(button_state_current == button_state_last) { + PRINT_INFO("button state check failed!!! maybe the release is too quick. button_state_current=%d, button_state_last=%d\n", + button_state_current, button_state_last); + return IRQ_HANDLED; + } + + headset_irq_button_enable(0, ht->irq_button); + wake_lock_timeout(&headset_button_wakelock, msecs_to_jiffies(2000)); + gpio_button_value_last = gpio_button_value_current; + PRINT_DBG("headset_button_irq_handler: IRQ_%d(GPIO_%d) = %d, ANA_STS0 = 0x%08X\n", + ht->irq_button, ht->platform_data->gpio_button, gpio_button_value_last, + sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0)); + queue_work(ht->button_work_queue, &ht->work_button); + PRINT_INFO("headset_button_irq_handler go out\n"); + return IRQ_HANDLED; +} + +static irqreturn_t headset_detect_irq_handler(int irq, void *dev) +{ + struct sprd_headset *ht = dev; + headset_irq_button_enable(0, ht->irq_button); + headset_irq_detect_enable(0, ht->irq_detect); + wake_lock_timeout(&headset_detect_wakelock, msecs_to_jiffies(2000)); + gpio_detect_value_last = gpio_get_value(ht->platform_data->gpio_detect); + PRINT_DBG("headset_detect_irq_handler: IRQ_%d(GPIO_%d) = %d, ANA_STS0 = 0x%08X\n", + ht->irq_detect, ht->platform_data->gpio_detect, gpio_detect_value_last, + sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0)); + queue_delayed_work(ht->detect_work_queue, &ht->work_detect, msecs_to_jiffies(0)); + return IRQ_HANDLED; +} + +//================= create sys fs for debug =================== +//============= /sys/kernel/headset/debug_level =============== + +static ssize_t headset_debug_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + PRINT_INFO("debug_level = %d\n", debug_level); + return sprintf(buff, "%d\n", debug_level); +} + +static ssize_t headset_debug_level_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t len) +{ + unsigned long level = simple_strtoul(buff, NULL, 10); + debug_level = level; + PRINT_INFO("debug_level = %d\n", debug_level); + if(debug_level >= 2) + queue_delayed_work(reg_dump_work_queue, ®_dump_work, msecs_to_jiffies(500)); + return len; +} + +static struct kobject *headset_debug_kobj = NULL; +static struct kobj_attribute headset_debug_attr = + __ATTR(debug_level, 0644, headset_debug_level_show, headset_debug_level_store); + + +/* < add by lgd */ +#ifdef CONFIG_CAMERA_POLE_SUPPORT +static ssize_t headset_ctl_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + PRINT_INFO("headset_ctl = %d\n", headset_ctl); + return sprintf(buff, "%d\n", headset_ctl); +} + +static ssize_t headset_ctl_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t len) +{ + unsigned long ctl = simple_strtoul(buff, NULL, 10); + if (headset_ctl == ctl) + return len; + headset_ctl = ctl; + PRINT_INFO("headset_ctl = %d\n", headset_ctl); + queue_delayed_work(camera_pole_work_queue, &camera_pole_work, msecs_to_jiffies(100)); + return len; +} + +static struct kobj_attribute headset_ctl_attr = + __ATTR(headset_ctl, 0666, headset_ctl_show, headset_ctl_store); +#endif +/* add by lgd > */ + +static int headset_debug_sysfs_init(void) +{ + int ret = -1; + + headset_debug_kobj = kobject_create_and_add("headset", kernel_kobj); + if (headset_debug_kobj == NULL) { + ret = -ENOMEM; + PRINT_ERR("register sysfs failed. ret = %d\n", ret); + return ret; + } + + ret = sysfs_create_file(headset_debug_kobj, &headset_debug_attr.attr); + if (ret) { + PRINT_ERR("create sysfs failed. ret = %d\n", ret); + return ret; + } + /* < add by lgd */ +#ifdef CONFIG_CAMERA_POLE_SUPPORT + ret = sysfs_create_file(headset_debug_kobj, &headset_ctl_attr.attr); + if (ret) { + PRINT_ERR("create sysfs failed 2. ret = %d\n", ret); + return ret; + } +#endif + /* add by lgd > */ + + PRINT_INFO("headset_debug_sysfs_init success\n"); + return ret; +} +//================= create sys fs for debug =================== + +#ifdef CONFIG_OF +static struct sprd_headset_platform_data *headset_detect_parse_dt( + struct device *dev) +{ + struct sprd_headset_platform_data *pdata; + struct device_node *np = dev->of_node,*buttons_np = NULL; + int ret; + struct headset_buttons *buttons_data; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "could not allocate memory for platform data\n"); + return NULL; + } + ret = of_property_read_u32(np, "gpio_switch", &pdata->gpio_switch); + if(ret){ + dev_err(dev, "fail to get gpio_switch\n"); + goto fail; + } + ret = of_property_read_u32(np, "gpio_detect", &pdata->gpio_detect); + if(ret){ + dev_err(dev, "fail to get gpio_detect\n"); + goto fail; + } + ret = of_property_read_u32(np, "gpio_button", &pdata->gpio_button); + if(ret){ + dev_err(dev, "fail to get gpio_button\n"); + goto fail; + } + ret = of_property_read_u32(np, "irq_trigger_level_detect", &pdata->irq_trigger_level_detect); + if(ret){ + dev_err(dev, "fail to get irq_trigger_level_detect\n"); + goto fail; + } + ret = of_property_read_u32(np, "irq_trigger_level_button", &pdata->irq_trigger_level_button); + if(ret){ + dev_err(dev, "fail to get irq_trigger_level_button\n"); + goto fail; + } + ret = of_property_read_u32(np, "adc_threshold_3pole_detect", &pdata->adc_threshold_3pole_detect); + if(ret){ + dev_err(dev, "fail to get adc_threshold_3pole_detect\n"); + goto fail; + } + + ret = of_property_read_u32(np, "adc_threshold_4pole_detect", &pdata->adc_threshold_4pole_detect); + if(ret){ + dev_err(dev, "fail to get adc_threshold_4pole_detect\n"); + goto fail; + } + + ret = of_property_read_u32(np, "irq_threshold_buttont", &pdata->irq_threshold_buttont); + if(ret){ + dev_err(dev, "fail to get irq_threshold_buttont\n"); + goto fail; + } + + ret = of_property_read_u32(np, "voltage_headmicbias", &pdata->voltage_headmicbias); + if(ret){ + dev_err(dev, "fail to get voltage_headmicbias\n"); + goto fail; + } + ret = of_property_read_u32(np, "nbuttons", &pdata->nbuttons); + if(ret){ + dev_err(dev, "fail to get nbuttons\n"); + goto fail; + } + + buttons_data = kzalloc(pdata->nbuttons*sizeof(*buttons_data),GFP_KERNEL); + if (!buttons_data) { + dev_err(dev, "could not allocate memory for headset_buttons\n"); + goto fail; + } + pdata->headset_buttons = buttons_data; + + for_each_child_of_node(np,buttons_np){ + + ret = of_property_read_u32(buttons_np, "adc_min", &buttons_data->adc_min); + if (ret) { + dev_err(dev, "fail to get adc_min\n"); + goto fail_buttons_data; + } + ret = of_property_read_u32(buttons_np, "adc_max", &buttons_data->adc_max); + if (ret) { + dev_err(dev, "fail to get adc_max\n"); + goto fail_buttons_data; + } + ret = of_property_read_u32(buttons_np, "code", &buttons_data->code); + if (ret) { + dev_err(dev, "fail to get code\n"); + goto fail_buttons_data; + } + ret = of_property_read_u32(buttons_np, "type", &buttons_data->type); + if (ret) { + dev_err(dev, "fail to get type\n"); + goto fail_buttons_data; + } + printk("device tree data: adc_min = %d adc_max = %d code = %d type = %d \n", buttons_data->adc_min, + buttons_data->adc_max, buttons_data->code, buttons_data->type); + buttons_data++; + }; + + return pdata; + +fail_buttons_data: + kfree(buttons_data); + pdata->headset_buttons = NULL; +fail: + kfree(pdata); + return NULL; +} +#endif + +static int headset_detect_probe(struct platform_device *pdev) +{ + struct sprd_headset_platform_data *pdata = pdev->dev.platform_data; + struct sprd_headset *ht = &headset; + unsigned long irqflags = 0; + struct input_dev *input_dev = NULL; + int i = 0; + int ret = -1; + int ana_sts0 = 0; + int adie_chip_id_low = 0; + int adie_chip_id_high = 0; + +#ifdef CONFIG_OF + struct device_node *np = pdev->dev.of_node; + if (pdev->dev.of_node && !pdata){ + pdata = headset_detect_parse_dt(&pdev->dev); + if(pdata) + pdev->dev.platform_data = pdata; + } + if (!pdata) { + printk(KERN_WARNING "headset_detect_probe get platform_data NULL\n"); + ret = -EINVAL; + goto fail_to_get_platform_data; + } +#endif + + adie_chip_id_low = sci_adi_read(ANA_CTL_GLB_BASE+ADIE_CHID_LOW);//A-die chip id LOW + adie_chip_id_high = sci_adi_read(ANA_CTL_GLB_BASE+ADIE_CHID_HIGH);//A-die chip id HIGH + + sci_adi_write(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_AUD_EN, BIT_ANA_AUD_EN);//arm base address:0x40038800 for register accessable + ana_sts0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0);//arm base address:0x40038600 + + switch (adie_chip_id_high) { + case 0x2713: {//chip is 2713 + switch (adie_chip_id_low) { + case 0xA000: { + adie_type = 1; //AC + break; + } + case 0xA001: { + if ((0x00000040 & ana_sts0) == 0x00000040) { + adie_type = 2; //BA + break; + } else { + adie_type = 3; //BB + break; + } + break; + } + case 0xCA00: { + adie_type = 5; //CA + break; + } + default: { + adie_type = 100; //unknow + PRINT_ERR("unknow 2713 chip ID!!!\n"); + break; + } + } + PRINT_INFO("adie chip is 2713, type = %d\n", adie_type); + break;//break from 2713 + } + + case 0x2711: {//chip is 2711 + adie_type = 4; //Dolphin + PRINT_INFO("adie chip is 2711, type = %d\n", adie_type); + break; //break from 2711 + } + + default: { + adie_type = 100; //unknow + PRINT_ERR("unknow adie chip !!!\n"); + break; + } + } + + ht->platform_data = pdata; + ht->this_pdev = pdev; + headset_detect_init(); + + ret = sprd_headset_power_init(&pdev->dev); + if (ret) + goto fail_to_sprd_headset_power_init; + + if(adie_type < 3) { + headmicbias_power_on(&pdev->dev, 1); + msleep(5);//this time delay is necessary here + } + + PRINT_INFO("====================================================================\n"); + PRINT_INFO(" write 0~3 to \"/sys/kernel/headset/debug_level\" for headset debug\n"); + PRINT_INFO("====================================================================\n"); + PRINT_INFO("D-die chip id = 0x%08X\n", __raw_readl(REG_AON_APB_CHIP_ID)); + PRINT_INFO("A-die chip id HIGH = 0x%08X\n", adie_chip_id_high); + PRINT_INFO("A-die chip id LOW = 0x%08X\n", adie_chip_id_low); + + if (1 == adie_type) { + pdata->gpio_detect -= 1; + PRINT_WARN("use EIC_AUD_HEAD_INST (EIC4, GPIO_%d) for insert detecting\n", pdata->gpio_detect); + } else { + PRINT_INFO("use GPIO_%d for insert detecting\n", pdata->gpio_detect); + } + + if(0 != pdata->gpio_switch) { + ret = gpio_request(pdata->gpio_switch, "headset_switch"); + if (ret < 0) { + PRINT_ERR("failed to request GPIO_%d(headset_switch)\n", pdata->gpio_switch); + goto failed_to_request_gpio_switch; + } + } + else + PRINT_INFO("automatic type switch is unsupported\n"); + + ret = gpio_request(pdata->gpio_detect, "headset_detect"); + if (ret < 0) { + PRINT_ERR("failed to request GPIO_%d(headset_detect)\n", pdata->gpio_detect); + goto failed_to_request_gpio_detect; + } + + ret = gpio_request(pdata->gpio_button, "headset_button"); + if (ret < 0) { + PRINT_ERR("failed to request GPIO_%d(headset_button)\n", pdata->gpio_button); + goto failed_to_request_gpio_button; + } + + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + gpio_direction_input(pdata->gpio_detect); + gpio_direction_input(pdata->gpio_button); + ht->irq_detect = gpio_to_irq(pdata->gpio_detect); + ht->irq_button = gpio_to_irq(pdata->gpio_button); + + ret = switch_dev_register(&ht->sdev); + if (ret < 0) { + PRINT_ERR("switch_dev_register failed!\n"); + goto failed_to_register_switch_dev; + } + + input_dev = input_allocate_device(); + if ( !input_dev) { + PRINT_ERR("input_allocate_device for headset_button failed!\n"); + ret = -ENOMEM; + goto failed_to_allocate_input_device; + } + + input_dev->name = "headset-keyboard"; + input_dev->id.bustype = BUS_HOST; + ht->input_dev = input_dev; + + for (i = 0; i < pdata->nbuttons; i++) { + struct headset_buttons *buttons = &pdata->headset_buttons[i]; + unsigned int type = buttons->type ?: EV_KEY; + input_set_capability(input_dev, type, buttons->code); + } + /* < add by lgd */ + #ifdef CONFIG_CAMERA_POLE_SUPPORT + input_set_capability(input_dev, EV_KEY, CAMERA_POLE_CODE); + #endif + /* add by lgd > */ + ret = input_register_device(input_dev); + if (ret) { + PRINT_ERR("input_register_device for headset_button failed!\n"); + goto failed_to_register_input_device; + } + + sema_init(&headset_sem, 1); + + INIT_WORK(&ht->work_button, headset_button_work_func); + ht->button_work_queue = create_singlethread_workqueue("headset_button"); + if(ht->button_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for headset_button failed!\n"); + goto failed_to_create_singlethread_workqueue_for_headset_button; + } + + INIT_DELAYED_WORK(&ht->work_detect, headset_detect_work_func); + ht->detect_work_queue = create_singlethread_workqueue("headset_detect"); + if(ht->detect_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for headset_detect failed!\n"); + goto failed_to_create_singlethread_workqueue_for_headset_detect; + } + + /***polling ana_sts0 to avoid the hardware defect***/ +#ifdef SPRD_STS_POLLING_EN + INIT_DELAYED_WORK(&sts_check_work, headset_sts_check_func); + sts_check_work_queue = create_singlethread_workqueue("headset_sts_check"); + if(sts_check_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for headset_sts_check failed!\n"); + goto failed_to_create_singlethread_workqueue_for_headset_sts_check; + } +#endif + /***polling ana_sts0 to avoid the hardware defect***/ + + INIT_DELAYED_WORK(®_dump_work, reg_dump_func); + reg_dump_work_queue = create_singlethread_workqueue("headset_reg_dump"); + if(reg_dump_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for headset_reg_dump failed!\n"); + goto failed_to_create_singlethread_workqueue_for_headset_reg_dump; + } + if (debug_level >= 2) + queue_delayed_work(reg_dump_work_queue, ®_dump_work, msecs_to_jiffies(500)); + /* < add by lgd */ +#ifdef CONFIG_CAMERA_POLE_SUPPORT + INIT_DELAYED_WORK(&camera_pole_work, camera_pole_func); + camera_pole_work_queue = create_singlethread_workqueue("camera_pole"); + if(NULL == camera_pole_work_queue) { + PRINT_ERR("create_singlethread_workqueue for camera_pole failed!\n"); + goto failed_to_create_singlethread_workqueue_for_camera_pole; + } +#endif + /* add by lgd > */ +#ifdef ADPGAR_BYP_SELECT + //work queue for Bug#298417 + INIT_DELAYED_WORK(&adpgar_byp_select_work, adpgar_byp_select_func); + adpgar_byp_select_work_queue = create_singlethread_workqueue("adpgar_byp_select"); + if(adpgar_byp_select_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for adpgar_byp_select failed!\n"); + goto failed_to_create_singlethread_workqueue_for_adpgar_byp_select; + } + PRINT_INFO("ADPGAR_BYP_SELECT is enabled!\n"); +#endif + + wake_lock_init(&headset_detect_wakelock, WAKE_LOCK_SUSPEND, "headset_detect_wakelock"); + wake_lock_init(&headset_button_wakelock, WAKE_LOCK_SUSPEND, "headset_button_wakelock"); + + //set EIC3 de-bounce time for button irq + headset_reg_set_val((ANA_EIC_BASE+EIC3_DBNC_CTRL), DBNC_CNT3_VALUE, DBNC_CNT3_MASK, DBNC_CNT3_SHIFT); + //set EIC5 de-bounce time for inser irq + headset_reg_set_val((ANA_EIC_BASE+EIC5_DBNC_CTRL), DBNC_CNT5_VALUE, DBNC_CNT5_MASK, DBNC_CNT5_SHIFT); + + irqflags = pdata->irq_trigger_level_button ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW; + ret = request_irq(ht->irq_button, headset_button_irq_handler, irqflags | IRQF_NO_SUSPEND, "headset_button", ht); + if (ret) { + PRINT_ERR("failed to request IRQ_%d(GPIO_%d)\n", ht->irq_button, pdata->gpio_button); + goto failed_to_request_irq_headset_button; + } + headset_irq_button_enable(0, ht->irq_button);//disable button irq before headset detected + headset_button_hardware_bug_fix_stage = 0; + + irqflags = pdata->irq_trigger_level_detect ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW; + ret = request_irq(ht->irq_detect, headset_detect_irq_handler, irqflags | IRQF_NO_SUSPEND, "headset_detect", ht); + if (ret < 0) { + PRINT_ERR("failed to request IRQ_%d(GPIO_%d)\n", ht->irq_detect, pdata->gpio_detect); + goto failed_to_request_irq_headset_detect; + } + + ret = headset_debug_sysfs_init(); + + PRINT_INFO("headset_detect_probe success\n"); + return ret; + +failed_to_request_irq_headset_detect: + free_irq(ht->irq_button, ht); +failed_to_request_irq_headset_button: + +#ifdef ADPGAR_BYP_SELECT + cancel_delayed_work_sync(&adpgar_byp_select_work); + destroy_workqueue(adpgar_byp_select_work_queue); +failed_to_create_singlethread_workqueue_for_adpgar_byp_select: +#endif + +/* < add by lgd */ +#ifdef CONFIG_CAMERA_POLE_SUPPORT + cancel_delayed_work_sync(&camera_pole_work); + destroy_workqueue(camera_pole_work_queue); +failed_to_create_singlethread_workqueue_for_camera_pole: +#endif +/* add by lgd > */ + + cancel_delayed_work_sync(®_dump_work); + destroy_workqueue(reg_dump_work_queue); +failed_to_create_singlethread_workqueue_for_headset_reg_dump: + +#ifdef SPRD_STS_POLLING_EN + destroy_workqueue(sts_check_work_queue); +failed_to_create_singlethread_workqueue_for_headset_sts_check: +#endif + + destroy_workqueue(ht->detect_work_queue); +failed_to_create_singlethread_workqueue_for_headset_detect: + destroy_workqueue(ht->button_work_queue); +failed_to_create_singlethread_workqueue_for_headset_button: + input_unregister_device(input_dev); +failed_to_register_input_device: + input_free_device(input_dev); +failed_to_allocate_input_device: + switch_dev_unregister(&ht->sdev); +failed_to_register_switch_dev: + gpio_free(pdata->gpio_button); +failed_to_request_gpio_button: + gpio_free(pdata->gpio_detect); +failed_to_request_gpio_detect: + if(0 != pdata->gpio_switch) + gpio_free(pdata->gpio_switch); +failed_to_request_gpio_switch: + headmicbias_power_on(&pdev->dev, 0); +fail_to_sprd_headset_power_init: +fail_to_get_platform_data: + PRINT_ERR("headset_detect_probe failed\n"); + return ret; +} + +#ifdef CONFIG_PM +static int headset_suspend(struct platform_device *dev, pm_message_t state) +{ + PRINT_INFO("suspend (det_irq = %d, but_irq = %d, plug_state_last = %d)\n", + headset.irq_detect, headset.irq_button, plug_state_last); + return 0; +} + +static int headset_resume(struct platform_device *dev) +{ + PRINT_INFO("resume (det_irq = %d, but_irq = %d, plug_state_last = %d)\n", + headset.irq_detect, headset.irq_button, plug_state_last); + return 0; +} +#else +#define headset_suspend NULL +#define headset_resume NULL +#endif + +#ifdef CONFIG_HEADSET_AUXADC_CAL_SUPPORTED +#define SPRD_HEADSET_AUXADC_CAL_NO 0 +#define SPRD_HEADSET_AUXADC_CAL_NV 1 +#define SPRD_HEADSET_AUXADC_CAL_CHIP 2 + +struct sprd_headset_auxadc_cal { + uint16_t p0_vol; //900mV + uint16_t p0_adc; + uint16_t p1_vol; //300mV + uint16_t p1_adc; + uint16_t cal_type; +}; + +static struct sprd_headset_auxadc_cal adc_cal = { + 0, 0, + 0, 0, + SPRD_HEADSET_AUXADC_CAL_NO, +}; + +static void adc_cal_from_efuse(void); +extern int sci_efuse_get_cal_v3(unsigned int * pdata, int num); + +static int adc_get_ideal(int adc_mic) +{ + int32_t A = 0; + int32_t B = 0; + int32_t temp1 = 0; + int32_t temp2 = 0; + int32_t temp3 = 0; + int32_t adc_ideal = -1; + PRINT_INFO("adc_get_ideal headset enter into \n"); + if (SPRD_HEADSET_AUXADC_CAL_CHIP == adc_cal.cal_type) { + A = adc_cal.p1_adc; + B = adc_cal.p0_adc; + + temp1 = 18 * adc_mic - 27 * A + 9 * B; + temp2 = B - A; + temp3 = 109 * temp1; + adc_ideal = temp3 / temp2; + + adc_ideal = (adc_ideal <= 0) ? 0 : adc_ideal; + + PRINT_INFO("Calibration data from efuse, adc_mic=%d, adc_ideal=%d\n", adc_mic, adc_ideal); + } else { + PRINT_ERR("The chip and NV do not calibrate ADC!\n"); + } + + return adc_ideal; +} + +static void adc_cal_from_efuse(void) +{ + int ret; + unsigned int efuse_cal_data[2] = {0}; + + PRINT_INFO("getting calibration data from efuse ...\n"); + + if (adc_cal.cal_type != SPRD_HEADSET_AUXADC_CAL_CHIP) { + if (!(sci_efuse_get_cal_v3(efuse_cal_data,2))) { + if(((efuse_cal_data[0] >> 16) & 0xffff - (efuse_cal_data[1] >> 16) & 0xffff == 0)) { + PRINT_ERR("ADC is equal to two points, So calibrate from efuse error!\n"); + return; + } + + adc_cal.p0_vol = efuse_cal_data[0] & 0xffff; + adc_cal.p0_adc = (efuse_cal_data[0] >> 16) & 0xffff; + adc_cal.p1_vol = efuse_cal_data[1] & 0xffff; + adc_cal.p1_adc = (efuse_cal_data[1] >> 16) & 0xffff; + + adc_cal.cal_type = SPRD_HEADSET_AUXADC_CAL_CHIP; + + PRINT_INFO("efuse_cal_data[0]=0x%08x\n", efuse_cal_data[0]); + PRINT_INFO("efuse_cal_data[1]=0x%08x\n", efuse_cal_data[1]); + + PRINT_INFO("========auxadc cal from efuse=========\n"); + PRINT_INFO(" ADC VOL\n"); + PRINT_INFO("p0: %4d %4d\n", adc_cal.p0_adc, adc_cal.p0_vol); + PRINT_INFO("p1: %4d %4d\n", adc_cal.p1_adc, adc_cal.p1_vol); + PRINT_INFO("========auxadc cal from efuse=========\n"); + + PRINT_INFO("calibrate from efuse success!\n"); + } else { + PRINT_ERR("calibrate from efuse failed!\n"); + } + } else { + PRINT_INFO("no need to calibrate from efuse!\n"); + } +} + +#endif + +static const struct of_device_id headset_detect_of_match[] = { + {.compatible = "sprd,headset-detect",}, + { } +}; +static struct platform_driver headset_detect_driver = { + .driver = { + .name = "headset-detect", + .owner = THIS_MODULE, + .of_match_table = headset_detect_of_match, + }, + .probe = headset_detect_probe, + .suspend = headset_suspend, + .resume = headset_resume, +}; + +static int __init headset_init(void) +{ + int ret; +#ifdef CONFIG_HEADSET_AUXADC_CAL_SUPPORTED + adc_cal_from_efuse(); +#endif + ret = platform_driver_register(&headset_detect_driver); + return ret; +} + +static void __exit headset_exit(void) +{ + sprd_headset_power_deinit(); + platform_driver_unregister(&headset_detect_driver); +} + +late_initcall(headset_init); +module_exit(headset_exit); + +MODULE_DESCRIPTION("headset & button detect driver"); +MODULE_AUTHOR("Yaochuan Li <yaochuan.li@spreadtrum.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/headset_sprd_sc2723.c b/drivers/input/misc/headset_sprd_sc2723.c new file mode 100644 index 00000000..3c210d31 --- /dev/null +++ b/drivers/input/misc/headset_sprd_sc2723.c @@ -0,0 +1,1920 @@ +/* + * Copyright (C) 2013 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/delay.h> +#include <soc/sprd/gpio.h> +#include <linux/headset_sprd_sc2723.h> +#include <soc/sprd/board.h> +#include <linux/input.h> + +#include <soc/sprd/adc.h> +#include <asm/io.h> +#include <soc/sprd/hardware.h> +#include <soc/sprd/adi.h> +#include <linux/module.h> +#include <linux/wakelock.h> + +#include <linux/regulator/consumer.h> +#include <soc/sprd/regulator.h> +#include <soc/sprd/sci_glb_regs.h> +#include <soc/sprd/arch_misc.h> +#include <linux/notifier.h> +#ifdef CONFIG_OF +#include <linux/slab.h> +#include <linux/of_device.h> +#endif +#include <asm/div64.h> +#include <linux/sched.h> +#include <linux/timer.h> + +//==================== debug ==================== +#define ENTER \ +do{ if(debug_level >= 1) printk(KERN_INFO "[SPRD_HEADSET_DBG][%d] func: %s line: %04d\n", adie_type, __func__, __LINE__); }while(0) + +#define PRINT_DBG(format,x...) \ +do{ if(debug_level >= 1) printk(KERN_INFO "[SPRD_HEADSET_DBG][%d] " format, adie_type, ## x); }while(0) + +#define PRINT_INFO(format,x...) \ +do{ printk(KERN_INFO "[SPRD_HEADSET_INFO][%d] " format, adie_type, ## x); }while(0) + +#define PRINT_WARN(format,x...) \ +do{ printk(KERN_INFO "[SPRD_HEADSET_WARN][%d] " format, adie_type, ## x); }while(0) + +#define PRINT_ERR(format,x...) \ +do{ printk(KERN_ERR "[SPRD_HEADSET_ERR][%d] func: %s line: %04d info: " format, adie_type, __func__, __LINE__, ## x); }while(0) +//==================== debug ==================== + +/********************************************** + * The micro ADPGAR_BYP_SELECT is used for avoiding the Bug#298417, + * please refer to Bug#298417 to confirm your configuration. + **********************************************/ +#define ADPGAR_BYP_SELECT + +#define PLUG_CONFIRM_COUNT (1) +#define NO_MIC_RETRY_COUNT (0) +#define ADC_READ_COUNT (5) +#define ADC_READ_LOOP (2) +#define ADC_GND (300) +#define SCI_ADC_GET_VALUE_COUNT (50) + +#define EIC3_DBNC_CTRL (0x4C) +#define SW_DBNC_VALUE (10) +#define DBNC_CNT3_VALUE (30) +#define DBNC_CNT3_SHIFT (0) +#define DBNC_CNT3_MASK (0xFFF << DBNC_CNT3_SHIFT) + +#define EIC5_DBNC_CTRL (0x54) +#define DBNC_CNT5_VALUE (25) +#define DBNC_CNT5_SHIFT (0) +#define DBNC_CNT5_MASK (0xFFF << DBNC_CNT5_SHIFT) + + +#define EIC8_DBNC_CTRL (0x60) +#define DBNC_CNT8_VALUE (25) +#define DBNC_CNT8_SHIFT (0) +#define DBNC_CNT8_MASK (0xFFF << DBNC_CNT8_SHIFT) + +#define CLK_AUD_HID_EN (BIT(4)) +#define CLK_AUD_HBD_EN (BIT(3)) + +#define HEADMIC_DETECT_BASE (ANA_AUDCFGA_INT_BASE) +#define HEADMIC_DETECT_REG(X) (HEADMIC_DETECT_BASE + (X)) + +#define HEADMIC_DETECT_GLB_REG(X) (ANA_REGS_GLB_BASE + (X)) +#define HDT_EN (BIT(5)) +#define AUD_EN (BIT(4)) + +//no use +#define HEADMIC_BUTTON_BASE (ANA_HDT_INT_BASE) +#define HEADMIC_BUTTON_REG(X) (HEADMIC_BUTTON_BASE + (X)) +#define HID_CFG0 (0x0080) +#define HID_CFG2 (0x0088) +#define HID_CFG3 (0x008C) +#define HID_CFG4 (0x0090) +//no use + +#define ANA_PMU0 (0x0000) +#define ANA_CDC1 (0x0020) +#define ANA_HDT0 (0x0068) +#define ANA_STS0 (0x0074) + +#define AUDIO_HEADMIC_SLEEP_EN (BIT(6)) + +#define AUDIO_ADPGAR_BYP_SHIFT (10) +#define AUDIO_ADPGAR_BYP_MASK (0x3 << AUDIO_ADPGAR_BYP_SHIFT) +#define AUDIO_ADPGAR_BYP_NORMAL (0) +#define AUDIO_ADPGAR_BYP_ADC_PGAR1_2_ADCR (1) +#define AUDIO_ADPGAR_BYP_HEADMIC_2_ADCR (2) +#define AUDIO_ADPGAR_BYP_ALL_DISCONNECTED (3) + +#define AUDIO_HEAD_SDET_SHIFT (5) +#define AUDIO_HEAD_SDET_MASK (0x3 << AUDIO_HEAD_SDET_SHIFT) +#define AUDIO_HEAD_SDET_2P1 (3) +#define AUDIO_HEAD_SDET_2P3 (2) +#define AUDIO_HEAD_SDET_2P5 (1) +#define AUDIO_HEAD_SDET_2P7 (0) + +#define AUDIO_HEAD_INS_VREF_SHIFT (8) +#define AUDIO_HEAD_INS_VREF_MASK (0x3 << AUDIO_HEAD_INS_VREF_SHIFT) +#define AUDIO_HEAD_INS_VREF_2P1 (3) +#define AUDIO_HEAD_INS_VREF_2P3 (2) +#define AUDIO_HEAD_INS_VREF_2P5 (1) +#define AUDIO_HEAD_INS_VREF_2P7 (0) + +#define AUDIO_HEAD_SBUT_SHIFT (1) +#define AUDIO_HEAD_SBUT_MASK (0xF << AUDIO_HEAD_SBUT_SHIFT) + +#define AUDIO_HEAD_INS_PD (BIT(10)) +#define AUDIO_HEADDETECT_PD (BIT(7)) + +#define AUDIO_V2ADC_EN (BIT(15)) +#define AUDIO_HEAD2ADC_SEL_SHIFT (11) +#define AUDIO_HEAD2ADC_SEL_MASK (0xF << AUDIO_HEAD2ADC_SEL_SHIFT) +#define AUDIO_HEAD2ADC_SEL_DISABLE (0) +#define AUDIO_HEAD2ADC_SEL_HEADMIC_IN (1) +#define AUDIO_HEAD2ADC_SEL_HEADSET_L_INT (3) +#define AUDIO_HEAD2ADC_SEL_HEAD_DRO_L (4) + +#define AUDIO_HEAD_BUTTON (BIT(11)) +#define AUDIO_HEAD_INSERT (BIT(10)) +#define AUDIO_HEAD_INSERT_2 (BIT(9)) + +#define AUDIO_PMUR1 (BIT(0)) +#define ANA_PMU1 (0x0004) + +#define ABS(x) (((x) < (0)) ? (-(x)) : (x)) +#define MAX(x,y) (((x) > (y)) ? (x) : (y)) + +#define headset_reg_read(addr) \ + do { \ + sci_adi_read(addr); \ +} while(0) + +#define headset_reg_set_val(addr, val, mask, shift) \ + do { \ + uint32_t temp; \ + temp = sci_adi_read(addr); \ + temp = (temp & (~mask)) | ((val) << (shift)); \ + sci_adi_raw_write(addr, temp); \ + } while(0) + +#define headset_reg_clr_bit(addr, bit) \ + do { \ + uint32_t temp; \ + temp = sci_adi_read(addr); \ + temp = temp & (~bit); \ + sci_adi_raw_write(addr, temp); \ + } while(0) + +#define headset_reg_set_bit(addr, bit) \ + do { \ + uint32_t temp; \ + temp = sci_adi_read(addr); \ + temp = temp | bit; \ + sci_adi_raw_write(addr, temp); \ + } while(0) + +static inline uint32_t headset_reg_get_bit(uint32_t addr, uint32_t bit) +{ + uint32_t temp; + temp = sci_adi_read(addr); + temp = temp & bit; + return temp; +} + +typedef enum sprd_headset_type { + HEADSET_4POLE_NORMAL, + HEADSET_NO_MIC, + HEADSET_4POLE_NOT_NORMAL, + HEADSET_APPLE, + HEADSET_TYPE_ERR = -1, +} SPRD_HEADSET_TYPE; + +static struct delayed_work reg_dump_work; +static struct workqueue_struct *reg_dump_work_queue; + +#ifdef ADPGAR_BYP_SELECT +static struct delayed_work adpgar_byp_select_work; +static struct workqueue_struct *adpgar_byp_select_work_queue; +#endif + +struct sprd_headset_auxadc_cal_l { + u32 A; + u32 B; + u32 E; + u32 cal_type; +}; + +#define SPRD_HEADSET_AUXADC_CAL_NO 0 +#define SPRD_HEADSET_AUXADC_CAL_DO 1 + +static struct sprd_headset_auxadc_cal_l adc_cal_headset = { + 0, 0,0,SPRD_HEADSET_AUXADC_CAL_NO, +}; + + + +static DEFINE_SPINLOCK(irq_button_lock); +static DEFINE_SPINLOCK(irq_detect_lock); +static DEFINE_SPINLOCK(irq_detect_mic_lock); + +static int detect_err_count = 0; +static int debug_level = 0; +static int adie_type = -1; +static int gpio_detect_value_last = 0; +static int gpio_button_value_last = 0; +static volatile int button_state_last = 0; //0==released, 1==pressed +static int current_key_code = KEY_RESERVED; +static int plug_state_last = 0; //if the hardware detected the headset is plug in, set plug_state_last = 1 +static struct wake_lock headset_detect_wakelock; +static struct wake_lock headset_button_wakelock; +static struct semaphore headset_sem; +static struct platform_device *this_pdev = NULL; +static struct sprd_headset headset = { + .sdev = { + .name = "h2w", + }, +}; + +//======================== audio codec ======================== +static struct sprd_headset_power { + struct regulator *head_mic; + struct regulator *vcom_buf; + struct regulator *vbo; +} sprd_hts_power; + +static int sprd_headset_power_get(struct device *dev, + struct regulator **regu, const char *id) +{ + if (!*regu) { + *regu = regulator_get(dev, id); + if (IS_ERR(*regu)) { + pr_err("ERR:Failed to request %ld: %s\n", PTR_ERR(*regu), id); + *regu = 0; + return PTR_ERR(*regu); + } + } + return 0; +} + +static int sprd_headset_power_init(struct device *dev) +{ + int ret = 0; + ret = sprd_headset_power_get(dev, &sprd_hts_power.head_mic, "HEADMICBIAS"); + if (ret || (sprd_hts_power.head_mic == NULL)) { + sprd_hts_power.head_mic = 0; + return ret; + } + regulator_set_voltage(sprd_hts_power.head_mic, 950000, 950000); + + ret = sprd_headset_power_get(dev, &sprd_hts_power.vcom_buf, "VCOM_BUF"); + if (ret) { + sprd_hts_power.vcom_buf = 0; + goto __err1; + } + + ret = sprd_headset_power_get(dev, &sprd_hts_power.vbo, "VBO"); + if (ret) { + sprd_hts_power.vbo = 0; + goto __err2; + } + + goto __ok; +__err2: + regulator_put(sprd_hts_power.vcom_buf); +__err1: + regulator_put(sprd_hts_power.head_mic); +__ok: + return ret; +} + +static void sprd_headset_power_deinit(void) +{ + regulator_put(sprd_hts_power.head_mic); + regulator_put(sprd_hts_power.vcom_buf); + regulator_put(sprd_hts_power.vbo); +} + +static int sprd_headset_audio_block_is_running(struct device *dev) +{ + return regulator_is_enabled(sprd_hts_power.vcom_buf) + || regulator_is_enabled(sprd_hts_power.vbo); +} + +static int sprd_headset_audio_headmic_sleep_disable(struct device *dev, int on) +{ + int ret = 0; + if (!sprd_hts_power.head_mic) { + return -1; + } + + if (on) { + ret = regulator_enable(sprd_hts_power.vbo); + ret = regulator_set_mode(sprd_hts_power.head_mic, REGULATOR_MODE_NORMAL); + } else { + ret = regulator_disable(sprd_hts_power.vbo); + if (sprd_headset_audio_block_is_running(dev)) { + ret = regulator_set_mode(sprd_hts_power.head_mic, REGULATOR_MODE_NORMAL); + } else { + ret = regulator_set_mode(sprd_hts_power.head_mic, REGULATOR_MODE_STANDBY); + } + } + return ret; +} + +static int sprd_headset_headmic_bias_control(struct device *dev, int on) +{ + int ret = 0; + if (!sprd_hts_power.head_mic) { + return -1; + } + if (on) { + ret = regulator_enable(sprd_hts_power.head_mic); + } else { + ret = regulator_disable(sprd_hts_power.head_mic); + } + if (!ret) { + /* Set HEADMIC_SLEEP when audio block closed */ + if (sprd_headset_audio_block_is_running(dev)) { + ret = regulator_set_mode(sprd_hts_power.head_mic, REGULATOR_MODE_NORMAL); + } else { + ret = regulator_set_mode(sprd_hts_power.head_mic, REGULATOR_MODE_STANDBY); + } + } + return ret; +} + +static BLOCKING_NOTIFIER_HEAD(hp_chain_list); +int hp_register_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_register(&hp_chain_list, nb); +} +EXPORT_SYMBOL(hp_register_notifier); + +int hp_unregister_notifier(struct notifier_block *nb) +{ + return blocking_notifier_chain_unregister(&hp_chain_list, nb); +} +EXPORT_SYMBOL(hp_unregister_notifier); + +#if 0 +static int hp_notifier_call_chain(unsigned long val) +{ + return (blocking_notifier_call_chain(&hp_chain_list, val, NULL) + == NOTIFY_BAD) ? -EINVAL : 0; +} +#endif + +int get_hp_plug_state(void) +{ + return !!plug_state_last; +} +EXPORT_SYMBOL(get_hp_plug_state); +//======================== audio codec ======================== + +static int wrap_sci_adc_get_value(unsigned int channel, int scale) +{ + int count = 0; + int average = 0; + + while(count < SCI_ADC_GET_VALUE_COUNT) { + average += sci_adc_get_value(channel, scale); + count++; + } + average /= SCI_ADC_GET_VALUE_COUNT; + + return average; +} + +/* on = 0: open headmic detect circuit */ +static void headset_detect_circuit(unsigned on) +{ + if (on) { + headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD_INS_PD); + headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEADDETECT_PD); + } else { + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD_INS_PD); + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEADDETECT_PD); + } +} + +static void headset_detect_clk_en(void) +{ + //address:0x4003_8800+0x00 + headset_reg_set_bit(HEADMIC_DETECT_GLB_REG(0x00), (HDT_EN | AUD_EN)); + //address:0x4003_8800+0x04 + headset_reg_set_bit(HEADMIC_DETECT_GLB_REG(0x04), (CLK_AUD_HID_EN | CLK_AUD_HBD_EN)); +} + +static void headset_detect_init(void) +{ + headset_detect_clk_en(); + /* set headset detect voltage */ + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_PMU1), AUDIO_PMUR1); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD_SDET_2P5, AUDIO_HEAD_SDET_MASK, AUDIO_HEAD_SDET_SHIFT); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD_INS_VREF_2P1, AUDIO_HEAD_INS_VREF_MASK, AUDIO_HEAD_INS_VREF_SHIFT); +} + +static void headmic_sleep_disable(struct device *dev, int on); + +static void headset_adc_en(int en) +{ + if (en) { + headset_reg_set_bit(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_V2ADC_EN); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD2ADC_SEL_HEADMIC_IN, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + headmic_sleep_disable(&this_pdev->dev, 1); + } else { + if(debug_level <= 2) { + headset_reg_clr_bit(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_V2ADC_EN); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD2ADC_SEL_DISABLE, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + } + headmic_sleep_disable(&this_pdev->dev, 0); + } +} + +/* is_set = 1, headset_mic to AUXADC */ +static void set_adc_to_headmic(unsigned is_set) +{ + headset_adc_en(1); + + if (is_set) { + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD2ADC_SEL_HEADMIC_IN, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + } else { + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), AUDIO_HEAD2ADC_SEL_HEADSET_L_INT, AUDIO_HEAD2ADC_SEL_MASK, AUDIO_HEAD2ADC_SEL_SHIFT); + } +} + +static void headset_button_irq_threshold(int enable) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int audio_head_sbut = 0; + + audio_head_sbut = pdata->irq_threshold_buttont; + + if (enable) + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), audio_head_sbut, AUDIO_HEAD_SBUT_MASK, AUDIO_HEAD_SBUT_SHIFT); + else + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_HDT0), 0xF, AUDIO_HEAD_SBUT_MASK, AUDIO_HEAD_SBUT_SHIFT); +} + +static void headset_irq_button_enable(int enable, unsigned int irq) +{ + unsigned long spin_lock_flags; + static int current_irq_state = 1;//irq is enabled after request_irq() + spin_lock_irqsave(&irq_button_lock, spin_lock_flags); + if (1 == enable) { + if (0 == current_irq_state) { + enable_irq(irq); + current_irq_state = 1; + } + } else { + if (1 == current_irq_state) { + disable_irq_nosync(irq); + current_irq_state = 0; + } + } + spin_unlock_irqrestore(&irq_button_lock, spin_lock_flags); + return; +} + +static void headset_irq_detect_enable(int enable, unsigned int irq) +{ + unsigned long spin_lock_flags; + static int current_irq_state = 1;//irq is enabled after request_irq() + spin_lock_irqsave(&irq_detect_lock, spin_lock_flags); + if (1 == enable) { + if (0 == current_irq_state) { + enable_irq(irq); + current_irq_state = 1; + } + } else { + if (1 == current_irq_state) { + disable_irq_nosync(irq); + current_irq_state = 0; + } + } + spin_unlock_irqrestore(&irq_detect_lock, spin_lock_flags); + return; +} + +static void headset_irq_detect_mic_enable(int enable, unsigned int irq) +{ + unsigned long spin_lock_flags; + static int current_irq_state = 1; + spin_lock_irqsave(&irq_detect_mic_lock, spin_lock_flags); + if (1 == enable) { + if (0 == current_irq_state) { + enable_irq(irq); + current_irq_state = 1; + } + } else { + if (1 == current_irq_state) { + disable_irq_nosync(irq); + current_irq_state = 0; + } + } + spin_unlock_irqrestore(&irq_detect_mic_lock, spin_lock_flags); + return; +} + +static void headmic_sleep_disable(struct device *dev, int on) +{ + static int current_power_state = 0; + + if (1 == on) { + if (0 == current_power_state) { + sprd_headset_audio_headmic_sleep_disable(dev, 1); + current_power_state = 1; + } + } else { + if (1 == current_power_state) { + sprd_headset_audio_headmic_sleep_disable(dev, 0); + current_power_state = 0; + } + } + + return; +} + +static void headmicbias_power_on(struct device *dev, int on) +{ + static int current_power_state = 0; + struct sprd_headset *ht = &headset; + + if (1 == on) { + if (0 == current_power_state) { + if(NULL != ht->platform_data->external_headmicbias_power_on) + ht->platform_data->external_headmicbias_power_on(1); + sprd_headset_headmic_bias_control(dev, 1); + current_power_state = 1; + } + } else { + if (1 == current_power_state) { + if(NULL != ht->platform_data->external_headmicbias_power_on) + ht->platform_data->external_headmicbias_power_on(0); + sprd_headset_headmic_bias_control(dev, 0); + current_power_state = 0; + } + } + + return; +} + +static int ana_sts0_confirm(void) +{ + if (0 == headset_reg_get_bit(HEADMIC_DETECT_REG(ANA_STS0), AUDIO_HEAD_INSERT)) +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + return 0; +#else + return 1; +#endif + else + return 1; +} + +static int adc_compare(int x1, int x2) +{ + int delta = 0; + int max = 0; + + if((x1<300) && (x2<300)) + return 1; + + x1 = ((0==x1) ? 1 : (x1*100)); + x2 = ((0==x2) ? 1 : (x2*100)); + + delta = ABS(x1-x2); + max = MAX(x1, x2); + + if(delta < ((max*10)/100)) + return 1; + else + return 0; +} + +static int adc_get_average(int gpio_num, int gpio_value) +{ + int i = 0; + int j = 0; + int k = 0; + int adc_average = 0; + int success = 1; + int adc[ADC_READ_COUNT] = {0}; + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + + //================= debug ===================== + if(debug_level >= 3) { + int count = 0; + int adc_val = 0; + int gpio_button = 0; + int ana_pmu0 = 0; + int ana_cfg1 = 0; + int ana_hdt0 = 0; + int ana_sts0 = 0; + + while(count < 20) { + count++; + adc_val = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 0); + gpio_button = gpio_get_value(pdata->gpio_button); + ana_pmu0 = sci_adi_read(HEADMIC_DETECT_REG(ANA_PMU0)); + ana_hdt0 = sci_adi_read(HEADMIC_DETECT_REG(ANA_HDT0)); + ana_sts0 = sci_adi_read(HEADMIC_DETECT_REG(ANA_STS0)); + + PRINT_INFO("%2d: gpio_button=%d adc=%4d ana_pmu0=0x%08X ana_cfg1=0x%08X ana_hdt0=0x%08X ana_sts0=0x%08X\n", + count, gpio_button, adc_val, ana_pmu0, ana_cfg1, ana_hdt0, ana_sts0); + msleep(1); + } + } + //================= debug ===================== + + msleep(1); + if(0 == headset_reg_get_bit(HEADMIC_DETECT_REG(ANA_PMU0), AUDIO_HEADMIC_SLEEP_EN)) + PRINT_INFO("AUDIO_HEADMIC_SLEEP_EN is disabled\n"); + + for(i=0; i< ADC_READ_LOOP; i++) { + if(gpio_get_value(gpio_num) != gpio_value) { + PRINT_INFO("gpio value changed!!! the adc read operation aborted (step1)\n"); + return -1; + } + if(0 == ana_sts0_confirm()) { + PRINT_INFO("ana_sts0_confirm failed!!! the adc read operation aborted (step2)\n"); + return -1; + } + adc[0] = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 0); + + for(j=0; j<ADC_READ_COUNT-1; j++) { + udelay(100); + if(gpio_get_value(gpio_num) != gpio_value) { + PRINT_INFO("gpio value changed!!! the adc read operation aborted (step3)\n"); + return -1; + } + if(0 == ana_sts0_confirm()) { + PRINT_INFO("ana_sts0_confirm failed!!! the adc read operation aborted (step4)\n"); + return -1; + } + adc[j+1] = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 0); + + if(0 == adc_compare(adc[j], adc[j+1])) { + success = 0; + for(k=0; k<=j+1; k++) { + PRINT_DBG("adc[%d] = %d\n", k, adc[k]); + } + break; + } + } + if(1 == success) { + msleep(3); + if(gpio_get_value(gpio_num) != gpio_value) { + PRINT_INFO("gpio value changed!!! the adc read operation aborted (step5)\n"); + return -1; + } + if(0 == ana_sts0_confirm()) { + PRINT_INFO("ana_sts0_confirm failed!!! the adc read operation aborted (step6)\n"); + return -1; + } + for(i=0; i<ADC_READ_COUNT; i++) { + adc_average += adc[i]; + PRINT_DBG("adc[%d] = %d\n", i, adc[i]); + } + adc_average = adc_average / ADC_READ_COUNT; + PRINT_DBG("adc_get_average success, adc_average = %d\n", adc_average); + return adc_average; + } else if(i+1< ADC_READ_LOOP) { + PRINT_INFO("adc_get_average failed, retrying count = %d\n", i+1); + msleep(1); + } + } + PRINT_INFO("adc_get_average out \n"); + return -1; +} + +static int headset_irq_set_irq_type(unsigned int irq, unsigned int type) +{ + struct sprd_headset *ht = &headset; + struct irq_desc *irq_desc = NULL; + unsigned int irq_flags = 0; + int ret = -1; + + ret = irq_set_irq_type(irq, type); + irq_desc = irq_to_desc(irq); + irq_flags = irq_desc->action->flags; + + if(irq == ht->irq_button) { + if(IRQF_TRIGGER_HIGH == type) { + PRINT_DBG("IRQF_TRIGGER_HIGH is set for irq_button(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_button, irq_flags, ret); + } else if(IRQF_TRIGGER_LOW == type) { + PRINT_DBG("IRQF_TRIGGER_LOW is set for irq_button(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_button, irq_flags, ret); + } + } else if(irq == ht->irq_detect) { + if(IRQF_TRIGGER_HIGH == type) { + PRINT_DBG("IRQF_TRIGGER_HIGH is set for irq_detect(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_detect, irq_flags, ret); + } else if(IRQF_TRIGGER_LOW == type) { + PRINT_DBG("IRQF_TRIGGER_LOW is set for irq_detect(%d). irq_flags = 0x%08X, ret = %d\n", + ht->irq_detect, irq_flags, ret); + } + } + return 0; +} + +static int headset_button_valid(int gpio_detect_value_current) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int button_is_valid = 0; + + if(1 == pdata->irq_trigger_level_detect) { + if(1 == gpio_detect_value_current) + button_is_valid = 1; + else + button_is_valid = 0; + } else { + if(0 == gpio_detect_value_current) + button_is_valid = 1; + else + button_is_valid = 0; + } + + return button_is_valid; +} + +static int headset_gpio_2_button_state(int gpio_button_value_current) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int button_state_current = 0; + + if(1 == pdata->irq_trigger_level_button) { + if(1 == gpio_button_value_current) + button_state_current = 1; + else + button_state_current = 0; + } else { + if(0 == gpio_button_value_current) + button_state_current = 1; + else + button_state_current = 0; + } + + return button_state_current; //0==released, 1==pressed +} + +static int headset_plug_confirm_by_adc(int last_gpio_detect_value) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int adc_last = 0; + int adc_current = 0; + int count = 0; + int adc_read_interval = 10; + + adc_last = adc_get_average(pdata->gpio_detect, last_gpio_detect_value); + if(-1 == adc_last) { + PRINT_INFO("headset_plug_confirm_by_adc failed!!!\n"); + return -1; + } + + while(count < PLUG_CONFIRM_COUNT) { + msleep(adc_read_interval); + adc_current = adc_get_average(pdata->gpio_detect, last_gpio_detect_value); + if(-1 == adc_current) { + PRINT_INFO("headset_plug_confirm_by_adc failed!!!\n"); + return -1; + } + if(0 == adc_compare(adc_last, adc_current)) { + PRINT_INFO("headset_plug_confirm_by_adc failed!!!\n"); + return -1; + } + adc_last = adc_current; + count++; + } + PRINT_INFO("headset_plug_confirm_by_adc success!!!\n"); + return adc_current; +} +static int adc_get_ideal(u32 adc_mic); + +static SPRD_HEADSET_TYPE headset_type_detect(int last_gpio_detect_value) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int adc_mic_average = 0; + int adc_left_average = 0; + int adc_mic_ideal = 0; + int no_mic_retry_count = NO_MIC_RETRY_COUNT; + + ENTER; + + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + else + PRINT_INFO("automatic type switch is unsupported\n"); + + headset_button_irq_threshold(1); + headset_detect_init(); + headset_detect_circuit(1); + +no_mic_retry: + + //get adc value of left + if(EIC_AUD_HEAD_INST2 == pdata->gpio_detect) { + PRINT_INFO("HEADSET_DETECT_GPIO=%d, matched the reference phone, now getting adc value of left\n", pdata->gpio_detect); + set_adc_to_headmic(0); + msleep(100); + adc_left_average = headset_plug_confirm_by_adc(last_gpio_detect_value); + if(-1 == adc_left_average) + return HEADSET_TYPE_ERR; + } else { + PRINT_INFO("HEADSET_DETECT_GPIO=%d, NOT matched the reference phone(GPIO_%d), set adc_left_average to 0\n", + pdata->gpio_detect, EIC_AUD_HEAD_INST2); + adc_left_average = 0; + } + + //get adc value of mic + set_adc_to_headmic(1); + msleep(50); + adc_mic_average = headset_plug_confirm_by_adc(last_gpio_detect_value); + if(-1 == adc_mic_average) + return HEADSET_TYPE_ERR; + adc_mic_ideal = adc_get_ideal(adc_mic_average); + PRINT_INFO("adc_mic_average=%d, adc_mic_ideal=%d\n", adc_mic_average, adc_mic_ideal); + if(adc_mic_ideal >= 0) { + adc_mic_average = adc_mic_ideal; + } + PRINT_INFO("adc_mic_average = %d\n", adc_mic_average); + PRINT_INFO("adc_left_average = %d\n", adc_left_average); + + if((gpio_get_value(pdata->gpio_detect)) != last_gpio_detect_value) { + PRINT_INFO("software debance (gpio check)!!!(headset_type_detect)\n"); + return HEADSET_TYPE_ERR; + } + + if(0 == ana_sts0_confirm()) { + PRINT_INFO("software debance (ana_sts0_confirm)!!!(headset_type_detect)\n"); + return HEADSET_TYPE_ERR; + } + + if((adc_mic_average < pdata->adc_threshold_3pole_detect)) { + if(0 != no_mic_retry_count) { + PRINT_INFO("no_mic_retry\n"); + no_mic_retry_count--; + goto no_mic_retry; + } + return HEADSET_NO_MIC; + } else if((adc_left_average < ADC_GND) && (adc_mic_average > ADC_GND)) + return HEADSET_4POLE_NORMAL; + else if((adc_left_average > ADC_GND) && (adc_mic_average > ADC_GND) + && (ABS(adc_mic_average - adc_left_average) < ADC_GND)) + return HEADSET_4POLE_NOT_NORMAL; + else + return HEADSET_TYPE_ERR; + + return HEADSET_TYPE_ERR; +} + +static void button_release_verify(void) +{ + struct sprd_headset *ht = &headset; + + if(1 == button_state_last) { + input_event(ht->input_dev, EV_KEY, current_key_code, 0); + input_sync(ht->input_dev); + button_state_last = 0; + PRINT_INFO("headset button released by force!!! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + } +} + +static void headset_button_work_func(struct work_struct *work) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + int gpio_button_value_current = 0; + int button_state_current = 0; + int adc_mic_average = 0; + int i = 0; + int adc_ideal = 0; + + down(&headset_sem); + set_adc_to_headmic(1); + + ENTER; + + gpio_button_value_current = gpio_get_value(pdata->gpio_button); + if(gpio_button_value_current != gpio_button_value_last) { + PRINT_INFO("software debance (step 1: gpio check)!!!(headset_button_work_func)\n"); +#ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); +#endif + goto out; + } + + button_state_current = headset_gpio_2_button_state(gpio_button_value_current); + + if(1 == button_state_current) {//pressed! + if(ht->platform_data->nbuttons > 0) { + adc_mic_average = adc_get_average(pdata->gpio_button, gpio_button_value_last); + if(-1 == adc_mic_average) { + PRINT_INFO("software debance (step 3: adc check)!!!(headset_button_work_func)(pressed)\n"); +#ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); +#endif + goto out; + } + adc_ideal = adc_get_ideal(adc_mic_average); + PRINT_INFO("adc_mic_average=%d, adc_ideal=%d\n", adc_mic_average, adc_ideal); + if (adc_ideal >= 0) + adc_mic_average = adc_ideal; + PRINT_INFO("adc_mic_average = %d\n", adc_mic_average); + for (i = 0; i < ht->platform_data->nbuttons; i++) { + if (adc_mic_average >= ht->platform_data->headset_buttons[i].adc_min && + adc_mic_average < ht->platform_data->headset_buttons[i].adc_max) { + current_key_code = ht->platform_data->headset_buttons[i].code; + break; + } + current_key_code = KEY_RESERVED; + } + } + + if(0 == button_state_last) { + input_event(ht->input_dev, EV_KEY, current_key_code, 1); + input_sync(ht->input_dev); + button_state_last = 1; + PRINT_INFO("headset button pressed! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + } else { + PRINT_ERR("headset button pressed already! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); +#ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); +#endif + } + + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + + headset_irq_button_enable(1, ht->irq_button); + } else { //released! + if(1 == button_state_last) { + input_event(ht->input_dev, EV_KEY, current_key_code, 0); + input_sync(ht->input_dev); + button_state_last = 0; + PRINT_INFO("headset button released! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + } else + PRINT_ERR("headset button released already! current_key_code = %d(0x%04X)\n", current_key_code, current_key_code); + + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + + headset_irq_button_enable(1, ht->irq_button); + } +out: + headset_adc_en(0); + headset_irq_button_enable(1, ht->irq_button); + //wake_unlock(&headset_button_wakelock); + up(&headset_sem); + return; +} + +static void headset_detect_work_func(struct work_struct *work) +{ + struct sprd_headset *ht = &headset; + struct sprd_headset_platform_data *pdata = ht->platform_data; + SPRD_HEADSET_TYPE headset_type; + int plug_state_current = 0; + int gpio_detect_value_current = 0; + down(&headset_sem); + + ENTER; + if (sprd_hts_power.head_mic == NULL) { + sprd_headset_power_init(&this_pdev->dev); + } + if (sprd_hts_power.head_mic == NULL ) { + PRINT_INFO("sprd_headset_power_init fail 0 \n"); + goto out; + } + + if(0 == plug_state_last) { + //schedule_timeout_uninterruptible(msecs_to_jiffies(10)); + headmicbias_power_on(&this_pdev->dev, 1); + } + + if(0 == plug_state_last) { + gpio_detect_value_current = gpio_get_value(pdata->gpio_detect); + PRINT_INFO("gpio_detect_value_current = %d, gpio_detect_value_last = %d, plug_state_last = %d\n", + gpio_detect_value_current, gpio_detect_value_last, plug_state_last); + + if(gpio_detect_value_current != gpio_detect_value_last) { + PRINT_INFO("software debance (step 1)!!!(headset_detect_work_func)\n"); + goto out; + } + + if(1 == pdata->irq_trigger_level_detect) { + if(1 == gpio_detect_value_current) + plug_state_current = 1; + else + plug_state_current = 0; + } else { + if(0 == gpio_detect_value_current) + plug_state_current = 1; + else + plug_state_current = 0; + } + } else + plug_state_current = 0;//no debounce for plug out!!! + + if(1 == plug_state_current && 0 == plug_state_last) { + headset_type = headset_type_detect(gpio_detect_value_last); + headset_adc_en(0); + switch (headset_type) { + case HEADSET_TYPE_ERR: + detect_err_count ++; + PRINT_INFO("headset_type = %d detect_err_count = %d(HEADSET_TYPE_ERR)\n", headset_type,detect_err_count); + goto out; + case HEADSET_4POLE_NOT_NORMAL: + PRINT_INFO("headset_type = %d (HEADSET_4POLE_NOT_NORMAL)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 1); + break; + case HEADSET_4POLE_NORMAL: + PRINT_INFO("headset_type = %d (HEADSET_4POLE_NORMAL)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + break; + case HEADSET_NO_MIC: + PRINT_INFO("headset_type = %d (HEADSET_NO_MIC)\n", headset_type); + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + break; + case HEADSET_APPLE: + PRINT_INFO("headset_type = %d (HEADSET_APPLE)\n", headset_type); + PRINT_INFO("we have not yet implemented this in the code\n"); + break; + default: + PRINT_INFO("headset_type = %d (HEADSET_UNKNOWN)\n", headset_type); + break; + } + + detect_err_count = 0; + if(headset_type == HEADSET_NO_MIC) + ht->headphone = 1; + else + ht->headphone = 0; + + if (ht->headphone) { + headset_button_irq_threshold(0); + headset_irq_button_enable(0, ht->irq_button); + + ht->type = BIT_HEADSET_NO_MIC; + //hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + PRINT_INFO("headphone plug in (headset_detect_work_func)\n"); + } else { + headset_button_irq_threshold(1); + + if((HEADSET_4POLE_NOT_NORMAL == headset_type) && (0 == pdata->gpio_switch)) { + headset_irq_button_enable(0, ht->irq_button); + PRINT_INFO("HEADSET_4POLE_NOT_NORMAL is not supported by your hardware! so disable the button irq!\n"); + } else { + if (1 == ht->platform_data->irq_trigger_level_button) + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_button, IRQF_TRIGGER_LOW); + + headset_irq_button_enable(1, ht->irq_button); + } + + ht->type = BIT_HEADSET_MIC; + //hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + PRINT_INFO("headset plug in (headset_detect_work_func)\n"); +#ifdef ADPGAR_BYP_SELECT + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(500)); +#endif + } + + plug_state_last = 1; +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + headset_irq_detect_enable(0, ht->irq_detect); + headset_irq_detect_mic_enable(1, ht->irq_detect_mic); +#else + if(1 == pdata->irq_trigger_level_detect) + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_LOW); + else + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_HIGH); + + headset_irq_detect_enable(1, ht->irq_detect); +#endif + } else if(0 == plug_state_current && 1 == plug_state_last) { + + headset_irq_button_enable(0, ht->irq_button); + button_release_verify(); + + if (ht->headphone) { + PRINT_INFO("headphone plug out (headset_detect_work_func)\n"); + } else { + PRINT_INFO("headset plug out (headset_detect_work_func)\n"); + } + ht->type = BIT_HEADSET_OUT; + //hp_notifier_call_chain(ht->type); + switch_set_state(&ht->sdev, ht->type); + plug_state_last = 0; +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + headset_irq_detect_mic_enable(0, ht->irq_detect_mic); +#else + if(1 == pdata->irq_trigger_level_detect) + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_HIGH); + else + headset_irq_set_irq_type(ht->irq_detect, IRQF_TRIGGER_LOW); +#endif + headset_irq_detect_enable(1, ht->irq_detect); + } else { + PRINT_INFO("irq_detect must be enabled anyway!!!\n"); + goto out; + } +out: + + if(0 == plug_state_last) { +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + headset_irq_detect_mic_enable(0, ht->irq_detect_mic); + if (detect_err_count < 500) { + headset_irq_detect_enable(1, ht->irq_detect); + } +#endif + headmicbias_power_on(&this_pdev->dev, 0); + //headset_detect_clk_en(); + //if (sprd_hts_power.head_mic) { + //regulator_set_mode(sprd_hts_power.head_mic, REGULATOR_MODE_NORMAL); + //} + } +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + if (1 == plug_state_last) { + headset_irq_detect_mic_enable(1, ht->irq_detect_mic); + headset_irq_detect_enable(0, ht->irq_detect); + } +#else + headset_irq_detect_enable(1, ht->irq_detect); +#endif + //wake_unlock(&headset_detect_wakelock); + up(&headset_sem); + return; +} + +#ifdef ADPGAR_BYP_SELECT +static void adpgar_byp_select_func(struct work_struct *work) +{ + if ((0x2723 == (sci_get_ana_chip_id() >> 16)) + && (SC2723ES == sci_get_ana_chip_ver() + || SC2723TS == sci_get_ana_chip_ver())) { + return; + } + + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CDC1), AUDIO_ADPGAR_BYP_HEADMIC_2_ADCR, AUDIO_ADPGAR_BYP_MASK, AUDIO_ADPGAR_BYP_SHIFT); + PRINT_INFO("ANA_CDC1 = 0x%08X\n", sci_adi_read(HEADMIC_DETECT_REG(ANA_CDC1))); + msleep(100); + headset_reg_set_val(HEADMIC_DETECT_REG(ANA_CDC1), AUDIO_ADPGAR_BYP_NORMAL, AUDIO_ADPGAR_BYP_MASK, AUDIO_ADPGAR_BYP_SHIFT); + PRINT_INFO("ANA_CDC1 = 0x%08X\n", sci_adi_read(HEADMIC_DETECT_REG(ANA_CDC1))); + PRINT_INFO("adpgar_byp_select_func\n"); +} +#endif + +static void reg_dump_func(struct work_struct *work) +{ + int adc_mic = 0; + + int gpio_detect = 0; + int gpio_button = 0; + int gpio_detect_mic = 0; + + int ana_sts0 = 0; + int ana_pmu0 = 0; + int ana_cfg1 = 0; + int ana_hdt0 = 0; + + int hid_cfg0 = 0; + int hid_cfg2 = 0; + int hid_cfg3 = 0; + int hid_cfg4 = 0; + + int arm_module_en = 0; + int arm_clk_en = 0; + + adc_mic = wrap_sci_adc_get_value(ADC_CHANNEL_HEADMIC, 1); + + gpio_detect = gpio_get_value(headset.platform_data->gpio_detect); + gpio_button = gpio_get_value(headset.platform_data->gpio_button); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + gpio_detect_mic = gpio_get_value(headset.platform_data->gpio_detect_mic); +#endif + + sci_adi_write(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_AUD_EN, BIT_ANA_AUD_EN);//arm base address:0x40038800 for register accessable + ana_pmu0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_PMU0);//arm base address:0x40038600 + ana_hdt0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_HDT0);//arm base address:0x40038600 + ana_sts0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0);//arm base address:0x40038600 + + sci_adi_write(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_HDT_EN, BIT_ANA_HDT_EN);//arm base address:0x40038800 for register accessable + sci_adi_write(ANA_REG_GLB_ARM_CLK_EN, BIT_CLK_AUD_HID_EN, BIT_CLK_AUD_HID_EN);//arm base address:0x40038800 for register accessable + + hid_cfg2 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG2);//arm base address:0x40038700 + hid_cfg3 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG3);//arm base address:0x40038700 + hid_cfg4 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG4);//arm base address:0x40038700 + hid_cfg0 = sci_adi_read(ANA_HDT_INT_BASE+HID_CFG0);//arm base address:0x40038700 + + arm_module_en = sci_adi_read(ANA_REG_GLB_ARM_MODULE_EN); + arm_clk_en = sci_adi_read(ANA_REG_GLB_ARM_CLK_EN); + + PRINT_INFO("GPIO_%03d(det)=%d GPIO_%03d(but)=%d GPIO_%03d(det_mic)=%d adc_mic=%d\n", + headset.platform_data->gpio_detect, gpio_detect, + headset.platform_data->gpio_button, gpio_button, + headset.platform_data->gpio_detect_mic, gpio_detect_mic, + adc_mic); + PRINT_INFO("arm_module_en|arm_clk_en|ana_pmu0 |ana_cfg1 |ana_hdt0 |ana_sts0 |hid_cfg2 |hid_cfg3 |hid_cfg4 |hid_cfg0\n"); + PRINT_INFO("0x%08X |0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X\n", + arm_module_en, arm_clk_en, ana_pmu0, ana_cfg1, ana_hdt0, ana_sts0, hid_cfg2, hid_cfg3, hid_cfg4, hid_cfg0); +#if 0 + int i = 0; + int hbd[20] = {0}; + + for(i=0; i<20; i++) + hbd[i] = sci_adi_read(ANA_HDT_INT_BASE + (i*4)); + + PRINT_INFO(" hbd_cfg0 | hbd_cfg1 | hbd_cfg2 | hbd_cfg3 | hbd_cfg4 | hbd_cfg5 | hbd_cfg6 | hbd_cfg7 | hbd_cfg8 | hbd_cfg9\n"); + PRINT_INFO("0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X\n", + hbd[0], hbd[1], hbd[2], hbd[3], hbd[4], hbd[5], hbd[6], hbd[7], hbd[8], hbd[9]); + + PRINT_INFO("hbd_cfg10 |hbd_cfg11 |hbd_cfg12 |hbd_cfg13 |hbd_cfg14 |hbd_cfg15 |hbd_cfg16 | hbd_sts0 | hbd_sts1 | hbd_sts2\n"); + PRINT_INFO("0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X|0x%08X\n", + hbd[10], hbd[11], hbd[12], hbd[13], hbd[14], hbd[15], hbd[16], hbd[17], hbd[18], hbd[19]); +#endif + + if (debug_level >= 2) + queue_delayed_work(reg_dump_work_queue, ®_dump_work, msecs_to_jiffies(500)); + return; +} + +/** + * some phone boards, when no headset plugin, the button headset_button_valid() + * will always return 0, so we need to disable headset_button irq in here + * otherwise button irq will always happen. + */ +volatile int headset_button_hardware_bug_fix_stage = 1; +static void headset_button_hardware_bug_fix(struct sprd_headset *ht) +{ + if (headset_button_hardware_bug_fix_stage) { + headset_irq_button_enable(0, ht->irq_button); // disable button irq before headset detected + pr_err("This phone board headset button circuit has bug, please fix the hardware\n"); + } +} + +static irqreturn_t headset_button_irq_handler(int irq, void *dev) +{ + struct sprd_headset *ht = dev; + int button_state_current = 0; + int gpio_button_value_current = 0; + + if(0 == headset_button_valid(gpio_get_value(ht->platform_data->gpio_detect))) { + PRINT_INFO("headset_button_irq_handler: button is invalid!!! IRQ_%d(GPIO_%d) = %d, ANA_STS0 = 0x%08X\n", + ht->irq_button, ht->platform_data->gpio_button, gpio_button_value_last, + sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0)); + headset_button_hardware_bug_fix(ht); + + if(0 == plug_state_last) + headset_irq_button_enable(0, ht->irq_button); + + return IRQ_HANDLED; + } + + gpio_button_value_current = gpio_get_value(ht->platform_data->gpio_button); + button_state_current = headset_gpio_2_button_state(gpio_button_value_current); +#ifdef ADPGAR_BYP_SELECT + if(0 == button_state_current) { + queue_delayed_work(adpgar_byp_select_work_queue, &adpgar_byp_select_work, msecs_to_jiffies(0)); + } +#endif + if(button_state_current == button_state_last) { + PRINT_INFO("button state check failed!!! maybe the release is too quick. button_state_current=%d, button_state_last=%d\n", + button_state_current, button_state_last); + return IRQ_HANDLED; + } + + headset_irq_button_enable(0, ht->irq_button); + wake_lock_timeout(&headset_button_wakelock, msecs_to_jiffies(2000)); + gpio_button_value_last = gpio_button_value_current; + PRINT_DBG("headset_button_irq_handler: IRQ_%d(GPIO_%d) = %d, ANA_STS0 = 0x%08X\n", + ht->irq_button, ht->platform_data->gpio_button, gpio_button_value_last, + sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0)); + queue_work(ht->button_work_queue, &ht->work_button); + return IRQ_HANDLED; +} + +static irqreturn_t headset_detect_irq_handler(int irq, void *dev) +{ + struct sprd_headset *ht = dev; + PRINT_INFO("headset_detect_irq_handler in \n"); + headset_irq_button_enable(0, ht->irq_button); + headset_irq_detect_enable(0, ht->irq_detect); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + headset_irq_detect_mic_enable(0, ht->irq_detect_mic); +#endif + wake_lock_timeout(&headset_detect_wakelock, msecs_to_jiffies(2000)); + gpio_detect_value_last = gpio_get_value(ht->platform_data->gpio_detect); + PRINT_DBG("headset_detect_irq_handler: IRQ_%d(GPIO_%d) = %d, ANA_STS0 = 0x%08X\n", + ht->irq_detect, ht->platform_data->gpio_detect, gpio_detect_value_last, + sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0)); + queue_delayed_work(ht->detect_work_queue, &ht->work_detect, msecs_to_jiffies(0)); + PRINT_INFO("headset_detect_irq_handler out \n"); + return IRQ_HANDLED; +} + +static irqreturn_t headset_detect_mic_irq_handler(int irq, void *dev) +{ + struct sprd_headset *ht = dev; + PRINT_INFO("headset_detect_mic_irq_handler in \n"); + if (plug_state_last != 1) { + headset_irq_detect_mic_enable(0, ht->irq_detect_mic); + PRINT_INFO("headset_detect_mic_irq_handler out0 \n"); + return IRQ_HANDLED; + } + headset_irq_detect_mic_enable(0, ht->irq_detect_mic); + headset_irq_detect_enable(0, ht->irq_detect); + wake_lock_timeout(&headset_detect_wakelock, msecs_to_jiffies(2000)); + queue_delayed_work(ht->detect_work_queue, &ht->work_detect, msecs_to_jiffies(0)); + PRINT_INFO("headset_detect_mic_irq_handler out1 \n"); + return IRQ_HANDLED; +} + +//================= create sys fs for debug =================== +//============= /sys/kernel/headset/debug_level =============== + +static ssize_t headset_debug_level_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + PRINT_INFO("debug_level = %d\n", debug_level); + return sprintf(buff, "%d\n", debug_level); +} + +static ssize_t headset_debug_level_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t len) +{ + unsigned long level = simple_strtoul(buff, NULL, 10); + debug_level = level; + PRINT_INFO("debug_level = %d\n", debug_level); + if(debug_level >= 2) + queue_delayed_work(reg_dump_work_queue, ®_dump_work, msecs_to_jiffies(500)); + return len; +} + +static struct kobject *headset_debug_kobj = NULL; +static struct kobj_attribute headset_debug_attr = + __ATTR(debug_level, 0644, headset_debug_level_show, headset_debug_level_store); + +static int headset_debug_sysfs_init(void) +{ + int ret = -1; + + headset_debug_kobj = kobject_create_and_add("headset", kernel_kobj); + if (headset_debug_kobj == NULL) { + ret = -ENOMEM; + PRINT_ERR("register sysfs failed. ret = %d\n", ret); + return ret; + } + + ret = sysfs_create_file(headset_debug_kobj, &headset_debug_attr.attr); + if (ret) { + PRINT_ERR("create sysfs failed. ret = %d\n", ret); + return ret; + } + + PRINT_INFO("headset_debug_sysfs_init success\n"); + return ret; +} +//================= create sys fs for debug =================== + +#ifdef CONFIG_OF +static struct sprd_headset_platform_data *headset_detect_parse_dt(struct device *dev) +{ + struct sprd_headset_platform_data *pdata; + struct device_node *np = dev->of_node,*buttons_np = NULL; + int ret; + struct headset_buttons *buttons_data; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "could not allocate memory for platform data\n"); + return NULL; + } + ret = of_property_read_u32(np, "gpio_switch", &pdata->gpio_switch); + if(ret) { + dev_err(dev, "fail to get gpio_switch\n"); + goto fail; + } + ret = of_property_read_u32(np, "gpio_detect", &pdata->gpio_detect); + if(ret) { + dev_err(dev, "fail to get gpio_detect\n"); + goto fail; + } +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + ret = of_property_read_u32(np, "gpio_detect_mic", &pdata->gpio_detect_mic); + if (ret) { + dev_err(dev, "fail to get gpio_detect_mic\n"); + goto fail; + } +#endif + ret = of_property_read_u32(np, "gpio_button", &pdata->gpio_button); + if(ret) { + dev_err(dev, "fail to get gpio_button\n"); + goto fail; + } + ret = of_property_read_u32(np, "irq_trigger_level_detect", &pdata->irq_trigger_level_detect); + if(ret) { + dev_err(dev, "fail to get irq_trigger_level_detect\n"); + goto fail; + } +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + ret = of_property_read_u32(np, "irq_trigger_level_detect_mic", &pdata->irq_trigger_level_detect_mic); + if (ret) { + dev_err(dev, "fail to get irq_trigger_level_detect_mic\n"); + goto fail; + } +#endif + ret = of_property_read_u32(np, "irq_trigger_level_button", &pdata->irq_trigger_level_button); + if(ret) { + dev_err(dev, "fail to get irq_trigger_level_button\n"); + goto fail; + } + ret = of_property_read_u32(np, "adc_threshold_3pole_detect", &pdata->adc_threshold_3pole_detect); + if(ret) { + dev_err(dev, "fail to get adc_threshold_3pole_detect\n"); + goto fail; + } + + ret = of_property_read_u32(np, "adc_threshold_4pole_detect", &pdata->adc_threshold_4pole_detect); + if(ret) { + dev_err(dev, "fail to get adc_threshold_4pole_detect\n"); + goto fail; + } + + ret = of_property_read_u32(np, "irq_threshold_buttont", &pdata->irq_threshold_buttont); + if(ret) { + dev_err(dev, "fail to get irq_threshold_buttont\n"); + goto fail; + } + + ret = of_property_read_u32(np, "voltage_headmicbias", &pdata->voltage_headmicbias); + if(ret) { + dev_err(dev, "fail to get voltage_headmicbias\n"); + goto fail; + } + ret = of_property_read_u32(np, "nbuttons", &pdata->nbuttons); + if(ret) { + dev_err(dev, "fail to get nbuttons\n"); + goto fail; + } + + buttons_data = kzalloc(pdata->nbuttons*sizeof(*buttons_data),GFP_KERNEL); + if (!buttons_data) { + dev_err(dev, "could not allocate memory for headset_buttons\n"); + goto fail; + } + pdata->headset_buttons = buttons_data; + + for_each_child_of_node(np,buttons_np) { + + ret = of_property_read_u32(buttons_np, "adc_min", &buttons_data->adc_min); + if (ret) { + dev_err(dev, "fail to get adc_min\n"); + goto fail_buttons_data; + } + ret = of_property_read_u32(buttons_np, "adc_max", &buttons_data->adc_max); + if (ret) { + dev_err(dev, "fail to get adc_max\n"); + goto fail_buttons_data; + } + ret = of_property_read_u32(buttons_np, "code", &buttons_data->code); + if (ret) { + dev_err(dev, "fail to get code\n"); + goto fail_buttons_data; + } + ret = of_property_read_u32(buttons_np, "type", &buttons_data->type); + if (ret) { + dev_err(dev, "fail to get type\n"); + goto fail_buttons_data; + } + PRINT_INFO("device tree data: adc_min = %d adc_max = %d code = %d type = %d \n", buttons_data->adc_min, + buttons_data->adc_max, buttons_data->code, buttons_data->type); + buttons_data++; + }; + + return pdata; + +fail_buttons_data: + kfree(buttons_data); + pdata->headset_buttons = NULL; +fail: + kfree(pdata); + return NULL; +} +#endif +static void adc_cal_from_efuse(void); + +static int headset_detect_probe(struct platform_device *pdev) +{ + struct sprd_headset_platform_data *pdata = pdev->dev.platform_data; + struct sprd_headset *ht = &headset; + unsigned long irqflags = 0; + struct input_dev *input_dev = NULL; + int i = 0; + int ret = -1; + int ana_sts0 = 0; + int adie_chip_id_low = 0; + int adie_chip_id_high = 0; + unsigned int adie_chip_id = 0; + unsigned int ddie_chip_id = 0; + +#ifdef CONFIG_OF + struct device_node *np = NULL; + np = pdev->dev.of_node; + + if (pdev->dev.of_node && !pdata) { + pdata = headset_detect_parse_dt(&pdev->dev); + if(pdata) + pdev->dev.platform_data = pdata; + } + if (!pdata) { + PRINT_ERR("headset_detect_probe get platform_data NULL\n"); + ret = -EINVAL; + goto fail_to_get_platform_data; + } +#endif + + this_pdev = pdev; + + sci_adi_write(ANA_REG_GLB_ARM_MODULE_EN, BIT_ANA_AUD_EN, BIT_ANA_AUD_EN);//arm base address:0x40038800 for register accessable + ana_sts0 = sci_adi_read(ANA_AUDCFGA_INT_BASE+ANA_STS0);//arm base address:0x40038600 + + ddie_chip_id = sci_get_chip_id(); + PRINT_INFO("ddie_chip_id=0x%08X\n", ddie_chip_id); + adie_chip_id = sci_get_ana_chip_id(); + PRINT_INFO("adie_chip_id=0x%08X\n", adie_chip_id); + + adie_chip_id_high = (adie_chip_id >> 16) & 0xFFFF; + adie_chip_id_low = adie_chip_id & 0xFFFF; + + switch (adie_chip_id_high) { + case 0x2723: {//chip is 2723 + adie_type = 2723; + PRINT_INFO("adie chip is 2723, type = %d\n", adie_type); + break; //break from 2723 + } + + default: { + adie_type = -1; //unknow + PRINT_ERR("unknow adie chip !!!\n"); + break; + } + } + + ht->platform_data = pdata; + headset_detect_init(); + + ret = sprd_headset_power_init(&pdev->dev); + if (ret) + goto fail_to_sprd_headset_power_init; + + PRINT_INFO("====================== SPRD HEADSET DETECT v2 ======================\n"); + PRINT_INFO(" write 0~3 to \"/sys/kernel/headset/debug_level\" for headset debug\n"); + PRINT_INFO("====================================================================\n"); + PRINT_INFO("use GPIO_%d for insert detecting\n", pdata->gpio_detect); + + if(0 != pdata->gpio_switch) { + ret = gpio_request(pdata->gpio_switch, "headset_switch"); + if (ret < 0) { + PRINT_ERR("failed to request GPIO_%d(headset_switch)\n", pdata->gpio_switch); + goto failed_to_request_gpio_switch; + } + } else + PRINT_INFO("automatic type switch is unsupported\n"); + + ret = gpio_request(pdata->gpio_detect, "headset_detect"); + if (ret < 0) { + PRINT_ERR("failed to request GPIO_%d(headset_detect)\n", pdata->gpio_detect); + goto failed_to_request_gpio_detect; + } +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + ret = gpio_request(pdata->gpio_detect_mic, "headset_detect_mic"); + if (ret < 0) { + PRINT_ERR("failed to request GPIO_%d(headset_detect_mic)\n", pdata->gpio_detect_mic); + goto failed_to_request_gpio_detect_mic; + } +#endif + ret = gpio_request(pdata->gpio_button, "headset_button"); + if (ret < 0) { + PRINT_ERR("failed to request GPIO_%d(headset_button)\n", pdata->gpio_button); + goto failed_to_request_gpio_button; + } + + if(0 != pdata->gpio_switch) + gpio_direction_output(pdata->gpio_switch, 0); + gpio_direction_input(pdata->gpio_detect); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + gpio_direction_input(pdata->gpio_detect_mic); +#endif + gpio_direction_input(pdata->gpio_button); + ht->irq_detect = gpio_to_irq(pdata->gpio_detect); + ht->irq_button = gpio_to_irq(pdata->gpio_button); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + ht->irq_detect_mic = gpio_to_irq(pdata->gpio_detect_mic); +#endif + ret = switch_dev_register(&ht->sdev); + if (ret < 0) { + PRINT_ERR("switch_dev_register failed!\n"); + goto failed_to_register_switch_dev; + } + + input_dev = input_allocate_device(); + if ( !input_dev) { + PRINT_ERR("input_allocate_device for headset_button failed!\n"); + ret = -ENOMEM; + goto failed_to_allocate_input_device; + } + + input_dev->name = "headset-keyboard"; + input_dev->id.bustype = BUS_HOST; + ht->input_dev = input_dev; + + for (i = 0; i < pdata->nbuttons; i++) { + struct headset_buttons *buttons = &pdata->headset_buttons[i]; + unsigned int type = buttons->type ?: EV_KEY; + input_set_capability(input_dev, type, buttons->code); + } + + ret = input_register_device(input_dev); + if (ret) { + PRINT_ERR("input_register_device for headset_button failed!\n"); + goto failed_to_register_input_device; + } + + sema_init(&headset_sem, 1); + + INIT_WORK(&ht->work_button, headset_button_work_func); + ht->button_work_queue = create_singlethread_workqueue("headset_button"); + if(ht->button_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for headset_button failed!\n"); + goto failed_to_create_singlethread_workqueue_for_headset_button; + } + + INIT_DELAYED_WORK(&ht->work_detect, headset_detect_work_func); + ht->detect_work_queue = create_singlethread_workqueue("headset_detect"); + if(ht->detect_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for headset_detect failed!\n"); + goto failed_to_create_singlethread_workqueue_for_headset_detect; + } + + INIT_DELAYED_WORK(®_dump_work, reg_dump_func); + reg_dump_work_queue = create_singlethread_workqueue("headset_reg_dump"); + if(reg_dump_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for headset_reg_dump failed!\n"); + goto failed_to_create_singlethread_workqueue_for_headset_reg_dump; + } + if (debug_level >= 2) + queue_delayed_work(reg_dump_work_queue, ®_dump_work, msecs_to_jiffies(500)); + +#ifdef ADPGAR_BYP_SELECT + //work queue for Bug#298417 + INIT_DELAYED_WORK(&adpgar_byp_select_work, adpgar_byp_select_func); + adpgar_byp_select_work_queue = create_singlethread_workqueue("adpgar_byp_select"); + if(adpgar_byp_select_work_queue == NULL) { + PRINT_ERR("create_singlethread_workqueue for adpgar_byp_select failed!\n"); + goto failed_to_create_singlethread_workqueue_for_adpgar_byp_select; + } + PRINT_INFO("ADPGAR_BYP_SELECT is enabled!\n"); +#endif + + wake_lock_init(&headset_detect_wakelock, WAKE_LOCK_SUSPEND, "headset_detect_wakelock"); + wake_lock_init(&headset_button_wakelock, WAKE_LOCK_SUSPEND, "headset_button_wakelock"); + + //set EIC3 de-bounce time for button irq + headset_reg_set_val((ANA_EIC_BASE+EIC3_DBNC_CTRL), DBNC_CNT3_VALUE, DBNC_CNT3_MASK, DBNC_CNT3_SHIFT); + //set EIC5 de-bounce time for inser irq + headset_reg_set_val((ANA_EIC_BASE+EIC8_DBNC_CTRL), DBNC_CNT8_VALUE, DBNC_CNT8_MASK, DBNC_CNT8_SHIFT); + +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + /* set EIC5 de-bounce time for ins_mic irq */ + headset_reg_set_val((ANA_EIC_BASE+EIC5_DBNC_CTRL), DBNC_CNT5_VALUE, DBNC_CNT5_MASK, DBNC_CNT5_SHIFT); +#endif + irqflags = pdata->irq_trigger_level_button ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW; + ret = request_irq(ht->irq_button, headset_button_irq_handler, irqflags | IRQF_NO_SUSPEND, "headset_button", ht); + if (ret) { + PRINT_ERR("failed to request IRQ_%d(GPIO_%d)\n", ht->irq_button, pdata->gpio_button); + goto failed_to_request_irq_headset_button; + } + headset_irq_button_enable(0, ht->irq_button);//disable button irq before headset detected + headset_button_hardware_bug_fix_stage = 0; + + irqflags = pdata->irq_trigger_level_detect ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW; + ret = request_irq(ht->irq_detect, headset_detect_irq_handler, irqflags | IRQF_NO_SUSPEND, "headset_detect", ht); + if (ret < 0) { + PRINT_ERR("failed to request IRQ_%d(GPIO_%d)\n", ht->irq_detect, pdata->gpio_detect); + goto failed_to_request_irq_headset_detect; + } +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 + irqflags = pdata->irq_trigger_level_detect_mic ? IRQF_TRIGGER_HIGH : IRQF_TRIGGER_LOW; + ret = request_irq(ht->irq_detect_mic, headset_detect_mic_irq_handler, irqflags | IRQF_NO_SUSPEND, "headset_detect_mic", ht); + if (ret < 0) { + PRINT_ERR("failed to request IRQ_%d(GPIO_%d)\n", ht->irq_detect_mic, pdata->gpio_detect_mic); + goto failed_to_request_irq_mic_headset_detect; + } +#endif + ret = headset_debug_sysfs_init(); + adc_cal_from_efuse(); + PRINT_INFO("headset_detect_probe success\n"); + return ret; +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 +failed_to_request_irq_mic_headset_detect: + free_irq(ht->irq_detect_mic, ht); +#endif +failed_to_request_irq_headset_detect: + free_irq(ht->irq_button, ht); +failed_to_request_irq_headset_button: + +#ifdef ADPGAR_BYP_SELECT + cancel_delayed_work_sync(&adpgar_byp_select_work); + destroy_workqueue(adpgar_byp_select_work_queue); +failed_to_create_singlethread_workqueue_for_adpgar_byp_select: +#endif + + cancel_delayed_work_sync(®_dump_work); + destroy_workqueue(reg_dump_work_queue); +failed_to_create_singlethread_workqueue_for_headset_reg_dump: + destroy_workqueue(ht->detect_work_queue); +failed_to_create_singlethread_workqueue_for_headset_detect: + destroy_workqueue(ht->button_work_queue); +failed_to_create_singlethread_workqueue_for_headset_button: + input_unregister_device(input_dev); +failed_to_register_input_device: + input_free_device(input_dev); +failed_to_allocate_input_device: + switch_dev_unregister(&ht->sdev); +failed_to_register_switch_dev: + gpio_free(pdata->gpio_button); +failed_to_request_gpio_button: + gpio_free(pdata->gpio_detect); +#ifdef CONFIG_SND_SOC_SPRD_USE_EAR_JACK_TYPE13 +failed_to_request_gpio_detect_mic: + gpio_free(pdata->gpio_detect_mic); +#endif +failed_to_request_gpio_detect: + if(0 != pdata->gpio_switch) + gpio_free(pdata->gpio_switch); +failed_to_request_gpio_switch: + headmicbias_power_on(&pdev->dev, 0); +fail_to_sprd_headset_power_init: +fail_to_get_platform_data: + PRINT_ERR("headset_detect_probe failed\n"); + return ret; +} + +#ifdef CONFIG_PM +static int headset_suspend(struct platform_device *dev, pm_message_t state) +{ + PRINT_INFO("suspend (det_irq = %d, but_irq = %d, plug_state_last = %d)\n", + headset.irq_detect, headset.irq_button, plug_state_last); + return 0; +} + +static int headset_resume(struct platform_device *dev) +{ + PRINT_INFO("resume (det_irq = %d, but_irq = %d, plug_state_last = %d)\n", + headset.irq_detect, headset.irq_button, plug_state_last); + return 0; +} +#else +#define headset_suspend NULL +#define headset_resume NULL +#endif + +static int adc_get_ideal(u32 adc_mic) +{ + u64 numerator = 0; + u32 denominator = 0; + u32 adc_ideal = 0; + if (adc_cal_headset.cal_type == SPRD_HEADSET_AUXADC_CAL_DO){ + PRINT_INFO("wangzuo adc_get_ideal in\n"); + u32 a = adc_cal_headset.A; + u32 b = adc_cal_headset.B; + u32 e = adc_cal_headset.E; + + if (9*adc_mic + b < 10*a){ + return adc_mic; + } + denominator = e*(b-a); + numerator = 917280*(9*(u64)adc_mic+(u64)b-10*(u64)a); + PRINT_INFO("denominator=%u, numerator=%llu\n", denominator, numerator); + do_div(numerator,denominator); + adc_ideal = (u32)numerator; + PRINT_INFO("adc_mic=%d, adc_ideal=%d\n", adc_mic, adc_ideal); + return adc_ideal; + } else { + return adc_mic; + } +} +extern u32 __adie_efuse_read_bits(int bit_index, int length); + +#define DELTA1_BLOCK9 9 +#define DELTA2_BLOCK10 10 +#define DELTA3_BLOCK11 11 +#define BITCOUNT 8 +#define BLK_WIDTH 8 +static void adc_cal_from_efuse(void) +{ + u32 delta[3] = {0}; + u32 block0_bit7 = 128; + u8 test[3] = {0}; + PRINT_INFO("to get calibration data from efuse ...\n"); + + if (adc_cal_headset.cal_type == SPRD_HEADSET_AUXADC_CAL_NO) { + + delta[0] = __adie_efuse_read_bits(DELTA1_BLOCK9*BLK_WIDTH,BITCOUNT); + delta[1] = __adie_efuse_read_bits(DELTA2_BLOCK10*BLK_WIDTH,BITCOUNT); + delta[2] = __adie_efuse_read_bits(DELTA3_BLOCK11*BLK_WIDTH,BITCOUNT); + + test[0] = delta[0]&0xff; + test[1] = delta[1]&0xff; + test[2] = delta[2]&0xff; + PRINT_INFO("test[0] 0x%x %d, test[1] 0x%x %d, test[2] 0x%x %d \n",test[0],test[0],test[1],test[1],test[2],test[2]); + + block0_bit7 = __adie_efuse_read_bits(0,BITCOUNT); + PRINT_INFO("block_7 0x%08x \n",block0_bit7); + if(block0_bit7&(1<<7)){ + PRINT_INFO("block 0 bit 7 set 1 no efuse data\n"); + return; + } else { + adc_cal_headset.cal_type = SPRD_HEADSET_AUXADC_CAL_DO; + PRINT_INFO("block 0 bit 7 set 0 have efuse data\n"); + } + + PRINT_INFO("test[0] %d, test[1] %d, test[2] %d \n",test[0],test[1],test[2]); + + adc_cal_headset.A = (test[0]*4)-184; + adc_cal_headset.B = (test[1]*4)+2764; + adc_cal_headset.E = (test[2]*2)+2500; + PRINT_INFO("adc_cal_headset.A %d, adc_cal_headset.B %d, adc_cal_headset.E %d \n",adc_cal_headset.A,adc_cal_headset.B,adc_cal_headset.E); + } else { + PRINT_INFO("efuse A,B,E has been calculated! \n"); + } + +} + +static const struct of_device_id headset_detect_of_match[] = { + {.compatible = "sprd,headset_sprd_sc2723",}, + { } +}; +static struct platform_driver headset_detect_driver = { + .driver = { + .name = "headset_sprd_sc2723", + .owner = THIS_MODULE, + .of_match_table = headset_detect_of_match, + }, + .probe = headset_detect_probe, + //.suspend = headset_suspend, + //.resume = headset_resume, +}; + +static int __init headset_init(void) +{ + int ret; + ret = platform_driver_register(&headset_detect_driver); + return ret; +} + +static void __exit headset_exit(void) +{ + sprd_headset_power_deinit(); + platform_driver_unregister(&headset_detect_driver); +} + +late_initcall(headset_init); +module_exit(headset_exit); + +MODULE_DESCRIPTION("headset & button detect driver v2"); +MODULE_AUTHOR("Yaochuan Li <yaochuan.li@spreadtrum.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/hp_sdc_rtc.c b/drivers/input/misc/hp_sdc_rtc.c new file mode 100644 index 00000000..86b82280 --- /dev/null +++ b/drivers/input/misc/hp_sdc_rtc.c @@ -0,0 +1,730 @@ +/* + * HP i8042 SDC + MSM-58321 BBRTC driver. + * + * Copyright (c) 2001 Brian S. Julin + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL"). + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * + * References: + * System Device Controller Microprocessor Firmware Theory of Operation + * for Part Number 1820-4784 Revision B. Dwg No. A-1820-4784-2 + * efirtc.c by Stephane Eranian/Hewlett Packard + * + */ + +#include <linux/hp_sdc.h> +#include <linux/errno.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/time.h> +#include <linux/miscdevice.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <linux/poll.h> +#include <linux/rtc.h> +#include <linux/mutex.h> +#include <linux/semaphore.h> + +MODULE_AUTHOR("Brian S. Julin <bri@calyx.com>"); +MODULE_DESCRIPTION("HP i8042 SDC + MSM-58321 RTC Driver"); +MODULE_LICENSE("Dual BSD/GPL"); + +#define RTC_VERSION "1.10d" + +static DEFINE_MUTEX(hp_sdc_rtc_mutex); +static unsigned long epoch = 2000; + +static struct semaphore i8042tregs; + +static hp_sdc_irqhook hp_sdc_rtc_isr; + +static struct fasync_struct *hp_sdc_rtc_async_queue; + +static DECLARE_WAIT_QUEUE_HEAD(hp_sdc_rtc_wait); + +static ssize_t hp_sdc_rtc_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos); + +static long hp_sdc_rtc_unlocked_ioctl(struct file *file, + unsigned int cmd, unsigned long arg); + +static unsigned int hp_sdc_rtc_poll(struct file *file, poll_table *wait); + +static int hp_sdc_rtc_open(struct inode *inode, struct file *file); +static int hp_sdc_rtc_fasync (int fd, struct file *filp, int on); + +static void hp_sdc_rtc_isr (int irq, void *dev_id, + uint8_t status, uint8_t data) +{ + return; +} + +static int hp_sdc_rtc_do_read_bbrtc (struct rtc_time *rtctm) +{ + struct semaphore tsem; + hp_sdc_transaction t; + uint8_t tseq[91]; + int i; + + i = 0; + while (i < 91) { + tseq[i++] = HP_SDC_ACT_DATAREG | + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN; + tseq[i++] = 0x01; /* write i8042[0x70] */ + tseq[i] = i / 7; /* BBRTC reg address */ + i++; + tseq[i++] = HP_SDC_CMD_DO_RTCR; /* Trigger command */ + tseq[i++] = 2; /* expect 1 stat/dat pair back. */ + i++; i++; /* buffer for stat/dat pair */ + } + tseq[84] |= HP_SDC_ACT_SEMAPHORE; + t.endidx = 91; + t.seq = tseq; + t.act.semaphore = &tsem; + sema_init(&tsem, 0); + + if (hp_sdc_enqueue_transaction(&t)) return -1; + + /* Put ourselves to sleep for results. */ + if (WARN_ON(down_interruptible(&tsem))) + return -1; + + /* Check for nonpresence of BBRTC */ + if (!((tseq[83] | tseq[90] | tseq[69] | tseq[76] | + tseq[55] | tseq[62] | tseq[34] | tseq[41] | + tseq[20] | tseq[27] | tseq[6] | tseq[13]) & 0x0f)) + return -1; + + memset(rtctm, 0, sizeof(struct rtc_time)); + rtctm->tm_year = (tseq[83] & 0x0f) + (tseq[90] & 0x0f) * 10; + rtctm->tm_mon = (tseq[69] & 0x0f) + (tseq[76] & 0x0f) * 10; + rtctm->tm_mday = (tseq[55] & 0x0f) + (tseq[62] & 0x0f) * 10; + rtctm->tm_wday = (tseq[48] & 0x0f); + rtctm->tm_hour = (tseq[34] & 0x0f) + (tseq[41] & 0x0f) * 10; + rtctm->tm_min = (tseq[20] & 0x0f) + (tseq[27] & 0x0f) * 10; + rtctm->tm_sec = (tseq[6] & 0x0f) + (tseq[13] & 0x0f) * 10; + + return 0; +} + +static int hp_sdc_rtc_read_bbrtc (struct rtc_time *rtctm) +{ + struct rtc_time tm, tm_last; + int i = 0; + + /* MSM-58321 has no read latch, so must read twice and compare. */ + + if (hp_sdc_rtc_do_read_bbrtc(&tm_last)) return -1; + if (hp_sdc_rtc_do_read_bbrtc(&tm)) return -1; + + while (memcmp(&tm, &tm_last, sizeof(struct rtc_time))) { + if (i++ > 4) return -1; + memcpy(&tm_last, &tm, sizeof(struct rtc_time)); + if (hp_sdc_rtc_do_read_bbrtc(&tm)) return -1; + } + + memcpy(rtctm, &tm, sizeof(struct rtc_time)); + + return 0; +} + + +static int64_t hp_sdc_rtc_read_i8042timer (uint8_t loadcmd, int numreg) +{ + hp_sdc_transaction t; + uint8_t tseq[26] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + 0, + HP_SDC_CMD_READ_T1, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T2, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T3, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T4, 2, 0, 0, + HP_SDC_ACT_POSTCMD | HP_SDC_ACT_DATAIN, + HP_SDC_CMD_READ_T5, 2, 0, 0 + }; + + t.endidx = numreg * 5; + + tseq[1] = loadcmd; + tseq[t.endidx - 4] |= HP_SDC_ACT_SEMAPHORE; /* numreg assumed > 1 */ + + t.seq = tseq; + t.act.semaphore = &i8042tregs; + + /* Sleep if output regs in use. */ + if (WARN_ON(down_interruptible(&i8042tregs))) + return -1; + + if (hp_sdc_enqueue_transaction(&t)) return -1; + + /* Sleep until results come back. */ + if (WARN_ON(down_interruptible(&i8042tregs))) + return -1; + + up(&i8042tregs); + + return (tseq[5] | + ((uint64_t)(tseq[10]) << 8) | ((uint64_t)(tseq[15]) << 16) | + ((uint64_t)(tseq[20]) << 24) | ((uint64_t)(tseq[25]) << 32)); +} + + +/* Read the i8042 real-time clock */ +static inline int hp_sdc_rtc_read_rt(struct timeval *res) { + int64_t raw; + uint32_t tenms; + unsigned int days; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_RT, 5); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + days = (unsigned int)(raw >> 24) & 0xffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100) + days * 86400; + + return 0; +} + + +/* Read the i8042 fast handshake timer */ +static inline int hp_sdc_rtc_read_fhs(struct timeval *res) { + int64_t raw; + unsigned int tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_FHS, 2); + if (raw < 0) return -1; + + tenms = (unsigned int)raw & 0xffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +/* Read the i8042 match timer (a.k.a. alarm) */ +static inline int hp_sdc_rtc_read_mt(struct timeval *res) { + int64_t raw; + uint32_t tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_MT, 3); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +/* Read the i8042 delay timer */ +static inline int hp_sdc_rtc_read_dt(struct timeval *res) { + int64_t raw; + uint32_t tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_DT, 3); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +/* Read the i8042 cycle timer (a.k.a. periodic) */ +static inline int hp_sdc_rtc_read_ct(struct timeval *res) { + int64_t raw; + uint32_t tenms; + + raw = hp_sdc_rtc_read_i8042timer(HP_SDC_CMD_LOAD_CT, 3); + if (raw < 0) return -1; + + tenms = (uint32_t)raw & 0xffffff; + + res->tv_usec = (suseconds_t)(tenms % 100) * 10000; + res->tv_sec = (time_t)(tenms / 100); + + return 0; +} + + +#if 0 /* not used yet */ +/* Set the i8042 real-time clock */ +static int hp_sdc_rtc_set_rt (struct timeval *setto) +{ + uint32_t tenms; + unsigned int days; + hp_sdc_transaction t; + uint8_t tseq[11] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + HP_SDC_CMD_SET_RTMS, 3, 0, 0, 0, + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + HP_SDC_CMD_SET_RTD, 2, 0, 0 + }; + + t.endidx = 10; + + if (0xffff < setto->tv_sec / 86400) return -1; + days = setto->tv_sec / 86400; + if (0xffff < setto->tv_usec / 1000000 / 86400) return -1; + days += ((setto->tv_sec % 86400) + setto->tv_usec / 1000000) / 86400; + if (days > 0xffff) return -1; + + if (0xffffff < setto->tv_sec) return -1; + tenms = setto->tv_sec * 100; + if (0xffffff < setto->tv_usec / 10000) return -1; + tenms += setto->tv_usec / 10000; + if (tenms > 0xffffff) return -1; + + tseq[3] = (uint8_t)(tenms & 0xff); + tseq[4] = (uint8_t)((tenms >> 8) & 0xff); + tseq[5] = (uint8_t)((tenms >> 16) & 0xff); + + tseq[9] = (uint8_t)(days & 0xff); + tseq[10] = (uint8_t)((days >> 8) & 0xff); + + t.seq = tseq; + + if (hp_sdc_enqueue_transaction(&t)) return -1; + return 0; +} + +/* Set the i8042 fast handshake timer */ +static int hp_sdc_rtc_set_fhs (struct timeval *setto) +{ + uint32_t tenms; + hp_sdc_transaction t; + uint8_t tseq[5] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + HP_SDC_CMD_SET_FHS, 2, 0, 0 + }; + + t.endidx = 4; + + if (0xffff < setto->tv_sec) return -1; + tenms = setto->tv_sec * 100; + if (0xffff < setto->tv_usec / 10000) return -1; + tenms += setto->tv_usec / 10000; + if (tenms > 0xffff) return -1; + + tseq[3] = (uint8_t)(tenms & 0xff); + tseq[4] = (uint8_t)((tenms >> 8) & 0xff); + + t.seq = tseq; + + if (hp_sdc_enqueue_transaction(&t)) return -1; + return 0; +} + + +/* Set the i8042 match timer (a.k.a. alarm) */ +#define hp_sdc_rtc_set_mt (setto) \ + hp_sdc_rtc_set_i8042timer(setto, HP_SDC_CMD_SET_MT) + +/* Set the i8042 delay timer */ +#define hp_sdc_rtc_set_dt (setto) \ + hp_sdc_rtc_set_i8042timer(setto, HP_SDC_CMD_SET_DT) + +/* Set the i8042 cycle timer (a.k.a. periodic) */ +#define hp_sdc_rtc_set_ct (setto) \ + hp_sdc_rtc_set_i8042timer(setto, HP_SDC_CMD_SET_CT) + +/* Set one of the i8042 3-byte wide timers */ +static int hp_sdc_rtc_set_i8042timer (struct timeval *setto, uint8_t setcmd) +{ + uint32_t tenms; + hp_sdc_transaction t; + uint8_t tseq[6] = { + HP_SDC_ACT_PRECMD | HP_SDC_ACT_DATAOUT, + 0, 3, 0, 0, 0 + }; + + t.endidx = 6; + + if (0xffffff < setto->tv_sec) return -1; + tenms = setto->tv_sec * 100; + if (0xffffff < setto->tv_usec / 10000) return -1; + tenms += setto->tv_usec / 10000; + if (tenms > 0xffffff) return -1; + + tseq[1] = setcmd; + tseq[3] = (uint8_t)(tenms & 0xff); + tseq[4] = (uint8_t)((tenms >> 8) & 0xff); + tseq[5] = (uint8_t)((tenms >> 16) & 0xff); + + t.seq = tseq; + + if (hp_sdc_enqueue_transaction(&t)) { + return -1; + } + return 0; +} +#endif + +static ssize_t hp_sdc_rtc_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) { + ssize_t retval; + + if (count < sizeof(unsigned long)) + return -EINVAL; + + retval = put_user(68, (unsigned long __user *)buf); + return retval; +} + +static unsigned int hp_sdc_rtc_poll(struct file *file, poll_table *wait) +{ + unsigned long l; + + l = 0; + if (l != 0) + return POLLIN | POLLRDNORM; + return 0; +} + +static int hp_sdc_rtc_open(struct inode *inode, struct file *file) +{ + return 0; +} + +static int hp_sdc_rtc_fasync (int fd, struct file *filp, int on) +{ + return fasync_helper (fd, filp, on, &hp_sdc_rtc_async_queue); +} + +static int hp_sdc_rtc_proc_show(struct seq_file *m, void *v) +{ +#define YN(bit) ("no") +#define NY(bit) ("yes") + struct rtc_time tm; + struct timeval tv; + + memset(&tm, 0, sizeof(struct rtc_time)); + + if (hp_sdc_rtc_read_bbrtc(&tm)) { + seq_puts(m, "BBRTC\t\t: READ FAILED!\n"); + } else { + seq_printf(m, + "rtc_time\t: %02d:%02d:%02d\n" + "rtc_date\t: %04d-%02d-%02d\n" + "rtc_epoch\t: %04lu\n", + tm.tm_hour, tm.tm_min, tm.tm_sec, + tm.tm_year + 1900, tm.tm_mon + 1, + tm.tm_mday, epoch); + } + + if (hp_sdc_rtc_read_rt(&tv)) { + seq_puts(m, "i8042 rtc\t: READ FAILED!\n"); + } else { + seq_printf(m, "i8042 rtc\t: %ld.%02d seconds\n", + tv.tv_sec, (int)tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_fhs(&tv)) { + seq_puts(m, "handshake\t: READ FAILED!\n"); + } else { + seq_printf(m, "handshake\t: %ld.%02d seconds\n", + tv.tv_sec, (int)tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_mt(&tv)) { + seq_puts(m, "alarm\t\t: READ FAILED!\n"); + } else { + seq_printf(m, "alarm\t\t: %ld.%02d seconds\n", + tv.tv_sec, (int)tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_dt(&tv)) { + seq_puts(m, "delay\t\t: READ FAILED!\n"); + } else { + seq_printf(m, "delay\t\t: %ld.%02d seconds\n", + tv.tv_sec, (int)tv.tv_usec/1000); + } + + if (hp_sdc_rtc_read_ct(&tv)) { + seq_puts(m, "periodic\t: READ FAILED!\n"); + } else { + seq_printf(m, "periodic\t: %ld.%02d seconds\n", + tv.tv_sec, (int)tv.tv_usec/1000); + } + + seq_printf(m, + "DST_enable\t: %s\n" + "BCD\t\t: %s\n" + "24hr\t\t: %s\n" + "square_wave\t: %s\n" + "alarm_IRQ\t: %s\n" + "update_IRQ\t: %s\n" + "periodic_IRQ\t: %s\n" + "periodic_freq\t: %ld\n" + "batt_status\t: %s\n", + YN(RTC_DST_EN), + NY(RTC_DM_BINARY), + YN(RTC_24H), + YN(RTC_SQWE), + YN(RTC_AIE), + YN(RTC_UIE), + YN(RTC_PIE), + 1UL, + 1 ? "okay" : "dead"); + + return 0; +#undef YN +#undef NY +} + +static int hp_sdc_rtc_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, hp_sdc_rtc_proc_show, NULL); +} + +static const struct file_operations hp_sdc_rtc_proc_fops = { + .open = hp_sdc_rtc_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int hp_sdc_rtc_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ +#if 1 + return -EINVAL; +#else + + struct rtc_time wtime; + struct timeval ttime; + int use_wtime = 0; + + /* This needs major work. */ + + switch (cmd) { + + case RTC_AIE_OFF: /* Mask alarm int. enab. bit */ + case RTC_AIE_ON: /* Allow alarm interrupts. */ + case RTC_PIE_OFF: /* Mask periodic int. enab. bit */ + case RTC_PIE_ON: /* Allow periodic ints */ + case RTC_UIE_ON: /* Allow ints for RTC updates. */ + case RTC_UIE_OFF: /* Allow ints for RTC updates. */ + { + /* We cannot mask individual user timers and we + cannot tell them apart when they occur, so it + would be disingenuous to succeed these IOCTLs */ + return -EINVAL; + } + case RTC_ALM_READ: /* Read the present alarm time */ + { + if (hp_sdc_rtc_read_mt(&ttime)) return -EFAULT; + if (hp_sdc_rtc_read_bbrtc(&wtime)) return -EFAULT; + + wtime.tm_hour = ttime.tv_sec / 3600; ttime.tv_sec %= 3600; + wtime.tm_min = ttime.tv_sec / 60; ttime.tv_sec %= 60; + wtime.tm_sec = ttime.tv_sec; + + break; + } + case RTC_IRQP_READ: /* Read the periodic IRQ rate. */ + { + return put_user(hp_sdc_rtc_freq, (unsigned long *)arg); + } + case RTC_IRQP_SET: /* Set periodic IRQ rate. */ + { + /* + * The max we can do is 100Hz. + */ + + if ((arg < 1) || (arg > 100)) return -EINVAL; + ttime.tv_sec = 0; + ttime.tv_usec = 1000000 / arg; + if (hp_sdc_rtc_set_ct(&ttime)) return -EFAULT; + hp_sdc_rtc_freq = arg; + return 0; + } + case RTC_ALM_SET: /* Store a time into the alarm */ + { + /* + * This expects a struct hp_sdc_rtc_time. Writing 0xff means + * "don't care" or "match all" for PC timers. The HP SDC + * does not support that perk, but it could be emulated fairly + * easily. Only the tm_hour, tm_min and tm_sec are used. + * We could do it with 10ms accuracy with the HP SDC, if the + * rtc interface left us a way to do that. + */ + struct hp_sdc_rtc_time alm_tm; + + if (copy_from_user(&alm_tm, (struct hp_sdc_rtc_time*)arg, + sizeof(struct hp_sdc_rtc_time))) + return -EFAULT; + + if (alm_tm.tm_hour > 23) return -EINVAL; + if (alm_tm.tm_min > 59) return -EINVAL; + if (alm_tm.tm_sec > 59) return -EINVAL; + + ttime.sec = alm_tm.tm_hour * 3600 + + alm_tm.tm_min * 60 + alm_tm.tm_sec; + ttime.usec = 0; + if (hp_sdc_rtc_set_mt(&ttime)) return -EFAULT; + return 0; + } + case RTC_RD_TIME: /* Read the time/date from RTC */ + { + if (hp_sdc_rtc_read_bbrtc(&wtime)) return -EFAULT; + break; + } + case RTC_SET_TIME: /* Set the RTC */ + { + struct rtc_time hp_sdc_rtc_tm; + unsigned char mon, day, hrs, min, sec, leap_yr; + unsigned int yrs; + + if (!capable(CAP_SYS_TIME)) + return -EACCES; + if (copy_from_user(&hp_sdc_rtc_tm, (struct rtc_time *)arg, + sizeof(struct rtc_time))) + return -EFAULT; + + yrs = hp_sdc_rtc_tm.tm_year + 1900; + mon = hp_sdc_rtc_tm.tm_mon + 1; /* tm_mon starts at zero */ + day = hp_sdc_rtc_tm.tm_mday; + hrs = hp_sdc_rtc_tm.tm_hour; + min = hp_sdc_rtc_tm.tm_min; + sec = hp_sdc_rtc_tm.tm_sec; + + if (yrs < 1970) + return -EINVAL; + + leap_yr = ((!(yrs % 4) && (yrs % 100)) || !(yrs % 400)); + + if ((mon > 12) || (day == 0)) + return -EINVAL; + if (day > (days_in_mo[mon] + ((mon == 2) && leap_yr))) + return -EINVAL; + if ((hrs >= 24) || (min >= 60) || (sec >= 60)) + return -EINVAL; + + if ((yrs -= eH) > 255) /* They are unsigned */ + return -EINVAL; + + + return 0; + } + case RTC_EPOCH_READ: /* Read the epoch. */ + { + return put_user (epoch, (unsigned long *)arg); + } + case RTC_EPOCH_SET: /* Set the epoch. */ + { + /* + * There were no RTC clocks before 1900. + */ + if (arg < 1900) + return -EINVAL; + if (!capable(CAP_SYS_TIME)) + return -EACCES; + + epoch = arg; + return 0; + } + default: + return -EINVAL; + } + return copy_to_user((void *)arg, &wtime, sizeof wtime) ? -EFAULT : 0; +#endif +} + +static long hp_sdc_rtc_unlocked_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret; + + mutex_lock(&hp_sdc_rtc_mutex); + ret = hp_sdc_rtc_ioctl(file, cmd, arg); + mutex_unlock(&hp_sdc_rtc_mutex); + + return ret; +} + + +static const struct file_operations hp_sdc_rtc_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = hp_sdc_rtc_read, + .poll = hp_sdc_rtc_poll, + .unlocked_ioctl = hp_sdc_rtc_unlocked_ioctl, + .open = hp_sdc_rtc_open, + .fasync = hp_sdc_rtc_fasync, +}; + +static struct miscdevice hp_sdc_rtc_dev = { + .minor = RTC_MINOR, + .name = "rtc_HIL", + .fops = &hp_sdc_rtc_fops +}; + +static int __init hp_sdc_rtc_init(void) +{ + int ret; + +#ifdef __mc68000__ + if (!MACH_IS_HP300) + return -ENODEV; +#endif + + sema_init(&i8042tregs, 1); + + if ((ret = hp_sdc_request_timer_irq(&hp_sdc_rtc_isr))) + return ret; + if (misc_register(&hp_sdc_rtc_dev) != 0) + printk(KERN_INFO "Could not register misc. dev for i8042 rtc\n"); + + proc_create("driver/rtc", 0, NULL, &hp_sdc_rtc_proc_fops); + + printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support loaded " + "(RTC v " RTC_VERSION ")\n"); + + return 0; +} + +static void __exit hp_sdc_rtc_exit(void) +{ + remove_proc_entry ("driver/rtc", NULL); + misc_deregister(&hp_sdc_rtc_dev); + hp_sdc_release_timer_irq(hp_sdc_rtc_isr); + printk(KERN_INFO "HP i8042 SDC + MSM-58321 RTC support unloaded\n"); +} + +module_init(hp_sdc_rtc_init); +module_exit(hp_sdc_rtc_exit); diff --git a/drivers/input/misc/ims-pcu.c b/drivers/input/misc/ims-pcu.c new file mode 100644 index 00000000..e204f26b --- /dev/null +++ b/drivers/input/misc/ims-pcu.c @@ -0,0 +1,1901 @@ +/* + * Driver for IMS Passenger Control Unit Devices + * + * Copyright (C) 2013 The IMS Company + * + * 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. + */ + +#include <linux/completion.h> +#include <linux/device.h> +#include <linux/firmware.h> +#include <linux/ihex.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/usb/input.h> +#include <linux/usb/cdc.h> +#include <asm/unaligned.h> + +#define IMS_PCU_KEYMAP_LEN 32 + +struct ims_pcu_buttons { + struct input_dev *input; + char name[32]; + char phys[32]; + unsigned short keymap[IMS_PCU_KEYMAP_LEN]; +}; + +struct ims_pcu_gamepad { + struct input_dev *input; + char name[32]; + char phys[32]; +}; + +struct ims_pcu_backlight { + struct led_classdev cdev; + struct work_struct work; + enum led_brightness desired_brightness; + char name[32]; +}; + +#define IMS_PCU_PART_NUMBER_LEN 15 +#define IMS_PCU_SERIAL_NUMBER_LEN 8 +#define IMS_PCU_DOM_LEN 8 +#define IMS_PCU_FW_VERSION_LEN (9 + 1) +#define IMS_PCU_BL_VERSION_LEN (9 + 1) +#define IMS_PCU_BL_RESET_REASON_LEN (2 + 1) + +#define IMS_PCU_BUF_SIZE 128 + +struct ims_pcu { + struct usb_device *udev; + struct device *dev; /* control interface's device, used for logging */ + + unsigned int device_no; + + bool bootloader_mode; + + char part_number[IMS_PCU_PART_NUMBER_LEN]; + char serial_number[IMS_PCU_SERIAL_NUMBER_LEN]; + char date_of_manufacturing[IMS_PCU_DOM_LEN]; + char fw_version[IMS_PCU_FW_VERSION_LEN]; + char bl_version[IMS_PCU_BL_VERSION_LEN]; + char reset_reason[IMS_PCU_BL_RESET_REASON_LEN]; + int update_firmware_status; + + struct usb_interface *ctrl_intf; + + struct usb_endpoint_descriptor *ep_ctrl; + struct urb *urb_ctrl; + u8 *urb_ctrl_buf; + dma_addr_t ctrl_dma; + size_t max_ctrl_size; + + struct usb_interface *data_intf; + + struct usb_endpoint_descriptor *ep_in; + struct urb *urb_in; + u8 *urb_in_buf; + dma_addr_t read_dma; + size_t max_in_size; + + struct usb_endpoint_descriptor *ep_out; + u8 *urb_out_buf; + size_t max_out_size; + + u8 read_buf[IMS_PCU_BUF_SIZE]; + u8 read_pos; + u8 check_sum; + bool have_stx; + bool have_dle; + + u8 cmd_buf[IMS_PCU_BUF_SIZE]; + u8 ack_id; + u8 expected_response; + u8 cmd_buf_len; + struct completion cmd_done; + struct mutex cmd_mutex; + + u32 fw_start_addr; + u32 fw_end_addr; + struct completion async_firmware_done; + + struct ims_pcu_buttons buttons; + struct ims_pcu_gamepad *gamepad; + struct ims_pcu_backlight backlight; + + bool setup_complete; /* Input and LED devices have been created */ +}; + + +/********************************************************************* + * Buttons Input device support * + *********************************************************************/ + +static const unsigned short ims_pcu_keymap_1[] = { + [1] = KEY_ATTENDANT_OFF, + [2] = KEY_ATTENDANT_ON, + [3] = KEY_LIGHTS_TOGGLE, + [4] = KEY_VOLUMEUP, + [5] = KEY_VOLUMEDOWN, + [6] = KEY_INFO, +}; + +static const unsigned short ims_pcu_keymap_2[] = { + [4] = KEY_VOLUMEUP, + [5] = KEY_VOLUMEDOWN, + [6] = KEY_INFO, +}; + +static const unsigned short ims_pcu_keymap_3[] = { + [1] = KEY_HOMEPAGE, + [2] = KEY_ATTENDANT_TOGGLE, + [3] = KEY_LIGHTS_TOGGLE, + [4] = KEY_VOLUMEUP, + [5] = KEY_VOLUMEDOWN, + [6] = KEY_DISPLAYTOGGLE, + [18] = KEY_PLAYPAUSE, +}; + +static const unsigned short ims_pcu_keymap_4[] = { + [1] = KEY_ATTENDANT_OFF, + [2] = KEY_ATTENDANT_ON, + [3] = KEY_LIGHTS_TOGGLE, + [4] = KEY_VOLUMEUP, + [5] = KEY_VOLUMEDOWN, + [6] = KEY_INFO, + [18] = KEY_PLAYPAUSE, +}; + +static const unsigned short ims_pcu_keymap_5[] = { + [1] = KEY_ATTENDANT_OFF, + [2] = KEY_ATTENDANT_ON, + [3] = KEY_LIGHTS_TOGGLE, +}; + +struct ims_pcu_device_info { + const unsigned short *keymap; + size_t keymap_len; + bool has_gamepad; +}; + +#define IMS_PCU_DEVINFO(_n, _gamepad) \ + [_n] = { \ + .keymap = ims_pcu_keymap_##_n, \ + .keymap_len = ARRAY_SIZE(ims_pcu_keymap_##_n), \ + .has_gamepad = _gamepad, \ + } + +static const struct ims_pcu_device_info ims_pcu_device_info[] = { + IMS_PCU_DEVINFO(1, true), + IMS_PCU_DEVINFO(2, true), + IMS_PCU_DEVINFO(3, true), + IMS_PCU_DEVINFO(4, true), + IMS_PCU_DEVINFO(5, false), +}; + +static void ims_pcu_buttons_report(struct ims_pcu *pcu, u32 data) +{ + struct ims_pcu_buttons *buttons = &pcu->buttons; + struct input_dev *input = buttons->input; + int i; + + for (i = 0; i < 32; i++) { + unsigned short keycode = buttons->keymap[i]; + + if (keycode != KEY_RESERVED) + input_report_key(input, keycode, data & (1UL << i)); + } + + input_sync(input); +} + +static int ims_pcu_setup_buttons(struct ims_pcu *pcu, + const unsigned short *keymap, + size_t keymap_len) +{ + struct ims_pcu_buttons *buttons = &pcu->buttons; + struct input_dev *input; + int i; + int error; + + input = input_allocate_device(); + if (!input) { + dev_err(pcu->dev, + "Not enough memory for input input device\n"); + return -ENOMEM; + } + + snprintf(buttons->name, sizeof(buttons->name), + "IMS PCU#%d Button Interface", pcu->device_no); + + usb_make_path(pcu->udev, buttons->phys, sizeof(buttons->phys)); + strlcat(buttons->phys, "/input0", sizeof(buttons->phys)); + + memcpy(buttons->keymap, keymap, sizeof(*keymap) * keymap_len); + + input->name = buttons->name; + input->phys = buttons->phys; + usb_to_input_id(pcu->udev, &input->id); + input->dev.parent = &pcu->ctrl_intf->dev; + + input->keycode = buttons->keymap; + input->keycodemax = ARRAY_SIZE(buttons->keymap); + input->keycodesize = sizeof(buttons->keymap[0]); + + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < IMS_PCU_KEYMAP_LEN; i++) + __set_bit(buttons->keymap[i], input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + error = input_register_device(input); + if (error) { + dev_err(pcu->dev, + "Failed to register buttons input device: %d\n", + error); + input_free_device(input); + return error; + } + + buttons->input = input; + return 0; +} + +static void ims_pcu_destroy_buttons(struct ims_pcu *pcu) +{ + struct ims_pcu_buttons *buttons = &pcu->buttons; + + input_unregister_device(buttons->input); +} + + +/********************************************************************* + * Gamepad Input device support * + *********************************************************************/ + +static void ims_pcu_gamepad_report(struct ims_pcu *pcu, u32 data) +{ + struct ims_pcu_gamepad *gamepad = pcu->gamepad; + struct input_dev *input = gamepad->input; + int x, y; + + x = !!(data & (1 << 14)) - !!(data & (1 << 13)); + y = !!(data & (1 << 12)) - !!(data & (1 << 11)); + + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + + input_report_key(input, BTN_A, data & (1 << 7)); + input_report_key(input, BTN_B, data & (1 << 8)); + input_report_key(input, BTN_X, data & (1 << 9)); + input_report_key(input, BTN_Y, data & (1 << 10)); + input_report_key(input, BTN_START, data & (1 << 15)); + input_report_key(input, BTN_SELECT, data & (1 << 16)); + + input_sync(input); +} + +static int ims_pcu_setup_gamepad(struct ims_pcu *pcu) +{ + struct ims_pcu_gamepad *gamepad; + struct input_dev *input; + int error; + + gamepad = kzalloc(sizeof(struct ims_pcu_gamepad), GFP_KERNEL); + input = input_allocate_device(); + if (!gamepad || !input) { + dev_err(pcu->dev, + "Not enough memory for gamepad device\n"); + error = -ENOMEM; + goto err_free_mem; + } + + gamepad->input = input; + + snprintf(gamepad->name, sizeof(gamepad->name), + "IMS PCU#%d Gamepad Interface", pcu->device_no); + + usb_make_path(pcu->udev, gamepad->phys, sizeof(gamepad->phys)); + strlcat(gamepad->phys, "/input1", sizeof(gamepad->phys)); + + input->name = gamepad->name; + input->phys = gamepad->phys; + usb_to_input_id(pcu->udev, &input->id); + input->dev.parent = &pcu->ctrl_intf->dev; + + __set_bit(EV_KEY, input->evbit); + __set_bit(BTN_A, input->keybit); + __set_bit(BTN_B, input->keybit); + __set_bit(BTN_X, input->keybit); + __set_bit(BTN_Y, input->keybit); + __set_bit(BTN_START, input->keybit); + __set_bit(BTN_SELECT, input->keybit); + + __set_bit(EV_ABS, input->evbit); + input_set_abs_params(input, ABS_X, -1, 1, 0, 0); + input_set_abs_params(input, ABS_Y, -1, 1, 0, 0); + + error = input_register_device(input); + if (error) { + dev_err(pcu->dev, + "Failed to register gamepad input device: %d\n", + error); + goto err_free_mem; + } + + pcu->gamepad = gamepad; + return 0; + +err_free_mem: + input_free_device(input); + kfree(gamepad); + return -ENOMEM; +} + +static void ims_pcu_destroy_gamepad(struct ims_pcu *pcu) +{ + struct ims_pcu_gamepad *gamepad = pcu->gamepad; + + input_unregister_device(gamepad->input); + kfree(gamepad); +} + + +/********************************************************************* + * PCU Communication protocol handling * + *********************************************************************/ + +#define IMS_PCU_PROTOCOL_STX 0x02 +#define IMS_PCU_PROTOCOL_ETX 0x03 +#define IMS_PCU_PROTOCOL_DLE 0x10 + +/* PCU commands */ +#define IMS_PCU_CMD_STATUS 0xa0 +#define IMS_PCU_CMD_PCU_RESET 0xa1 +#define IMS_PCU_CMD_RESET_REASON 0xa2 +#define IMS_PCU_CMD_SEND_BUTTONS 0xa3 +#define IMS_PCU_CMD_JUMP_TO_BTLDR 0xa4 +#define IMS_PCU_CMD_GET_INFO 0xa5 +#define IMS_PCU_CMD_SET_BRIGHTNESS 0xa6 +#define IMS_PCU_CMD_EEPROM 0xa7 +#define IMS_PCU_CMD_GET_FW_VERSION 0xa8 +#define IMS_PCU_CMD_GET_BL_VERSION 0xa9 +#define IMS_PCU_CMD_SET_INFO 0xab +#define IMS_PCU_CMD_GET_BRIGHTNESS 0xac +#define IMS_PCU_CMD_GET_DEVICE_ID 0xae +#define IMS_PCU_CMD_SPECIAL_INFO 0xb0 +#define IMS_PCU_CMD_BOOTLOADER 0xb1 /* Pass data to bootloader */ + +/* PCU responses */ +#define IMS_PCU_RSP_STATUS 0xc0 +#define IMS_PCU_RSP_PCU_RESET 0 /* Originally 0xc1 */ +#define IMS_PCU_RSP_RESET_REASON 0xc2 +#define IMS_PCU_RSP_SEND_BUTTONS 0xc3 +#define IMS_PCU_RSP_JUMP_TO_BTLDR 0 /* Originally 0xc4 */ +#define IMS_PCU_RSP_GET_INFO 0xc5 +#define IMS_PCU_RSP_SET_BRIGHTNESS 0xc6 +#define IMS_PCU_RSP_EEPROM 0xc7 +#define IMS_PCU_RSP_GET_FW_VERSION 0xc8 +#define IMS_PCU_RSP_GET_BL_VERSION 0xc9 +#define IMS_PCU_RSP_SET_INFO 0xcb +#define IMS_PCU_RSP_GET_BRIGHTNESS 0xcc +#define IMS_PCU_RSP_CMD_INVALID 0xcd +#define IMS_PCU_RSP_GET_DEVICE_ID 0xce +#define IMS_PCU_RSP_SPECIAL_INFO 0xd0 +#define IMS_PCU_RSP_BOOTLOADER 0xd1 /* Bootloader response */ + +#define IMS_PCU_RSP_EVNT_BUTTONS 0xe0 /* Unsolicited, button state */ +#define IMS_PCU_GAMEPAD_MASK 0x0001ff80UL /* Bits 7 through 16 */ + + +#define IMS_PCU_MIN_PACKET_LEN 3 +#define IMS_PCU_DATA_OFFSET 2 + +#define IMS_PCU_CMD_WRITE_TIMEOUT 100 /* msec */ +#define IMS_PCU_CMD_RESPONSE_TIMEOUT 500 /* msec */ + +static void ims_pcu_report_events(struct ims_pcu *pcu) +{ + u32 data = get_unaligned_be32(&pcu->read_buf[3]); + + ims_pcu_buttons_report(pcu, data & ~IMS_PCU_GAMEPAD_MASK); + if (pcu->gamepad) + ims_pcu_gamepad_report(pcu, data); +} + +static void ims_pcu_handle_response(struct ims_pcu *pcu) +{ + switch (pcu->read_buf[0]) { + case IMS_PCU_RSP_EVNT_BUTTONS: + if (likely(pcu->setup_complete)) + ims_pcu_report_events(pcu); + break; + + default: + /* + * See if we got command completion. + * If both the sequence and response code match save + * the data and signal completion. + */ + if (pcu->read_buf[0] == pcu->expected_response && + pcu->read_buf[1] == pcu->ack_id - 1) { + + memcpy(pcu->cmd_buf, pcu->read_buf, pcu->read_pos); + pcu->cmd_buf_len = pcu->read_pos; + complete(&pcu->cmd_done); + } + break; + } +} + +static void ims_pcu_process_data(struct ims_pcu *pcu, struct urb *urb) +{ + int i; + + for (i = 0; i < urb->actual_length; i++) { + u8 data = pcu->urb_in_buf[i]; + + /* Skip everything until we get Start Xmit */ + if (!pcu->have_stx && data != IMS_PCU_PROTOCOL_STX) + continue; + + if (pcu->have_dle) { + pcu->have_dle = false; + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + continue; + } + + switch (data) { + case IMS_PCU_PROTOCOL_STX: + if (pcu->have_stx) + dev_warn(pcu->dev, + "Unexpected STX at byte %d, discarding old data\n", + pcu->read_pos); + pcu->have_stx = true; + pcu->have_dle = false; + pcu->read_pos = 0; + pcu->check_sum = 0; + break; + + case IMS_PCU_PROTOCOL_DLE: + pcu->have_dle = true; + break; + + case IMS_PCU_PROTOCOL_ETX: + if (pcu->read_pos < IMS_PCU_MIN_PACKET_LEN) { + dev_warn(pcu->dev, + "Short packet received (%d bytes), ignoring\n", + pcu->read_pos); + } else if (pcu->check_sum != 0) { + dev_warn(pcu->dev, + "Invalid checksum in packet (%d bytes), ignoring\n", + pcu->read_pos); + } else { + ims_pcu_handle_response(pcu); + } + + pcu->have_stx = false; + pcu->have_dle = false; + pcu->read_pos = 0; + break; + + default: + pcu->read_buf[pcu->read_pos++] = data; + pcu->check_sum += data; + break; + } + } +} + +static bool ims_pcu_byte_needs_escape(u8 byte) +{ + return byte == IMS_PCU_PROTOCOL_STX || + byte == IMS_PCU_PROTOCOL_ETX || + byte == IMS_PCU_PROTOCOL_DLE; +} + +static int ims_pcu_send_cmd_chunk(struct ims_pcu *pcu, + u8 command, int chunk, int len) +{ + int error; + + error = usb_bulk_msg(pcu->udev, + usb_sndbulkpipe(pcu->udev, + pcu->ep_out->bEndpointAddress), + pcu->urb_out_buf, len, + NULL, IMS_PCU_CMD_WRITE_TIMEOUT); + if (error < 0) { + dev_dbg(pcu->dev, + "Sending 0x%02x command failed at chunk %d: %d\n", + command, chunk, error); + return error; + } + + return 0; +} + +static int ims_pcu_send_command(struct ims_pcu *pcu, + u8 command, const u8 *data, int len) +{ + int count = 0; + int chunk = 0; + int delta; + int i; + int error; + u8 csum = 0; + u8 ack_id; + + pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_STX; + + /* We know the command need not be escaped */ + pcu->urb_out_buf[count++] = command; + csum += command; + + ack_id = pcu->ack_id++; + if (ack_id == 0xff) + ack_id = pcu->ack_id++; + + if (ims_pcu_byte_needs_escape(ack_id)) + pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; + + pcu->urb_out_buf[count++] = ack_id; + csum += ack_id; + + for (i = 0; i < len; i++) { + + delta = ims_pcu_byte_needs_escape(data[i]) ? 2 : 1; + if (count + delta >= pcu->max_out_size) { + error = ims_pcu_send_cmd_chunk(pcu, command, + ++chunk, count); + if (error) + return error; + + count = 0; + } + + if (delta == 2) + pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; + + pcu->urb_out_buf[count++] = data[i]; + csum += data[i]; + } + + csum = 1 + ~csum; + + delta = ims_pcu_byte_needs_escape(csum) ? 3 : 2; + if (count + delta >= pcu->max_out_size) { + error = ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); + if (error) + return error; + + count = 0; + } + + if (delta == 3) + pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_DLE; + + pcu->urb_out_buf[count++] = csum; + pcu->urb_out_buf[count++] = IMS_PCU_PROTOCOL_ETX; + + return ims_pcu_send_cmd_chunk(pcu, command, ++chunk, count); +} + +static int __ims_pcu_execute_command(struct ims_pcu *pcu, + u8 command, const void *data, size_t len, + u8 expected_response, int response_time) +{ + int error; + + pcu->expected_response = expected_response; + init_completion(&pcu->cmd_done); + + error = ims_pcu_send_command(pcu, command, data, len); + if (error) + return error; + + if (expected_response && + !wait_for_completion_timeout(&pcu->cmd_done, + msecs_to_jiffies(response_time))) { + dev_dbg(pcu->dev, "Command 0x%02x timed out\n", command); + return -ETIMEDOUT; + } + + return 0; +} + +#define ims_pcu_execute_command(pcu, code, data, len) \ + __ims_pcu_execute_command(pcu, \ + IMS_PCU_CMD_##code, data, len, \ + IMS_PCU_RSP_##code, \ + IMS_PCU_CMD_RESPONSE_TIMEOUT) + +#define ims_pcu_execute_query(pcu, code) \ + ims_pcu_execute_command(pcu, code, NULL, 0) + +/* Bootloader commands */ +#define IMS_PCU_BL_CMD_QUERY_DEVICE 0xa1 +#define IMS_PCU_BL_CMD_UNLOCK_CONFIG 0xa2 +#define IMS_PCU_BL_CMD_ERASE_APP 0xa3 +#define IMS_PCU_BL_CMD_PROGRAM_DEVICE 0xa4 +#define IMS_PCU_BL_CMD_PROGRAM_COMPLETE 0xa5 +#define IMS_PCU_BL_CMD_READ_APP 0xa6 +#define IMS_PCU_BL_CMD_RESET_DEVICE 0xa7 +#define IMS_PCU_BL_CMD_LAUNCH_APP 0xa8 + +/* Bootloader commands */ +#define IMS_PCU_BL_RSP_QUERY_DEVICE 0xc1 +#define IMS_PCU_BL_RSP_UNLOCK_CONFIG 0xc2 +#define IMS_PCU_BL_RSP_ERASE_APP 0xc3 +#define IMS_PCU_BL_RSP_PROGRAM_DEVICE 0xc4 +#define IMS_PCU_BL_RSP_PROGRAM_COMPLETE 0xc5 +#define IMS_PCU_BL_RSP_READ_APP 0xc6 +#define IMS_PCU_BL_RSP_RESET_DEVICE 0 /* originally 0xa7 */ +#define IMS_PCU_BL_RSP_LAUNCH_APP 0 /* originally 0xa8 */ + +#define IMS_PCU_BL_DATA_OFFSET 3 + +static int __ims_pcu_execute_bl_command(struct ims_pcu *pcu, + u8 command, const void *data, size_t len, + u8 expected_response, int response_time) +{ + int error; + + pcu->cmd_buf[0] = command; + if (data) + memcpy(&pcu->cmd_buf[1], data, len); + + error = __ims_pcu_execute_command(pcu, + IMS_PCU_CMD_BOOTLOADER, pcu->cmd_buf, len + 1, + expected_response ? IMS_PCU_RSP_BOOTLOADER : 0, + response_time); + if (error) { + dev_err(pcu->dev, + "Failure when sending 0x%02x command to bootloader, error: %d\n", + pcu->cmd_buf[0], error); + return error; + } + + if (expected_response && pcu->cmd_buf[2] != expected_response) { + dev_err(pcu->dev, + "Unexpected response from bootloader: 0x%02x, wanted 0x%02x\n", + pcu->cmd_buf[2], expected_response); + return -EINVAL; + } + + return 0; +} + +#define ims_pcu_execute_bl_command(pcu, code, data, len, timeout) \ + __ims_pcu_execute_bl_command(pcu, \ + IMS_PCU_BL_CMD_##code, data, len, \ + IMS_PCU_BL_RSP_##code, timeout) \ + +#define IMS_PCU_INFO_PART_OFFSET 2 +#define IMS_PCU_INFO_DOM_OFFSET 17 +#define IMS_PCU_INFO_SERIAL_OFFSET 25 + +#define IMS_PCU_SET_INFO_SIZE 31 + +static int ims_pcu_get_info(struct ims_pcu *pcu) +{ + int error; + + error = ims_pcu_execute_query(pcu, GET_INFO); + if (error) { + dev_err(pcu->dev, + "GET_INFO command failed, error: %d\n", error); + return error; + } + + memcpy(pcu->part_number, + &pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], + sizeof(pcu->part_number)); + memcpy(pcu->date_of_manufacturing, + &pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], + sizeof(pcu->date_of_manufacturing)); + memcpy(pcu->serial_number, + &pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], + sizeof(pcu->serial_number)); + + return 0; +} + +static int ims_pcu_set_info(struct ims_pcu *pcu) +{ + int error; + + memcpy(&pcu->cmd_buf[IMS_PCU_INFO_PART_OFFSET], + pcu->part_number, sizeof(pcu->part_number)); + memcpy(&pcu->cmd_buf[IMS_PCU_INFO_DOM_OFFSET], + pcu->date_of_manufacturing, sizeof(pcu->date_of_manufacturing)); + memcpy(&pcu->cmd_buf[IMS_PCU_INFO_SERIAL_OFFSET], + pcu->serial_number, sizeof(pcu->serial_number)); + + error = ims_pcu_execute_command(pcu, SET_INFO, + &pcu->cmd_buf[IMS_PCU_DATA_OFFSET], + IMS_PCU_SET_INFO_SIZE); + if (error) { + dev_err(pcu->dev, + "Failed to update device information, error: %d\n", + error); + return error; + } + + return 0; +} + +static int ims_pcu_switch_to_bootloader(struct ims_pcu *pcu) +{ + int error; + + /* Execute jump to the bootoloader */ + error = ims_pcu_execute_command(pcu, JUMP_TO_BTLDR, NULL, 0); + if (error) { + dev_err(pcu->dev, + "Failure when sending JUMP TO BOOLTLOADER command, error: %d\n", + error); + return error; + } + + return 0; +} + +/********************************************************************* + * Firmware Update handling * + *********************************************************************/ + +#define IMS_PCU_FIRMWARE_NAME "imspcu.fw" + +struct ims_pcu_flash_fmt { + __le32 addr; + u8 len; + u8 data[]; +}; + +static unsigned int ims_pcu_count_fw_records(const struct firmware *fw) +{ + const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; + unsigned int count = 0; + + while (rec) { + count++; + rec = ihex_next_binrec(rec); + } + + return count; +} + +static int ims_pcu_verify_block(struct ims_pcu *pcu, + u32 addr, u8 len, const u8 *data) +{ + struct ims_pcu_flash_fmt *fragment; + int error; + + fragment = (void *)&pcu->cmd_buf[1]; + put_unaligned_le32(addr, &fragment->addr); + fragment->len = len; + + error = ims_pcu_execute_bl_command(pcu, READ_APP, NULL, 5, + IMS_PCU_CMD_RESPONSE_TIMEOUT); + if (error) { + dev_err(pcu->dev, + "Failed to retrieve block at 0x%08x, len %d, error: %d\n", + addr, len, error); + return error; + } + + fragment = (void *)&pcu->cmd_buf[IMS_PCU_BL_DATA_OFFSET]; + if (get_unaligned_le32(&fragment->addr) != addr || + fragment->len != len) { + dev_err(pcu->dev, + "Wrong block when retrieving 0x%08x (0x%08x), len %d (%d)\n", + addr, get_unaligned_le32(&fragment->addr), + len, fragment->len); + return -EINVAL; + } + + if (memcmp(fragment->data, data, len)) { + dev_err(pcu->dev, + "Mismatch in block at 0x%08x, len %d\n", + addr, len); + return -EINVAL; + } + + return 0; +} + +static int ims_pcu_flash_firmware(struct ims_pcu *pcu, + const struct firmware *fw, + unsigned int n_fw_records) +{ + const struct ihex_binrec *rec = (const struct ihex_binrec *)fw->data; + struct ims_pcu_flash_fmt *fragment; + unsigned int count = 0; + u32 addr; + u8 len; + int error; + + error = ims_pcu_execute_bl_command(pcu, ERASE_APP, NULL, 0, 2000); + if (error) { + dev_err(pcu->dev, + "Failed to erase application image, error: %d\n", + error); + return error; + } + + while (rec) { + /* + * The firmware format is messed up for some reason. + * The address twice that of what is needed for some + * reason and we end up overwriting half of the data + * with the next record. + */ + addr = be32_to_cpu(rec->addr) / 2; + len = be16_to_cpu(rec->len); + + fragment = (void *)&pcu->cmd_buf[1]; + put_unaligned_le32(addr, &fragment->addr); + fragment->len = len; + memcpy(fragment->data, rec->data, len); + + error = ims_pcu_execute_bl_command(pcu, PROGRAM_DEVICE, + NULL, len + 5, + IMS_PCU_CMD_RESPONSE_TIMEOUT); + if (error) { + dev_err(pcu->dev, + "Failed to write block at 0x%08x, len %d, error: %d\n", + addr, len, error); + return error; + } + + if (addr >= pcu->fw_start_addr && addr < pcu->fw_end_addr) { + error = ims_pcu_verify_block(pcu, addr, len, rec->data); + if (error) + return error; + } + + count++; + pcu->update_firmware_status = (count * 100) / n_fw_records; + + rec = ihex_next_binrec(rec); + } + + error = ims_pcu_execute_bl_command(pcu, PROGRAM_COMPLETE, + NULL, 0, 2000); + if (error) + dev_err(pcu->dev, + "Failed to send PROGRAM_COMPLETE, error: %d\n", + error); + + return 0; +} + +static int ims_pcu_handle_firmware_update(struct ims_pcu *pcu, + const struct firmware *fw) +{ + unsigned int n_fw_records; + int retval; + + dev_info(pcu->dev, "Updating firmware %s, size: %zu\n", + IMS_PCU_FIRMWARE_NAME, fw->size); + + n_fw_records = ims_pcu_count_fw_records(fw); + + retval = ims_pcu_flash_firmware(pcu, fw, n_fw_records); + if (retval) + goto out; + + retval = ims_pcu_execute_bl_command(pcu, LAUNCH_APP, NULL, 0, 0); + if (retval) + dev_err(pcu->dev, + "Failed to start application image, error: %d\n", + retval); + +out: + pcu->update_firmware_status = retval; + sysfs_notify(&pcu->dev->kobj, NULL, "update_firmware_status"); + return retval; +} + +static void ims_pcu_process_async_firmware(const struct firmware *fw, + void *context) +{ + struct ims_pcu *pcu = context; + int error; + + if (!fw) { + dev_err(pcu->dev, "Failed to get firmware %s\n", + IMS_PCU_FIRMWARE_NAME); + goto out; + } + + error = ihex_validate_fw(fw); + if (error) { + dev_err(pcu->dev, "Firmware %s is invalid\n", + IMS_PCU_FIRMWARE_NAME); + goto out; + } + + mutex_lock(&pcu->cmd_mutex); + ims_pcu_handle_firmware_update(pcu, fw); + mutex_unlock(&pcu->cmd_mutex); + + release_firmware(fw); + +out: + complete(&pcu->async_firmware_done); +} + +/********************************************************************* + * Backlight LED device support * + *********************************************************************/ + +#define IMS_PCU_MAX_BRIGHTNESS 31998 + +static void ims_pcu_backlight_work(struct work_struct *work) +{ + struct ims_pcu_backlight *backlight = + container_of(work, struct ims_pcu_backlight, work); + struct ims_pcu *pcu = + container_of(backlight, struct ims_pcu, backlight); + int desired_brightness = backlight->desired_brightness; + __le16 br_val = cpu_to_le16(desired_brightness); + int error; + + mutex_lock(&pcu->cmd_mutex); + + error = ims_pcu_execute_command(pcu, SET_BRIGHTNESS, + &br_val, sizeof(br_val)); + if (error && error != -ENODEV) + dev_warn(pcu->dev, + "Failed to set desired brightness %u, error: %d\n", + desired_brightness, error); + + mutex_unlock(&pcu->cmd_mutex); +} + +static void ims_pcu_backlight_set_brightness(struct led_classdev *cdev, + enum led_brightness value) +{ + struct ims_pcu_backlight *backlight = + container_of(cdev, struct ims_pcu_backlight, cdev); + + backlight->desired_brightness = value; + schedule_work(&backlight->work); +} + +static enum led_brightness +ims_pcu_backlight_get_brightness(struct led_classdev *cdev) +{ + struct ims_pcu_backlight *backlight = + container_of(cdev, struct ims_pcu_backlight, cdev); + struct ims_pcu *pcu = + container_of(backlight, struct ims_pcu, backlight); + int brightness; + int error; + + mutex_lock(&pcu->cmd_mutex); + + error = ims_pcu_execute_query(pcu, GET_BRIGHTNESS); + if (error) { + dev_warn(pcu->dev, + "Failed to get current brightness, error: %d\n", + error); + /* Assume the LED is OFF */ + brightness = LED_OFF; + } else { + brightness = + get_unaligned_le16(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); + } + + mutex_unlock(&pcu->cmd_mutex); + + return brightness; +} + +static int ims_pcu_setup_backlight(struct ims_pcu *pcu) +{ + struct ims_pcu_backlight *backlight = &pcu->backlight; + int error; + + INIT_WORK(&backlight->work, ims_pcu_backlight_work); + snprintf(backlight->name, sizeof(backlight->name), + "pcu%d::kbd_backlight", pcu->device_no); + + backlight->cdev.name = backlight->name; + backlight->cdev.max_brightness = IMS_PCU_MAX_BRIGHTNESS; + backlight->cdev.brightness_get = ims_pcu_backlight_get_brightness; + backlight->cdev.brightness_set = ims_pcu_backlight_set_brightness; + + error = led_classdev_register(pcu->dev, &backlight->cdev); + if (error) { + dev_err(pcu->dev, + "Failed to register backlight LED device, error: %d\n", + error); + return error; + } + + return 0; +} + +static void ims_pcu_destroy_backlight(struct ims_pcu *pcu) +{ + struct ims_pcu_backlight *backlight = &pcu->backlight; + + led_classdev_unregister(&backlight->cdev); + cancel_work_sync(&backlight->work); +} + + +/********************************************************************* + * Sysfs attributes handling * + *********************************************************************/ + +struct ims_pcu_attribute { + struct device_attribute dattr; + size_t field_offset; + int field_length; +}; + +static ssize_t ims_pcu_attribute_show(struct device *dev, + struct device_attribute *dattr, + char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct ims_pcu *pcu = usb_get_intfdata(intf); + struct ims_pcu_attribute *attr = + container_of(dattr, struct ims_pcu_attribute, dattr); + char *field = (char *)pcu + attr->field_offset; + + return scnprintf(buf, PAGE_SIZE, "%.*s\n", attr->field_length, field); +} + +static ssize_t ims_pcu_attribute_store(struct device *dev, + struct device_attribute *dattr, + const char *buf, size_t count) +{ + + struct usb_interface *intf = to_usb_interface(dev); + struct ims_pcu *pcu = usb_get_intfdata(intf); + struct ims_pcu_attribute *attr = + container_of(dattr, struct ims_pcu_attribute, dattr); + char *field = (char *)pcu + attr->field_offset; + size_t data_len; + int error; + + if (count > attr->field_length) + return -EINVAL; + + data_len = strnlen(buf, attr->field_length); + if (data_len > attr->field_length) + return -EINVAL; + + error = mutex_lock_interruptible(&pcu->cmd_mutex); + if (error) + return error; + + memset(field, 0, attr->field_length); + memcpy(field, buf, data_len); + + error = ims_pcu_set_info(pcu); + + /* + * Even if update failed, let's fetch the info again as we just + * clobbered one of the fields. + */ + ims_pcu_get_info(pcu); + + mutex_unlock(&pcu->cmd_mutex); + + return error < 0 ? error : count; +} + +#define IMS_PCU_ATTR(_field, _mode) \ +struct ims_pcu_attribute ims_pcu_attr_##_field = { \ + .dattr = __ATTR(_field, _mode, \ + ims_pcu_attribute_show, \ + ims_pcu_attribute_store), \ + .field_offset = offsetof(struct ims_pcu, _field), \ + .field_length = sizeof(((struct ims_pcu *)NULL)->_field), \ +} + +#define IMS_PCU_RO_ATTR(_field) \ + IMS_PCU_ATTR(_field, S_IRUGO) +#define IMS_PCU_RW_ATTR(_field) \ + IMS_PCU_ATTR(_field, S_IRUGO | S_IWUSR) + +static IMS_PCU_RW_ATTR(part_number); +static IMS_PCU_RW_ATTR(serial_number); +static IMS_PCU_RW_ATTR(date_of_manufacturing); + +static IMS_PCU_RO_ATTR(fw_version); +static IMS_PCU_RO_ATTR(bl_version); +static IMS_PCU_RO_ATTR(reset_reason); + +static ssize_t ims_pcu_reset_device(struct device *dev, + struct device_attribute *dattr, + const char *buf, size_t count) +{ + static const u8 reset_byte = 1; + struct usb_interface *intf = to_usb_interface(dev); + struct ims_pcu *pcu = usb_get_intfdata(intf); + int value; + int error; + + error = kstrtoint(buf, 0, &value); + if (error) + return error; + + if (value != 1) + return -EINVAL; + + dev_info(pcu->dev, "Attempting to reset device\n"); + + error = ims_pcu_execute_command(pcu, PCU_RESET, &reset_byte, 1); + if (error) { + dev_info(pcu->dev, + "Failed to reset device, error: %d\n", + error); + return error; + } + + return count; +} + +static DEVICE_ATTR(reset_device, S_IWUSR, NULL, ims_pcu_reset_device); + +static ssize_t ims_pcu_update_firmware_store(struct device *dev, + struct device_attribute *dattr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct ims_pcu *pcu = usb_get_intfdata(intf); + const struct firmware *fw = NULL; + int value; + int error; + + error = kstrtoint(buf, 0, &value); + if (error) + return error; + + if (value != 1) + return -EINVAL; + + error = mutex_lock_interruptible(&pcu->cmd_mutex); + if (error) + return error; + + error = request_ihex_firmware(&fw, IMS_PCU_FIRMWARE_NAME, pcu->dev); + if (error) { + dev_err(pcu->dev, "Failed to request firmware %s, error: %d\n", + IMS_PCU_FIRMWARE_NAME, error); + goto out; + } + + /* + * If we are already in bootloader mode we can proceed with + * flashing the firmware. + * + * If we are in application mode, then we need to switch into + * bootloader mode, which will cause the device to disconnect + * and reconnect as different device. + */ + if (pcu->bootloader_mode) + error = ims_pcu_handle_firmware_update(pcu, fw); + else + error = ims_pcu_switch_to_bootloader(pcu); + + release_firmware(fw); + +out: + mutex_unlock(&pcu->cmd_mutex); + return error ?: count; +} + +static DEVICE_ATTR(update_firmware, S_IWUSR, + NULL, ims_pcu_update_firmware_store); + +static ssize_t +ims_pcu_update_firmware_status_show(struct device *dev, + struct device_attribute *dattr, + char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct ims_pcu *pcu = usb_get_intfdata(intf); + + return scnprintf(buf, PAGE_SIZE, "%d\n", pcu->update_firmware_status); +} + +static DEVICE_ATTR(update_firmware_status, S_IRUGO, + ims_pcu_update_firmware_status_show, NULL); + +static struct attribute *ims_pcu_attrs[] = { + &ims_pcu_attr_part_number.dattr.attr, + &ims_pcu_attr_serial_number.dattr.attr, + &ims_pcu_attr_date_of_manufacturing.dattr.attr, + &ims_pcu_attr_fw_version.dattr.attr, + &ims_pcu_attr_bl_version.dattr.attr, + &ims_pcu_attr_reset_reason.dattr.attr, + &dev_attr_reset_device.attr, + &dev_attr_update_firmware.attr, + &dev_attr_update_firmware_status.attr, + NULL +}; + +static umode_t ims_pcu_is_attr_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct usb_interface *intf = to_usb_interface(dev); + struct ims_pcu *pcu = usb_get_intfdata(intf); + umode_t mode = attr->mode; + + if (pcu->bootloader_mode) { + if (attr != &dev_attr_update_firmware_status.attr && + attr != &dev_attr_update_firmware.attr && + attr != &dev_attr_reset_device.attr) { + mode = 0; + } + } else { + if (attr == &dev_attr_update_firmware_status.attr) + mode = 0; + } + + return mode; +} + +static struct attribute_group ims_pcu_attr_group = { + .is_visible = ims_pcu_is_attr_visible, + .attrs = ims_pcu_attrs, +}; + +static void ims_pcu_irq(struct urb *urb) +{ + struct ims_pcu *pcu = urb->context; + int retval, status; + + status = urb->status; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(pcu->dev, "%s - urb shutting down with status: %d\n", + __func__, status); + return; + default: + dev_dbg(pcu->dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + dev_dbg(pcu->dev, "%s: received %d: %*ph\n", __func__, + urb->actual_length, urb->actual_length, pcu->urb_in_buf); + + if (urb == pcu->urb_in) + ims_pcu_process_data(pcu, urb); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval && retval != -ENODEV) + dev_err(pcu->dev, "%s - usb_submit_urb failed with result %d\n", + __func__, retval); +} + +static int ims_pcu_buffers_alloc(struct ims_pcu *pcu) +{ + int error; + + pcu->urb_in_buf = usb_alloc_coherent(pcu->udev, pcu->max_in_size, + GFP_KERNEL, &pcu->read_dma); + if (!pcu->urb_in_buf) { + dev_err(pcu->dev, + "Failed to allocate memory for read buffer\n"); + return -ENOMEM; + } + + pcu->urb_in = usb_alloc_urb(0, GFP_KERNEL); + if (!pcu->urb_in) { + dev_err(pcu->dev, "Failed to allocate input URB\n"); + error = -ENOMEM; + goto err_free_urb_in_buf; + } + + pcu->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + pcu->urb_in->transfer_dma = pcu->read_dma; + + usb_fill_bulk_urb(pcu->urb_in, pcu->udev, + usb_rcvbulkpipe(pcu->udev, + pcu->ep_in->bEndpointAddress), + pcu->urb_in_buf, pcu->max_in_size, + ims_pcu_irq, pcu); + + /* + * We are using usb_bulk_msg() for sending so there is no point + * in allocating memory with usb_alloc_coherent(). + */ + pcu->urb_out_buf = kmalloc(pcu->max_out_size, GFP_KERNEL); + if (!pcu->urb_out_buf) { + dev_err(pcu->dev, "Failed to allocate memory for write buffer\n"); + error = -ENOMEM; + goto err_free_in_urb; + } + + pcu->urb_ctrl_buf = usb_alloc_coherent(pcu->udev, pcu->max_ctrl_size, + GFP_KERNEL, &pcu->ctrl_dma); + if (!pcu->urb_ctrl_buf) { + dev_err(pcu->dev, + "Failed to allocate memory for read buffer\n"); + goto err_free_urb_out_buf; + } + + pcu->urb_ctrl = usb_alloc_urb(0, GFP_KERNEL); + if (!pcu->urb_ctrl) { + dev_err(pcu->dev, "Failed to allocate input URB\n"); + error = -ENOMEM; + goto err_free_urb_ctrl_buf; + } + + pcu->urb_ctrl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + pcu->urb_ctrl->transfer_dma = pcu->ctrl_dma; + + usb_fill_int_urb(pcu->urb_ctrl, pcu->udev, + usb_rcvintpipe(pcu->udev, + pcu->ep_ctrl->bEndpointAddress), + pcu->urb_ctrl_buf, pcu->max_ctrl_size, + ims_pcu_irq, pcu, pcu->ep_ctrl->bInterval); + + return 0; + +err_free_urb_ctrl_buf: + usb_free_coherent(pcu->udev, pcu->max_ctrl_size, + pcu->urb_ctrl_buf, pcu->ctrl_dma); +err_free_urb_out_buf: + kfree(pcu->urb_out_buf); +err_free_in_urb: + usb_free_urb(pcu->urb_in); +err_free_urb_in_buf: + usb_free_coherent(pcu->udev, pcu->max_in_size, + pcu->urb_in_buf, pcu->read_dma); + return error; +} + +static void ims_pcu_buffers_free(struct ims_pcu *pcu) +{ + usb_kill_urb(pcu->urb_in); + usb_free_urb(pcu->urb_in); + + usb_free_coherent(pcu->udev, pcu->max_out_size, + pcu->urb_in_buf, pcu->read_dma); + + kfree(pcu->urb_out_buf); + + usb_kill_urb(pcu->urb_ctrl); + usb_free_urb(pcu->urb_ctrl); + + usb_free_coherent(pcu->udev, pcu->max_ctrl_size, + pcu->urb_ctrl_buf, pcu->ctrl_dma); +} + +static const struct usb_cdc_union_desc * +ims_pcu_get_cdc_union_desc(struct usb_interface *intf) +{ + const void *buf = intf->altsetting->extra; + size_t buflen = intf->altsetting->extralen; + struct usb_cdc_union_desc *union_desc; + + if (!buf) { + dev_err(&intf->dev, "Missing descriptor data\n"); + return NULL; + } + + if (!buflen) { + dev_err(&intf->dev, "Zero length descriptor\n"); + return NULL; + } + + while (buflen > 0) { + union_desc = (struct usb_cdc_union_desc *)buf; + + if (union_desc->bDescriptorType == USB_DT_CS_INTERFACE && + union_desc->bDescriptorSubType == USB_CDC_UNION_TYPE) { + dev_dbg(&intf->dev, "Found union header\n"); + return union_desc; + } + + buflen -= union_desc->bLength; + buf += union_desc->bLength; + } + + dev_err(&intf->dev, "Missing CDC union descriptor\n"); + return NULL; +} + +static int ims_pcu_parse_cdc_data(struct usb_interface *intf, struct ims_pcu *pcu) +{ + const struct usb_cdc_union_desc *union_desc; + struct usb_host_interface *alt; + + union_desc = ims_pcu_get_cdc_union_desc(intf); + if (!union_desc) + return -EINVAL; + + pcu->ctrl_intf = usb_ifnum_to_if(pcu->udev, + union_desc->bMasterInterface0); + + alt = pcu->ctrl_intf->cur_altsetting; + pcu->ep_ctrl = &alt->endpoint[0].desc; + pcu->max_ctrl_size = usb_endpoint_maxp(pcu->ep_ctrl); + + pcu->data_intf = usb_ifnum_to_if(pcu->udev, + union_desc->bSlaveInterface0); + + alt = pcu->data_intf->cur_altsetting; + if (alt->desc.bNumEndpoints != 2) { + dev_err(pcu->dev, + "Incorrect number of endpoints on data interface (%d)\n", + alt->desc.bNumEndpoints); + return -EINVAL; + } + + pcu->ep_out = &alt->endpoint[0].desc; + if (!usb_endpoint_is_bulk_out(pcu->ep_out)) { + dev_err(pcu->dev, + "First endpoint on data interface is not BULK OUT\n"); + return -EINVAL; + } + + pcu->max_out_size = usb_endpoint_maxp(pcu->ep_out); + if (pcu->max_out_size < 8) { + dev_err(pcu->dev, + "Max OUT packet size is too small (%zd)\n", + pcu->max_out_size); + return -EINVAL; + } + + pcu->ep_in = &alt->endpoint[1].desc; + if (!usb_endpoint_is_bulk_in(pcu->ep_in)) { + dev_err(pcu->dev, + "Second endpoint on data interface is not BULK IN\n"); + return -EINVAL; + } + + pcu->max_in_size = usb_endpoint_maxp(pcu->ep_in); + if (pcu->max_in_size < 8) { + dev_err(pcu->dev, + "Max IN packet size is too small (%zd)\n", + pcu->max_in_size); + return -EINVAL; + } + + return 0; +} + +static int ims_pcu_start_io(struct ims_pcu *pcu) +{ + int error; + + error = usb_submit_urb(pcu->urb_ctrl, GFP_KERNEL); + if (error) { + dev_err(pcu->dev, + "Failed to start control IO - usb_submit_urb failed with result: %d\n", + error); + return -EIO; + } + + error = usb_submit_urb(pcu->urb_in, GFP_KERNEL); + if (error) { + dev_err(pcu->dev, + "Failed to start IO - usb_submit_urb failed with result: %d\n", + error); + usb_kill_urb(pcu->urb_ctrl); + return -EIO; + } + + return 0; +} + +static void ims_pcu_stop_io(struct ims_pcu *pcu) +{ + usb_kill_urb(pcu->urb_in); + usb_kill_urb(pcu->urb_ctrl); +} + +static int ims_pcu_line_setup(struct ims_pcu *pcu) +{ + struct usb_host_interface *interface = pcu->ctrl_intf->cur_altsetting; + struct usb_cdc_line_coding *line = (void *)pcu->cmd_buf; + int error; + + memset(line, 0, sizeof(*line)); + line->dwDTERate = cpu_to_le32(57600); + line->bDataBits = 8; + + error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), + USB_CDC_REQ_SET_LINE_CODING, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, interface->desc.bInterfaceNumber, + line, sizeof(struct usb_cdc_line_coding), + 5000); + if (error < 0) { + dev_err(pcu->dev, "Failed to set line coding, error: %d\n", + error); + return error; + } + + error = usb_control_msg(pcu->udev, usb_sndctrlpipe(pcu->udev, 0), + USB_CDC_REQ_SET_CONTROL_LINE_STATE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0x03, interface->desc.bInterfaceNumber, + NULL, 0, 5000); + if (error < 0) { + dev_err(pcu->dev, "Failed to set line state, error: %d\n", + error); + return error; + } + + return 0; +} + +static int ims_pcu_get_device_info(struct ims_pcu *pcu) +{ + int error; + + error = ims_pcu_get_info(pcu); + if (error) + return error; + + error = ims_pcu_execute_query(pcu, GET_FW_VERSION); + if (error) { + dev_err(pcu->dev, + "GET_FW_VERSION command failed, error: %d\n", error); + return error; + } + + snprintf(pcu->fw_version, sizeof(pcu->fw_version), + "%02d%02d%02d%02d.%c%c", + pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], + pcu->cmd_buf[6], pcu->cmd_buf[7]); + + error = ims_pcu_execute_query(pcu, GET_BL_VERSION); + if (error) { + dev_err(pcu->dev, + "GET_BL_VERSION command failed, error: %d\n", error); + return error; + } + + snprintf(pcu->bl_version, sizeof(pcu->bl_version), + "%02d%02d%02d%02d.%c%c", + pcu->cmd_buf[2], pcu->cmd_buf[3], pcu->cmd_buf[4], pcu->cmd_buf[5], + pcu->cmd_buf[6], pcu->cmd_buf[7]); + + error = ims_pcu_execute_query(pcu, RESET_REASON); + if (error) { + dev_err(pcu->dev, + "RESET_REASON command failed, error: %d\n", error); + return error; + } + + snprintf(pcu->reset_reason, sizeof(pcu->reset_reason), + "%02x", pcu->cmd_buf[IMS_PCU_DATA_OFFSET]); + + dev_dbg(pcu->dev, + "P/N: %s, MD: %s, S/N: %s, FW: %s, BL: %s, RR: %s\n", + pcu->part_number, + pcu->date_of_manufacturing, + pcu->serial_number, + pcu->fw_version, + pcu->bl_version, + pcu->reset_reason); + + return 0; +} + +static int ims_pcu_identify_type(struct ims_pcu *pcu, u8 *device_id) +{ + int error; + + error = ims_pcu_execute_query(pcu, GET_DEVICE_ID); + if (error) { + dev_err(pcu->dev, + "GET_DEVICE_ID command failed, error: %d\n", error); + return error; + } + + *device_id = pcu->cmd_buf[IMS_PCU_DATA_OFFSET]; + dev_dbg(pcu->dev, "Detected device ID: %d\n", *device_id); + + return 0; +} + +static int ims_pcu_init_application_mode(struct ims_pcu *pcu) +{ + static atomic_t device_no = ATOMIC_INIT(0); + + const struct ims_pcu_device_info *info; + u8 device_id; + int error; + + error = ims_pcu_get_device_info(pcu); + if (error) { + /* Device does not respond to basic queries, hopeless */ + return error; + } + + error = ims_pcu_identify_type(pcu, &device_id); + if (error) { + dev_err(pcu->dev, + "Failed to identify device, error: %d\n", error); + /* + * Do not signal error, but do not create input nor + * backlight devices either, let userspace figure this + * out (flash a new firmware?). + */ + return 0; + } + + if (device_id >= ARRAY_SIZE(ims_pcu_device_info) || + !ims_pcu_device_info[device_id].keymap) { + dev_err(pcu->dev, "Device ID %d is not valid\n", device_id); + /* Same as above, punt to userspace */ + return 0; + } + + /* Device appears to be operable, complete initialization */ + pcu->device_no = atomic_inc_return(&device_no) - 1; + + error = ims_pcu_setup_backlight(pcu); + if (error) + return error; + + info = &ims_pcu_device_info[device_id]; + error = ims_pcu_setup_buttons(pcu, info->keymap, info->keymap_len); + if (error) + goto err_destroy_backlight; + + if (info->has_gamepad) { + error = ims_pcu_setup_gamepad(pcu); + if (error) + goto err_destroy_buttons; + } + + pcu->setup_complete = true; + + return 0; + +err_destroy_backlight: + ims_pcu_destroy_backlight(pcu); +err_destroy_buttons: + ims_pcu_destroy_buttons(pcu); + return error; +} + +static void ims_pcu_destroy_application_mode(struct ims_pcu *pcu) +{ + if (pcu->setup_complete) { + pcu->setup_complete = false; + mb(); /* make sure flag setting is not reordered */ + + if (pcu->gamepad) + ims_pcu_destroy_gamepad(pcu); + ims_pcu_destroy_buttons(pcu); + ims_pcu_destroy_backlight(pcu); + } +} + +static int ims_pcu_init_bootloader_mode(struct ims_pcu *pcu) +{ + int error; + + error = ims_pcu_execute_bl_command(pcu, QUERY_DEVICE, NULL, 0, + IMS_PCU_CMD_RESPONSE_TIMEOUT); + if (error) { + dev_err(pcu->dev, "Bootloader does not respond, aborting\n"); + return error; + } + + pcu->fw_start_addr = + get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 11]); + pcu->fw_end_addr = + get_unaligned_le32(&pcu->cmd_buf[IMS_PCU_DATA_OFFSET + 15]); + + dev_info(pcu->dev, + "Device is in bootloader mode (addr 0x%08x-0x%08x), requesting firmware\n", + pcu->fw_start_addr, pcu->fw_end_addr); + + error = request_firmware_nowait(THIS_MODULE, true, + IMS_PCU_FIRMWARE_NAME, + pcu->dev, GFP_KERNEL, pcu, + ims_pcu_process_async_firmware); + if (error) { + /* This error is not fatal, let userspace have another chance */ + complete(&pcu->async_firmware_done); + } + + return 0; +} + +static void ims_pcu_destroy_bootloader_mode(struct ims_pcu *pcu) +{ + /* Make sure our initial firmware request has completed */ + wait_for_completion(&pcu->async_firmware_done); +} + +#define IMS_PCU_APPLICATION_MODE 0 +#define IMS_PCU_BOOTLOADER_MODE 1 + +static struct usb_driver ims_pcu_driver; + +static int ims_pcu_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct ims_pcu *pcu; + int error; + + pcu = kzalloc(sizeof(struct ims_pcu), GFP_KERNEL); + if (!pcu) + return -ENOMEM; + + pcu->dev = &intf->dev; + pcu->udev = udev; + pcu->bootloader_mode = id->driver_info == IMS_PCU_BOOTLOADER_MODE; + mutex_init(&pcu->cmd_mutex); + init_completion(&pcu->cmd_done); + init_completion(&pcu->async_firmware_done); + + error = ims_pcu_parse_cdc_data(intf, pcu); + if (error) + goto err_free_mem; + + error = usb_driver_claim_interface(&ims_pcu_driver, + pcu->data_intf, pcu); + if (error) { + dev_err(&intf->dev, + "Unable to claim corresponding data interface: %d\n", + error); + goto err_free_mem; + } + + usb_set_intfdata(pcu->ctrl_intf, pcu); + usb_set_intfdata(pcu->data_intf, pcu); + + error = ims_pcu_buffers_alloc(pcu); + if (error) + goto err_unclaim_intf; + + error = ims_pcu_start_io(pcu); + if (error) + goto err_free_buffers; + + error = ims_pcu_line_setup(pcu); + if (error) + goto err_stop_io; + + error = sysfs_create_group(&intf->dev.kobj, &ims_pcu_attr_group); + if (error) + goto err_stop_io; + + error = pcu->bootloader_mode ? + ims_pcu_init_bootloader_mode(pcu) : + ims_pcu_init_application_mode(pcu); + if (error) + goto err_remove_sysfs; + + return 0; + +err_remove_sysfs: + sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); +err_stop_io: + ims_pcu_stop_io(pcu); +err_free_buffers: + ims_pcu_buffers_free(pcu); +err_unclaim_intf: + usb_driver_release_interface(&ims_pcu_driver, pcu->data_intf); +err_free_mem: + kfree(pcu); + return error; +} + +static void ims_pcu_disconnect(struct usb_interface *intf) +{ + struct ims_pcu *pcu = usb_get_intfdata(intf); + struct usb_host_interface *alt = intf->cur_altsetting; + + usb_set_intfdata(intf, NULL); + + /* + * See if we are dealing with control or data interface. The cleanup + * happens when we unbind primary (control) interface. + */ + if (alt->desc.bInterfaceClass != USB_CLASS_COMM) + return; + + sysfs_remove_group(&intf->dev.kobj, &ims_pcu_attr_group); + + ims_pcu_stop_io(pcu); + + if (pcu->bootloader_mode) + ims_pcu_destroy_bootloader_mode(pcu); + else + ims_pcu_destroy_application_mode(pcu); + + ims_pcu_buffers_free(pcu); + kfree(pcu); +} + +#ifdef CONFIG_PM +static int ims_pcu_suspend(struct usb_interface *intf, + pm_message_t message) +{ + struct ims_pcu *pcu = usb_get_intfdata(intf); + struct usb_host_interface *alt = intf->cur_altsetting; + + if (alt->desc.bInterfaceClass == USB_CLASS_COMM) + ims_pcu_stop_io(pcu); + + return 0; +} + +static int ims_pcu_resume(struct usb_interface *intf) +{ + struct ims_pcu *pcu = usb_get_intfdata(intf); + struct usb_host_interface *alt = intf->cur_altsetting; + int retval = 0; + + if (alt->desc.bInterfaceClass == USB_CLASS_COMM) { + retval = ims_pcu_start_io(pcu); + if (retval == 0) + retval = ims_pcu_line_setup(pcu); + } + + return retval; +} +#endif + +static const struct usb_device_id ims_pcu_id_table[] = { + { + USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0082, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_V25TER), + .driver_info = IMS_PCU_APPLICATION_MODE, + }, + { + USB_DEVICE_AND_INTERFACE_INFO(0x04d8, 0x0083, + USB_CLASS_COMM, + USB_CDC_SUBCLASS_ACM, + USB_CDC_ACM_PROTO_AT_V25TER), + .driver_info = IMS_PCU_BOOTLOADER_MODE, + }, + { } +}; + +static struct usb_driver ims_pcu_driver = { + .name = "ims_pcu", + .id_table = ims_pcu_id_table, + .probe = ims_pcu_probe, + .disconnect = ims_pcu_disconnect, +#ifdef CONFIG_PM + .suspend = ims_pcu_suspend, + .resume = ims_pcu_resume, + .reset_resume = ims_pcu_resume, +#endif +}; + +module_usb_driver(ims_pcu_driver); + +MODULE_DESCRIPTION("IMS Passenger Control Unit driver"); +MODULE_AUTHOR("Dmitry Torokhov <dmitry.torokhov@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/ixp4xx-beeper.c b/drivers/input/misc/ixp4xx-beeper.c new file mode 100644 index 00000000..6ab3decc --- /dev/null +++ b/drivers/input/misc/ixp4xx-beeper.c @@ -0,0 +1,172 @@ +/* + * Generic IXP4xx beeper driver + * + * Copyright (C) 2005 Tower Technologies + * + * based on nslu2-io.c + * Copyright (C) 2004 Karen Spearel + * + * Author: Alessandro Zummo <a.zummo@towertech.it> + * Maintainers: http://www.nslu2-linux.org/ + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <mach/hardware.h> + +MODULE_AUTHOR("Alessandro Zummo <a.zummo@towertech.it>"); +MODULE_DESCRIPTION("ixp4xx beeper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ixp4xx-beeper"); + +static DEFINE_SPINLOCK(beep_lock); + +static void ixp4xx_spkr_control(unsigned int pin, unsigned int count) +{ + unsigned long flags; + + spin_lock_irqsave(&beep_lock, flags); + + if (count) { + gpio_line_config(pin, IXP4XX_GPIO_OUT); + gpio_line_set(pin, IXP4XX_GPIO_LOW); + + *IXP4XX_OSRT2 = (count & ~IXP4XX_OST_RELOAD_MASK) | IXP4XX_OST_ENABLE; + } else { + gpio_line_config(pin, IXP4XX_GPIO_IN); + gpio_line_set(pin, IXP4XX_GPIO_HIGH); + + *IXP4XX_OSRT2 = 0; + } + + spin_unlock_irqrestore(&beep_lock, flags); +} + +static int ixp4xx_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int pin = (unsigned int) input_get_drvdata(dev); + unsigned int count = 0; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: + if (value) + value = 1000; + case SND_TONE: + break; + default: + return -1; + } + + if (value > 20 && value < 32767) + count = (IXP4XX_TIMER_FREQ / (value * 4)) - 1; + + ixp4xx_spkr_control(pin, count); + + return 0; +} + +static irqreturn_t ixp4xx_spkr_interrupt(int irq, void *dev_id) +{ + /* clear interrupt */ + *IXP4XX_OSST = IXP4XX_OSST_TIMER_2_PEND; + + /* flip the beeper output */ + *IXP4XX_GPIO_GPOUTR ^= (1 << (unsigned int) dev_id); + + return IRQ_HANDLED; +} + +static int ixp4xx_spkr_probe(struct platform_device *dev) +{ + struct input_dev *input_dev; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_set_drvdata(input_dev, (void *) dev->id); + + input_dev->name = "ixp4xx beeper", + input_dev->phys = "ixp4xx/gpio"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &dev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = ixp4xx_spkr_event; + + err = request_irq(IRQ_IXP4XX_TIMER2, &ixp4xx_spkr_interrupt, + IRQF_NO_SUSPEND, "ixp4xx-beeper", + (void *) dev->id); + if (err) + goto err_free_device; + + err = input_register_device(input_dev); + if (err) + goto err_free_irq; + + platform_set_drvdata(dev, input_dev); + + return 0; + + err_free_irq: + free_irq(IRQ_IXP4XX_TIMER2, dev); + err_free_device: + input_free_device(input_dev); + + return err; +} + +static int ixp4xx_spkr_remove(struct platform_device *dev) +{ + struct input_dev *input_dev = platform_get_drvdata(dev); + unsigned int pin = (unsigned int) input_get_drvdata(input_dev); + + input_unregister_device(input_dev); + platform_set_drvdata(dev, NULL); + + /* turn the speaker off */ + disable_irq(IRQ_IXP4XX_TIMER2); + ixp4xx_spkr_control(pin, 0); + + free_irq(IRQ_IXP4XX_TIMER2, dev); + + return 0; +} + +static void ixp4xx_spkr_shutdown(struct platform_device *dev) +{ + struct input_dev *input_dev = platform_get_drvdata(dev); + unsigned int pin = (unsigned int) input_get_drvdata(input_dev); + + /* turn off the speaker */ + disable_irq(IRQ_IXP4XX_TIMER2); + ixp4xx_spkr_control(pin, 0); +} + +static struct platform_driver ixp4xx_spkr_platform_driver = { + .driver = { + .name = "ixp4xx-beeper", + .owner = THIS_MODULE, + }, + .probe = ixp4xx_spkr_probe, + .remove = ixp4xx_spkr_remove, + .shutdown = ixp4xx_spkr_shutdown, +}; +module_platform_driver(ixp4xx_spkr_platform_driver); + diff --git a/drivers/input/misc/keychord.c b/drivers/input/misc/keychord.c new file mode 100644 index 00000000..a5ea27ad --- /dev/null +++ b/drivers/input/misc/keychord.c @@ -0,0 +1,391 @@ +/* + * drivers/input/misc/keychord.c + * + * Copyright (C) 2008 Google, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * +*/ + +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/keychord.h> +#include <linux/sched.h> + +#define KEYCHORD_NAME "keychord" +#define BUFFER_SIZE 16 + +MODULE_AUTHOR("Mike Lockwood <lockwood@android.com>"); +MODULE_DESCRIPTION("Key chord input driver"); +MODULE_SUPPORTED_DEVICE("keychord"); +MODULE_LICENSE("GPL"); + +#define NEXT_KEYCHORD(kc) ((struct input_keychord *) \ + ((char *)kc + sizeof(struct input_keychord) + \ + kc->count * sizeof(kc->keycodes[0]))) + +struct keychord_device { + struct input_handler input_handler; + int registered; + + /* list of keychords to monitor */ + struct input_keychord *keychords; + int keychord_count; + + /* bitmask of keys contained in our keychords */ + unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; + /* current state of the keys */ + unsigned long keystate[BITS_TO_LONGS(KEY_CNT)]; + /* number of keys that are currently pressed */ + int key_down; + + /* second input_device_id is needed for null termination */ + struct input_device_id device_ids[2]; + + spinlock_t lock; + wait_queue_head_t waitq; + unsigned char head; + unsigned char tail; + __u16 buff[BUFFER_SIZE]; +}; + +static int check_keychord(struct keychord_device *kdev, + struct input_keychord *keychord) +{ + int i; + + if (keychord->count != kdev->key_down) + return 0; + + for (i = 0; i < keychord->count; i++) { + if (!test_bit(keychord->keycodes[i], kdev->keystate)) + return 0; + } + + /* we have a match */ + return 1; +} + +static void keychord_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + struct keychord_device *kdev = handle->private; + struct input_keychord *keychord; + unsigned long flags; + int i, got_chord = 0; + + if (type != EV_KEY || code >= KEY_MAX) + return; + + spin_lock_irqsave(&kdev->lock, flags); + /* do nothing if key state did not change */ + if (!test_bit(code, kdev->keystate) == !value) + goto done; + __change_bit(code, kdev->keystate); + if (value) + kdev->key_down++; + else + kdev->key_down--; + + /* don't notify on key up */ + if (!value) + goto done; + /* ignore this event if it is not one of the keys we are monitoring */ + if (!test_bit(code, kdev->keybit)) + goto done; + + keychord = kdev->keychords; + if (!keychord) + goto done; + + /* check to see if the keyboard state matches any keychords */ + for (i = 0; i < kdev->keychord_count; i++) { + if (check_keychord(kdev, keychord)) { + kdev->buff[kdev->head] = keychord->id; + kdev->head = (kdev->head + 1) % BUFFER_SIZE; + got_chord = 1; + break; + } + /* skip to next keychord */ + keychord = NEXT_KEYCHORD(keychord); + } + +done: + spin_unlock_irqrestore(&kdev->lock, flags); + + if (got_chord) { + pr_info("keychord: got keychord id %d. Any tasks: %d\n", + keychord->id, + !list_empty_careful(&kdev->waitq.task_list)); + wake_up_interruptible(&kdev->waitq); + } +} + +static int keychord_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + int i, ret; + struct input_handle *handle; + struct keychord_device *kdev = + container_of(handler, struct keychord_device, input_handler); + + /* + * ignore this input device if it does not contain any keycodes + * that we are monitoring + */ + for (i = 0; i < KEY_MAX; i++) { + if (test_bit(i, kdev->keybit) && test_bit(i, dev->keybit)) + break; + } + if (i == KEY_MAX) + return -ENODEV; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = KEYCHORD_NAME; + handle->private = kdev; + + ret = input_register_handle(handle); + if (ret) + goto err_input_register_handle; + + ret = input_open_device(handle); + if (ret) + goto err_input_open_device; + + pr_info("keychord: using input dev %s for fevent\n", dev->name); + + return 0; + +err_input_open_device: + input_unregister_handle(handle); +err_input_register_handle: + kfree(handle); + return ret; +} + +static void keychord_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +/* + * keychord_read is used to read keychord events from the driver + */ +static ssize_t keychord_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct keychord_device *kdev = file->private_data; + __u16 id; + int retval; + unsigned long flags; + + if (count < sizeof(id)) + return -EINVAL; + count = sizeof(id); + + if (kdev->head == kdev->tail && (file->f_flags & O_NONBLOCK)) + return -EAGAIN; + + retval = wait_event_interruptible(kdev->waitq, + kdev->head != kdev->tail); + if (retval) + return retval; + + spin_lock_irqsave(&kdev->lock, flags); + /* pop a keychord ID off the queue */ + id = kdev->buff[kdev->tail]; + kdev->tail = (kdev->tail + 1) % BUFFER_SIZE; + spin_unlock_irqrestore(&kdev->lock, flags); + + if (copy_to_user(buffer, &id, count)) + return -EFAULT; + + return count; +} + +/* + * keychord_write is used to configure the driver + */ +static ssize_t keychord_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct keychord_device *kdev = file->private_data; + struct input_keychord *keychords = 0; + struct input_keychord *keychord, *next, *end; + int ret, i, key; + unsigned long flags; + + if (count < sizeof(struct input_keychord)) + return -EINVAL; + keychords = kzalloc(count, GFP_KERNEL); + if (!keychords) + return -ENOMEM; + + /* read list of keychords from userspace */ + if (copy_from_user(keychords, buffer, count)) { + kfree(keychords); + return -EFAULT; + } + + /* unregister handler before changing configuration */ + if (kdev->registered) { + input_unregister_handler(&kdev->input_handler); + kdev->registered = 0; + } + + spin_lock_irqsave(&kdev->lock, flags); + /* clear any existing configuration */ + kfree(kdev->keychords); + kdev->keychords = 0; + kdev->keychord_count = 0; + kdev->key_down = 0; + memset(kdev->keybit, 0, sizeof(kdev->keybit)); + memset(kdev->keystate, 0, sizeof(kdev->keystate)); + kdev->head = kdev->tail = 0; + + keychord = keychords; + end = (struct input_keychord *)((char *)keychord + count); + + while (keychord < end) { + next = NEXT_KEYCHORD(keychord); + if (keychord->count <= 0 || next > end) { + pr_err("keychord: invalid keycode count %d\n", + keychord->count); + goto err_unlock_return; + } + if (keychord->version != KEYCHORD_VERSION) { + pr_err("keychord: unsupported version %d\n", + keychord->version); + goto err_unlock_return; + } + + /* keep track of the keys we are monitoring in keybit */ + for (i = 0; i < keychord->count; i++) { + key = keychord->keycodes[i]; + if (key < 0 || key >= KEY_CNT) { + pr_err("keychord: keycode %d out of range\n", + key); + goto err_unlock_return; + } + __set_bit(key, kdev->keybit); + } + + kdev->keychord_count++; + keychord = next; + } + + kdev->keychords = keychords; + spin_unlock_irqrestore(&kdev->lock, flags); + + ret = input_register_handler(&kdev->input_handler); + if (ret) { + kfree(keychords); + kdev->keychords = 0; + return ret; + } + kdev->registered = 1; + + return count; + +err_unlock_return: + spin_unlock_irqrestore(&kdev->lock, flags); + kfree(keychords); + return -EINVAL; +} + +static unsigned int keychord_poll(struct file *file, poll_table *wait) +{ + struct keychord_device *kdev = file->private_data; + + poll_wait(file, &kdev->waitq, wait); + + if (kdev->head != kdev->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int keychord_open(struct inode *inode, struct file *file) +{ + struct keychord_device *kdev; + + kdev = kzalloc(sizeof(struct keychord_device), GFP_KERNEL); + if (!kdev) + return -ENOMEM; + + spin_lock_init(&kdev->lock); + init_waitqueue_head(&kdev->waitq); + + kdev->input_handler.event = keychord_event; + kdev->input_handler.connect = keychord_connect; + kdev->input_handler.disconnect = keychord_disconnect; + kdev->input_handler.name = KEYCHORD_NAME; + kdev->input_handler.id_table = kdev->device_ids; + + kdev->device_ids[0].flags = INPUT_DEVICE_ID_MATCH_EVBIT; + __set_bit(EV_KEY, kdev->device_ids[0].evbit); + + file->private_data = kdev; + + return 0; +} + +static int keychord_release(struct inode *inode, struct file *file) +{ + struct keychord_device *kdev = file->private_data; + + if (kdev->registered) + input_unregister_handler(&kdev->input_handler); + kfree(kdev); + + return 0; +} + +static const struct file_operations keychord_fops = { + .owner = THIS_MODULE, + .open = keychord_open, + .release = keychord_release, + .read = keychord_read, + .write = keychord_write, + .poll = keychord_poll, +}; + +static struct miscdevice keychord_misc = { + .fops = &keychord_fops, + .name = KEYCHORD_NAME, + .minor = MISC_DYNAMIC_MINOR, +}; + +static int __init keychord_init(void) +{ + return misc_register(&keychord_misc); +} + +static void __exit keychord_exit(void) +{ + misc_deregister(&keychord_misc); +} + +module_init(keychord_init); +module_exit(keychord_exit); diff --git a/drivers/input/misc/keyspan_remote.c b/drivers/input/misc/keyspan_remote.c new file mode 100644 index 00000000..290fa5f9 --- /dev/null +++ b/drivers/input/misc/keyspan_remote.c @@ -0,0 +1,597 @@ +/* + * keyspan_remote: USB driver for the Keyspan DMR + * + * Copyright (C) 2005 Zymeta Corporation - Michael Downey (downey@zymeta.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2. + * + * This driver has been put together with the support of Innosys, Inc. + * and Keyspan, Inc the manufacturers of the Keyspan USB DMR product. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb/input.h> + +#define DRIVER_VERSION "v0.1" +#define DRIVER_AUTHOR "Michael Downey <downey@zymeta.com>" +#define DRIVER_DESC "Driver for the USB Keyspan remote control." +#define DRIVER_LICENSE "GPL" + +/* Parameters that can be passed to the driver. */ +static int debug; +module_param(debug, int, 0444); +MODULE_PARM_DESC(debug, "Enable extra debug messages and information"); + +/* Vendor and product ids */ +#define USB_KEYSPAN_VENDOR_ID 0x06CD +#define USB_KEYSPAN_PRODUCT_UIA11 0x0202 + +/* Defines for converting the data from the remote. */ +#define ZERO 0x18 +#define ZERO_MASK 0x1F /* 5 bits for a 0 */ +#define ONE 0x3C +#define ONE_MASK 0x3F /* 6 bits for a 1 */ +#define SYNC 0x3F80 +#define SYNC_MASK 0x3FFF /* 14 bits for a SYNC sequence */ +#define STOP 0x00 +#define STOP_MASK 0x1F /* 5 bits for the STOP sequence */ +#define GAP 0xFF + +#define RECV_SIZE 8 /* The UIA-11 type have a 8 byte limit. */ + +/* + * Table that maps the 31 possible keycodes to input keys. + * Currently there are 15 and 17 button models so RESERVED codes + * are blank areas in the mapping. + */ +static const unsigned short keyspan_key_table[] = { + KEY_RESERVED, /* 0 is just a place holder. */ + KEY_RESERVED, + KEY_STOP, + KEY_PLAYCD, + KEY_RESERVED, + KEY_PREVIOUSSONG, + KEY_REWIND, + KEY_FORWARD, + KEY_NEXTSONG, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_PAUSE, + KEY_VOLUMEUP, + KEY_RESERVED, + KEY_RESERVED, + KEY_RESERVED, + KEY_VOLUMEDOWN, + KEY_RESERVED, + KEY_UP, + KEY_RESERVED, + KEY_MUTE, + KEY_LEFT, + KEY_ENTER, + KEY_RIGHT, + KEY_RESERVED, + KEY_RESERVED, + KEY_DOWN, + KEY_RESERVED, + KEY_KPASTERISK, + KEY_RESERVED, + KEY_MENU +}; + +/* table of devices that work with this driver */ +static struct usb_device_id keyspan_table[] = { + { USB_DEVICE(USB_KEYSPAN_VENDOR_ID, USB_KEYSPAN_PRODUCT_UIA11) }, + { } /* Terminating entry */ +}; + +/* Structure to store all the real stuff that a remote sends to us. */ +struct keyspan_message { + u16 system; + u8 button; + u8 toggle; +}; + +/* Structure used for all the bit testing magic needed to be done. */ +struct bit_tester { + u32 tester; + int len; + int pos; + int bits_left; + u8 buffer[32]; +}; + +/* Structure to hold all of our driver specific stuff */ +struct usb_keyspan { + char name[128]; + char phys[64]; + unsigned short keymap[ARRAY_SIZE(keyspan_key_table)]; + struct usb_device *udev; + struct input_dev *input; + struct usb_interface *interface; + struct usb_endpoint_descriptor *in_endpoint; + struct urb* irq_urb; + int open; + dma_addr_t in_dma; + unsigned char *in_buffer; + + /* variables used to parse messages from remote. */ + struct bit_tester data; + int stage; + int toggle; +}; + +static struct usb_driver keyspan_driver; + +/* + * Debug routine that prints out what we've received from the remote. + */ +static void keyspan_print(struct usb_keyspan* dev) /*unsigned char* data)*/ +{ + char codes[4 * RECV_SIZE]; + int i; + + for (i = 0; i < RECV_SIZE; i++) + snprintf(codes + i * 3, 4, "%02x ", dev->in_buffer[i]); + + dev_info(&dev->udev->dev, "%s\n", codes); +} + +/* + * Routine that manages the bit_tester structure. It makes sure that there are + * at least bits_needed bits loaded into the tester. + */ +static int keyspan_load_tester(struct usb_keyspan* dev, int bits_needed) +{ + if (dev->data.bits_left >= bits_needed) + return 0; + + /* + * Somehow we've missed the last message. The message will be repeated + * though so it's not too big a deal + */ + if (dev->data.pos >= dev->data.len) { + dev_dbg(&dev->interface->dev, + "%s - Error ran out of data. pos: %d, len: %d\n", + __func__, dev->data.pos, dev->data.len); + return -1; + } + + /* Load as much as we can into the tester. */ + while ((dev->data.bits_left + 7 < (sizeof(dev->data.tester) * 8)) && + (dev->data.pos < dev->data.len)) { + dev->data.tester += (dev->data.buffer[dev->data.pos++] << dev->data.bits_left); + dev->data.bits_left += 8; + } + + return 0; +} + +static void keyspan_report_button(struct usb_keyspan *remote, int button, int press) +{ + struct input_dev *input = remote->input; + + input_event(input, EV_MSC, MSC_SCAN, button); + input_report_key(input, remote->keymap[button], press); + input_sync(input); +} + +/* + * Routine that handles all the logic needed to parse out the message from the remote. + */ +static void keyspan_check_data(struct usb_keyspan *remote) +{ + int i; + int found = 0; + struct keyspan_message message; + + switch(remote->stage) { + case 0: + /* + * In stage 0 we want to find the start of a message. The remote sends a 0xFF as filler. + * So the first byte that isn't a FF should be the start of a new message. + */ + for (i = 0; i < RECV_SIZE && remote->in_buffer[i] == GAP; ++i); + + if (i < RECV_SIZE) { + memcpy(remote->data.buffer, remote->in_buffer, RECV_SIZE); + remote->data.len = RECV_SIZE; + remote->data.pos = 0; + remote->data.tester = 0; + remote->data.bits_left = 0; + remote->stage = 1; + } + break; + + case 1: + /* + * Stage 1 we should have 16 bytes and should be able to detect a + * SYNC. The SYNC is 14 bits, 7 0's and then 7 1's. + */ + memcpy(remote->data.buffer + remote->data.len, remote->in_buffer, RECV_SIZE); + remote->data.len += RECV_SIZE; + + found = 0; + while ((remote->data.bits_left >= 14 || remote->data.pos < remote->data.len) && !found) { + for (i = 0; i < 8; ++i) { + if (keyspan_load_tester(remote, 14) != 0) { + remote->stage = 0; + return; + } + + if ((remote->data.tester & SYNC_MASK) == SYNC) { + remote->data.tester = remote->data.tester >> 14; + remote->data.bits_left -= 14; + found = 1; + break; + } else { + remote->data.tester = remote->data.tester >> 1; + --remote->data.bits_left; + } + } + } + + if (!found) { + remote->stage = 0; + remote->data.len = 0; + } else { + remote->stage = 2; + } + break; + + case 2: + /* + * Stage 2 we should have 24 bytes which will be enough for a full + * message. We need to parse out the system code, button code, + * toggle code, and stop. + */ + memcpy(remote->data.buffer + remote->data.len, remote->in_buffer, RECV_SIZE); + remote->data.len += RECV_SIZE; + + message.system = 0; + for (i = 0; i < 9; i++) { + keyspan_load_tester(remote, 6); + + if ((remote->data.tester & ZERO_MASK) == ZERO) { + message.system = message.system << 1; + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else if ((remote->data.tester & ONE_MASK) == ONE) { + message.system = (message.system << 1) + 1; + remote->data.tester = remote->data.tester >> 6; + remote->data.bits_left -= 6; + } else { + dev_err(&remote->interface->dev, + "%s - Unknown sequence found in system data.\n", + __func__); + remote->stage = 0; + return; + } + } + + message.button = 0; + for (i = 0; i < 5; i++) { + keyspan_load_tester(remote, 6); + + if ((remote->data.tester & ZERO_MASK) == ZERO) { + message.button = message.button << 1; + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else if ((remote->data.tester & ONE_MASK) == ONE) { + message.button = (message.button << 1) + 1; + remote->data.tester = remote->data.tester >> 6; + remote->data.bits_left -= 6; + } else { + dev_err(&remote->interface->dev, + "%s - Unknown sequence found in button data.\n", + __func__); + remote->stage = 0; + return; + } + } + + keyspan_load_tester(remote, 6); + if ((remote->data.tester & ZERO_MASK) == ZERO) { + message.toggle = 0; + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else if ((remote->data.tester & ONE_MASK) == ONE) { + message.toggle = 1; + remote->data.tester = remote->data.tester >> 6; + remote->data.bits_left -= 6; + } else { + dev_err(&remote->interface->dev, + "%s - Error in message, invalid toggle.\n", + __func__); + remote->stage = 0; + return; + } + + keyspan_load_tester(remote, 5); + if ((remote->data.tester & STOP_MASK) == STOP) { + remote->data.tester = remote->data.tester >> 5; + remote->data.bits_left -= 5; + } else { + dev_err(&remote->interface->dev, + "Bad message received, no stop bit found.\n"); + } + + dev_dbg(&remote->interface->dev, + "%s found valid message: system: %d, button: %d, toggle: %d\n", + __func__, message.system, message.button, message.toggle); + + if (message.toggle != remote->toggle) { + keyspan_report_button(remote, message.button, 1); + keyspan_report_button(remote, message.button, 0); + remote->toggle = message.toggle; + } + + remote->stage = 0; + break; + } +} + +/* + * Routine for sending all the initialization messages to the remote. + */ +static int keyspan_setup(struct usb_device* dev) +{ + int retval = 0; + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x11, 0x40, 0x5601, 0x0, NULL, 0, 0); + if (retval) { + dev_dbg(&dev->dev, "%s - failed to set bit rate due to error: %d\n", + __func__, retval); + return(retval); + } + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x44, 0x40, 0x0, 0x0, NULL, 0, 0); + if (retval) { + dev_dbg(&dev->dev, "%s - failed to set resume sensitivity due to error: %d\n", + __func__, retval); + return(retval); + } + + retval = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x22, 0x40, 0x0, 0x0, NULL, 0, 0); + if (retval) { + dev_dbg(&dev->dev, "%s - failed to turn receive on due to error: %d\n", + __func__, retval); + return(retval); + } + + dev_dbg(&dev->dev, "%s - Setup complete.\n", __func__); + return(retval); +} + +/* + * Routine used to handle a new message that has come in. + */ +static void keyspan_irq_recv(struct urb *urb) +{ + struct usb_keyspan *dev = urb->context; + int retval; + + /* Check our status in case we need to bail out early. */ + switch (urb->status) { + case 0: + break; + + /* Device went away so don't keep trying to read from it. */ + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + + default: + goto resubmit; + break; + } + + if (debug) + keyspan_print(dev); + + keyspan_check_data(dev); + +resubmit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->interface->dev, + "%s - usb_submit_urb failed with result: %d\n", + __func__, retval); +} + +static int keyspan_open(struct input_dev *dev) +{ + struct usb_keyspan *remote = input_get_drvdata(dev); + + remote->irq_urb->dev = remote->udev; + if (usb_submit_urb(remote->irq_urb, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void keyspan_close(struct input_dev *dev) +{ + struct usb_keyspan *remote = input_get_drvdata(dev); + + usb_kill_urb(remote->irq_urb); +} + +static struct usb_endpoint_descriptor *keyspan_get_in_endpoint(struct usb_host_interface *iface) +{ + + struct usb_endpoint_descriptor *endpoint; + int i; + + for (i = 0; i < iface->desc.bNumEndpoints; ++i) { + endpoint = &iface->endpoint[i].desc; + + if (usb_endpoint_is_int_in(endpoint)) { + /* we found our interrupt in endpoint */ + return endpoint; + } + } + + return NULL; +} + +/* + * Routine that sets up the driver to handle a specific USB device detected on the bus. + */ +static int keyspan_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_endpoint_descriptor *endpoint; + struct usb_keyspan *remote; + struct input_dev *input_dev; + int i, error; + + endpoint = keyspan_get_in_endpoint(interface->cur_altsetting); + if (!endpoint) + return -ENODEV; + + remote = kzalloc(sizeof(*remote), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!remote || !input_dev) { + error = -ENOMEM; + goto fail1; + } + + remote->udev = udev; + remote->input = input_dev; + remote->interface = interface; + remote->in_endpoint = endpoint; + remote->toggle = -1; /* Set to -1 so we will always not match the toggle from the first remote message. */ + + remote->in_buffer = usb_alloc_coherent(udev, RECV_SIZE, GFP_ATOMIC, &remote->in_dma); + if (!remote->in_buffer) { + error = -ENOMEM; + goto fail1; + } + + remote->irq_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!remote->irq_urb) { + error = -ENOMEM; + goto fail2; + } + + error = keyspan_setup(udev); + if (error) { + error = -ENODEV; + goto fail3; + } + + if (udev->manufacturer) + strlcpy(remote->name, udev->manufacturer, sizeof(remote->name)); + + if (udev->product) { + if (udev->manufacturer) + strlcat(remote->name, " ", sizeof(remote->name)); + strlcat(remote->name, udev->product, sizeof(remote->name)); + } + + if (!strlen(remote->name)) + snprintf(remote->name, sizeof(remote->name), + "USB Keyspan Remote %04x:%04x", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + + usb_make_path(udev, remote->phys, sizeof(remote->phys)); + strlcat(remote->phys, "/input0", sizeof(remote->phys)); + memcpy(remote->keymap, keyspan_key_table, sizeof(remote->keymap)); + + input_dev->name = remote->name; + input_dev->phys = remote->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &interface->dev; + input_dev->keycode = remote->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(remote->keymap); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input_dev->evbit); + for (i = 0; i < ARRAY_SIZE(keyspan_key_table); i++) + __set_bit(keyspan_key_table[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_drvdata(input_dev, remote); + + input_dev->open = keyspan_open; + input_dev->close = keyspan_close; + + /* + * Initialize the URB to access the device. + * The urb gets sent to the device in keyspan_open() + */ + usb_fill_int_urb(remote->irq_urb, + remote->udev, + usb_rcvintpipe(remote->udev, endpoint->bEndpointAddress), + remote->in_buffer, RECV_SIZE, keyspan_irq_recv, remote, + endpoint->bInterval); + remote->irq_urb->transfer_dma = remote->in_dma; + remote->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* we can register the device now, as it is ready */ + error = input_register_device(remote->input); + if (error) + goto fail3; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, remote); + + return 0; + + fail3: usb_free_urb(remote->irq_urb); + fail2: usb_free_coherent(udev, RECV_SIZE, remote->in_buffer, remote->in_dma); + fail1: kfree(remote); + input_free_device(input_dev); + + return error; +} + +/* + * Routine called when a device is disconnected from the USB. + */ +static void keyspan_disconnect(struct usb_interface *interface) +{ + struct usb_keyspan *remote; + + remote = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + if (remote) { /* We have a valid driver structure so clean up everything we allocated. */ + input_unregister_device(remote->input); + usb_kill_urb(remote->irq_urb); + usb_free_urb(remote->irq_urb); + usb_free_coherent(remote->udev, RECV_SIZE, remote->in_buffer, remote->in_dma); + kfree(remote); + } +} + +/* + * Standard driver set up sections + */ +static struct usb_driver keyspan_driver = +{ + .name = "keyspan_remote", + .probe = keyspan_probe, + .disconnect = keyspan_disconnect, + .id_table = keyspan_table +}; + +module_usb_driver(keyspan_driver); + +MODULE_DEVICE_TABLE(usb, keyspan_table); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); diff --git a/drivers/input/misc/kxtj9.c b/drivers/input/misc/kxtj9.c new file mode 100644 index 00000000..a993b67a --- /dev/null +++ b/drivers/input/misc/kxtj9.c @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2011 Kionix, Inc. + * Written by Chris Hudson <chudson@kionix.com> + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307, USA + */ + +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/input/kxtj9.h> +#include <linux/input-polldev.h> + +#define NAME "kxtj9" +#define G_MAX 8000 +/* OUTPUT REGISTERS */ +#define XOUT_L 0x06 +#define WHO_AM_I 0x0F +/* CONTROL REGISTERS */ +#define INT_REL 0x1A +#define CTRL_REG1 0x1B +#define INT_CTRL1 0x1E +#define DATA_CTRL 0x21 +/* CONTROL REGISTER 1 BITS */ +#define PC1_OFF 0x7F +#define PC1_ON (1 << 7) +/* Data ready funtion enable bit: set during probe if using irq mode */ +#define DRDYE (1 << 5) +/* DATA CONTROL REGISTER BITS */ +#define ODR12_5F 0 +#define ODR25F 1 +#define ODR50F 2 +#define ODR100F 3 +#define ODR200F 4 +#define ODR400F 5 +#define ODR800F 6 +/* INTERRUPT CONTROL REGISTER 1 BITS */ +/* Set these during probe if using irq mode */ +#define KXTJ9_IEL (1 << 3) +#define KXTJ9_IEA (1 << 4) +#define KXTJ9_IEN (1 << 5) +/* INPUT_ABS CONSTANTS */ +#define FUZZ 3 +#define FLAT 3 +/* RESUME STATE INDICES */ +#define RES_DATA_CTRL 0 +#define RES_CTRL_REG1 1 +#define RES_INT_CTRL1 2 +#define RESUME_ENTRIES 3 + +/* + * The following table lists the maximum appropriate poll interval for each + * available output data rate. + */ +static const struct { + unsigned int cutoff; + u8 mask; +} kxtj9_odr_table[] = { + { 3, ODR800F }, + { 5, ODR400F }, + { 10, ODR200F }, + { 20, ODR100F }, + { 40, ODR50F }, + { 80, ODR25F }, + { 0, ODR12_5F}, +}; + +struct kxtj9_data { + struct i2c_client *client; + struct kxtj9_platform_data pdata; + struct input_dev *input_dev; +#ifdef CONFIG_INPUT_KXTJ9_POLLED_MODE + struct input_polled_dev *poll_dev; +#endif + unsigned int last_poll_interval; + u8 shift; + u8 ctrl_reg1; + u8 data_ctrl; + u8 int_ctrl; +}; + +static int kxtj9_i2c_read(struct kxtj9_data *tj9, u8 addr, u8 *data, int len) +{ + struct i2c_msg msgs[] = { + { + .addr = tj9->client->addr, + .flags = tj9->client->flags, + .len = 1, + .buf = &addr, + }, + { + .addr = tj9->client->addr, + .flags = tj9->client->flags | I2C_M_RD, + .len = len, + .buf = data, + }, + }; + + return i2c_transfer(tj9->client->adapter, msgs, 2); +} + +static void kxtj9_report_acceleration_data(struct kxtj9_data *tj9) +{ + s16 acc_data[3]; /* Data bytes from hardware xL, xH, yL, yH, zL, zH */ + s16 x, y, z; + int err; + + err = kxtj9_i2c_read(tj9, XOUT_L, (u8 *)acc_data, 6); + if (err < 0) + dev_err(&tj9->client->dev, "accelerometer data read failed\n"); + + x = le16_to_cpu(acc_data[tj9->pdata.axis_map_x]); + y = le16_to_cpu(acc_data[tj9->pdata.axis_map_y]); + z = le16_to_cpu(acc_data[tj9->pdata.axis_map_z]); + + x >>= tj9->shift; + y >>= tj9->shift; + z >>= tj9->shift; + + input_report_abs(tj9->input_dev, ABS_X, tj9->pdata.negate_x ? -x : x); + input_report_abs(tj9->input_dev, ABS_Y, tj9->pdata.negate_y ? -y : y); + input_report_abs(tj9->input_dev, ABS_Z, tj9->pdata.negate_z ? -z : z); + input_sync(tj9->input_dev); +} + +static irqreturn_t kxtj9_isr(int irq, void *dev) +{ + struct kxtj9_data *tj9 = dev; + int err; + + /* data ready is the only possible interrupt type */ + kxtj9_report_acceleration_data(tj9); + + err = i2c_smbus_read_byte_data(tj9->client, INT_REL); + if (err < 0) + dev_err(&tj9->client->dev, + "error clearing interrupt status: %d\n", err); + + return IRQ_HANDLED; +} + +static int kxtj9_update_g_range(struct kxtj9_data *tj9, u8 new_g_range) +{ + switch (new_g_range) { + case KXTJ9_G_2G: + tj9->shift = 4; + break; + case KXTJ9_G_4G: + tj9->shift = 3; + break; + case KXTJ9_G_8G: + tj9->shift = 2; + break; + default: + return -EINVAL; + } + + tj9->ctrl_reg1 &= 0xe7; + tj9->ctrl_reg1 |= new_g_range; + + return 0; +} + +static int kxtj9_update_odr(struct kxtj9_data *tj9, unsigned int poll_interval) +{ + int err; + int i; + + /* Use the lowest ODR that can support the requested poll interval */ + for (i = 0; i < ARRAY_SIZE(kxtj9_odr_table); i++) { + tj9->data_ctrl = kxtj9_odr_table[i].mask; + if (poll_interval < kxtj9_odr_table[i].cutoff) + break; + } + + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, 0); + if (err < 0) + return err; + + err = i2c_smbus_write_byte_data(tj9->client, DATA_CTRL, tj9->data_ctrl); + if (err < 0) + return err; + + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, tj9->ctrl_reg1); + if (err < 0) + return err; + + return 0; +} + +static int kxtj9_device_power_on(struct kxtj9_data *tj9) +{ + if (tj9->pdata.power_on) + return tj9->pdata.power_on(); + + return 0; +} + +static void kxtj9_device_power_off(struct kxtj9_data *tj9) +{ + int err; + + tj9->ctrl_reg1 &= PC1_OFF; + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, tj9->ctrl_reg1); + if (err < 0) + dev_err(&tj9->client->dev, "soft power off failed\n"); + + if (tj9->pdata.power_off) + tj9->pdata.power_off(); +} + +static int kxtj9_enable(struct kxtj9_data *tj9) +{ + int err; + + err = kxtj9_device_power_on(tj9); + if (err < 0) + return err; + + /* ensure that PC1 is cleared before updating control registers */ + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, 0); + if (err < 0) + return err; + + /* only write INT_CTRL_REG1 if in irq mode */ + if (tj9->client->irq) { + err = i2c_smbus_write_byte_data(tj9->client, + INT_CTRL1, tj9->int_ctrl); + if (err < 0) + return err; + } + + err = kxtj9_update_g_range(tj9, tj9->pdata.g_range); + if (err < 0) + return err; + + /* turn on outputs */ + tj9->ctrl_reg1 |= PC1_ON; + err = i2c_smbus_write_byte_data(tj9->client, CTRL_REG1, tj9->ctrl_reg1); + if (err < 0) + return err; + + err = kxtj9_update_odr(tj9, tj9->last_poll_interval); + if (err < 0) + return err; + + /* clear initial interrupt if in irq mode */ + if (tj9->client->irq) { + err = i2c_smbus_read_byte_data(tj9->client, INT_REL); + if (err < 0) { + dev_err(&tj9->client->dev, + "error clearing interrupt: %d\n", err); + goto fail; + } + } + + return 0; + +fail: + kxtj9_device_power_off(tj9); + return err; +} + +static void kxtj9_disable(struct kxtj9_data *tj9) +{ + kxtj9_device_power_off(tj9); +} + +static int kxtj9_input_open(struct input_dev *input) +{ + struct kxtj9_data *tj9 = input_get_drvdata(input); + + return kxtj9_enable(tj9); +} + +static void kxtj9_input_close(struct input_dev *dev) +{ + struct kxtj9_data *tj9 = input_get_drvdata(dev); + + kxtj9_disable(tj9); +} + +static void kxtj9_init_input_device(struct kxtj9_data *tj9, + struct input_dev *input_dev) +{ + __set_bit(EV_ABS, input_dev->evbit); + input_set_abs_params(input_dev, ABS_X, -G_MAX, G_MAX, FUZZ, FLAT); + input_set_abs_params(input_dev, ABS_Y, -G_MAX, G_MAX, FUZZ, FLAT); + input_set_abs_params(input_dev, ABS_Z, -G_MAX, G_MAX, FUZZ, FLAT); + + input_dev->name = "kxtj9_accel"; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &tj9->client->dev; +} + +static int kxtj9_setup_input_device(struct kxtj9_data *tj9) +{ + struct input_dev *input_dev; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) { + dev_err(&tj9->client->dev, "input device allocate failed\n"); + return -ENOMEM; + } + + tj9->input_dev = input_dev; + + input_dev->open = kxtj9_input_open; + input_dev->close = kxtj9_input_close; + input_set_drvdata(input_dev, tj9); + + kxtj9_init_input_device(tj9, input_dev); + + err = input_register_device(tj9->input_dev); + if (err) { + dev_err(&tj9->client->dev, + "unable to register input polled device %s: %d\n", + tj9->input_dev->name, err); + input_free_device(tj9->input_dev); + return err; + } + + return 0; +} + +/* + * When IRQ mode is selected, we need to provide an interface to allow the user + * to change the output data rate of the part. For consistency, we are using + * the set_poll method, which accepts a poll interval in milliseconds, and then + * calls update_odr() while passing this value as an argument. In IRQ mode, the + * data outputs will not be read AT the requested poll interval, rather, the + * lowest ODR that can support the requested interval. The client application + * will be responsible for retrieving data from the input node at the desired + * interval. + */ + +/* Returns currently selected poll interval (in ms) */ +static ssize_t kxtj9_get_poll(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kxtj9_data *tj9 = i2c_get_clientdata(client); + + return sprintf(buf, "%d\n", tj9->last_poll_interval); +} + +/* Allow users to select a new poll interval (in ms) */ +static ssize_t kxtj9_set_poll(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kxtj9_data *tj9 = i2c_get_clientdata(client); + struct input_dev *input_dev = tj9->input_dev; + unsigned int interval; + int error; + + error = kstrtouint(buf, 10, &interval); + if (error < 0) + return error; + + /* Lock the device to prevent races with open/close (and itself) */ + mutex_lock(&input_dev->mutex); + + disable_irq(client->irq); + + /* + * Set current interval to the greater of the minimum interval or + * the requested interval + */ + tj9->last_poll_interval = max(interval, tj9->pdata.min_interval); + + kxtj9_update_odr(tj9, tj9->last_poll_interval); + + enable_irq(client->irq); + mutex_unlock(&input_dev->mutex); + + return count; +} + +static DEVICE_ATTR(poll, S_IRUGO|S_IWUSR, kxtj9_get_poll, kxtj9_set_poll); + +static struct attribute *kxtj9_attributes[] = { + &dev_attr_poll.attr, + NULL +}; + +static struct attribute_group kxtj9_attribute_group = { + .attrs = kxtj9_attributes +}; + + +#ifdef CONFIG_INPUT_KXTJ9_POLLED_MODE +static void kxtj9_poll(struct input_polled_dev *dev) +{ + struct kxtj9_data *tj9 = dev->private; + unsigned int poll_interval = dev->poll_interval; + + kxtj9_report_acceleration_data(tj9); + + if (poll_interval != tj9->last_poll_interval) { + kxtj9_update_odr(tj9, poll_interval); + tj9->last_poll_interval = poll_interval; + } +} + +static void kxtj9_polled_input_open(struct input_polled_dev *dev) +{ + struct kxtj9_data *tj9 = dev->private; + + kxtj9_enable(tj9); +} + +static void kxtj9_polled_input_close(struct input_polled_dev *dev) +{ + struct kxtj9_data *tj9 = dev->private; + + kxtj9_disable(tj9); +} + +static int kxtj9_setup_polled_device(struct kxtj9_data *tj9) +{ + int err; + struct input_polled_dev *poll_dev; + poll_dev = input_allocate_polled_device(); + + if (!poll_dev) { + dev_err(&tj9->client->dev, + "Failed to allocate polled device\n"); + return -ENOMEM; + } + + tj9->poll_dev = poll_dev; + tj9->input_dev = poll_dev->input; + + poll_dev->private = tj9; + poll_dev->poll = kxtj9_poll; + poll_dev->open = kxtj9_polled_input_open; + poll_dev->close = kxtj9_polled_input_close; + + kxtj9_init_input_device(tj9, poll_dev->input); + + err = input_register_polled_device(poll_dev); + if (err) { + dev_err(&tj9->client->dev, + "Unable to register polled device, err=%d\n", err); + input_free_polled_device(poll_dev); + return err; + } + + return 0; +} + +static void kxtj9_teardown_polled_device(struct kxtj9_data *tj9) +{ + input_unregister_polled_device(tj9->poll_dev); + input_free_polled_device(tj9->poll_dev); +} + +#else + +static inline int kxtj9_setup_polled_device(struct kxtj9_data *tj9) +{ + return -ENOSYS; +} + +static inline void kxtj9_teardown_polled_device(struct kxtj9_data *tj9) +{ +} + +#endif + +static int kxtj9_verify(struct kxtj9_data *tj9) +{ + int retval; + + retval = kxtj9_device_power_on(tj9); + if (retval < 0) + return retval; + + retval = i2c_smbus_read_byte_data(tj9->client, WHO_AM_I); + if (retval < 0) { + dev_err(&tj9->client->dev, "read err int source\n"); + goto out; + } + + retval = (retval != 0x07 && retval != 0x08) ? -EIO : 0; + +out: + kxtj9_device_power_off(tj9); + return retval; +} + +static int kxtj9_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct kxtj9_platform_data *pdata = client->dev.platform_data; + struct kxtj9_data *tj9; + int err; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_I2C | I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(&client->dev, "client is not i2c capable\n"); + return -ENXIO; + } + + if (!pdata) { + dev_err(&client->dev, "platform data is NULL; exiting\n"); + return -EINVAL; + } + + tj9 = kzalloc(sizeof(*tj9), GFP_KERNEL); + if (!tj9) { + dev_err(&client->dev, + "failed to allocate memory for module data\n"); + return -ENOMEM; + } + + tj9->client = client; + tj9->pdata = *pdata; + + if (pdata->init) { + err = pdata->init(); + if (err < 0) + goto err_free_mem; + } + + err = kxtj9_verify(tj9); + if (err < 0) { + dev_err(&client->dev, "device not recognized\n"); + goto err_pdata_exit; + } + + i2c_set_clientdata(client, tj9); + + tj9->ctrl_reg1 = tj9->pdata.res_12bit | tj9->pdata.g_range; + tj9->last_poll_interval = tj9->pdata.init_interval; + + if (client->irq) { + /* If in irq mode, populate INT_CTRL_REG1 and enable DRDY. */ + tj9->int_ctrl |= KXTJ9_IEN | KXTJ9_IEA | KXTJ9_IEL; + tj9->ctrl_reg1 |= DRDYE; + + err = kxtj9_setup_input_device(tj9); + if (err) + goto err_pdata_exit; + + err = request_threaded_irq(client->irq, NULL, kxtj9_isr, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "kxtj9-irq", tj9); + if (err) { + dev_err(&client->dev, "request irq failed: %d\n", err); + goto err_destroy_input; + } + + err = sysfs_create_group(&client->dev.kobj, &kxtj9_attribute_group); + if (err) { + dev_err(&client->dev, "sysfs create failed: %d\n", err); + goto err_free_irq; + } + + } else { + err = kxtj9_setup_polled_device(tj9); + if (err) + goto err_pdata_exit; + } + + return 0; + +err_free_irq: + free_irq(client->irq, tj9); +err_destroy_input: + input_unregister_device(tj9->input_dev); +err_pdata_exit: + if (tj9->pdata.exit) + tj9->pdata.exit(); +err_free_mem: + kfree(tj9); + return err; +} + +static int kxtj9_remove(struct i2c_client *client) +{ + struct kxtj9_data *tj9 = i2c_get_clientdata(client); + + if (client->irq) { + sysfs_remove_group(&client->dev.kobj, &kxtj9_attribute_group); + free_irq(client->irq, tj9); + input_unregister_device(tj9->input_dev); + } else { + kxtj9_teardown_polled_device(tj9); + } + + if (tj9->pdata.exit) + tj9->pdata.exit(); + + kfree(tj9); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int kxtj9_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kxtj9_data *tj9 = i2c_get_clientdata(client); + struct input_dev *input_dev = tj9->input_dev; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + kxtj9_disable(tj9); + + mutex_unlock(&input_dev->mutex); + return 0; +} + +static int kxtj9_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct kxtj9_data *tj9 = i2c_get_clientdata(client); + struct input_dev *input_dev = tj9->input_dev; + int retval = 0; + + mutex_lock(&input_dev->mutex); + + if (input_dev->users) + kxtj9_enable(tj9); + + mutex_unlock(&input_dev->mutex); + return retval; +} +#endif + +static SIMPLE_DEV_PM_OPS(kxtj9_pm_ops, kxtj9_suspend, kxtj9_resume); + +static const struct i2c_device_id kxtj9_id[] = { + { NAME, 0 }, + { }, +}; + +MODULE_DEVICE_TABLE(i2c, kxtj9_id); + +static struct i2c_driver kxtj9_driver = { + .driver = { + .name = NAME, + .owner = THIS_MODULE, + .pm = &kxtj9_pm_ops, + }, + .probe = kxtj9_probe, + .remove = kxtj9_remove, + .id_table = kxtj9_id, +}; + +module_i2c_driver(kxtj9_driver); + +MODULE_DESCRIPTION("KXTJ9 accelerometer driver"); +MODULE_AUTHOR("Chris Hudson <chudson@kionix.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/lis3dh.c b/drivers/input/misc/lis3dh.c new file mode 100644 index 00000000..947c3bbb --- /dev/null +++ b/drivers/input/misc/lis3dh.c @@ -0,0 +1,1707 @@ +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** + * + * File Name : lis3dh_acc.c + * Authors : MSH - Motion Mems BU - Application Team + * : Carmine Iascone (carmine.iascone@st.com) + * : Matteo Dameno (matteo.dameno@st.com) + * Version : V.1.0.5 + * Date : 16/08/2010 + * Description : LIS3DH accelerometer sensor API + * + ******************************************************************************* + * + * 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. + * + * THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE + * PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. + * AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, + * INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE + * CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING + * INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + * THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS. + * + ****************************************************************************** + Revision 1.0.0 05/11/09 + First Release + Revision 1.0.3 22/01/2010 + Linux K&R Compliant Release; + Revision 1.0.5 16/08/2010 + modified _get_acceleration_data function + modified _update_odr function + manages 2 interrupts + + ******************************************************************************/ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/i2c.h> + +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/slab.h> + +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#include <linux/i2c/lis3dh.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/of_gpio.h> + +#define INTERRUPT_MANAGEMENT 1 + +#define G_MAX 16000 /** Maximum polled-device-reported g value */ + +/* +#define SHIFT_ADJ_2G 4 +#define SHIFT_ADJ_4G 3 +#define SHIFT_ADJ_8G 2 +#define SHIFT_ADJ_16G 1 +*/ + +#define SENSITIVITY_2G 1 /** mg/LSB */ +#define SENSITIVITY_4G 2 /** mg/LSB */ +#define SENSITIVITY_8G 4 /** mg/LSB */ +#define SENSITIVITY_16G 12 /** mg/LSB */ + +#define HIGH_RESOLUTION 0x08 + +#define AXISDATA_REG 0x28 +#define WHOAMI_LIS3DH_ACC 0x33 /* Expctd content for WAI */ + +/* CONTROL REGISTERS */ +#define WHO_AM_I 0x0F /* WhoAmI register */ +#define TEMP_CFG_REG 0x1F /* temper sens control reg */ +/* ctrl 1: ODR3 ODR2 ODR ODR0 LPen Zenable Yenable Zenable */ +#define CTRL_REG1 0x20 /* control reg 1 */ +#define CTRL_REG2 0x21 /* control reg 2 */ +#define CTRL_REG3 0x22 /* control reg 3 */ +#define CTRL_REG4 0x23 /* control reg 4 */ +#define CTRL_REG5 0x24 /* control reg 5 */ +#define CTRL_REG6 0x25 /* control reg 6 */ + +#define FIFO_CTRL_REG 0x2E /* FiFo control reg */ + +#define INT_CFG1 0x30 /* interrupt 1 config */ +#define INT_SRC1 0x31 /* interrupt 1 source */ +#define INT_THS1 0x32 /* interrupt 1 threshold */ +#define INT_DUR1 0x33 /* interrupt 1 duration */ + +#define INT_CFG2 0x34 /* interrupt 2 config */ +#define INT_SRC2 0x35 /* interrupt 2 source */ +#define INT_THS2 0x36 /* interrupt 2 threshold */ +#define INT_DUR2 0x37 /* interrupt 2 duration */ + +#define TT_CFG 0x38 /* tap config */ +#define TT_SRC 0x39 /* tap source */ +#define TT_THS 0x3A /* tap threshold */ +#define TT_LIM 0x3B /* tap time limit */ +#define TT_TLAT 0x3C /* tap time latency */ +#define TT_TW 0x3D /* tap time window */ +/* end CONTROL REGISTRES */ + +#define ENABLE_HIGH_RESOLUTION 1 + +#define LIS3DH_ACC_PM_OFF 0x00 +#define LIS3DH_ACC_ENABLE_ALL_AXES 0x07 + +#define PMODE_MASK 0x08 +#define ODR_MASK 0XF0 + +#define ODR1 0x10 /* 1Hz output data rate */ +#define ODR10 0x20 /* 10Hz output data rate */ +#define ODR25 0x30 /* 25Hz output data rate */ +#define ODR50 0x40 /* 50Hz output data rate */ +#define ODR100 0x50 /* 100Hz output data rate */ +#define ODR200 0x60 /* 200Hz output data rate */ +#define ODR400 0x70 /* 400Hz output data rate */ +#define ODR1250 0x90 /* 1250Hz output data rate */ + +#define IA 0x40 +#define ZH 0x20 +#define ZL 0x10 +#define YH 0x08 +#define YL 0x04 +#define XH 0x02 +#define XL 0x01 +/* */ +/* CTRL REG BITS*/ +#define CTRL_REG3_I1_AOI1 0x40 +#define CTRL_REG6_I2_TAPEN 0x80 +#define CTRL_REG6_HLACTIVE 0x02 +/* */ + +/* TAP_SOURCE_REG BIT */ +#define DTAP 0x20 +#define STAP 0x10 +#define SIGNTAP 0x08 +#define ZTAP 0x04 +#define YTAP 0x02 +#define XTAZ 0x01 + +#define FUZZ 32 +#define FLAT 32 +#define I2C_RETRY_DELAY 5 +#define I2C_RETRIES 5 +#define I2C_AUTO_INCREMENT 0x80 + +/* RESUME STATE INDICES */ +#define RES_CTRL_REG1 0 +#define RES_CTRL_REG2 1 +#define RES_CTRL_REG3 2 +#define RES_CTRL_REG4 3 +#define RES_CTRL_REG5 4 +#define RES_CTRL_REG6 5 + +#define RES_INT_CFG1 6 +#define RES_INT_THS1 7 +#define RES_INT_DUR1 8 +#define RES_INT_CFG2 9 +#define RES_INT_THS2 10 +#define RES_INT_DUR2 11 + +#define RES_TT_CFG 12 +#define RES_TT_THS 13 +#define RES_TT_LIM 14 +#define RES_TT_TLAT 15 +#define RES_TT_TW 16 + +#define RES_TEMP_CFG_REG 17 +#define RES_REFERENCE_REG 18 +#define RES_FIFO_CTRL_REG 19 + +#define RESUME_ENTRIES 20 +#define DEVICE_INFO "ST, LIS3DH" +#define DEVICE_INFO_LEN 32 +/* end RESUME STATE INDICES */ + +#define GSENSOR_GINT1_GPI 0 +#define GSENSOR_GINT2_GPI 1 + +struct { + unsigned int cutoff_ms; + unsigned int mask; +} lis3dh_acc_odr_table[] = { + { + 1, ODR1250}, { + 3, ODR400}, { + 5, ODR200}, { + 10, ODR100}, { + 20, ODR50}, { + 40, ODR25}, { + 100, ODR10}, { +1000, ODR1},}; + +struct lis3dh_acc_data { + struct i2c_client *client; + struct lis3dh_acc_platform_data *pdata; + + struct mutex lock; + struct delayed_work input_work; + + struct input_dev *input_dev; + + int hw_initialized; + /* hw_working=-1 means not tested yet */ + int hw_working; + atomic_t enabled; + int on_before_suspend; + + u8 sensitivity; + + u8 resume_state[RESUME_ENTRIES]; + + int irq1; + struct work_struct irq1_work; + struct workqueue_struct *irq1_work_queue; + int irq2; + struct work_struct irq2_work; + struct workqueue_struct *irq2_work_queue; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif +}; + +/* + * Because misc devices can not carry a pointer from driver register to + * open, we keep this global. This limits the driver to a single instance. + */ +struct lis3dh_acc_data *lis3dh_acc_misc_data; +struct i2c_client *lis3dh_i2c_client; + +static int lis3dh_acc_i2c_read(struct lis3dh_acc_data *acc, u8 * buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { + { + .addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = 1, + .buf = buf,}, + { + .addr = acc->client->addr, + .flags = (acc->client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = buf,}, + }; + + do { + err = i2c_transfer(acc->client->adapter, msgs, 2); + if (err != 2) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 2) && (++tries < I2C_RETRIES)); + + if (err != 2) { + dev_err(&acc->client->dev, "read transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lis3dh_acc_i2c_write(struct lis3dh_acc_data *acc, u8 * buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { {.addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = len + 1,.buf = buf,}, + }; + do { + err = i2c_transfer(acc->client->adapter, msgs, 1); + if (err != 1) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 1) && (++tries < I2C_RETRIES)); + + if (err != 1) { + dev_err(&acc->client->dev, "write transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lis3dh_acc_hw_init(struct lis3dh_acc_data *acc) +{ + int err = -1; + u8 buf[7]; + + pr_debug("%s: hw init start\n", LIS3DH_ACC_DEV_NAME); + + buf[0] = WHO_AM_I; + err = lis3dh_acc_i2c_read(acc, buf, 1); + if (err < 0) + goto error_firstread; + else + acc->hw_working = 1; + if (buf[0] != WHOAMI_LIS3DH_ACC) { + err = -1; /* choose the right coded error */ + goto error_unknown_device; + } + + buf[0] = CTRL_REG1; + buf[1] = acc->resume_state[RES_CTRL_REG1]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = TEMP_CFG_REG; + buf[1] = acc->resume_state[RES_TEMP_CFG_REG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = FIFO_CTRL_REG; + buf[1] = acc->resume_state[RES_FIFO_CTRL_REG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | TT_THS); + buf[1] = acc->resume_state[RES_TT_THS]; + buf[2] = acc->resume_state[RES_TT_LIM]; + buf[3] = acc->resume_state[RES_TT_TLAT]; + buf[4] = acc->resume_state[RES_TT_TW]; + err = lis3dh_acc_i2c_write(acc, buf, 4); + if (err < 0) + goto error1; + buf[0] = TT_CFG; + buf[1] = acc->resume_state[RES_TT_CFG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS1); + buf[1] = acc->resume_state[RES_INT_THS1]; + buf[2] = acc->resume_state[RES_INT_DUR1]; + err = lis3dh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto error1; + buf[0] = INT_CFG1; + buf[1] = acc->resume_state[RES_INT_CFG1]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS2); + buf[1] = acc->resume_state[RES_INT_THS2]; + buf[2] = acc->resume_state[RES_INT_DUR2]; + err = lis3dh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto error1; + buf[0] = INT_CFG2; + buf[1] = acc->resume_state[RES_INT_CFG2]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG2); + buf[1] = acc->resume_state[RES_CTRL_REG2]; + buf[2] = acc->resume_state[RES_CTRL_REG3]; + buf[3] = acc->resume_state[RES_CTRL_REG4]; + buf[4] = acc->resume_state[RES_CTRL_REG5]; + buf[5] = acc->resume_state[RES_CTRL_REG6]; + err = lis3dh_acc_i2c_write(acc, buf, 5); + if (err < 0) + goto error1; + + acc->hw_initialized = 1; + pr_debug("%s: hw init done\n", LIS3DH_ACC_DEV_NAME); + return 0; + +error_firstread: + acc->hw_working = 0; + dev_warn(&acc->client->dev, "Error reading WHO_AM_I: is device " + "available/working?\n"); + goto error1; +error_unknown_device: + dev_err(&acc->client->dev, + "device unknown. Expected: 0x%x," + " Replies: 0x%x\n", WHOAMI_LIS3DH_ACC, buf[0]); +error1: + acc->hw_initialized = 0; + dev_err(&acc->client->dev, "hw init error 0x%x,0x%x: %d\n", buf[0], + buf[1], err); + return err; +} + +static void lis3dh_acc_device_power_off(struct lis3dh_acc_data *acc) +{ + int err; + u8 buf[2] = { CTRL_REG1, LIS3DH_ACC_PM_OFF }; + + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + dev_err(&acc->client->dev, "soft power off failed: %d\n", err); + + if (acc->pdata->power_off) { + if (acc->irq1 != 0) + disable_irq_nosync(acc->irq1); + if (acc->irq2 != 0) + disable_irq_nosync(acc->irq2); + acc->pdata->power_off(); + acc->hw_initialized = 0; + } + if (acc->hw_initialized) { + if (acc->irq1 != 0) + disable_irq_nosync(acc->irq1); + if (acc->irq2 != 0) + disable_irq_nosync(acc->irq2); + acc->hw_initialized = 0; + } +} + +static int lis3dh_acc_device_power_on(struct lis3dh_acc_data *acc) +{ + int err = -1; + + if (acc->pdata->power_on) { + err = acc->pdata->power_on(); + if (err < 0) { + dev_err(&acc->client->dev, + "power_on failed: %d\n", err); + return err; + } + } + + if (!acc->hw_initialized) { + err = lis3dh_acc_hw_init(acc); + if (acc->hw_working == 1 && err < 0) { + lis3dh_acc_device_power_off(acc); + return err; + } + } + return 0; +} + +static irqreturn_t lis3dh_acc_isr1(int irq, void *dev) +{ + struct lis3dh_acc_data *acc = dev; + + disable_irq_nosync(irq); + queue_work(acc->irq1_work_queue, &acc->irq1_work); + + pr_debug("%s: isr1 queued\n", LIS3DH_ACC_DEV_NAME); + + return IRQ_HANDLED; +} + +static irqreturn_t lis3dh_acc_isr2(int irq, void *dev) +{ + struct lis3dh_acc_data *acc = dev; + + disable_irq_nosync(irq); + queue_work(acc->irq2_work_queue, &acc->irq2_work); + + pr_debug("%s: isr2 queued\n", LIS3DH_ACC_DEV_NAME); + + return IRQ_HANDLED; +} + +static void lis3dh_acc_irq1_work_func(struct work_struct *work) +{ + + /*struct lis3dh_acc_data *acc = + container_of(work, struct lis3dh_acc_data, irq1_work); + */ + /* TODO add interrupt service procedure. + ie:lis3dh_acc_get_int1_source(acc); */ + ; + /* */ + pr_debug("%s: IRQ1 triggered\n", LIS3DH_ACC_DEV_NAME); +} + +static void lis3dh_acc_irq2_work_func(struct work_struct *work) +{ + + /*struct lis3dh_acc_data *acc = + container_of(work, struct lis3dh_acc_data, irq2_work); + */ + /* TODO add interrupt service procedure. + ie:lis3dh_acc_get_tap_source(acc); */ + ; + /* */ + + pr_debug("%s: IRQ2 triggered\n", LIS3DH_ACC_DEV_NAME); +} + +int lis3dh_acc_update_g_range(struct lis3dh_acc_data *acc, u8 new_g_range) +{ + int err; + + u8 sensitivity; + u8 buf[2]; + u8 updated_val; + u8 init_val; + u8 new_val; + u8 mask = LIS3DH_ACC_FS_MASK | HIGH_RESOLUTION; + + pr_debug("%s\n", __func__); + + switch (new_g_range) { + case LIS3DH_ACC_G_2G: + + sensitivity = SENSITIVITY_2G; + break; + case LIS3DH_ACC_G_4G: + + sensitivity = SENSITIVITY_4G; + break; + case LIS3DH_ACC_G_8G: + + sensitivity = SENSITIVITY_8G; + break; + case LIS3DH_ACC_G_16G: + + sensitivity = SENSITIVITY_16G; + break; + default: + dev_err(&acc->client->dev, "invalid g range requested: %u\n", + new_g_range); + return -EINVAL; + } + + if (atomic_read(&acc->enabled)) { + /* Set configuration register 4, which contains g range setting + * NOTE: this is a straight overwrite because this driver does + * not use any of the other configuration bits in this + * register. Should this become untrue, we will have to read + * out the value and only change the relevant bits --XX---- + * (marked by X) */ + buf[0] = CTRL_REG4; + err = lis3dh_acc_i2c_read(acc, buf, 1); + if (err < 0) + goto error; + init_val = buf[0]; + acc->resume_state[RES_CTRL_REG4] = init_val; + new_val = new_g_range | HIGH_RESOLUTION; + updated_val = ((mask & new_val) | ((~mask) & init_val)); + buf[1] = updated_val; + buf[0] = CTRL_REG4; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG4] = updated_val; + acc->sensitivity = sensitivity; + + pr_debug("%s sensitivity %d g-range %d\n", __func__, sensitivity,new_g_range); + } + + return 0; +error: + dev_err(&acc->client->dev, "update g range failed 0x%x,0x%x: %d\n", + buf[0], buf[1], err); + + return err; +} + +int lis3dh_acc_update_odr(struct lis3dh_acc_data *acc, int poll_interval_ms) +{ + int err = -1; + int i; + u8 config[2]; + + /* Convert the poll interval into an output data rate configuration + * that is as low as possible. The ordering of these checks must be + * maintained due to the cascading cut off values - poll intervals are + * checked from shortest to longest. At each check, if the next lower + * ODR cannot support the current poll interval, we stop searching */ + for (i = ARRAY_SIZE(lis3dh_acc_odr_table) - 1; i >= 0; i--) { + if (lis3dh_acc_odr_table[i].cutoff_ms <= poll_interval_ms) + break; + } + config[1] = lis3dh_acc_odr_table[i].mask; + + config[1] |= LIS3DH_ACC_ENABLE_ALL_AXES; + + /* If device is currently enabled, we need to write new + * configuration out to it */ + if (atomic_read(&acc->enabled)) { + config[0] = CTRL_REG1; + err = lis3dh_acc_i2c_write(acc, config, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG1] = config[1]; + } + + return 0; + +error: + dev_err(&acc->client->dev, "update odr failed 0x%x,0x%x: %d\n", + config[0], config[1], err); + + return err; +} + +/* */ + +static int lis3dh_acc_register_write(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address, u8 new_value) +{ + int err = -1; + + if (atomic_read(&acc->enabled)) { + /* Sets configuration register at reg_address + * NOTE: this is a straight overwrite */ + buf[0] = reg_address; + buf[1] = new_value; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + return err; + } + return err; +} + +static int lis3dh_acc_register_read(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address) +{ + + int err = -1; + buf[0] = (reg_address); + err = lis3dh_acc_i2c_read(acc, buf, 1); + return err; +} + +static int lis3dh_acc_register_update(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address, u8 mask, + u8 new_bit_values) +{ + int err = -1; + u8 init_val; + u8 updated_val; + err = lis3dh_acc_register_read(acc, buf, reg_address); + if (!(err < 0)) { + init_val = buf[1]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + err = lis3dh_acc_register_write(acc, buf, reg_address, + updated_val); + } + return err; +} + +/* */ + +static int lis3dh_acc_get_acceleration_data(struct lis3dh_acc_data *acc, + int *xyz) +{ + int err = -1; + /* Data bytes from hardware xL, xH, yL, yH, zL, zH */ + u8 acc_data[6]; + /* x,y,z hardware data */ + s16 hw_d[3] = { 0 }; + + acc_data[0] = (I2C_AUTO_INCREMENT | AXISDATA_REG); + err = lis3dh_acc_i2c_read(acc, acc_data, 6); + + if (err < 0) { + pr_debug("%s I2C read error %d\n", LIS3DH_ACC_I2C_NAME, + err); + return err; + } + + hw_d[0] = (((s16) ((acc_data[1] << 8) | acc_data[0])) >> 4); + hw_d[1] = (((s16) ((acc_data[3] << 8) | acc_data[2])) >> 4); + hw_d[2] = (((s16) ((acc_data[5] << 8) | acc_data[4])) >> 4); + + hw_d[0] = hw_d[0] * acc->sensitivity; + hw_d[1] = hw_d[1] * acc->sensitivity; + hw_d[2] = hw_d[2] * acc->sensitivity; + + xyz[0] = ((acc->pdata->negate_x) ? (-hw_d[acc->pdata->axis_map_x]) + : (hw_d[acc->pdata->axis_map_x])); + xyz[1] = ((acc->pdata->negate_y) ? (-hw_d[acc->pdata->axis_map_y]) + : (hw_d[acc->pdata->axis_map_y])); + xyz[2] = ((acc->pdata->negate_z) ? (-hw_d[acc->pdata->axis_map_z]) + : (hw_d[acc->pdata->axis_map_z])); + + pr_debug("%s read x=%d, y=%d, z=%d\n", + LIS3DH_ACC_DEV_NAME, xyz[0], xyz[1], xyz[2]); + + return err; +} + +static void lis3dh_acc_report_values(struct lis3dh_acc_data *acc, int *xyz) +{ + input_report_abs(acc->input_dev, ABS_X, xyz[0]); + input_report_abs(acc->input_dev, ABS_Y, xyz[1]); + input_report_abs(acc->input_dev, ABS_Z, xyz[2]); + input_sync(acc->input_dev); +} + +static int lis3dh_acc_enable(struct lis3dh_acc_data *acc) +{ + int err; + printk("sprd-gsensor: -- %s -- !\n",__func__); + if (!atomic_cmpxchg(&acc->enabled, 0, 1)) { + err = lis3dh_acc_device_power_on(acc); + if (err < 0) { + atomic_set(&acc->enabled, 0); + return err; + } + + if (acc->hw_initialized) { + if (acc->irq1 != 0) + enable_irq(acc->irq1); + if (acc->irq2 != 0) + enable_irq(acc->irq2); + pr_debug("%s: power on: irq enabled\n", + LIS3DH_ACC_DEV_NAME); + } + + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata-> + poll_interval)); + } + printk("sprd-gsensor: -- %s -- success!\n",__func__); + return 0; +} + +static int lis3dh_acc_disable(struct lis3dh_acc_data *acc) +{ + printk("sprd-gsensor: -- %s -- \n",__func__); + if (atomic_cmpxchg(&acc->enabled, 1, 0)) { + cancel_delayed_work_sync(&acc->input_work); + lis3dh_acc_device_power_off(acc); + } + + return 0; +} + +static int lis3dh_acc_misc_open(struct inode *inode, struct file *file) +{ + int err; + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + file->private_data = lis3dh_acc_misc_data; + + return 0; +} + +static long lis3dh_acc_misc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + u8 buf[4]; + u8 mask; + u8 reg_address; + u8 bit_values; + int err; + int interval; + int xyz[3] = { 0 }; + struct lis3dh_acc_data *acc = file->private_data; + + switch (cmd) { + case LIS3DH_ACC_IOCTL_GET_DELAY: + interval = acc->pdata->poll_interval; + if (copy_to_user(argp, &interval, sizeof(interval))) + return -EFAULT; + break; + + case LIS3DH_ACC_IOCTL_SET_DELAY: + if (copy_from_user(&interval, argp, sizeof(interval))) + return -EFAULT; + if (interval < 0 || interval > 1000) + return -EINVAL; + + acc->pdata->poll_interval = max(interval, + acc->pdata->min_interval); +err = lis3dh_acc_update_odr(acc, 2); + /* TODO: if update fails poll is still set */ + if (err < 0) + return err; + break; + + case LIS3DH_ACC_IOCTL_SET_ENABLE: + if (copy_from_user(&interval, argp, sizeof(interval))) + return -EFAULT; + if (interval > 1) + return -EINVAL; + if (interval) + err = lis3dh_acc_enable(acc); + else + err = lis3dh_acc_disable(acc); + return err; + break; + + case LIS3DH_ACC_IOCTL_GET_ENABLE: + interval = atomic_read(&acc->enabled); + if (copy_to_user(argp, &interval, sizeof(interval))) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_SET_G_RANGE: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + bit_values = buf[0]; + err = lis3dh_acc_update_g_range(acc, bit_values); + if (err < 0) + return err; + break; + +#ifdef INTERRUPT_MANAGEMENT + case LIS3DH_ACC_IOCTL_SET_CTRL_REG3: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = CTRL_REG3; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_CTRL_REG3] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_CTRL_REG3])); + break; + + case LIS3DH_ACC_IOCTL_SET_CTRL_REG6: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = CTRL_REG6; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_CTRL_REG6] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_CTRL_REG6])); + break; + + case LIS3DH_ACC_IOCTL_SET_DURATION1: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_DUR1; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_DUR1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_DUR1])); + break; + + case LIS3DH_ACC_IOCTL_SET_THRESHOLD1: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_THS1; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_THS1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_THS1])); + break; + + case LIS3DH_ACC_IOCTL_SET_CONFIG1: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = INT_CFG1; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_CFG1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_CFG1])); + break; + + case LIS3DH_ACC_IOCTL_GET_SOURCE1: + err = lis3dh_acc_register_read(acc, buf, INT_SRC1); + if (err < 0) + return err; + + pr_debug("INT1_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_SET_DURATION2: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_DUR2; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_DUR2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_DUR2])); + break; + + case LIS3DH_ACC_IOCTL_SET_THRESHOLD2: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_THS2; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_THS2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_THS2])); + break; + + case LIS3DH_ACC_IOCTL_SET_CONFIG2: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = INT_CFG2; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_CFG2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_CFG2])); + break; + + case LIS3DH_ACC_IOCTL_GET_SOURCE2: + err = lis3dh_acc_register_read(acc, buf, INT_SRC2); + if (err < 0) + return err; + + pr_debug("INT2_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_GET_TAP_SOURCE: + err = lis3dh_acc_register_read(acc, buf, TT_SRC); + if (err < 0) + return err; + pr_debug("TT_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) { + pr_err("%s: %s error in copy_to_user \n", + LIS3DH_ACC_DEV_NAME, __func__); + return -EINVAL; + } + break; + case LIS3DH_ACC_IOCTL_GET_COOR_XYZ: + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + return err; + + if (copy_to_user(argp, xyz, sizeof(xyz))) { + pr_err(" %s %d error in copy_to_user \n", + __func__, __LINE__); + return -EINVAL; + } + break; + case LIS3DH_ACC_IOCTL_SET_TAP_CFG: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_CFG; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_CFG] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_CFG])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TLIM: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_LIM; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_LIM] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_LIM])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_THS: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_THS; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_THS] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_THS])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TLAT: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_TLAT; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_TLAT] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_TLAT])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TW: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_TW; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_TW] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_TW])); + break; + +#endif /* INTERRUPT_MANAGEMENT */ + case LIS3DH_ACC_IOCTL_GET_CHIP_ID: + { + u8 devid = 0; + u8 devinfo[DEVICE_INFO_LEN] = {0}; + err = lis3dh_acc_register_read(acc, &devid, WHO_AM_I); + if (err < 0) { + printk("__func__, error read register WHO_AM_I\n", __func__); + return -EAGAIN; + } + sprintf(devinfo, "%s, %#x", DEVICE_INFO, devid); + + if (copy_to_user(argp, devinfo, sizeof(devinfo))) { + printk("%s error in copy_to_user(IOCTL_GET_CHIP_ID)\n", __func__); + return -EINVAL; + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct file_operations lis3dh_acc_misc_fops = { + .owner = THIS_MODULE, + .open = lis3dh_acc_misc_open, + .unlocked_ioctl = lis3dh_acc_misc_ioctl, +}; + +static struct miscdevice lis3dh_acc_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = LIS3DH_ACC_DEV_NAME, + .fops = &lis3dh_acc_misc_fops, +}; + +static void lis3dh_acc_input_work_func(struct work_struct *work) +{ + struct lis3dh_acc_data *acc; + + int xyz[3] = { 0 }; + int err; + + acc = container_of((struct delayed_work *)work, + struct lis3dh_acc_data, input_work); + + mutex_lock(&acc->lock); + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + dev_err(&acc->client->dev, "get_acceleration_data failed\n"); + else + lis3dh_acc_report_values(acc, xyz); + + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata->poll_interval)); + mutex_unlock(&acc->lock); +} + +#ifdef LIS3DH_OPEN_ENABLE +int lis3dh_acc_input_open(struct input_dev *input) +{ + struct lis3dh_acc_data *acc = input_get_drvdata(input); + + return lis3dh_acc_enable(acc); +} + +void lis3dh_acc_input_close(struct input_dev *dev) +{ + struct lis3dh_acc_data *acc = input_get_drvdata(dev); + + lis3dh_acc_disable(acc); +} +#endif + +static int lis3dh_acc_validate_pdata(struct lis3dh_acc_data *acc) +{ + acc->pdata->poll_interval = max(acc->pdata->poll_interval, + acc->pdata->min_interval); + + if (acc->pdata->axis_map_x > 2 || acc->pdata->axis_map_y > 2 + || acc->pdata->axis_map_z > 2) { + dev_err(&acc->client->dev, "invalid axis_map value " + "x:%u y:%u z%u\n", acc->pdata->axis_map_x, + acc->pdata->axis_map_y, acc->pdata->axis_map_z); + return -EINVAL; + } + + /* Only allow 0 and 1 for negation boolean flag */ + if (acc->pdata->negate_x > 1 || acc->pdata->negate_y > 1 + || acc->pdata->negate_z > 1) { + dev_err(&acc->client->dev, "invalid negate value " + "x:%u y:%u z:%u\n", acc->pdata->negate_x, + acc->pdata->negate_y, acc->pdata->negate_z); + return -EINVAL; + } + + /* Enforce minimum polling interval */ + if (acc->pdata->poll_interval < acc->pdata->min_interval) { + dev_err(&acc->client->dev, "minimum poll interval violated\n"); + return -EINVAL; + } + + return 0; +} + +static int lis3dh_acc_input_init(struct lis3dh_acc_data *acc) +{ + int err; + /* Polling rx data when the interrupt is not used.*/ + if (1 /*acc->irq1 == 0 && acc->irq1 == 0 */ ) { + INIT_DELAYED_WORK(&acc->input_work, lis3dh_acc_input_work_func); + } + + acc->input_dev = input_allocate_device(); + if (!acc->input_dev) { + err = -ENOMEM; + dev_err(&acc->client->dev, "input device allocate failed\n"); + goto err0; + } +#ifdef LIS3DH_ACC_OPEN_ENABLE + acc->input_dev->open = lis3dh_acc_input_open; + acc->input_dev->close = lis3dh_acc_input_close; +#endif + + input_set_drvdata(acc->input_dev, acc); + + set_bit(EV_ABS, acc->input_dev->evbit); + /* next is used for interruptA sources data if the case */ + set_bit(ABS_MISC, acc->input_dev->absbit); + /* next is used for interruptB sources data if the case */ + set_bit(ABS_WHEEL, acc->input_dev->absbit); + + input_set_abs_params(acc->input_dev, ABS_X, -G_MAX, G_MAX, 0, 0); + input_set_abs_params(acc->input_dev, ABS_Y, -G_MAX, G_MAX, 0, 0); + input_set_abs_params(acc->input_dev, ABS_Z, -G_MAX, G_MAX, 0, 0); + /* next is used for interruptA sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_MISC, INT_MIN, INT_MAX, 0, 0); + /* next is used for interruptB sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_WHEEL, INT_MIN, INT_MAX, 0, 0); + + acc->input_dev->name = "accelerometer"; + + err = input_register_device(acc->input_dev); + if (err) { + dev_err(&acc->client->dev, + "unable to register input polled device %s\n", + acc->input_dev->name); + goto err1; + } + + return 0; + +err1: + input_free_device(acc->input_dev); +err0: + return err; +} + +static void lis3dh_acc_input_cleanup(struct lis3dh_acc_data *acc) +{ + input_unregister_device(acc->input_dev); + input_free_device(acc->input_dev); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lis3dh_early_suspend(struct early_suspend *es); +static void lis3dh_early_resume(struct early_suspend *es); +#endif + +#ifdef CONFIG_OF +static struct lis3dh_acc_platform_data *lis3dh_acc_parse_dt(struct device *dev) +{ + struct lis3dh_acc_platform_data *pdata; + struct device_node *np = dev->of_node; + int ret; + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "Could not allocate struct lis3dh_acc_platform_data"); + return NULL; + } + ret = of_property_read_u32(np, "poll_interval", &pdata->poll_interval); + if(ret){ + dev_err(dev, "fail to get poll_interval\n"); + goto fail; + } + ret = of_property_read_u32(np, "min_interval", &pdata->min_interval); + if(ret){ + dev_err(dev, "fail to get min_interval\n"); + goto fail; + } + ret = of_property_read_u32(np, "g_range", &pdata->g_range); + if(ret){ + dev_err(dev, "fail to get g_range\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_x", &pdata->axis_map_x); + if(ret){ + dev_err(dev, "fail to get axis_map_x\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_y", &pdata->axis_map_y); + if(ret){ + dev_err(dev, "fail to get axis_map_y\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_z", &pdata->axis_map_z); + if(ret){ + dev_err(dev, "fail to get axis_map_z\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_x", &pdata->negate_x); + if(ret){ + dev_err(dev, "fail to get negate_x\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_y", &pdata->negate_y); + if(ret){ + dev_err(dev, "fail to get negate_y\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_z", &pdata->negate_z); + if(ret){ + dev_err(dev, "fail to get negate_z\n"); + goto fail; + } + return pdata; +fail: + kfree(pdata); + return NULL; +} +#endif +static int lis3dh_acc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + + struct lis3dh_acc_data *acc; + struct lis3dh_acc_platform_data *pdata = client->dev.platform_data; + + int err = -1; + int tempvalue; + printk("sprd-gsensor: -- %s -- start !\n",__func__); + pr_debug("%s: probe start.\n", LIS3DH_ACC_DEV_NAME); + +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; + if (np && !pdata){ + pdata = lis3dh_acc_parse_dt(&client->dev); + if(pdata){ + client->dev.platform_data = pdata; + } + if(!pdata){ + err = -ENOMEM; + goto exit_alloc_platform_data_failed; + } + } +#endif + /* + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "platform data is NULL. exiting.\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "client not i2c capable\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "client not smb-i2c capable:2\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&client->dev, "client not smb-i2c capable:3\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + /* + * OK. From now, we presume we have a valid client. We now create the + * client structure, even though we cannot fill it completely yet. + */ + + acc = kzalloc(sizeof(struct lis3dh_acc_data), GFP_KERNEL); + if (acc == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for module data: " + "%d\n", err); + goto exit_alloc_data_failed; + } + + mutex_init(&acc->lock); + mutex_lock(&acc->lock); + + acc->client = client; + lis3dh_i2c_client = client; + i2c_set_clientdata(client, acc); + + pr_debug("%s: %s has set irq1 to irq: %d\n", + LIS3DH_ACC_DEV_NAME, __func__, acc->irq1); + pr_debug("%s: %s has set irq2 to irq: %d\n", + LIS3DH_ACC_DEV_NAME, __func__, acc->irq2); + + gpio_request(GSENSOR_GINT1_GPI, "GSENSOR_INT1"); + gpio_request(GSENSOR_GINT2_GPI, "GSENSOR_INT2"); + acc->irq1 = 0; /* gpio_to_irq(GSENSOR_GINT1_GPI); */ + acc->irq2 = 0; /* gpio_to_irq(GSENSOR_GINT2_GPI); */ + + if (acc->irq1 != 0) { + pr_debug("%s request irq1\n", __func__); + err = + request_irq(acc->irq1, lis3dh_acc_isr1, IRQF_TRIGGER_RISING, + "lis3dh_acc_irq1", acc); + if (err < 0) { + dev_err(&client->dev, "request irq1 failed: %d\n", err); + goto err_mutexunlockfreedata; + } + disable_irq_nosync(acc->irq1); + + INIT_WORK(&acc->irq1_work, lis3dh_acc_irq1_work_func); + acc->irq1_work_queue = + create_singlethread_workqueue("lis3dh_acc_wq1"); + if (!acc->irq1_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, "cannot create work queue1: %d\n", + err); + goto err_free_irq1; + } + } + + if (acc->irq2 != 0) { + err = + request_irq(acc->irq2, lis3dh_acc_isr2, IRQF_TRIGGER_RISING, + "lis3dh_acc_irq2", acc); + if (err < 0) { + dev_err(&client->dev, "request irq2 failed: %d\n", err); + goto err_destoyworkqueue1; + } + disable_irq_nosync(acc->irq2); + +/* Create workqueue for IRQ.*/ + + INIT_WORK(&acc->irq2_work, lis3dh_acc_irq2_work_func); + acc->irq2_work_queue = + create_singlethread_workqueue("lis3dh_acc_wq2"); + if (!acc->irq2_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, "cannot create work queue2: %d\n", + err); + goto err_free_irq2; + } + } + + acc->pdata = kmalloc(sizeof(*acc->pdata), GFP_KERNEL); + if (acc->pdata == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for pdata: %d\n", err); + goto err_destoyworkqueue2; + } + + memcpy(acc->pdata, client->dev.platform_data, sizeof(*acc->pdata)); + + err = lis3dh_acc_validate_pdata(acc); + if (err < 0) { + dev_err(&client->dev, "failed to validate platform data\n"); + goto exit_kfree_pdata; + } + + err = lis3dh_acc_device_power_on(acc); + if (err < 0) { + dev_err(&client->dev, "power on failed: %d\n", err); + goto err2; + } + +#if 1 + if (i2c_smbus_read_byte(client) < 0) { + pr_err("i2c_smbus_read_byte error!!\n"); + goto err_destoyworkqueue2; + } else { + pr_debug("%s Device detected!\n", LIS3DH_ACC_DEV_NAME); + } +#endif + /* read chip id */ + + tempvalue = i2c_smbus_read_word_data(client, WHO_AM_I); + + if ((tempvalue & 0x00FF) == WHOAMI_LIS3DH_ACC) { + pr_debug("%s I2C driver registered!\n", + LIS3DH_ACC_DEV_NAME); + } else { + acc->client = NULL; + pr_debug("I2C driver not registered!" + " Device unknown 0x%x\n", tempvalue); + goto err_destoyworkqueue2; + } + + i2c_set_clientdata(client, acc); + + if (acc->pdata->init) { + err = acc->pdata->init(); + if (err < 0) { + dev_err(&client->dev, "init failed: %d\n", err); + goto err2; + } + } + + memset(acc->resume_state, 0, ARRAY_SIZE(acc->resume_state)); + + acc->resume_state[RES_CTRL_REG1] = LIS3DH_ACC_ENABLE_ALL_AXES; + acc->resume_state[RES_CTRL_REG2] = 0x00; + acc->resume_state[RES_CTRL_REG3] = 0x00; + acc->resume_state[RES_CTRL_REG4] = 0x00; + acc->resume_state[RES_CTRL_REG5] = 0x00; + acc->resume_state[RES_CTRL_REG6] = 0x00; + + acc->resume_state[RES_TEMP_CFG_REG] = 0x00; + acc->resume_state[RES_FIFO_CTRL_REG] = 0x00; + acc->resume_state[RES_INT_CFG1] = 0x00; + acc->resume_state[RES_INT_THS1] = 0x00; + acc->resume_state[RES_INT_DUR1] = 0x00; + acc->resume_state[RES_INT_CFG2] = 0x00; + acc->resume_state[RES_INT_THS2] = 0x00; + acc->resume_state[RES_INT_DUR2] = 0x00; + + acc->resume_state[RES_TT_CFG] = 0x00; + acc->resume_state[RES_TT_THS] = 0x00; + acc->resume_state[RES_TT_LIM] = 0x00; + acc->resume_state[RES_TT_TLAT] = 0x00; + acc->resume_state[RES_TT_TW] = 0x00; + + + atomic_set(&acc->enabled, 1); + + err = lis3dh_acc_update_g_range(acc, acc->pdata->g_range); + if (err < 0) { + dev_err(&client->dev, "update_g_range failed\n"); + goto err_power_off; + } + + err = lis3dh_acc_update_odr(acc, acc->pdata->poll_interval); + if (err < 0) { + dev_err(&client->dev, "update_odr failed\n"); + goto err_power_off; + } + + err = lis3dh_acc_input_init(acc); + if (err < 0) { + dev_err(&client->dev, "input init failed\n"); + goto err_power_off; + } + lis3dh_acc_misc_data = acc; + + err = misc_register(&lis3dh_acc_misc_device); + if (err < 0) { + dev_err(&client->dev, + "misc LIS3DH_ACC_DEV_NAME register failed\n"); + goto err_input_cleanup; + } + + lis3dh_acc_device_power_off(acc); + + /* As default, do not report information */ + atomic_set(&acc->enabled, 0); +/* +#ifdef CONFIG_HAS_EARLYSUSPEND + acc->early_suspend.suspend = lis3dh_early_suspend; + acc->early_suspend.resume = lis3dh_early_resume; + acc->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; + register_early_suspend(&acc->early_suspend); +#endif +*/ + mutex_unlock(&acc->lock); + dev_info(&client->dev, "###%s###\n", __func__); + printk("sprd-gsensor: -- %s -- success !\n",__func__); + return 0; + +err_input_cleanup: + lis3dh_acc_input_cleanup(acc); +err_power_off: + lis3dh_acc_device_power_off(acc); +err2: + if (acc->pdata->exit) + acc->pdata->exit(); +exit_kfree_pdata: + kfree(acc->pdata); +err_destoyworkqueue2: + if (acc->irq2_work_queue) + destroy_workqueue(acc->irq2_work_queue); +err_free_irq2: + if (acc->irq2) { + free_irq(acc->irq2, acc); + gpio_free(GSENSOR_GINT2_GPI); + } +err_destoyworkqueue1: + if (acc->irq1_work_queue) + destroy_workqueue(acc->irq1_work_queue); +err_free_irq1: + if (acc->irq1) { + free_irq(acc->irq1, acc); + gpio_free(GSENSOR_GINT1_GPI); + } +err_mutexunlockfreedata: + i2c_set_clientdata(client, NULL); + mutex_unlock(&acc->lock); + kfree(acc); + lis3dh_acc_misc_data = NULL; +exit_alloc_data_failed: +exit_check_functionality_failed: + pr_err("%s: Driver Init failed\n", LIS3DH_ACC_DEV_NAME); +exit_alloc_platform_data_failed: + return err; +} + +static int lis3dh_acc_remove(struct i2c_client *client) +{ + /* TODO: revisit ordering here once _probe order is finalized */ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + if (acc != NULL) { + if (acc->irq1) { + free_irq(acc->irq1, acc); + gpio_free(GSENSOR_GINT1_GPI); + } + if (acc->irq2) { + free_irq(acc->irq2, acc); + gpio_free(GSENSOR_GINT2_GPI); + } + + if (acc->irq1_work_queue) + destroy_workqueue(acc->irq1_work_queue); + if (acc->irq2_work_queue) + destroy_workqueue(acc->irq2_work_queue); + misc_deregister(&lis3dh_acc_misc_device); + lis3dh_acc_input_cleanup(acc); + lis3dh_acc_device_power_off(acc); + if (acc->pdata->exit) + acc->pdata->exit(); + kfree(acc->pdata); + kfree(acc); + } + + return 0; +} + +static int lis3dh_acc_resume(struct i2c_client *client) +{ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + printk("sprd-gsensor: -- %s -- !\n",__func__); + dev_dbg(&client->dev, "###%s###\n", __func__); + + if (acc != NULL && acc->on_before_suspend) + return lis3dh_acc_enable(acc); + + return 0; +} + +static int lis3dh_acc_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + printk("sprd-gsensor: -- %s -- !\n",__func__); + dev_dbg(&client->dev, "###%s###\n", __func__); + + if (acc != NULL) { + acc->on_before_suspend = atomic_read(&acc->enabled); + return lis3dh_acc_disable(acc); + } + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND + +static void lis3dh_early_suspend(struct early_suspend *es) +{ + lis3dh_acc_suspend(lis3dh_i2c_client, (pm_message_t) { + .event = 0}); +} + +static void lis3dh_early_resume(struct early_suspend *es) +{ + lis3dh_acc_resume(lis3dh_i2c_client); +} + +#endif /* CONFIG_HAS_EARLYSUSPEND */ + +static const struct i2c_device_id lis3dh_acc_id[] += { {LIS3DH_ACC_DEV_NAME, 0}, {}, }; + +MODULE_DEVICE_TABLE(i2c, lis3dh_acc_id); + +static const struct of_device_id lis3dh_acc_of_match[] = { + { .compatible = "ST,lis3dh_acc", }, + { } +}; +MODULE_DEVICE_TABLE(of, lis3dh_acc_of_match); +static struct i2c_driver lis3dh_acc_driver = { + .driver = { + .name = LIS3DH_ACC_I2C_NAME, + .of_match_table = lis3dh_acc_of_match, + }, + .probe = lis3dh_acc_probe, + .remove = lis3dh_acc_remove, + .resume = lis3dh_acc_resume, + .suspend = lis3dh_acc_suspend, + .id_table = lis3dh_acc_id, +}; + + +static int __init lis3dh_acc_init(void) +{ + pr_debug("%s accelerometer driver: init\n", LIS3DH_ACC_I2C_NAME); + + return i2c_add_driver(&lis3dh_acc_driver); +} + +static void __exit lis3dh_acc_exit(void) +{ + pr_debug("%s accelerometer driver exit\n", LIS3DH_ACC_DEV_NAME); + + i2c_del_driver(&lis3dh_acc_driver); + return; +} + +module_init(lis3dh_acc_init); +module_exit(lis3dh_acc_exit); + +MODULE_DESCRIPTION("lis3dh accelerometer misc driver"); +MODULE_AUTHOR("STMicroelectronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/lis3dh_interrupt.c b/drivers/input/misc/lis3dh_interrupt.c new file mode 100644 index 00000000..ba801834 --- /dev/null +++ b/drivers/input/misc/lis3dh_interrupt.c @@ -0,0 +1,2261 @@ +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** + * + * File Name : lis3dh_acc.c + * Authors : MSH - Motion Mems BU - Application Team + * : Carmine Iascone (carmine.iascone@st.com) + * : Matteo Dameno (matteo.dameno@st.com) + * Version : V.1.0.5 + * Date : 16/08/2010 + * Description : LIS3DH accelerometer sensor API + * + ******************************************************************************* + * + * 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. + * + * THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE + * PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. + * AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, + * INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE + * CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING + * INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + * THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS. + * + ****************************************************************************** + Revision 1.0.0 05/11/09 + First Release + Revision 1.0.3 22/01/2010 + Linux K&R Compliant Release; + Revision 1.0.5 16/08/2010 + modified _get_acceleration_data function + modified _update_odr function + manages 2 interrupts + + ******************************************************************************/ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/i2c.h> + +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/slab.h> + +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#include <linux/i2c/lis3dh.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/of_gpio.h> + +#include <linux/sched.h> +#include <linux/kthread.h> +#include <linux/completion.h> +#include <linux/err.h> + +#include <soc/sprd/i2c-sprd.h> + +#define INTERRUPT_MANAGEMENT 1 + +#define G_MAX 16000 /** Maximum polled-device-reported g value */ + +/* +#define SHIFT_ADJ_2G 4 +#define SHIFT_ADJ_4G 3 +#define SHIFT_ADJ_8G 2 +#define SHIFT_ADJ_16G 1 +*/ +#define GSENSOR_INTERRUPT_MODE + +#define SENSITIVITY_2G 1 /** mg/LSB */ +#define SENSITIVITY_4G 2 /** mg/LSB */ +#define SENSITIVITY_8G 4 /** mg/LSB */ +#define SENSITIVITY_16G 12 /** mg/LSB */ + +#define HIGH_RESOLUTION 0x08 + +#define AXISDATA_REG 0x28 +#define WHOAMI_LIS3DH_ACC 0x33 /* Expctd content for WAI */ + +/* CONTROL REGISTERS */ +#define WHO_AM_I 0x0F /* WhoAmI register */ +#define TEMP_CFG_REG 0x1F /* temper sens control reg */ +/* ctrl 1: ODR3 ODR2 ODR ODR0 LPen Zenable Yenable Zenable */ +#define CTRL_REG1 0x20 /* control reg 1 */ +#define CTRL_REG2 0x21 /* control reg 2 */ +#define CTRL_REG3 0x22 /* control reg 3 */ +#define CTRL_REG4 0x23 /* control reg 4 */ +#define CTRL_REG5 0x24 /* control reg 5 */ +#define CTRL_REG6 0x25 /* control reg 6 */ + +#define FIFO_CTRL_REG 0x2E /* FiFo control reg */ + +#define INT_CFG1 0x30 /* interrupt 1 config */ +#define INT_SRC1 0x31 /* interrupt 1 source */ +#define INT_THS1 0x32 /* interrupt 1 threshold */ +#define INT_DUR1 0x33 /* interrupt 1 duration */ + +#define INT_CFG2 0x34 /* interrupt 2 config */ +#define INT_SRC2 0x35 /* interrupt 2 source */ +#define INT_THS2 0x36 /* interrupt 2 threshold */ +#define INT_DUR2 0x37 /* interrupt 2 duration */ + +#define TT_CFG 0x38 /* tap config */ +#define TT_SRC 0x39 /* tap source */ +#define TT_THS 0x3A /* tap threshold */ +#define TT_LIM 0x3B /* tap time limit */ +#define TT_TLAT 0x3C /* tap time latency */ +#define TT_TW 0x3D /* tap time window */ +/* end CONTROL REGISTRES */ + +#define ENABLE_HIGH_RESOLUTION 1 + +#define LIS3DH_ACC_PM_OFF 0x00 +#define LIS3DH_ACC_ENABLE_ALL_AXES 0x07 + +#define PMODE_MASK 0x08 +#define ODR_MASK 0XF0 + +#define ODR1 0x10 /* 1Hz output data rate */ +#define ODR10 0x20 /* 10Hz output data rate */ +#define ODR25 0x30 /* 25Hz output data rate */ +#define ODR50 0x40 /* 50Hz output data rate */ +#define ODR100 0x50 /* 100Hz output data rate */ +#define ODR200 0x60 /* 200Hz output data rate */ +#define ODR400 0x70 /* 400Hz output data rate */ +#define ODR1250 0x90 /* 1250Hz output data rate : test actually 650hz*/ + +#define IA 0x40 +#define ZH 0x20 +#define ZL 0x10 +#define YH 0x08 +#define YL 0x04 +#define XH 0x02 +#define XL 0x01 +/* */ +/* CTRL REG BITS*/ +#define CTRL_REG3_I1_AOI1 0x40 +#define CTRL_REG6_I2_TAPEN 0x80 +/* +CTRL_REG3 (22h) µÄI1_DRDY1 λ enable +CTRL_REG5 (24h) LIR_INT1 1 Ϊ ´¥·¢ÖÐ¶Ï ºó Öжϱ£³Ö£¬0 Ϊ Âö³å +CTRL_REG6 (25h) 0 ΪÖÐ¶Ï ¸ßÓÐЧ£¬ 1 ΪÖжϵÍÓÐЧ +********must read data then will interrupter,****** +*/ +#define CTRL_REG3_I1_DRDY1 0x10 //DRDY1 interrupt on INT1 +#define CTRL_REG5_LIR_INT1 0x08 //latch interrupt +//#define CTRL_REG6_HLACTIVE 0x02 //low +#define CTRL_REG6_HLACTIVE 0x00 //high + + +/* */ + +/* TAP_SOURCE_REG BIT */ +#define DTAP 0x20 +#define STAP 0x10 +#define SIGNTAP 0x08 +#define ZTAP 0x04 +#define YTAP 0x02 +#define XTAZ 0x01 + +#define FUZZ 32 +#define FLAT 32 +#define I2C_RETRY_DELAY 5 +#define I2C_RETRIES 5 +#define I2C_AUTO_INCREMENT 0x80 + +/* RESUME STATE INDICES */ +#define RES_CTRL_REG1 0 +#define RES_CTRL_REG2 1 +#define RES_CTRL_REG3 2 +#define RES_CTRL_REG4 3 +#define RES_CTRL_REG5 4 +#define RES_CTRL_REG6 5 + +#define RES_INT_CFG1 6 +#define RES_INT_THS1 7 +#define RES_INT_DUR1 8 +#define RES_INT_CFG2 9 +#define RES_INT_THS2 10 +#define RES_INT_DUR2 11 + +#define RES_TT_CFG 12 +#define RES_TT_THS 13 +#define RES_TT_LIM 14 +#define RES_TT_TLAT 15 +#define RES_TT_TW 16 + +#define RES_TEMP_CFG_REG 17 +#define RES_REFERENCE_REG 18 +#define RES_FIFO_CTRL_REG 19 + +#define RESUME_ENTRIES 20 +#define DEVICE_INFO "ST, LIS3DH" +#define DEVICE_INFO_LEN 32 +/* end RESUME STATE INDICES */ + +//#define GSENSOR_GINT1_GPI 238 +#define GSENSOR_GINT2_GPI 1 + +#define LOG_TAG "[LIS3DH] " +#define LOG_FUN( ) printk(KERN_INFO LOG_TAG"%s\n", __FUNCTION__) +#define LOG_INFO(fmt, args...) printk(KERN_INFO LOG_TAG fmt, ##args) +#define LOG_ERR(fmt, args...) printk(KERN_ERR LOG_TAG"%s %d : "fmt, __FUNCTION__, __LINE__, ##args) + +//#define LIS3DH_DBG +#define GSENSOR_INTERRUPT_MODE +#define USE_WAIT_QUEUE 1 +#if USE_WAIT_QUEUE +static struct task_struct *thread = NULL; +static DECLARE_WAIT_QUEUE_HEAD(waiter); +static int gsensor_flag = 0; +#endif + +struct { + unsigned int cutoff_ms; + unsigned int mask; +} lis3dh_acc_odr_table[] = { + { + 1, ODR1250}, { + 2, ODR400}, { + 5, ODR200}, { + 10, ODR100}, { + 20, ODR50}, { + 40, ODR25}, { + 100, ODR10}, { + 1000, ODR1},}; + +struct lis3dh_acc_data { + struct i2c_client *client; + struct lis3dh_acc_platform_data *pdata; + + struct mutex lock; + struct delayed_work input_work; + + struct input_dev *input_dev; + + int hw_initialized; + /* hw_working=-1 means not tested yet */ + int hw_working; + atomic_t enabled; + int on_before_suspend; + + u8 sensitivity; + + u8 resume_state[RESUME_ENTRIES]; + +// int irq1_gpio_number; + int irq1; + struct work_struct irq1_work; + struct workqueue_struct *irq1_work_queue; + int irq2; + struct work_struct irq2_work; + struct workqueue_struct *irq2_work_queue; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif +#ifdef LIS3DH_DBG +struct { + u8 diag_reg_addr_wr; + u8 diag_reg_val_wr; + } debug; + u64 data_num; +#endif +}; + +/* + * Because misc devices can not carry a pointer from driver register to + * open, we keep this global. This limits the driver to a single instance. + */ +struct lis3dh_acc_data *lis3dh_acc_misc_data; +struct i2c_client *lis3dh_i2c_client; + +#ifndef GSENSOR_INTERRUPT_MODE +static struct workqueue_struct *_gSensorWork_workqueue = NULL; +#endif + +static int lis3dh_acc_get_acceleration_data(struct lis3dh_acc_data *acc,int *xyz); +static void lis3dh_acc_report_values(struct lis3dh_acc_data *acc, int *xyz); + +static int lis3dh_acc_i2c_read(struct lis3dh_acc_data *acc, u8 * buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { + { + .addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = 1, + .buf = buf,}, + { + .addr = acc->client->addr, + .flags = (acc->client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = buf,}, + }; + + do { + err = i2c_transfer(acc->client->adapter, msgs, 2); + if (err != 2){ + LOG_ERR("************* exception read transfer error,tries=%d********\n",tries); + msleep_interruptible(I2C_RETRY_DELAY); + } + } while ((err != 2) && (++tries < I2C_RETRIES)); + + if (err != 2) { + LOG_ERR(" exception read transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lis3dh_acc_i2c_write(struct lis3dh_acc_data *acc, u8 * buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { {.addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = len + 1,.buf = buf,}, + }; + do { + err = i2c_transfer(acc->client->adapter, msgs, 1); + if (err != 1) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 1) && (++tries < I2C_RETRIES)); + + if (err != 1) { + dev_err(&acc->client->dev, "write transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + + +static int lis3dh_acc_register_read(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address) +{ + int err = -1; + buf[0] = (reg_address); + err = lis3dh_acc_i2c_read(acc, buf, 1); + return err; +} + +static void lis3dh_read_interrupt_reg(struct lis3dh_acc_data *acc) +{ + u8 value = 0; + int err; + + err = lis3dh_acc_register_read(acc, &value, CTRL_REG3); + LOG_INFO("REG content: =======================>\n"); + if (err >= 0) + LOG_INFO("REG content: [CTRL_REG3:0x%x]= 0x%02x\n", CTRL_REG3, value); + + err = lis3dh_acc_register_read(acc, &value, CTRL_REG5); + if (err >= 0) + LOG_INFO("REG content: [CTRL_REG5:0x%x]= 0x%02x\n", CTRL_REG5, value); + err = lis3dh_acc_register_read(acc, &value, CTRL_REG6); + if (err >= 0) + LOG_INFO("REG content: [CTRL_REG6:0x%x]= 0x%02x\n", CTRL_REG6, value); + + LOG_INFO("REG content:< =======================\n"); + +} + + + +/* if CTM want to turn off vdd & vddio , we recommend to call this function to retrive register setting */ + +static int lis3dh_acc_hw_init(struct lis3dh_acc_data *acc) +{ + int err = -1; + u8 buf[7]; + + pr_debug("%s: hw init start\n", LIS3DH_ACC_DEV_NAME); + LOG_INFO("sprd-gsensor: -- %s entry :acc-> pdata ->gpio_int1=%d-- !\n",__func__,gpio_get_value(acc-> pdata ->gpio_int1)); + + buf[0] = WHO_AM_I; + err = lis3dh_acc_i2c_read(acc, buf, 1); + if (err < 0) + goto error_firstread; + else + acc->hw_working = 1; + if (buf[0] != WHOAMI_LIS3DH_ACC) { + err = -1; /* choose the right coded error */ + goto error_unknown_device; + } + + buf[0] = CTRL_REG1; + buf[1] = acc->resume_state[RES_CTRL_REG1]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = TEMP_CFG_REG; + buf[1] = acc->resume_state[RES_TEMP_CFG_REG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = FIFO_CTRL_REG; + buf[1] = acc->resume_state[RES_FIFO_CTRL_REG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | TT_THS); + buf[1] = acc->resume_state[RES_TT_THS]; + buf[2] = acc->resume_state[RES_TT_LIM]; + buf[3] = acc->resume_state[RES_TT_TLAT]; + buf[4] = acc->resume_state[RES_TT_TW]; + err = lis3dh_acc_i2c_write(acc, buf, 4); + if (err < 0) + goto error1; + buf[0] = TT_CFG; + buf[1] = acc->resume_state[RES_TT_CFG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS1); + buf[1] = acc->resume_state[RES_INT_THS1]; + buf[2] = acc->resume_state[RES_INT_DUR1]; + err = lis3dh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto error1; + buf[0] = INT_CFG1; + buf[1] = acc->resume_state[RES_INT_CFG1]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS2); + buf[1] = acc->resume_state[RES_INT_THS2]; + buf[2] = acc->resume_state[RES_INT_DUR2]; + err = lis3dh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto error1; + buf[0] = INT_CFG2; + buf[1] = acc->resume_state[RES_INT_CFG2]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG2); + buf[1] = acc->resume_state[RES_CTRL_REG2]; + buf[2] = acc->resume_state[RES_CTRL_REG3]; + buf[3] = acc->resume_state[RES_CTRL_REG4]; + buf[4] = acc->resume_state[RES_CTRL_REG5]; + buf[5] = acc->resume_state[RES_CTRL_REG6]; + err = lis3dh_acc_i2c_write(acc, buf, 5); + if (err < 0) + goto error1; + + acc->hw_initialized = 1; + + LOG_INFO("sprd-gsensor: -- %s exit :acc-> pdata ->gpio_int1=%d-- !\n",__func__,gpio_get_value(acc-> pdata ->gpio_int1)); + + pr_debug("%s: hw init done\n", LIS3DH_ACC_DEV_NAME); + return 0; + +error_firstread: + acc->hw_working = 0; + dev_warn(&acc->client->dev, "Error reading WHO_AM_I: is device " + "available/working?\n"); + goto error1; +error_unknown_device: + dev_err(&acc->client->dev, + "device unknown. Expected: 0x%x," + " Replies: 0x%x\n", WHOAMI_LIS3DH_ACC, buf[0]); +error1: + acc->hw_initialized = 0; + dev_err(&acc->client->dev, "hw init error 0x%x,0x%x: %d\n", buf[0], + buf[1], err); + return err; +} + +static void lis3dh_acc_device_power_off(struct lis3dh_acc_data *acc) +{ + int err; + u8 buf[2] = { CTRL_REG1, LIS3DH_ACC_PM_OFF }; + int xyz[3] = { 0 }; + + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + dev_err(&acc->client->dev, "soft power off failed: %d\n", err); +#ifdef GSENSOR_INTERRUPT_MODE +/* +read data:clear interrupter +*/ + err = lis3dh_acc_get_acceleration_data(acc, xyz); +#endif + LOG_INFO("sprd-gsensor: -- %s acc-> pdata ->gpio_int1=%d acc->hw_initialized=%d-- !\n",__func__,gpio_get_value(acc-> pdata ->gpio_int1),acc->hw_initialized); + +} + +static int lis3dh_acc_device_power_on(struct lis3dh_acc_data *acc) +{ + int err = -1; + u8 buf[2]; + + + if (acc->pdata->power_on) { + err = acc->pdata->power_on(); + if (err < 0) { + dev_err(&acc->client->dev, + "power_on failed: %d\n", err); + return err; + } + } + + LOG_INFO("sprd-gsensor: -- %s acc->irq1_gpio_number=%d acc->hw_initialized=%d-- !\n",__func__,gpio_get_value(acc-> pdata ->gpio_int1),acc->hw_initialized); + buf[0] = CTRL_REG1; + buf[1] = acc->resume_state[RES_CTRL_REG1]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + return err; + LOG_INFO("sprd-gsensor: -- %s acc->resume_state[RES_CTRL_REG1]=0x%02x-- !\n",__func__, acc->resume_state[RES_CTRL_REG1]); + + + if (!acc->hw_initialized) { + err = lis3dh_acc_hw_init(acc); + lis3dh_read_interrupt_reg(acc); + if (acc->hw_working == 1 && err < 0) { + lis3dh_acc_device_power_off(acc); + return err; + } + } + return 0; +} + +static irqreturn_t lis3dh_acc_isr1(int irq, void *dev) +{ +#ifndef USE_WAIT_QUEUE + struct lis3dh_acc_data *acc = dev; +#endif + +#ifdef LIS3DH_DBG + static int count_intr = 0; + static s64 interrupt_last = 0; + static s64 interrupt_cur = 0; + static int poll_interval=0; + ktime_t time_mono; + + time_mono = ktime_get(); + if (poll_interval !=lis3dh_acc_misc_data->pdata->poll_interval) { + count_intr=1; + poll_interval = lis3dh_acc_misc_data->pdata->poll_interval; + interrupt_last = ktime_to_ns(time_mono); + } + interrupt_cur = ktime_to_ns(time_mono); + int interrupt_interval = interrupt_cur - interrupt_last; + interrupt_last = interrupt_cur; + + if(interrupt_interval > (lis3dh_acc_misc_data->pdata->poll_interval*1000000) +(lis3dh_acc_misc_data->pdata->poll_interval*1000000) /4) + LOG_INFO("***sprd-gsensor ISR exception:count_intr=%d,interrupt_interval=%d,poll_interva=%d********\n",count_intr++,interrupt_interval,lis3dh_acc_misc_data->pdata->poll_interval); + LOG_INFO("sprd-gsensor: -- %s entry : interrupt_interval=%d,poll_interval=%d,acc->irq1_gpio_number=%d-- >>>>>>>>>>>>>>>>>!\n",__func__,interrupt_interval,poll_interval,gpio_get_value(lis3dh_acc_misc_data->pdata->gpio_int1)); +#endif + + disable_irq_nosync(irq); +#if USE_WAIT_QUEUE + gsensor_flag = 1; + wake_up_interruptible(&waiter); +#else + queue_work(acc->irq1_work_queue, &acc->irq1_work); +#endif + pr_debug("%s: isr1 queued\n", LIS3DH_ACC_DEV_NAME); + return IRQ_HANDLED; +} + +static irqreturn_t lis3dh_acc_isr2(int irq, void *dev) +{ + struct lis3dh_acc_data *acc = dev; + +// disable_irq_nosync(irq); + queue_work(acc->irq2_work_queue, &acc->irq2_work); + + pr_debug("%s: isr2 queued\n", LIS3DH_ACC_DEV_NAME); + + return IRQ_HANDLED; +} +//static int lis3dh_acc_get_acceleration_data(struct lis3dh_acc_data *acc,int *xyz); +static void lis3dh_acc_irq1_work_func(struct work_struct *work) +{ + int xyz[3] = { 0 }; + int err; + u8 value; + struct lis3dh_acc_data *acc = + container_of(work, struct lis3dh_acc_data, irq1_work); + + /* TODO add interrupt service procedure. + ie:lis3dh_acc_get_int1_source(acc); */ + ; + /* */ + LOG_INFO("%s: isr1 queued=========>>>>>>>>>>>>>>>>>\n", __func__); + LOG_INFO("sprd-gsensor: -- %s entry :acc->irq1_gpio_number=%d-- !\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + + err = lis3dh_acc_register_read(acc, &value, INT_SRC1); + + if (err < 0) { + LOG_INFO("%s, error read register INT_SRC1\n", __func__); + } + LOG_INFO("%s: IRQ1 triggered INT_SRC1=0x%02x\n",__func__,value); + + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + dev_err(&acc->client->dev, "get_acceleration_data failed\n"); + else + lis3dh_acc_report_values(acc, xyz); + + + LOG_INFO("sprd-gsensor: -- %s exit :acc->irq1_gpio_number=%d-- !\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + enable_irq(acc->irq1); +} + + +#if USE_WAIT_QUEUE + +static void lis3dh_acc_update_data_func(void) +{ + int xyz[3] = { 0 }; + int err; + struct lis3dh_acc_data *acc =lis3dh_acc_misc_data; + +// LOG_INFO("sprd-gsensor: -- %s entry :acc->irq1_gpio_number=%d-- !\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + LOG_ERR("get_acceleration_data failed*******************************\n"); + else + lis3dh_acc_report_values(acc, xyz); +#ifdef LIS3DH_DBG + LOG_INFO("sprd-gsensor: -- %s exit :acc->irq1_gpio_number=%d-->>>>>>>>>>>>>>>>> !\n\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + LOG_INFO("\n"); + #endif + enable_irq(acc->irq1); +} + +static int gsensor_event_handler(void *unused) +{ +#ifdef LIS3DH_DBG + static int count_intr = 0; + static s64 interrupt_last = 0; + static s64 interrupt_read = 0; + static s64 interrupt_cur = 0; + static int poll_interval=0; + ktime_t time_mono; +#endif + struct sched_param param = { .sched_priority = 5}; + + sched_setscheduler(current, SCHED_RR, ¶m); + LOG_INFO("sprd-gsensor: -- %s entry :acc->irq1_gpio_number=%d-- !\n",__func__,gpio_get_value(lis3dh_acc_misc_data->pdata->gpio_int1)); + + do { +#ifdef LIS3DH_DBG + LOG_INFO("sprd-gsensor: -- %s 1 : gsensor_flag=%d,acc->irq1_gpio_number=%d-- !\n",__func__,gsensor_flag,gpio_get_value(lis3dh_acc_misc_data->pdata->gpio_int1)); +#endif + + set_current_state(TASK_INTERRUPTIBLE); + wait_event_interruptible(waiter, (0 != gsensor_flag)); +// +#ifdef LIS3DH_DBG + time_mono = ktime_get(); + if (poll_interval !=lis3dh_acc_misc_data->pdata->poll_interval) { + count_intr=1; + poll_interval = lis3dh_acc_misc_data->pdata->poll_interval; + interrupt_last = ktime_to_ns(time_mono); + } + interrupt_cur = ktime_to_ns(time_mono); + int interrupt_interval = interrupt_cur - interrupt_last; + interrupt_last = interrupt_cur; + + if(interrupt_interval > (lis3dh_acc_misc_data->pdata->poll_interval*1000000) +(lis3dh_acc_misc_data->pdata->poll_interval*1000000) /4) + LOG_INFO("***sprd-gsensor SHED exception:count_intr=%d,interrupt_interval=%d,poll_interva=%d********\n",count_intr++,interrupt_interval,lis3dh_acc_misc_data->pdata->poll_interval); + + LOG_INFO("sprd-gsensor: -- %s 2: interrupt_interval=%d,poll_interval=%d,gsensor_flag=%d,acc->irq1_gpio_number=%d-- !\n",__func__,interrupt_interval,poll_interval,gsensor_flag,gpio_get_value(lis3dh_acc_misc_data->pdata->gpio_int1)); +#endif + gsensor_flag = 0; + set_current_state(TASK_RUNNING); + lis3dh_acc_update_data_func(); +#ifdef LIS3DH_DBG + time_mono = ktime_get(); + interrupt_read=ktime_to_ns(time_mono)-interrupt_cur; + if(interrupt_interval > (lis3dh_acc_misc_data->pdata->poll_interval*1000000) +(lis3dh_acc_misc_data->pdata->poll_interval*1000000) /4) + LOG_INFO("***sprd-gsensor SHED exception:count_intr=%d,interrupt_interval=%d,poll_interva=%d, interrupt_read=%lld********\n",count_intr++,interrupt_interval,lis3dh_acc_misc_data->pdata->poll_interval,interrupt_read); + + LOG_INFO("sprd-gsensor: -- %s 3: interrupt_read=%lld,poll_interval=%d,gsensor_flag=%d,acc->irq1_gpio_number=%d-- !\n",__func__,interrupt_read,poll_interval,gsensor_flag,gpio_get_value(lis3dh_acc_misc_data->pdata->gpio_int1)); +#endif + } while (!kthread_should_stop()); + + return 0; +} +#endif + + +static void lis3dh_acc_irq2_work_func(struct work_struct *work) +{ + + /*struct lis3dh_acc_data *acc = + container_of(work, struct lis3dh_acc_data, irq2_work); + */ + /* TODO add interrupt service procedure. + ie:lis3dh_acc_get_tap_source(acc); */ + ; + /* */ + + LOG_INFO("%s: IRQ2 triggered\n", LIS3DH_ACC_DEV_NAME); +} + +int lis3dh_acc_update_g_range(struct lis3dh_acc_data *acc, u8 new_g_range) +{ + int err; + + u8 sensitivity; + u8 buf[2]; + u8 updated_val; + u8 init_val; + u8 new_val; + u8 mask = LIS3DH_ACC_FS_MASK | HIGH_RESOLUTION; + + pr_debug("%s\n", __func__); + + switch (new_g_range) { + case LIS3DH_ACC_G_2G: + + sensitivity = SENSITIVITY_2G; + break; + case LIS3DH_ACC_G_4G: + + sensitivity = SENSITIVITY_4G; + break; + case LIS3DH_ACC_G_8G: + + sensitivity = SENSITIVITY_8G; + break; + case LIS3DH_ACC_G_16G: + + sensitivity = SENSITIVITY_16G; + break; + default: + dev_err(&acc->client->dev, "invalid g range requested: %u\n", + new_g_range); + return -EINVAL; + } + + if (atomic_read(&acc->enabled)) { + /* Set configuration register 4, which contains g range setting + * NOTE: this is a straight overwrite because this driver does + * not use any of the other configuration bits in this + * register. Should this become untrue, we will have to read + * out the value and only change the relevant bits --XX---- + * (marked by X) */ + buf[0] = CTRL_REG4; + err = lis3dh_acc_i2c_read(acc, buf, 1); + if (err < 0) + goto error; + init_val = buf[0]; + acc->resume_state[RES_CTRL_REG4] = init_val; + new_val = new_g_range | HIGH_RESOLUTION; + updated_val = ((mask & new_val) | ((~mask) & init_val)); + buf[1] = updated_val; + buf[0] = CTRL_REG4; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG4] = updated_val; + acc->sensitivity = sensitivity; + + pr_debug("%s sensitivity %d g-range %d\n", __func__, sensitivity,new_g_range); + } + + return 0; +error: + dev_err(&acc->client->dev, "update g range failed 0x%x,0x%x: %d\n", + buf[0], buf[1], err); + + return err; +} + +int lis3dh_acc_update_odr(struct lis3dh_acc_data *acc, int poll_interval_ms) +{ + int err = -1; + int i; + u8 config[2]; + + /* Convert the poll interval into an output data rate configuration + * that is as low as possible. The ordering of these checks must be + * maintained due to the cascading cut off values - poll intervals are + * checked from shortest to longest. At each check, if the next lower + * ODR cannot support the current poll interval, we stop searching */ + for (i = ARRAY_SIZE(lis3dh_acc_odr_table) - 1; i >= 0; i--) { + if (lis3dh_acc_odr_table[i].cutoff_ms <= poll_interval_ms) + break; + } + config[1] = lis3dh_acc_odr_table[i].mask; + + LOG_INFO("%s :ODR poll_interval_ms=%d, config[1]=0x%02x,cutoff_ms %d",__func__,poll_interval_ms,config[1],lis3dh_acc_odr_table[i].cutoff_ms ); + + config[1] |= LIS3DH_ACC_ENABLE_ALL_AXES; + + /* If device is currently enabled, we need to write new + * configuration out to it */ + if (atomic_read(&acc->enabled)) { + config[0] = CTRL_REG1; + err = lis3dh_acc_i2c_write(acc, config, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG1] = config[1]; + } + + return 0; + +error: + dev_err(&acc->client->dev, "update odr failed 0x%x,0x%x: %d\n", + config[0], config[1], err); + + return err; +} + +/* */ + +static int lis3dh_acc_register_write(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address, u8 new_value) +{ + int err = -1; + + if (atomic_read(&acc->enabled)) { + /* Sets configuration register at reg_address + * NOTE: this is a straight overwrite */ + buf[0] = reg_address; + buf[1] = new_value; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + return err; + } + return err; +} + +static int lis3dh_acc_register_update(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address, u8 mask, + u8 new_bit_values) +{ + int err = -1; + u8 init_val; + u8 updated_val; + err = lis3dh_acc_register_read(acc, buf, reg_address); + if (!(err < 0)) { + init_val = buf[1]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + err = lis3dh_acc_register_write(acc, buf, reg_address, + updated_val); + } + return err; +} + +/* */ + +static int lis3dh_acc_get_acceleration_data(struct lis3dh_acc_data *acc, + int *xyz) +{ + int err = -1; + /* Data bytes from hardware xL, xH, yL, yH, zL, zH */ + u8 acc_data[6]; + /* x,y,z hardware data */ + s16 hw_d[3] = { 0 }; + + acc_data[0] = (I2C_AUTO_INCREMENT | AXISDATA_REG); + err = lis3dh_acc_i2c_read(acc, acc_data, 6); + + if (err < 0) { + LOG_ERR("%s exception ssssI2C read error %d\n", LIS3DH_ACC_I2C_NAME, + err); + return err; + } + + hw_d[0] = (((s16) ((acc_data[1] << 8) | acc_data[0])) >> 4); + hw_d[1] = (((s16) ((acc_data[3] << 8) | acc_data[2])) >> 4); + hw_d[2] = (((s16) ((acc_data[5] << 8) | acc_data[4])) >> 4); + + hw_d[0] = hw_d[0] * acc->sensitivity; + hw_d[1] = hw_d[1] * acc->sensitivity; + hw_d[2] = hw_d[2] * acc->sensitivity; + + xyz[0] = ((acc->pdata->negate_x) ? (-hw_d[acc->pdata->axis_map_x]) + : (hw_d[acc->pdata->axis_map_x])); + xyz[1] = ((acc->pdata->negate_y) ? (-hw_d[acc->pdata->axis_map_y]) + : (hw_d[acc->pdata->axis_map_y])); + xyz[2] = ((acc->pdata->negate_z) ? (-hw_d[acc->pdata->axis_map_z]) + : (hw_d[acc->pdata->axis_map_z])); +#ifdef LIS3DH_DBG + LOG_INFO("%s read x=%d, y=%d, z=%d\n", + LIS3DH_ACC_DEV_NAME, xyz[0], xyz[1], xyz[2]); +#endif + + return err; +} + +static void lis3dh_acc_report_values(struct lis3dh_acc_data *acc, int *xyz) +{ + input_report_abs(acc->input_dev, ABS_X, xyz[0]); + input_report_abs(acc->input_dev, ABS_Y, xyz[1]); + input_report_abs(acc->input_dev, ABS_Z, xyz[2]); + input_sync(acc->input_dev); + #ifdef LIS3DH_DBG + acc->data_num++; + #endif +} + +static int lis3dh_acc_enable(struct lis3dh_acc_data *acc) +{ + int err; + + pr_debug("%s: pr_debug PRINT \n", __func__); + + LOG_INFO("sprd-gsensor: -- %s entry :acc->irq1_gpio_number=%d-- !\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + + if (!atomic_cmpxchg(&acc->enabled, 0, 1)) { + err = lis3dh_acc_device_power_on(acc); + LOG_INFO("sprd-gsensor: -- %s err=%d-- !\n",__func__,err); + if (err < 0) { + atomic_set(&acc->enabled, 0); + return err; + } + + LOG_INFO("sprd-gsensor: -- %s acc->hw_initialized=%d acc->irq1=%d-- !\n",__func__,acc->hw_initialized,acc->irq1); + if (acc->hw_initialized) { + if (acc->irq1 != 0){ + LOG_INFO("sprd-gsensor: -- %s after init : acc->irq1_gpio_number=%d-- !\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + enable_irq(acc->irq1); + } + if (acc->irq2 != 0) + enable_irq(acc->irq2); + LOG_INFO("%s: power on: irq enabled\n", + LIS3DH_ACC_DEV_NAME); + } + + #ifdef LIS3DH_DBG + acc->data_num = 0; + #endif + + /* + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata-> + poll_interval)); + */ +#ifndef GSENSOR_INTERRUPT_MODE + queue_delayed_work(_gSensorWork_workqueue, &acc->input_work, msecs_to_jiffies(acc->pdata->poll_interval)); +#endif + } + LOG_INFO("sprd-gsensor: -- %s exit :acc->irq1_gpio_number=%d-- !\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + LOG_INFO("sprd-gsensor: -- %s -- success!\n",__func__); + return 0; +} + +static int lis3dh_acc_disable(struct lis3dh_acc_data *acc) +{ + LOG_INFO("sprd-gsensor: -- %s --acc->hw_initialized=%d\n",__func__,acc->hw_initialized); + if (atomic_cmpxchg(&acc->enabled, 1, 0)) { + cancel_delayed_work_sync(&acc->input_work); + + if (acc->pdata->power_off) { + if (acc->irq1 != 0) + disable_irq_nosync(acc->irq1); + if (acc->irq2 != 0) + disable_irq_nosync(acc->irq2); + acc->pdata->power_off(); + acc->hw_initialized = 0; + } + if (acc->hw_initialized) { + if (acc->irq1 != 0) + disable_irq_nosync(acc->irq1); + if (acc->irq2 != 0) + disable_irq_nosync(acc->irq2); + //only init once during probe, the sensor is ALWAYS power supply + // acc->hw_initialized = 0; + } + lis3dh_acc_device_power_off(acc); + } +#ifdef LIS3DH_DBG + LOG_INFO("******sprd-gsensor: -- %s --acc->data_num =%lld ,acc->pdata->poll_interval =%d****\n",__func__,acc->data_num,acc->pdata->poll_interval); + acc->data_num = 0; +#endif + return 0; +} + +static int lis3dh_acc_misc_open(struct inode *inode, struct file *file) +{ + int err; + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + file->private_data = lis3dh_acc_misc_data; + + return 0; +} + +static long lis3dh_acc_misc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + u8 buf[4]; + u8 mask; + u8 reg_address; + u8 bit_values; + int err; + int interval; + int xyz[3] = { 0 }; + struct lis3dh_acc_data *acc = file->private_data; + + + LOG_INFO("%s :ioctl cmd %d\n",__func__, _IOC_NR(cmd)); + switch (cmd) { + case LIS3DH_ACC_IOCTL_GET_DELAY: + interval = acc->pdata->poll_interval; + if (copy_to_user(argp, &interval, sizeof(interval))) + return -EFAULT; + break; + + case LIS3DH_ACC_IOCTL_SET_DELAY: + if (copy_from_user(&interval, argp, sizeof(interval))) + return -EFAULT; + if (interval < 0 || interval > 1000) + return -EINVAL; + interval = interval/2; + acc->pdata->poll_interval = max(interval,acc->pdata->min_interval); // max the frequecy of g sensor + LOG_INFO(" %s,acc->pdata->poll_interval =%d,interval=%d,acc->pdata->min_interval=%d\n", __func__,acc->pdata->poll_interval,interval,acc->pdata->min_interval ); + err = lis3dh_acc_update_odr(acc, acc->pdata->poll_interval); + /* TODO: if update fails poll is still set */ + if (err < 0) + return err; + break; + + case LIS3DH_ACC_IOCTL_SET_ENABLE: + if (copy_from_user(&interval, argp, sizeof(interval))) + return -EFAULT; + if (interval > 1) + return -EINVAL; + if (interval) + err = lis3dh_acc_enable(acc); + else + err = lis3dh_acc_disable(acc); + return err; + break; + + case LIS3DH_ACC_IOCTL_GET_ENABLE: + interval = atomic_read(&acc->enabled); + if (copy_to_user(argp, &interval, sizeof(interval))) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_SET_G_RANGE: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + bit_values = buf[0]; + err = lis3dh_acc_update_g_range(acc, bit_values); + if (err < 0) + return err; + break; + +#ifdef INTERRUPT_MANAGEMENT + case LIS3DH_ACC_IOCTL_SET_CTRL_REG3: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = CTRL_REG3; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_CTRL_REG3] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_CTRL_REG3])); + break; + + case LIS3DH_ACC_IOCTL_SET_CTRL_REG6: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = CTRL_REG6; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_CTRL_REG6] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_CTRL_REG6])); + break; + + case LIS3DH_ACC_IOCTL_SET_DURATION1: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_DUR1; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_DUR1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_DUR1])); + break; + + case LIS3DH_ACC_IOCTL_SET_THRESHOLD1: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_THS1; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_THS1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_THS1])); + break; + + case LIS3DH_ACC_IOCTL_SET_CONFIG1: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = INT_CFG1; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_CFG1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_CFG1])); + break; + + case LIS3DH_ACC_IOCTL_GET_SOURCE1: + err = lis3dh_acc_register_read(acc, buf, INT_SRC1); + if (err < 0) + return err; + + pr_debug("INT1_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_SET_DURATION2: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_DUR2; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_DUR2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_DUR2])); + break; + + case LIS3DH_ACC_IOCTL_SET_THRESHOLD2: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_THS2; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_THS2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_THS2])); + break; + + case LIS3DH_ACC_IOCTL_SET_CONFIG2: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = INT_CFG2; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_CFG2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_CFG2])); + break; + + case LIS3DH_ACC_IOCTL_GET_SOURCE2: + err = lis3dh_acc_register_read(acc, buf, INT_SRC2); + if (err < 0) + return err; + + pr_debug("INT2_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_GET_TAP_SOURCE: + err = lis3dh_acc_register_read(acc, buf, TT_SRC); + if (err < 0) + return err; + pr_debug("TT_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) { + pr_err("%s: %s error in copy_to_user \n", + LIS3DH_ACC_DEV_NAME, __func__); + return -EINVAL; + } + break; + case LIS3DH_ACC_IOCTL_GET_COOR_XYZ: + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + return err; + + if (copy_to_user(argp, xyz, sizeof(xyz))) { + pr_err(" %s %d error in copy_to_user \n", + __func__, __LINE__); + return -EINVAL; + } + break; + case LIS3DH_ACC_IOCTL_SET_TAP_CFG: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_CFG; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_CFG] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_CFG])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TLIM: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_LIM; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_LIM] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_LIM])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_THS: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_THS; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_THS] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_THS])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TLAT: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_TLAT; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_TLAT] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_TLAT])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TW: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_TW; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_TW] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_TW])); + break; + +#endif /* INTERRUPT_MANAGEMENT */ + case LIS3DH_ACC_IOCTL_GET_CHIP_ID: + { + u8 devid = 0; + u8 devinfo[DEVICE_INFO_LEN] = {0}; + err = lis3dh_acc_register_read(acc, &devid, WHO_AM_I); + if (err < 0) { + LOG_INFO("%s, error read register WHO_AM_I\n", __func__); + return -EAGAIN; + } + sprintf(devinfo, "%s, %#x", DEVICE_INFO, devid); + + if (copy_to_user(argp, devinfo, sizeof(devinfo))) { + printk("%s error in copy_to_user(IOCTL_GET_CHIP_ID)\n", __func__); + return -EINVAL; + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct file_operations lis3dh_acc_misc_fops = { + .owner = THIS_MODULE, + .open = lis3dh_acc_misc_open, + .unlocked_ioctl = lis3dh_acc_misc_ioctl, +}; + +static struct miscdevice lis3dh_acc_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = LIS3DH_ACC_DEV_NAME, + .fops = &lis3dh_acc_misc_fops, +}; + + +#ifndef GSENSOR_INTERRUPT_MODE + +static void lis3dh_acc_input_work_func(struct work_struct *work) +{ + struct lis3dh_acc_data *acc; + + int xyz[3] = { 0 }; + int err; + static int count_intr = 0; + static s64 interrupt_last = 0; + static s64 interrupt_cur = 0; + ktime_t time_mono; + time_mono = ktime_get(); + interrupt_cur = ktime_to_ns(time_mono); + int interrupt_interval = interrupt_cur - interrupt_last; + interrupt_last = interrupt_cur; + +// LOG_INFO("ENTRY----->%s\n",__func__); + acc = container_of((struct delayed_work *)work, + struct lis3dh_acc_data, input_work); + + mutex_lock(&acc->lock); + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + dev_err(&acc->client->dev, "get_acceleration_data failed\n"); + else + lis3dh_acc_report_values(acc, xyz); +// LOG_INFO(KERN_ERR "lihai: sprd-gsensor count_intr=%8d interrupt_interval=%8d, poll_interval=%d, err=%d \n", count_intr++, interrupt_interval, acc->pdata->poll_interval, err); +#if 0 + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata->poll_interval)); +#else + queue_delayed_work(_gSensorWork_workqueue, &acc->input_work, msecs_to_jiffies(acc->pdata->poll_interval)); +#endif + mutex_unlock(&acc->lock); + +// LOG_INFO("EXIT----->%s\n\n",__func__); +} +#endif + +#ifdef LIS3DH_OPEN_ENABLE +int lis3dh_acc_input_open(struct input_dev *input) +{ + struct lis3dh_acc_data *acc = input_get_drvdata(input); + + return lis3dh_acc_enable(acc); +} + +void lis3dh_acc_input_close(struct input_dev *dev) +{ + struct lis3dh_acc_data *acc = input_get_drvdata(dev); + + lis3dh_acc_disable(acc); +} +#endif + +static int lis3dh_acc_validate_pdata(struct lis3dh_acc_data *acc) +{ + acc->pdata->poll_interval = max(acc->pdata->poll_interval, + acc->pdata->min_interval); + + if (acc->pdata->axis_map_x > 2 || acc->pdata->axis_map_y > 2 + || acc->pdata->axis_map_z > 2) { + dev_err(&acc->client->dev, "invalid axis_map value " + "x:%u y:%u z%u\n", acc->pdata->axis_map_x, + acc->pdata->axis_map_y, acc->pdata->axis_map_z); + return -EINVAL; + } + + /* Only allow 0 and 1 for negation boolean flag */ + if (acc->pdata->negate_x > 1 || acc->pdata->negate_y > 1 + || acc->pdata->negate_z > 1) { + dev_err(&acc->client->dev, "invalid negate value " + "x:%u y:%u z:%u\n", acc->pdata->negate_x, + acc->pdata->negate_y, acc->pdata->negate_z); + return -EINVAL; + } + + /* Enforce minimum polling interval */ + if (acc->pdata->poll_interval < acc->pdata->min_interval) { + dev_err(&acc->client->dev, "minimum poll interval violated\n"); + return -EINVAL; + } + + return 0; +} + +static int lis3dh_acc_input_init(struct lis3dh_acc_data *acc) +{ + int err; + +#ifndef GSENSOR_INTERRUPT_MODE + /* Polling rx data when the interrupt is not used.*/ + if (1 /*acc->irq1 == 0 && acc->irq1 == 0 */ ) { + INIT_DELAYED_WORK(&acc->input_work, lis3dh_acc_input_work_func); + _gSensorWork_workqueue = create_singlethread_workqueue("acc_singlethread"); + if (!_gSensorWork_workqueue) { + err = -ESRCH; + printk(KERN_ERR "acc: create_singlethread_workqueue\n"); + return err; + } + } +#endif + + acc->input_dev = input_allocate_device(); + if (!acc->input_dev) { + err = -ENOMEM; + dev_err(&acc->client->dev, "input device allocate failed\n"); + goto err0; + } +#ifdef LIS3DH_ACC_OPEN_ENABLE + acc->input_dev->open = lis3dh_acc_input_open; + acc->input_dev->close = lis3dh_acc_input_close; +#endif + + input_set_drvdata(acc->input_dev, acc); + + set_bit(EV_ABS, acc->input_dev->evbit); + /* next is used for interruptA sources data if the case */ + set_bit(ABS_MISC, acc->input_dev->absbit); + /* next is used for interruptB sources data if the case */ + set_bit(ABS_WHEEL, acc->input_dev->absbit); + + input_set_abs_params(acc->input_dev, ABS_X, -G_MAX, G_MAX, 0, 0); + input_set_abs_params(acc->input_dev, ABS_Y, -G_MAX, G_MAX, 0, 0); + input_set_abs_params(acc->input_dev, ABS_Z, -G_MAX, G_MAX, 0, 0); + /* next is used for interruptA sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_MISC, INT_MIN, INT_MAX, 0, 0); + /* next is used for interruptB sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_WHEEL, INT_MIN, INT_MAX, 0, 0); + + acc->input_dev->name = "accelerometer"; + + err = input_register_device(acc->input_dev); + if (err) { + dev_err(&acc->client->dev, + "unable to register input polled device %s\n", + acc->input_dev->name); + goto err1; + } + + return 0; + +err1: + input_free_device(acc->input_dev); +err0: + return err; +} + +static void lis3dh_acc_input_cleanup(struct lis3dh_acc_data *acc) +{ + input_unregister_device(acc->input_dev); + input_free_device(acc->input_dev); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lis3dh_early_suspend(struct early_suspend *es); +static void lis3dh_early_resume(struct early_suspend *es); +#endif + +#ifdef CONFIG_OF +static struct lis3dh_acc_platform_data *lis3dh_acc_parse_dt(struct device *dev) +{ + struct lis3dh_acc_platform_data *pdata; + struct device_node *np = dev->of_node; + int ret; + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "Could not allocate struct lis3dh_acc_platform_data"); + return NULL; + } + + pdata->gpio_int1 = of_get_gpio(np, 0); + LOG_INFO(" %s:get gpio_int1 %d\n",__func__,pdata->gpio_int1); + if(pdata->gpio_int1 < 0){ + dev_err(dev, "fail to get irq_gpio_number\n"); + LOG_INFO(" get irq1_gpio_number fail\n"); + goto fail; + } + ret = of_property_read_u32(np, "poll_interval", &pdata->poll_interval); + LOG_INFO("%s: get pdata->poll_interval %d\n",__func__,pdata->poll_interval); + if(ret){ + dev_err(dev, "fail to get poll_interval\n"); + goto fail; + } + ret = of_property_read_u32(np, "min_interval", &pdata->min_interval); + if(ret){ + dev_err(dev, "fail to get min_interval\n"); + goto fail; + } + ret = of_property_read_u32(np, "g_range", &pdata->g_range); + if(ret){ + dev_err(dev, "fail to get g_range\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_x", &pdata->axis_map_x); + if(ret){ + dev_err(dev, "fail to get axis_map_x\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_y", &pdata->axis_map_y); + if(ret){ + dev_err(dev, "fail to get axis_map_y\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_z", &pdata->axis_map_z); + if(ret){ + dev_err(dev, "fail to get axis_map_z\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_x", &pdata->negate_x); + if(ret){ + dev_err(dev, "fail to get negate_x\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_y", &pdata->negate_y); + if(ret){ + dev_err(dev, "fail to get negate_y\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_z", &pdata->negate_z); + if(ret){ + dev_err(dev, "fail to get negate_z\n"); + goto fail; + } + return pdata; +fail: + kfree(pdata); + return NULL; +} +#endif + + + + +#ifdef LIS3DH_DBG +static int str2int(char *p, char *end) +{ + int result = 0; + int len = end - p + 1; + int c; + + for(; len > 0; len--, p++) { + if(p > end) + return -1; + c = *p - '0'; + if((unsigned int)c >= 10) + return -1; + result = result * 10 + c; + } + return result; +} + +/*parse a int from the string*/ +static int get_int(char *p, u8 max_len) +{ + char *start = p; + char *end =NULL; + + if(max_len > 60) { + LOG_ERR("CMD ERROR!the max length of the cmd should less than 60\n"); + return -1; + } + for(; max_len > 0; max_len--, p++) { + if(*p >= '0' && *p <= '9') { + start = p; + break; + } + } + if(0 == max_len) + return -1; + + end = start; + + for(; max_len > 0; max_len--, p++) { + if(*p >= '0' && *p <= '9') { + end = p; + continue; + } + break; + } + + return str2int(start, end); +} + +static ssize_t lis3dh_val_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + LOG_FUN( ); + + return sprintf(buff, "acc->pdata->poll_interva=%d\n",lis3dh_acc_misc_data->pdata->poll_interval); +} +// adjust poll interval dynamiclly,eg echo 5>val_show ,means poll interval is 5ms +static ssize_t lis3dh_val_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t n) +{ + LOG_FUN( ); + char *buff_temp = NULL; + int interval; + int err; + + buff_temp = kmalloc(n, GFP_KERNEL); + if(NULL == buff_temp){ + LOG_ERR("kmalloc err\n"); + return n; + } + + memcpy(buff_temp, buff, n); + interval = get_int(buff_temp, n); + + if (interval < 0 || interval > 1000) + return -EINVAL; + + lis3dh_acc_misc_data->pdata->poll_interval = max(interval,lis3dh_acc_misc_data->pdata->min_interval); // max the frequecy of g sensor + LOG_INFO("yuebao %s,acc->pdata->poll_interval =%d,interval=%d,acc->pdata->min_interval=%d\n", __func__,lis3dh_acc_misc_data->pdata->poll_interval,interval,lis3dh_acc_misc_data->pdata->min_interval ); + err = lis3dh_acc_update_odr(lis3dh_acc_misc_data, lis3dh_acc_misc_data->pdata->poll_interval ); + /* TODO: if update fails poll is still set */ + // if (err < 0) + // return err; + if(buff_temp != NULL) + kfree(buff_temp); + + return 0; +} + +/* +1 should first write ,eg echo 0x20=0x97>reg +2 then read ,eg cat reg:Addr: 0x20 Val: 0x97 +*/ +static ssize_t sensor_reg_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + int err; + u8 value = 0; + + LOG_FUN( ); + + err = lis3dh_acc_register_read(lis3dh_acc_misc_data, &value, lis3dh_acc_misc_data->debug.diag_reg_addr_wr); + return sprintf(buff, "Addr: 0x%x Val: 0x%x\n", + lis3dh_acc_misc_data->debug.diag_reg_addr_wr, value); +} + +static ssize_t sensor_reg_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t n) +{ + char buf[32]; + char *sptr, *token; + unsigned int len = 0; + u8 reg_addr, reg_val; + int err = -1; + + LOG_INFO("%s, user echo is:%s\n",__func__,buff); + len = min(n, sizeof(buf) - 1); + if (!memcpy(buf, buff, len)){ + LOG_INFO("%s, %s\n",__func__,buf); + return -EFAULT; + } + + buf[len] = '\0'; + sptr = buf; + + token = strsep(&sptr, "="); + LOG_INFO("%s, token=%s\n",__func__,token); + + if (!token) + return -EINVAL; + + LOG_INFO("%s,now token=%s,sptr=%s\n",__func__,token,sptr); + + if (kstrtou8(token, 0, ®_addr)){ + LOG_INFO("%s,now reg_addr=0x%x\n",__func__,reg_addr); + return -EINVAL; + } +// if (!ath6kl_dbg_is_diag_reg_valid(reg_addr)) +// return -EINVAL; + + if (kstrtou8(sptr, 0, ®_val)){ + LOG_INFO("%s,now reg_val=0x%x\n",__func__,reg_val); + return -EINVAL; + } + LOG_INFO("%s,now reg_addr=0x%x,reg_val=0x%x\n",__func__,reg_addr,reg_val); + + lis3dh_acc_misc_data->debug.diag_reg_addr_wr = reg_addr; + lis3dh_acc_misc_data->debug.diag_reg_val_wr = reg_val; + + buf[0] = reg_addr; + buf[1] = reg_val; + + err = lis3dh_acc_i2c_write(lis3dh_acc_misc_data, buf, 1); + if (err < 0){ + LOG_ERR("%s: %d\n",__func__,err); + return -EIO; + } + //resume_state[index] index is not reg addr, so ONLY CAN config CTRL_REG1~CTRL_REG6 + lis3dh_acc_misc_data->resume_state[reg_addr-0x20] = reg_val; + LOG_INFO("%s,now n=%d\n",__func__,n); + return n; +} +static struct kobject *lis3dh_kobj; +static struct kobj_attribute lis3dh_val_attr = + __ATTR(show_val, 0644, lis3dh_val_show, lis3dh_val_store); +static struct kobj_attribute sensor_reg_attr = + __ATTR(reg, 0644, sensor_reg_show, sensor_reg_store); + + +static struct attribute *sensor_sysfs_attrs[] = { + &lis3dh_val_attr.attr, + &sensor_reg_attr.attr, + + NULL +}; + +static struct attribute_group sensor_attribute_group = { + .attrs = sensor_sysfs_attrs, +}; + + +static int lis3dh_sysfs_init(struct lis3dh_acc_data *acc) +{ + int ret = -1; + + lis3dh_kobj = kobject_create_and_add("lis3dh", &(acc->input_dev->dev.kobj)); + if (lis3dh_kobj == NULL) { + ret = -ENOMEM; + LOG_ERR("register sysfs failed. ret = %d\n", ret); + return ret; + } + ret = sysfs_create_group(lis3dh_kobj, &sensor_attribute_group); + if (ret) { + LOG_ERR("create sysfs failed. ret = %d\n", ret); + return ret; + } + return ret; +} + +#endif + +static int lis3dh_acc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + + struct lis3dh_acc_data *acc; + struct lis3dh_acc_platform_data *pdata = client->dev.platform_data; + + int err = -1; + int tempvalue; + LOG_INFO("sprd-gsensor: -- %s -- start !\n",__func__); + pr_debug("%s: probe start.\n", LIS3DH_ACC_DEV_NAME); + + +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; + if (np && !pdata){ + pdata = lis3dh_acc_parse_dt(&client->dev); + if(pdata){ + client->dev.platform_data = pdata; + } + if(!pdata){ + err = -ENOMEM; + goto exit_alloc_platform_data_failed; + } + } +#endif + /* + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "platform data is NULL. exiting.\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "client not i2c capable\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "client not smb-i2c capable:2\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&client->dev, "client not smb-i2c capable:3\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + /* + * OK. From now, we presume we have a valid client. We now create the + * client structure, even though we cannot fill it completely yet. + */ + + acc = kzalloc(sizeof(struct lis3dh_acc_data), GFP_KERNEL); + if (acc == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for module data: " + "%d\n", err); + goto exit_alloc_data_failed; + } + + mutex_init(&acc->lock); + mutex_lock(&acc->lock); + + acc->client = client; + lis3dh_i2c_client = client; + i2c_set_clientdata(client, acc); + + LOG_INFO("gpio pin request (%d)\n", pdata->gpio_int1); + err=gpio_request(pdata->gpio_int1, "GSENSOR_INT1"); + + if (err) { + LOG_ERR("gpio pin request fail (%d)\n", err); + goto err_free_irq1; + } else { + gpio_direction_input(pdata->gpio_int1); + LOG_INFO("sprd-gsensor: -- %s acc->irq1_gpio_number status is [%d]-- !\n",__func__,gpio_get_value(pdata->gpio_int1)); + /*get irq*/ + acc->irq1 = gpio_to_irq(pdata->gpio_int1); + LOG_INFO("IRQ1 number is %d\n", acc->irq1 ); + } + if (acc->irq1 != 0) { + pr_debug("%s request irq1\n", __func__); + err = + request_irq(acc->irq1, lis3dh_acc_isr1, IRQF_TRIGGER_HIGH| IRQF_NO_SUSPEND , + "lis3dh_acc_irq1", acc); + if (err < 0) { + dev_err(&client->dev, "request irq1 failed: %d\n", err); + goto err_mutexunlockfreedata; + } + disable_irq_nosync(acc->irq1); + + INIT_WORK(&acc->irq1_work, lis3dh_acc_irq1_work_func); + acc->irq1_work_queue = + create_singlethread_workqueue("lis3dh_acc_wq1"); + if (!acc->irq1_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, "cannot create work queue1: %d\n", + err); + goto err_free_irq1; + } + } + + +// gpio_request(GSENSOR_GINT2_GPI, "GSENSOR_INT2"); + acc->irq2 = 0; /* gpio_to_irq(GSENSOR_GINT2_GPI); */ + if (acc->irq2 != 0) { + err = + request_irq(acc->irq2, lis3dh_acc_isr2, IRQF_TRIGGER_RISING, + "lis3dh_acc_irq2", acc); + if (err < 0) { + dev_err(&client->dev, "request irq2 failed: %d\n", err); + goto err_destoyworkqueue1; + } + disable_irq_nosync(acc->irq2); + +/* Create workqueue for IRQ.*/ + + INIT_WORK(&acc->irq2_work, lis3dh_acc_irq2_work_func); + acc->irq2_work_queue = + create_singlethread_workqueue("lis3dh_acc_wq2"); + if (!acc->irq2_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, "cannot create work queue2: %d\n", + err); + goto err_free_irq2; + } + } + + acc->pdata = kmalloc(sizeof(*acc->pdata), GFP_KERNEL); + if (acc->pdata == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for pdata: %d\n", err); + goto err_destoyworkqueue2; + } + + memcpy(acc->pdata, client->dev.platform_data, sizeof(*acc->pdata)); + + err = lis3dh_acc_validate_pdata(acc); + if (err < 0) { + dev_err(&client->dev, "failed to validate platform data\n"); + goto exit_kfree_pdata; + } + +#ifdef GSENSOR_INTERRUPT_MODE +memset(acc->resume_state, 0, ARRAY_SIZE(acc->resume_state)); + + acc->resume_state[RES_CTRL_REG1] = LIS3DH_ACC_ENABLE_ALL_AXES; + acc->resume_state[RES_CTRL_REG2] = 0x00; + acc->resume_state[RES_CTRL_REG3] = CTRL_REG3_I1_DRDY1;//0x00; + acc->resume_state[RES_CTRL_REG4] = 0x00; + acc->resume_state[RES_CTRL_REG5] = CTRL_REG5_LIR_INT1;//0x00; + acc->resume_state[RES_CTRL_REG6] = CTRL_REG6_HLACTIVE;//0x00; + + acc->resume_state[RES_TEMP_CFG_REG] = 0x00; + acc->resume_state[RES_FIFO_CTRL_REG] = 0x00; + acc->resume_state[RES_INT_CFG1] = 0x00; + acc->resume_state[RES_INT_THS1] = 0x00; + acc->resume_state[RES_INT_DUR1] = 0x00; + acc->resume_state[RES_INT_CFG2] = 0x00; + acc->resume_state[RES_INT_THS2] = 0x00; + acc->resume_state[RES_INT_DUR2] = 0x00; + + acc->resume_state[RES_TT_CFG] = 0x00; + acc->resume_state[RES_TT_THS] = 0x00; + acc->resume_state[RES_TT_LIM] = 0x00; + acc->resume_state[RES_TT_TLAT] = 0x00; + acc->resume_state[RES_TT_TW] = 0x00; + +#endif + sprd_i2c_ctl_chg_clk(client->adapter->nr, 400000); + + + + + err = lis3dh_acc_device_power_on(acc); + if (err < 0) { + dev_err(&client->dev, "power on failed: %d\n", err); + goto err2; + } + +#if 1 + if (i2c_smbus_read_byte(client) < 0) { + pr_err("i2c_smbus_read_byte error!!\n"); + goto err_destoyworkqueue2; + } else { + pr_debug("%s Device detected!\n", LIS3DH_ACC_DEV_NAME); + } +#endif + /* read chip id */ + + tempvalue = i2c_smbus_read_word_data(client, WHO_AM_I); + + if ((tempvalue & 0x00FF) == WHOAMI_LIS3DH_ACC) { + pr_debug("%s I2C driver registered!\n", + LIS3DH_ACC_DEV_NAME); + } else { + acc->client = NULL; + pr_debug("I2C driver not registered!" + " Device unknown 0x%x\n", tempvalue); + goto err_destoyworkqueue2; + } + + i2c_set_clientdata(client, acc); + + if (acc->pdata->init) { + err = acc->pdata->init(); + if (err < 0) { + dev_err(&client->dev, "init failed: %d\n", err); + goto err2; + } + } +#ifndef GSENSOR_INTERRUPT_MODE + memset(acc->resume_state, 0, ARRAY_SIZE(acc->resume_state)); + + acc->resume_state[RES_CTRL_REG1] = LIS3DH_ACC_ENABLE_ALL_AXES; + acc->resume_state[RES_CTRL_REG2] = 0x00; + acc->resume_state[RES_CTRL_REG3] = CTRL_REG3_I1_DRDY1;//0x00; + acc->resume_state[RES_CTRL_REG4] = 0x00; + acc->resume_state[RES_CTRL_REG5] = 0x00;//CTRL_REG5_LIR_INT1;//0x04; + acc->resume_state[RES_CTRL_REG6] = CTRL_REG6_HLACTIVE;//0x00; + + acc->resume_state[RES_TEMP_CFG_REG] = 0x00; + acc->resume_state[RES_FIFO_CTRL_REG] = 0x00; + acc->resume_state[RES_INT_CFG1] = 0x00; + acc->resume_state[RES_INT_THS1] = 0x00; + acc->resume_state[RES_INT_DUR1] = 0x00; + acc->resume_state[RES_INT_CFG2] = 0x00; + acc->resume_state[RES_INT_THS2] = 0x00; + acc->resume_state[RES_INT_DUR2] = 0x00; + + acc->resume_state[RES_TT_CFG] = 0x00; + acc->resume_state[RES_TT_THS] = 0x00; + acc->resume_state[RES_TT_LIM] = 0x00; + acc->resume_state[RES_TT_TLAT] = 0x00; + acc->resume_state[RES_TT_TW] = 0x00; + +#endif + atomic_set(&acc->enabled, 1); + + err = lis3dh_acc_update_g_range(acc, acc->pdata->g_range); + if (err < 0) { + dev_err(&client->dev, "update_g_range failed\n"); + goto err_power_off; + } + + err = lis3dh_acc_update_odr(acc, acc->pdata->poll_interval); + if (err < 0) { + dev_err(&client->dev, "update_odr failed\n"); + goto err_power_off; + } + + err = lis3dh_acc_input_init(acc); + if (err < 0) { + dev_err(&client->dev, "input init failed\n"); + goto err_power_off; + } + lis3dh_acc_misc_data = acc; + + err = misc_register(&lis3dh_acc_misc_device); + if (err < 0) { + dev_err(&client->dev, + "misc LIS3DH_ACC_DEV_NAME register failed\n"); + goto err_input_cleanup; + } + + lis3dh_acc_device_power_off(acc); + + /* As default, do not report information */ + atomic_set(&acc->enabled, 0); +/* +#ifdef CONFIG_HAS_EARLYSUSPEND + acc->early_suspend.suspend = lis3dh_early_suspend; + acc->early_suspend.resume = lis3dh_early_resume; + acc->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; + register_early_suspend(&acc->early_suspend); +#endif +*/ +#ifdef LIS3DH_DBG + err = lis3dh_sysfs_init(acc); + if(err) { + LOG_ERR("lis3dh_sysfs_init failed!\n"); + goto exit_lis3dh_sysfs_init_failed; + } +#endif +#if USE_WAIT_QUEUE + thread = kthread_run(gsensor_event_handler, 0, "gsensor-wait"); + if (IS_ERR(thread)) + { + err = PTR_ERR(thread); + LOG_ERR("failed to create kernel thread: %d\n", err); + } +#endif + mutex_unlock(&acc->lock); + dev_info(&client->dev, "###%s###\n", __func__); + LOG_INFO("sprd-gsensor: -- %s exit :acc->irq1_gpio_number status is [%d]-- !\n",__func__,gpio_get_value(acc->pdata->gpio_int1)); + LOG_INFO("sprd-gsensor: -- %s -- success !\n",__func__); + return 0; + +#ifdef LIS3DH_DBG +exit_lis3dh_sysfs_init_failed: +#endif + +err_input_cleanup: + lis3dh_acc_input_cleanup(acc); +err_power_off: + lis3dh_acc_device_power_off(acc); +err2: + if (acc->pdata->exit) + acc->pdata->exit(); +exit_kfree_pdata: + kfree(acc->pdata); +err_destoyworkqueue2: + if (acc->irq2_work_queue) + destroy_workqueue(acc->irq2_work_queue); +err_free_irq2: + if (acc->irq2) { + free_irq(acc->irq2, acc); + gpio_free(GSENSOR_GINT2_GPI); + } +err_destoyworkqueue1: + if (acc->irq1_work_queue) + destroy_workqueue(acc->irq1_work_queue); +err_free_irq1: + if (acc->irq1) { + free_irq(acc->irq1, acc); + gpio_free(acc->pdata->gpio_int1); + } +err_mutexunlockfreedata: + i2c_set_clientdata(client, NULL); + mutex_unlock(&acc->lock); + kfree(acc); + lis3dh_acc_misc_data = NULL; +exit_alloc_data_failed: +exit_check_functionality_failed: + pr_err("%s: Driver Init failed\n", LIS3DH_ACC_DEV_NAME); +exit_alloc_platform_data_failed: + return err; +} + +static int lis3dh_acc_remove(struct i2c_client *client) +{ + + /* TODO: revisit ordering here once _probe order is finalized */ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + if (acc != NULL) { + if (acc->irq1) { + free_irq(acc->irq1, acc); + gpio_free(acc->pdata->gpio_int1); + } + if (acc->irq2) { + free_irq(acc->irq2, acc); + gpio_free(GSENSOR_GINT2_GPI); + } + + if (acc->irq1_work_queue) + destroy_workqueue(acc->irq1_work_queue); + if (acc->irq2_work_queue) + destroy_workqueue(acc->irq2_work_queue); + misc_deregister(&lis3dh_acc_misc_device); + lis3dh_acc_input_cleanup(acc); + lis3dh_acc_device_power_off(acc); + if (acc->pdata->exit) + acc->pdata->exit(); + kfree(acc->pdata); + kfree(acc); + } + + return 0; +} + +static int lis3dh_acc_resume(struct i2c_client *client) +{ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + + LOG_INFO("sprd-gsensor: -- %s -- !\n",__func__); + dev_dbg(&client->dev, "###%s###\n", __func__); + if (acc != NULL && acc->on_before_suspend) + return lis3dh_acc_enable(acc); + + return 0; +} + +static int lis3dh_acc_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + + LOG_INFO("sprd-gsensor: -- %s -- !\n",__func__); + dev_dbg(&client->dev, "###%s###\n", __func__); + if (acc != NULL) { + acc->on_before_suspend = atomic_read(&acc->enabled); + return lis3dh_acc_disable(acc); + } + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND + +static void lis3dh_early_suspend(struct early_suspend *es) +{ + lis3dh_acc_suspend(lis3dh_i2c_client, (pm_message_t) { + .event = 0}); +} + +static void lis3dh_early_resume(struct early_suspend *es) +{ + lis3dh_acc_resume(lis3dh_i2c_client); +} + +#endif /* CONFIG_HAS_EARLYSUSPEND */ + +static const struct i2c_device_id lis3dh_acc_id[] += { {LIS3DH_ACC_DEV_NAME, 0}, {}, }; + +MODULE_DEVICE_TABLE(i2c, lis3dh_acc_id); + +static const struct of_device_id lis3dh_acc_of_match[] = { + { .compatible = "ST,lis3dh_acc", }, + { } +}; +MODULE_DEVICE_TABLE(of, lis3dh_acc_of_match); +static struct i2c_driver lis3dh_acc_driver = { + .driver = { + .name = LIS3DH_ACC_I2C_NAME, + .of_match_table = lis3dh_acc_of_match, + }, + .probe = lis3dh_acc_probe, + .remove = lis3dh_acc_remove, + .resume = lis3dh_acc_resume, + .suspend = lis3dh_acc_suspend, + .id_table = lis3dh_acc_id, +}; + + +static int __init lis3dh_acc_init(void) +{ + pr_debug("%s accelerometer driver: init\n", LIS3DH_ACC_I2C_NAME); + + return i2c_add_driver(&lis3dh_acc_driver); +} + +static void __exit lis3dh_acc_exit(void) +{ + pr_debug("%s accelerometer driver exit\n", LIS3DH_ACC_DEV_NAME); + + i2c_del_driver(&lis3dh_acc_driver); + return; +} + +module_init(lis3dh_acc_init); +module_exit(lis3dh_acc_exit); + +MODULE_DESCRIPTION("lis3dh accelerometer misc driver"); +MODULE_AUTHOR("STMicroelectronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/lis3dh_zt.c b/drivers/input/misc/lis3dh_zt.c new file mode 100644 index 00000000..c0494cb3 --- /dev/null +++ b/drivers/input/misc/lis3dh_zt.c @@ -0,0 +1,1729 @@ +/******************** (C) COPYRIGHT 2010 STMicroelectronics ******************** + * + * File Name : lis3dh_acc.c + * Authors : MSH - Motion Mems BU - Application Team + * : Carmine Iascone (carmine.iascone@st.com) + * : Matteo Dameno (matteo.dameno@st.com) + * Version : V.1.0.5 + * Date : 16/08/2010 + * Description : LIS3DH accelerometer sensor API + * + ******************************************************************************* + * + * 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. + * + * THE PRESENT SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES + * OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, FOR THE SOLE + * PURPOSE TO SUPPORT YOUR APPLICATION DEVELOPMENT. + * AS A RESULT, STMICROELECTRONICS SHALL NOT BE HELD LIABLE FOR ANY DIRECT, + * INDIRECT OR CONSEQUENTIAL DAMAGES WITH RESPECT TO ANY CLAIMS ARISING FROM THE + * CONTENT OF SUCH SOFTWARE AND/OR THE USE MADE BY CUSTOMERS OF THE CODING + * INFORMATION CONTAINED HEREIN IN CONNECTION WITH THEIR PRODUCTS. + * + * THIS SOFTWARE IS SPECIFICALLY DESIGNED FOR EXCLUSIVE USE WITH ST PARTS. + * + ****************************************************************************** + Revision 1.0.0 05/11/09 + First Release + Revision 1.0.3 22/01/2010 + Linux K&R Compliant Release; + Revision 1.0.5 16/08/2010 + modified _get_acceleration_data function + modified _update_odr function + manages 2 interrupts + + ******************************************************************************/ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/i2c.h> + +#include <linux/input.h> +#include <linux/input-polldev.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/slab.h> + +#include <linux/workqueue.h> +#include <linux/irq.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#ifdef CONFIG_HAS_EARLYSUSPEND +#include <linux/earlysuspend.h> +#endif + +#include <linux/i2c/lis3dh_zt.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/of_gpio.h> + +#define INTERRUPT_MANAGEMENT 1 + +#define G_MAX 16000 /** Maximum polled-device-reported g value */ + +/* +#define SHIFT_ADJ_2G 4 +#define SHIFT_ADJ_4G 3 +#define SHIFT_ADJ_8G 2 +#define SHIFT_ADJ_16G 1 +*/ + +//#define SENSITIVITY_2G 1 /** mg/LSB */ +#define SENSITIVITY_2G 15.6 /** mg/LSB */ +//#define SENSITIVITY_4G 2 /** mg/LSB */ +#define SENSITIVITY_4G 31.2 /** mg/LSB */ +//#define SENSITIVITY_8G 4 /** mg/LSB */ +#define SENSITIVITY_8G 62.5 /** mg/LSB */ +//#define SENSITIVITY_16G 12 /** mg/LSB */ +#define SENSITIVITY_16G 187.5 /** mg/LSB */ + +#define HIGH_RESOLUTION 0x08 + +#define AXISDATA_REG 0x28 +#define WHOAMI_LIS3DH_ACC 0x33 /* Expctd content for WAI */ + +/* CONTROL REGISTERS */ +#define WHO_AM_I 0x0F /* WhoAmI register */ +#define TEMP_CFG_REG 0x1F /* temper sens control reg */ +/* ctrl 1: ODR3 ODR2 ODR ODR0 LPen Zenable Yenable Zenable */ +#define CTRL_REG1 0x20 /* control reg 1 */ +#define CTRL_REG2 0x21 /* control reg 2 */ +#define CTRL_REG3 0x22 /* control reg 3 */ +#define CTRL_REG4 0x23 /* control reg 4 */ +#define CTRL_REG5 0x24 /* control reg 5 */ +#define CTRL_REG6 0x25 /* control reg 6 */ + +#define FIFO_CTRL_REG 0x2E /* FiFo control reg */ + +#define INT_CFG1 0x30 /* interrupt 1 config */ +#define INT_SRC1 0x31 /* interrupt 1 source */ +#define INT_THS1 0x32 /* interrupt 1 threshold */ +#define INT_DUR1 0x33 /* interrupt 1 duration */ + +#define INT_CFG2 0x34 /* interrupt 2 config */ +#define INT_SRC2 0x35 /* interrupt 2 source */ +#define INT_THS2 0x36 /* interrupt 2 threshold */ +#define INT_DUR2 0x37 /* interrupt 2 duration */ + +#define TT_CFG 0x38 /* tap config */ +#define TT_SRC 0x39 /* tap source */ +#define TT_THS 0x3A /* tap threshold */ +#define TT_LIM 0x3B /* tap time limit */ +#define TT_TLAT 0x3C /* tap time latency */ +#define TT_TW 0x3D /* tap time window */ +/* end CONTROL REGISTRES */ + +#define ENABLE_HIGH_RESOLUTION 1 + +#define LIS3DH_ACC_PM_OFF 0x00 +#define LIS3DH_ACC_ENABLE_ALL_AXES 0x07 + +#define PMODE_MASK 0x08 +#define ODR_MASK 0XF0 + +#define ODR1 0x10 /* 1Hz output data rate */ +#define ODR10 0x20 /* 10Hz output data rate */ +#define ODR25 0x30 /* 25Hz output data rate */ +#define ODR50 0x40 /* 50Hz output data rate */ +#define ODR100 0x50 /* 100Hz output data rate */ +#define ODR200 0x60 /* 200Hz output data rate */ +#define ODR400 0x70 /* 400Hz output data rate */ +#define ODR1250 0x90 /* 1250Hz output data rate */ + +#define IA 0x40 +#define ZH 0x20 +#define ZL 0x10 +#define YH 0x08 +#define YL 0x04 +#define XH 0x02 +#define XL 0x01 +/* */ +/* CTRL REG BITS*/ +#define CTRL_REG3_I1_AOI1 0x40 +#define CTRL_REG6_I2_TAPEN 0x80 +#define CTRL_REG6_HLACTIVE 0x02 +/* */ + +/* TAP_SOURCE_REG BIT */ +#define DTAP 0x20 +#define STAP 0x10 +#define SIGNTAP 0x08 +#define ZTAP 0x04 +#define YTAP 0x02 +#define XTAZ 0x01 + +#define FUZZ 32 +#define FLAT 32 +#define I2C_RETRY_DELAY 5 +#define I2C_RETRIES 5 +#define I2C_AUTO_INCREMENT 0x80 + +/* RESUME STATE INDICES */ +#define RES_CTRL_REG1 0 +#define RES_CTRL_REG2 1 +#define RES_CTRL_REG3 2 +#define RES_CTRL_REG4 3 +#define RES_CTRL_REG5 4 +#define RES_CTRL_REG6 5 + +#define RES_INT_CFG1 6 +#define RES_INT_THS1 7 +#define RES_INT_DUR1 8 +#define RES_INT_CFG2 9 +#define RES_INT_THS2 10 +#define RES_INT_DUR2 11 + +#define RES_TT_CFG 12 +#define RES_TT_THS 13 +#define RES_TT_LIM 14 +#define RES_TT_TLAT 15 +#define RES_TT_TW 16 + +#define RES_TEMP_CFG_REG 17 +#define RES_REFERENCE_REG 18 +#define RES_FIFO_CTRL_REG 19 + +#define RESUME_ENTRIES 20 +#define DEVICE_INFO "ST, LIS3DH" +#define DEVICE_INFO_LEN 32 +/* end RESUME STATE INDICES */ + +#define GSENSOR_GINT1_GPI 0 +#define GSENSOR_GINT2_GPI 1 + +struct { + unsigned int cutoff_ms; + unsigned int mask; +} lis3dh_acc_odr_table[] = { + { + 1, ODR1250}, { + 3, ODR400}, { + 5, ODR200}, { + 10, ODR100}, { + 20, ODR50}, { + 40, ODR25}, { + 100, ODR10}, { +1000, ODR1},}; + +struct lis3dh_acc_data { + struct i2c_client *client; + struct lis3dh_acc_platform_data *pdata; + + struct mutex lock; + struct delayed_work input_work; + + struct input_dev *input_dev; + + int hw_initialized; + /* hw_working=-1 means not tested yet */ + int hw_working; + atomic_t enabled; + int on_before_suspend; + + u8 sensitivity; + + u8 resume_state[RESUME_ENTRIES]; + + int irq1; + struct work_struct irq1_work; + struct workqueue_struct *irq1_work_queue; + int irq2; + struct work_struct irq2_work; + struct workqueue_struct *irq2_work_queue; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend early_suspend; +#endif +}; + +/* + * Because misc devices can not carry a pointer from driver register to + * open, we keep this global. This limits the driver to a single instance. + */ +struct lis3dh_acc_data *lis3dh_acc_misc_data; +struct i2c_client *lis3dh_i2c_client; + +static int lis3dh_acc_i2c_read(struct lis3dh_acc_data *acc, u8 * buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { + { + .addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = 1, + .buf = buf,}, + { + .addr = acc->client->addr, + .flags = (acc->client->flags & I2C_M_TEN) | I2C_M_RD, + .len = len, + .buf = buf,}, + }; + + do { + err = i2c_transfer(acc->client->adapter, msgs, 2); + if (err != 2) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 2) && (++tries < I2C_RETRIES)); + + if (err != 2) { + dev_err(&acc->client->dev, "read transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lis3dh_acc_i2c_write(struct lis3dh_acc_data *acc, u8 * buf, int len) +{ + int err; + int tries = 0; + + struct i2c_msg msgs[] = { {.addr = acc->client->addr, + .flags = acc->client->flags & I2C_M_TEN, + .len = len + 1,.buf = buf,}, + }; + do { + err = i2c_transfer(acc->client->adapter, msgs, 1); + if (err != 1) + msleep_interruptible(I2C_RETRY_DELAY); + } while ((err != 1) && (++tries < I2C_RETRIES)); + + if (err != 1) { + dev_err(&acc->client->dev, "write transfer error\n"); + err = -EIO; + } else { + err = 0; + } + + return err; +} + +static int lis3dh_acc_hw_init(struct lis3dh_acc_data *acc) +{ + int err = -1; + u8 buf[7]; + + pr_debug("%s: hw init start\n", LIS3DH_ACC_DEV_NAME); + + buf[0] = WHO_AM_I; + err = lis3dh_acc_i2c_read(acc, buf, 1); + if (err < 0) + goto error_firstread; + else + acc->hw_working = 1; + if (buf[0] != WHOAMI_LIS3DH_ACC) { + err = -1; /* choose the right coded error */ + goto error_unknown_device; + } + + buf[0] = CTRL_REG1; + buf[1] = acc->resume_state[RES_CTRL_REG1]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = TEMP_CFG_REG; + buf[1] = acc->resume_state[RES_TEMP_CFG_REG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = FIFO_CTRL_REG; + buf[1] = acc->resume_state[RES_FIFO_CTRL_REG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | TT_THS); + buf[1] = acc->resume_state[RES_TT_THS]; + buf[2] = acc->resume_state[RES_TT_LIM]; + buf[3] = acc->resume_state[RES_TT_TLAT]; + buf[4] = acc->resume_state[RES_TT_TW]; + err = lis3dh_acc_i2c_write(acc, buf, 4); + if (err < 0) + goto error1; + buf[0] = TT_CFG; + buf[1] = acc->resume_state[RES_TT_CFG]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS1); + buf[1] = acc->resume_state[RES_INT_THS1]; + buf[2] = acc->resume_state[RES_INT_DUR1]; + err = lis3dh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto error1; + buf[0] = INT_CFG1; + buf[1] = acc->resume_state[RES_INT_CFG1]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | INT_THS2); + buf[1] = acc->resume_state[RES_INT_THS2]; + buf[2] = acc->resume_state[RES_INT_DUR2]; + err = lis3dh_acc_i2c_write(acc, buf, 2); + if (err < 0) + goto error1; + buf[0] = INT_CFG2; + buf[1] = acc->resume_state[RES_INT_CFG2]; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error1; + + buf[0] = (I2C_AUTO_INCREMENT | CTRL_REG2); + buf[1] = acc->resume_state[RES_CTRL_REG2]; + buf[2] = acc->resume_state[RES_CTRL_REG3]; + buf[3] = acc->resume_state[RES_CTRL_REG4]; + buf[4] = acc->resume_state[RES_CTRL_REG5]; + buf[5] = acc->resume_state[RES_CTRL_REG6]; + err = lis3dh_acc_i2c_write(acc, buf, 5); + if (err < 0) + goto error1; + + acc->hw_initialized = 1; + pr_debug("%s: hw init done\n", LIS3DH_ACC_DEV_NAME); + return 0; + +error_firstread: + acc->hw_working = 0; + dev_warn(&acc->client->dev, "Error reading WHO_AM_I: is device " + "available/working?\n"); + goto error1; +error_unknown_device: + dev_err(&acc->client->dev, + "device unknown. Expected: 0x%x," + " Replies: 0x%x\n", WHOAMI_LIS3DH_ACC, buf[0]); +error1: + acc->hw_initialized = 0; + dev_err(&acc->client->dev, "hw init error 0x%x,0x%x: %d\n", buf[0], + buf[1], err); + return err; +} + +static void lis3dh_acc_device_power_off(struct lis3dh_acc_data *acc) +{ + int err; + u8 buf[2] = { CTRL_REG1, LIS3DH_ACC_PM_OFF }; + + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + dev_err(&acc->client->dev, "soft power off failed: %d\n", err); + + if (acc->pdata->power_off) { + if (acc->irq1 != 0) + disable_irq_nosync(acc->irq1); + if (acc->irq2 != 0) + disable_irq_nosync(acc->irq2); + acc->pdata->power_off(); + acc->hw_initialized = 0; + } + if (acc->hw_initialized) { + if (acc->irq1 != 0) + disable_irq_nosync(acc->irq1); + if (acc->irq2 != 0) + disable_irq_nosync(acc->irq2); + acc->hw_initialized = 0; + } +} + +static int lis3dh_acc_device_power_on(struct lis3dh_acc_data *acc) +{ + int err = -1; + + if (acc->pdata->power_on) { + err = acc->pdata->power_on(); + if (err < 0) { + dev_err(&acc->client->dev, + "power_on failed: %d\n", err); + return err; + } + } + + if (!acc->hw_initialized) { + err = lis3dh_acc_hw_init(acc); + if (acc->hw_working == 1 && err < 0) { + lis3dh_acc_device_power_off(acc); + return err; + } + } + return 0; +} + +static irqreturn_t lis3dh_acc_isr1(int irq, void *dev) +{ + struct lis3dh_acc_data *acc = dev; + + disable_irq_nosync(irq); + queue_work(acc->irq1_work_queue, &acc->irq1_work); + + pr_debug("%s: isr1 queued\n", LIS3DH_ACC_DEV_NAME); + + return IRQ_HANDLED; +} + +static irqreturn_t lis3dh_acc_isr2(int irq, void *dev) +{ + struct lis3dh_acc_data *acc = dev; + + disable_irq_nosync(irq); + queue_work(acc->irq2_work_queue, &acc->irq2_work); + + pr_debug("%s: isr2 queued\n", LIS3DH_ACC_DEV_NAME); + + return IRQ_HANDLED; +} + +static void lis3dh_acc_irq1_work_func(struct work_struct *work) +{ + + struct lis3dh_acc_data *acc = + container_of(work, struct lis3dh_acc_data, irq1_work); + + /* TODO add interrupt service procedure. + ie:lis3dh_acc_get_int1_source(acc); */ + ; + /* */ + pr_debug("%s: IRQ1 triggered\n", LIS3DH_ACC_DEV_NAME); +} + +static void lis3dh_acc_irq2_work_func(struct work_struct *work) +{ + + /*struct lis3dh_acc_data *acc = + container_of(work, struct lis3dh_acc_data, irq2_work); + */ + /* TODO add interrupt service procedure. + ie:lis3dh_acc_get_tap_source(acc); */ + ; + /* */ + + pr_debug("%s: IRQ2 triggered\n", LIS3DH_ACC_DEV_NAME); +} + +int lis3dh_acc_update_g_range(struct lis3dh_acc_data *acc, u8 new_g_range) +{ + int err; + + u8 sensitivity; + u8 buf[2]; + u8 updated_val; + u8 init_val; + u8 new_val; + u8 mask = LIS3DH_ACC_FS_MASK | HIGH_RESOLUTION; + + pr_debug("%s\n", __func__); + + switch (new_g_range) { + case LIS3DH_ACC_G_2G: + + sensitivity = SENSITIVITY_2G; + break; + case LIS3DH_ACC_G_4G: + + sensitivity = SENSITIVITY_4G; + break; + case LIS3DH_ACC_G_8G: + + sensitivity = SENSITIVITY_8G; + break; + case LIS3DH_ACC_G_16G: + + sensitivity = SENSITIVITY_16G; + break; + default: + dev_err(&acc->client->dev, "invalid g range requested: %u\n", + new_g_range); + return -EINVAL; + } + + if (atomic_read(&acc->enabled)) { + /* Set configuration register 4, which contains g range setting + * NOTE: this is a straight overwrite because this driver does + * not use any of the other configuration bits in this + * register. Should this become untrue, we will have to read + * out the value and only change the relevant bits --XX---- + * (marked by X) */ + buf[0] = CTRL_REG4; + err = lis3dh_acc_i2c_read(acc, buf, 1); + if (err < 0) + goto error; + init_val = buf[0]; + acc->resume_state[RES_CTRL_REG4] = init_val; + new_val = new_g_range | HIGH_RESOLUTION; + updated_val = ((mask & new_val) | ((~mask) & init_val)); + buf[1] = updated_val; + buf[0] = CTRL_REG4; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG4] = updated_val; + acc->sensitivity = sensitivity; + + pr_debug("%s sensitivity %d g-range %d\n", __func__, sensitivity,new_g_range); + } + + return 0; +error: + dev_err(&acc->client->dev, "update g range failed 0x%x,0x%x: %d\n", + buf[0], buf[1], err); + + return err; +} + +int lis3dh_acc_update_odr(struct lis3dh_acc_data *acc, int poll_interval_ms) +{ + int err = -1; + int i; + u8 config[2]; + + /* Convert the poll interval into an output data rate configuration + * that is as low as possible. The ordering of these checks must be + * maintained due to the cascading cut off values - poll intervals are + * checked from shortest to longest. At each check, if the next lower + * ODR cannot support the current poll interval, we stop searching */ + for (i = ARRAY_SIZE(lis3dh_acc_odr_table) - 1; i >= 0; i--) { + if (lis3dh_acc_odr_table[i].cutoff_ms <= poll_interval_ms) + break; + } + config[1] = lis3dh_acc_odr_table[i].mask; + + config[1] |= LIS3DH_ACC_ENABLE_ALL_AXES; + + /* If device is currently enabled, we need to write new + * configuration out to it */ + if (atomic_read(&acc->enabled)) { + config[0] = CTRL_REG1; + err = lis3dh_acc_i2c_write(acc, config, 1); + if (err < 0) + goto error; + acc->resume_state[RES_CTRL_REG1] = config[1]; + } + + return 0; + +error: + dev_err(&acc->client->dev, "update odr failed 0x%x,0x%x: %d\n", + config[0], config[1], err); + + return err; +} + +/* */ + +static int lis3dh_acc_register_write(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address, u8 new_value) +{ + int err = -1; + + if (atomic_read(&acc->enabled)) { + /* Sets configuration register at reg_address + * NOTE: this is a straight overwrite */ + buf[0] = reg_address; + buf[1] = new_value; + err = lis3dh_acc_i2c_write(acc, buf, 1); + if (err < 0) + return err; + } + return err; +} + +static int lis3dh_acc_register_read(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address) +{ + + int err = -1; + buf[0] = (reg_address); + err = lis3dh_acc_i2c_read(acc, buf, 1); + return err; +} + +static int lis3dh_acc_register_update(struct lis3dh_acc_data *acc, u8 * buf, + u8 reg_address, u8 mask, + u8 new_bit_values) +{ + int err = -1; + u8 init_val; + u8 updated_val; + err = lis3dh_acc_register_read(acc, buf, reg_address); + if (!(err < 0)) { + init_val = buf[1]; + updated_val = ((mask & new_bit_values) | ((~mask) & init_val)); + err = lis3dh_acc_register_write(acc, buf, reg_address, + updated_val); + } + return err; +} + +/* */ + +static int lis3dh_acc_get_acceleration_data(struct lis3dh_acc_data *acc, + int *xyz) +{ + int err = -1; + /* Data bytes from hardware xL, xH, yL, yH, zL, zH */ + u8 acc_data[6]; + /* x,y,z hardware data */ + s16 hw_d[3] = { 0 }; + s8 acc_data_term[3]={0}; + + acc_data[0] = (I2C_AUTO_INCREMENT | AXISDATA_REG); + err = lis3dh_acc_i2c_read(acc, acc_data, 6); + + if (err < 0) { + pr_debug("%s I2C read error %d\n", LIS3DH_ACC_I2C_NAME, + err); + return err; + } + + //hw_d[0] = (((s16) ((acc_data[1] << 8) | acc_data[0])) >> 4); + //hw_d[1] = (((s16) ((acc_data[3] << 8) | acc_data[2])) >> 4); + //hw_d[2] = (((s16) ((acc_data[5] << 8) | acc_data[4])) >> 4); + acc_data_term[0]=acc_data[1]; + acc_data_term[1]=acc_data[3]; + acc_data_term[2]=acc_data[5]; + + //printk("%s read 000 x=%d, y=%d, z=%d\n", + // LIS3DH_ACC_DEV_NAME, acc_data_term[0], acc_data_term[1], acc_data_term[2]); + //hw_d[0] = hw_d[0] * acc->sensitivity; + hw_d[0] = acc_data_term[0] * acc->sensitivity; + //hw_d[1] = hw_d[1] * acc->sensitivity; + hw_d[1] = acc_data_term[1] * acc->sensitivity; + //hw_d[2] = hw_d[2] * acc->sensitivity; + hw_d[2] = acc_data_term[2] * acc->sensitivity; + + acc->pdata->negate_x = -acc->pdata->negate_x; + acc->pdata->negate_y = -acc->pdata->negate_y; + + xyz[0] = ((acc->pdata->negate_x) ? (-hw_d[acc->pdata->axis_map_x]) + : (hw_d[acc->pdata->axis_map_x])); + xyz[1] = ((acc->pdata->negate_y) ? (-hw_d[acc->pdata->axis_map_y]) + : (hw_d[acc->pdata->axis_map_y])); + xyz[2] = ((acc->pdata->negate_z) ? (-hw_d[acc->pdata->axis_map_z]) + : (hw_d[acc->pdata->axis_map_z])); + + pr_debug("%s read x=%d, y=%d, z=%d\n", + LIS3DH_ACC_DEV_NAME, xyz[0], xyz[1], xyz[2]); + + return err; +} + +static void lis3dh_acc_report_values(struct lis3dh_acc_data *acc, int *xyz) +{ + input_report_abs(acc->input_dev, ABS_X, xyz[0]); + input_report_abs(acc->input_dev, ABS_Y, xyz[1]); + input_report_abs(acc->input_dev, ABS_Z, xyz[2]); + input_sync(acc->input_dev); +} + +static int lis3dh_acc_enable(struct lis3dh_acc_data *acc) +{ + int err; + printk("sprd-gsensor: -- %s -- !\n",__func__); + if (!atomic_cmpxchg(&acc->enabled, 0, 1)) { + err = lis3dh_acc_device_power_on(acc); + if (err < 0) { + atomic_set(&acc->enabled, 0); + return err; + } + + if (acc->hw_initialized) { + if (acc->irq1 != 0) + enable_irq(acc->irq1); + if (acc->irq2 != 0) + enable_irq(acc->irq2); + pr_debug("%s: power on: irq enabled\n", + LIS3DH_ACC_DEV_NAME); + } + + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata-> + poll_interval)); + } + printk("sprd-gsensor: -- %s -- success!\n",__func__); + return 0; +} + +static int lis3dh_acc_disable(struct lis3dh_acc_data *acc) +{ + printk("sprd-gsensor: -- %s -- \n",__func__); + if (atomic_cmpxchg(&acc->enabled, 1, 0)) { + cancel_delayed_work_sync(&acc->input_work); + lis3dh_acc_device_power_off(acc); + } + + return 0; +} + +static int lis3dh_acc_misc_open(struct inode *inode, struct file *file) +{ + int err; + err = nonseekable_open(inode, file); + if (err < 0) + return err; + + file->private_data = lis3dh_acc_misc_data; + + return 0; +} + +static long lis3dh_acc_misc_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + u8 buf[4]; + u8 mask; + u8 reg_address; + u8 bit_values; + int err; + int interval; + int xyz[3] = { 0 }; + struct lis3dh_acc_data *acc = file->private_data; + + switch (cmd) { + case LIS3DH_ACC_IOCTL_GET_DELAY: + interval = acc->pdata->poll_interval; + if (copy_to_user(argp, &interval, sizeof(interval))) + return -EFAULT; + break; + + case LIS3DH_ACC_IOCTL_SET_DELAY: + if (copy_from_user(&interval, argp, sizeof(interval))) + return -EFAULT; + if (interval < 0 || interval > 1000) + return -EINVAL; + + acc->pdata->poll_interval = max(interval, + acc->pdata->min_interval); + err = lis3dh_acc_update_odr(acc, acc->pdata->poll_interval); + /* TODO: if update fails poll is still set */ + if (err < 0) + return err; + break; + + case LIS3DH_ACC_IOCTL_SET_ENABLE: + if (copy_from_user(&interval, argp, sizeof(interval))) + return -EFAULT; + if (interval > 1) + return -EINVAL; + if (interval) + err = lis3dh_acc_enable(acc); + else + err = lis3dh_acc_disable(acc); + return err; + break; + + case LIS3DH_ACC_IOCTL_GET_ENABLE: + interval = atomic_read(&acc->enabled); + if (copy_to_user(argp, &interval, sizeof(interval))) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_SET_G_RANGE: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + bit_values = buf[0]; + err = lis3dh_acc_update_g_range(acc, bit_values); + if (err < 0) + return err; + break; + +#ifdef INTERRUPT_MANAGEMENT + case LIS3DH_ACC_IOCTL_SET_CTRL_REG3: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = CTRL_REG3; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_CTRL_REG3] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_CTRL_REG3])); + break; + + case LIS3DH_ACC_IOCTL_SET_CTRL_REG6: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = CTRL_REG6; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_CTRL_REG6] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_CTRL_REG6])); + break; + + case LIS3DH_ACC_IOCTL_SET_DURATION1: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_DUR1; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_DUR1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_DUR1])); + break; + + case LIS3DH_ACC_IOCTL_SET_THRESHOLD1: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_THS1; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_THS1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_THS1])); + break; + + case LIS3DH_ACC_IOCTL_SET_CONFIG1: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = INT_CFG1; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_CFG1] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_CFG1])); + break; + + case LIS3DH_ACC_IOCTL_GET_SOURCE1: + err = lis3dh_acc_register_read(acc, buf, INT_SRC1); + if (err < 0) + return err; + + pr_debug("INT1_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_SET_DURATION2: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_DUR2; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_DUR2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_DUR2])); + break; + + case LIS3DH_ACC_IOCTL_SET_THRESHOLD2: + if (copy_from_user(buf, argp, 1)) + return -EFAULT; + reg_address = INT_THS2; + mask = 0x7F; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_THS2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_THS2])); + break; + + case LIS3DH_ACC_IOCTL_SET_CONFIG2: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = INT_CFG2; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_INT_CFG2] = ((mask & bit_values) | + (~mask & acc-> + resume_state + [RES_INT_CFG2])); + break; + + case LIS3DH_ACC_IOCTL_GET_SOURCE2: + err = lis3dh_acc_register_read(acc, buf, INT_SRC2); + if (err < 0) + return err; + + pr_debug("INT2_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) + return -EINVAL; + break; + + case LIS3DH_ACC_IOCTL_GET_TAP_SOURCE: + err = lis3dh_acc_register_read(acc, buf, TT_SRC); + if (err < 0) + return err; + pr_debug("TT_SRC content: %d , 0x%x\n", buf[0], buf[0]); + + if (copy_to_user(argp, buf, 1)) { + pr_err("%s: %s error in copy_to_user \n", + LIS3DH_ACC_DEV_NAME, __func__); + return -EINVAL; + } + break; + case LIS3DH_ACC_IOCTL_GET_COOR_XYZ: + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + return err; + + if (copy_to_user(argp, xyz, sizeof(xyz))) { + pr_err(" %s %d error in copy_to_user \n", + __func__, __LINE__); + return -EINVAL; + } + break; + case LIS3DH_ACC_IOCTL_SET_TAP_CFG: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_CFG; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_CFG] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_CFG])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TLIM: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_LIM; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_LIM] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_LIM])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_THS: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_THS; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_THS] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_THS])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TLAT: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_TLAT; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_TLAT] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_TLAT])); + break; + + case LIS3DH_ACC_IOCTL_SET_TAP_TW: + if (copy_from_user(buf, argp, 2)) + return -EFAULT; + reg_address = TT_TW; + mask = buf[1]; + bit_values = buf[0]; + err = lis3dh_acc_register_update(acc, (u8 *) arg, reg_address, + mask, bit_values); + if (err < 0) + return err; + acc->resume_state[RES_TT_TW] = ((mask & bit_values) | + (~mask & acc-> + resume_state[RES_TT_TW])); + break; + +#endif /* INTERRUPT_MANAGEMENT */ + case LIS3DH_ACC_IOCTL_GET_CHIP_ID: + { + u8 devid = 0; + u8 devinfo[DEVICE_INFO_LEN] = {0}; + err = lis3dh_acc_register_read(acc, &devid, WHO_AM_I); + if (err < 0) { + printk("__func__, error read register WHO_AM_I\n", __func__); + return -EAGAIN; + } + sprintf(devinfo, "%s, %#x", DEVICE_INFO, devid); + + if (copy_to_user(argp, devinfo, sizeof(devinfo))) { + printk("%s error in copy_to_user(IOCTL_GET_CHIP_ID)\n", __func__); + return -EINVAL; + } + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct file_operations lis3dh_acc_misc_fops = { + .owner = THIS_MODULE, + .open = lis3dh_acc_misc_open, + .unlocked_ioctl = lis3dh_acc_misc_ioctl, +}; + +static struct miscdevice lis3dh_acc_misc_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = LIS3DH_ACC_DEV_NAME, + .fops = &lis3dh_acc_misc_fops, +}; + +static void lis3dh_acc_input_work_func(struct work_struct *work) +{ + struct lis3dh_acc_data *acc; + + int xyz[3] = { 0 }; + int err; + + acc = container_of((struct delayed_work *)work, + struct lis3dh_acc_data, input_work); + + mutex_lock(&acc->lock); + err = lis3dh_acc_get_acceleration_data(acc, xyz); + if (err < 0) + dev_err(&acc->client->dev, "get_acceleration_data failed\n"); + else + lis3dh_acc_report_values(acc, xyz); + + schedule_delayed_work(&acc->input_work, + msecs_to_jiffies(acc->pdata->poll_interval)); + mutex_unlock(&acc->lock); +} + +#ifdef LIS3DH_OPEN_ENABLE +int lis3dh_acc_input_open(struct input_dev *input) +{ + struct lis3dh_acc_data *acc = input_get_drvdata(input); + + return lis3dh_acc_enable(acc); +} + +void lis3dh_acc_input_close(struct input_dev *dev) +{ + struct lis3dh_acc_data *acc = input_get_drvdata(dev); + + lis3dh_acc_disable(acc); +} +#endif + +static int lis3dh_acc_validate_pdata(struct lis3dh_acc_data *acc) +{ + acc->pdata->poll_interval = max(acc->pdata->poll_interval, + acc->pdata->min_interval); + + if (acc->pdata->axis_map_x > 2 || acc->pdata->axis_map_y > 2 + || acc->pdata->axis_map_z > 2) { + dev_err(&acc->client->dev, "invalid axis_map value " + "x:%u y:%u z%u\n", acc->pdata->axis_map_x, + acc->pdata->axis_map_y, acc->pdata->axis_map_z); + return -EINVAL; + } + + /* Only allow 0 and 1 for negation boolean flag */ + if (acc->pdata->negate_x > 1 || acc->pdata->negate_y > 1 + || acc->pdata->negate_z > 1) { + dev_err(&acc->client->dev, "invalid negate value " + "x:%u y:%u z:%u\n", acc->pdata->negate_x, + acc->pdata->negate_y, acc->pdata->negate_z); + return -EINVAL; + } + + /* Enforce minimum polling interval */ + if (acc->pdata->poll_interval < acc->pdata->min_interval) { + dev_err(&acc->client->dev, "minimum poll interval violated\n"); + return -EINVAL; + } + + return 0; +} + +static int lis3dh_acc_input_init(struct lis3dh_acc_data *acc) +{ + int err; + /* Polling rx data when the interrupt is not used.*/ + if (1 /*acc->irq1 == 0 && acc->irq1 == 0 */ ) { + INIT_DELAYED_WORK(&acc->input_work, lis3dh_acc_input_work_func); + } + + acc->input_dev = input_allocate_device(); + if (!acc->input_dev) { + err = -ENOMEM; + dev_err(&acc->client->dev, "input device allocate failed\n"); + goto err0; + } +#ifdef LIS3DH_ACC_OPEN_ENABLE + acc->input_dev->open = lis3dh_acc_input_open; + acc->input_dev->close = lis3dh_acc_input_close; +#endif + + input_set_drvdata(acc->input_dev, acc); + + set_bit(EV_ABS, acc->input_dev->evbit); + /* next is used for interruptA sources data if the case */ + set_bit(ABS_MISC, acc->input_dev->absbit); + /* next is used for interruptB sources data if the case */ + set_bit(ABS_WHEEL, acc->input_dev->absbit); + + input_set_abs_params(acc->input_dev, ABS_X, -G_MAX, G_MAX, 0, 0); + input_set_abs_params(acc->input_dev, ABS_Y, -G_MAX, G_MAX, 0, 0); + input_set_abs_params(acc->input_dev, ABS_Z, -G_MAX, G_MAX, 0, 0); + /* next is used for interruptA sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_MISC, INT_MIN, INT_MAX, 0, 0); + /* next is used for interruptB sources data if the case */ + input_set_abs_params(acc->input_dev, ABS_WHEEL, INT_MIN, INT_MAX, 0, 0); + + acc->input_dev->name = "accelerometer"; + + err = input_register_device(acc->input_dev); + if (err) { + dev_err(&acc->client->dev, + "unable to register input polled device %s\n", + acc->input_dev->name); + goto err1; + } + + return 0; + +err1: + input_free_device(acc->input_dev); +err0: + return err; +} + +static void lis3dh_acc_input_cleanup(struct lis3dh_acc_data *acc) +{ + input_unregister_device(acc->input_dev); + input_free_device(acc->input_dev); +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void lis3dh_early_suspend(struct early_suspend *es); +static void lis3dh_early_resume(struct early_suspend *es); +#endif + +#ifdef CONFIG_OF +static struct lis3dh_acc_platform_data *lis3dh_acc_parse_dt(struct device *dev) +{ + struct lis3dh_acc_platform_data *pdata; + struct device_node *np = dev->of_node; + int ret; + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(dev, "Could not allocate struct lis3dh_acc_platform_data"); + return NULL; + } + ret = of_property_read_u32(np, "poll_interval", &pdata->poll_interval); + if(ret){ + dev_err(dev, "fail to get poll_interval\n"); + goto fail; + } + ret = of_property_read_u32(np, "min_interval", &pdata->min_interval); + if(ret){ + dev_err(dev, "fail to get min_interval\n"); + goto fail; + } + ret = of_property_read_u32(np, "g_range", &pdata->g_range); + if(ret){ + dev_err(dev, "fail to get g_range\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_x", &pdata->axis_map_x); + if(ret){ + dev_err(dev, "fail to get axis_map_x\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_y", &pdata->axis_map_y); + if(ret){ + dev_err(dev, "fail to get axis_map_y\n"); + goto fail; + } + ret = of_property_read_u32(np, "axis_map_z", &pdata->axis_map_z); + if(ret){ + dev_err(dev, "fail to get axis_map_z\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_x", &pdata->negate_x); + if(ret){ + dev_err(dev, "fail to get negate_x\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_y", &pdata->negate_y); + if(ret){ + dev_err(dev, "fail to get negate_y\n"); + goto fail; + } + ret = of_property_read_u32(np, "negate_z", &pdata->negate_z); + if(ret){ + dev_err(dev, "fail to get negate_z\n"); + goto fail; + } + return pdata; +fail: + kfree(pdata); + return NULL; +} +#endif +static int lis3dh_acc_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + + struct lis3dh_acc_data *acc; + struct lis3dh_acc_platform_data *pdata = client->dev.platform_data; + int xyz[3] = { 0 }; + + int err = -1; + int tempvalue; + printk("sprd-gsensor: -- %s -- start !\n",__func__); + pr_debug("%s: probe start.\n", LIS3DH_ACC_DEV_NAME); + +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; + if (np && !pdata){ + pdata = lis3dh_acc_parse_dt(&client->dev); + if(pdata){ + client->dev.platform_data = pdata; + } + if(!pdata){ + err = -ENOMEM; + goto exit_alloc_platform_data_failed; + } + } +#endif + /* + if (client->dev.platform_data == NULL) { + dev_err(&client->dev, "platform data is NULL. exiting.\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + */ + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "client not i2c capable\n"); + err = -ENODEV; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE | + I2C_FUNC_SMBUS_BYTE_DATA | + I2C_FUNC_SMBUS_WORD_DATA)) { + dev_err(&client->dev, "client not smb-i2c capable:2\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) { + dev_err(&client->dev, "client not smb-i2c capable:3\n"); + err = -EIO; + goto exit_check_functionality_failed; + } + /* + * OK. From now, we presume we have a valid client. We now create the + * client structure, even though we cannot fill it completely yet. + */ + + acc = kzalloc(sizeof(struct lis3dh_acc_data), GFP_KERNEL); + if (acc == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for module data: " + "%d\n", err); + goto exit_alloc_data_failed; + } + + mutex_init(&acc->lock); + mutex_lock(&acc->lock); + + acc->client = client; + lis3dh_i2c_client = client; + i2c_set_clientdata(client, acc); + + pr_debug("%s: %s has set irq1 to irq: %d\n", + LIS3DH_ACC_DEV_NAME, __func__, acc->irq1); + pr_debug("%s: %s has set irq2 to irq: %d\n", + LIS3DH_ACC_DEV_NAME, __func__, acc->irq2); + + gpio_request(GSENSOR_GINT1_GPI, "GSENSOR_INT1"); + gpio_request(GSENSOR_GINT2_GPI, "GSENSOR_INT2"); + acc->irq1 = 0; /* gpio_to_irq(GSENSOR_GINT1_GPI); */ + acc->irq2 = 0; /* gpio_to_irq(GSENSOR_GINT2_GPI); */ + + if (acc->irq1 != 0) { + pr_debug("%s request irq1\n", __func__); + err = + request_irq(acc->irq1, lis3dh_acc_isr1, IRQF_TRIGGER_RISING, + "lis3dh_acc_irq1", acc); + if (err < 0) { + dev_err(&client->dev, "request irq1 failed: %d\n", err); + goto err_mutexunlockfreedata; + } + disable_irq_nosync(acc->irq1); + + INIT_WORK(&acc->irq1_work, lis3dh_acc_irq1_work_func); + acc->irq1_work_queue = + create_singlethread_workqueue("lis3dh_acc_wq1"); + if (!acc->irq1_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, "cannot create work queue1: %d\n", + err); + goto err_free_irq1; + } + } + + if (acc->irq2 != 0) { + err = + request_irq(acc->irq2, lis3dh_acc_isr2, IRQF_TRIGGER_RISING, + "lis3dh_acc_irq2", acc); + if (err < 0) { + dev_err(&client->dev, "request irq2 failed: %d\n", err); + goto err_destoyworkqueue1; + } + disable_irq_nosync(acc->irq2); + +/* Create workqueue for IRQ.*/ + + INIT_WORK(&acc->irq2_work, lis3dh_acc_irq2_work_func); + acc->irq2_work_queue = + create_singlethread_workqueue("lis3dh_acc_wq2"); + if (!acc->irq2_work_queue) { + err = -ENOMEM; + dev_err(&client->dev, "cannot create work queue2: %d\n", + err); + goto err_free_irq2; + } + } + + acc->pdata = kmalloc(sizeof(*acc->pdata), GFP_KERNEL); + if (acc->pdata == NULL) { + err = -ENOMEM; + dev_err(&client->dev, + "failed to allocate memory for pdata: %d\n", err); + goto err_destoyworkqueue2; + } + + memcpy(acc->pdata, client->dev.platform_data, sizeof(*acc->pdata)); + + err = lis3dh_acc_validate_pdata(acc); + if (err < 0) { + dev_err(&client->dev, "failed to validate platform data\n"); + goto exit_kfree_pdata; + } + + err = lis3dh_acc_device_power_on(acc); + if (err < 0) { + dev_err(&client->dev, "power on failed: %d\n", err); + goto err2; + } + +#if 1 + if (i2c_smbus_read_byte(client) < 0) { + pr_err("i2c_smbus_read_byte error!!\n"); + goto err_destoyworkqueue2; + } else { + pr_debug("%s Device detected!\n", LIS3DH_ACC_DEV_NAME); + } +#endif + /* read chip id */ + + tempvalue = i2c_smbus_read_word_data(client, WHO_AM_I); + + if ((tempvalue & 0x00FF) == WHOAMI_LIS3DH_ACC) { + pr_debug("%s I2C driver registered!\n", + LIS3DH_ACC_DEV_NAME); + } else { + acc->client = NULL; + pr_debug("I2C driver not registered!" + " Device unknown 0x%x\n", tempvalue); + goto err_destoyworkqueue2; + } + + i2c_set_clientdata(client, acc); + + if (acc->pdata->init) { + err = acc->pdata->init(); + if (err < 0) { + dev_err(&client->dev, "init failed: %d\n", err); + goto err2; + } + } + + memset(acc->resume_state, 0, ARRAY_SIZE(acc->resume_state)); + + acc->resume_state[RES_CTRL_REG1] = LIS3DH_ACC_ENABLE_ALL_AXES; + acc->resume_state[RES_CTRL_REG2] = 0x00; + acc->resume_state[RES_CTRL_REG3] = 0x00; + acc->resume_state[RES_CTRL_REG4] = 0x00; + acc->resume_state[RES_CTRL_REG5] = 0x00; + acc->resume_state[RES_CTRL_REG6] = 0x00; + + acc->resume_state[RES_TEMP_CFG_REG] = 0x00; + acc->resume_state[RES_FIFO_CTRL_REG] = 0x00; + acc->resume_state[RES_INT_CFG1] = 0x00; + acc->resume_state[RES_INT_THS1] = 0x00; + acc->resume_state[RES_INT_DUR1] = 0x00; + acc->resume_state[RES_INT_CFG2] = 0x00; + acc->resume_state[RES_INT_THS2] = 0x00; + acc->resume_state[RES_INT_DUR2] = 0x00; + + acc->resume_state[RES_TT_CFG] = 0x00; + acc->resume_state[RES_TT_THS] = 0x00; + acc->resume_state[RES_TT_LIM] = 0x00; + acc->resume_state[RES_TT_TLAT] = 0x00; + acc->resume_state[RES_TT_TW] = 0x00; + + + atomic_set(&acc->enabled, 1); + + err = lis3dh_acc_update_g_range(acc, acc->pdata->g_range); + if (err < 0) { + dev_err(&client->dev, "update_g_range failed\n"); + goto err_power_off; + } + + err = lis3dh_acc_update_odr(acc, acc->pdata->poll_interval); + if (err < 0) { + dev_err(&client->dev, "update_odr failed\n"); + goto err_power_off; + } + + err = lis3dh_acc_input_init(acc); + if (err < 0) { + dev_err(&client->dev, "input init failed\n"); + goto err_power_off; + } + lis3dh_acc_misc_data = acc; + + err = misc_register(&lis3dh_acc_misc_device); + if (err < 0) { + dev_err(&client->dev, + "misc LIS3DH_ACC_DEV_NAME register failed\n"); + goto err_input_cleanup; + } + + lis3dh_acc_device_power_off(acc); + + /* As default, do not report information */ + atomic_set(&acc->enabled, 0); +/* +#ifdef CONFIG_HAS_EARLYSUSPEND + acc->early_suspend.suspend = lis3dh_early_suspend; + acc->early_suspend.resume = lis3dh_early_resume; + acc->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN; + register_early_suspend(&acc->early_suspend); +#endif +*/ + mutex_unlock(&acc->lock); + dev_info(&client->dev, "###%s###\n", __func__); + printk("sprd-gsensor: -- %s -- success !\n",__func__); + + + err = lis3dh_acc_get_acceleration_data(acc, xyz); + + + return 0; + +err_input_cleanup: + lis3dh_acc_input_cleanup(acc); +err_power_off: + lis3dh_acc_device_power_off(acc); +err2: + if (acc->pdata->exit) + acc->pdata->exit(); +exit_kfree_pdata: + kfree(acc->pdata); +err_destoyworkqueue2: + if (acc->irq2_work_queue) + destroy_workqueue(acc->irq2_work_queue); +err_free_irq2: + if (acc->irq2) { + free_irq(acc->irq2, acc); + gpio_free(GSENSOR_GINT2_GPI); + } +err_destoyworkqueue1: + if (acc->irq1_work_queue) + destroy_workqueue(acc->irq1_work_queue); +err_free_irq1: + if (acc->irq1) { + free_irq(acc->irq1, acc); + gpio_free(GSENSOR_GINT1_GPI); + } +err_mutexunlockfreedata: + i2c_set_clientdata(client, NULL); + mutex_unlock(&acc->lock); + kfree(acc); + lis3dh_acc_misc_data = NULL; +exit_alloc_data_failed: +exit_check_functionality_failed: + pr_err("%s: Driver Init failed\n", LIS3DH_ACC_DEV_NAME); +exit_alloc_platform_data_failed: + return err; +} + +static int lis3dh_acc_remove(struct i2c_client *client) +{ + /* TODO: revisit ordering here once _probe order is finalized */ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + if (acc != NULL) { + if (acc->irq1) { + free_irq(acc->irq1, acc); + gpio_free(GSENSOR_GINT1_GPI); + } + if (acc->irq2) { + free_irq(acc->irq2, acc); + gpio_free(GSENSOR_GINT2_GPI); + } + + if (acc->irq1_work_queue) + destroy_workqueue(acc->irq1_work_queue); + if (acc->irq2_work_queue) + destroy_workqueue(acc->irq2_work_queue); + misc_deregister(&lis3dh_acc_misc_device); + lis3dh_acc_input_cleanup(acc); + lis3dh_acc_device_power_off(acc); + if (acc->pdata->exit) + acc->pdata->exit(); + kfree(acc->pdata); + kfree(acc); + } + + return 0; +} + +static int lis3dh_acc_resume(struct i2c_client *client) +{ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + printk("sprd-gsensor: -- %s -- !\n",__func__); + dev_dbg(&client->dev, "###%s###\n", __func__); + + if (acc != NULL && acc->on_before_suspend) + return lis3dh_acc_enable(acc); + + return 0; +} + +static int lis3dh_acc_suspend(struct i2c_client *client, pm_message_t mesg) +{ + struct lis3dh_acc_data *acc = i2c_get_clientdata(client); + printk("sprd-gsensor: -- %s -- !\n",__func__); + dev_dbg(&client->dev, "###%s###\n", __func__); + + if (acc != NULL) { + acc->on_before_suspend = atomic_read(&acc->enabled); + return lis3dh_acc_disable(acc); + } + return 0; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND + +static void lis3dh_early_suspend(struct early_suspend *es) +{ + lis3dh_acc_suspend(lis3dh_i2c_client, (pm_message_t) { + .event = 0}); +} + +static void lis3dh_early_resume(struct early_suspend *es) +{ + lis3dh_acc_resume(lis3dh_i2c_client); +} + +#endif /* CONFIG_HAS_EARLYSUSPEND */ + +static const struct i2c_device_id lis3dh_acc_id[] += { {LIS3DH_ACC_DEV_NAME, 0}, {}, }; + +MODULE_DEVICE_TABLE(i2c, lis3dh_acc_id); + +static const struct of_device_id lis3dh_acc_of_match[] = { + { .compatible = "ST,lis3dh_acc", }, + { } +}; +MODULE_DEVICE_TABLE(of, lis3dh_acc_of_match); +static struct i2c_driver lis3dh_acc_driver = { + .driver = { + .name = LIS3DH_ACC_I2C_NAME, + .of_match_table = lis3dh_acc_of_match, + }, + .probe = lis3dh_acc_probe, + .remove = lis3dh_acc_remove, + .resume = lis3dh_acc_resume, + .suspend = lis3dh_acc_suspend, + .id_table = lis3dh_acc_id, +}; + + +static int __init lis3dh_acc_init(void) +{ + pr_debug("%s accelerometer driver: init\n", LIS3DH_ACC_I2C_NAME); + + return i2c_add_driver(&lis3dh_acc_driver); +} + +static void __exit lis3dh_acc_exit(void) +{ + pr_debug("%s accelerometer driver exit\n", LIS3DH_ACC_DEV_NAME); + + i2c_del_driver(&lis3dh_acc_driver); + return; +} + +module_init(lis3dh_acc_init); +module_exit(lis3dh_acc_exit); + +MODULE_DESCRIPTION("lis3dh accelerometer misc driver"); +MODULE_AUTHOR("STMicroelectronics"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/ltr_558als.c b/drivers/input/misc/ltr_558als.c new file mode 100644 index 00000000..0251cdb9 --- /dev/null +++ b/drivers/input/misc/ltr_558als.c @@ -0,0 +1,1236 @@ +/* + * File:ltr_558als.c + * Author:Yaochuan Li <yaochuan.li@spreadtrum.com> + * Created:2013-03-18 + * Description:LTR-558ALS Driver + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/i2c-dev.h> +#include <linux/miscdevice.h> +#include <linux/mutex.h> +#include <linux/mm.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/sysctl.h> +#include <linux/input.h> +#include <linux/gpio.h> +#include <linux/irq.h> +#include <linux/earlysuspend.h> +#include <linux/platform_device.h> +#include <asm/uaccess.h> +#include <linux/wakelock.h> +#include <linux/i2c/ltr_558als.h> +#include <linux/slab.h> +#include <linux/of_device.h> +#include <linux/of_gpio.h> + +#define LTR588_DBG +#define LTR558_ADAPTIVE +#ifdef LTR588_DBG +#define ENTER printk(KERN_INFO "[LTR588_DBG] func: %s line: %04d ", __func__, __LINE__) +#define PRINT_DBG(x...) printk(KERN_INFO "[LTR588_DBG] " x) +#define PRINT_INFO(x...) printk(KERN_INFO "[LTR588_INFO] " x) +#define PRINT_WARN(x...) printk(KERN_INFO "[LTR588_WARN] " x) +#define PRINT_ERR(format,x...) printk(KERN_ERR "[LTR588_ERR] func: %s line: %04d info: " format, __func__, __LINE__, ## x) +#else +#define ENTER +#define PRINT_DBG(x...) +#define PRINT_INFO(x...) printk(KERN_INFO "[LTR588_INFO] " x) +#define PRINT_WARN(x...) printk(KERN_INFO "[LTR588_WARN] " x) +#define PRINT_ERR(format,x...) printk(KERN_ERR "[LTR588_ERR] func: %s line: %04d info: " format, __func__, __LINE__, ## x) +#endif + +typedef struct tag_ltr558 { + struct input_dev *input; + struct i2c_client *client; + struct work_struct work; + struct workqueue_struct *ltr_work_queue; +#ifdef CONFIG_HAS_EARLYSUSPEND + struct early_suspend ltr_early_suspend; +#endif +} ltr558_t, *ltr558_p; + +static int ps_threshold = 0; +static int ps_threshold_high = 600; +static int ps_threshold_low = 500; +static int dyna_cali = 2047; + +static int p_flag = 0; +static int l_flag = 0; +static u8 p_gainrange = PS_RANGE4; +static u8 l_gainrange = ALS_RANGE1_320; +//static int ps_threshold = 0; +static struct i2c_client *this_client = NULL; +static int LTR_PLS_MODE = 0; + +#ifdef LTR558_ADAPTIVE +#define DEBOUNCE 10 +#define MIN_SPACING 120 +#define DIVEDE 10 +static int ps_max_filter[5] = {0}; +static int ps_min_filter[5] = {0}; +static int max_index = 0; +static int min_index = 0; +static int ps_min = 0; +static int ps_max = 0; +#endif + +static int ltr558_reg_init(void); + +static int ltr558_i2c_read_bytes(u8 index, u8 *rx_buff, u8 length) +{ + int ret = -1; + struct i2c_msg msgs[] = { + { + .addr = this_client->addr,//chip address, 7bit + .flags = 0,//write + .len = 1, + .buf = &index, + }, + { + .addr = this_client->addr, + .flags = I2C_M_RD,//read + .len = length, + .buf = rx_buff, + }, + }; + + ret = i2c_transfer(this_client->adapter, msgs, 2); + if(ret != 2) { + PRINT_ERR("READ ERROR!ret=%d\n", ret); + } + return ret; +} + +static int ltr558_i2c_write_bytes(u8 *tx_buff, u8 length) +{ + int ret = -1; + struct i2c_msg msgs[1]; + + msgs[0].addr = this_client->addr; + msgs[0].flags = 0; + msgs[0].len = length; + msgs[0].buf = tx_buff; + + ret = i2c_transfer(this_client->adapter, msgs, 1); + if(ret != 1) { + PRINT_ERR("WRITE ERROR!ret=%d\n", ret); + } + return ret; +} + +static int ltr558_i2c_read_2_bytes(u8 reg) +{ + int ret = 0; + u8 data[2] = {0}; + + ret = ltr558_i2c_read_bytes(reg, data, 2); + if(ret != 2) { + PRINT_ERR("READ ERROR!ret=%d\n", ret); + return -1; + } + + ret = data[1]; + ret = ( ret<<8) |data[0]; + + return ret; +} + +static int ltr558_i2c_read_1_byte(u8 reg) +{ + int ret = 0; + u8 data[1] = {0}; + + ret = ltr558_i2c_read_bytes(reg, data, 1); + if(ret != 2) { + PRINT_ERR("READ ERROR!ret=%d\n", ret); + return -1; + } + + ret = data[0]; + return ret; +} + +static int ltr558_i2c_write_2_bytes(u8 reg, u16 value) +{ + int ret = 0; + u8 data[3] = {0}; + + data[0] = reg; + data[1] = value & 0x00FF; + data[2] = value >> 8; + + ret = ltr558_i2c_write_bytes(data, 3); + if(ret != 1) { + PRINT_ERR("WRITE ERROR!ret=%d\n", ret); + return -1; + } + return 1; +} + +static int ltr558_i2c_write_1_byte(u8 reg, u8 value) +{ + int ret = 0; + u8 data[2] = {0}; + + data[0] = reg; + data[1] = value; + + ret = ltr558_i2c_write_bytes(data, 2); + if(ret != 1) { + PRINT_ERR("WRITE ERROR!ret=%d\n", ret); + return -1; + } + return 1; +} + +static int ltr_read_chip_info(struct i2c_client *client, char *buf) +{ + if(NULL == buf) { + return -1; + } + if(NULL == client) { + *buf = 0; + return -2; + } + + if( LTR_PLS_553 == LTR_PLS_MODE) + { + sprintf(buf, "LTR558ALS"); + printk("[LTR553] ltr_read_chip_info LTR553ALS\n"); + } + if(LTR_PLS_558 == LTR_PLS_MODE) + { + sprintf(buf, "LTR558ALS"); + printk("[LTR558] ltr_read_chip_info LTR558ALS\n"); + } + return 0; +} + +static int dynamic_cali(void) +{ + int i=0; + int j=0; + int val=0; + int data_total=0; + int noise=0; + + for (i = 0; i < 3; i++) + { + msleep(10); + val = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0); + data_total += val; + } + noise = data_total/3; + + if(noise < (dyna_cali + 500)) + { + if(noise < 1500) + { + ps_threshold_high = noise + 250; + ps_threshold_low = noise + 220; + }else{ + ps_threshold_high = 1800; + ps_threshold_low = 1600; + } + } + PRINT_INFO("ltr558_ps_enable, ps_threshold_high=%d, ps_threshold_low=%d\n", ps_threshold_high, ps_threshold_low); +} + +static int ltr558_ps_enable(u8 gainrange) +{ + int ret = -1; + u8 setgain; + ltr558_t *ltr_558als = (ltr558_t *)i2c_get_clientdata(this_client); + + if(LTR_PLS_553 == LTR_PLS_MODE) + { + switch (gainrange) + { + case PS_553_RANGE16: + setgain = MODE_PS_553_ON_Gain16; + break; + case PS_553_RANGE32: + setgain = MODE_PS_553_ON_Gain32; + break; + case PS_553_RANGE64: + setgain = MODE_PS_553_ON_Gain64; + break; + default: + setgain = MODE_PS_553_ON_Gain16; + break; + } + } + else + { + switch (gainrange) { + case PS_558_RANGE1: + setgain = MODE_PS_558_ON_Gain1; + break; + case PS_558_RANGE2: + setgain = MODE_PS_558_ON_Gain4; + break; + case PS_558_RANGE4: + setgain = MODE_PS_558_ON_Gain8; + break; + case PS_558_RANGE8: + setgain = MODE_PS_558_ON_Gain16; + break; + default: + setgain = MODE_PS_558_ON_Gain8; + break; + } + } + + ret = ltr558_i2c_write_1_byte(LTR558_PS_CONTR, setgain); + mdelay(WAKEUP_DELAY); + + if(setgain != ltr558_i2c_read_1_byte(LTR558_PS_CONTR)) + { + ret = ltr558_i2c_write_1_byte(LTR558_PS_CONTR, setgain); + mdelay(WAKEUP_DELAY); + } + + /*input report init*/ + input_report_abs(ltr_558als->input, ABS_DISTANCE, 1); + input_sync(ltr_558als->input); + + dynamic_cali(); + + PRINT_INFO("ltr558_ps_enable, gainrange=%d, ret=%d\n", gainrange, ret); + if(ret >= 0) + return 0; + else + return ret; +} + +static int ltr558_ps_disable(void) +{ + int ret = -1; + ltr558_t *ltr_558als = (ltr558_t *)i2c_get_clientdata(this_client); + ret = ltr558_i2c_write_1_byte(LTR558_PS_CONTR, MODE_PS_STANDBY); + + /*input report init*/ + input_report_abs(ltr_558als->input, ABS_DISTANCE, 1); + input_sync(ltr_558als->input); + + PRINT_INFO("ltr558_ps_disable, ret=%d\n", ret); + if(ret >= 0) + return 0; + else + return ret; +} + +static int ltr558_als_enable(u8 gainrange) +{ + int ret = -1; + u8 setgain; + + if(LTR_PLS_553 == LTR_PLS_MODE) + { + switch (gainrange) + { + case ALS_553_RANGE1_64K: + setgain = MODE_ALS_553_ON_Range1; + break; + + case ALS_553_RANGE2_32K: + setgain = MODE_ALS_553_ON_Range2; + break; + + case ALS_553_RANGE4_16K: + setgain = MODE_ALS_553_ON_Range4; + break; + + case ALS_553_RANGE8_8K: + setgain = MODE_ALS_553_ON_Range8; + break; + + case ALS_553_RANGE48_1K3: + setgain = MODE_ALS_553_ON_Range48; + break; + + case ALS_553_RANGE96_600: + setgain = MODE_ALS_553_ON_Range96; + break; + + default: + setgain = MODE_ALS_553_ON_Range1; + break; + } + } + else + { + switch (gainrange) + { + case ALS_558_RANGE1_320: + setgain = MODE_ALS_558_ON_Range1; + break; + + case ALS_558_RANGE2_64K: + setgain = MODE_ALS_558_ON_Range2; + break; + + default: + setgain = MODE_ALS_558_ON_Range1; + break; + } + } + + ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, setgain); + mdelay(WAKEUP_DELAY); + + if(setgain != ltr558_i2c_read_1_byte(LTR558_ALS_CONTR)) + { + ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, setgain); + mdelay(WAKEUP_DELAY); + } + + ltr558_i2c_read_1_byte(LTR558_ALS_PS_STATUS); + ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0); + ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH1); + ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH0); + + PRINT_INFO("ltr558_als_enable, gainrange=%d, ret = %d\n", gainrange, ret); + if(ret >= 0) + return 0; + else + return ret; +} + +// Put ALS into Standby mode +static int ltr558_als_disable(void) +{ + int ret = -1; + ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, MODE_ALS_STANDBY); + PRINT_INFO("ltr558_als_disable, ret = %d \n", ret); + if(ret >= 0) + return 0; + else + return ret; +} + +static int ltr558_als_read(int gainrange) +{ + int luxdata_int; + int luxdata_flt, ratio; + int ch0, ch1; + + /*IMPORTANT!CH1 MUST BE READ FIRST!*/ + ch1 = ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH1); + ch0 = ltr558_i2c_read_2_bytes(LTR558_ALS_DATA_CH0); + + PRINT_DBG("ch0=%d, ch1=%d\n", ch0, ch1); + if (0 == ch0) + ratio = 100; + else + ratio = (ch1 * 100) / ch0; + + // Compute Lux data from ALS data (ch0 and ch1) + // For Ratio < 0.69: + // 1.3618*CH0 – 1.5*CH1 + // For 0.69 <= Ratio < 1: + // 0.57*CH0 – 0.345*CH1 + // For high gain, divide the calculated lux by 150. + if (ratio < 69) { + luxdata_flt = (13618 * ch0) - (15000 * ch1); + luxdata_flt = luxdata_flt / 10000; + } else if ((ratio >= 69) && (ratio < 100)) { + luxdata_flt = (5700 * ch0) - (3450 * ch1); + luxdata_flt = luxdata_flt / 10000; + } else { + luxdata_flt = 0; + } + + // For Range1 + //if (gainrange == ALS_RANGE1_320) + // luxdata_flt = luxdata_flt / 150; + + luxdata_int = luxdata_flt / 80; + return luxdata_int; +} + +static int ltr558_open(struct inode *inode, struct file *file) +{ + PRINT_INFO("ltr558_open\n"); + return 0; +} + +static int ltr558_release(struct inode *inode, struct file *file) +{ + PRINT_INFO("ltr558_release\n"); + return 0; +} + +static long ltr558_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int flag, err; + char strbuf[256]; + PRINT_INFO("cmd = %d, %d\n", _IOC_NR(cmd), cmd); + switch (cmd) { + case LTR_IOCTL_SET_PFLAG: { + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + PRINT_INFO("LTR_IOCTL_SET_PFLAG = %d\n", flag); + if (1 == flag) { + ltr558_reg_init(); + msleep(20); + if (ltr558_ps_enable(p_gainrange)) + return -EIO; + } else if (0 == flag) { + if (ltr558_ps_disable()) + return -EIO; + } else { + return -EINVAL; + } + p_flag = flag; + } + break; + case LTR_IOCTL_SET_LFLAG: { + if (copy_from_user(&flag, argp, sizeof(flag))) + return -EFAULT; + PRINT_INFO("LTR_IOCTL_SET_LFLAG = %d\n", flag); + if (1 == flag) { + ltr558_reg_init(); + msleep(20); + if (ltr558_als_enable(l_gainrange)) + return -EIO; + } else if (0 == flag) { + if (ltr558_als_disable()) + return -EIO; + } else { + return -EINVAL; + } + l_flag = flag; + } + break; + case LTR_IOCTL_GET_CHIPINFO: { + err = ltr_read_chip_info(this_client, strbuf); + if(err < 0) + return -EFAULT; + if(copy_to_user(argp, strbuf, strlen(strbuf)+1)) + return -EFAULT; + } + break; + case LTR_IOCTL_GET_PFLAG: { + flag = p_flag; + PRINT_INFO("LTR_IOCTL_GET_PFLAG = %d\n", flag); + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + } + break; + case LTR_IOCTL_GET_LFLAG: { + flag = l_flag; + PRINT_INFO("LTR_IOCTL_GET_LFLAG = %d\n", flag); + if (copy_to_user(argp, &flag, sizeof(flag))) + return -EFAULT; + } + break; + default: + PRINT_ERR("unknown command: 0x%08X (%d)\n", cmd, cmd); + break; + } + return 0; +} + +static struct file_operations ltr558_fops = { + .owner = THIS_MODULE, + .open = ltr558_open, + .release = ltr558_release, + .unlocked_ioctl = ltr558_ioctl, +}; + +static struct miscdevice ltr558_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = LTR558_I2C_NAME, + .fops = <r558_fops, +}; + +static int get_min_value(int* array, int size) +{ + int ret = 0; + int i = 0; + ret = array[0]; + for(i=1; i<size; i++) { + if(ret > array[i]) + ret = array[i]; + } + return ret; +} + +static int get_max_value(int* array, int size) +{ + int ret = 0; + int i = 0; + ret = array[0]; + for(i=1; i<size; i++) { + if(ret < array[i]) + ret = array[i]; + } + return ret; +} + +static void ltr558_work(struct work_struct *work) +{ + int status = 0; + int value = 0; + ltr558_t *pls = container_of(work, ltr558_t, work); + + status = ltr558_i2c_read_1_byte(LTR558_ALS_PS_STATUS); + PRINT_DBG("LTR558_ALS_PS_STATUS = 0x%02X\n", status); + + if ((0x03 == (status & 0x03)) && (LTR_PLS_MODE == LTR_PLS_558)) {/*is 558 PS*/ + value = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0); + PRINT_DBG("LTR_PLS_MODE is pls 558, LTR558_PS_DATA_0 = %d\n", value); + if (value >= ps_threshold_high) { // 3cm //high + /*ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_0, 0xff); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_1, 0x07); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_0, 0xDC); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_1, 0x05); + */ + + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x07FF); + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, ps_threshold_low); + + input_report_abs(pls->input, ABS_DISTANCE, 0); + input_sync(pls->input); + } else if (value <= ps_threshold_low) { // 5cm //low + /*ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_0, 0x0E); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_1, 0x06); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_0, 0x00); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_1, 0x00); + */ + + if(dyna_cali > 20 && value <(dyna_cali - 100)) + { + if(value < 1500) + { + ps_threshold_high = value + 250; + ps_threshold_low = value + 220; + }else{ + ps_threshold_high = 1800; + ps_threshold_low = 1600; + } + } + + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, ps_threshold_high); + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000); + + input_report_abs(pls->input, ABS_DISTANCE, 1); + input_sync(pls->input); + } + } + + if ((0x03 == (status & 0x03)) && (LTR_PLS_MODE == LTR_PLS_553)) {/*is 553 PS*/ + value = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0); + PRINT_DBG("LTR_PLS_MODE is pls 553 \n"); +#ifdef LTR558_ADAPTIVE + /*threshold detect process*/ + if(0 == ps_max || 0 == ps_min) { + ps_min = value; + ps_max = value; + ps_threshold = ps_min + MIN_SPACING; + } + if(value > ps_max) { + ps_max_filter[max_index++] = value; + if(ARRAY_SIZE(ps_max_filter) == max_index) { + ps_max = get_min_value(ps_max_filter, ARRAY_SIZE(ps_max_filter)); + ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min; + if(ps_threshold - ps_min < MIN_SPACING) + ps_threshold = ps_min + MIN_SPACING; + max_index = 0; + } + min_index = 0; + } else if(value < ps_min) { + ps_min_filter[min_index++] = value; + if(ARRAY_SIZE(ps_min_filter) == min_index) { + ps_min = get_max_value(ps_min_filter, ARRAY_SIZE(ps_min_filter)); + ps_threshold = ((ps_max - ps_min) / DIVEDE) + ps_min; + if(ps_threshold - ps_min < MIN_SPACING) + ps_threshold = ps_min + MIN_SPACING; + min_index = 0; + } + max_index = 0; + } else { + max_index = 0; + min_index = 0; + } + /*threshold detect process end*/ + + if (value > (ps_threshold + DEBOUNCE)) { + input_report_abs(pls->input, ABS_DISTANCE, 0); + input_sync(pls->input); + } else if (value < (ps_threshold - DEBOUNCE)) { + input_report_abs(pls->input, ABS_DISTANCE, 1); + input_sync(pls->input); + } + PRINT_DBG("PS INT: PS_DATA_VAL = 0x%04X(%4d) ps_min=%4d ps_max=%4d ps_threshold=%4d\n", \ + value, value, ps_min, ps_max, ps_threshold); +#else + if (value >= ps_threshold) { + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x07FF); + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, ps_threshold); + input_report_abs(pls->input, ABS_DISTANCE, 0); + input_sync(pls->input); + } else if (value <= ps_threshold) { + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, ps_threshold); + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000); + input_report_abs(pls->input, ABS_DISTANCE, 1); + input_sync(pls->input); + } + PRINT_DBG("PS INT: PS_DATA_VAL = 0x%04X ( %d )\n", value, value); +#endif + } + if (0x0c == (status & 0x0c)) {/*is ALS*/ + value = ltr558_als_read(l_gainrange); + PRINT_DBG("ALS INT: ALS_DATA_VAL = 0x%04X(%4d)\n", value, value); + input_report_abs(pls->input, ABS_MISC, value); + input_sync(pls->input); + } + + enable_irq(pls->client->irq); +} + +static irqreturn_t ltr558_irq_handler(int irq, void *dev_id) +{ + ltr558_t *pls = (ltr558_t *) dev_id; + + disable_irq_nosync(pls->client->irq); + queue_work(pls->ltr_work_queue, &pls->work); + return IRQ_HANDLED; +} + +static int ltr558_sw_reset(void) +{ + int ret = 0; + ret = ltr558_i2c_write_1_byte(LTR558_ALS_CONTR, 0x04); + if(1 == ret) + PRINT_INFO("ltr558_sw_reset success\n"); + else + PRINT_ERR("ltr558_sw_reset failed! ret = %d\n", ret); + return ret; +} + +static int ltr558_reg_init(void) +{ + int ret = 0; +// ret = ltr558_sw_reset(); +// if(1 != ret) { +// PRINT_ERR("ltr558_reg_init failed! ret = %d\n", ret); +// return ret; +// } +// mdelay(PON_DELAY); + + if(LTR_PLS_558 == LTR_PLS_MODE){ + //set: LED Pulse Frequency=60KHz,LED Duty Cycle=100%,LED Peak Current=50mA + ltr558_i2c_write_1_byte(LTR558_PS_LED, 0x7B); + //set: LED Pulse Count=15 + ltr558_i2c_write_1_byte(LTR558_PS_N_PULSES , 0x1f); + //set: PS Measurement Repeat Rate: 0x00=50ms 0x02=100ms + ltr558_i2c_write_1_byte(LTR558_PS_MEAS_RATE, 0x00); + //set: ALS Integration Time=100ms, ALS Measurement Repeat Rate=100ms + ltr558_i2c_write_1_byte(LTR558_ALS_MEAS_RATE, 0x13); + ltr558_i2c_write_1_byte(LTR558_INTERRUPT_PERSIST,0x02); + //set: INT MODE=updated after every measurement, + //=active low level, + //=both PS & ALS measurement can trigger interrupt + ltr558_i2c_write_1_byte(LTR558_INTERRUPT, 0x0B); + }else{ + //set: LED Pulse Frequency=60KHz,LED Duty Cycle=100%,LED Peak Current=50mA + ltr558_i2c_write_1_byte(LTR558_PS_LED, 0x7B); + //set: LED Pulse Count=15 + ltr558_i2c_write_1_byte(LTR558_PS_N_PULSES , 0x08); + //set: PS Measurement Repeat Rate: 0x00=50ms 0x02=100ms + ltr558_i2c_write_1_byte(LTR558_PS_MEAS_RATE, 0x00); + //set: ALS Integration Time=100ms, ALS Measurement Repeat Rate=100ms + ltr558_i2c_write_1_byte(LTR558_ALS_MEAS_RATE, 0x03); + ltr558_i2c_write_1_byte(LTR558_INTERRUPT_PERSIST,0x12); + //set: INT MODE=updated after every measurement, + //=active low level, + //=both PS & ALS measurement can trigger interrupt + ltr558_i2c_write_1_byte(LTR558_INTERRUPT, 0x03); + } + //set: PS Persist=2, ALS Persist=2 + //ltr558_i2c_write_1_byte(LTR558_INTERRUPT_PERSIST, 0x22); + //set: PS Threshold + //ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x0000); + //ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000); + //set: ALS Threshold + //ltr558_i2c_write_2_bytes(LTR558_ALS_THRES_UP, 0x0000); + //ltr558_i2c_write_2_bytes(LTR558_ALS_THRES_LOW, 0x0001);/*DO NOT set:0x0000*/ + + // ps + ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_0, 0x00); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_UP_1, 0x03); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_0, 0xf0); + ltr558_i2c_write_1_byte(LTR558_PS_THRES_LOW_1, 0x02); + + // als + ltr558_i2c_write_1_byte(LTR558_ALS_THRES_UP_0, 0x00); + ltr558_i2c_write_1_byte(LTR558_ALS_THRES_UP_1, 0x00); + ltr558_i2c_write_1_byte(LTR558_ALS_THRES_LOW_0, 0x01); + ltr558_i2c_write_1_byte(LTR558_ALS_THRES_LOW_1, 0x00); + + msleep(WAKEUP_DELAY); + + PRINT_INFO("ltr558_reg_init success!\n"); + return ret; +} + +static int ltr558_version_check(void) +{ + int part_id = -1; + int manufacturer_id = -1; + part_id = ltr558_i2c_read_1_byte(LTR558_PART_NUMBER_ID); + manufacturer_id = ltr558_i2c_read_1_byte(LTR558_MANUFACTURER_ID); + PRINT_INFO("PART_ID: 0x%02X MANUFACTURER_ID: 0x%02X\n", part_id, manufacturer_id); + + if(part_id == 0x80 && manufacturer_id == 0x05) { + PRINT_INFO("I'm LTR558, and I'm working now\n"); + return 0; + }else if(part_id == 0x92 && manufacturer_id == 0x05) + { + PRINT_INFO("I'm LTR553, and I'm working now\n"); + return 0; + } + else if(part_id < 0 || manufacturer_id < 0) { + PRINT_ERR("can't read who am I\n"); + return -1; + } else { + PRINT_ERR("I'm working, but I'm NOT LTR558\n"); + return -1; + } +} + +static ssize_t ltr_558als_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + return sprintf(buff, "ps_min=%4d ps_max=%4d ps_threshold=%4d\n", ps_min, ps_max, ps_threshold); +} + +#ifdef LTR588_DBG +static int str2int(char *p, char *end) +{ + int result = 0; + int len = end - p + 1; + int c; + + for(; len > 0; len--, p++) { + if(p > end) + return -1; + c = *p - '0'; + if((unsigned int)c >= 10) + return -1; + result = result * 10 + c; + } + return result; +} + +/*parse a int from the string*/ +static int get_int(char *p, u8 max_len) +{ + char *start = p; + char *end =NULL; + + if(max_len > 60) { + PRINT_ERR("CMD ERROR!the max length of the cmd should less than 60\n"); + return -1; + } + for(; max_len > 0; max_len--, p++) { + if(*p >= '0' && *p <= '9') { + start = p; + break; + } + } + if(0 == max_len) + return -1; + + end = start; + + for(; max_len > 0; max_len--, p++) { + if(*p >= '0' && *p <= '9') { + end = p; + continue; + } + break; + } + + return str2int(start, end); +} + +static ssize_t ltr_558als_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t n) +{ + char *buff_temp = NULL; + + buff_temp = kmalloc(n, GFP_KERNEL); + if(NULL == buff_temp){ + PRINT_ERR("kmalloc err\n"); + return n; + } + + memcpy(buff_temp, buff, n); + + ps_threshold = get_int(buff_temp, n); + if(0x07FF < ps_threshold) { + ps_threshold = LTR558_PS_THRESHOLD; + PRINT_WARN("ps_threshold value over range, replace with the default value\n"); + } + PRINT_INFO("set ps_threshold = 0x%04X ( %d )\n", ps_threshold, ps_threshold); + + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x0000); + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000); + + if(buff_temp != NULL) + kfree(buff_temp); + + return n; +} + +static int break_loop = 0; +static ssize_t ltr_558als_val_show(struct kobject *kobj, struct kobj_attribute *attr, char *buff) +{ + int measured_val = 0; + ltr558_t *pls = (ltr558_t *)i2c_get_clientdata(this_client); + + disable_irq_nosync(pls->client->irq); + ltr558_als_enable(l_gainrange); + ltr558_ps_enable(p_gainrange); + + break_loop = 0; + while(1) { + measured_val = ltr558_i2c_read_2_bytes(LTR558_PS_DATA_0); + PRINT_DBG("PS_DATA_VAL = 0x%04X ( %d )\n", measured_val, measured_val); + input_report_abs(pls->input, ABS_DISTANCE, measured_val); + input_sync(pls->input); + + measured_val = ltr558_als_read(l_gainrange); + PRINT_DBG("ALS_DATA_VAL = 0x%04X ( %d )\n", measured_val, measured_val); + input_report_abs(pls->input, ABS_MISC, measured_val); + input_sync(pls->input); + + if(1 == break_loop) + break; + msleep(200); + } + ltr558_ps_disable(); + ltr558_als_disable(); + enable_irq(pls->client->irq); + + return 0; +} + +static ssize_t ltr_558als_val_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buff, size_t n) +{ + break_loop = 1; + PRINT_INFO("stop show value\n"); + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_UP, 0x0000); + ltr558_i2c_write_2_bytes(LTR558_PS_THRES_LOW, 0x0000); + return n; +} +#endif + +static struct kobject *ltr_558als_kobj; +static struct kobj_attribute ltr_558als_attr = + __ATTR(ps_threshold, 0644, ltr_558als_show, NULL); +#ifdef LTR588_DBG +static struct kobj_attribute ltr_558als_val_attr = + __ATTR(show_val, 0644, ltr_558als_val_show, ltr_558als_val_store); +#endif + +static int ltr_558als_sysfs_init(struct input_dev *input_dev) +{ + int ret = -1; + + ltr_558als_kobj = kobject_create_and_add("ltr_558als", &(input_dev->dev.kobj)); + if (ltr_558als_kobj == NULL) { + ret = -ENOMEM; + PRINT_ERR("register sysfs failed. ret = %d\n", ret); + return ret; + } + + ret = sysfs_create_file(ltr_558als_kobj, <r_558als_attr.attr); + if (ret) { + PRINT_ERR("create sysfs failed. ret = %d\n", ret); + return ret; + } +#ifdef LTR588_DBG + ret = sysfs_create_file(ltr_558als_kobj, <r_558als_val_attr.attr); + if (ret) { + PRINT_ERR("create sysfs failed. ret = %d\n", ret); + return ret; + } +#endif + return ret; +} + +#ifdef CONFIG_HAS_EARLYSUSPEND +static void ltr558_early_suspend(struct early_suspend *handler) +{ + PRINT_INFO("ltr558_early_suspend\n"); +} + +static void ltr558_late_resume(struct early_suspend *handler) +{ + PRINT_INFO("ltr558_late_resume\n"); +} +#endif + +static int ltr558_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int ret = 0; + ltr558_t *ltr_558als = NULL; + struct input_dev *input_dev = NULL; + struct ltr558_pls_platform_data *pdata = client->dev.platform_data; + int chip_id = 0; +#ifdef CONFIG_OF + struct device_node *np = client->dev.of_node; + if (np && !pdata){ + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, "Could not allocate struct ltr558_pls_platform_data"); + goto exit_allocate_pdata_failed; + } + pdata->irq_gpio_number = of_get_gpio(np, 0); + if(pdata->irq_gpio_number < 0){ + dev_err(&client->dev, "fail to get irq_gpio_number\n"); + kfree(pdata); + goto exit_irq_gpio_read_fail; + } + client->dev.platform_data = pdata; + } +#endif + ret = gpio_request(pdata->irq_gpio_number, LTR558_PLS_IRQ_PIN); + if(ret) { + PRINT_ERR("gpio_request failed!\n"); + goto exit_gpio_request_failed; + } + gpio_direction_input(pdata->irq_gpio_number); + client->irq = gpio_to_irq(pdata->irq_gpio_number); + PRINT_INFO("client->irq = %d\n", client->irq); + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + PRINT_ERR("i2c_check_functionality failed!\n"); + ret = -ENODEV; + goto exit_i2c_check_functionality_failed; + } + + ltr_558als = kzalloc(sizeof(ltr558_t), GFP_KERNEL); + if (!ltr_558als) { + PRINT_ERR("kzalloc failed!\n"); + ret = -ENOMEM; + goto exit_kzalloc_failed; + } + + i2c_set_clientdata(client, ltr_558als); + ltr_558als->client = client; + this_client = client; + + chip_id = ltr558_i2c_read_1_byte(LTR558_PART_ID); + if(chip_id < 0) + { + PRINT_ERR("ltr558 or ltr553 read chip_id failed!\n"); + ret = -ENOMEM; + goto exit_read_chip_id_failed; + } + if(LTR_553_PART_ID == chip_id) + { + LTR_PLS_MODE = LTR_PLS_553; + p_gainrange = PS_553_RANGE16; + l_gainrange = ALS_553_RANGE1_64K; + } + else + { + LTR_PLS_MODE = LTR_PLS_558; + p_gainrange = PS_558_RANGE4; + l_gainrange = ALS_558_RANGE1_320; + } + + input_dev = input_allocate_device(); + if (!input_dev) { + PRINT_ERR("input_allocate_device failed!\n"); + ret = -ENOMEM; + goto exit_input_allocate_device_failed; + } + + input_dev->name = LTR558_INPUT_DEV; + input_dev->phys = LTR558_INPUT_DEV; + input_dev->id.bustype = BUS_I2C; + input_dev->dev.parent = &client->dev; + input_dev->id.vendor = 0x0001; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0010; + ltr_558als->input = input_dev; + + __set_bit(EV_ABS, input_dev->evbit); + input_set_abs_params(input_dev, ABS_DISTANCE, 0, 1, 0, 0); + input_set_abs_params(input_dev, ABS_MISC, 0, 100001, 0, 0); + + ret = input_register_device(input_dev); + if (ret < 0) { + PRINT_ERR("input_register_device failed!\n"); + input_free_device(input_dev); + input_dev = NULL; + goto exit_input_register_device_failed; + } + + ret = misc_register(<r558_device); + if (ret) { + PRINT_ERR("misc_register failed!\n"); + goto exit_misc_register_failed; + } + + if (ltr558_reg_init() < 0) { + PRINT_ERR("ltr558_reg_init failed!\n"); + ret = -1; + goto exit_ltr558_reg_init_failed; + } + + ret = ltr558_version_check(); + if(ret) { + PRINT_ERR("ltr558_version_check failed!\n"); + goto exit_ltr558_version_check_failed; + } + + INIT_WORK(<r_558als->work, ltr558_work); + ltr_558als->ltr_work_queue = create_singlethread_workqueue(LTR558_I2C_NAME); + if (!ltr_558als->ltr_work_queue) { + PRINT_ERR("create_singlethread_workqueue failed!\n"); + goto exit_create_singlethread_workqueue_failed; + } + + if (client->irq > 0) { + ret = request_irq(client->irq, ltr558_irq_handler, IRQ_TYPE_LEVEL_LOW | IRQF_NO_SUSPEND, client->name, ltr_558als); + if (ret < 0) { + PRINT_ERR("request_irq failed!\n"); + goto exit_request_irq_failed; + } + } + +#ifdef CONFIG_HAS_EARLYSUSPEND + ltr_558als->ltr_early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB + 25; + ltr_558als->ltr_early_suspend.suspend = ltr558_early_suspend; + ltr_558als->ltr_early_suspend.resume = ltr558_late_resume; + register_early_suspend(<r_558als->ltr_early_suspend); +#endif + + ret = ltr_558als_sysfs_init(input_dev); + if(ret) { + PRINT_ERR("ltr_558als_sysfs_init failed!\n"); + goto exit_ltr_558als_sysfs_init_failed; + } + + PRINT_INFO("probe success!\n"); + return 0; + +exit_ltr_558als_sysfs_init_failed: + free_irq(ltr_558als->client->irq, ltr_558als); +exit_request_irq_failed: + destroy_workqueue(ltr_558als->ltr_work_queue); + ltr_558als->ltr_work_queue = NULL; +exit_create_singlethread_workqueue_failed: +exit_ltr558_version_check_failed: +exit_ltr558_reg_init_failed: + misc_deregister(<r558_device); +exit_misc_register_failed: + input_unregister_device(input_dev); +exit_input_register_device_failed: +exit_input_allocate_device_failed: +exit_read_chip_id_failed: + kfree(ltr_558als); + ltr_558als = NULL; +exit_kzalloc_failed: +exit_i2c_check_functionality_failed: + gpio_free(pdata->irq_gpio_number); +exit_gpio_request_failed: +exit_irq_gpio_read_fail: +exit_allocate_pdata_failed: + PRINT_ERR("probe failed!\n"); + return ret; +} + +static int ltr558_remove(struct i2c_client *client) +{ + ltr558_t *ltr_558als = i2c_get_clientdata(client); + +#ifdef CONFIG_HAS_EARLYSUSPEND + unregister_early_suspend(<r_558als->ltr_early_suspend); +#endif + + flush_workqueue(ltr_558als->ltr_work_queue); + destroy_workqueue(ltr_558als->ltr_work_queue); + ltr_558als->ltr_work_queue = NULL; + + misc_deregister(<r558_device); + input_unregister_device(ltr_558als->input); + input_free_device(ltr_558als->input); + ltr_558als->input = NULL; + free_irq(ltr_558als->client->irq, ltr_558als); + gpio_free(irq_to_gpio(ltr_558als->client->irq)); + kfree(ltr_558als); + ltr_558als = NULL; + this_client = NULL; + + PRINT_INFO("ltr558_remove\n"); + return 0; +} + +static const struct i2c_device_id ltr558_id[] = { + {LTR558_I2C_NAME, 0}, + {} +}; + +static const struct of_device_id ltr558_of_match[] = { + { .compatible = "LITEON,ltr_558als", }, + {} +}; +MODULE_DEVICE_TABLE(of, ltr558_of_match); +static struct i2c_driver ltr558_driver = { + .driver = { + .owner = THIS_MODULE, + .name = LTR558_I2C_NAME, + .of_match_table = ltr558_of_match, + }, + .probe = ltr558_probe, + .remove = ltr558_remove, + .id_table = ltr558_id, +}; + +static int __init ltr558_init(void) +{ + int ret = -1; + ret = i2c_add_driver(<r558_driver); + if(ret) { + PRINT_ERR("i2c_add_driver failed!\n"); + return ret; + } + return ret; +} + +static void __exit ltr558_exit(void) +{ + i2c_del_driver(<r558_driver); +} + +late_initcall(ltr558_init); +module_exit(ltr558_exit); + +MODULE_AUTHOR("Yaochuan Li <yaochuan.li@spreadtrum.com>"); +MODULE_DESCRIPTION("Proximity&Light Sensor LTR558ALS DRIVER"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/m68kspkr.c b/drivers/input/misc/m68kspkr.c new file mode 100644 index 00000000..b40ee4b4 --- /dev/null +++ b/drivers/input/misc/m68kspkr.c @@ -0,0 +1,151 @@ +/* + * m68k beeper driver for Linux + * + * Copyright (c) 2002 Richard Zidlicky + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 1992 Orest Zborowski + * + */ + +/* + * 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 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <asm/machdep.h> +#include <asm/io.h> + +MODULE_AUTHOR("Richard Zidlicky <rz@linux-m68k.org>"); +MODULE_DESCRIPTION("m68k beeper driver"); +MODULE_LICENSE("GPL"); + +static struct platform_device *m68kspkr_platform_device; + +static int m68kspkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int count = 0; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = 1193182 / value; + + mach_beep(count, -1); + + return 0; +} + +static int m68kspkr_probe(struct platform_device *dev) +{ + struct input_dev *input_dev; + int err; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "m68k beeper"; + input_dev->phys = "m68k/generic"; + input_dev->id.bustype = BUS_HOST; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = &dev->dev; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + input_dev->event = m68kspkr_event; + + err = input_register_device(input_dev); + if (err) { + input_free_device(input_dev); + return err; + } + + platform_set_drvdata(dev, input_dev); + + return 0; +} + +static int m68kspkr_remove(struct platform_device *dev) +{ + struct input_dev *input_dev = platform_get_drvdata(dev); + + input_unregister_device(input_dev); + platform_set_drvdata(dev, NULL); + /* turn off the speaker */ + m68kspkr_event(NULL, EV_SND, SND_BELL, 0); + + return 0; +} + +static void m68kspkr_shutdown(struct platform_device *dev) +{ + /* turn off the speaker */ + m68kspkr_event(NULL, EV_SND, SND_BELL, 0); +} + +static struct platform_driver m68kspkr_platform_driver = { + .driver = { + .name = "m68kspkr", + .owner = THIS_MODULE, + }, + .probe = m68kspkr_probe, + .remove = m68kspkr_remove, + .shutdown = m68kspkr_shutdown, +}; + +static int __init m68kspkr_init(void) +{ + int err; + + if (!mach_beep) { + printk(KERN_INFO "m68kspkr: no lowlevel beep support\n"); + return -ENODEV; + } + + err = platform_driver_register(&m68kspkr_platform_driver); + if (err) + return err; + + m68kspkr_platform_device = platform_device_alloc("m68kspkr", -1); + if (!m68kspkr_platform_device) { + err = -ENOMEM; + goto err_unregister_driver; + } + + err = platform_device_add(m68kspkr_platform_device); + if (err) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(m68kspkr_platform_device); + err_unregister_driver: + platform_driver_unregister(&m68kspkr_platform_driver); + + return err; +} + +static void __exit m68kspkr_exit(void) +{ + platform_device_unregister(m68kspkr_platform_device); + platform_driver_unregister(&m68kspkr_platform_driver); +} + +module_init(m68kspkr_init); +module_exit(m68kspkr_exit); diff --git a/drivers/input/misc/max8925_onkey.c b/drivers/input/misc/max8925_onkey.c new file mode 100644 index 00000000..f9179b25 --- /dev/null +++ b/drivers/input/misc/max8925_onkey.c @@ -0,0 +1,201 @@ +/** + * MAX8925 ONKEY driver + * + * Copyright (C) 2009 Marvell International Ltd. + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/mfd/max8925.h> +#include <linux/slab.h> + +#define SW_INPUT (1 << 7) /* 0/1 -- up/down */ +#define HARDRESET_EN (1 << 7) +#define PWREN_EN (1 << 7) + +struct max8925_onkey_info { + struct input_dev *idev; + struct i2c_client *i2c; + struct device *dev; + unsigned int irq[2]; +}; + +/* + * MAX8925 gives us an interrupt when ONKEY is pressed or released. + * max8925_set_bits() operates I2C bus and may sleep. So implement + * it in thread IRQ handler. + */ +static irqreturn_t max8925_onkey_handler(int irq, void *data) +{ + struct max8925_onkey_info *info = data; + int state; + + state = max8925_reg_read(info->i2c, MAX8925_ON_OFF_STATUS); + + input_report_key(info->idev, KEY_POWER, state & SW_INPUT); + input_sync(info->idev); + + dev_dbg(info->dev, "onkey state:%d\n", state); + + /* Enable hardreset to halt if system isn't shutdown on time */ + max8925_set_bits(info->i2c, MAX8925_SYSENSEL, + HARDRESET_EN, HARDRESET_EN); + + return IRQ_HANDLED; +} + +static int max8925_onkey_probe(struct platform_device *pdev) +{ + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + struct max8925_onkey_info *info; + struct input_dev *input; + int irq[2], error; + + irq[0] = platform_get_irq(pdev, 0); + if (irq[0] < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + irq[1] = platform_get_irq(pdev, 1); + if (irq[1] < 0) { + dev_err(&pdev->dev, "No IRQ resource!\n"); + return -EINVAL; + } + + info = kzalloc(sizeof(struct max8925_onkey_info), GFP_KERNEL); + input = input_allocate_device(); + if (!info || !input) { + error = -ENOMEM; + goto err_free_mem; + } + + info->idev = input; + info->i2c = chip->i2c; + info->dev = &pdev->dev; + info->irq[0] = irq[0]; + info->irq[1] = irq[1]; + + input->name = "max8925_on"; + input->phys = "max8925_on/input0"; + input->id.bustype = BUS_I2C; + input->dev.parent = &pdev->dev; + input_set_capability(input, EV_KEY, KEY_POWER); + + error = request_threaded_irq(irq[0], NULL, max8925_onkey_handler, + IRQF_ONESHOT, "onkey-down", info); + if (error < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + irq[0], error); + goto err_free_mem; + } + + error = request_threaded_irq(irq[1], NULL, max8925_onkey_handler, + IRQF_ONESHOT, "onkey-up", info); + if (error < 0) { + dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", + irq[1], error); + goto err_free_irq0; + } + + error = input_register_device(info->idev); + if (error) { + dev_err(chip->dev, "Can't register input device: %d\n", error); + goto err_free_irq1; + } + + platform_set_drvdata(pdev, info); + device_init_wakeup(&pdev->dev, 1); + + return 0; + +err_free_irq1: + free_irq(irq[1], info); +err_free_irq0: + free_irq(irq[0], info); +err_free_mem: + input_free_device(input); + kfree(info); + + return error; +} + +static int max8925_onkey_remove(struct platform_device *pdev) +{ + struct max8925_onkey_info *info = platform_get_drvdata(pdev); + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + + free_irq(info->irq[0] + chip->irq_base, info); + free_irq(info->irq[1] + chip->irq_base, info); + input_unregister_device(info->idev); + kfree(info); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max8925_onkey_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct max8925_onkey_info *info = platform_get_drvdata(pdev); + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + + if (device_may_wakeup(dev)) { + chip->wakeup_flag |= 1 << info->irq[0]; + chip->wakeup_flag |= 1 << info->irq[1]; + } + + return 0; +} + +static int max8925_onkey_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct max8925_onkey_info *info = platform_get_drvdata(pdev); + struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent); + + if (device_may_wakeup(dev)) { + chip->wakeup_flag &= ~(1 << info->irq[0]); + chip->wakeup_flag &= ~(1 << info->irq[1]); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max8925_onkey_pm_ops, max8925_onkey_suspend, max8925_onkey_resume); + +static struct platform_driver max8925_onkey_driver = { + .driver = { + .name = "max8925-onkey", + .owner = THIS_MODULE, + .pm = &max8925_onkey_pm_ops, + }, + .probe = max8925_onkey_probe, + .remove = max8925_onkey_remove, +}; +module_platform_driver(max8925_onkey_driver); + +MODULE_DESCRIPTION("Maxim MAX8925 ONKEY driver"); +MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/max8997_haptic.c b/drivers/input/misc/max8997_haptic.c new file mode 100644 index 00000000..e9731332 --- /dev/null +++ b/drivers/input/misc/max8997_haptic.c @@ -0,0 +1,407 @@ +/* + * MAX8997-haptic controller driver + * + * Copyright (C) 2012 Samsung Electronics + * Donggeun Kim <dg77.kim@samsung.com> + * + * This program is not provided / owned by Maxim Integrated Products. + * + * 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/init.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/pwm.h> +#include <linux/input.h> +#include <linux/mfd/max8997-private.h> +#include <linux/mfd/max8997.h> +#include <linux/regulator/consumer.h> + +/* Haptic configuration 2 register */ +#define MAX8997_MOTOR_TYPE_SHIFT 7 +#define MAX8997_ENABLE_SHIFT 6 +#define MAX8997_MODE_SHIFT 5 + +/* Haptic driver configuration register */ +#define MAX8997_CYCLE_SHIFT 6 +#define MAX8997_SIG_PERIOD_SHIFT 4 +#define MAX8997_SIG_DUTY_SHIFT 2 +#define MAX8997_PWM_DUTY_SHIFT 0 + +struct max8997_haptic { + struct device *dev; + struct i2c_client *client; + struct input_dev *input_dev; + struct regulator *regulator; + + struct work_struct work; + struct mutex mutex; + + bool enabled; + unsigned int level; + + struct pwm_device *pwm; + unsigned int pwm_period; + enum max8997_haptic_pwm_divisor pwm_divisor; + + enum max8997_haptic_motor_type type; + enum max8997_haptic_pulse_mode mode; + + unsigned int internal_mode_pattern; + unsigned int pattern_cycle; + unsigned int pattern_signal_period; +}; + +static int max8997_haptic_set_duty_cycle(struct max8997_haptic *chip) +{ + int ret = 0; + + if (chip->mode == MAX8997_EXTERNAL_MODE) { + unsigned int duty = chip->pwm_period * chip->level / 100; + ret = pwm_config(chip->pwm, duty, chip->pwm_period); + } else { + int i; + u8 duty_index = 0; + + for (i = 0; i <= 64; i++) { + if (chip->level <= i * 100 / 64) { + duty_index = i; + break; + } + } + switch (chip->internal_mode_pattern) { + case 0: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC1, duty_index); + break; + case 1: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC2, duty_index); + break; + case 2: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC3, duty_index); + break; + case 3: + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGPWMDC4, duty_index); + break; + default: + break; + } + } + return ret; +} + +static void max8997_haptic_configure(struct max8997_haptic *chip) +{ + u8 value; + + value = chip->type << MAX8997_MOTOR_TYPE_SHIFT | + chip->enabled << MAX8997_ENABLE_SHIFT | + chip->mode << MAX8997_MODE_SHIFT | chip->pwm_divisor; + max8997_write_reg(chip->client, MAX8997_HAPTIC_REG_CONF2, value); + + if (chip->mode == MAX8997_INTERNAL_MODE && chip->enabled) { + value = chip->internal_mode_pattern << MAX8997_CYCLE_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_PERIOD_SHIFT | + chip->internal_mode_pattern << MAX8997_SIG_DUTY_SHIFT | + chip->internal_mode_pattern << MAX8997_PWM_DUTY_SHIFT; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_DRVCONF, value); + + switch (chip->internal_mode_pattern) { + case 0: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF1, value); + break; + + case 1: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF1, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF2, value); + break; + + case 2: + value = chip->pattern_cycle << 4; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF3, value); + break; + + case 3: + value = chip->pattern_cycle; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_CYCLECONF2, value); + value = chip->pattern_signal_period; + max8997_write_reg(chip->client, + MAX8997_HAPTIC_REG_SIGCONF4, value); + break; + + default: + break; + } + } +} + +static void max8997_haptic_enable(struct max8997_haptic *chip) +{ + int error; + + mutex_lock(&chip->mutex); + + error = max8997_haptic_set_duty_cycle(chip); + if (error) { + dev_err(chip->dev, "set_pwm_cycle failed, error: %d\n", error); + goto out; + } + + if (!chip->enabled) { + chip->enabled = true; + regulator_enable(chip->regulator); + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_enable(chip->pwm); + } + +out: + mutex_unlock(&chip->mutex); +} + +static void max8997_haptic_disable(struct max8997_haptic *chip) +{ + mutex_lock(&chip->mutex); + + if (chip->enabled) { + chip->enabled = false; + max8997_haptic_configure(chip); + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_disable(chip->pwm); + regulator_disable(chip->regulator); + } + + mutex_unlock(&chip->mutex); +} + +static void max8997_haptic_play_effect_work(struct work_struct *work) +{ + struct max8997_haptic *chip = + container_of(work, struct max8997_haptic, work); + + if (chip->level) + max8997_haptic_enable(chip); + else + max8997_haptic_disable(chip); +} + +static int max8997_haptic_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct max8997_haptic *chip = input_get_drvdata(dev); + + chip->level = effect->u.rumble.strong_magnitude; + if (!chip->level) + chip->level = effect->u.rumble.weak_magnitude; + + schedule_work(&chip->work); + + return 0; +} + +static void max8997_haptic_close(struct input_dev *dev) +{ + struct max8997_haptic *chip = input_get_drvdata(dev); + + cancel_work_sync(&chip->work); + max8997_haptic_disable(chip); +} + +static int max8997_haptic_probe(struct platform_device *pdev) +{ + struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent); + const struct max8997_platform_data *pdata = + dev_get_platdata(iodev->dev); + const struct max8997_haptic_platform_data *haptic_pdata = + pdata->haptic_pdata; + struct max8997_haptic *chip; + struct input_dev *input_dev; + int error; + + if (!haptic_pdata) { + dev_err(&pdev->dev, "no haptic platform data\n"); + return -EINVAL; + } + + chip = kzalloc(sizeof(struct max8997_haptic), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!chip || !input_dev) { + dev_err(&pdev->dev, "unable to allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + INIT_WORK(&chip->work, max8997_haptic_play_effect_work); + mutex_init(&chip->mutex); + + chip->client = iodev->haptic; + chip->dev = &pdev->dev; + chip->input_dev = input_dev; + chip->pwm_period = haptic_pdata->pwm_period; + chip->type = haptic_pdata->type; + chip->mode = haptic_pdata->mode; + chip->pwm_divisor = haptic_pdata->pwm_divisor; + + switch (chip->mode) { + case MAX8997_INTERNAL_MODE: + chip->internal_mode_pattern = + haptic_pdata->internal_mode_pattern; + chip->pattern_cycle = haptic_pdata->pattern_cycle; + chip->pattern_signal_period = + haptic_pdata->pattern_signal_period; + break; + + case MAX8997_EXTERNAL_MODE: + chip->pwm = pwm_request(haptic_pdata->pwm_channel_id, + "max8997-haptic"); + if (IS_ERR(chip->pwm)) { + error = PTR_ERR(chip->pwm); + dev_err(&pdev->dev, + "unable to request PWM for haptic, error: %d\n", + error); + goto err_free_mem; + } + break; + + default: + dev_err(&pdev->dev, + "Invalid chip mode specified (%d)\n", chip->mode); + error = -EINVAL; + goto err_free_mem; + } + + chip->regulator = regulator_get(&pdev->dev, "inmotor"); + if (IS_ERR(chip->regulator)) { + error = PTR_ERR(chip->regulator); + dev_err(&pdev->dev, + "unable to get regulator, error: %d\n", + error); + goto err_free_pwm; + } + + input_dev->name = "max8997-haptic"; + input_dev->id.version = 1; + input_dev->dev.parent = &pdev->dev; + input_dev->close = max8997_haptic_close; + input_set_drvdata(input_dev, chip); + input_set_capability(input_dev, EV_FF, FF_RUMBLE); + + error = input_ff_create_memless(input_dev, NULL, + max8997_haptic_play_effect); + if (error) { + dev_err(&pdev->dev, + "unable to create FF device, error: %d\n", + error); + goto err_put_regulator; + } + + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, + "unable to register input device, error: %d\n", + error); + goto err_destroy_ff; + } + + platform_set_drvdata(pdev, chip); + return 0; + +err_destroy_ff: + input_ff_destroy(input_dev); +err_put_regulator: + regulator_put(chip->regulator); +err_free_pwm: + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); +err_free_mem: + input_free_device(input_dev); + kfree(chip); + + return error; +} + +static int max8997_haptic_remove(struct platform_device *pdev) +{ + struct max8997_haptic *chip = platform_get_drvdata(pdev); + + input_unregister_device(chip->input_dev); + regulator_put(chip->regulator); + + if (chip->mode == MAX8997_EXTERNAL_MODE) + pwm_free(chip->pwm); + + kfree(chip); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int max8997_haptic_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct max8997_haptic *chip = platform_get_drvdata(pdev); + + max8997_haptic_disable(chip); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(max8997_haptic_pm_ops, max8997_haptic_suspend, NULL); + +static const struct platform_device_id max8997_haptic_id[] = { + { "max8997-haptic", 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, max8997_haptic_id); + +static struct platform_driver max8997_haptic_driver = { + .driver = { + .name = "max8997-haptic", + .owner = THIS_MODULE, + .pm = &max8997_haptic_pm_ops, + }, + .probe = max8997_haptic_probe, + .remove = max8997_haptic_remove, + .id_table = max8997_haptic_id, +}; +module_platform_driver(max8997_haptic_driver); + +MODULE_ALIAS("platform:max8997-haptic"); +MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); +MODULE_DESCRIPTION("max8997_haptic driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/mc13783-pwrbutton.c b/drivers/input/misc/mc13783-pwrbutton.c new file mode 100644 index 00000000..0906ca59 --- /dev/null +++ b/drivers/input/misc/mc13783-pwrbutton.c @@ -0,0 +1,272 @@ +/** + * Copyright (C) 2011 Philippe Rétornaz + * + * Based on twl4030-pwrbutton driver by: + * Peter De Schrijver <peter.de-schrijver@nokia.com> + * Felipe Balbi <felipe.balbi@nokia.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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 Street, Suite 500, Boston, MA 02110-1335 USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mfd/mc13783.h> +#include <linux/sched.h> +#include <linux/slab.h> + +struct mc13783_pwrb { + struct input_dev *pwr; + struct mc13xxx *mc13783; +#define MC13783_PWRB_B1_POL_INVERT (1 << 0) +#define MC13783_PWRB_B2_POL_INVERT (1 << 1) +#define MC13783_PWRB_B3_POL_INVERT (1 << 2) + int flags; + unsigned short keymap[3]; +}; + +#define MC13783_REG_INTERRUPT_SENSE_1 5 +#define MC13783_IRQSENSE1_ONOFD1S (1 << 3) +#define MC13783_IRQSENSE1_ONOFD2S (1 << 4) +#define MC13783_IRQSENSE1_ONOFD3S (1 << 5) + +#define MC13783_REG_POWER_CONTROL_2 15 +#define MC13783_POWER_CONTROL_2_ON1BDBNC 4 +#define MC13783_POWER_CONTROL_2_ON2BDBNC 6 +#define MC13783_POWER_CONTROL_2_ON3BDBNC 8 +#define MC13783_POWER_CONTROL_2_ON1BRSTEN (1 << 1) +#define MC13783_POWER_CONTROL_2_ON2BRSTEN (1 << 2) +#define MC13783_POWER_CONTROL_2_ON3BRSTEN (1 << 3) + +static irqreturn_t button_irq(int irq, void *_priv) +{ + struct mc13783_pwrb *priv = _priv; + int val; + + mc13xxx_irq_ack(priv->mc13783, irq); + mc13xxx_reg_read(priv->mc13783, MC13783_REG_INTERRUPT_SENSE_1, &val); + + switch (irq) { + case MC13783_IRQ_ONOFD1: + val = val & MC13783_IRQSENSE1_ONOFD1S ? 1 : 0; + if (priv->flags & MC13783_PWRB_B1_POL_INVERT) + val ^= 1; + input_report_key(priv->pwr, priv->keymap[0], val); + break; + + case MC13783_IRQ_ONOFD2: + val = val & MC13783_IRQSENSE1_ONOFD2S ? 1 : 0; + if (priv->flags & MC13783_PWRB_B2_POL_INVERT) + val ^= 1; + input_report_key(priv->pwr, priv->keymap[1], val); + break; + + case MC13783_IRQ_ONOFD3: + val = val & MC13783_IRQSENSE1_ONOFD3S ? 1 : 0; + if (priv->flags & MC13783_PWRB_B3_POL_INVERT) + val ^= 1; + input_report_key(priv->pwr, priv->keymap[2], val); + break; + } + + input_sync(priv->pwr); + + return IRQ_HANDLED; +} + +static int mc13783_pwrbutton_probe(struct platform_device *pdev) +{ + const struct mc13xxx_buttons_platform_data *pdata; + struct mc13xxx *mc13783 = dev_get_drvdata(pdev->dev.parent); + struct input_dev *pwr; + struct mc13783_pwrb *priv; + int err = 0; + int reg = 0; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "missing platform data\n"); + return -ENODEV; + } + + pwr = input_allocate_device(); + if (!pwr) { + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + return -ENOMEM; + } + + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (!priv) { + err = -ENOMEM; + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + goto free_input_dev; + } + + reg |= (pdata->b1on_flags & 0x3) << MC13783_POWER_CONTROL_2_ON1BDBNC; + reg |= (pdata->b2on_flags & 0x3) << MC13783_POWER_CONTROL_2_ON2BDBNC; + reg |= (pdata->b3on_flags & 0x3) << MC13783_POWER_CONTROL_2_ON3BDBNC; + + priv->pwr = pwr; + priv->mc13783 = mc13783; + + mc13xxx_lock(mc13783); + + if (pdata->b1on_flags & MC13783_BUTTON_ENABLE) { + priv->keymap[0] = pdata->b1on_key; + if (pdata->b1on_key != KEY_RESERVED) + __set_bit(pdata->b1on_key, pwr->keybit); + + if (pdata->b1on_flags & MC13783_BUTTON_POL_INVERT) + priv->flags |= MC13783_PWRB_B1_POL_INVERT; + + if (pdata->b1on_flags & MC13783_BUTTON_RESET_EN) + reg |= MC13783_POWER_CONTROL_2_ON1BRSTEN; + + err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD1, + button_irq, "b1on", priv); + if (err) { + dev_dbg(&pdev->dev, "Can't request irq\n"); + goto free_priv; + } + } + + if (pdata->b2on_flags & MC13783_BUTTON_ENABLE) { + priv->keymap[1] = pdata->b2on_key; + if (pdata->b2on_key != KEY_RESERVED) + __set_bit(pdata->b2on_key, pwr->keybit); + + if (pdata->b2on_flags & MC13783_BUTTON_POL_INVERT) + priv->flags |= MC13783_PWRB_B2_POL_INVERT; + + if (pdata->b2on_flags & MC13783_BUTTON_RESET_EN) + reg |= MC13783_POWER_CONTROL_2_ON2BRSTEN; + + err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD2, + button_irq, "b2on", priv); + if (err) { + dev_dbg(&pdev->dev, "Can't request irq\n"); + goto free_irq_b1; + } + } + + if (pdata->b3on_flags & MC13783_BUTTON_ENABLE) { + priv->keymap[2] = pdata->b3on_key; + if (pdata->b3on_key != KEY_RESERVED) + __set_bit(pdata->b3on_key, pwr->keybit); + + if (pdata->b3on_flags & MC13783_BUTTON_POL_INVERT) + priv->flags |= MC13783_PWRB_B3_POL_INVERT; + + if (pdata->b3on_flags & MC13783_BUTTON_RESET_EN) + reg |= MC13783_POWER_CONTROL_2_ON3BRSTEN; + + err = mc13xxx_irq_request(mc13783, MC13783_IRQ_ONOFD3, + button_irq, "b3on", priv); + if (err) { + dev_dbg(&pdev->dev, "Can't request irq: %d\n", err); + goto free_irq_b2; + } + } + + mc13xxx_reg_rmw(mc13783, MC13783_REG_POWER_CONTROL_2, 0x3FE, reg); + + mc13xxx_unlock(mc13783); + + pwr->name = "mc13783_pwrbutton"; + pwr->phys = "mc13783_pwrbutton/input0"; + pwr->dev.parent = &pdev->dev; + + pwr->keycode = priv->keymap; + pwr->keycodemax = ARRAY_SIZE(priv->keymap); + pwr->keycodesize = sizeof(priv->keymap[0]); + __set_bit(EV_KEY, pwr->evbit); + + err = input_register_device(pwr); + if (err) { + dev_dbg(&pdev->dev, "Can't register power button: %d\n", err); + goto free_irq; + } + + platform_set_drvdata(pdev, priv); + + return 0; + +free_irq: + mc13xxx_lock(mc13783); + + if (pdata->b3on_flags & MC13783_BUTTON_ENABLE) + mc13xxx_irq_free(mc13783, MC13783_IRQ_ONOFD3, priv); + +free_irq_b2: + if (pdata->b2on_flags & MC13783_BUTTON_ENABLE) + mc13xxx_irq_free(mc13783, MC13783_IRQ_ONOFD2, priv); + +free_irq_b1: + if (pdata->b1on_flags & MC13783_BUTTON_ENABLE) + mc13xxx_irq_free(mc13783, MC13783_IRQ_ONOFD1, priv); + +free_priv: + mc13xxx_unlock(mc13783); + kfree(priv); + +free_input_dev: + input_free_device(pwr); + + return err; +} + +static int mc13783_pwrbutton_remove(struct platform_device *pdev) +{ + struct mc13783_pwrb *priv = platform_get_drvdata(pdev); + const struct mc13xxx_buttons_platform_data *pdata; + + pdata = dev_get_platdata(&pdev->dev); + + mc13xxx_lock(priv->mc13783); + + if (pdata->b3on_flags & MC13783_BUTTON_ENABLE) + mc13xxx_irq_free(priv->mc13783, MC13783_IRQ_ONOFD3, priv); + if (pdata->b2on_flags & MC13783_BUTTON_ENABLE) + mc13xxx_irq_free(priv->mc13783, MC13783_IRQ_ONOFD2, priv); + if (pdata->b1on_flags & MC13783_BUTTON_ENABLE) + mc13xxx_irq_free(priv->mc13783, MC13783_IRQ_ONOFD1, priv); + + mc13xxx_unlock(priv->mc13783); + + input_unregister_device(priv->pwr); + kfree(priv); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver mc13783_pwrbutton_driver = { + .probe = mc13783_pwrbutton_probe, + .remove = mc13783_pwrbutton_remove, + .driver = { + .name = "mc13783-pwrbutton", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver(mc13783_pwrbutton_driver); + +MODULE_ALIAS("platform:mc13783-pwrbutton"); +MODULE_DESCRIPTION("MC13783 Power Button"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Philippe Retornaz"); diff --git a/drivers/input/misc/mma8450.c b/drivers/input/misc/mma8450.c new file mode 100644 index 00000000..f3309696 --- /dev/null +++ b/drivers/input/misc/mma8450.c @@ -0,0 +1,254 @@ +/* + * Driver for Freescale's 3-Axis Accelerometer MMA8450 + * + * Copyright (C) 2011 Freescale Semiconductor, Inc. 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/input-polldev.h> +#include <linux/of_device.h> + +#define MMA8450_DRV_NAME "mma8450" + +#define MODE_CHANGE_DELAY_MS 100 +#define POLL_INTERVAL 100 +#define POLL_INTERVAL_MAX 500 + +/* register definitions */ +#define MMA8450_STATUS 0x00 +#define MMA8450_STATUS_ZXYDR 0x08 + +#define MMA8450_OUT_X8 0x01 +#define MMA8450_OUT_Y8 0x02 +#define MMA8450_OUT_Z8 0x03 + +#define MMA8450_OUT_X_LSB 0x05 +#define MMA8450_OUT_X_MSB 0x06 +#define MMA8450_OUT_Y_LSB 0x07 +#define MMA8450_OUT_Y_MSB 0x08 +#define MMA8450_OUT_Z_LSB 0x09 +#define MMA8450_OUT_Z_MSB 0x0a + +#define MMA8450_XYZ_DATA_CFG 0x16 + +#define MMA8450_CTRL_REG1 0x38 +#define MMA8450_CTRL_REG2 0x39 + +/* mma8450 status */ +struct mma8450 { + struct i2c_client *client; + struct input_polled_dev *idev; +}; + +static int mma8450_read(struct mma8450 *m, unsigned off) +{ + struct i2c_client *c = m->client; + int ret; + + ret = i2c_smbus_read_byte_data(c, off); + if (ret < 0) + dev_err(&c->dev, + "failed to read register 0x%02x, error %d\n", + off, ret); + + return ret; +} + +static int mma8450_write(struct mma8450 *m, unsigned off, u8 v) +{ + struct i2c_client *c = m->client; + int error; + + error = i2c_smbus_write_byte_data(c, off, v); + if (error < 0) { + dev_err(&c->dev, + "failed to write to register 0x%02x, error %d\n", + off, error); + return error; + } + + return 0; +} + +static int mma8450_read_block(struct mma8450 *m, unsigned off, + u8 *buf, size_t size) +{ + struct i2c_client *c = m->client; + int err; + + err = i2c_smbus_read_i2c_block_data(c, off, size, buf); + if (err < 0) { + dev_err(&c->dev, + "failed to read block data at 0x%02x, error %d\n", + MMA8450_OUT_X_LSB, err); + return err; + } + + return 0; +} + +static void mma8450_poll(struct input_polled_dev *dev) +{ + struct mma8450 *m = dev->private; + int x, y, z; + int ret; + u8 buf[6]; + + ret = mma8450_read(m, MMA8450_STATUS); + if (ret < 0) + return; + + if (!(ret & MMA8450_STATUS_ZXYDR)) + return; + + ret = mma8450_read_block(m, MMA8450_OUT_X_LSB, buf, sizeof(buf)); + if (ret < 0) + return; + + x = ((int)(s8)buf[1] << 4) | (buf[0] & 0xf); + y = ((int)(s8)buf[3] << 4) | (buf[2] & 0xf); + z = ((int)(s8)buf[5] << 4) | (buf[4] & 0xf); + + input_report_abs(dev->input, ABS_X, x); + input_report_abs(dev->input, ABS_Y, y); + input_report_abs(dev->input, ABS_Z, z); + input_sync(dev->input); +} + +/* Initialize the MMA8450 chip */ +static void mma8450_open(struct input_polled_dev *dev) +{ + struct mma8450 *m = dev->private; + int err; + + /* enable all events from X/Y/Z, no FIFO */ + err = mma8450_write(m, MMA8450_XYZ_DATA_CFG, 0x07); + if (err) + return; + + /* + * Sleep mode poll rate - 50Hz + * System output data rate - 400Hz + * Full scale selection - Active, +/- 2G + */ + err = mma8450_write(m, MMA8450_CTRL_REG1, 0x01); + if (err < 0) + return; + + msleep(MODE_CHANGE_DELAY_MS); +} + +static void mma8450_close(struct input_polled_dev *dev) +{ + struct mma8450 *m = dev->private; + + mma8450_write(m, MMA8450_CTRL_REG1, 0x00); + mma8450_write(m, MMA8450_CTRL_REG2, 0x01); +} + +/* + * I2C init/probing/exit functions + */ +static int mma8450_probe(struct i2c_client *c, + const struct i2c_device_id *id) +{ + struct input_polled_dev *idev; + struct mma8450 *m; + int err; + + m = kzalloc(sizeof(struct mma8450), GFP_KERNEL); + idev = input_allocate_polled_device(); + if (!m || !idev) { + err = -ENOMEM; + goto err_free_mem; + } + + m->client = c; + m->idev = idev; + + idev->private = m; + idev->input->name = MMA8450_DRV_NAME; + idev->input->id.bustype = BUS_I2C; + idev->poll = mma8450_poll; + idev->poll_interval = POLL_INTERVAL; + idev->poll_interval_max = POLL_INTERVAL_MAX; + idev->open = mma8450_open; + idev->close = mma8450_close; + + __set_bit(EV_ABS, idev->input->evbit); + input_set_abs_params(idev->input, ABS_X, -2048, 2047, 32, 32); + input_set_abs_params(idev->input, ABS_Y, -2048, 2047, 32, 32); + input_set_abs_params(idev->input, ABS_Z, -2048, 2047, 32, 32); + + err = input_register_polled_device(idev); + if (err) { + dev_err(&c->dev, "failed to register polled input device\n"); + goto err_free_mem; + } + + return 0; + +err_free_mem: + input_free_polled_device(idev); + kfree(m); + return err; +} + +static int mma8450_remove(struct i2c_client *c) +{ + struct mma8450 *m = i2c_get_clientdata(c); + struct input_polled_dev *idev = m->idev; + + input_unregister_polled_device(idev); + input_free_polled_device(idev); + kfree(m); + + return 0; +} + +static const struct i2c_device_id mma8450_id[] = { + { MMA8450_DRV_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, mma8450_id); + +static const struct of_device_id mma8450_dt_ids[] = { + { .compatible = "fsl,mma8450", }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, mma8450_dt_ids); + +static struct i2c_driver mma8450_driver = { + .driver = { + .name = MMA8450_DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = mma8450_dt_ids, + }, + .probe = mma8450_probe, + .remove = mma8450_remove, + .id_table = mma8450_id, +}; + +module_i2c_driver(mma8450_driver); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("MMA8450 3-Axis Accelerometer Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/mpu3050.c b/drivers/input/misc/mpu3050.c new file mode 100644 index 00000000..dce0d959 --- /dev/null +++ b/drivers/input/misc/mpu3050.c @@ -0,0 +1,482 @@ +/* + * MPU3050 Tri-axis gyroscope driver + * + * Copyright (C) 2011 Wistron Co.Ltd + * Joseph Lai <joseph_lai@wistron.com> + * + * Trimmed down by Alan Cox <alan@linux.intel.com> to produce this version + * + * This is a 'lite' version of the driver, while we consider the right way + * to present the other features to user space. In particular it requires the + * device has an IRQ, and it only provides an input interface, so is not much + * use for device orientation. A fuller version is available from the Meego + * tree. + * + * This program is based on bma023.c. + * + * 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; version 2 of the License. + * + * 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/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/input.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> + +#define MPU3050_CHIP_ID 0x69 + +#define MPU3050_AUTO_DELAY 1000 + +#define MPU3050_MIN_VALUE -32768 +#define MPU3050_MAX_VALUE 32767 + +#define MPU3050_DEFAULT_POLL_INTERVAL 200 +#define MPU3050_DEFAULT_FS_RANGE 3 + +/* Register map */ +#define MPU3050_CHIP_ID_REG 0x00 +#define MPU3050_SMPLRT_DIV 0x15 +#define MPU3050_DLPF_FS_SYNC 0x16 +#define MPU3050_INT_CFG 0x17 +#define MPU3050_XOUT_H 0x1D +#define MPU3050_PWR_MGM 0x3E +#define MPU3050_PWR_MGM_POS 6 + +/* Register bits */ + +/* DLPF_FS_SYNC */ +#define MPU3050_EXT_SYNC_NONE 0x00 +#define MPU3050_EXT_SYNC_TEMP 0x20 +#define MPU3050_EXT_SYNC_GYROX 0x40 +#define MPU3050_EXT_SYNC_GYROY 0x60 +#define MPU3050_EXT_SYNC_GYROZ 0x80 +#define MPU3050_EXT_SYNC_ACCELX 0xA0 +#define MPU3050_EXT_SYNC_ACCELY 0xC0 +#define MPU3050_EXT_SYNC_ACCELZ 0xE0 +#define MPU3050_EXT_SYNC_MASK 0xE0 +#define MPU3050_FS_250DPS 0x00 +#define MPU3050_FS_500DPS 0x08 +#define MPU3050_FS_1000DPS 0x10 +#define MPU3050_FS_2000DPS 0x18 +#define MPU3050_FS_MASK 0x18 +#define MPU3050_DLPF_CFG_256HZ_NOLPF2 0x00 +#define MPU3050_DLPF_CFG_188HZ 0x01 +#define MPU3050_DLPF_CFG_98HZ 0x02 +#define MPU3050_DLPF_CFG_42HZ 0x03 +#define MPU3050_DLPF_CFG_20HZ 0x04 +#define MPU3050_DLPF_CFG_10HZ 0x05 +#define MPU3050_DLPF_CFG_5HZ 0x06 +#define MPU3050_DLPF_CFG_2100HZ_NOLPF 0x07 +#define MPU3050_DLPF_CFG_MASK 0x07 +/* INT_CFG */ +#define MPU3050_RAW_RDY_EN 0x01 +#define MPU3050_MPU_RDY_EN 0x02 +#define MPU3050_LATCH_INT_EN 0x04 +/* PWR_MGM */ +#define MPU3050_PWR_MGM_PLL_X 0x01 +#define MPU3050_PWR_MGM_PLL_Y 0x02 +#define MPU3050_PWR_MGM_PLL_Z 0x03 +#define MPU3050_PWR_MGM_CLKSEL 0x07 +#define MPU3050_PWR_MGM_STBY_ZG 0x08 +#define MPU3050_PWR_MGM_STBY_YG 0x10 +#define MPU3050_PWR_MGM_STBY_XG 0x20 +#define MPU3050_PWR_MGM_SLEEP 0x40 +#define MPU3050_PWR_MGM_RESET 0x80 +#define MPU3050_PWR_MGM_MASK 0x40 + +struct axis_data { + s16 x; + s16 y; + s16 z; +}; + +struct mpu3050_sensor { + struct i2c_client *client; + struct device *dev; + struct input_dev *idev; +}; + +/** + * mpu3050_xyz_read_reg - read the axes values + * @buffer: provide register addr and get register + * @length: length of register + * + * Reads the register values in one transaction or returns a negative + * error code on failure. + */ +static int mpu3050_xyz_read_reg(struct i2c_client *client, + u8 *buffer, int length) +{ + /* + * Annoying we can't make this const because the i2c layer doesn't + * declare input buffers const. + */ + char cmd = MPU3050_XOUT_H; + struct i2c_msg msg[] = { + { + .addr = client->addr, + .flags = 0, + .len = 1, + .buf = &cmd, + }, + { + .addr = client->addr, + .flags = I2C_M_RD, + .len = length, + .buf = buffer, + }, + }; + + return i2c_transfer(client->adapter, msg, 2); +} + +/** + * mpu3050_read_xyz - get co-ordinates from device + * @client: i2c address of sensor + * @coords: co-ordinates to update + * + * Return the converted X Y and Z co-ordinates from the sensor device + */ +static void mpu3050_read_xyz(struct i2c_client *client, + struct axis_data *coords) +{ + u16 buffer[3]; + + mpu3050_xyz_read_reg(client, (u8 *)buffer, 6); + coords->x = be16_to_cpu(buffer[0]); + coords->y = be16_to_cpu(buffer[1]); + coords->z = be16_to_cpu(buffer[2]); + dev_dbg(&client->dev, "%s: x %d, y %d, z %d\n", __func__, + coords->x, coords->y, coords->z); +} + +/** + * mpu3050_set_power_mode - set the power mode + * @client: i2c client for the sensor + * @val: value to switch on/off of power, 1: normal power, 0: low power + * + * Put device to normal-power mode or low-power mode. + */ +static void mpu3050_set_power_mode(struct i2c_client *client, u8 val) +{ + u8 value; + + value = i2c_smbus_read_byte_data(client, MPU3050_PWR_MGM); + value = (value & ~MPU3050_PWR_MGM_MASK) | + (((val << MPU3050_PWR_MGM_POS) & MPU3050_PWR_MGM_MASK) ^ + MPU3050_PWR_MGM_MASK); + i2c_smbus_write_byte_data(client, MPU3050_PWR_MGM, value); +} + +/** + * mpu3050_input_open - called on input event open + * @input: input dev of opened device + * + * The input layer calls this function when input event is opened. The + * function will push the device to resume. Then, the device is ready + * to provide data. + */ +static int mpu3050_input_open(struct input_dev *input) +{ + struct mpu3050_sensor *sensor = input_get_drvdata(input); + int error; + + pm_runtime_get(sensor->dev); + + /* Enable interrupts */ + error = i2c_smbus_write_byte_data(sensor->client, MPU3050_INT_CFG, + MPU3050_LATCH_INT_EN | + MPU3050_RAW_RDY_EN | + MPU3050_MPU_RDY_EN); + if (error < 0) { + pm_runtime_put(sensor->dev); + return error; + } + + return 0; +} + +/** + * mpu3050_input_close - called on input event close + * @input: input dev of closed device + * + * The input layer calls this function when input event is closed. The + * function will push the device to suspend. + */ +static void mpu3050_input_close(struct input_dev *input) +{ + struct mpu3050_sensor *sensor = input_get_drvdata(input); + + pm_runtime_put(sensor->dev); +} + +/** + * mpu3050_interrupt_thread - handle an IRQ + * @irq: interrupt numner + * @data: the sensor + * + * Called by the kernel single threaded after an interrupt occurs. Read + * the sensor data and generate an input event for it. + */ +static irqreturn_t mpu3050_interrupt_thread(int irq, void *data) +{ + struct mpu3050_sensor *sensor = data; + struct axis_data axis; + + mpu3050_read_xyz(sensor->client, &axis); + + input_report_abs(sensor->idev, ABS_X, axis.x); + input_report_abs(sensor->idev, ABS_Y, axis.y); + input_report_abs(sensor->idev, ABS_Z, axis.z); + input_sync(sensor->idev); + + return IRQ_HANDLED; +} + +/** + * mpu3050_hw_init - initialize hardware + * @sensor: the sensor + * + * Called during device probe; configures the sampling method. + */ +static int mpu3050_hw_init(struct mpu3050_sensor *sensor) +{ + struct i2c_client *client = sensor->client; + int ret; + u8 reg; + + /* Reset */ + ret = i2c_smbus_write_byte_data(client, MPU3050_PWR_MGM, + MPU3050_PWR_MGM_RESET); + if (ret < 0) + return ret; + + ret = i2c_smbus_read_byte_data(client, MPU3050_PWR_MGM); + if (ret < 0) + return ret; + + ret &= ~MPU3050_PWR_MGM_CLKSEL; + ret |= MPU3050_PWR_MGM_PLL_Z; + ret = i2c_smbus_write_byte_data(client, MPU3050_PWR_MGM, ret); + if (ret < 0) + return ret; + + /* Output frequency divider. The poll interval */ + ret = i2c_smbus_write_byte_data(client, MPU3050_SMPLRT_DIV, + MPU3050_DEFAULT_POLL_INTERVAL - 1); + if (ret < 0) + return ret; + + /* Set low pass filter and full scale */ + reg = MPU3050_DEFAULT_FS_RANGE; + reg |= MPU3050_DLPF_CFG_42HZ << 3; + reg |= MPU3050_EXT_SYNC_NONE << 5; + ret = i2c_smbus_write_byte_data(client, MPU3050_DLPF_FS_SYNC, reg); + if (ret < 0) + return ret; + + return 0; +} + +/** + * mpu3050_probe - device detection callback + * @client: i2c client of found device + * @id: id match information + * + * The I2C layer calls us when it believes a sensor is present at this + * address. Probe to see if this is correct and to validate the device. + * + * If present install the relevant sysfs interfaces and input device. + */ +static int mpu3050_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct mpu3050_sensor *sensor; + struct input_dev *idev; + int ret; + int error; + + sensor = kzalloc(sizeof(struct mpu3050_sensor), GFP_KERNEL); + idev = input_allocate_device(); + if (!sensor || !idev) { + dev_err(&client->dev, "failed to allocate driver data\n"); + error = -ENOMEM; + goto err_free_mem; + } + + sensor->client = client; + sensor->dev = &client->dev; + sensor->idev = idev; + + mpu3050_set_power_mode(client, 1); + msleep(10); + + ret = i2c_smbus_read_byte_data(client, MPU3050_CHIP_ID_REG); + if (ret < 0) { + dev_err(&client->dev, "failed to detect device\n"); + error = -ENXIO; + goto err_free_mem; + } + + if (ret != MPU3050_CHIP_ID) { + dev_err(&client->dev, "unsupported chip id\n"); + error = -ENXIO; + goto err_free_mem; + } + + idev->name = "MPU3050"; + idev->id.bustype = BUS_I2C; + idev->dev.parent = &client->dev; + + idev->open = mpu3050_input_open; + idev->close = mpu3050_input_close; + + __set_bit(EV_ABS, idev->evbit); + input_set_abs_params(idev, ABS_X, + MPU3050_MIN_VALUE, MPU3050_MAX_VALUE, 0, 0); + input_set_abs_params(idev, ABS_Y, + MPU3050_MIN_VALUE, MPU3050_MAX_VALUE, 0, 0); + input_set_abs_params(idev, ABS_Z, + MPU3050_MIN_VALUE, MPU3050_MAX_VALUE, 0, 0); + + input_set_drvdata(idev, sensor); + + pm_runtime_set_active(&client->dev); + + error = mpu3050_hw_init(sensor); + if (error) + goto err_pm_set_suspended; + + error = request_threaded_irq(client->irq, + NULL, mpu3050_interrupt_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "mpu3050", sensor); + if (error) { + dev_err(&client->dev, + "can't get IRQ %d, error %d\n", client->irq, error); + goto err_pm_set_suspended; + } + + error = input_register_device(idev); + if (error) { + dev_err(&client->dev, "failed to register input device\n"); + goto err_free_irq; + } + + pm_runtime_enable(&client->dev); + pm_runtime_set_autosuspend_delay(&client->dev, MPU3050_AUTO_DELAY); + + return 0; + +err_free_irq: + free_irq(client->irq, sensor); +err_pm_set_suspended: + pm_runtime_set_suspended(&client->dev); +err_free_mem: + input_free_device(idev); + kfree(sensor); + return error; +} + +/** + * mpu3050_remove - remove a sensor + * @client: i2c client of sensor being removed + * + * Our sensor is going away, clean up the resources. + */ +static int mpu3050_remove(struct i2c_client *client) +{ + struct mpu3050_sensor *sensor = i2c_get_clientdata(client); + + pm_runtime_disable(&client->dev); + pm_runtime_set_suspended(&client->dev); + + free_irq(client->irq, sensor); + input_unregister_device(sensor->idev); + kfree(sensor); + + return 0; +} + +#ifdef CONFIG_PM +/** + * mpu3050_suspend - called on device suspend + * @dev: device being suspended + * + * Put the device into sleep mode before we suspend the machine. + */ +static int mpu3050_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + mpu3050_set_power_mode(client, 0); + + return 0; +} + +/** + * mpu3050_resume - called on device resume + * @dev: device being resumed + * + * Put the device into powered mode on resume. + */ +static int mpu3050_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + mpu3050_set_power_mode(client, 1); + msleep(100); /* wait for gyro chip resume */ + + return 0; +} +#endif + +static UNIVERSAL_DEV_PM_OPS(mpu3050_pm, mpu3050_suspend, mpu3050_resume, NULL); + +static const struct i2c_device_id mpu3050_ids[] = { + { "mpu3050", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mpu3050_ids); + +static const struct of_device_id mpu3050_of_match[] = { + { .compatible = "invn,mpu3050", }, + { }, +}; +MODULE_DEVICE_TABLE(of, mpu3050_of_match); + +static struct i2c_driver mpu3050_i2c_driver = { + .driver = { + .name = "mpu3050", + .owner = THIS_MODULE, + .pm = &mpu3050_pm, + .of_match_table = mpu3050_of_match, + }, + .probe = mpu3050_probe, + .remove = mpu3050_remove, + .id_table = mpu3050_ids, +}; + +module_i2c_driver(mpu3050_i2c_driver); + +MODULE_AUTHOR("Wistron Corp."); +MODULE_DESCRIPTION("MPU3050 Tri-axis gyroscope driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/pcap_keys.c b/drivers/input/misc/pcap_keys.c new file mode 100644 index 00000000..40ac9a5a --- /dev/null +++ b/drivers/input/misc/pcap_keys.c @@ -0,0 +1,133 @@ +/* + * Input driver for PCAP events: + * * Power key + * * Headphone button + * + * Copyright (c) 2008,2009 Ilya Petrov <ilya.muromec@gmail.com> + * + * 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. + * + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/mfd/ezx-pcap.h> +#include <linux/slab.h> + +struct pcap_keys { + struct pcap_chip *pcap; + struct input_dev *input; +}; + +/* PCAP2 interrupts us on keypress */ +static irqreturn_t pcap_keys_handler(int irq, void *_pcap_keys) +{ + struct pcap_keys *pcap_keys = _pcap_keys; + int pirq = irq_to_pcap(pcap_keys->pcap, irq); + u32 pstat; + + ezx_pcap_read(pcap_keys->pcap, PCAP_REG_PSTAT, &pstat); + pstat &= 1 << pirq; + + switch (pirq) { + case PCAP_IRQ_ONOFF: + input_report_key(pcap_keys->input, KEY_POWER, !pstat); + break; + case PCAP_IRQ_MIC: + input_report_key(pcap_keys->input, KEY_HP, !pstat); + break; + } + + input_sync(pcap_keys->input); + + return IRQ_HANDLED; +} + +static int pcap_keys_probe(struct platform_device *pdev) +{ + int err = -ENOMEM; + struct pcap_keys *pcap_keys; + struct input_dev *input_dev; + + pcap_keys = kmalloc(sizeof(struct pcap_keys), GFP_KERNEL); + if (!pcap_keys) + return err; + + pcap_keys->pcap = dev_get_drvdata(pdev->dev.parent); + + input_dev = input_allocate_device(); + if (!input_dev) + goto fail; + + pcap_keys->input = input_dev; + + platform_set_drvdata(pdev, pcap_keys); + input_dev->name = pdev->name; + input_dev->phys = "pcap-keys/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + __set_bit(EV_KEY, input_dev->evbit); + __set_bit(KEY_POWER, input_dev->keybit); + __set_bit(KEY_HP, input_dev->keybit); + + err = input_register_device(input_dev); + if (err) + goto fail_allocate; + + err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), + pcap_keys_handler, 0, "Power key", pcap_keys); + if (err) + goto fail_register; + + err = request_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC), + pcap_keys_handler, 0, "Headphone button", pcap_keys); + if (err) + goto fail_pwrkey; + + return 0; + +fail_pwrkey: + free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys); +fail_register: + input_unregister_device(input_dev); + goto fail; +fail_allocate: + input_free_device(input_dev); +fail: + kfree(pcap_keys); + return err; +} + +static int pcap_keys_remove(struct platform_device *pdev) +{ + struct pcap_keys *pcap_keys = platform_get_drvdata(pdev); + + free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_ONOFF), pcap_keys); + free_irq(pcap_to_irq(pcap_keys->pcap, PCAP_IRQ_MIC), pcap_keys); + + input_unregister_device(pcap_keys->input); + kfree(pcap_keys); + + return 0; +} + +static struct platform_driver pcap_keys_device_driver = { + .probe = pcap_keys_probe, + .remove = pcap_keys_remove, + .driver = { + .name = "pcap-keys", + .owner = THIS_MODULE, + } +}; +module_platform_driver(pcap_keys_device_driver); + +MODULE_DESCRIPTION("Motorola PCAP2 input events driver"); +MODULE_AUTHOR("Ilya Petrov <ilya.muromec@gmail.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcap_keys"); diff --git a/drivers/input/misc/pcf50633-input.c b/drivers/input/misc/pcf50633-input.c new file mode 100644 index 00000000..73b13eba --- /dev/null +++ b/drivers/input/misc/pcf50633-input.c @@ -0,0 +1,121 @@ +/* NXP PCF50633 Input Driver + * + * (C) 2006-2008 by Openmoko, Inc. + * Author: Balaji Rao <balajirrao@openmoko.org> + * All rights reserved. + * + * Broken down from monstrous PCF50633 driver mainly by + * Harald Welte, Andy Green and Werner Almesberger + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/slab.h> + +#include <linux/mfd/pcf50633/core.h> + +#define PCF50633_OOCSTAT_ONKEY 0x01 +#define PCF50633_REG_OOCSTAT 0x12 +#define PCF50633_REG_OOCMODE 0x10 + +struct pcf50633_input { + struct pcf50633 *pcf; + struct input_dev *input_dev; +}; + +static void +pcf50633_input_irq(int irq, void *data) +{ + struct pcf50633_input *input; + int onkey_released; + + input = data; + + /* We report only one event depending on the key press status */ + onkey_released = pcf50633_reg_read(input->pcf, PCF50633_REG_OOCSTAT) + & PCF50633_OOCSTAT_ONKEY; + + if (irq == PCF50633_IRQ_ONKEYF && !onkey_released) + input_report_key(input->input_dev, KEY_POWER, 1); + else if (irq == PCF50633_IRQ_ONKEYR && onkey_released) + input_report_key(input->input_dev, KEY_POWER, 0); + + input_sync(input->input_dev); +} + +static int pcf50633_input_probe(struct platform_device *pdev) +{ + struct pcf50633_input *input; + struct input_dev *input_dev; + int ret; + + + input = kzalloc(sizeof(*input), GFP_KERNEL); + if (!input) + return -ENOMEM; + + input_dev = input_allocate_device(); + if (!input_dev) { + kfree(input); + return -ENOMEM; + } + + platform_set_drvdata(pdev, input); + input->pcf = dev_to_pcf50633(pdev->dev.parent); + input->input_dev = input_dev; + + input_dev->name = "PCF50633 PMU events"; + input_dev->id.bustype = BUS_I2C; + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_PWR); + set_bit(KEY_POWER, input_dev->keybit); + + ret = input_register_device(input_dev); + if (ret) { + input_free_device(input_dev); + kfree(input); + return ret; + } + pcf50633_register_irq(input->pcf, PCF50633_IRQ_ONKEYR, + pcf50633_input_irq, input); + pcf50633_register_irq(input->pcf, PCF50633_IRQ_ONKEYF, + pcf50633_input_irq, input); + + return 0; +} + +static int pcf50633_input_remove(struct platform_device *pdev) +{ + struct pcf50633_input *input = platform_get_drvdata(pdev); + + pcf50633_free_irq(input->pcf, PCF50633_IRQ_ONKEYR); + pcf50633_free_irq(input->pcf, PCF50633_IRQ_ONKEYF); + + input_unregister_device(input->input_dev); + kfree(input); + + return 0; +} + +static struct platform_driver pcf50633_input_driver = { + .driver = { + .name = "pcf50633-input", + }, + .probe = pcf50633_input_probe, + .remove = pcf50633_input_remove, +}; +module_platform_driver(pcf50633_input_driver); + +MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>"); +MODULE_DESCRIPTION("PCF50633 input driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcf50633-input"); diff --git a/drivers/input/misc/pcf8574_keypad.c b/drivers/input/misc/pcf8574_keypad.c new file mode 100644 index 00000000..e3739297 --- /dev/null +++ b/drivers/input/misc/pcf8574_keypad.c @@ -0,0 +1,223 @@ +/* + * Driver for a keypad w/16 buttons connected to a PCF8574 I2C I/O expander + * + * Copyright 2005-2008 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#define DRV_NAME "pcf8574_keypad" + +static const unsigned char pcf8574_kp_btncode[] = { + [0] = KEY_RESERVED, + [1] = KEY_ENTER, + [2] = KEY_BACKSLASH, + [3] = KEY_0, + [4] = KEY_RIGHTBRACE, + [5] = KEY_C, + [6] = KEY_9, + [7] = KEY_8, + [8] = KEY_7, + [9] = KEY_B, + [10] = KEY_6, + [11] = KEY_5, + [12] = KEY_4, + [13] = KEY_A, + [14] = KEY_3, + [15] = KEY_2, + [16] = KEY_1 +}; + +struct kp_data { + unsigned short btncode[ARRAY_SIZE(pcf8574_kp_btncode)]; + struct input_dev *idev; + struct i2c_client *client; + char name[64]; + char phys[32]; + unsigned char laststate; +}; + +static short read_state(struct kp_data *lp) +{ + unsigned char x, y, a, b; + + i2c_smbus_write_byte(lp->client, 240); + x = 0xF & (~(i2c_smbus_read_byte(lp->client) >> 4)); + + i2c_smbus_write_byte(lp->client, 15); + y = 0xF & (~i2c_smbus_read_byte(lp->client)); + + for (a = 0; x > 0; a++) + x = x >> 1; + for (b = 0; y > 0; b++) + y = y >> 1; + + return ((a - 1) * 4) + b; +} + +static irqreturn_t pcf8574_kp_irq_handler(int irq, void *dev_id) +{ + struct kp_data *lp = dev_id; + unsigned char nextstate = read_state(lp); + + if (lp->laststate != nextstate) { + int key_down = nextstate < ARRAY_SIZE(lp->btncode); + unsigned short keycode = key_down ? + lp->btncode[nextstate] : lp->btncode[lp->laststate]; + + input_report_key(lp->idev, keycode, key_down); + input_sync(lp->idev); + + lp->laststate = nextstate; + } + + return IRQ_HANDLED; +} + +static int pcf8574_kp_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + int i, ret; + struct input_dev *idev; + struct kp_data *lp; + + if (i2c_smbus_write_byte(client, 240) < 0) { + dev_err(&client->dev, "probe: write fail\n"); + return -ENODEV; + } + + lp = kzalloc(sizeof(*lp), GFP_KERNEL); + if (!lp) + return -ENOMEM; + + idev = input_allocate_device(); + if (!idev) { + dev_err(&client->dev, "Can't allocate input device\n"); + ret = -ENOMEM; + goto fail_allocate; + } + + lp->idev = idev; + lp->client = client; + + idev->evbit[0] = BIT_MASK(EV_KEY); + idev->keycode = lp->btncode; + idev->keycodesize = sizeof(lp->btncode[0]); + idev->keycodemax = ARRAY_SIZE(lp->btncode); + + for (i = 0; i < ARRAY_SIZE(pcf8574_kp_btncode); i++) { + lp->btncode[i] = pcf8574_kp_btncode[i]; + __set_bit(lp->btncode[i] & KEY_MAX, idev->keybit); + } + + sprintf(lp->name, DRV_NAME); + sprintf(lp->phys, "kp_data/input0"); + + idev->name = lp->name; + idev->phys = lp->phys; + idev->id.bustype = BUS_I2C; + idev->id.vendor = 0x0001; + idev->id.product = 0x0001; + idev->id.version = 0x0100; + + lp->laststate = read_state(lp); + + ret = request_threaded_irq(client->irq, NULL, pcf8574_kp_irq_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + DRV_NAME, lp); + if (ret) { + dev_err(&client->dev, "IRQ %d is not free\n", client->irq); + goto fail_free_device; + } + + ret = input_register_device(idev); + if (ret) { + dev_err(&client->dev, "input_register_device() failed\n"); + goto fail_free_irq; + } + + i2c_set_clientdata(client, lp); + return 0; + + fail_free_irq: + free_irq(client->irq, lp); + fail_free_device: + input_free_device(idev); + fail_allocate: + kfree(lp); + + return ret; +} + +static int pcf8574_kp_remove(struct i2c_client *client) +{ + struct kp_data *lp = i2c_get_clientdata(client); + + free_irq(client->irq, lp); + + input_unregister_device(lp->idev); + kfree(lp); + + return 0; +} + +#ifdef CONFIG_PM +static int pcf8574_kp_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + enable_irq(client->irq); + + return 0; +} + +static int pcf8574_kp_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + + disable_irq(client->irq); + + return 0; +} + +static const struct dev_pm_ops pcf8574_kp_pm_ops = { + .suspend = pcf8574_kp_suspend, + .resume = pcf8574_kp_resume, +}; + +#else +# define pcf8574_kp_resume NULL +# define pcf8574_kp_suspend NULL +#endif + +static const struct i2c_device_id pcf8574_kp_id[] = { + { DRV_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pcf8574_kp_id); + +static struct i2c_driver pcf8574_kp_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &pcf8574_kp_pm_ops, +#endif + }, + .probe = pcf8574_kp_probe, + .remove = pcf8574_kp_remove, + .id_table = pcf8574_kp_id, +}; + +module_i2c_driver(pcf8574_kp_driver); + +MODULE_AUTHOR("Michael Hennerich"); +MODULE_DESCRIPTION("Keypad input driver for 16 keys connected to PCF8574"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/pcspkr.c b/drivers/input/misc/pcspkr.c new file mode 100644 index 00000000..199db78a --- /dev/null +++ b/drivers/input/misc/pcspkr.c @@ -0,0 +1,138 @@ +/* + * PC Speaker beeper driver for Linux + * + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 1992 Orest Zborowski + * + */ + +/* + * 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 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/i8253.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/platform_device.h> +#include <linux/timex.h> +#include <asm/io.h> + +MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>"); +MODULE_DESCRIPTION("PC Speaker beeper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pcspkr"); + +static int pcspkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + unsigned int count = 0; + unsigned long flags; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = PIT_TICK_RATE / value; + + raw_spin_lock_irqsave(&i8253_lock, flags); + + if (count) { + /* set command for counter 2, 2 byte write */ + outb_p(0xB6, 0x43); + /* select desired HZ */ + outb_p(count & 0xff, 0x42); + outb((count >> 8) & 0xff, 0x42); + /* enable counter 2 */ + outb_p(inb_p(0x61) | 3, 0x61); + } else { + /* disable counter 2 */ + outb(inb_p(0x61) & 0xFC, 0x61); + } + + raw_spin_unlock_irqrestore(&i8253_lock, flags); + + return 0; +} + +static int pcspkr_probe(struct platform_device *dev) +{ + struct input_dev *pcspkr_dev; + int err; + + pcspkr_dev = input_allocate_device(); + if (!pcspkr_dev) + return -ENOMEM; + + pcspkr_dev->name = "PC Speaker"; + pcspkr_dev->phys = "isa0061/input0"; + pcspkr_dev->id.bustype = BUS_ISA; + pcspkr_dev->id.vendor = 0x001f; + pcspkr_dev->id.product = 0x0001; + pcspkr_dev->id.version = 0x0100; + pcspkr_dev->dev.parent = &dev->dev; + + pcspkr_dev->evbit[0] = BIT_MASK(EV_SND); + pcspkr_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + pcspkr_dev->event = pcspkr_event; + + err = input_register_device(pcspkr_dev); + if (err) { + input_free_device(pcspkr_dev); + return err; + } + + platform_set_drvdata(dev, pcspkr_dev); + + return 0; +} + +static int pcspkr_remove(struct platform_device *dev) +{ + struct input_dev *pcspkr_dev = platform_get_drvdata(dev); + + input_unregister_device(pcspkr_dev); + platform_set_drvdata(dev, NULL); + /* turn off the speaker */ + pcspkr_event(NULL, EV_SND, SND_BELL, 0); + + return 0; +} + +static int pcspkr_suspend(struct device *dev) +{ + pcspkr_event(NULL, EV_SND, SND_BELL, 0); + + return 0; +} + +static void pcspkr_shutdown(struct platform_device *dev) +{ + /* turn off the speaker */ + pcspkr_event(NULL, EV_SND, SND_BELL, 0); +} + +static const struct dev_pm_ops pcspkr_pm_ops = { + .suspend = pcspkr_suspend, +}; + +static struct platform_driver pcspkr_platform_driver = { + .driver = { + .name = "pcspkr", + .owner = THIS_MODULE, + .pm = &pcspkr_pm_ops, + }, + .probe = pcspkr_probe, + .remove = pcspkr_remove, + .shutdown = pcspkr_shutdown, +}; +module_platform_driver(pcspkr_platform_driver); + diff --git a/drivers/input/misc/pm8xxx-vibrator.c b/drivers/input/misc/pm8xxx-vibrator.c new file mode 100644 index 00000000..a9da65e4 --- /dev/null +++ b/drivers/input/misc/pm8xxx-vibrator.c @@ -0,0 +1,285 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 version 2 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/platform_device.h> +#include <linux/input.h> +#include <linux/slab.h> +#include <linux/mfd/pm8xxx/core.h> + +#define VIB_DRV 0x4A + +#define VIB_DRV_SEL_MASK 0xf8 +#define VIB_DRV_SEL_SHIFT 0x03 +#define VIB_DRV_EN_MANUAL_MASK 0xfc + +#define VIB_MAX_LEVEL_mV (3100) +#define VIB_MIN_LEVEL_mV (1200) +#define VIB_MAX_LEVELS (VIB_MAX_LEVEL_mV - VIB_MIN_LEVEL_mV) + +#define MAX_FF_SPEED 0xff + +/** + * struct pm8xxx_vib - structure to hold vibrator data + * @vib_input_dev: input device supporting force feedback + * @work: work structure to set the vibration parameters + * @dev: device supporting force feedback + * @speed: speed of vibration set from userland + * @active: state of vibrator + * @level: level of vibration to set in the chip + * @reg_vib_drv: VIB_DRV register value + */ +struct pm8xxx_vib { + struct input_dev *vib_input_dev; + struct work_struct work; + struct device *dev; + int speed; + int level; + bool active; + u8 reg_vib_drv; +}; + +/** + * pm8xxx_vib_read_u8 - helper to read a byte from pmic chip + * @vib: pointer to vibrator structure + * @data: placeholder for data to be read + * @reg: register address + */ +static int pm8xxx_vib_read_u8(struct pm8xxx_vib *vib, + u8 *data, u16 reg) +{ + int rc; + + rc = pm8xxx_readb(vib->dev->parent, reg, data); + if (rc < 0) + dev_warn(vib->dev, "Error reading pm8xxx reg 0x%x(0x%x)\n", + reg, rc); + return rc; +} + +/** + * pm8xxx_vib_write_u8 - helper to write a byte to pmic chip + * @vib: pointer to vibrator structure + * @data: data to write + * @reg: register address + */ +static int pm8xxx_vib_write_u8(struct pm8xxx_vib *vib, + u8 data, u16 reg) +{ + int rc; + + rc = pm8xxx_writeb(vib->dev->parent, reg, data); + if (rc < 0) + dev_warn(vib->dev, "Error writing pm8xxx reg 0x%x(0x%x)\n", + reg, rc); + return rc; +} + +/** + * pm8xxx_vib_set - handler to start/stop vibration + * @vib: pointer to vibrator structure + * @on: state to set + */ +static int pm8xxx_vib_set(struct pm8xxx_vib *vib, bool on) +{ + int rc; + u8 val = vib->reg_vib_drv; + + if (on) + val |= ((vib->level << VIB_DRV_SEL_SHIFT) & VIB_DRV_SEL_MASK); + else + val &= ~VIB_DRV_SEL_MASK; + + rc = pm8xxx_vib_write_u8(vib, val, VIB_DRV); + if (rc < 0) + return rc; + + vib->reg_vib_drv = val; + return 0; +} + +/** + * pm8xxx_work_handler - worker to set vibration level + * @work: pointer to work_struct + */ +static void pm8xxx_work_handler(struct work_struct *work) +{ + struct pm8xxx_vib *vib = container_of(work, struct pm8xxx_vib, work); + int rc; + u8 val; + + rc = pm8xxx_vib_read_u8(vib, &val, VIB_DRV); + if (rc < 0) + return; + + /* + * pmic vibrator supports voltage ranges from 1.2 to 3.1V, so + * scale the level to fit into these ranges. + */ + if (vib->speed) { + vib->active = true; + vib->level = ((VIB_MAX_LEVELS * vib->speed) / MAX_FF_SPEED) + + VIB_MIN_LEVEL_mV; + vib->level /= 100; + } else { + vib->active = false; + vib->level = VIB_MIN_LEVEL_mV / 100; + } + + pm8xxx_vib_set(vib, vib->active); +} + +/** + * pm8xxx_vib_close - callback of input close callback + * @dev: input device pointer + * + * Turns off the vibrator. + */ +static void pm8xxx_vib_close(struct input_dev *dev) +{ + struct pm8xxx_vib *vib = input_get_drvdata(dev); + + cancel_work_sync(&vib->work); + if (vib->active) + pm8xxx_vib_set(vib, false); +} + +/** + * pm8xxx_vib_play_effect - function to handle vib effects. + * @dev: input device pointer + * @data: data of effect + * @effect: effect to play + * + * Currently this driver supports only rumble effects. + */ +static int pm8xxx_vib_play_effect(struct input_dev *dev, void *data, + struct ff_effect *effect) +{ + struct pm8xxx_vib *vib = input_get_drvdata(dev); + + vib->speed = effect->u.rumble.strong_magnitude >> 8; + if (!vib->speed) + vib->speed = effect->u.rumble.weak_magnitude >> 9; + + schedule_work(&vib->work); + + return 0; +} + +static int pm8xxx_vib_probe(struct platform_device *pdev) + +{ + struct pm8xxx_vib *vib; + struct input_dev *input_dev; + int error; + u8 val; + + vib = kzalloc(sizeof(*vib), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!vib || !input_dev) { + dev_err(&pdev->dev, "couldn't allocate memory\n"); + error = -ENOMEM; + goto err_free_mem; + } + + INIT_WORK(&vib->work, pm8xxx_work_handler); + vib->dev = &pdev->dev; + vib->vib_input_dev = input_dev; + + /* operate in manual mode */ + error = pm8xxx_vib_read_u8(vib, &val, VIB_DRV); + if (error < 0) + goto err_free_mem; + val &= ~VIB_DRV_EN_MANUAL_MASK; + error = pm8xxx_vib_write_u8(vib, val, VIB_DRV); + if (error < 0) + goto err_free_mem; + + vib->reg_vib_drv = val; + + input_dev->name = "pm8xxx_vib_ffmemless"; + input_dev->id.version = 1; + input_dev->dev.parent = &pdev->dev; + input_dev->close = pm8xxx_vib_close; + input_set_drvdata(input_dev, vib); + input_set_capability(vib->vib_input_dev, EV_FF, FF_RUMBLE); + + error = input_ff_create_memless(input_dev, NULL, + pm8xxx_vib_play_effect); + if (error) { + dev_err(&pdev->dev, + "couldn't register vibrator as FF device\n"); + goto err_free_mem; + } + + error = input_register_device(input_dev); + if (error) { + dev_err(&pdev->dev, "couldn't register input device\n"); + goto err_destroy_memless; + } + + platform_set_drvdata(pdev, vib); + return 0; + +err_destroy_memless: + input_ff_destroy(input_dev); +err_free_mem: + input_free_device(input_dev); + kfree(vib); + + return error; +} + +static int pm8xxx_vib_remove(struct platform_device *pdev) +{ + struct pm8xxx_vib *vib = platform_get_drvdata(pdev); + + input_unregister_device(vib->vib_input_dev); + kfree(vib); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int pm8xxx_vib_suspend(struct device *dev) +{ + struct pm8xxx_vib *vib = dev_get_drvdata(dev); + + /* Turn off the vibrator */ + pm8xxx_vib_set(vib, false); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pm8xxx_vib_pm_ops, pm8xxx_vib_suspend, NULL); + +static struct platform_driver pm8xxx_vib_driver = { + .probe = pm8xxx_vib_probe, + .remove = pm8xxx_vib_remove, + .driver = { + .name = "pm8xxx-vib", + .owner = THIS_MODULE, + .pm = &pm8xxx_vib_pm_ops, + }, +}; +module_platform_driver(pm8xxx_vib_driver); + +MODULE_ALIAS("platform:pm8xxx_vib"); +MODULE_DESCRIPTION("PMIC8xxx vibrator driver based on ff-memless framework"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Amy Maloche <amaloche@codeaurora.org>"); diff --git a/drivers/input/misc/pmic8xxx-pwrkey.c b/drivers/input/misc/pmic8xxx-pwrkey.c new file mode 100644 index 00000000..4b811be7 --- /dev/null +++ b/drivers/input/misc/pmic8xxx-pwrkey.c @@ -0,0 +1,221 @@ +/* Copyright (c) 2010-2011, Code Aurora Forum. 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 version 2 and + * only 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. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/log2.h> + +#include <linux/mfd/pm8xxx/core.h> +#include <linux/input/pmic8xxx-pwrkey.h> + +#define PON_CNTL_1 0x1C +#define PON_CNTL_PULL_UP BIT(7) +#define PON_CNTL_TRIG_DELAY_MASK (0x7) + +/** + * struct pmic8xxx_pwrkey - pmic8xxx pwrkey information + * @key_press_irq: key press irq number + */ +struct pmic8xxx_pwrkey { + struct input_dev *pwr; + int key_press_irq; +}; + +static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey) +{ + struct pmic8xxx_pwrkey *pwrkey = _pwrkey; + + input_report_key(pwrkey->pwr, KEY_POWER, 1); + input_sync(pwrkey->pwr); + + return IRQ_HANDLED; +} + +static irqreturn_t pwrkey_release_irq(int irq, void *_pwrkey) +{ + struct pmic8xxx_pwrkey *pwrkey = _pwrkey; + + input_report_key(pwrkey->pwr, KEY_POWER, 0); + input_sync(pwrkey->pwr); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_PM_SLEEP +static int pmic8xxx_pwrkey_suspend(struct device *dev) +{ + struct pmic8xxx_pwrkey *pwrkey = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + enable_irq_wake(pwrkey->key_press_irq); + + return 0; +} + +static int pmic8xxx_pwrkey_resume(struct device *dev) +{ + struct pmic8xxx_pwrkey *pwrkey = dev_get_drvdata(dev); + + if (device_may_wakeup(dev)) + disable_irq_wake(pwrkey->key_press_irq); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(pm8xxx_pwr_key_pm_ops, + pmic8xxx_pwrkey_suspend, pmic8xxx_pwrkey_resume); + +static int pmic8xxx_pwrkey_probe(struct platform_device *pdev) +{ + struct input_dev *pwr; + int key_release_irq = platform_get_irq(pdev, 0); + int key_press_irq = platform_get_irq(pdev, 1); + int err; + unsigned int delay; + u8 pon_cntl; + struct pmic8xxx_pwrkey *pwrkey; + const struct pm8xxx_pwrkey_platform_data *pdata = + dev_get_platdata(&pdev->dev); + + if (!pdata) { + dev_err(&pdev->dev, "power key platform data not supplied\n"); + return -EINVAL; + } + + if (pdata->kpd_trigger_delay_us > 62500) { + dev_err(&pdev->dev, "invalid power key trigger delay\n"); + return -EINVAL; + } + + pwrkey = kzalloc(sizeof(*pwrkey), GFP_KERNEL); + if (!pwrkey) + return -ENOMEM; + + pwr = input_allocate_device(); + if (!pwr) { + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + err = -ENOMEM; + goto free_pwrkey; + } + + input_set_capability(pwr, EV_KEY, KEY_POWER); + + pwr->name = "pmic8xxx_pwrkey"; + pwr->phys = "pmic8xxx_pwrkey/input0"; + pwr->dev.parent = &pdev->dev; + + delay = (pdata->kpd_trigger_delay_us << 10) / USEC_PER_SEC; + delay = 1 + ilog2(delay); + + err = pm8xxx_readb(pdev->dev.parent, PON_CNTL_1, &pon_cntl); + if (err < 0) { + dev_err(&pdev->dev, "failed reading PON_CNTL_1 err=%d\n", err); + goto free_input_dev; + } + + pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK; + pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK); + if (pdata->pull_up) + pon_cntl |= PON_CNTL_PULL_UP; + else + pon_cntl &= ~PON_CNTL_PULL_UP; + + err = pm8xxx_writeb(pdev->dev.parent, PON_CNTL_1, pon_cntl); + if (err < 0) { + dev_err(&pdev->dev, "failed writing PON_CNTL_1 err=%d\n", err); + goto free_input_dev; + } + + err = input_register_device(pwr); + if (err) { + dev_dbg(&pdev->dev, "Can't register power key: %d\n", err); + goto free_input_dev; + } + + pwrkey->key_press_irq = key_press_irq; + pwrkey->pwr = pwr; + + platform_set_drvdata(pdev, pwrkey); + + err = request_irq(key_press_irq, pwrkey_press_irq, + IRQF_TRIGGER_RISING, "pmic8xxx_pwrkey_press", pwrkey); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", + key_press_irq, err); + goto unreg_input_dev; + } + + err = request_irq(key_release_irq, pwrkey_release_irq, + IRQF_TRIGGER_RISING, "pmic8xxx_pwrkey_release", pwrkey); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n", + key_release_irq, err); + + goto free_press_irq; + } + + device_init_wakeup(&pdev->dev, pdata->wakeup); + + return 0; + +free_press_irq: + free_irq(key_press_irq, NULL); +unreg_input_dev: + platform_set_drvdata(pdev, NULL); + input_unregister_device(pwr); + pwr = NULL; +free_input_dev: + input_free_device(pwr); +free_pwrkey: + kfree(pwrkey); + return err; +} + +static int pmic8xxx_pwrkey_remove(struct platform_device *pdev) +{ + struct pmic8xxx_pwrkey *pwrkey = platform_get_drvdata(pdev); + int key_release_irq = platform_get_irq(pdev, 0); + int key_press_irq = platform_get_irq(pdev, 1); + + device_init_wakeup(&pdev->dev, 0); + + free_irq(key_press_irq, pwrkey); + free_irq(key_release_irq, pwrkey); + input_unregister_device(pwrkey->pwr); + platform_set_drvdata(pdev, NULL); + kfree(pwrkey); + + return 0; +} + +static struct platform_driver pmic8xxx_pwrkey_driver = { + .probe = pmic8xxx_pwrkey_probe, + .remove = pmic8xxx_pwrkey_remove, + .driver = { + .name = PM8XXX_PWRKEY_DEV_NAME, + .owner = THIS_MODULE, + .pm = &pm8xxx_pwr_key_pm_ops, + }, +}; +module_platform_driver(pmic8xxx_pwrkey_driver); + +MODULE_ALIAS("platform:pmic8xxx_pwrkey"); +MODULE_DESCRIPTION("PMIC8XXX Power Key driver"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Trilok Soni <tsoni@codeaurora.org>"); diff --git a/drivers/input/misc/powermate.c b/drivers/input/misc/powermate.c new file mode 100644 index 00000000..49c0c3eb --- /dev/null +++ b/drivers/input/misc/powermate.c @@ -0,0 +1,453 @@ +/* + * A driver for the Griffin Technology, Inc. "PowerMate" USB controller dial. + * + * v1.1, (c)2002 William R Sowerbutts <will@sowerbutts.com> + * + * This device is a anodised aluminium knob which connects over USB. It can measure + * clockwise and anticlockwise rotation. The dial also acts as a pushbutton with + * a spring for automatic release. The base contains a pair of LEDs which illuminate + * the translucent base. It rotates without limit and reports its relative rotation + * back to the host when polled by the USB controller. + * + * Testing with the knob I have has shown that it measures approximately 94 "clicks" + * for one full rotation. Testing with my High Speed Rotation Actuator (ok, it was + * a variable speed cordless electric drill) has shown that the device can measure + * speeds of up to 7 clicks either clockwise or anticlockwise between pollings from + * the host. If it counts more than 7 clicks before it is polled, it will wrap back + * to zero and start counting again. This was at quite high speed, however, almost + * certainly faster than the human hand could turn it. Griffin say that it loses a + * pulse or two on a direction change; the granularity is so fine that I never + * noticed this in practice. + * + * The device's microcontroller can be programmed to set the LED to either a constant + * intensity, or to a rhythmic pulsing. Several patterns and speeds are available. + * + * Griffin were very happy to provide documentation and free hardware for development. + * + * Some userspace tools are available on the web: http://sowerbutts.com/powermate/ + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/usb/input.h> + +#define POWERMATE_VENDOR 0x077d /* Griffin Technology, Inc. */ +#define POWERMATE_PRODUCT_NEW 0x0410 /* Griffin PowerMate */ +#define POWERMATE_PRODUCT_OLD 0x04AA /* Griffin soundKnob */ + +#define CONTOUR_VENDOR 0x05f3 /* Contour Design, Inc. */ +#define CONTOUR_JOG 0x0240 /* Jog and Shuttle */ + +/* these are the command codes we send to the device */ +#define SET_STATIC_BRIGHTNESS 0x01 +#define SET_PULSE_ASLEEP 0x02 +#define SET_PULSE_AWAKE 0x03 +#define SET_PULSE_MODE 0x04 + +/* these refer to bits in the powermate_device's requires_update field. */ +#define UPDATE_STATIC_BRIGHTNESS (1<<0) +#define UPDATE_PULSE_ASLEEP (1<<1) +#define UPDATE_PULSE_AWAKE (1<<2) +#define UPDATE_PULSE_MODE (1<<3) + +/* at least two versions of the hardware exist, with differing payload + sizes. the first three bytes always contain the "interesting" data in + the relevant format. */ +#define POWERMATE_PAYLOAD_SIZE_MAX 6 +#define POWERMATE_PAYLOAD_SIZE_MIN 3 +struct powermate_device { + signed char *data; + dma_addr_t data_dma; + struct urb *irq, *config; + struct usb_ctrlrequest *configcr; + struct usb_device *udev; + struct usb_interface *intf; + struct input_dev *input; + spinlock_t lock; + int static_brightness; + int pulse_speed; + int pulse_table; + int pulse_asleep; + int pulse_awake; + int requires_update; // physical settings which are out of sync + char phys[64]; +}; + +static char pm_name_powermate[] = "Griffin PowerMate"; +static char pm_name_soundknob[] = "Griffin SoundKnob"; + +static void powermate_config_complete(struct urb *urb); + +/* Callback for data arriving from the PowerMate over the USB interrupt pipe */ +static void powermate_irq(struct urb *urb) +{ + struct powermate_device *pm = urb->context; + struct device *dev = &pm->intf->dev; + int retval; + + switch (urb->status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shutting down with status: %d\n", + __func__, urb->status); + return; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", + __func__, urb->status); + goto exit; + } + + /* handle updates to device state */ + input_report_key(pm->input, BTN_0, pm->data[0] & 0x01); + input_report_rel(pm->input, REL_DIAL, pm->data[1]); + input_sync(pm->input); + +exit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + dev_err(dev, "%s - usb_submit_urb failed with result: %d\n", + __func__, retval); +} + +/* Decide if we need to issue a control message and do so. Must be called with pm->lock taken */ +static void powermate_sync_state(struct powermate_device *pm) +{ + if (pm->requires_update == 0) + return; /* no updates are required */ + if (pm->config->status == -EINPROGRESS) + return; /* an update is already in progress; it'll issue this update when it completes */ + + if (pm->requires_update & UPDATE_PULSE_ASLEEP){ + pm->configcr->wValue = cpu_to_le16( SET_PULSE_ASLEEP ); + pm->configcr->wIndex = cpu_to_le16( pm->pulse_asleep ? 1 : 0 ); + pm->requires_update &= ~UPDATE_PULSE_ASLEEP; + }else if (pm->requires_update & UPDATE_PULSE_AWAKE){ + pm->configcr->wValue = cpu_to_le16( SET_PULSE_AWAKE ); + pm->configcr->wIndex = cpu_to_le16( pm->pulse_awake ? 1 : 0 ); + pm->requires_update &= ~UPDATE_PULSE_AWAKE; + }else if (pm->requires_update & UPDATE_PULSE_MODE){ + int op, arg; + /* the powermate takes an operation and an argument for its pulse algorithm. + the operation can be: + 0: divide the speed + 1: pulse at normal speed + 2: multiply the speed + the argument only has an effect for operations 0 and 2, and ranges between + 1 (least effect) to 255 (maximum effect). + + thus, several states are equivalent and are coalesced into one state. + + we map this onto a range from 0 to 510, with: + 0 -- 254 -- use divide (0 = slowest) + 255 -- use normal speed + 256 -- 510 -- use multiple (510 = fastest). + + Only values of 'arg' quite close to 255 are particularly useful/spectacular. + */ + if (pm->pulse_speed < 255) { + op = 0; // divide + arg = 255 - pm->pulse_speed; + } else if (pm->pulse_speed > 255) { + op = 2; // multiply + arg = pm->pulse_speed - 255; + } else { + op = 1; // normal speed + arg = 0; // can be any value + } + pm->configcr->wValue = cpu_to_le16( (pm->pulse_table << 8) | SET_PULSE_MODE ); + pm->configcr->wIndex = cpu_to_le16( (arg << 8) | op ); + pm->requires_update &= ~UPDATE_PULSE_MODE; + } else if (pm->requires_update & UPDATE_STATIC_BRIGHTNESS) { + pm->configcr->wValue = cpu_to_le16( SET_STATIC_BRIGHTNESS ); + pm->configcr->wIndex = cpu_to_le16( pm->static_brightness ); + pm->requires_update &= ~UPDATE_STATIC_BRIGHTNESS; + } else { + printk(KERN_ERR "powermate: unknown update required"); + pm->requires_update = 0; /* fudge the bug */ + return; + } + +/* printk("powermate: %04x %04x\n", pm->configcr->wValue, pm->configcr->wIndex); */ + + pm->configcr->bRequestType = 0x41; /* vendor request */ + pm->configcr->bRequest = 0x01; + pm->configcr->wLength = 0; + + usb_fill_control_urb(pm->config, pm->udev, usb_sndctrlpipe(pm->udev, 0), + (void *) pm->configcr, NULL, 0, + powermate_config_complete, pm); + + if (usb_submit_urb(pm->config, GFP_ATOMIC)) + printk(KERN_ERR "powermate: usb_submit_urb(config) failed"); +} + +/* Called when our asynchronous control message completes. We may need to issue another immediately */ +static void powermate_config_complete(struct urb *urb) +{ + struct powermate_device *pm = urb->context; + unsigned long flags; + + if (urb->status) + printk(KERN_ERR "powermate: config urb returned %d\n", urb->status); + + spin_lock_irqsave(&pm->lock, flags); + powermate_sync_state(pm); + spin_unlock_irqrestore(&pm->lock, flags); +} + +/* Set the LED up as described and begin the sync with the hardware if required */ +static void powermate_pulse_led(struct powermate_device *pm, int static_brightness, int pulse_speed, + int pulse_table, int pulse_asleep, int pulse_awake) +{ + unsigned long flags; + + if (pulse_speed < 0) + pulse_speed = 0; + if (pulse_table < 0) + pulse_table = 0; + if (pulse_speed > 510) + pulse_speed = 510; + if (pulse_table > 2) + pulse_table = 2; + + pulse_asleep = !!pulse_asleep; + pulse_awake = !!pulse_awake; + + + spin_lock_irqsave(&pm->lock, flags); + + /* mark state updates which are required */ + if (static_brightness != pm->static_brightness) { + pm->static_brightness = static_brightness; + pm->requires_update |= UPDATE_STATIC_BRIGHTNESS; + } + if (pulse_asleep != pm->pulse_asleep) { + pm->pulse_asleep = pulse_asleep; + pm->requires_update |= (UPDATE_PULSE_ASLEEP | UPDATE_STATIC_BRIGHTNESS); + } + if (pulse_awake != pm->pulse_awake) { + pm->pulse_awake = pulse_awake; + pm->requires_update |= (UPDATE_PULSE_AWAKE | UPDATE_STATIC_BRIGHTNESS); + } + if (pulse_speed != pm->pulse_speed || pulse_table != pm->pulse_table) { + pm->pulse_speed = pulse_speed; + pm->pulse_table = pulse_table; + pm->requires_update |= UPDATE_PULSE_MODE; + } + + powermate_sync_state(pm); + + spin_unlock_irqrestore(&pm->lock, flags); +} + +/* Callback from the Input layer when an event arrives from userspace to configure the LED */ +static int powermate_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int _value) +{ + unsigned int command = (unsigned int)_value; + struct powermate_device *pm = input_get_drvdata(dev); + + if (type == EV_MSC && code == MSC_PULSELED){ + /* + bits 0- 7: 8 bits: LED brightness + bits 8-16: 9 bits: pulsing speed modifier (0 ... 510); 0-254 = slower, 255 = standard, 256-510 = faster. + bits 17-18: 2 bits: pulse table (0, 1, 2 valid) + bit 19: 1 bit : pulse whilst asleep? + bit 20: 1 bit : pulse constantly? + */ + int static_brightness = command & 0xFF; // bits 0-7 + int pulse_speed = (command >> 8) & 0x1FF; // bits 8-16 + int pulse_table = (command >> 17) & 0x3; // bits 17-18 + int pulse_asleep = (command >> 19) & 0x1; // bit 19 + int pulse_awake = (command >> 20) & 0x1; // bit 20 + + powermate_pulse_led(pm, static_brightness, pulse_speed, pulse_table, pulse_asleep, pulse_awake); + } + + return 0; +} + +static int powermate_alloc_buffers(struct usb_device *udev, struct powermate_device *pm) +{ + pm->data = usb_alloc_coherent(udev, POWERMATE_PAYLOAD_SIZE_MAX, + GFP_ATOMIC, &pm->data_dma); + if (!pm->data) + return -1; + + pm->configcr = kmalloc(sizeof(*(pm->configcr)), GFP_KERNEL); + if (!pm->configcr) + return -ENOMEM; + + return 0; +} + +static void powermate_free_buffers(struct usb_device *udev, struct powermate_device *pm) +{ + usb_free_coherent(udev, POWERMATE_PAYLOAD_SIZE_MAX, + pm->data, pm->data_dma); + kfree(pm->configcr); +} + +/* Called whenever a USB device matching one in our supported devices table is connected */ +static int powermate_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev (intf); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct powermate_device *pm; + struct input_dev *input_dev; + int pipe, maxp; + int error = -ENOMEM; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(endpoint)) + return -EIO; + + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x0a, USB_TYPE_CLASS | USB_RECIP_INTERFACE, + 0, interface->desc.bInterfaceNumber, NULL, 0, + USB_CTRL_SET_TIMEOUT); + + pm = kzalloc(sizeof(struct powermate_device), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!pm || !input_dev) + goto fail1; + + if (powermate_alloc_buffers(udev, pm)) + goto fail2; + + pm->irq = usb_alloc_urb(0, GFP_KERNEL); + if (!pm->irq) + goto fail2; + + pm->config = usb_alloc_urb(0, GFP_KERNEL); + if (!pm->config) + goto fail3; + + pm->udev = udev; + pm->intf = intf; + pm->input = input_dev; + + usb_make_path(udev, pm->phys, sizeof(pm->phys)); + strlcat(pm->phys, "/input0", sizeof(pm->phys)); + + spin_lock_init(&pm->lock); + + switch (le16_to_cpu(udev->descriptor.idProduct)) { + case POWERMATE_PRODUCT_NEW: + input_dev->name = pm_name_powermate; + break; + case POWERMATE_PRODUCT_OLD: + input_dev->name = pm_name_soundknob; + break; + default: + input_dev->name = pm_name_soundknob; + printk(KERN_WARNING "powermate: unknown product id %04x\n", + le16_to_cpu(udev->descriptor.idProduct)); + } + + input_dev->phys = pm->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, pm); + + input_dev->event = powermate_input_event; + + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) | + BIT_MASK(EV_MSC); + input_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0); + input_dev->relbit[BIT_WORD(REL_DIAL)] = BIT_MASK(REL_DIAL); + input_dev->mscbit[BIT_WORD(MSC_PULSELED)] = BIT_MASK(MSC_PULSELED); + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + + if (maxp < POWERMATE_PAYLOAD_SIZE_MIN || maxp > POWERMATE_PAYLOAD_SIZE_MAX) { + printk(KERN_WARNING "powermate: Expected payload of %d--%d bytes, found %d bytes!\n", + POWERMATE_PAYLOAD_SIZE_MIN, POWERMATE_PAYLOAD_SIZE_MAX, maxp); + maxp = POWERMATE_PAYLOAD_SIZE_MAX; + } + + usb_fill_int_urb(pm->irq, udev, pipe, pm->data, + maxp, powermate_irq, + pm, endpoint->bInterval); + pm->irq->transfer_dma = pm->data_dma; + pm->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* register our interrupt URB with the USB system */ + if (usb_submit_urb(pm->irq, GFP_KERNEL)) { + error = -EIO; + goto fail4; + } + + error = input_register_device(pm->input); + if (error) + goto fail5; + + + /* force an update of everything */ + pm->requires_update = UPDATE_PULSE_ASLEEP | UPDATE_PULSE_AWAKE | UPDATE_PULSE_MODE | UPDATE_STATIC_BRIGHTNESS; + powermate_pulse_led(pm, 0x80, 255, 0, 1, 0); // set default pulse parameters + + usb_set_intfdata(intf, pm); + return 0; + + fail5: usb_kill_urb(pm->irq); + fail4: usb_free_urb(pm->config); + fail3: usb_free_urb(pm->irq); + fail2: powermate_free_buffers(udev, pm); + fail1: input_free_device(input_dev); + kfree(pm); + return error; +} + +/* Called when a USB device we've accepted ownership of is removed */ +static void powermate_disconnect(struct usb_interface *intf) +{ + struct powermate_device *pm = usb_get_intfdata (intf); + + usb_set_intfdata(intf, NULL); + if (pm) { + pm->requires_update = 0; + usb_kill_urb(pm->irq); + input_unregister_device(pm->input); + usb_free_urb(pm->irq); + usb_free_urb(pm->config); + powermate_free_buffers(interface_to_usbdev(intf), pm); + + kfree(pm); + } +} + +static struct usb_device_id powermate_devices [] = { + { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_NEW) }, + { USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_OLD) }, + { USB_DEVICE(CONTOUR_VENDOR, CONTOUR_JOG) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, powermate_devices); + +static struct usb_driver powermate_driver = { + .name = "powermate", + .probe = powermate_probe, + .disconnect = powermate_disconnect, + .id_table = powermate_devices, +}; + +module_usb_driver(powermate_driver); + +MODULE_AUTHOR( "William R Sowerbutts" ); +MODULE_DESCRIPTION( "Griffin Technology, Inc PowerMate driver" ); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/pwm-beeper.c b/drivers/input/misc/pwm-beeper.c new file mode 100644 index 00000000..08088684 --- /dev/null +++ b/drivers/input/misc/pwm-beeper.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de> + * PWM beeper driver + * + * 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. + * + * 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., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/input.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> + +struct pwm_beeper { + struct input_dev *input; + struct pwm_device *pwm; + unsigned long period; +}; + +#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x)) + +static int pwm_beeper_event(struct input_dev *input, + unsigned int type, unsigned int code, int value) +{ + int ret = 0; + struct pwm_beeper *beeper = input_get_drvdata(input); + unsigned long period; + + if (type != EV_SND || value < 0) + return -EINVAL; + + switch (code) { + case SND_BELL: + value = value ? 1000 : 0; + break; + case SND_TONE: + break; + default: + return -EINVAL; + } + + if (value == 0) { + pwm_config(beeper->pwm, 0, 0); + pwm_disable(beeper->pwm); + } else { + period = HZ_TO_NANOSECONDS(value); + ret = pwm_config(beeper->pwm, period / 2, period); + if (ret) + return ret; + ret = pwm_enable(beeper->pwm); + if (ret) + return ret; + beeper->period = period; + } + + return 0; +} + +static int pwm_beeper_probe(struct platform_device *pdev) +{ + unsigned long pwm_id = (unsigned long)pdev->dev.platform_data; + struct pwm_beeper *beeper; + int error; + + beeper = kzalloc(sizeof(*beeper), GFP_KERNEL); + if (!beeper) + return -ENOMEM; + + beeper->pwm = pwm_get(&pdev->dev, NULL); + if (IS_ERR(beeper->pwm)) { + dev_dbg(&pdev->dev, "unable to request PWM, trying legacy API\n"); + beeper->pwm = pwm_request(pwm_id, "pwm beeper"); + } + + if (IS_ERR(beeper->pwm)) { + error = PTR_ERR(beeper->pwm); + dev_err(&pdev->dev, "Failed to request pwm device: %d\n", error); + goto err_free; + } + + beeper->input = input_allocate_device(); + if (!beeper->input) { + dev_err(&pdev->dev, "Failed to allocate input device\n"); + error = -ENOMEM; + goto err_pwm_free; + } + beeper->input->dev.parent = &pdev->dev; + + beeper->input->name = "pwm-beeper"; + beeper->input->phys = "pwm/input0"; + beeper->input->id.bustype = BUS_HOST; + beeper->input->id.vendor = 0x001f; + beeper->input->id.product = 0x0001; + beeper->input->id.version = 0x0100; + + beeper->input->evbit[0] = BIT(EV_SND); + beeper->input->sndbit[0] = BIT(SND_TONE) | BIT(SND_BELL); + + beeper->input->event = pwm_beeper_event; + + input_set_drvdata(beeper->input, beeper); + + error = input_register_device(beeper->input); + if (error) { + dev_err(&pdev->dev, "Failed to register input device: %d\n", error); + goto err_input_free; + } + + platform_set_drvdata(pdev, beeper); + + return 0; + +err_input_free: + input_free_device(beeper->input); +err_pwm_free: + pwm_free(beeper->pwm); +err_free: + kfree(beeper); + + return error; +} + +static int pwm_beeper_remove(struct platform_device *pdev) +{ + struct pwm_beeper *beeper = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + input_unregister_device(beeper->input); + + pwm_disable(beeper->pwm); + pwm_free(beeper->pwm); + + kfree(beeper); + + return 0; +} + +#ifdef CONFIG_PM +static int pwm_beeper_suspend(struct device *dev) +{ + struct pwm_beeper *beeper = dev_get_drvdata(dev); + + if (beeper->period) + pwm_disable(beeper->pwm); + + return 0; +} + +static int pwm_beeper_resume(struct device *dev) +{ + struct pwm_beeper *beeper = dev_get_drvdata(dev); + + if (beeper->period) { + pwm_config(beeper->pwm, beeper->period / 2, beeper->period); + pwm_enable(beeper->pwm); + } + + return 0; +} + +static SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops, + pwm_beeper_suspend, pwm_beeper_resume); + +#define PWM_BEEPER_PM_OPS (&pwm_beeper_pm_ops) +#else +#define PWM_BEEPER_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id pwm_beeper_match[] = { + { .compatible = "pwm-beeper", }, + { }, +}; +#endif + +static struct platform_driver pwm_beeper_driver = { + .probe = pwm_beeper_probe, + .remove = pwm_beeper_remove, + .driver = { + .name = "pwm-beeper", + .owner = THIS_MODULE, + .pm = PWM_BEEPER_PM_OPS, + .of_match_table = of_match_ptr(pwm_beeper_match), + }, +}; +module_platform_driver(pwm_beeper_driver); + +MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>"); +MODULE_DESCRIPTION("PWM beeper driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:pwm-beeper"); diff --git a/drivers/input/misc/rb532_button.c b/drivers/input/misc/rb532_button.c new file mode 100644 index 00000000..fb4f8ac3 --- /dev/null +++ b/drivers/input/misc/rb532_button.c @@ -0,0 +1,108 @@ +/* + * Support for the S1 button on Routerboard 532 + * + * Copyright (C) 2009 Phil Sutter <n0-1@freewrt.org> + */ + +#include <linux/input-polldev.h> +#include <linux/module.h> +#include <linux/platform_device.h> + +#include <asm/mach-rc32434/gpio.h> +#include <asm/mach-rc32434/rb.h> + +#define DRV_NAME "rb532-button" + +#define RB532_BTN_RATE 100 /* msec */ +#define RB532_BTN_KSYM BTN_0 + +/* The S1 button state is provided by GPIO pin 1. But as this + * pin is also used for uart input as alternate function, the + * operational modes must be switched first: + * 1) disable uart using set_latch_u5() + * 2) turn off alternate function implicitly through + * gpio_direction_input() + * 3) read the GPIO's current value + * 4) undo step 2 by enabling alternate function (in this + * mode the GPIO direction is fixed, so no change needed) + * 5) turn on uart again + * The GPIO value occurs to be inverted, so pin high means + * button is not pressed. + */ +static bool rb532_button_pressed(void) +{ + int val; + + set_latch_u5(0, LO_FOFF); + gpio_direction_input(GPIO_BTN_S1); + + val = gpio_get_value(GPIO_BTN_S1); + + rb532_gpio_set_func(GPIO_BTN_S1); + set_latch_u5(LO_FOFF, 0); + + return !val; +} + +static void rb532_button_poll(struct input_polled_dev *poll_dev) +{ + input_report_key(poll_dev->input, RB532_BTN_KSYM, + rb532_button_pressed()); + input_sync(poll_dev->input); +} + +static int rb532_button_probe(struct platform_device *pdev) +{ + struct input_polled_dev *poll_dev; + int error; + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) + return -ENOMEM; + + poll_dev->poll = rb532_button_poll; + poll_dev->poll_interval = RB532_BTN_RATE; + + poll_dev->input->name = "rb532 button"; + poll_dev->input->phys = "rb532/button0"; + poll_dev->input->id.bustype = BUS_HOST; + poll_dev->input->dev.parent = &pdev->dev; + + dev_set_drvdata(&pdev->dev, poll_dev); + + input_set_capability(poll_dev->input, EV_KEY, RB532_BTN_KSYM); + + error = input_register_polled_device(poll_dev); + if (error) { + input_free_polled_device(poll_dev); + return error; + } + + return 0; +} + +static int rb532_button_remove(struct platform_device *pdev) +{ + struct input_polled_dev *poll_dev = dev_get_drvdata(&pdev->dev); + + input_unregister_polled_device(poll_dev); + input_free_polled_device(poll_dev); + dev_set_drvdata(&pdev->dev, NULL); + + return 0; +} + +static struct platform_driver rb532_button_driver = { + .probe = rb532_button_probe, + .remove = rb532_button_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, +}; +module_platform_driver(rb532_button_driver); + +MODULE_AUTHOR("Phil Sutter <n0-1@freewrt.org>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Support for S1 button on Routerboard 532"); +MODULE_ALIAS("platform:" DRV_NAME); diff --git a/drivers/input/misc/retu-pwrbutton.c b/drivers/input/misc/retu-pwrbutton.c new file mode 100644 index 00000000..7ca09baa --- /dev/null +++ b/drivers/input/misc/retu-pwrbutton.c @@ -0,0 +1,99 @@ +/* + * Retu power button driver. + * + * Copyright (C) 2004-2010 Nokia Corporation + * + * Original code written by Ari Saastamoinen, Juha Yrjölä and Felipe Balbi. + * Rewritten by Aaro Koskinen. + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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. + */ + +#include <linux/irq.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mfd/retu.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> + +#define RETU_STATUS_PWRONX (1 << 5) + +static irqreturn_t retu_pwrbutton_irq(int irq, void *_pwr) +{ + struct input_dev *idev = _pwr; + struct retu_dev *rdev = input_get_drvdata(idev); + bool state; + + state = !(retu_read(rdev, RETU_REG_STATUS) & RETU_STATUS_PWRONX); + input_report_key(idev, KEY_POWER, state); + input_sync(idev); + + return IRQ_HANDLED; +} + +static int retu_pwrbutton_probe(struct platform_device *pdev) +{ + struct retu_dev *rdev = dev_get_drvdata(pdev->dev.parent); + struct input_dev *idev; + int irq; + int error; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + idev = devm_input_allocate_device(&pdev->dev); + if (!idev) + return -ENOMEM; + + idev->name = "retu-pwrbutton"; + idev->dev.parent = &pdev->dev; + + input_set_capability(idev, EV_KEY, KEY_POWER); + input_set_drvdata(idev, rdev); + + error = devm_request_threaded_irq(&pdev->dev, irq, + NULL, retu_pwrbutton_irq, 0, + "retu-pwrbutton", idev); + if (error) + return error; + + error = input_register_device(idev); + if (error) + return error; + + return 0; +} + +static int retu_pwrbutton_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver retu_pwrbutton_driver = { + .probe = retu_pwrbutton_probe, + .remove = retu_pwrbutton_remove, + .driver = { + .name = "retu-pwrbutton", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(retu_pwrbutton_driver); + +MODULE_ALIAS("platform:retu-pwrbutton"); +MODULE_DESCRIPTION("Retu Power Button"); +MODULE_AUTHOR("Ari Saastamoinen"); +MODULE_AUTHOR("Felipe Balbi"); +MODULE_AUTHOR("Aaro Koskinen <aaro.koskinen@iki.fi>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/rotary_encoder.c b/drivers/input/misc/rotary_encoder.c new file mode 100644 index 00000000..aff47b2c --- /dev/null +++ b/drivers/input/misc/rotary_encoder.c @@ -0,0 +1,339 @@ +/* + * rotary_encoder.c + * + * (c) 2009 Daniel Mack <daniel@caiaq.de> + * Copyright (C) 2011 Johan Hovold <jhovold@gmail.com> + * + * state machine code inspired by code from Tim Ruetz + * + * A generic driver for rotary encoders connected to GPIO lines. + * See file:Documentation/input/rotary-encoder.txt for more information + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/device.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/rotary_encoder.h> +#include <linux/slab.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> + +#define DRV_NAME "rotary-encoder" + +struct rotary_encoder { + struct input_dev *input; + const struct rotary_encoder_platform_data *pdata; + + unsigned int axis; + unsigned int pos; + + unsigned int irq_a; + unsigned int irq_b; + + bool armed; + unsigned char dir; /* 0 - clockwise, 1 - CCW */ + + char last_stable; +}; + +static int rotary_encoder_get_state(const struct rotary_encoder_platform_data *pdata) +{ + int a = !!gpio_get_value(pdata->gpio_a); + int b = !!gpio_get_value(pdata->gpio_b); + + a ^= pdata->inverted_a; + b ^= pdata->inverted_b; + + return ((a << 1) | b); +} + +static void rotary_encoder_report_event(struct rotary_encoder *encoder) +{ + const struct rotary_encoder_platform_data *pdata = encoder->pdata; + + if (pdata->relative_axis) { + input_report_rel(encoder->input, + pdata->axis, encoder->dir ? -1 : 1); + } else { + unsigned int pos = encoder->pos; + + if (encoder->dir) { + /* turning counter-clockwise */ + if (pdata->rollover) + pos += pdata->steps; + if (pos) + pos--; + } else { + /* turning clockwise */ + if (pdata->rollover || pos < pdata->steps) + pos++; + } + + if (pdata->rollover) + pos %= pdata->steps; + + encoder->pos = pos; + input_report_abs(encoder->input, pdata->axis, encoder->pos); + } + + input_sync(encoder->input); +} + +static irqreturn_t rotary_encoder_irq(int irq, void *dev_id) +{ + struct rotary_encoder *encoder = dev_id; + int state; + + state = rotary_encoder_get_state(encoder->pdata); + + switch (state) { + case 0x0: + if (encoder->armed) { + rotary_encoder_report_event(encoder); + encoder->armed = false; + } + break; + + case 0x1: + case 0x2: + if (encoder->armed) + encoder->dir = state - 1; + break; + + case 0x3: + encoder->armed = true; + break; + } + + return IRQ_HANDLED; +} + +static irqreturn_t rotary_encoder_half_period_irq(int irq, void *dev_id) +{ + struct rotary_encoder *encoder = dev_id; + int state; + + state = rotary_encoder_get_state(encoder->pdata); + + switch (state) { + case 0x00: + case 0x03: + if (state != encoder->last_stable) { + rotary_encoder_report_event(encoder); + encoder->last_stable = state; + } + break; + + case 0x01: + case 0x02: + encoder->dir = (encoder->last_stable + state) & 0x01; + break; + } + + return IRQ_HANDLED; +} + +#ifdef CONFIG_OF +static struct of_device_id rotary_encoder_of_match[] = { + { .compatible = "rotary-encoder", }, + { }, +}; +MODULE_DEVICE_TABLE(of, rotary_encoder_of_match); + +static struct rotary_encoder_platform_data *rotary_encoder_parse_dt(struct device *dev) +{ + const struct of_device_id *of_id = + of_match_device(rotary_encoder_of_match, dev); + struct device_node *np = dev->of_node; + struct rotary_encoder_platform_data *pdata; + enum of_gpio_flags flags; + + if (!of_id || !np) + return NULL; + + pdata = kzalloc(sizeof(struct rotary_encoder_platform_data), + GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + of_property_read_u32(np, "rotary-encoder,steps", &pdata->steps); + of_property_read_u32(np, "linux,axis", &pdata->axis); + + pdata->gpio_a = of_get_gpio_flags(np, 0, &flags); + pdata->inverted_a = flags & OF_GPIO_ACTIVE_LOW; + + pdata->gpio_b = of_get_gpio_flags(np, 1, &flags); + pdata->inverted_b = flags & OF_GPIO_ACTIVE_LOW; + + pdata->relative_axis = !!of_get_property(np, + "rotary-encoder,relative-axis", NULL); + pdata->rollover = !!of_get_property(np, + "rotary-encoder,rollover", NULL); + pdata->half_period = !!of_get_property(np, + "rotary-encoder,half-period", NULL); + + return pdata; +} +#else +static inline struct rotary_encoder_platform_data * +rotary_encoder_parse_dt(struct device *dev) +{ + return NULL; +} +#endif + +static int rotary_encoder_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct rotary_encoder_platform_data *pdata = dev_get_platdata(dev); + struct rotary_encoder *encoder; + struct input_dev *input; + irq_handler_t handler; + int err; + + if (!pdata) { + pdata = rotary_encoder_parse_dt(dev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + if (!pdata) { + dev_err(dev, "missing platform data\n"); + return -EINVAL; + } + } + + encoder = kzalloc(sizeof(struct rotary_encoder), GFP_KERNEL); + input = input_allocate_device(); + if (!encoder || !input) { + err = -ENOMEM; + goto exit_free_mem; + } + + encoder->input = input; + encoder->pdata = pdata; + + input->name = pdev->name; + input->id.bustype = BUS_HOST; + input->dev.parent = dev; + + if (pdata->relative_axis) { + input->evbit[0] = BIT_MASK(EV_REL); + input->relbit[0] = BIT_MASK(pdata->axis); + } else { + input->evbit[0] = BIT_MASK(EV_ABS); + input_set_abs_params(encoder->input, + pdata->axis, 0, pdata->steps, 0, 1); + } + + /* request the GPIOs */ + err = gpio_request_one(pdata->gpio_a, GPIOF_IN, dev_name(dev)); + if (err) { + dev_err(dev, "unable to request GPIO %d\n", pdata->gpio_a); + goto exit_free_mem; + } + + err = gpio_request_one(pdata->gpio_b, GPIOF_IN, dev_name(dev)); + if (err) { + dev_err(dev, "unable to request GPIO %d\n", pdata->gpio_b); + goto exit_free_gpio_a; + } + + encoder->irq_a = gpio_to_irq(pdata->gpio_a); + encoder->irq_b = gpio_to_irq(pdata->gpio_b); + + /* request the IRQs */ + if (pdata->half_period) { + handler = &rotary_encoder_half_period_irq; + encoder->last_stable = rotary_encoder_get_state(pdata); + } else { + handler = &rotary_encoder_irq; + } + + err = request_irq(encoder->irq_a, handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + DRV_NAME, encoder); + if (err) { + dev_err(dev, "unable to request IRQ %d\n", encoder->irq_a); + goto exit_free_gpio_b; + } + + err = request_irq(encoder->irq_b, handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + DRV_NAME, encoder); + if (err) { + dev_err(dev, "unable to request IRQ %d\n", encoder->irq_b); + goto exit_free_irq_a; + } + + err = input_register_device(input); + if (err) { + dev_err(dev, "failed to register input device\n"); + goto exit_free_irq_b; + } + + platform_set_drvdata(pdev, encoder); + + return 0; + +exit_free_irq_b: + free_irq(encoder->irq_b, encoder); +exit_free_irq_a: + free_irq(encoder->irq_a, encoder); +exit_free_gpio_b: + gpio_free(pdata->gpio_b); +exit_free_gpio_a: + gpio_free(pdata->gpio_a); +exit_free_mem: + input_free_device(input); + kfree(encoder); + if (!dev_get_platdata(&pdev->dev)) + kfree(pdata); + + return err; +} + +static int rotary_encoder_remove(struct platform_device *pdev) +{ + struct rotary_encoder *encoder = platform_get_drvdata(pdev); + const struct rotary_encoder_platform_data *pdata = encoder->pdata; + + free_irq(encoder->irq_a, encoder); + free_irq(encoder->irq_b, encoder); + gpio_free(pdata->gpio_a); + gpio_free(pdata->gpio_b); + + input_unregister_device(encoder->input); + kfree(encoder); + + if (!dev_get_platdata(&pdev->dev)) + kfree(pdata); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver rotary_encoder_driver = { + .probe = rotary_encoder_probe, + .remove = rotary_encoder_remove, + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(rotary_encoder_of_match), + } +}; +module_platform_driver(rotary_encoder_driver); + +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_DESCRIPTION("GPIO rotary encoder driver"); +MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>, Johan Hovold"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/input/misc/sgi_btns.c b/drivers/input/misc/sgi_btns.c new file mode 100644 index 00000000..ad6415ce --- /dev/null +++ b/drivers/input/misc/sgi_btns.c @@ -0,0 +1,169 @@ +/* + * SGI Volume Button interface driver + * + * Copyright (C) 2008 Thomas Bogendoerfer <tsbogend@alpha.franken.de> + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/ioport.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#ifdef CONFIG_SGI_IP22 +#include <asm/sgi/ioc.h> + +static inline u8 button_status(void) +{ + u8 status; + + status = readb(&sgioc->panel) ^ 0xa0; + return ((status & 0x80) >> 6) | ((status & 0x20) >> 5); +} +#endif + +#ifdef CONFIG_SGI_IP32 +#include <asm/ip32/mace.h> + +static inline u8 button_status(void) +{ + u64 status; + + status = readq(&mace->perif.audio.control); + writeq(status & ~(3U << 23), &mace->perif.audio.control); + + return (status >> 23) & 3; +} +#endif + +#define BUTTONS_POLL_INTERVAL 30 /* msec */ +#define BUTTONS_COUNT_THRESHOLD 3 + +static const unsigned short sgi_map[] = { + KEY_VOLUMEDOWN, + KEY_VOLUMEUP +}; + +struct buttons_dev { + struct input_polled_dev *poll_dev; + unsigned short keymap[ARRAY_SIZE(sgi_map)]; + int count[ARRAY_SIZE(sgi_map)]; +}; + +static void handle_buttons(struct input_polled_dev *dev) +{ + struct buttons_dev *bdev = dev->private; + struct input_dev *input = dev->input; + u8 status; + int i; + + status = button_status(); + + for (i = 0; i < ARRAY_SIZE(bdev->keymap); i++) { + if (status & (1U << i)) { + if (++bdev->count[i] == BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 1); + input_sync(input); + } + } else { + if (bdev->count[i] >= BUTTONS_COUNT_THRESHOLD) { + input_event(input, EV_MSC, MSC_SCAN, i); + input_report_key(input, bdev->keymap[i], 0); + input_sync(input); + } + bdev->count[i] = 0; + } + } +} + +static int sgi_buttons_probe(struct platform_device *pdev) +{ + struct buttons_dev *bdev; + struct input_polled_dev *poll_dev; + struct input_dev *input; + int error, i; + + bdev = kzalloc(sizeof(struct buttons_dev), GFP_KERNEL); + poll_dev = input_allocate_polled_device(); + if (!bdev || !poll_dev) { + error = -ENOMEM; + goto err_free_mem; + } + + memcpy(bdev->keymap, sgi_map, sizeof(bdev->keymap)); + + poll_dev->private = bdev; + poll_dev->poll = handle_buttons; + poll_dev->poll_interval = BUTTONS_POLL_INTERVAL; + + input = poll_dev->input; + input->name = "SGI buttons"; + input->phys = "sgi/input0"; + input->id.bustype = BUS_HOST; + input->dev.parent = &pdev->dev; + + input->keycode = bdev->keymap; + input->keycodemax = ARRAY_SIZE(bdev->keymap); + input->keycodesize = sizeof(unsigned short); + + input_set_capability(input, EV_MSC, MSC_SCAN); + __set_bit(EV_KEY, input->evbit); + for (i = 0; i < ARRAY_SIZE(sgi_map); i++) + __set_bit(bdev->keymap[i], input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + bdev->poll_dev = poll_dev; + dev_set_drvdata(&pdev->dev, bdev); + + error = input_register_polled_device(poll_dev); + if (error) + goto err_free_mem; + + return 0; + + err_free_mem: + input_free_polled_device(poll_dev); + kfree(bdev); + dev_set_drvdata(&pdev->dev, NULL); + return error; +} + +static int sgi_buttons_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct buttons_dev *bdev = dev_get_drvdata(dev); + + input_unregister_polled_device(bdev->poll_dev); + input_free_polled_device(bdev->poll_dev); + kfree(bdev); + dev_set_drvdata(dev, NULL); + + return 0; +} + +static struct platform_driver sgi_buttons_driver = { + .probe = sgi_buttons_probe, + .remove = sgi_buttons_remove, + .driver = { + .name = "sgibtns", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(sgi_buttons_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/sparcspkr.c b/drivers/input/misc/sparcspkr.c new file mode 100644 index 00000000..a53586a7 --- /dev/null +++ b/drivers/input/misc/sparcspkr.c @@ -0,0 +1,372 @@ +/* + * Driver for PC-speaker like devices found on various Sparc systems. + * + * Copyright (c) 2002 Vojtech Pavlik + * Copyright (c) 2002, 2006, 2008 David S. Miller (davem@davemloft.net) + */ +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/of_device.h> +#include <linux/slab.h> + +#include <asm/io.h> + +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); +MODULE_DESCRIPTION("Sparc Speaker beeper driver"); +MODULE_LICENSE("GPL"); + +struct grover_beep_info { + void __iomem *freq_regs; + void __iomem *enable_reg; +}; + +struct bbc_beep_info { + u32 clock_freq; + void __iomem *regs; +}; + +struct sparcspkr_state { + const char *name; + int (*event)(struct input_dev *dev, unsigned int type, unsigned int code, int value); + spinlock_t lock; + struct input_dev *input_dev; + union { + struct grover_beep_info grover; + struct bbc_beep_info bbc; + } u; +}; + +static u32 bbc_count_to_reg(struct bbc_beep_info *info, unsigned int count) +{ + u32 val, clock_freq = info->clock_freq; + int i; + + if (!count) + return 0; + + if (count <= clock_freq >> 20) + return 1 << 18; + + if (count >= clock_freq >> 12) + return 1 << 10; + + val = 1 << 18; + for (i = 19; i >= 11; i--) { + val >>= 1; + if (count <= clock_freq >> i) + break; + } + + return val; +} + +static int bbc_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct sparcspkr_state *state = dev_get_drvdata(dev->dev.parent); + struct bbc_beep_info *info = &state->u.bbc; + unsigned int count = 0; + unsigned long flags; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = 1193182 / value; + + count = bbc_count_to_reg(info, count); + + spin_lock_irqsave(&state->lock, flags); + + if (count) { + outb(0x01, info->regs + 0); + outb(0x00, info->regs + 2); + outb((count >> 16) & 0xff, info->regs + 3); + outb((count >> 8) & 0xff, info->regs + 4); + outb(0x00, info->regs + 5); + } else { + outb(0x00, info->regs + 0); + } + + spin_unlock_irqrestore(&state->lock, flags); + + return 0; +} + +static int grover_spkr_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +{ + struct sparcspkr_state *state = dev_get_drvdata(dev->dev.parent); + struct grover_beep_info *info = &state->u.grover; + unsigned int count = 0; + unsigned long flags; + + if (type != EV_SND) + return -1; + + switch (code) { + case SND_BELL: if (value) value = 1000; + case SND_TONE: break; + default: return -1; + } + + if (value > 20 && value < 32767) + count = 1193182 / value; + + spin_lock_irqsave(&state->lock, flags); + + if (count) { + /* enable counter 2 */ + outb(inb(info->enable_reg) | 3, info->enable_reg); + /* set command for counter 2, 2 byte write */ + outb(0xB6, info->freq_regs + 1); + /* select desired HZ */ + outb(count & 0xff, info->freq_regs + 0); + outb((count >> 8) & 0xff, info->freq_regs + 0); + } else { + /* disable counter 2 */ + outb(inb_p(info->enable_reg) & 0xFC, info->enable_reg); + } + + spin_unlock_irqrestore(&state->lock, flags); + + return 0; +} + +static int sparcspkr_probe(struct device *dev) +{ + struct sparcspkr_state *state = dev_get_drvdata(dev); + struct input_dev *input_dev; + int error; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = state->name; + input_dev->phys = "sparc/input0"; + input_dev->id.bustype = BUS_ISA; + input_dev->id.vendor = 0x001f; + input_dev->id.product = 0x0001; + input_dev->id.version = 0x0100; + input_dev->dev.parent = dev; + + input_dev->evbit[0] = BIT_MASK(EV_SND); + input_dev->sndbit[0] = BIT_MASK(SND_BELL) | BIT_MASK(SND_TONE); + + input_dev->event = state->event; + + error = input_register_device(input_dev); + if (error) { + input_free_device(input_dev); + return error; + } + + state->input_dev = input_dev; + + return 0; +} + +static void sparcspkr_shutdown(struct platform_device *dev) +{ + struct sparcspkr_state *state = dev_get_drvdata(&dev->dev); + struct input_dev *input_dev = state->input_dev; + + /* turn off the speaker */ + state->event(input_dev, EV_SND, SND_BELL, 0); +} + +static int bbc_beep_probe(struct platform_device *op) +{ + struct sparcspkr_state *state; + struct bbc_beep_info *info; + struct device_node *dp; + int err = -ENOMEM; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + goto out_err; + + state->name = "Sparc BBC Speaker"; + state->event = bbc_spkr_event; + spin_lock_init(&state->lock); + + dp = of_find_node_by_path("/"); + err = -ENODEV; + if (!dp) + goto out_free; + + info = &state->u.bbc; + info->clock_freq = of_getintprop_default(dp, "clock-frequency", 0); + if (!info->clock_freq) + goto out_free; + + info->regs = of_ioremap(&op->resource[0], 0, 6, "bbc beep"); + if (!info->regs) + goto out_free; + + dev_set_drvdata(&op->dev, state); + + err = sparcspkr_probe(&op->dev); + if (err) + goto out_clear_drvdata; + + return 0; + +out_clear_drvdata: + dev_set_drvdata(&op->dev, NULL); + of_iounmap(&op->resource[0], info->regs, 6); + +out_free: + kfree(state); +out_err: + return err; +} + +static int bbc_remove(struct platform_device *op) +{ + struct sparcspkr_state *state = dev_get_drvdata(&op->dev); + struct input_dev *input_dev = state->input_dev; + struct bbc_beep_info *info = &state->u.bbc; + + /* turn off the speaker */ + state->event(input_dev, EV_SND, SND_BELL, 0); + + input_unregister_device(input_dev); + + of_iounmap(&op->resource[0], info->regs, 6); + + dev_set_drvdata(&op->dev, NULL); + kfree(state); + + return 0; +} + +static const struct of_device_id bbc_beep_match[] = { + { + .name = "beep", + .compatible = "SUNW,bbc-beep", + }, + {}, +}; + +static struct platform_driver bbc_beep_driver = { + .driver = { + .name = "bbcbeep", + .owner = THIS_MODULE, + .of_match_table = bbc_beep_match, + }, + .probe = bbc_beep_probe, + .remove = bbc_remove, + .shutdown = sparcspkr_shutdown, +}; + +static int grover_beep_probe(struct platform_device *op) +{ + struct sparcspkr_state *state; + struct grover_beep_info *info; + int err = -ENOMEM; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + goto out_err; + + state->name = "Sparc Grover Speaker"; + state->event = grover_spkr_event; + spin_lock_init(&state->lock); + + info = &state->u.grover; + info->freq_regs = of_ioremap(&op->resource[2], 0, 2, "grover beep freq"); + if (!info->freq_regs) + goto out_free; + + info->enable_reg = of_ioremap(&op->resource[3], 0, 1, "grover beep enable"); + if (!info->enable_reg) + goto out_unmap_freq_regs; + + dev_set_drvdata(&op->dev, state); + + err = sparcspkr_probe(&op->dev); + if (err) + goto out_clear_drvdata; + + return 0; + +out_clear_drvdata: + dev_set_drvdata(&op->dev, NULL); + of_iounmap(&op->resource[3], info->enable_reg, 1); + +out_unmap_freq_regs: + of_iounmap(&op->resource[2], info->freq_regs, 2); +out_free: + kfree(state); +out_err: + return err; +} + +static int grover_remove(struct platform_device *op) +{ + struct sparcspkr_state *state = dev_get_drvdata(&op->dev); + struct grover_beep_info *info = &state->u.grover; + struct input_dev *input_dev = state->input_dev; + + /* turn off the speaker */ + state->event(input_dev, EV_SND, SND_BELL, 0); + + input_unregister_device(input_dev); + + of_iounmap(&op->resource[3], info->enable_reg, 1); + of_iounmap(&op->resource[2], info->freq_regs, 2); + + dev_set_drvdata(&op->dev, NULL); + kfree(state); + + return 0; +} + +static const struct of_device_id grover_beep_match[] = { + { + .name = "beep", + .compatible = "SUNW,smbus-beep", + }, + {}, +}; + +static struct platform_driver grover_beep_driver = { + .driver = { + .name = "groverbeep", + .owner = THIS_MODULE, + .of_match_table = grover_beep_match, + }, + .probe = grover_beep_probe, + .remove = grover_remove, + .shutdown = sparcspkr_shutdown, +}; + +static int __init sparcspkr_init(void) +{ + int err = platform_driver_register(&bbc_beep_driver); + + if (!err) { + err = platform_driver_register(&grover_beep_driver); + if (err) + platform_driver_unregister(&bbc_beep_driver); + } + + return err; +} + +static void __exit sparcspkr_exit(void) +{ + platform_driver_unregister(&bbc_beep_driver); + platform_driver_unregister(&grover_beep_driver); +} + +module_init(sparcspkr_init); +module_exit(sparcspkr_exit); diff --git a/drivers/input/misc/stk3x1x.c b/drivers/input/misc/stk3x1x.c new file mode 100644 index 00000000..a6ed55fb --- /dev/null +++ b/drivers/input/misc/stk3x1x.c @@ -0,0 +1,4262 @@ +/*
+ * stk3x1x.c - Linux kernel modules for sensortek stk301x, stk321x, stk331x
+ * , and stk3410 proximity/ambient light sensor
+ *
+ * Copyright (C) 2012~2015 Lex Hsieh / sensortek <lex_hsieh@sensortek.com.tw>
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/irq.h>
+#include <linux/input.h>
+#include <linux/gpio.h>
+#ifdef CONFIG_HAS_EARLYSUSPEND
+#include <linux/earlysuspend.h>
+#endif
+#include <linux/fs.h>
+#include <asm/uaccess.h>
+//#include <linux/i2c/stk3x1x.h>
+#include <linux/miscdevice.h>
+
+#include <linux/wakelock.h>
+
+#define DRIVER_VERSION "3.10.1 20151217"
+
+/* Driver Settings */
+#define CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+#ifdef CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+#define STK_ALS_CHANGE_THD 5 //10 /* The threshold to trigger ALS interrupt, unit: lux */
+#endif /* #ifdef CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD */
+#define STK_INT_PS_MODE 1 /* 1, 2, or 3 */
+//#define STK_POLL_PS
+#define STK_POLL_ALS /* ALS interrupt is valid only when STK_INT_PS_MODE = 1 or 4*/
+#define STK_TUNE0
+#define CALI_PS_EVERY_TIME
+//#define STK_DEBUG_PRINTF
+#define SPREADTRUM_PLATFORM
+#define STK_ALS_FIR
+//#define STK_IRS
+//#define STK_CHK_REG
+//#define STK_GES
+//#define STK_QUALCOMM_POWER_CTRL
+//#define QUALCOMM_PLATFORM
+
+#if 0
+#ifdef SPREADTRUM_PLATFORM
+#include <linux/i2c/stk3x1x.h>
+#else
+ #include "stk3x1x.h"
+#endif
+#endif
+#include "stk3x1x.h"
+
+/* Define Register Map */
+#define STK_STATE_REG 0x00
+#define STK_PSCTRL_REG 0x01
+#define STK_ALSCTRL_REG 0x02
+#define STK_LEDCTRL_REG 0x03
+#define STK_INT_REG 0x04
+#define STK_WAIT_REG 0x05
+#define STK_THDH1_PS_REG 0x06
+#define STK_THDH2_PS_REG 0x07
+#define STK_THDL1_PS_REG 0x08
+#define STK_THDL2_PS_REG 0x09
+#define STK_THDH1_ALS_REG 0x0A
+#define STK_THDH2_ALS_REG 0x0B
+#define STK_THDL1_ALS_REG 0x0C
+#define STK_THDL2_ALS_REG 0x0D
+#define STK_FLAG_REG 0x10
+#define STK_DATA1_PS_REG 0x11
+#define STK_DATA2_PS_REG 0x12
+#define STK_DATA1_ALS_REG 0x13
+#define STK_DATA2_ALS_REG 0x14
+#define STK_DATA1_OFFSET_REG 0x15
+#define STK_DATA2_OFFSET_REG 0x16
+#define STK_DATA1_IR_REG 0x17
+#define STK_DATA2_IR_REG 0x18
+#define STK_PDT_ID_REG 0x3E
+#define STK_RSRVD_REG 0x3F
+#define STK_SW_RESET_REG 0x80
+
+#define STK_GSCTRL_REG 0x1A
+#define STK_FLAG2_REG 0x1C
+
+/* Define state reg */
+#define STK_STATE_EN_IRS_SHIFT 7
+#define STK_STATE_EN_AK_SHIFT 6
+#define STK_STATE_EN_ASO_SHIFT 5
+#define STK_STATE_EN_IRO_SHIFT 4
+#define STK_STATE_EN_WAIT_SHIFT 2
+#define STK_STATE_EN_ALS_SHIFT 1
+#define STK_STATE_EN_PS_SHIFT 0
+
+#define STK_STATE_EN_IRS_MASK 0x80
+#define STK_STATE_EN_AK_MASK 0x40
+#define STK_STATE_EN_ASO_MASK 0x20
+#define STK_STATE_EN_IRO_MASK 0x10
+#define STK_STATE_EN_WAIT_MASK 0x04
+#define STK_STATE_EN_ALS_MASK 0x02
+#define STK_STATE_EN_PS_MASK 0x01
+
+/* Define PS ctrl reg */
+#define STK_PS_PRS_SHIFT 6
+#define STK_PS_GAIN_SHIFT 4
+#define STK_PS_IT_SHIFT 0
+
+#define STK_PS_PRS_MASK 0xC0
+#define STK_PS_GAIN_MASK 0x30
+#define STK_PS_IT_MASK 0x0F
+
+/* Define ALS ctrl reg */
+#define STK_ALS_PRS_SHIFT 6
+#define STK_ALS_GAIN_SHIFT 4
+#define STK_ALS_IT_SHIFT 0
+
+#define STK_ALS_PRS_MASK 0xC0
+#define STK_ALS_GAIN_MASK 0x30
+#define STK_ALS_IT_MASK 0x0F
+
+/* Define LED ctrl reg */
+#define STK_LED_IRDR_SHIFT 6
+#define STK_LED_DT_SHIFT 0
+
+#define STK_LED_IRDR_MASK 0xC0
+#define STK_LED_DT_MASK 0x3F
+
+/* Define interrupt reg */
+#define STK_INT_CTRL_SHIFT 7
+#define STK_INT_OUI_SHIFT 4
+#define STK_INT_ALS_SHIFT 3
+#define STK_INT_PS_SHIFT 0
+
+#define STK_INT_CTRL_MASK 0x80
+#define STK_INT_OUI_MASK 0x10
+#define STK_INT_ALS_MASK 0x08
+#define STK_INT_PS_MASK 0x07
+
+#define STK_INT_ALS 0x08
+
+/* Define flag reg */
+#define STK_FLG_ALSDR_SHIFT 7
+#define STK_FLG_PSDR_SHIFT 6
+#define STK_FLG_ALSINT_SHIFT 5
+#define STK_FLG_PSINT_SHIFT 4
+#define STK_FLG_OUI_SHIFT 2
+#define STK_FLG_IR_RDY_SHIFT 1
+#define STK_FLG_NF_SHIFT 0
+
+#define STK_FLG_ALSDR_MASK 0x80
+#define STK_FLG_PSDR_MASK 0x40
+#define STK_FLG_ALSINT_MASK 0x20
+#define STK_FLG_PSINT_MASK 0x10
+#define STK_FLG_OUI_MASK 0x04
+#define STK_FLG_IR_RDY_MASK 0x02
+#define STK_FLG_NF_MASK 0x01
+
+/* Define flag2 reg */
+#define STK_FLG2_INT_GS_SHIFT 6
+#define STK_FLG2_GS10_SHIFT 5
+#define STK_FLG2_GS01_SHIFT 4
+
+#define STK_FLG2_INT_GS_MASK 0x40
+#define STK_FLG2_GS10_MASK 0x20
+#define STK_FLG2_GS01_MASK 0x10
+
+
+/* misc define */
+#define MIN_ALS_POLL_DELAY_NS 60000000
+
+
+#ifdef STK_TUNE0
+ #define STK_MAX_MIN_DIFF 150 //200
+ #define STK_LT_N_CT 50 //100
+ #define STK_HT_N_CT 80 //150
+#endif /* #ifdef STK_TUNE0 */
+
+#define STK_IRC_MAX_ALS_CODE 20000
+#define STK_IRC_MIN_ALS_CODE 25
+#define STK_IRC_MIN_IR_CODE 50
+#define STK_IRC_ALS_DENOMI 2
+#define STK_IRC_ALS_NUMERA 5
+#define STK_IRC_ALS_CORREC 850
+
+#define STK_IRS_IT_REDUCE 2
+#define STK_ALS_READ_IRS_IT_REDUCE 5
+#define STK_ALS_THRESHOLD 30
+
+#define DEVICE_NAME "stk_ps"
+#define ALS_NAME "lightsensor-level"
+#define PS_NAME "proximity"
+
+#ifdef STK_QUALCOMM_POWER_CTRL
+ /* POWER SUPPLY VOLTAGE RANGE */
+ #define STK3X1X_VDD_MIN_UV 2000000
+ #define STK3X1X_VDD_MAX_UV 3300000
+ #define STK3X1X_VIO_MIN_UV 1750000
+ #define STK3X1X_VIO_MAX_UV 1950000
+#endif
+
+#define STK3310SA_PID 0x17
+#define STK3311SA_PID 0x1E
+#define STK3311WV_PID 0x1D
+
+#ifdef SPREADTRUM_PLATFORM
+extern int sprd_3rdparty_gpio_pls_irq;
+
+static struct stk3x1x_platform_data stk3x1x_pfdata={
+ .state_reg = 0x0, /* disable all */
+ .psctrl_reg = 0x31, /* ps_persistance=1, ps_gain=64X, PS_IT=0.391ms */
+ .alsctrl_reg = 0x39, /* als_persistance=1, als_gain=64X, ALS_IT=100ms */
+ .ledctrl_reg = 0xFF, /* 100mA IRDR, 64/64 LED duty */
+ .wait_reg = 0xF, /* 100 ms */
+ .ps_thd_h =1700,
+ .ps_thd_l = 1500,
+ .int_pin = 216,//GPIO_PROX_INT//sprd_3rdparty_gpio_pls_irq
+ .transmittance = 3100, //500
+};
+#endif
+
+#ifdef STK_ALS_FIR
+ #define STK_FIR_LEN 4 //6 //8
+ #define MAX_FIR_LEN 32
+struct data_filter {
+ u16 raw[MAX_FIR_LEN];
+ int sum;
+ int number;
+ int idx;
+};
+#endif
+
+#ifdef STK_GES
+union stk_ges_operation{
+ uint8_t ops[4];
+ struct {
+ uint8_t rw_len_retry;
+ uint8_t reg;
+ uint8_t reg_value_retry_crit;
+ uint8_t sleep_10ns;
+ }action;
+};
+
+union stk_ges_operation stk_ges_op[10] =
+{
+ {.ops={0xc1, 0x24, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}},
+ {.ops={0, 0, 0, 0}}
+};
+#endif
+
+struct stk3x1x_data {
+ struct i2c_client *client;
+ struct stk3x1x_platform_data *pdata;
+#if (!defined(STK_POLL_PS) || !defined(STK_POLL_ALS))
+ int32_t irq;
+ struct work_struct stk_work;
+ struct workqueue_struct *stk_wq;
+#endif
+ uint16_t ir_code;
+ uint16_t als_correct_factor;
+ uint8_t alsctrl_reg;
+ uint8_t psctrl_reg;
+ uint8_t ledctrl_reg;
+ uint8_t state_reg;
+ int int_pin;
+ uint8_t wait_reg;
+ uint8_t int_reg;
+#ifdef CONFIG_HAS_EARLYSUSPEND
+ //struct early_suspend stk_early_suspend;
+#endif
+ uint16_t ps_thd_h;
+ uint16_t ps_thd_l;
+#ifdef CALI_PS_EVERY_TIME
+ uint16_t ps_high_thd_boot;
+ uint16_t ps_low_thd_boot;
+#endif
+ struct mutex io_lock;
+ struct input_dev *ps_input_dev;
+ int32_t ps_distance_last;
+ bool ps_enabled;
+ bool re_enable_ps;
+ struct wake_lock ps_wakelock;
+#ifdef STK_POLL_PS
+ struct hrtimer ps_timer;
+ struct work_struct stk_ps_work;
+ struct workqueue_struct *stk_ps_wq;
+ struct wake_lock ps_nosuspend_wl;
+#endif
+ struct input_dev *als_input_dev;
+ int32_t als_lux_last;
+ uint32_t als_transmittance;
+ bool als_enabled;
+ bool re_enable_als;
+ ktime_t ps_poll_delay;
+ ktime_t als_poll_delay;
+#ifdef STK_POLL_ALS
+ struct work_struct stk_als_work;
+ struct hrtimer als_timer;
+ struct workqueue_struct *stk_als_wq;
+#endif
+ bool first_boot;
+#ifdef STK_TUNE0
+ uint16_t psa;
+ uint16_t psi;
+ uint16_t psi_set;
+ struct hrtimer ps_tune0_timer;
+ struct workqueue_struct *stk_ps_tune0_wq;
+ struct work_struct stk_ps_tune0_work;
+ ktime_t ps_tune0_delay;
+ bool tune_zero_init_proc;
+ uint32_t ps_stat_data[3];
+ int data_count;
+ int stk_max_min_diff;
+ int stk_lt_n_ct;
+ int stk_ht_n_ct;
+#endif
+#ifdef STK_ALS_FIR
+ struct data_filter fir;
+ atomic_t firlength;
+#endif
+ atomic_t recv_reg;
+
+#ifdef STK_GES
+ struct input_dev *ges_input_dev;
+ int ges_enabled;
+ int re_enable_ges;
+ atomic_t gesture2;
+#endif
+#ifdef STK_IRS
+ int als_data_index;
+#endif
+#ifdef STK_QUALCOMM_POWER_CTRL
+ struct regulator *vdd;
+ struct regulator *vio;
+ bool power_enabled;
+#endif
+ uint8_t pid;
+ uint8_t p_wv_r_bd_with_co;
+ uint32_t als_code_last;
+};
+
+#if( !defined(CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD))
+static uint32_t lux_threshold_table[] =
+{
+ 3,
+ 10,
+ 40,
+ 65,
+ 145,
+ 300,
+ 550,
+ 930,
+ 1250,
+ 1700,
+};
+
+#define LUX_THD_TABLE_SIZE (sizeof(lux_threshold_table)/sizeof(uint32_t)+1)
+static uint16_t code_threshold_table[LUX_THD_TABLE_SIZE+1];
+#endif
+
+static int32_t stk3x1x_enable_ps(struct stk3x1x_data *ps_data, uint8_t enable, uint8_t validate_reg);
+static int32_t stk3x1x_enable_als(struct stk3x1x_data *ps_data, uint8_t enable);
+static int32_t stk3x1x_set_ps_thd_l(struct stk3x1x_data *ps_data, uint16_t thd_l);
+static int32_t stk3x1x_set_ps_thd_h(struct stk3x1x_data *ps_data, uint16_t thd_h);
+static int32_t stk3x1x_set_als_thd_l(struct stk3x1x_data *ps_data, uint16_t thd_l);
+static int32_t stk3x1x_set_als_thd_h(struct stk3x1x_data *ps_data, uint16_t thd_h);
+static int32_t stk3x1x_get_ir_reading(struct stk3x1x_data *ps_data, int32_t als_it_reduce);
+#ifdef STK_TUNE0
+static int stk_ps_tune_zero_func_fae(struct stk3x1x_data *ps_data);
+#endif
+#ifdef STK_CHK_REG
+static int stk3x1x_validate_n_handle(struct i2c_client *client);
+#endif
+static int stk_ps_val(struct stk3x1x_data *ps_data);
+#ifdef STK_QUALCOMM_POWER_CTRL
+static int stk3x1x_device_ctl(struct stk3x1x_data *ps_data, bool enable);
+#endif
+
+static int stk3x1x_i2c_read_data(struct i2c_client *client, unsigned char command, int length, unsigned char *values)
+{
+ uint8_t retry;
+ int err;
+ struct i2c_msg msgs[] =
+ {
+ {
+ .addr = client->addr,
+ .flags = 0,
+ .len = 1,
+ .buf = &command,
+ },
+ {
+ .addr = client->addr,
+ .flags = I2C_M_RD,
+ .len = length,
+ .buf = values,
+ },
+ };
+
+ for (retry = 0; retry < 5; retry++)
+ {
+ err = i2c_transfer(client->adapter, msgs, 2);
+ if (err == 2)
+ break;
+ else
+ mdelay(5);
+ }
+ //printk("stk3x1x_i2c_read_data");
+
+ if (retry >= 5)
+ {
+ printk(KERN_ERR "%s: i2c read fail, err=%d\n", __func__, err);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int stk3x1x_i2c_write_data(struct i2c_client *client, unsigned char command, int length, unsigned char *values)
+{
+ int retry;
+ int err;
+ unsigned char data[11];
+ struct i2c_msg msg;
+ int index;
+ //printk("stk3x1x_i2c_write_data");
+
+ if (!client)
+ return -EINVAL;
+ else if (length >= 10)
+ {
+ printk(KERN_ERR "%s:length %d exceeds 10\n", __func__, length);
+ return -EINVAL;
+ }
+
+ data[0] = command;
+ for (index=1;index<=length;index++)
+ data[index] = values[index-1];
+
+ msg.addr = client->addr;
+ msg.flags = 0;
+ msg.len = length+1;
+ msg.buf = data;
+
+ for (retry = 0; retry < 5; retry++)
+ {
+ err = i2c_transfer(client->adapter, &msg, 1);
+ if (err == 1)
+ break;
+ else
+ mdelay(5);
+ }
+
+ if (retry >= 5)
+ {
+ printk(KERN_ERR "%s: i2c write fail, err=%d\n", __func__, err);
+ return -EIO;
+ }
+ return 0;
+}
+
+static int stk3x1x_i2c_smbus_read_byte_data(struct i2c_client *client, unsigned char command)
+{
+ unsigned char value;
+ int err;
+ err = stk3x1x_i2c_read_data(client, command, 1, &value);
+ if(err < 0)
+ return err;
+ return value;
+}
+
+static int stk3x1x_i2c_smbus_write_byte_data(struct i2c_client *client, unsigned char command, unsigned char value)
+{
+ int err;
+ err = stk3x1x_i2c_write_data(client, command, 1, &value);
+ return err;
+}
+
+uint32_t stk_alscode2lux(struct stk3x1x_data *ps_data, uint32_t alscode)
+{
+ alscode += ((alscode<<7)+(alscode<<3)+(alscode>>1));
+ alscode<<=3;
+ alscode/=ps_data->als_transmittance;
+ return alscode;
+}
+
+uint32_t stk_lux2alscode(struct stk3x1x_data *ps_data, uint32_t lux)
+{
+ lux*=ps_data->als_transmittance;
+ lux/=1100;
+ if (unlikely(lux>=(1<<16)))
+ lux = (1<<16) -1;
+ return lux;
+}
+
+#ifndef CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+static void stk_init_code_threshold_table(struct stk3x1x_data *ps_data)
+{
+ uint32_t i,j;
+ uint32_t alscode;
+
+ code_threshold_table[0] = 0;
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "alscode[0]=%d\n",0);
+#endif
+ for (i=1,j=0;i<LUX_THD_TABLE_SIZE;i++,j++)
+ {
+ alscode = stk_lux2alscode(ps_data, lux_threshold_table[j]);
+ printk(KERN_INFO "alscode[%d]=%d\n",i,alscode);
+ code_threshold_table[i] = (uint16_t)(alscode);
+ }
+ code_threshold_table[i] = 0xffff;
+ printk(KERN_INFO "alscode[%d]=%d\n",i,alscode);
+}
+
+static uint32_t stk_get_lux_interval_index(uint16_t alscode)
+{
+ uint32_t i;
+ for (i=1;i<=LUX_THD_TABLE_SIZE;i++)
+ {
+ if ((alscode>=code_threshold_table[i-1])&&(alscode<code_threshold_table[i]))
+ {
+ return i;
+ }
+ }
+ return LUX_THD_TABLE_SIZE;
+}
+#else
+void stk_als_set_new_thd(struct stk3x1x_data *ps_data, uint16_t alscode)
+{
+ int32_t high_thd,low_thd;
+ high_thd = alscode + stk_lux2alscode(ps_data, STK_ALS_CHANGE_THD);
+ low_thd = alscode - stk_lux2alscode(ps_data, STK_ALS_CHANGE_THD);
+ if (high_thd >= (1<<16))
+ high_thd = (1<<16) -1;
+ if (low_thd <0)
+ low_thd = 0;
+ stk3x1x_set_als_thd_h(ps_data, (uint16_t)high_thd);
+ stk3x1x_set_als_thd_l(ps_data, (uint16_t)low_thd);
+}
+#endif // CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+
+
+static void stk3x1x_proc_plat_data(struct stk3x1x_data *ps_data, struct stk3x1x_platform_data *plat_data)
+{
+ uint8_t w_reg;
+
+ ps_data->state_reg = plat_data->state_reg;
+ ps_data->psctrl_reg = plat_data->psctrl_reg;
+#ifdef STK_POLL_PS
+ ps_data->psctrl_reg &= 0x3F;
+#endif
+ ps_data->alsctrl_reg = plat_data->alsctrl_reg;
+ ps_data->ledctrl_reg = plat_data->ledctrl_reg;
+ if(ps_data->pid == STK3310SA_PID || ps_data->pid == STK3311SA_PID)
+ ps_data->ledctrl_reg &= 0x3F;
+
+ ps_data->wait_reg = plat_data->wait_reg;
+ if(ps_data->wait_reg < 2)
+ {
+ printk(KERN_WARNING "%s: wait_reg should be larger than 2, force to write 2\n", __func__);
+ ps_data->wait_reg = 2;
+ }
+ else if (ps_data->wait_reg > 0xFF)
+ {
+ printk(KERN_WARNING "%s: wait_reg should be less than 0xFF, force to write 0xFF\n", __func__);
+ ps_data->wait_reg = 0xFF;
+ }
+//#ifndef STK_TUNE0
+ if(ps_data->ps_thd_h == 0 && ps_data->ps_thd_l == 0)
+ {
+ ps_data->ps_thd_h = plat_data->ps_thd_h;
+ ps_data->ps_thd_l = plat_data->ps_thd_l;
+ }
+//#endif
+#ifdef CALI_PS_EVERY_TIME
+ ps_data->ps_high_thd_boot = plat_data->ps_thd_h;
+ ps_data->ps_low_thd_boot = plat_data->ps_thd_l;
+#endif
+ w_reg = 0;
+#ifndef STK_POLL_PS
+ w_reg |= STK_INT_PS_MODE;
+#else
+ w_reg |= 0x01;
+#endif
+
+#if (!defined(STK_POLL_ALS) && (STK_INT_PS_MODE != 0x02) && (STK_INT_PS_MODE != 0x03))
+ w_reg |= STK_INT_ALS;
+#endif
+ ps_data->int_reg = w_reg;
+ return;
+}
+
+static int32_t stk3x1x_init_all_reg(struct stk3x1x_data *ps_data)
+{
+ int32_t ret;
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_STATE_REG, ps_data->state_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_PSCTRL_REG, ps_data->psctrl_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_ALSCTRL_REG, ps_data->alsctrl_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_LEDCTRL_REG, ps_data->ledctrl_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_WAIT_REG, ps_data->wait_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+#ifdef STK_TUNE0
+ ps_data->psa = 0x0;
+ ps_data->psi = 0xFFFF;
+#endif
+//#else
+ stk3x1x_set_ps_thd_h(ps_data, ps_data->ps_thd_h);
+ stk3x1x_set_ps_thd_l(ps_data, ps_data->ps_thd_l);
+//#endif
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_INT_REG, ps_data->int_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ /*
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, 0x87, 0x60);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ */
+
+ return 0;
+}
+
+static int32_t stk3x1x_read_otp25(struct stk3x1x_data *ps_data)
+{
+ int32_t ret, otp25;
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, 0x0, 0x2);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, 0x90, 0x25);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, 0x92, 0x82);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ usleep_range(1000, 5000);
+
+ ret = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,0x91);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+ }
+ otp25 = ret;
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, 0x0, 0x0);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ printk(KERN_INFO "%s: otp25=0x%x\n", __func__, otp25);
+ if(otp25 & 0x80)
+ return 1;
+ return 0;
+}
+
+static int32_t stk3x1x_check_pid(struct stk3x1x_data *ps_data)
+{
+ unsigned char value[2], pid_msb;
+ int err;
+
+ ps_data->p_wv_r_bd_with_co = 0;
+
+ err = stk3x1x_i2c_read_data(ps_data->client, STK_PDT_ID_REG, 2, &value[0]);
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, err);
+ return err;
+ }
+
+ printk(KERN_INFO "%s: PID=0x%x, RID=0x%x\n", __func__, value[0], value[1]);
+ ps_data->pid = value[0];
+
+ if(value[0] == STK3311WV_PID)
+ ps_data->p_wv_r_bd_with_co |= 0b100;
+ if(value[1] == 0xC3)
+ ps_data->p_wv_r_bd_with_co |= 0b010;
+
+ if(stk3x1x_read_otp25(ps_data) == 1)
+ {
+ ps_data->p_wv_r_bd_with_co |= 0b001;
+ }
+ printk(KERN_INFO "%s: p_wv_r_bd_with_co = 0x%x\n", __func__, ps_data->p_wv_r_bd_with_co);
+
+ if(value[0] == 0)
+ {
+ printk(KERN_ERR "PID=0x0, please make sure the chip is stk3x1x!\n");
+ return -2;
+ }
+
+ pid_msb = value[0] & 0xF0;
+ switch(pid_msb)
+ {
+ case 0x10:
+ case 0x20:
+ case 0x30:
+ return 0;
+ default:
+ printk(KERN_ERR "%s: invalid PID(%#x)\n", __func__, value[0]);
+ return -1;
+ }
+ return 0;
+}
+
+
+static int32_t stk3x1x_software_reset(struct stk3x1x_data *ps_data)
+{
+ int32_t r;
+ uint8_t w_reg;
+
+ w_reg = 0x7F;
+ r = stk3x1x_i2c_smbus_write_byte_data(ps_data->client,STK_WAIT_REG,w_reg);
+ if (r<0)
+ {
+ printk(KERN_ERR "%s: software reset: write i2c error, ret=%d\n", __func__, r);
+ return r;
+ }
+ r = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_WAIT_REG);
+ if (w_reg != r)
+ {
+ printk(KERN_ERR "%s: software reset: read-back value is not the same\n", __func__);
+ return -1;
+ }
+
+ r = stk3x1x_i2c_smbus_write_byte_data(ps_data->client,STK_SW_RESET_REG,0);
+ if (r<0)
+ {
+ printk(KERN_ERR "%s: software reset: read error after reset\n", __func__);
+ return r;
+ }
+ usleep_range(13000, 15000);
+ return 0;
+}
+
+
+static int32_t stk3x1x_set_als_thd_l(struct stk3x1x_data *ps_data, uint16_t thd_l)
+{
+ unsigned char val[2];
+ int ret;
+ val[0] = (thd_l & 0xFF00) >> 8;
+ val[1] = thd_l & 0x00FF;
+ ret = stk3x1x_i2c_write_data(ps_data->client, STK_THDL1_ALS_REG, 2, val);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+static int32_t stk3x1x_set_als_thd_h(struct stk3x1x_data *ps_data, uint16_t thd_h)
+{
+ unsigned char val[2];
+ int ret;
+ val[0] = (thd_h & 0xFF00) >> 8;
+ val[1] = thd_h & 0x00FF;
+ ret = stk3x1x_i2c_write_data(ps_data->client, STK_THDH1_ALS_REG, 2, val);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static int32_t stk3x1x_set_ps_thd_l(struct stk3x1x_data *ps_data, uint16_t thd_l)
+{
+ unsigned char val[2];
+ int ret;
+ val[0] = (thd_l & 0xFF00) >> 8;
+ val[1] = thd_l & 0x00FF;
+ ret = stk3x1x_i2c_write_data(ps_data->client, STK_THDL1_PS_REG, 2, val);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+static int32_t stk3x1x_set_ps_thd_h(struct stk3x1x_data *ps_data, uint16_t thd_h)
+{
+ unsigned char val[2];
+ int ret;
+ val[0] = (thd_h & 0xFF00) >> 8;
+ val[1] = thd_h & 0x00FF;
+ ret = stk3x1x_i2c_write_data(ps_data->client, STK_THDH1_PS_REG, 2, val);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static uint32_t stk3x1x_get_ps_reading(struct stk3x1x_data *ps_data)
+{
+ unsigned char value[2];
+ int err;
+ err = stk3x1x_i2c_read_data(ps_data->client, STK_DATA1_PS_REG, 2, &value[0]);
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, err);
+ return err;
+ }
+ return ((value[0]<<8) | value[1]);
+}
+
+
+static int32_t stk3x1x_set_flag(struct stk3x1x_data *ps_data, uint8_t org_flag_reg, uint8_t clr)
+{
+ uint8_t w_flag;
+ int ret;
+
+ w_flag = org_flag_reg | (STK_FLG_ALSINT_MASK | STK_FLG_PSINT_MASK | STK_FLG_OUI_MASK | STK_FLG_IR_RDY_MASK);
+ w_flag &= (~clr);
+ //printk(KERN_INFO "%s: org_flag_reg=0x%x, w_flag = 0x%x\n", __func__, org_flag_reg, w_flag);
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client,STK_FLAG_REG, w_flag);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static int32_t stk3x1x_get_flag(struct stk3x1x_data *ps_data)
+{
+ int ret;
+ ret = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_FLAG_REG);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+#ifdef STK_GES
+static int32_t stk3x1x_set_flag2(struct stk3x1x_data *ps_data, uint8_t org_flag2_reg, uint8_t clr)
+{
+ uint8_t w_flag2;
+ int ret;
+
+ w_flag2 = org_flag2_reg | 0x72;
+ w_flag2 &= (~clr);
+ //printk(KERN_INFO "%s: org_flag2_reg=0x%x, w_flag2 = 0x%x\n", __func__, org_flag2_reg, w_flag2);
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client,STK_FLAG2_REG, w_flag2);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static int32_t stk3x1x_get_flag2(struct stk3x1x_data *ps_data)
+{
+ int ret;
+ ret = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_FLAG2_REG);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+#endif
+
+static int32_t stk3x1x_set_state(struct stk3x1x_data *ps_data, uint8_t state)
+{
+ int ret;
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client,STK_STATE_REG, state);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static int32_t stk3x1x_get_state(struct stk3x1x_data *ps_data)
+{
+ int ret;
+ ret = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_STATE_REG);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+#ifdef STK_GES
+static int32_t stk3x1x_set_gsctrl(struct stk3x1x_data *ps_data, uint8_t state)
+{
+ int ret;
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client,STK_GSCTRL_REG, state);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+static int32_t stk3x1x_get_gsctrl(struct stk3x1x_data *ps_data)
+{
+ int ret;
+ ret = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_GSCTRL_REG);
+ if(ret < 0)
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, ret);
+ return ret;
+}
+
+#if 0
+static uint32_t stk3x1x_get_ges_reading(struct stk3x1x_data *ps_data, unsigned int *ges0,
+ unsigned int *ges1, unsigned int *ges2)
+{
+ int retry, len, no = 0;
+ uint8_t reg_val_crit;
+ int err;
+ unsigned char value[10];
+
+ while(stk_ges_op[no].ops[1] != 0)
+ {
+ retry = stk_ges_op[no].action.rw_len_retry & 0x0F;
+ len = (stk_ges_op[no].action.rw_len_retry & 0x70) >> 4;
+ reg_val_crit = stk_ges_op[no].action.reg_value_retry_crit;
+ if(stk_ges_op[no].action.rw_len_retry & 0x80)
+ {
+ while(retry != 0)
+ {
+ err = stk3x1x_i2c_read_data(ps_data->client,
+ stk_ges_op[no].action.reg, len, value);
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, err);
+ return err;
+ }
+ if(reg_val_crit)
+ {
+ if(value[0] & reg_val_crit)
+ break;
+ }
+ if(stk_ges_op[no].action.sleep_10ns != 0)
+ usleep_range(stk_ges_op[no].action.sleep_10ns*10,
+ stk_ges_op[no].action.sleep_10ns*10+300);
+ retry--;
+ }
+ }
+ else
+ {
+ while(retry != 0)
+ {
+ err = stk3x1x_i2c_write_data(ps_data->client, stk_ges_op[no].action.reg,
+ len, ®_val_crit);
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: fail, err=%d\n", __func__, err);
+ return err;
+ }
+
+ if(stk_ges_op[no].action.sleep_10ns != 0)
+ usleep_range(stk_ges_op[no].action.sleep_10ns*10,
+ stk_ges_op[no].action.sleep_10ns*10+300);
+ retry--;
+ }
+ }
+
+ if(stk_ges_op[no].action.reg == 0x24)
+ {
+ *ges0 = (value[0]<<8) | value[1];
+ *ges1 = (value[2]<<8) | value[3];
+ }
+ else if(stk_ges_op[no].action.reg == stk_ges_op[9].ops[0])
+ {
+ *ges2 = (value[0]<<8) | value[1];
+ }
+
+ no++;
+ }
+ return 0;
+}
+
+#else
+static uint32_t stk3x1x_get_ges_reading(struct stk3x1x_data *ps_data, unsigned int *ges0,unsigned int *ges1,unsigned int *ges2)
+{
+ unsigned char value[4];
+ int err, retry = 10;
+
+ do {
+ err = stk3x1x_get_flag(ps_data);
+ if(err < 0)
+ return err;
+ if(err & STK_FLG_PSDR_MASK)
+ break;
+ //printk(KERN_INFO "ges: not ready, %d\n", retry);
+ retry--;
+ usleep_range(350, 1000);
+ } while(retry > 0);
+ err = stk3x1x_i2c_read_data(ps_data->client, STK_DATA1_PS_REG, 2, &value[0]);
+
+ err = stk3x1x_i2c_read_data(ps_data->client, 0x24, 4, &value[0]);
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, err);
+ return err;
+ }
+ *ges0 = (value[0]<<8) | value[1];
+ *ges1 = (value[2]<<8) | value[3];
+ //printk(KERN_INFO "%s: ges=%d,%d\n",__func__, *ges0, *ges1);
+ return 0;
+}
+#endif
+
+static int32_t stk3x1x_enable_ges(struct stk3x1x_data *ps_data, uint8_t enable, uint8_t mode)
+{
+ int32_t ret;
+ uint8_t w_state_reg, gsctrl_reg;
+ uint8_t org_mode = 0;
+
+ if(ps_data->ps_enabled)
+ {
+ printk(KERN_INFO "%s: since PS is enabled, ges is disabled\n", __func__);
+ ps_data->re_enable_ges = enable;
+ return 0;
+ }
+
+ if(enable == ps_data->ges_enabled)
+ return 0;
+
+ if(enable)
+ {
+#ifdef STK_QUALCOMM_POWER_CTRL
+ ret = stk3x1x_device_ctl(ps_data, enable);
+ if (ret)
+ return ret;
+#endif
+ if(ps_data->als_enabled)
+ {
+ printk(KERN_INFO "%s: force disable ALS\n", __func__);
+ stk3x1x_enable_als(ps_data, 0);
+ ps_data->re_enable_als = true;
+ }
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_WAIT_REG, 0);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_INT_REG, 0);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ w_state_reg = STK_STATE_EN_WAIT_MASK | STK_STATE_EN_PS_MASK;
+ ret = stk3x1x_set_state(ps_data, w_state_reg);
+ if(ret < 0)
+ return ret;
+ ps_data->state_reg = w_state_reg;
+
+ if(mode == 2)
+ {
+ ret = stk3x1x_get_gsctrl(ps_data);
+ if(ret < 0)
+ return ret;
+ gsctrl_reg = ret & 0xF3;
+#ifdef STK_POLL_PS
+ gsctrl_reg |= 0x04;
+#else
+ gsctrl_reg |= 0x0C;
+#endif
+ ret = stk3x1x_set_gsctrl(ps_data, gsctrl_reg);
+ if(ret < 0)
+ return ret;
+#ifdef STK_POLL_PS
+ hrtimer_start(&ps_data->ps_timer, ps_data->ps_poll_delay, HRTIMER_MODE_REL);
+#else
+ enable_irq(ps_data->irq);
+#endif
+ }
+ ps_data->ges_enabled = mode;
+ }
+ else
+ {
+ org_mode = ps_data->ges_enabled;
+ if(org_mode == 2)
+ {
+#ifdef STK_POLL_PS
+ hrtimer_cancel(&ps_data->ps_timer);
+ cancel_work_sync(&ps_data->stk_ps_work);
+#else
+ disable_irq(ps_data->irq);
+#endif
+ }
+
+ ret = stk3x1x_set_state(ps_data, 0);
+ if(ret < 0)
+ return ret;
+ ps_data->state_reg = 0;
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_WAIT_REG, ps_data->wait_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_INT_REG, ps_data->int_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ if(org_mode == 2)
+ {
+ ret = stk3x1x_get_gsctrl(ps_data);
+ if(ret < 0)
+ return ret;
+ gsctrl_reg = ret & (~0x0C);
+ ret = stk3x1x_set_gsctrl(ps_data, gsctrl_reg);
+ if(ret < 0)
+ return ret;
+ }
+ ps_data->ges_enabled = 0;
+ if(ps_data->re_enable_als)
+ {
+ printk(KERN_INFO "%s: re-enable ALS\n", __func__);
+ stk3x1x_enable_als(ps_data, 1);
+ ps_data->re_enable_als = false;
+ }
+#ifdef STK_QUALCOMM_POWER_CTRL
+ ret = stk3x1x_device_ctl(ps_data, enable);
+ if (ret)
+ return ret;
+#endif
+ }
+
+ return 0;
+}
+#endif /* #ifdef STK_GES */
+
+static int32_t stk3x1x_enable_ps(struct stk3x1x_data *ps_data, uint8_t enable, uint8_t validate_reg)
+{
+ int32_t ret;
+ uint8_t w_state_reg;
+ uint8_t curr_ps_enable;
+ uint32_t reading;
+ int32_t near_far_state;
+
+#ifdef STK_QUALCOMM_POWER_CTRL
+ if (enable) {
+ ret = stk3x1x_device_ctl(ps_data, enable);
+ if (ret)
+ return ret;
+ }
+#endif
+
+#ifdef STK_CHK_REG
+ if(validate_reg)
+ {
+ ret = stk3x1x_validate_n_handle(ps_data->client);
+ if(ret < 0)
+ printk(KERN_ERR "stk3x1x_validate_n_handle fail: %d\n", ret);
+ }
+#endif /* #ifdef STK_CHK_REG */
+
+#ifdef STK_GES
+ if(ps_data->ges_enabled && enable)
+ {
+ printk(KERN_INFO "%s: force disable ges\n", __func__);
+ stk3x1x_enable_ges(ps_data, 0, 1);
+ ps_data->re_enable_ges = 1;
+ }
+#endif
+ curr_ps_enable = ps_data->ps_enabled?1:0;
+ if(curr_ps_enable == enable)
+ return 0;
+
+#ifdef STK_TUNE0
+ if (!(ps_data->psi_set) && !enable)
+ {
+ hrtimer_cancel(&ps_data->ps_tune0_timer);
+ cancel_work_sync(&ps_data->stk_ps_tune0_work);
+ }
+#endif
+ if(ps_data->first_boot == true)
+ {
+ ps_data->first_boot = false;
+ }
+
+ ret = stk3x1x_get_state(ps_data);
+ if(ret < 0)
+ return ret;
+ w_state_reg = ret;
+
+
+ w_state_reg &= ~(STK_STATE_EN_PS_MASK | STK_STATE_EN_WAIT_MASK | STK_STATE_EN_AK_MASK);
+ if(enable)
+ {
+ w_state_reg |= STK_STATE_EN_PS_MASK;
+ if(!(ps_data->als_enabled))
+ w_state_reg |= STK_STATE_EN_WAIT_MASK;
+ }
+ ret = stk3x1x_set_state(ps_data, w_state_reg);
+ if(ret < 0)
+ return ret;
+ ps_data->state_reg = w_state_reg;
+
+ if(enable)
+ {
+#ifdef STK_TUNE0
+ #ifdef CALI_PS_EVERY_TIME
+ ps_data->psi_set = 0;
+ ps_data->psa = 0;
+ ps_data->psi = 0xFFFF;
+ #ifndef QUALCOMM_PLATFORM
+ ps_data->ps_thd_h = ps_data->ps_high_thd_boot;
+ ps_data->ps_thd_l = ps_data->ps_low_thd_boot;
+ #endif
+ hrtimer_start(&ps_data->ps_tune0_timer, ps_data->ps_tune0_delay, HRTIMER_MODE_REL);
+ #else
+
+ if (!(ps_data->psi_set))
+ hrtimer_start(&ps_data->ps_tune0_timer, ps_data->ps_tune0_delay, HRTIMER_MODE_REL);
+ #endif /* #ifdef CALI_PS_EVERY_TIME */
+ stk3x1x_set_ps_thd_h(ps_data, ps_data->ps_thd_h);
+ stk3x1x_set_ps_thd_l(ps_data, ps_data->ps_thd_l);
+#endif
+ printk(KERN_INFO "%s: HT=%d,LT=%d\n", __func__, ps_data->ps_thd_h, ps_data->ps_thd_l);
+#ifdef STK_POLL_PS
+ hrtimer_start(&ps_data->ps_timer, ps_data->ps_poll_delay, HRTIMER_MODE_REL);
+ ps_data->ps_distance_last = -1;
+#endif
+#ifndef STK_POLL_PS
+ #ifndef STK_POLL_ALS
+ if(!(ps_data->als_enabled))
+ #endif /* #ifndef STK_POLL_ALS */
+ enable_irq(ps_data->irq);
+#endif /* #ifndef STK_POLL_PS */
+ ps_data->ps_enabled = true;
+#ifdef STK_CHK_REG
+ if(!validate_reg)
+ {
+ ps_data->ps_distance_last = 1;
+ input_report_abs(ps_data->ps_input_dev, ABS_DISTANCE, 1);
+ input_sync(ps_data->ps_input_dev);
+ wake_lock_timeout(&ps_data->ps_wakelock, 3*HZ);
+ reading = stk3x1x_get_ps_reading(ps_data);
+ printk(KERN_INFO "%s: force report ps input event=1, ps code = %d\n",__func__, reading);
+ }
+ else
+#endif /* #ifdef STK_CHK_REG */
+ {
+ usleep_range(4000, 5000);
+ ret = stk3x1x_get_flag(ps_data);
+ if (ret < 0)
+ return ret;
+ near_far_state = ret & STK_FLG_NF_MASK;
+ ps_data->ps_distance_last = near_far_state;
+ input_report_abs(ps_data->ps_input_dev, ABS_DISTANCE, near_far_state);
+ input_sync(ps_data->ps_input_dev);
+ wake_lock_timeout(&ps_data->ps_wakelock, 3*HZ);
+ reading = stk3x1x_get_ps_reading(ps_data);
+ printk(KERN_INFO "%s: ps input event=%d, ps code = %d\n",__func__, near_far_state, reading);
+ }
+ }
+ else
+ {
+#ifdef STK_POLL_PS
+ hrtimer_cancel(&ps_data->ps_timer);
+ cancel_work_sync(&ps_data->stk_ps_work);
+#else
+#ifndef STK_POLL_ALS
+ if(!(ps_data->als_enabled))
+#endif
+ disable_irq(ps_data->irq);
+#endif
+ ps_data->ps_enabled = false;
+#ifdef STK_GES
+ if(ps_data->re_enable_ges)
+ {
+ printk(KERN_INFO "%s: re-enable ges\n", __func__);
+ stk3x1x_enable_ges(ps_data, 1, 1);
+ ps_data->re_enable_ges = 0;
+ }
+#endif
+#ifdef STK_QUALCOMM_POWER_CTRL
+ ret = stk3x1x_device_ctl(ps_data, enable);
+ if (ret)
+ return ret;
+#endif
+ }
+ return ret;
+}
+
+static int32_t stk3x1x_enable_als(struct stk3x1x_data *ps_data, uint8_t enable)
+{
+ int32_t ret;
+ uint8_t w_state_reg;
+ uint8_t curr_als_enable = (ps_data->als_enabled)?1:0;
+
+#ifdef STK_GES
+ if(ps_data->ges_enabled)
+ {
+ printk(KERN_INFO "%s: since ges is enabled, ALS is disabled\n", __func__);
+ ps_data->re_enable_als = enable ? true : false;
+ return 0;
+ }
+#endif /* #ifdef STK_GES */
+ if(curr_als_enable == enable)
+ return 0;
+#ifdef STK_QUALCOMM_POWER_CTRL
+ if (enable) {
+ ret = stk3x1x_device_ctl(ps_data, enable);
+ if (ret)
+ return ret;
+ }
+#endif
+#ifndef STK_POLL_ALS
+ #ifdef STK_IRS
+ if(enable && !(ps_data->ps_enabled))
+ {
+ ret = stk3x1x_get_ir_reading(ps_data, STK_IRS_IT_REDUCE );
+ if(ret > 0)
+ ps_data->ir_code = ret;
+ }
+ #endif
+
+ if (enable)
+ {
+ stk3x1x_set_als_thd_h(ps_data, 0x0000);
+ stk3x1x_set_als_thd_l(ps_data, 0xFFFF);
+ }
+#endif
+
+ ret = stk3x1x_get_state(ps_data);
+ if(ret < 0)
+ return ret;
+
+ w_state_reg = (uint8_t)(ret & (~(STK_STATE_EN_ALS_MASK | STK_STATE_EN_WAIT_MASK)));
+ if(enable)
+ w_state_reg |= STK_STATE_EN_ALS_MASK;
+ else if (ps_data->ps_enabled)
+ w_state_reg |= STK_STATE_EN_WAIT_MASK;
+
+ ret = stk3x1x_set_state(ps_data, w_state_reg);
+ if(ret < 0)
+ return ret;
+ ps_data->state_reg = w_state_reg;
+
+ if (enable)
+ {
+ ps_data->als_enabled = true;
+#ifdef STK_POLL_ALS
+ hrtimer_start(&ps_data->als_timer, ps_data->als_poll_delay, HRTIMER_MODE_REL);
+#else
+#ifndef STK_POLL_PS
+ if(!(ps_data->ps_enabled))
+#endif
+ enable_irq(ps_data->irq);
+#endif
+#ifdef STK_IRS
+ ps_data->als_data_index = 0;
+#endif
+ }
+ else
+ {
+ ps_data->als_enabled = false;
+#ifdef STK_POLL_ALS
+ hrtimer_cancel(&ps_data->als_timer);
+ cancel_work_sync(&ps_data->stk_als_work);
+#else
+#ifndef STK_POLL_PS
+ if(!(ps_data->ps_enabled))
+#endif
+ disable_irq(ps_data->irq);
+#endif
+#ifdef STK_QUALCOMM_POWER_CTRL
+ ret = stk3x1x_device_ctl(ps_data, enable);
+ if (ret)
+ return ret;
+#endif
+ }
+ return ret;
+}
+
+static int32_t stk3x1x_get_als_reading(struct stk3x1x_data *ps_data)
+{
+ int32_t als_data, ir_data = 0;
+#ifdef STK_ALS_FIR
+ int index;
+ int firlen = atomic_read(&ps_data->firlength);
+#endif
+ unsigned char value[2];
+ int ret;
+ const int ir_enlarge = 1 << (STK_ALS_READ_IRS_IT_REDUCE - STK_IRS_IT_REDUCE);
+
+ ret = stk3x1x_i2c_read_data(ps_data->client, STK_DATA1_ALS_REG, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ als_data = (value[0]<<8) | value[1];
+ // printk("%s: raw als_data=%d\n", __func__, als_data);
+ if(ps_data->p_wv_r_bd_with_co & 0b010)
+ {
+ if(als_data < STK_ALS_THRESHOLD && ps_data->als_code_last > 10000)
+ {
+ ir_data = stk3x1x_get_ir_reading(ps_data, STK_ALS_READ_IRS_IT_REDUCE);
+#ifdef STK_IRS
+ if(ir_data > 0)
+ ps_data->ir_code = ir_data * ir_enlarge;
+#endif
+ // printk(KERN_INFO "%s: als_data=%d, als_code_last=%d,ir_data=%d\n",
+ // __func__, als_data, ps_data->als_code_last, ir_data);
+ if(ir_data > (STK_ALS_THRESHOLD*3))
+ {
+ als_data = ps_data->als_code_last;
+ }
+ }
+#ifdef STK_IRS
+ else
+ {
+ ps_data->ir_code = 0;
+ }
+#endif
+ }
+
+ ps_data->als_code_last = als_data;
+
+#ifdef STK_ALS_FIR
+ if(ps_data->fir.number < firlen)
+ {
+ ps_data->fir.raw[ps_data->fir.number] = als_data;
+ ps_data->fir.sum += als_data;
+ ps_data->fir.number++;
+ ps_data->fir.idx++;
+ }
+ else
+ {
+ index = ps_data->fir.idx % firlen;
+ ps_data->fir.sum -= ps_data->fir.raw[index];
+ ps_data->fir.raw[index] = als_data;
+ ps_data->fir.sum += als_data;
+ ps_data->fir.idx++;
+ als_data = ps_data->fir.sum/firlen;
+ }
+#endif
+
+ return als_data;
+}
+
+
+#if (defined(STK_IRS) && defined(STK_POLL_ALS))
+static int stk_als_ir_skip_als(struct stk3x1x_data *ps_data)
+{
+ int ret;
+ unsigned char value[2];
+
+ if(ps_data->als_data_index < 60000)
+ ps_data->als_data_index++;
+ else
+ ps_data->als_data_index = 0;
+
+ if( ps_data->als_data_index % 10 == 1)
+ {
+ ret = stk3x1x_i2c_read_data(ps_data->client, STK_DATA1_ALS_REG, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static void stk_als_ir_get_corr(struct stk3x1x_data *ps_data, int32_t als)
+{
+ int32_t als_comperator;
+
+ if(ps_data->ir_code)
+ {
+ ps_data->als_correct_factor = 1000;
+ if(als < STK_IRC_MAX_ALS_CODE && als > STK_IRC_MIN_ALS_CODE &&
+ ps_data->ir_code > STK_IRC_MIN_IR_CODE)
+ {
+ als_comperator = als * STK_IRC_ALS_NUMERA / STK_IRC_ALS_DENOMI;
+ if(ps_data->ir_code > als_comperator)
+ ps_data->als_correct_factor = STK_IRC_ALS_CORREC;
+ }
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: als=%d, ir=%d, als_correct_factor=%d", __func__,
+ als, ps_data->ir_code, ps_data->als_correct_factor);
+#endif
+ ps_data->ir_code = 0;
+ }
+ return;
+}
+
+static int stk_als_ir_run(struct stk3x1x_data *ps_data)
+{
+ int ret;
+
+ if( ps_data->als_data_index % 10 == 0)
+ {
+ if(ps_data->ps_distance_last != 0 && ps_data->ir_code == 0)
+ {
+ ret = stk3x1x_get_ir_reading(ps_data, STK_IRS_IT_REDUCE);
+ if(ret > 0)
+ ps_data->ir_code = ret;
+ }
+ return ret;
+ }
+ return 0;
+}
+#endif /* #if (defined(STK_IRS) && defined(STK_POLL_ALS)) */
+
+
+static int32_t stk3x1x_set_irs_it_slp(struct stk3x1x_data *ps_data, uint16_t *slp_time, int32_t ials_it_reduce)
+{
+ uint8_t irs_alsctrl;
+ int32_t ret;
+
+ irs_alsctrl = (ps_data->alsctrl_reg & 0x0F) - ials_it_reduce;
+ switch(irs_alsctrl)
+ {
+ case 2:
+ *slp_time = 1;
+ break;
+ case 3:
+ *slp_time = 2;
+ break;
+ case 4:
+ *slp_time = 3;
+ break;
+ case 5:
+ *slp_time = 6;
+ break;
+ case 6:
+ *slp_time = 12;
+ break;
+ case 7:
+ *slp_time = 24;
+ break;
+ case 8:
+ *slp_time = 48;
+ break;
+ case 9:
+ *slp_time = 96;
+ break;
+ case 10:
+ *slp_time = 192;
+ break;
+ default:
+ printk(KERN_ERR "%s: unknown ALS IT=0x%x\n", __func__, irs_alsctrl);
+ ret = -EINVAL;
+ return ret;
+ }
+ irs_alsctrl |= (ps_data->alsctrl_reg & 0xF0);
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_ALSCTRL_REG, irs_alsctrl);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ return 0;
+}
+
+static int32_t stk3x1x_get_ir_reading(struct stk3x1x_data *ps_data, int32_t als_it_reduce)
+{
+ int32_t word_data, ret;
+ uint8_t w_reg, retry = 0;
+ uint16_t irs_slp_time = 100;
+ unsigned char value[2];
+
+ ret = stk3x1x_set_irs_it_slp(ps_data, &irs_slp_time, als_it_reduce);
+ if(ret < 0)
+ goto irs_err_i2c_rw;
+
+ ret = stk3x1x_get_state(ps_data);
+ if(ret < 0)
+ goto irs_err_i2c_rw;
+
+ w_reg = ret | STK_STATE_EN_IRS_MASK;
+ ret = stk3x1x_set_state(ps_data, w_reg);
+ if(ret < 0)
+ goto irs_err_i2c_rw;
+ msleep(irs_slp_time);
+
+ do
+ {
+ usleep_range(3000, 4000);
+ ret = stk3x1x_get_flag(ps_data);
+ if (ret < 0)
+ goto irs_err_i2c_rw;
+ retry++;
+ }while(retry < 10 && ((ret&STK_FLG_IR_RDY_MASK) == 0));
+
+ if(retry == 10)
+ {
+ printk(KERN_ERR "%s: ir data is not ready for a long time\n", __func__);
+ ret = -EINVAL;
+ goto irs_err_i2c_rw;
+ }
+
+ ret = stk3x1x_set_flag(ps_data, ret, STK_FLG_IR_RDY_MASK);
+ if (ret < 0)
+ goto irs_err_i2c_rw;
+
+ ret = stk3x1x_i2c_read_data(ps_data->client, STK_DATA1_IR_REG, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ goto irs_err_i2c_rw;
+ }
+ word_data = ((value[0]<<8) | value[1]);
+ //printk(KERN_INFO "%s: ir=%d\n", __func__, word_data);
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_ALSCTRL_REG, ps_data->alsctrl_reg );
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ goto irs_err_i2c_rw;
+ }
+
+ return word_data;
+
+irs_err_i2c_rw:
+ return ret;
+}
+
+#ifdef STK_CHK_REG
+static int stk3x1x_chk_reg_valid(struct stk3x1x_data *ps_data)
+{
+ unsigned char value[9];
+ int err;
+ /*
+ uint8_t cnt;
+
+ for(cnt=0;cnt<9;cnt++)
+ {
+ value[cnt] = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, (cnt+1));
+ if(value[cnt] < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=%d", __func__, value[cnt]);
+ return value[cnt];
+ }
+ }
+ */
+ err = stk3x1x_i2c_read_data(ps_data->client, STK_PSCTRL_REG, 9, &value[0]);
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: fail, ret=%d\n", __func__, err);
+ return err;
+ }
+
+ if(value[0] != ps_data->psctrl_reg)
+ {
+ printk(KERN_ERR "%s: invalid reg 0x01=0x%2x\n", __func__, value[0]);
+ return 0xFF;
+ }
+#ifdef STK_IRS
+ if((value[1] != ps_data->alsctrl_reg) && (value[1] != (ps_data->alsctrl_reg - STK_IRS_IT_REDUCE))
+ && (value[1] != (ps_data->alsctrl_reg - STK_ALS_READ_IRS_IT_REDUCE)))
+#else
+ if((value[1] != ps_data->alsctrl_reg) && (value[1] != (ps_data->alsctrl_reg - STK_ALS_READ_IRS_IT_REDUCE)))
+#endif
+ {
+ printk(KERN_ERR "%s: invalid reg 0x02=0x%2x\n", __func__, value[1]);
+ return 0xFF;
+ }
+ if(value[2] != ps_data->ledctrl_reg)
+ {
+ printk(KERN_ERR "%s: invalid reg 0x03=0x%2x\n", __func__, value[2]);
+ return 0xFF;
+ }
+ if(value[3] != ps_data->int_reg)
+ {
+ printk(KERN_ERR "%s: invalid reg 0x04=0x%2x\n", __func__, value[3]);
+ return 0xFF;
+ }
+ if(value[4] != ps_data->wait_reg)
+ {
+ printk(KERN_ERR "%s: invalid reg 0x05=0x%2x\n", __func__, value[4]);
+ return 0xFF;
+ }
+ if(value[5] != ((ps_data->ps_thd_h & 0xFF00) >> 8))
+ {
+ printk(KERN_ERR "%s: invalid reg 0x06=0x%2x\n", __func__, value[5]);
+ return 0xFF;
+ }
+ if(value[6] != (ps_data->ps_thd_h & 0x00FF))
+ {
+ printk(KERN_ERR "%s: invalid reg 0x07=0x%2x\n", __func__, value[6]);
+ return 0xFF;
+ }
+ if(value[7] != ((ps_data->ps_thd_l & 0xFF00) >> 8))
+ {
+ printk(KERN_ERR "%s: invalid reg 0x08=0x%2x\n", __func__, value[7]);
+ return 0xFF;
+ }
+ if(value[8] != (ps_data->ps_thd_l & 0x00FF))
+ {
+ printk(KERN_ERR "%s: invalid reg 0x09=0x%2x\n", __func__, value[8]);
+ return 0xFF;
+ }
+
+ return 0;
+}
+
+static int stk3x1x_validate_n_handle(struct i2c_client *client)
+{
+ struct stk3x1x_data *ps_data = i2c_get_clientdata(client);
+ int err;
+
+ err = stk3x1x_chk_reg_valid(ps_data);
+ if(err < 0)
+ {
+ printk(KERN_ERR "stk3x1x_chk_reg_valid fail: %d\n", err);
+ return err;
+ }
+
+ if(err == 0xFF)
+ {
+ printk(KERN_ERR "%s: Re-init chip\n", __func__);
+ err = stk3x1x_software_reset(ps_data);
+ if(err < 0)
+ return err;
+ err = stk3x1x_init_all_reg(ps_data);
+ if(err < 0)
+ return err;
+
+ //ps_data->psa = 0;
+ //ps_data->psi = 0xFFFF;
+ stk3x1x_set_ps_thd_h(ps_data, ps_data->ps_thd_h);
+ stk3x1x_set_ps_thd_l(ps_data, ps_data->ps_thd_l);
+#ifdef STK_ALS_FIR
+ memset(&ps_data->fir, 0x00, sizeof(ps_data->fir));
+#endif
+ return 0xFF;
+ }
+ return 0;
+}
+#endif /* #ifdef STK_CHK_REG */
+
+static ssize_t stk_als_code_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t reading;
+#ifdef STK_POLL_ALS
+ reading = ps_data->als_code_last;
+#else
+ unsigned char value[2];
+ int ret;
+ ret = stk3x1x_i2c_read_data(ps_data->client, STK_DATA1_ALS_REG, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ reading = (value[0]<<8) | value[1];
+#endif
+ return scnprintf(buf, PAGE_SIZE, "%d\n", reading);
+}
+
+
+static ssize_t stk_als_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t ret;
+
+ ret = stk3x1x_get_state(ps_data);
+ if(ret < 0)
+ return ret;
+ ret = (ret & STK_STATE_EN_ALS_MASK)?1:0;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t stk_als_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ uint8_t en;
+ if (sysfs_streq(buf, "1"))
+ en = 1;
+ else if (sysfs_streq(buf, "0"))
+ en = 0;
+ else
+ {
+ printk(KERN_ERR "%s, invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "%s: Enable ALS : %d\n", __func__, en);
+ mutex_lock(&ps_data->io_lock);
+ stk3x1x_enable_als(ps_data, en);
+ mutex_unlock(&ps_data->io_lock);
+ return size;
+}
+
+static ssize_t stk_als_lux_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t als_reading;
+ uint32_t als_lux;
+ als_reading = stk3x1x_get_als_reading(ps_data);
+ als_lux = stk_alscode2lux(ps_data, als_reading);
+ return scnprintf(buf, PAGE_SIZE, "%d lux\n", als_lux);
+}
+
+static ssize_t stk_als_lux_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+ ret = kstrtoul(buf, 16, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ ps_data->als_lux_last = value;
+ input_report_abs(ps_data->als_input_dev, ABS_MISC, value);
+ input_sync(ps_data->als_input_dev);
+ printk(KERN_INFO "%s: als input event %ld lux\n",__func__, value);
+
+ return size;
+}
+
+
+static ssize_t stk_als_transmittance_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t transmittance;
+ transmittance = ps_data->als_transmittance;
+ return scnprintf(buf, PAGE_SIZE, "%d\n", transmittance);
+}
+
+
+static ssize_t stk_als_transmittance_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+ ret = kstrtoul(buf, 10, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ ps_data->als_transmittance = value;
+ return size;
+}
+
+static ssize_t stk_als_delay_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int64_t delay;
+ mutex_lock(&ps_data->io_lock);
+ delay = ktime_to_ns(ps_data->als_poll_delay);
+ mutex_unlock(&ps_data->io_lock);
+ return scnprintf(buf, PAGE_SIZE, "%lld\n", delay);
+}
+
+
+static ssize_t stk_als_delay_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ uint64_t value = 0;
+ int ret;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ ret = kstrtoull(buf, 10, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoull failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: set als poll delay=%lld\n", __func__, value);
+#endif
+ if(value < MIN_ALS_POLL_DELAY_NS)
+ {
+ printk(KERN_ERR "%s: delay is too small\n", __func__);
+ value = MIN_ALS_POLL_DELAY_NS;
+ }
+ mutex_lock(&ps_data->io_lock);
+ if(value != ktime_to_ns(ps_data->als_poll_delay))
+ ps_data->als_poll_delay = ns_to_ktime(value);
+#ifdef STK_ALS_FIR
+ ps_data->fir.number = 0;
+ ps_data->fir.idx = 0;
+ ps_data->fir.sum = 0;
+#endif
+ mutex_unlock(&ps_data->io_lock);
+ return size;
+}
+
+static ssize_t stk_als_ir_code_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t reading;
+ reading = stk3x1x_get_ir_reading(ps_data, STK_IRS_IT_REDUCE);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", reading);
+}
+
+#ifdef STK_ALS_FIR
+static ssize_t stk_als_firlen_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int len = atomic_read(&ps_data->firlength);
+
+ printk(KERN_INFO "%s: len = %2d, idx = %2d\n", __func__, len, ps_data->fir.idx);
+ printk(KERN_INFO "%s: sum = %5d, ave = %5d\n", __func__, ps_data->fir.sum, ps_data->fir.sum/len);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", len);
+}
+
+
+static ssize_t stk_als_firlen_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ uint64_t value = 0;
+ int ret;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ ret = kstrtoull(buf, 10, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoull failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+
+ if(value > MAX_FIR_LEN)
+ {
+ printk(KERN_ERR "%s: firlen exceed maximum filter length\n", __func__);
+ }
+ else if (value < 1)
+ {
+ atomic_set(&ps_data->firlength, 1);
+ memset(&ps_data->fir, 0x00, sizeof(ps_data->fir));
+ }
+ else
+ {
+ atomic_set(&ps_data->firlength, value);
+ memset(&ps_data->fir, 0x00, sizeof(ps_data->fir));
+ }
+ return size;
+}
+#endif /* #ifdef STK_ALS_FIR */
+
+#ifdef STK_GES
+static ssize_t stk_ges_code_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int ret;
+ unsigned int gest0 = 0, gest1 = 0, gest2 = 0;
+
+ if(!ps_data->ges_enabled)
+ return 0;
+
+ ret = stk3x1x_get_ges_reading(ps_data, &gest0, &gest1, &gest2);
+ if(ret < 0)
+ return ret;
+ else if(ret == 0xFFFF)
+ atomic_set(&ps_data->gesture2, 0);
+
+ return scnprintf(buf, PAGE_SIZE, "%5d,%5d,%5d\n", gest0, gest1, atomic_read(&ps_data->gesture2));
+}
+
+static ssize_t stk_ges_code_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ uint8_t ges;
+ unsigned long value = 0;
+ int ret;
+
+ ret = kstrtoul(buf, 16, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=%d\n", __func__, ret);
+ return ret;
+ }
+
+ switch(value)
+ {
+ case 3:
+ //printk(KERN_INFO "%s: ges input event, not detected\n",__func__);
+ case 0:
+ return size;
+ case 1:
+ ges = KEY_PAGEUP;
+ atomic_set(&ps_data->gesture2, 0);
+ printk(KERN_INFO "%s: ges input event >>>\n",__func__);
+ break;
+ case 2:
+ ges = KEY_PAGEDOWN;
+ atomic_set(&ps_data->gesture2, 0);
+ printk(KERN_INFO "%s: ges input event <<<\n",__func__);
+ break;
+ case 32:
+ ges = KEY_VOLUMEDOWN;
+ printk(KERN_INFO "%s: ges input event near\n",__func__);
+ break;
+ case 48:
+ ges = KEY_VOLUMEUP;
+ printk(KERN_INFO "%s: ges input event far\n",__func__);
+ break;
+ default:
+ printk(KERN_ERR "%s, invalid value %d\n", __func__, (int)value);
+ return -EINVAL;
+ }
+
+ input_report_key(ps_data->ges_input_dev, ges, 1);
+ input_report_key(ps_data->ges_input_dev, ges, 0);
+ input_sync(ps_data->ges_input_dev);
+ return size;
+}
+
+static ssize_t stk_ges_poll_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int len = 0, ii = 0, jj = 0;
+
+ while(stk_ges_op[ii].ops[0] != 0)
+ {
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%x ", ii);
+ for(jj=0;jj<4;jj++)
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%x ", stk_ges_op[ii].ops[jj]);
+ len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+ ii++;
+ }
+ return len;
+}
+
+
+static ssize_t stk_ges_poll_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ int32_t ret, i = 0, index = 0;
+ char *token;
+ unsigned long value = 0;
+
+ while(buf != '\0')
+ {
+ token = strsep((char **)&buf, " ");
+ if((ret = kstrtoul(token, 16, &value)) < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+
+ if(i == 0)
+ {
+ if(value >= 10)
+ {
+ memset(stk_ges_op, 0, sizeof(stk_ges_op));
+ break;
+ }
+ else
+ index = value;
+ }
+ else
+ {
+ stk_ges_op[index].ops[i-1] = value;
+ }
+ i++;
+ if(i == 5)
+ break;
+ }
+ if(i != 5)
+ {
+ printk(KERN_ERR "%s: invalid length(%d)\n", __func__, i);
+ memset(&(stk_ges_op[index]), 0, sizeof(union stk_ges_operation));
+ }
+ return size;
+}
+
+static ssize_t stk_ges_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=%d\n", __func__, ret);
+ return ret;
+ }
+ printk(KERN_INFO "%s: Enable GES : %d\n", __func__, (int)value);
+
+ switch(value)
+ {
+ case 0:
+ mutex_lock(&ps_data->io_lock);
+ if(ps_data->ges_enabled == 1)
+ stk3x1x_enable_ges(ps_data, 0, 1);
+ else
+ stk3x1x_enable_ges(ps_data, 0, 2);
+ mutex_unlock(&ps_data->io_lock);
+ break;
+ case 1:
+ mutex_lock(&ps_data->io_lock);
+ stk3x1x_enable_ges(ps_data, 1, 1);
+ mutex_unlock(&ps_data->io_lock);
+ break;
+ case 2:
+ mutex_lock(&ps_data->io_lock);
+ stk3x1x_enable_ges(ps_data, 1, 2);
+ mutex_unlock(&ps_data->io_lock);
+ break;
+ default:
+ printk(KERN_ERR "%s, invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ break;
+ }
+
+ return size;
+}
+
+static ssize_t stk_ges_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ps_data->ges_enabled);
+}
+
+#endif /* #ifdef STK_GES */
+
+static ssize_t stk_ps_code_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ uint32_t reading;
+ reading = stk3x1x_get_ps_reading(ps_data);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", reading);
+}
+
+static ssize_t stk_ps_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int32_t ret;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+
+ ret = stk3x1x_get_state(ps_data);
+ if(ret < 0)
+ return ret;
+ ret = (ret & STK_STATE_EN_PS_MASK)?1:0;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t stk_ps_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ uint8_t en;
+ if (sysfs_streq(buf, "1"))
+ en = 1;
+ else if (sysfs_streq(buf, "0"))
+ en = 0;
+ else
+ {
+ printk(KERN_ERR "%s, invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "%s: Enable PS : %d\n", __func__, en);
+ mutex_lock(&ps_data->io_lock);
+ stk3x1x_enable_ps(ps_data, en, 1);
+ mutex_unlock(&ps_data->io_lock);
+ return size;
+}
+
+static ssize_t stk_ps_enable_aso_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int32_t ret;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+
+ ret = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_STATE_REG);
+ ret = (ret & STK_STATE_EN_ASO_MASK)?1:0;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t stk_ps_enable_aso_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ uint8_t en;
+ int32_t ret;
+ uint8_t w_state_reg;
+
+ if (sysfs_streq(buf, "1"))
+ en = 1;
+ else if (sysfs_streq(buf, "0"))
+ en = 0;
+ else
+ {
+ printk(KERN_ERR "%s, invalid value %d\n", __func__, *buf);
+ return -EINVAL;
+ }
+ printk(KERN_INFO "%s: Enable PS ASO : %d\n", __func__, en);
+
+ ret = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, STK_STATE_REG);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ w_state_reg = (uint8_t)(ret & (~STK_STATE_EN_ASO_MASK));
+ if(en)
+ w_state_reg |= STK_STATE_EN_ASO_MASK;
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_STATE_REG, w_state_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ return size;
+}
+
+
+static ssize_t stk_ps_offset_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t word_data;
+ unsigned char value[2];
+ int ret;
+
+ ret = stk3x1x_i2c_read_data(ps_data->client, STK_DATA1_OFFSET_REG, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ word_data = (value[0]<<8) | value[1];
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", word_data);
+}
+
+static ssize_t stk_ps_offset_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long offset = 0;
+ int ret;
+ unsigned char val[2];
+
+ ret = kstrtoul(buf, 10, &offset);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ if(offset > 65535)
+ {
+ printk(KERN_ERR "%s: invalid value, offset=%ld\n", __func__, offset);
+ return -EINVAL;
+ }
+
+ val[0] = (offset & 0xFF00) >> 8;
+ val[1] = offset & 0x00FF;
+ ret = stk3x1x_i2c_write_data(ps_data->client, STK_DATA1_OFFSET_REG, 2, val);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ return size;
+}
+
+
+static ssize_t stk_ps_distance_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t dist=1, ret;
+
+ ret = stk3x1x_get_flag(ps_data);
+ if(ret < 0)
+ return ret;
+ dist = (ret & STK_FLG_NF_MASK)?1:0;
+
+ ps_data->ps_distance_last = dist;
+ input_report_abs(ps_data->ps_input_dev, ABS_DISTANCE, dist);
+ input_sync(ps_data->ps_input_dev);
+ wake_lock_timeout(&ps_data->ps_wakelock, 3*HZ);
+ printk(KERN_INFO "%s: ps input event %d cm\n",__func__, dist);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", dist);
+}
+
+
+static ssize_t stk_ps_distance_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+ ret = kstrtoul(buf, 10, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ ps_data->ps_distance_last = value;
+ input_report_abs(ps_data->ps_input_dev, ABS_DISTANCE, value);
+ input_sync(ps_data->ps_input_dev);
+ wake_lock_timeout(&ps_data->ps_wakelock, 3*HZ);
+ printk(KERN_INFO "%s: ps input event %ld cm\n",__func__, value);
+ return size;
+}
+
+
+static ssize_t stk_ps_code_thd_l_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int32_t ps_thd_l1_reg, ps_thd_l2_reg;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ ps_thd_l1_reg = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_THDL1_PS_REG);
+ if(ps_thd_l1_reg < 0)
+ {
+ printk(KERN_ERR "%s fail, err=0x%x", __func__, ps_thd_l1_reg);
+ return -EINVAL;
+ }
+ ps_thd_l2_reg = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_THDL2_PS_REG);
+ if(ps_thd_l2_reg < 0)
+ {
+ printk(KERN_ERR "%s fail, err=0x%x", __func__, ps_thd_l2_reg);
+ return -EINVAL;
+ }
+ ps_thd_l1_reg = ps_thd_l1_reg<<8 | ps_thd_l2_reg;
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ps_thd_l1_reg);
+}
+
+
+static ssize_t stk_ps_code_thd_l_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+ ret = kstrtoul(buf, 10, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ stk3x1x_set_ps_thd_l(ps_data, value);
+ return size;
+}
+
+static ssize_t stk_ps_code_thd_h_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int32_t ps_thd_h1_reg, ps_thd_h2_reg;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ ps_thd_h1_reg = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_THDH1_PS_REG);
+ if(ps_thd_h1_reg < 0)
+ {
+ printk(KERN_ERR "%s fail, err=0x%x", __func__, ps_thd_h1_reg);
+ return -EINVAL;
+ }
+ ps_thd_h2_reg = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,STK_THDH2_PS_REG);
+ if(ps_thd_h2_reg < 0)
+ {
+ printk(KERN_ERR "%s fail, err=0x%x", __func__, ps_thd_h2_reg);
+ return -EINVAL;
+ }
+ ps_thd_h1_reg = ps_thd_h1_reg<<8 | ps_thd_h2_reg;
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ps_thd_h1_reg);
+}
+
+
+static ssize_t stk_ps_code_thd_h_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+ ret = kstrtoul(buf, 10, &value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ stk3x1x_set_ps_thd_h(ps_data, value);
+ return size;
+}
+
+
+static ssize_t stk_all_reg_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int32_t ps_reg[0x22];
+ uint8_t cnt;
+ int len = 0;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+
+ for(cnt=0;cnt<0x20;cnt++)
+ {
+ ps_reg[cnt] = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, (cnt));
+ if(ps_reg[cnt] < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=%d", __func__, ps_reg[cnt]);
+ return -EINVAL;
+ }
+ else
+ {
+ printk(KERN_INFO "reg[0x%2X]=0x%2X\n", cnt, ps_reg[cnt]);
+ len += scnprintf(buf+len, PAGE_SIZE-len, "[%2X]%2X,", cnt, ps_reg[cnt]);
+ }
+ }
+ ps_reg[cnt] = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, STK_PDT_ID_REG);
+ if(ps_reg[cnt] < 0)
+ {
+ printk( KERN_ERR "%s fail, ret=%d", __func__, ps_reg[cnt]);
+ return -EINVAL;
+ }
+ printk( KERN_INFO "reg[0x%x]=0x%2X\n", STK_PDT_ID_REG, ps_reg[cnt]);
+ cnt++;
+ ps_reg[cnt] = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, STK_RSRVD_REG);
+ if(ps_reg[cnt] < 0)
+ {
+ printk( KERN_ERR "%s fail, ret=%d", __func__, ps_reg[cnt]);
+ return -EINVAL;
+ }
+ printk( KERN_INFO "reg[0x%x]=0x%2X\n", STK_RSRVD_REG, ps_reg[cnt]);
+ len += scnprintf(buf+len, PAGE_SIZE-len, "[%2X]%2X,[%2X]%2X\n", cnt-1, ps_reg[cnt-1], cnt, ps_reg[cnt]);
+ return len;
+/*
+ return scnprintf(buf, PAGE_SIZE, "[0]%2X [1]%2X [2]%2X [3]%2X [4]%2X [5]%2X [6/7 HTHD]%2X,%2X [8/9 LTHD]%2X, %2X [A]%2X [B]%2X [C]%2X [D]%2X [E/F Aoff]%2X,%2X,[10]%2X [11/12 PS]%2X,%2X [13]%2X [14]%2X [15/16 Foff]%2X,%2X [17]%2X [18]%2X [3E]%2X [3F]%2X\n",
+ ps_reg[0], ps_reg[1], ps_reg[2], ps_reg[3], ps_reg[4], ps_reg[5], ps_reg[6], ps_reg[7], ps_reg[8],
+ ps_reg[9], ps_reg[10], ps_reg[11], ps_reg[12], ps_reg[13], ps_reg[14], ps_reg[15], ps_reg[16], ps_reg[17],
+ ps_reg[18], ps_reg[19], ps_reg[20], ps_reg[21], ps_reg[22], ps_reg[23], ps_reg[24], ps_reg[25], ps_reg[26]);
+ */
+}
+
+static ssize_t stk_status_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ int32_t ps_reg[27];
+ uint8_t cnt;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ for(cnt=0;cnt<25;cnt++)
+ {
+ ps_reg[cnt] = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, (cnt));
+ if(ps_reg[cnt] < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=%d", __func__, ps_reg[cnt]);
+ return -EINVAL;
+ }
+ else
+ {
+ printk(KERN_INFO "reg[0x%2X]=0x%2X\n", cnt, ps_reg[cnt]);
+ }
+ }
+ ps_reg[cnt] = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, STK_PDT_ID_REG);
+ if(ps_reg[cnt] < 0)
+ {
+ printk( KERN_ERR "%s fail, ret=%d", __func__, ps_reg[cnt]);
+ return -EINVAL;
+ }
+ printk( KERN_INFO "reg[0x%x]=0x%2X\n", STK_PDT_ID_REG, ps_reg[cnt]);
+ cnt++;
+ ps_reg[cnt] = stk3x1x_i2c_smbus_read_byte_data(ps_data->client, STK_RSRVD_REG);
+ if(ps_reg[cnt] < 0)
+ {
+ printk( KERN_ERR "%s fail, ret=%d", __func__, ps_reg[cnt]);
+ return -EINVAL;
+ }
+ printk( KERN_INFO "reg[0x%x]=0x%2X\n", STK_RSRVD_REG, ps_reg[cnt]);
+
+ return scnprintf(buf, PAGE_SIZE, "[PS=%2X] [ALS=%2X] [WAIT=0x%4Xms] [EN_ASO=%2X] [EN_AK=%2X] [NEAR/FAR=%2X] [FLAG_OUI=%2X] [FLAG_PSINT=%2X] [FLAG_ALSINT=%2X]\n",
+ ps_reg[0]&0x01,(ps_reg[0]&0x02)>>1,((ps_reg[0]&0x04)>>2)*ps_reg[5]*6,(ps_reg[0]&0x20)>>5,
+ (ps_reg[0]&0x40)>>6,ps_reg[16]&0x01,(ps_reg[16]&0x04)>>2,(ps_reg[16]&0x10)>>4,(ps_reg[16]&0x20)>>5);
+}
+
+static ssize_t stk_recv_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "0x%04X\n", atomic_read(&ps_data->recv_reg));
+}
+
+
+static ssize_t stk_recv_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ unsigned long value = 0;
+ int ret;
+ int32_t recv_data;
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+
+ if((ret = kstrtoul(buf, 16, &value)) < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ recv_data = stk3x1x_i2c_smbus_read_byte_data(ps_data->client,value);
+// printk("%s: reg 0x%x=0x%x\n", __func__, (int)value, recv_data);
+ atomic_set(&ps_data->recv_reg, recv_data);
+ return size;
+}
+
+
+static ssize_t stk_send_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ return 0;
+}
+
+
+static ssize_t stk_send_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ int addr, cmd;
+ int32_t ret, i;
+ char *token[10];
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+
+ for (i = 0; i < 2; i++)
+ token[i] = strsep((char **)&buf, " ");
+ if((ret = kstrtoul(token[0], 16, (unsigned long *)&(addr))) < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ if((ret = kstrtoul(token[1], 16, (unsigned long *)&(cmd))) < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ printk(KERN_INFO "%s: write reg 0x%x=0x%x\n", __func__, addr, cmd);
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, (unsigned char)addr, (unsigned char)cmd);
+ if (0 != ret)
+ {
+ printk(KERN_ERR "%s: stk3x1x_i2c_smbus_write_byte_data fail\n", __func__);
+ return ret;
+ }
+
+ return size;
+}
+
+#ifdef STK_TUNE0
+
+static ssize_t stk_ps_cali_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ int32_t word_data;
+ unsigned char value[2];
+ int ret;
+
+ ret = stk3x1x_i2c_read_data(ps_data->client, 0x20, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ word_data = (value[0]<<8) | value[1];
+
+ ret = stk3x1x_i2c_read_data(ps_data->client, 0x22, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ word_data += ((value[0]<<8) | value[1]);
+
+ printk("%s: psi_set=%d, psa=%d,psi=%d, word_data=%d\n", __func__,
+ ps_data->psi_set, ps_data->psa, ps_data->psi, word_data);
+#ifdef CALI_PS_EVERY_TIME
+ printk("%s: boot HT=%d, LT=%d\n", __func__, ps_data->ps_high_thd_boot, ps_data->ps_low_thd_boot);
+#endif
+ return 0;
+}
+
+static ssize_t stk_ps_maxdiff_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+
+ if((ret = kstrtoul(buf, 10, &value)) < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ ps_data->stk_max_min_diff = (int) value;
+ return size;
+}
+
+
+static ssize_t stk_ps_maxdiff_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ps_data->stk_max_min_diff);
+}
+
+static ssize_t stk_ps_ltnct_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+
+ if((ret = kstrtoul(buf, 10, &value)) < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ ps_data->stk_lt_n_ct = (int) value;
+ return size;
+}
+
+static ssize_t stk_ps_ltnct_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ps_data->stk_lt_n_ct);
+}
+
+static ssize_t stk_ps_htnct_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ unsigned long value = 0;
+ int ret;
+
+ if((ret = kstrtoul(buf, 10, &value)) < 0)
+ {
+ printk(KERN_ERR "%s:kstrtoul failed, ret=0x%x\n", __func__, ret);
+ return ret;
+ }
+ ps_data->stk_ht_n_ct = (int) value;
+ return size;
+}
+
+static ssize_t stk_ps_htnct_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ps_data->stk_ht_n_ct);
+}
+#endif /* #ifdef STK_TUNE0 */
+
+static struct device_attribute als_enable_attribute = __ATTR(enable,0666,stk_als_enable_show,stk_als_enable_store);
+static struct device_attribute als_lux_attribute = __ATTR(lux,0664,stk_als_lux_show,stk_als_lux_store);
+static struct device_attribute als_code_attribute = __ATTR(code, 0444, stk_als_code_show, NULL);
+static struct device_attribute als_transmittance_attribute = __ATTR(transmittance,0664,stk_als_transmittance_show,stk_als_transmittance_store);
+static struct device_attribute als_poll_delay_attribute = __ATTR(delay,0664,stk_als_delay_show,stk_als_delay_store);
+static struct device_attribute als_ir_code_attribute = __ATTR(ircode,0444,stk_als_ir_code_show,NULL);
+#ifdef STK_ALS_FIR
+static struct device_attribute als_firlen_attribute = __ATTR(firlen,0664,stk_als_firlen_show,stk_als_firlen_store);
+#endif
+
+static struct attribute *stk_als_attrs [] =
+{
+ &als_enable_attribute.attr,
+ &als_lux_attribute.attr,
+ &als_code_attribute.attr,
+ &als_transmittance_attribute.attr,
+ &als_poll_delay_attribute.attr,
+ &als_ir_code_attribute.attr,
+#ifdef STK_ALS_FIR
+ &als_firlen_attribute.attr,
+#endif
+ NULL
+};
+
+static struct attribute_group stk_als_attribute_group = {
+#ifndef QUALCOMM_PLATFORM
+ .name = "driver",
+#endif
+ .attrs = stk_als_attrs,
+};
+
+#ifdef STK_GES
+static struct device_attribute ges_enable_attribute = __ATTR(enable,0666,stk_ges_enable_show,stk_ges_enable_store);
+static struct device_attribute ges_code_attribute = __ATTR(code, 0664, stk_ges_code_show, stk_ges_code_store);
+static struct device_attribute ges_poll_attribute = __ATTR(poll, 0664, stk_ges_poll_show, stk_ges_poll_store);
+static struct device_attribute ges_recv_attribute = __ATTR(recv,0664,stk_recv_show,stk_recv_store);
+static struct device_attribute ges_send_attribute = __ATTR(send,0664,stk_send_show, stk_send_store);
+
+static struct attribute *stk_ges_attrs [] =
+{
+ &ges_enable_attribute.attr,
+ &ges_code_attribute.attr,
+ &ges_poll_attribute.attr,
+ &ges_recv_attribute.attr,
+ &ges_send_attribute.attr,
+ NULL
+};
+
+static struct attribute_group stk_ges_attribute_group =
+{
+ .name = "driver",
+ .attrs = stk_ges_attrs,
+};
+#endif /* #ifdef STK_GES */
+
+static struct device_attribute ps_enable_attribute = __ATTR(enable,0666,stk_ps_enable_show,stk_ps_enable_store);
+static struct device_attribute ps_enable_aso_attribute = __ATTR(enableaso,0664,stk_ps_enable_aso_show,stk_ps_enable_aso_store);
+static struct device_attribute ps_distance_attribute = __ATTR(distance,0664,stk_ps_distance_show, stk_ps_distance_store);
+static struct device_attribute ps_offset_attribute = __ATTR(offset,0664,stk_ps_offset_show, stk_ps_offset_store);
+static struct device_attribute ps_code_attribute = __ATTR(code, 0444, stk_ps_code_show, NULL);
+static struct device_attribute ps_code_thd_l_attribute = __ATTR(codethdl,0664,stk_ps_code_thd_l_show,stk_ps_code_thd_l_store);
+static struct device_attribute ps_code_thd_h_attribute = __ATTR(codethdh,0664,stk_ps_code_thd_h_show,stk_ps_code_thd_h_store);
+static struct device_attribute ps_recv_attribute = __ATTR(recv,0664,stk_recv_show,stk_recv_store);
+static struct device_attribute ps_send_attribute = __ATTR(send,0664,stk_send_show, stk_send_store);
+static struct device_attribute all_reg_attribute = __ATTR(allreg, 0444, stk_all_reg_show, NULL);
+static struct device_attribute status_attribute = __ATTR(status, 0444, stk_status_show, NULL);
+#ifdef STK_TUNE0
+static struct device_attribute ps_cali_attribute = __ATTR(cali,0444,stk_ps_cali_show, NULL);
+static struct device_attribute ps_maxdiff_attribute = __ATTR(maxdiff,0664,stk_ps_maxdiff_show, stk_ps_maxdiff_store);
+static struct device_attribute ps_ltnct_attribute = __ATTR(ltnct,0664,stk_ps_ltnct_show, stk_ps_ltnct_store);
+static struct device_attribute ps_htnct_attribute = __ATTR(htnct,0664,stk_ps_htnct_show, stk_ps_htnct_store);
+#endif
+
+static struct attribute *stk_ps_attrs [] =
+{
+ &ps_enable_attribute.attr,
+ &ps_enable_aso_attribute.attr,
+ &ps_distance_attribute.attr,
+ &ps_offset_attribute.attr,
+ &ps_code_attribute.attr,
+ &ps_code_thd_l_attribute.attr,
+ &ps_code_thd_h_attribute.attr,
+ &ps_recv_attribute.attr,
+ &ps_send_attribute.attr,
+ &all_reg_attribute.attr,
+ &status_attribute.attr,
+#ifdef STK_TUNE0
+ &ps_cali_attribute.attr,
+ &ps_maxdiff_attribute.attr,
+ &ps_ltnct_attribute.attr,
+ &ps_htnct_attribute.attr,
+#endif
+ NULL
+};
+
+static struct attribute_group stk_ps_attribute_group = {
+#ifndef QUALCOMM_PLATFORM
+ .name = "driver",
+#endif
+ .attrs = stk_ps_attrs,
+};
+
+static int stk_ps_val(struct stk3x1x_data *ps_data)
+{
+ int mode;
+ int32_t word_data, lii;
+ unsigned char value[4];
+ int ret;
+
+ ret = stk3x1x_i2c_read_data(ps_data->client, 0x20, 4, value);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ word_data = (value[0]<<8) | value[1];
+ word_data += ((value[2]<<8) | value[3]);
+
+ mode = (ps_data->psctrl_reg) & 0x3F;
+ if(mode == 0x30)
+ lii = 100;
+ else if (mode == 0x31)
+ lii = 200;
+ else if (mode == 0x32)
+ lii = 400;
+ else if (mode == 0x33)
+ lii = 800;
+ else
+ {
+ printk(KERN_ERR "%s: unsupported PS_IT(0x%x)\n", __func__, mode);
+ return -1;
+ }
+
+ if(word_data > lii)
+ {
+ printk(KERN_INFO "%s: word_data=%d, lii=%d\n", __func__, word_data, lii);
+ return 0xFFFF;
+ }
+ return 0;
+}
+
+#ifdef STK_TUNE0
+
+static int stk_ps_tune_zero_final(struct stk3x1x_data *ps_data)
+{
+ int ret;
+
+ ps_data->tune_zero_init_proc = false;
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_INT_REG, ps_data->int_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_STATE_REG, 0);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ if(ps_data->data_count == -1)
+ {
+ printk(KERN_INFO "%s: exceed limit\n", __func__);
+ hrtimer_cancel(&ps_data->ps_tune0_timer);
+ return 0;
+ }
+
+ ps_data->psa = ps_data->ps_stat_data[0];
+ ps_data->psi = ps_data->ps_stat_data[2];
+
+#ifdef CALI_PS_EVERY_TIME
+ ps_data->ps_high_thd_boot = ps_data->ps_stat_data[1] + ps_data->stk_ht_n_ct*3;
+ ps_data->ps_low_thd_boot = ps_data->ps_stat_data[1] + ps_data->stk_lt_n_ct*3;
+ ps_data->ps_thd_h = ps_data->ps_high_thd_boot ;
+ ps_data->ps_thd_l = ps_data->ps_low_thd_boot ;
+#else
+ ps_data->ps_thd_h = ps_data->ps_stat_data[1] + ps_data->stk_ht_n_ct;
+ ps_data->ps_thd_l = ps_data->ps_stat_data[1] + ps_data->stk_lt_n_ct;
+#endif
+ stk3x1x_set_ps_thd_h(ps_data, ps_data->ps_thd_h);
+ stk3x1x_set_ps_thd_l(ps_data, ps_data->ps_thd_l);
+ printk(KERN_INFO "%s: set HT=%d,LT=%d\n", __func__, ps_data->ps_thd_h, ps_data->ps_thd_l);
+ hrtimer_cancel(&ps_data->ps_tune0_timer);
+ return 0;
+}
+
+static int32_t stk_tune_zero_get_ps_data(struct stk3x1x_data *ps_data)
+{
+ uint32_t ps_adc;
+ int ret;
+
+ ret = stk_ps_val(ps_data);
+ if(ret == 0xFFFF)
+ {
+ ps_data->data_count = -1;
+ stk_ps_tune_zero_final(ps_data);
+ return 0;
+ }
+
+ ps_adc = stk3x1x_get_ps_reading(ps_data);
+ printk(KERN_INFO "%s: ps_adc #%d=%d\n", __func__, ps_data->data_count, ps_adc);
+ if(ps_adc < 0)
+ return ps_adc;
+
+ ps_data->ps_stat_data[1] += ps_adc;
+ if(ps_adc > ps_data->ps_stat_data[0])
+ ps_data->ps_stat_data[0] = ps_adc;
+ if(ps_adc < ps_data->ps_stat_data[2])
+ ps_data->ps_stat_data[2] = ps_adc;
+ ps_data->data_count++;
+
+ if(ps_data->data_count == 5)
+ {
+ ps_data->ps_stat_data[1] /= ps_data->data_count;
+ stk_ps_tune_zero_final(ps_data);
+ }
+
+ return 0;
+}
+static int stk_ps_tune_zero_init(struct stk3x1x_data *ps_data)
+{
+ int32_t ret = 0;
+ uint8_t w_state_reg;
+
+ ps_data->psi_set = 0;
+ ps_data->ps_stat_data[0] = 0;
+ ps_data->ps_stat_data[2] = 9999;
+ ps_data->ps_stat_data[1] = 0;
+ ps_data->data_count = 0;
+
+ ps_data->tune_zero_init_proc = true;
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_INT_REG, 0);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+
+ w_state_reg = (STK_STATE_EN_PS_MASK | STK_STATE_EN_WAIT_MASK);
+ ret = stk3x1x_i2c_smbus_write_byte_data(ps_data->client, STK_STATE_REG, w_state_reg);
+ if (ret < 0)
+ {
+ printk(KERN_ERR "%s: write i2c error\n", __func__);
+ return ret;
+ }
+ hrtimer_start(&ps_data->ps_tune0_timer, ps_data->ps_tune0_delay, HRTIMER_MODE_REL);
+
+ return 0;
+}
+static int stk_ps_tune_zero_func_fae(struct stk3x1x_data *ps_data)
+{
+ int32_t word_data;
+ int ret, diff;
+ unsigned char value[2];
+
+#ifdef CALI_PS_EVERY_TIME
+ if(!(ps_data->ps_enabled))
+#else
+ if(ps_data->psi_set || !(ps_data->ps_enabled))
+#endif
+ {
+ return 0;
+ }
+
+ ret = stk3x1x_get_flag(ps_data);
+ if(ret < 0)
+ return ret;
+ if(!(ret&STK_FLG_PSDR_MASK))
+ {
+ //printk(KERN_INFO "%s: ps data is not ready yet\n", __func__);
+ return 0;
+ }
+
+ ret = stk_ps_val(ps_data);
+ if(ret == 0)
+ {
+ ret = stk3x1x_i2c_read_data(ps_data->client, 0x11, 2, &value[0]);
+ if(ret < 0)
+ {
+ printk(KERN_ERR "%s fail, ret=0x%x", __func__, ret);
+ return ret;
+ }
+ word_data = (value[0]<<8) | value[1];
+ //printk(KERN_INFO "%s: word_data=%d\n", __func__, word_data);
+
+ if(word_data == 0)
+ {
+ //printk(KERN_ERR "%s: incorrect word data (0)\n", __func__);
+ return 0xFFFF;
+ }
+
+ if(word_data > ps_data->psa)
+ {
+ ps_data->psa = word_data;
+ printk(KERN_INFO "%s: update psa: psa=%d,psi=%d\n", __func__, ps_data->psa, ps_data->psi);
+ }
+ if(word_data < ps_data->psi)
+ {
+ ps_data->psi = word_data;
+ printk(KERN_INFO "%s: update psi: psa=%d,psi=%d\n", __func__, ps_data->psa, ps_data->psi);
+ }
+ }
+ diff = ps_data->psa - ps_data->psi;
+ if(diff > ps_data->stk_max_min_diff)
+ {
+ ps_data->psi_set = ps_data->psi;
+ ps_data->ps_thd_h = ps_data->psi + ps_data->stk_ht_n_ct;
+ ps_data->ps_thd_l = ps_data->psi + ps_data->stk_lt_n_ct;
+
+
+#ifdef CALI_PS_EVERY_TIME
+ if(ps_data->ps_thd_h > ps_data->ps_high_thd_boot)
+ {
+ ps_data->ps_high_thd_boot = ps_data->ps_thd_h;
+ ps_data->ps_low_thd_boot = ps_data->ps_thd_l;
+ printk(KERN_INFO "%s: update boot HT=%d, LT=%d\n", __func__, ps_data->ps_high_thd_boot, ps_data->ps_low_thd_boot);
+ }
+#endif
+
+ stk3x1x_set_ps_thd_h(ps_data, ps_data->ps_thd_h);
+ stk3x1x_set_ps_thd_l(ps_data, ps_data->ps_thd_l);
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: FAE tune0 psa-psi(%d) > STK_DIFF found\n", __func__, diff);
+#endif
+ hrtimer_cancel(&ps_data->ps_tune0_timer);
+ }
+
+ return 0;
+}
+
+static void stk_ps_tune0_work_func(struct work_struct *work)
+{
+ struct stk3x1x_data *ps_data = container_of(work, struct stk3x1x_data, stk_ps_tune0_work);
+ if(ps_data->tune_zero_init_proc)
+ stk_tune_zero_get_ps_data(ps_data);
+ else
+ stk_ps_tune_zero_func_fae(ps_data);
+ return;
+}
+
+
+static enum hrtimer_restart stk_ps_tune0_timer_func(struct hrtimer *timer)
+{
+ struct stk3x1x_data *ps_data = container_of(timer, struct stk3x1x_data, ps_tune0_timer);
+ queue_work(ps_data->stk_ps_tune0_wq, &ps_data->stk_ps_tune0_work);
+ hrtimer_forward_now(&ps_data->ps_tune0_timer, ps_data->ps_tune0_delay);
+ return HRTIMER_RESTART;
+}
+#endif
+
+#ifdef STK_POLL_ALS
+static enum hrtimer_restart stk_als_timer_func(struct hrtimer *timer)
+{
+ struct stk3x1x_data *ps_data = container_of(timer, struct stk3x1x_data, als_timer);
+ queue_work(ps_data->stk_als_wq, &ps_data->stk_als_work);
+ hrtimer_forward_now(&ps_data->als_timer, ps_data->als_poll_delay);
+ return HRTIMER_RESTART;
+
+}
+
+static void stk_als_poll_work_func(struct work_struct *work)
+{
+ struct stk3x1x_data *ps_data = container_of(work, struct stk3x1x_data, stk_als_work);
+ int32_t reading = 0, reading_lux, flag_reg;
+#ifdef STK_IRS
+ int ret;
+#endif
+#ifdef STK_GES
+ if(ps_data->ges_enabled)
+ {
+ input_report_abs(ps_data->als_input_dev, ABS_MISC, ps_data->als_lux_last);
+ input_sync(ps_data->als_input_dev);
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: ges_enabled=1, als input event %d lux\n",__func__, ps_data->als_lux_last);
+ return;
+#endif
+ }
+#endif
+
+ flag_reg = stk3x1x_get_flag(ps_data);
+ if(flag_reg < 0)
+ return;
+ if(!(flag_reg&STK_FLG_ALSDR_MASK))
+ {
+ //printk(KERN_INFO "%s: als is not ready\n", __func__);
+ return;
+ }
+
+#ifdef STK_IRS
+ ret = stk_als_ir_skip_als(ps_data);
+ if(ret == 1)
+ return;
+#endif
+
+ reading = stk3x1x_get_als_reading(ps_data);
+ if(reading < 0)
+ return;
+ // printk("%s: als_data_index=%d, als_data=%d\n", __func__, ps_data->als_data_index, reading);
+#ifdef STK_IRS
+ stk_als_ir_get_corr(ps_data, reading);
+ reading = reading * ps_data->als_correct_factor / 1000;
+#endif
+ reading_lux = stk_alscode2lux(ps_data, reading);
+ if(abs(ps_data->als_lux_last - reading_lux) >= STK_ALS_CHANGE_THD)
+ {
+ ps_data->als_lux_last = reading_lux;
+ input_report_abs(ps_data->als_input_dev, ABS_MISC, reading_lux);
+ input_sync(ps_data->als_input_dev);
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: als input event %d lux\n",__func__, reading_lux);
+#endif
+ }
+
+#ifdef STK_IRS
+ stk_als_ir_run(ps_data);
+#endif
+ return;
+}
+#endif /* #ifdef STK_POLL_ALS */
+
+
+#ifdef STK_POLL_PS
+static enum hrtimer_restart stk_ps_timer_func(struct hrtimer *timer)
+{
+ struct stk3x1x_data *ps_data = container_of(timer, struct stk3x1x_data, ps_timer);
+ queue_work(ps_data->stk_ps_wq, &ps_data->stk_ps_work);
+ hrtimer_forward_now(&ps_data->ps_timer, ps_data->ps_poll_delay);
+ return HRTIMER_RESTART;
+}
+
+static void stk_ps_poll_work_func(struct work_struct *work)
+{
+ struct stk3x1x_data *ps_data = container_of(work, struct stk3x1x_data, stk_ps_work);
+ uint32_t reading;
+ int32_t near_far_state;
+ uint8_t org_flag_reg;
+#ifdef STK_GES
+ int32_t ret;
+ //uint8_t disable_flag = 0;
+ uint8_t disable_flag2 = 0, org_flag2_reg;
+ if(ps_data->ges_enabled == 2)
+ {
+ ret = stk3x1x_get_flag2(ps_data);
+ if(ret < 0)
+ goto err_i2c_rw;
+ org_flag2_reg = ret;
+
+ disable_flag2 = org_flag2_reg & (STK_FLG2_INT_GS_MASK |
+ STK_FLG2_GS10_MASK | STK_FLG2_GS01_MASK);
+ if(org_flag2_reg & STK_FLG2_GS10_MASK)
+ {
+ printk(KERN_INFO "%s: >>>>>>>>>>>>\n", __func__);
+ }
+ if(org_flag2_reg & STK_FLG2_GS01_MASK)
+ {
+ printk(KERN_INFO "%s: <<<<<<<<<<<<\n", __func__);
+ }
+ atomic_set(&ps_data->gesture2, (disable_flag2 &
+ (STK_FLG2_GS10_MASK | STK_FLG2_GS01_MASK)));
+
+ if(disable_flag2)
+ {
+ ret = stk3x1x_set_flag2(ps_data, org_flag2_reg, disable_flag2);
+ if(ret < 0)
+ goto err_i2c_rw;
+ }
+ }
+#endif
+
+ if(ps_data->ps_enabled)
+ {
+#ifdef STK_TUNE0
+ // if(!(ps_data->psi_set))
+ // return;
+#endif
+ org_flag_reg = stk3x1x_get_flag(ps_data);
+ if(org_flag_reg < 0)
+ goto err_i2c_rw;
+
+ if(!(org_flag_reg&STK_FLG_PSDR_MASK))
+ {
+ //printk(KERN_INFO "%s: ps is not ready\n", __func__);
+ return;
+ }
+
+ near_far_state = (org_flag_reg & STK_FLG_NF_MASK)?1:0;
+ reading = stk3x1x_get_ps_reading(ps_data);
+ if(ps_data->ps_distance_last != near_far_state)
+ {
+ ps_data->ps_distance_last = near_far_state;
+ input_report_abs(ps_data->ps_input_dev, ABS_DISTANCE, near_far_state);
+ input_sync(ps_data->ps_input_dev);
+ wake_lock_timeout(&ps_data->ps_wakelock, 3*HZ);
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: ps input event %d cm, ps code = %d\n",__func__, near_far_state, reading);
+#endif
+ }
+ // ret = stk3x1x_set_flag(ps_data, org_flag_reg, disable_flag);
+ // if(ret < 0)
+ // goto err_i2c_rw;
+ return;
+ }
+
+err_i2c_rw:
+ msleep(30);
+ return;
+}
+#endif
+
+#if (!defined(STK_POLL_PS) || !defined(STK_POLL_ALS))
+static void stk_work_func(struct work_struct *work)
+{
+ uint32_t reading;
+#if ((STK_INT_PS_MODE != 0x03) && (STK_INT_PS_MODE != 0x02))
+ int32_t ret;
+ uint8_t disable_flag = 0;
+ int32_t org_flag_reg;
+#endif /* #if ((STK_INT_PS_MODE != 0x03) && (STK_INT_PS_MODE != 0x02)) */
+
+#ifndef CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+ uint32_t nLuxIndex;
+#endif
+ struct stk3x1x_data *ps_data = container_of(work, struct stk3x1x_data, stk_work);
+ int32_t near_far_state;
+ int32_t als_comperator;
+#ifdef STK_GES
+ uint8_t disable_flag2 = 0, org_flag2_reg;
+#endif
+
+#if (STK_INT_PS_MODE == 0x03)
+ near_far_state = gpio_get_value(ps_data->int_pin);
+#elif (STK_INT_PS_MODE == 0x02)
+ near_far_state = !(gpio_get_value(ps_data->int_pin));
+#endif
+
+#if ((STK_INT_PS_MODE == 0x03) || (STK_INT_PS_MODE == 0x02))
+ ps_data->ps_distance_last = near_far_state;
+ input_report_abs(ps_data->ps_input_dev, ABS_DISTANCE, near_far_state);
+ input_sync(ps_data->ps_input_dev);
+ wake_lock_timeout(&ps_data->ps_wakelock, 3*HZ);
+ reading = stk3x1x_get_ps_reading(ps_data);
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: ps input event %d cm, ps code = %d\n",__func__, near_far_state, reading);
+#endif
+#else
+ /* mode 0x01 or 0x04 */
+ org_flag_reg = stk3x1x_get_flag(ps_data);
+ if(org_flag_reg < 0)
+ goto err_i2c_rw;
+
+ if (org_flag_reg & STK_FLG_ALSINT_MASK)
+ {
+ disable_flag |= STK_FLG_ALSINT_MASK;
+ reading = stk3x1x_get_als_reading(ps_data);
+ if(reading < 0)
+ {
+ printk(KERN_ERR "%s: stk3x1x_get_als_reading fail, ret=%d", __func__, reading);
+ goto err_i2c_rw;
+ }
+#ifndef CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+ nLuxIndex = stk_get_lux_interval_index(reading);
+ stk3x1x_set_als_thd_h(ps_data, code_threshold_table[nLuxIndex]);
+ stk3x1x_set_als_thd_l(ps_data, code_threshold_table[nLuxIndex-1]);
+#else
+ stk_als_set_new_thd(ps_data, reading);
+#endif //CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+
+ if(ps_data->ir_code)
+ {
+ if(reading < STK_IRC_MAX_ALS_CODE && reading > STK_IRC_MIN_ALS_CODE &&
+ ps_data->ir_code > STK_IRC_MIN_IR_CODE)
+ {
+ als_comperator = reading * STK_IRC_ALS_NUMERA / STK_IRC_ALS_DENOMI;
+ if(ps_data->ir_code > als_comperator)
+ ps_data->als_correct_factor = STK_IRC_ALS_CORREC;
+ else
+ ps_data->als_correct_factor = 1000;
+ }
+ printk(KERN_INFO "%s: als=%d, ir=%d, als_correct_factor=%d", __func__, reading, ps_data->ir_code, ps_data->als_correct_factor);
+ ps_data->ir_code = 0;
+ }
+
+ reading = reading * ps_data->als_correct_factor / 1000;
+
+ ps_data->als_lux_last = stk_alscode2lux(ps_data, reading);
+ input_report_abs(ps_data->als_input_dev, ABS_MISC, ps_data->als_lux_last);
+ input_sync(ps_data->als_input_dev);
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: als input event %d lux\n",__func__, ps_data->als_lux_last);
+#endif
+ }
+ if (org_flag_reg & STK_FLG_PSINT_MASK)
+ {
+ disable_flag |= STK_FLG_PSINT_MASK;
+ near_far_state = (org_flag_reg & STK_FLG_NF_MASK)?1:0;
+
+ ps_data->ps_distance_last = near_far_state;
+ input_report_abs(ps_data->ps_input_dev, ABS_DISTANCE, near_far_state);
+ input_sync(ps_data->ps_input_dev);
+ wake_lock_timeout(&ps_data->ps_wakelock, 3*HZ);
+ reading = stk3x1x_get_ps_reading(ps_data);
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: ps input event=%d, ps code = %d\n",__func__, near_far_state, reading);
+#endif
+ }
+
+ if(disable_flag)
+ {
+ ret = stk3x1x_set_flag(ps_data, org_flag_reg, disable_flag);
+ if(ret < 0)
+ goto err_i2c_rw;
+ }
+#endif
+ usleep_range(1000, 2000);
+ //msleep(1);
+ enable_irq(ps_data->irq);
+ return;
+
+err_i2c_rw:
+ msleep(30);
+ enable_irq(ps_data->irq);
+ return;
+}
+
+static irqreturn_t stk_oss_irq_handler(int irq, void *data)
+{
+ struct stk3x1x_data *pData = data;
+ disable_irq_nosync(irq);
+ queue_work(pData->stk_wq,&pData->stk_work);
+ return IRQ_HANDLED;
+}
+#endif /* #if (!defined(STK_POLL_PS) || !defined(STK_POLL_ALS)) */
+
+#ifdef STK_POLL_ALS
+static void stk3x1x_als_set_poll_delay(struct stk3x1x_data *ps_data)
+{
+ uint8_t als_it = ps_data->alsctrl_reg & 0x0F;
+
+ if(als_it == 0x8)
+ {
+ ps_data->als_poll_delay = ns_to_ktime(60 * NSEC_PER_MSEC);
+ }
+ else if(als_it == 0x9)
+ {
+ ps_data->als_poll_delay = ns_to_ktime(110 * NSEC_PER_MSEC);
+ }
+ else if(als_it == 0xA)
+ {
+ ps_data->als_poll_delay = ns_to_ktime(220 * NSEC_PER_MSEC);
+ }
+ else if(als_it == 0xB)
+ {
+ ps_data->als_poll_delay = ns_to_ktime(440 * NSEC_PER_MSEC);
+ }
+ else if(als_it == 0xC)
+ {
+ ps_data->als_poll_delay = ns_to_ktime(880 * NSEC_PER_MSEC);
+ }
+ else
+ {
+ ps_data->als_poll_delay = ns_to_ktime(110 * NSEC_PER_MSEC);
+ printk(KERN_INFO "%s: unknown ALS_IT=%d, set als_poll_delay=110ms\n", __func__, als_it);
+ }
+}
+#endif
+
+static int32_t stk3x1x_init_all_setting(struct i2c_client *client, struct stk3x1x_platform_data *plat_data)
+{
+ int32_t ret;
+ struct stk3x1x_data *ps_data = i2c_get_clientdata(client);
+
+ ret = stk3x1x_software_reset(ps_data);
+ if(ret < 0)
+ return ret;
+
+ ret = stk3x1x_check_pid(ps_data);
+ if(ret < 0)
+ return ret;
+ stk3x1x_proc_plat_data(ps_data, plat_data);
+ ret = stk3x1x_init_all_reg(ps_data);
+ if(ret < 0)
+ return ret;
+#ifdef STK_POLL_ALS
+ stk3x1x_als_set_poll_delay(ps_data);
+#endif
+ ps_data->als_enabled = false;
+ ps_data->ps_enabled = false;
+ ps_data->re_enable_als = false;
+ ps_data->re_enable_ps = false;
+ ps_data->ir_code = 0;
+ ps_data->als_correct_factor = 1000;
+ ps_data->first_boot = true;
+#ifndef CONFIG_STK_PS_ALS_USE_CHANGE_THRESHOLD
+ stk_init_code_threshold_table(ps_data);
+#endif
+#ifdef STK_TUNE0
+ #ifdef QUALCOMM_PLATFORM
+ ps_data->tune_zero_init_proc = false;
+ ps_data->psi_set = 0;
+ #else
+ stk_ps_tune_zero_init(ps_data);
+ #endif
+#endif
+#ifdef STK_ALS_FIR
+ memset(&ps_data->fir, 0x00, sizeof(ps_data->fir));
+ atomic_set(&ps_data->firlength, STK_FIR_LEN);
+#endif
+ atomic_set(&ps_data->recv_reg, 0);
+#ifdef STK_GES
+ ps_data->re_enable_ges = 0;
+ atomic_set(&ps_data->gesture2, 0);
+ //memset(stk_ges_op, 0, sizeof(stk_ges_op));
+#endif
+#ifdef STK_IRS
+ ps_data->als_data_index = 0;
+#endif
+ ps_data->ps_distance_last = 1;
+ ps_data->als_code_last = 0;
+ return 0;
+}
+
+#if (!defined(STK_POLL_PS) || !defined(STK_POLL_ALS))
+static int stk3x1x_setup_irq(struct i2c_client *client)
+{
+ int irq, err = -EIO;
+ struct stk3x1x_data *ps_data = i2c_get_clientdata(client);
+
+//#ifdef SPREADTRUM_PLATFORM
+// irq = sprd_alloc_gpio_irq(ps_data->int_pin);
+//#else
+ irq = gpio_to_irq(ps_data->int_pin);
+//#endif
+#ifdef STK_DEBUG_PRINTF
+ printk(KERN_INFO "%s: int pin #=%d, irq=%d\n",__func__, ps_data->int_pin, irq);
+#endif
+ if (irq <= 0)
+ {
+ printk(KERN_ERR "irq number is not specified, irq # = %d, int pin=%d\n",irq, ps_data->int_pin);
+ return irq;
+ }
+ ps_data->irq = irq;
+ err = gpio_request(ps_data->int_pin,"stk-int");
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: gpio_request, err=%d", __func__, err);
+ return err;
+ }
+ err = gpio_direction_input(ps_data->int_pin);
+ if(err < 0)
+ {
+ printk(KERN_ERR "%s: gpio_direction_input, err=%d", __func__, err);
+ return err;
+ }
+#if ((STK_INT_PS_MODE == 0x03) || (STK_INT_PS_MODE == 0x02))
+ err = request_any_context_irq(irq, stk_oss_irq_handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, DEVICE_NAME, ps_data);
+#else
+ err = request_any_context_irq(irq, stk_oss_irq_handler, IRQF_TRIGGER_LOW|IRQF_NO_SUSPEND|IRQF_ONESHOT, DEVICE_NAME, ps_data);
+#endif
+ if (err < 0)
+ {
+ printk(KERN_WARNING "%s: request_any_context_irq(%d) failed for (%d)\n", __func__, irq, err);
+ goto err_request_any_context_irq;
+ }
+ disable_irq(irq);
+
+ return 0;
+err_request_any_context_irq:
+//#ifdef SPREADTRUM_PLATFORM
+// sprd_free_gpio_irq(ps_data->int_pin);
+//#else
+ gpio_free(ps_data->int_pin);
+//#endif
+ return err;
+}
+#endif
+
+
+static int stk3x1x_suspend(struct device *dev)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+#if (defined(STK_CHK_REG) || !defined(STK_POLL_PS))
+ int err;
+#endif
+
+#ifndef STK_POLL_PS
+ struct i2c_client *client = to_i2c_client(dev);
+#endif
+
+ printk(KERN_INFO "%s", __func__);
+#ifndef SPREADTRUM_PLATFORM
+ mutex_lock(&ps_data->io_lock);
+#endif
+#ifdef STK_CHK_REG
+ err = stk3x1x_validate_n_handle(ps_data->client);
+ if(err < 0)
+ {
+ printk(KERN_ERR "stk3x1x_validate_n_handle fail: %d\n", err);
+ }
+ else if (err == 0xFF)
+ {
+ if(ps_data->ps_enabled)
+ stk3x1x_enable_ps(ps_data, 1, 0);
+ }
+#endif /* #ifdef STK_CHK_REG */
+#ifdef STK_GES
+ if(ps_data->ges_enabled == 1)
+ {
+ ps_data->re_enable_ges = ps_data->ges_enabled;
+ stk3x1x_enable_ges(ps_data, 0, 1);
+ }
+ else if(ps_data->ges_enabled == 2)
+ {
+ ps_data->re_enable_ges = ps_data->ges_enabled;
+ stk3x1x_enable_ges(ps_data, 0, 2);
+ }
+#endif
+#ifndef SPREADTRUM_PLATFORM
+ if(ps_data->als_enabled)
+ {
+ printk(KERN_INFO "%s: Enable ALS : 0\n", __func__);
+ stk3x1x_enable_als(ps_data, 0);
+ ps_data->re_enable_als = true;
+ }
+#endif
+ if(ps_data->ps_enabled)
+ {
+#ifdef STK_POLL_PS
+ wake_lock(&ps_data->ps_nosuspend_wl);
+#else
+ if(device_may_wakeup(&client->dev))
+ {
+ err = enable_irq_wake(ps_data->irq);
+ if (err)
+ printk(KERN_WARNING "%s: set_irq_wake(%d) failed, err=(%d)\n", __func__, ps_data->irq, err);
+ }
+ else
+ {
+ printk(KERN_ERR "%s: not support wakeup source", __func__);
+ }
+#endif
+ }
+#ifndef SPREADTRUM_PLATFORM
+ mutex_unlock(&ps_data->io_lock);
+#endif
+ return 0;
+}
+
+static int stk3x1x_resume(struct device *dev)
+{
+ struct stk3x1x_data *ps_data = dev_get_drvdata(dev);
+#if (defined(STK_CHK_REG) || !defined(STK_POLL_PS))
+ int err;
+#endif
+
+#ifndef STK_POLL_PS
+ struct i2c_client *client = to_i2c_client(dev);
+#endif
+ printk("%s called\n", __func__);
+ printk(KERN_INFO "%s", __func__);
+#ifndef SPREADTRUM_PLATFORM
+ mutex_lock(&ps_data->io_lock);
+#endif
+#ifdef STK_CHK_REG
+ err = stk3x1x_validate_n_handle(ps_data->client);
+ if(err < 0)
+ {
+ printk(KERN_ERR "stk3x1x_validate_n_handle fail: %d\n", err);
+ }
+ else if (err == 0xFF)
+ {
+ if(ps_data->ps_enabled)
+ stk3x1x_enable_ps(ps_data, 1, 0);
+ }
+#endif /* #ifdef STK_CHK_REG */
+#ifdef STK_GES
+ if(ps_data->re_enable_ges == 1)
+ {
+ stk3x1x_enable_ges(ps_data, 1, 1);
+ ps_data->re_enable_ges = 0;
+ }
+ else if(ps_data->re_enable_ges == 2)
+ {
+ stk3x1x_enable_ges(ps_data, 1, 2);
+ ps_data->re_enable_ges = 0;
+ }
+#endif
+#ifndef SPREADTRUM_PLATFORM
+ if(ps_data->re_enable_als)
+ {
+ printk(KERN_INFO "%s: Enable ALS : 1\n", __func__);
+ stk3x1x_enable_als(ps_data, 1);
+ ps_data->re_enable_als = false;
+ }
+#endif
+ if(ps_data->ps_enabled)
+ {
+#ifdef STK_POLL_PS
+ wake_unlock(&ps_data->ps_nosuspend_wl);
+#else
+ if(device_may_wakeup(&client->dev))
+ {
+ err = disable_irq_wake(ps_data->irq);
+ if (err)
+ printk(KERN_WARNING "%s: disable_irq_wake(%d) failed, err=(%d)\n", __func__, ps_data->irq, err);
+ }
+#endif
+ }
+#ifndef SPREADTRUM_PLATFORM
+ mutex_unlock(&ps_data->io_lock);
+#endif
+ return 0;
+}
+
+static const struct dev_pm_ops stk3x1x_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(stk3x1x_suspend, stk3x1x_resume)
+};
+
+//#ifdef CONFIG_HAS_EARLYSUSPEND
+#if 0
+static void stk3x1x_early_suspend(struct early_suspend *h)
+{
+
+}
+
+static void stk3x1x_late_resume(struct early_suspend *h)
+{
+
+}
+#endif //#ifdef CONFIG_HAS_EARLYSUSPEND
+
+
+#ifdef STK_QUALCOMM_POWER_CTRL
+static int stk3x1x_power_ctl(struct stk3x1x_data *data, bool on)
+{
+ int ret = 0;
+
+ if (!on && data->power_enabled) {
+ ret = regulator_disable(data->vdd);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "Regulator vdd disable failed ret=%d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_disable(data->vio);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "Regulator vio disable failed ret=%d\n", ret);
+ regulator_enable(data->vdd);
+ return ret;
+ }
+ data->power_enabled = on;
+ printk(KERN_INFO "%s: disable stk3x1x power", __func__);
+ dev_dbg(&data->client->dev, "stk3x1x_power_ctl on=%d\n",
+ on);
+ } else if (on && !data->power_enabled) {
+ ret = regulator_enable(data->vdd);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "Regulator vdd enable failed ret=%d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_enable(data->vio);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "Regulator vio enable failed ret=%d\n", ret);
+ regulator_disable(data->vdd);
+ return ret;
+ }
+ data->power_enabled = on;
+ printk(KERN_INFO "%s: enable stk3x1x power", __func__);
+ dev_dbg(&data->client->dev, "stk3x1x_power_ctl on=%d\n",
+ on);
+ } else {
+ dev_warn(&data->client->dev,
+ "Power on=%d. enabled=%d\n",
+ on, data->power_enabled);
+ }
+
+ return ret;
+}
+
+static int stk3x1x_power_init(struct stk3x1x_data *data, bool on)
+{
+ int ret;
+
+ if (!on) {
+ if (regulator_count_voltages(data->vdd) > 0)
+ regulator_set_voltage(data->vdd,
+ 0, STK3X1X_VDD_MAX_UV);
+
+ regulator_put(data->vdd);
+
+ if (regulator_count_voltages(data->vio) > 0)
+ regulator_set_voltage(data->vio,
+ 0, STK3X1X_VIO_MAX_UV);
+
+ regulator_put(data->vio);
+ } else {
+ data->vdd = regulator_get(&data->client->dev, "vdd");
+ if (IS_ERR(data->vdd)) {
+ ret = PTR_ERR(data->vdd);
+ dev_err(&data->client->dev,
+ "Regulator get failed vdd ret=%d\n", ret);
+ return ret;
+ }
+
+ if (regulator_count_voltages(data->vdd) > 0) {
+ ret = regulator_set_voltage(data->vdd,
+ STK3X1X_VDD_MIN_UV,
+ STK3X1X_VDD_MAX_UV);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "Regulator set failed vdd ret=%d\n",
+ ret);
+ goto reg_vdd_put;
+ }
+ }
+
+ data->vio = regulator_get(&data->client->dev, "vio");
+ if (IS_ERR(data->vio)) {
+ ret = PTR_ERR(data->vio);
+ dev_err(&data->client->dev,
+ "Regulator get failed vio ret=%d\n", ret);
+ goto reg_vdd_set;
+ }
+
+ if (regulator_count_voltages(data->vio) > 0) {
+ ret = regulator_set_voltage(data->vio,
+ STK3X1X_VIO_MIN_UV,
+ STK3X1X_VIO_MAX_UV);
+ if (ret) {
+ dev_err(&data->client->dev,
+ "Regulator set failed vio ret=%d\n", ret);
+ goto reg_vio_put;
+ }
+ }
+ }
+
+ return 0;
+
+reg_vio_put:
+ regulator_put(data->vio);
+reg_vdd_set:
+ if (regulator_count_voltages(data->vdd) > 0)
+ regulator_set_voltage(data->vdd, 0, STK3X1X_VDD_MAX_UV);
+reg_vdd_put:
+ regulator_put(data->vdd);
+ return ret;
+}
+
+static int stk3x1x_device_ctl(struct stk3x1x_data *ps_data, bool enable)
+{
+ int ret;
+ struct device *dev = &ps_data->client->dev;
+
+ if (enable && !ps_data->power_enabled) {
+ ret = stk3x1x_power_ctl(ps_data, true);
+ if (ret) {
+ dev_err(dev, "Failed to enable device power\n");
+ goto err_exit;
+ }
+ ret = stk3x1x_init_all_setting(ps_data->client, ps_data->pdata);
+ if (ret < 0) {
+ stk3x1x_power_ctl(ps_data, false);
+ dev_err(dev, "Failed to re-init device setting\n");
+ goto err_exit;
+ }
+ } else if (!enable && ps_data->power_enabled) {
+ #ifdef STK_GES
+ if (!ps_data->als_enabled && !ps_data->ps_enabled && !ps_data->ges_enabled) {
+ #else
+ if (!ps_data->als_enabled && !ps_data->ps_enabled) {
+ #endif
+ ret = stk3x1x_power_ctl(ps_data, false);
+ if (ret) {
+ dev_err(dev, "Failed to disable device power\n");
+ goto err_exit;
+ }
+ } else {
+ dev_dbg(dev, "device control: als_enabled=%d, ps_enabled=%d\n",
+ ps_data->als_enabled, ps_data->ps_enabled);
+ }
+ } else {
+ dev_dbg(dev, "device control: enable=%d, power_enabled=%d\n",
+ enable, ps_data->power_enabled);
+ }
+ return 0;
+
+err_exit:
+ return ret;
+}
+#endif /* #ifdef STK_QUALCOMM_POWER_CTRL */
+
+#ifdef CONFIG_OF
+static int stk3x1x_parse_dt(struct device *dev,
+ struct stk3x1x_platform_data *pdata)
+{
+#if 0
+ int rc;
+ struct device_node *np = dev->of_node;
+ u32 temp_val;
+
+ pdata->int_pin = of_get_named_gpio_flags(np, "stk,irq-gpio",
+ 0, &pdata->int_flags);
+ if (pdata->int_pin < 0) {
+ dev_err(dev, "Unable to read irq-gpio\n");
+ return pdata->int_pin;
+ }
+
+ rc = of_property_read_u32(np, "stk,transmittance", &temp_val);
+ if (!rc)
+ pdata->transmittance = temp_val;
+ else {
+ dev_err(dev, "Unable to read transmittance\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(np, "stk,state-reg", &temp_val);
+ if (!rc)
+ pdata->state_reg = temp_val;
+ else {
+ dev_err(dev, "Unable to read state-reg\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(np, "stk,psctrl-reg", &temp_val);
+ if (!rc)
+ pdata->psctrl_reg = (u8)temp_val;
+ else {
+ dev_err(dev, "Unable to read psctrl-reg\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(np, "stk,alsctrl-reg", &temp_val);
+ if (!rc)
+ pdata->alsctrl_reg = (u8)temp_val;
+ else {
+ dev_err(dev, "Unable to read alsctrl-reg\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(np, "stk,ledctrl-reg", &temp_val);
+ if (!rc)
+ pdata->ledctrl_reg = (u8)temp_val;
+ else {
+ dev_err(dev, "Unable to read ledctrl-reg\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(np, "stk,wait-reg", &temp_val);
+ if (!rc)
+ pdata->wait_reg = (u8)temp_val;
+ else {
+ dev_err(dev, "Unable to read wait-reg\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(np, "stk,ps-thdh", &temp_val);
+ if (!rc)
+ pdata->ps_thd_h = (u16)temp_val;
+ else {
+ dev_err(dev, "Unable to read ps-thdh\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(np, "stk,ps-thdl", &temp_val);
+ if (!rc)
+ pdata->ps_thd_l = (u16)temp_val;
+ else {
+ dev_err(dev, "Unable to read ps-thdl\n");
+ return rc;
+ }
+
+ //pdata->use_fir = of_property_read_bool(np, "stk,use-fir");
+#endif
+
+ return 0;
+}
+#else
+static int stk3x1x_parse_dt(struct device *dev,
+ struct stk3x1x_platform_data *pdata)
+{
+ return -ENODEV;
+}
+#endif /* !CONFIG_OF */
+
+static int stk3x1x_set_wq(struct stk3x1x_data *ps_data)
+{
+#ifdef STK_POLL_ALS
+ ps_data->stk_als_wq = create_singlethread_workqueue("stk_als_wq");
+ INIT_WORK(&ps_data->stk_als_work, stk_als_poll_work_func);
+ hrtimer_init(&ps_data->als_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ ps_data->als_poll_delay = ns_to_ktime(110 * NSEC_PER_MSEC);
+ ps_data->als_timer.function = stk_als_timer_func;
+#endif
+
+#ifdef STK_POLL_PS
+ ps_data->stk_ps_wq = create_singlethread_workqueue("stk_ps_wq");
+ INIT_WORK(&ps_data->stk_ps_work, stk_ps_poll_work_func);
+ hrtimer_init(&ps_data->ps_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ ps_data->ps_poll_delay = ns_to_ktime(60 * NSEC_PER_MSEC);
+ ps_data->ps_timer.function = stk_ps_timer_func;
+#endif
+
+#ifdef STK_TUNE0
+ ps_data->stk_ps_tune0_wq = create_singlethread_workqueue("stk_ps_tune0_wq");
+ INIT_WORK(&ps_data->stk_ps_tune0_work, stk_ps_tune0_work_func);
+ hrtimer_init(&ps_data->ps_tune0_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ ps_data->ps_tune0_delay = ns_to_ktime(60 * NSEC_PER_MSEC);
+ ps_data->ps_tune0_timer.function = stk_ps_tune0_timer_func;
+#endif
+
+#if (!defined(STK_POLL_ALS) || !defined(STK_POLL_PS))
+ ps_data->stk_wq = create_singlethread_workqueue("stk_wq");
+ INIT_WORK(&ps_data->stk_work, stk_work_func);
+#endif
+ return 0;
+}
+
+
+static int stk3x1x_set_input_devices(struct stk3x1x_data *ps_data)
+{
+ int err;
+
+ ps_data->als_input_dev = input_allocate_device();
+ if (ps_data->als_input_dev==NULL)
+ {
+ printk(KERN_ERR "%s: could not allocate als device\n", __func__);
+ err = -ENOMEM;
+ return err;
+ }
+ ps_data->ps_input_dev = input_allocate_device();
+ if (ps_data->ps_input_dev==NULL)
+ {
+ printk(KERN_ERR "%s: could not allocate ps device\n", __func__);
+ err = -ENOMEM;
+ return err;
+ }
+ ps_data->als_input_dev->name = ALS_NAME;
+ ps_data->ps_input_dev->name = PS_NAME;
+ set_bit(EV_ABS, ps_data->als_input_dev->evbit);
+ set_bit(EV_ABS, ps_data->ps_input_dev->evbit);
+ input_set_abs_params(ps_data->als_input_dev, ABS_MISC, 0, stk_alscode2lux(ps_data, (1<<16)-1), 0, 0);
+ input_set_abs_params(ps_data->ps_input_dev, ABS_DISTANCE, 0,1, 0, 0);
+ err = input_register_device(ps_data->als_input_dev);
+ if (err<0)
+ {
+ printk(KERN_ERR "%s: can not register als input device\n", __func__);
+ return err;
+ }
+ err = input_register_device(ps_data->ps_input_dev);
+ if (err<0)
+ {
+ printk(KERN_ERR "%s: can not register ps input device\n", __func__);
+ return err;
+ }
+
+ err = sysfs_create_group(&ps_data->als_input_dev->dev.kobj, &stk_als_attribute_group);
+ if (err < 0)
+ {
+ printk(KERN_ERR "%s:could not create sysfs group for als\n", __func__);
+ return err;
+ }
+ err = sysfs_create_group(&ps_data->ps_input_dev->dev.kobj, &stk_ps_attribute_group);
+ if (err < 0)
+ {
+ printk(KERN_ERR "%s:could not create sysfs group for ps\n", __func__);
+ return err;
+ }
+ input_set_drvdata(ps_data->als_input_dev, ps_data);
+ input_set_drvdata(ps_data->ps_input_dev, ps_data);
+
+#ifdef STK_GES
+ ps_data->ges_input_dev = input_allocate_device();
+ if (ps_data->ges_input_dev==NULL)
+ {
+ printk(KERN_ERR "%s: could not allocate ps device\n", __func__);
+ err = -ENOMEM;
+ return err;
+ }
+ ps_data->ges_input_dev->name = "stk_ges";
+ ps_data->ges_input_dev->evbit[0] = BIT_MASK(EV_KEY);
+ set_bit(KEY_PAGEUP, ps_data->ges_input_dev->keybit);
+ set_bit(KEY_PAGEDOWN, ps_data->ges_input_dev->keybit);
+ set_bit(KEY_VOLUMEUP, ps_data->ges_input_dev->keybit);
+ set_bit(KEY_VOLUMEDOWN, ps_data->ges_input_dev->keybit);
+ /*
+ set_bit(KEY_LEFT, ps_data->ges_input_dev->keybit);
+ set_bit(KEY_RIGHT, ps_data->ges_input_dev->keybit);
+ set_bit(KEY_UP, ps_data->ges_input_dev->keybit);
+ set_bit(KEY_DOWN, ps_data->ges_input_dev->keybit);
+ */
+ err = input_register_device(ps_data->ges_input_dev);
+ if (err<0)
+ {
+ printk(KERN_ERR "%s: can not register ps input device\n", __func__);
+ return err;
+ }
+
+ err = sysfs_create_group(&ps_data->ges_input_dev->dev.kobj, &stk_ges_attribute_group);
+ if (err < 0)
+ {
+ printk(KERN_ERR "%s:could not create sysfs group for ps\n", __func__);
+ return err;
+ }
+ input_set_drvdata(ps_data->ges_input_dev, ps_data);
+#endif
+
+ return 0;
+}
+
+static int stk3x1x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int err = -ENODEV;
+ struct stk3x1x_data *ps_data;
+ struct stk3x1x_platform_data *plat_data;
+ printk(KERN_INFO "%s: driver version = %s\n", __func__, DRIVER_VERSION);
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ {
+ printk(KERN_ERR "%s: No Support for I2C_FUNC_I2C\n", __func__);
+ return -ENODEV;
+ }
+
+ ps_data = kzalloc(sizeof(struct stk3x1x_data),GFP_KERNEL);
+ if(!ps_data)
+ {
+ printk(KERN_ERR "%s: failed to allocate stk3x1x_data\n", __func__);
+ return -ENOMEM;
+ }
+ ps_data->client = client;
+ i2c_set_clientdata(client,ps_data);
+ mutex_init(&ps_data->io_lock);
+ wake_lock_init(&ps_data->ps_wakelock,WAKE_LOCK_SUSPEND, "stk_input_wakelock");
+
+#ifdef STK_POLL_PS
+ wake_lock_init(&ps_data->ps_nosuspend_wl,WAKE_LOCK_SUSPEND, "stk_nosuspend_wakelock");
+#endif
+
+ if (client->dev.of_node) {
+ printk(KERN_INFO "%s: probe with device tree\n", __func__);
+ plat_data = devm_kzalloc(&client->dev,
+ sizeof(struct stk3x1x_platform_data), GFP_KERNEL);
+ if (!plat_data) {
+ dev_err(&client->dev, "Failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ err = stk3x1x_parse_dt(&client->dev, plat_data);
+//huanggq
+ plat_data->state_reg = stk3x1x_pfdata.state_reg;
+ plat_data->psctrl_reg = stk3x1x_pfdata.psctrl_reg;
+ plat_data->alsctrl_reg = stk3x1x_pfdata.alsctrl_reg;
+ plat_data->ledctrl_reg = stk3x1x_pfdata.ledctrl_reg;
+ plat_data->wait_reg = stk3x1x_pfdata.wait_reg;
+ plat_data->ps_thd_h = stk3x1x_pfdata.ps_thd_h;
+ plat_data->ps_thd_l = stk3x1x_pfdata.ps_thd_l;
+ plat_data->int_pin = stk3x1x_pfdata.int_pin;
+ plat_data->transmittance = stk3x1x_pfdata.transmittance;
+//end
+ dev_err(&client->dev,
+ "%s: stk3x1x_parse_dt ret=%d\n", __func__, err);
+ if (err)
+ return err;
+ } else {
+ printk(KERN_INFO "%s: probe with platform data\n", __func__);
+#ifdef SPREADTRUM_PLATFORM
+ plat_data = &stk3x1x_pfdata;
+#else
+ plat_data = client->dev.platform_data;
+#endif
+ }
+ if (!plat_data) {
+ dev_err(&client->dev,
+ "%s: no stk3x1x platform data!\n", __func__);
+ goto err_als_input_allocate;
+ }
+ ps_data->als_transmittance = plat_data->transmittance;
+ ps_data->int_pin = plat_data->int_pin;
+ ps_data->pdata = plat_data;
+
+ if (ps_data->als_transmittance == 0) {
+ dev_err(&client->dev,
+ "%s: Please set als_transmittance\n", __func__);
+ goto err_als_input_allocate;
+ }
+
+ stk3x1x_set_wq(ps_data);
+#ifdef QUALCOMM_PLATFORM
+ ps_data->ps_thd_h = 0;
+ ps_data->ps_thd_l = 0;
+#endif
+ ps_data->stk_max_min_diff = STK_MAX_MIN_DIFF;
+ ps_data->stk_lt_n_ct = STK_LT_N_CT;
+ ps_data->stk_ht_n_ct = STK_HT_N_CT;
+
+#ifdef STK_QUALCOMM_POWER_CTRL
+ err = stk3x1x_power_init(ps_data, true);
+ if (err)
+ goto err_init_all_setting;
+
+ err = stk3x1x_power_ctl(ps_data, true);
+ if (err)
+ goto err_power_on;
+
+ ps_data->als_enabled = false;
+ ps_data->ps_enabled = false;
+#endif
+
+ err = stk3x1x_init_all_setting(client, plat_data);
+ if(err < 0)
+ goto err_init_all_setting;
+
+ err = stk3x1x_set_input_devices(ps_data);
+ if(err < 0)
+ goto err_setup_input_device;
+
+#if (!defined(STK_POLL_ALS) || !defined(STK_POLL_PS))
+ err = stk3x1x_setup_irq(client);
+ if(err < 0)
+ goto err_stk3x1x_setup_irq;
+#endif
+ device_init_wakeup(&client->dev, true);
+
+//#ifdef CONFIG_HAS_EARLYSUSPEND
+#if 0
+ ps_data->stk_early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
+ ps_data->stk_early_suspend.suspend = stk3x1x_early_suspend;
+ ps_data->stk_early_suspend.resume = stk3x1x_late_resume;
+ register_early_suspend(&ps_data->stk_early_suspend);
+#endif
+
+#ifdef STK_QUALCOMM_POWER_CTRL
+ /* enable device power only when it is enabled */
+ err = stk3x1x_power_ctl(ps_data, false);
+ if (err)
+ goto err_stk3x1x_setup_irq;
+#endif
+ printk(KERN_INFO "%s: probe successfully", __func__);
+ return 0;
+
+ //device_init_wakeup(&client->dev, false);
+err_stk3x1x_setup_irq:
+#if (!defined(STK_POLL_ALS) || !defined(STK_POLL_PS))
+ free_irq(ps_data->irq, ps_data);
+ //#ifdef SPREADTRUM_PLATFORM
+ // sprd_free_gpio_irq(ps_data->int_pin);
+ //#else
+ gpio_free(ps_data->int_pin);
+ //#endif
+#endif
+err_setup_input_device:
+#ifdef STK_GES
+ sysfs_remove_group(&ps_data->ges_input_dev->dev.kobj, &stk_ges_attribute_group);
+ input_unregister_device(ps_data->ges_input_dev);
+ input_free_device(ps_data->ges_input_dev);
+#endif
+ sysfs_remove_group(&ps_data->ps_input_dev->dev.kobj, &stk_ps_attribute_group);
+ sysfs_remove_group(&ps_data->als_input_dev->dev.kobj, &stk_als_attribute_group);
+ input_unregister_device(ps_data->ps_input_dev);
+ input_unregister_device(ps_data->als_input_dev);
+ input_free_device(ps_data->ps_input_dev);
+ input_free_device(ps_data->als_input_dev);
+#ifdef STK_QUALCOMM_POWER_CTRL
+ stk3x1x_power_ctl(ps_data, false);
+err_power_on:
+ stk3x1x_power_init(ps_data, false);
+#endif
+err_init_all_setting:
+#ifdef STK_POLL_ALS
+ hrtimer_try_to_cancel(&ps_data->als_timer);
+ destroy_workqueue(ps_data->stk_als_wq);
+#endif
+#ifdef STK_TUNE0
+ destroy_workqueue(ps_data->stk_ps_tune0_wq);
+#endif
+#ifdef STK_POLL_PS
+ hrtimer_try_to_cancel(&ps_data->ps_timer);
+ destroy_workqueue(ps_data->stk_ps_wq);
+#endif
+#if (!defined(STK_POLL_ALS) || !defined(STK_POLL_PS))
+ destroy_workqueue(ps_data->stk_wq);
+#endif
+err_als_input_allocate:
+#ifdef STK_POLL_PS
+ wake_lock_destroy(&ps_data->ps_nosuspend_wl);
+#endif
+ wake_lock_destroy(&ps_data->ps_wakelock);
+ mutex_destroy(&ps_data->io_lock);
+ kfree(ps_data);
+ return err;
+}
+
+
+static int stk3x1x_remove(struct i2c_client *client)
+{
+ struct stk3x1x_data *ps_data = i2c_get_clientdata(client);
+ device_init_wakeup(&client->dev, false);
+#if (!defined(STK_POLL_ALS) || !defined(STK_POLL_PS))
+ free_irq(ps_data->irq, ps_data);
+ //#ifdef SPREADTRUM_PLATFORM
+ // sprd_free_gpio_irq(ps_data->int_pin);
+ //#else
+ gpio_free(ps_data->int_pin);
+ //#endif
+#endif /* #if (!defined(STK_POLL_ALS) || !defined(STK_POLL_PS)) */
+
+
+#ifdef STK_GES
+ sysfs_remove_group(&ps_data->ges_input_dev->dev.kobj, &stk_ges_attribute_group);
+ input_unregister_device(ps_data->ges_input_dev);
+ input_free_device(ps_data->ges_input_dev);
+#endif
+#if (!defined(STK_POLL_ALS) || !defined(STK_POLL_PS))
+ destroy_workqueue(ps_data->stk_wq);
+#endif
+ sysfs_remove_group(&ps_data->ps_input_dev->dev.kobj, &stk_ps_attribute_group);
+ sysfs_remove_group(&ps_data->als_input_dev->dev.kobj, &stk_als_attribute_group);
+ input_unregister_device(ps_data->ps_input_dev);
+ input_unregister_device(ps_data->als_input_dev);
+ input_free_device(ps_data->ps_input_dev);
+ input_free_device(ps_data->als_input_dev);
+#ifdef STK_QUALCOMM_POWER_CTRL
+ stk3x1x_power_ctl(ps_data, false);
+ stk3x1x_power_init(ps_data, false);
+#endif
+#ifdef STK_POLL_ALS
+ hrtimer_try_to_cancel(&ps_data->als_timer);
+ destroy_workqueue(ps_data->stk_als_wq);
+#endif
+#ifdef STK_TUNE0
+ destroy_workqueue(ps_data->stk_ps_tune0_wq);
+#endif
+#ifdef STK_POLL_PS
+ hrtimer_try_to_cancel(&ps_data->ps_timer);
+ destroy_workqueue(ps_data->stk_ps_wq);
+ wake_lock_destroy(&ps_data->ps_nosuspend_wl);
+#endif
+ wake_lock_destroy(&ps_data->ps_wakelock);
+ mutex_destroy(&ps_data->io_lock);
+ kfree(ps_data);
+
+ return 0;
+}
+
+static const struct i2c_device_id stk_ps_id[] =
+{
+ { "stk_ps", 0},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, stk_ps_id);
+
+static struct of_device_id stk_match_table[] = {
+ { .compatible = "stk,stk3x1x", },
+ { },
+};
+
+static struct i2c_driver stk_ps_driver =
+{
+ .driver = {
+ .name = DEVICE_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_OF
+ .of_match_table = stk_match_table,
+#endif
+ .pm = &stk3x1x_pm_ops,
+ },
+ .probe = stk3x1x_probe,
+ .remove = stk3x1x_remove,
+ .id_table = stk_ps_id,
+};
+
+static int __init stk3x1x_init(void)
+{
+ int ret;
+
+ printk("stk3x1x_init");
+
+ ret = i2c_add_driver(&stk_ps_driver);
+ if (ret)
+ {
+ i2c_del_driver(&stk_ps_driver);
+ return ret;
+ }
+ return 0;
+}
+
+static void __exit stk3x1x_exit(void)
+{
+ i2c_del_driver(&stk_ps_driver);
+}
+
+module_init(stk3x1x_init);
+module_exit(stk3x1x_exit);
+MODULE_AUTHOR("Lex Hsieh <lex_hsieh@sensortek.com.tw>");
+MODULE_DESCRIPTION("Sensortek stk3x1x Proximity Sensor driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);
\ No newline at end of file diff --git a/drivers/input/misc/stk3x1x.h b/drivers/input/misc/stk3x1x.h new file mode 100644 index 00000000..2d3cc79b --- /dev/null +++ b/drivers/input/misc/stk3x1x.h @@ -0,0 +1,31 @@ +/*
+ *
+ * $Id: stk3x1x.h
+ *
+ * Copyright (C) 2012~2013 Lex Hsieh <lex_hsieh@sensortek.com.tw>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+#ifndef __STK3X1X_H__
+#define __STK3X1X_H__
+
+/* platform data */
+struct stk3x1x_platform_data
+{
+ uint8_t state_reg;
+ uint8_t psctrl_reg;
+ uint8_t alsctrl_reg;
+ uint8_t ledctrl_reg;
+ uint8_t wait_reg;
+ uint16_t ps_thd_h;
+ uint16_t ps_thd_l;
+ int int_pin;
+ uint32_t transmittance;
+ uint32_t int_flags;
+};
+
+
+#endif // __STK3X1X_H__
\ No newline at end of file diff --git a/drivers/input/misc/tmd2771_pls.c b/drivers/input/misc/tmd2771_pls.c new file mode 100644 index 00000000..e3cc465e --- /dev/null +++ b/drivers/input/misc/tmd2771_pls.c @@ -0,0 +1,1591 @@ +/******************************************************************************* +* * +* File Name: taos.c * +* Description: Linux device driver for Taos ambient light and * +* proximity sensors. * +* Author: John Koshi * +* History: 09/16/2009 - Initial creation * +* 10/09/2009 - Triton version * +* 12/21/2009 - Probe/remove mode * +* 02/07/2010 - Add proximity * +* * +******************************************************************************** +* Proprietary to Taos Inc., 1001 Klein Road #300, Plano, TX 75074 * +*******************************************************************************/ +// includes +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/timer.h> +#include <asm/uaccess.h> +#include <asm/errno.h> +#include <asm/delay.h> +#include <linux/i2c/tmd2771_pls.h> +#include <linux/delay.h> +//iVIZM +#include <linux/irq.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <mach/gpio.h> +#include <linux/poll.h> +#include <linux/wakelock.h> +#include <linux/input.h> +#include <linux/regulator/consumer.h> +#include <mach/regulator.h> + +#include <linux/miscdevice.h> + +// device name/id/address/counts +#define TAOS_DEVICE_NAME "tmd2771_pls" +#define TAOS_DEVICE_ID "tritonFN" +#define TAOS_ID_NAME_SIZE 10 +#define TAOS_TRITON_CHIPIDVAL 0x00 +#define TAOS_TRITON_MAXREGS 32 +#define TAOS_DEVICE_ADDR1 0x29 +#define TAOS_DEVICE_ADDR2 0x39 +#define TAOS_DEVICE_ADDR3 0x49 +#define TAOS_MAX_NUM_DEVICES 3 +#define TAOS_MAX_DEVICE_REGS 32 +#define I2C_MAX_ADAPTERS 8 + +// TRITON register offsets +#define TAOS_TRITON_CNTRL 0x00 +#define TAOS_TRITON_ALS_TIME 0X01 +#define TAOS_TRITON_PRX_TIME 0x02 +#define TAOS_TRITON_WAIT_TIME 0x03 +#define TAOS_TRITON_ALS_MINTHRESHLO 0X04 +#define TAOS_TRITON_ALS_MINTHRESHHI 0X05 +#define TAOS_TRITON_ALS_MAXTHRESHLO 0X06 +#define TAOS_TRITON_ALS_MAXTHRESHHI 0X07 +#define TAOS_TRITON_PRX_MINTHRESHLO 0X08 +#define TAOS_TRITON_PRX_MINTHRESHHI 0X09 +#define TAOS_TRITON_PRX_MAXTHRESHLO 0X0A +#define TAOS_TRITON_PRX_MAXTHRESHHI 0X0B +#define TAOS_TRITON_INTERRUPT 0x0C +#define TAOS_TRITON_PRX_CFG 0x0D +#define TAOS_TRITON_PRX_COUNT 0x0E +#define TAOS_TRITON_GAIN 0x0F +#define TAOS_TRITON_REVID 0x11 +#define TAOS_TRITON_CHIPID 0x12 +#define TAOS_TRITON_STATUS 0x13 +#define TAOS_TRITON_ALS_CHAN0LO 0x14 +#define TAOS_TRITON_ALS_CHAN0HI 0x15 +#define TAOS_TRITON_ALS_CHAN1LO 0x16 +#define TAOS_TRITON_ALS_CHAN1HI 0x17 +#define TAOS_TRITON_PRX_LO 0x18 +#define TAOS_TRITON_PRX_HI 0x19 +#define TAOS_TRITON_TEST_STATUS 0x1F + +// Triton cmd reg masks +#define TAOS_TRITON_CMD_REG 0X80 +#define TAOS_TRITON_CMD_AUTO 0x10 //iVIZM +#define TAOS_TRITON_CMD_BYTE_RW 0x00 +#define TAOS_TRITON_CMD_WORD_BLK_RW 0x20 +#define TAOS_TRITON_CMD_SPL_FN 0x60 +#define TAOS_TRITON_CMD_PROX_INTCLR 0X05 +#define TAOS_TRITON_CMD_ALS_INTCLR 0X06 +#define TAOS_TRITON_CMD_PROXALS_INTCLR 0X07 +#define TAOS_TRITON_CMD_TST_REG 0X08 +#define TAOS_TRITON_CMD_USER_REG 0X09 + +// Triton cntrl reg masks +#define TAOS_TRITON_CNTL_PROX_INT_ENBL 0X20 +#define TAOS_TRITON_CNTL_ALS_INT_ENBL 0X10 +#define TAOS_TRITON_CNTL_WAIT_TMR_ENBL 0X08 +#define TAOS_TRITON_CNTL_PROX_DET_ENBL 0X04 +#define TAOS_TRITON_CNTL_ADC_ENBL 0x02 +#define TAOS_TRITON_CNTL_PWRON 0x01 + +// Triton status reg masks +#define TAOS_TRITON_STATUS_ADCVALID 0x01 +#define TAOS_TRITON_STATUS_PRXVALID 0x02 +#define TAOS_TRITON_STATUS_ADCINTR 0x10 +#define TAOS_TRITON_STATUS_PRXINTR 0x20 + +// lux constants +#define TAOS_MAX_LUX 10000 +#define TAOS_SCALE_MILLILUX 3 +#define TAOS_FILTER_DEPTH 3 +#define CHIP_ID 0x3d + +#define TAOS_INPUT_NAME "light sensor" +// forward declarations +static int taos_probe(struct i2c_client *clientp, const struct i2c_device_id *idp); +static int taos_remove(struct i2c_client *client); +static int taos_open(struct inode *inode, struct file *file); +static int taos_release(struct inode *inode, struct file *file); +static int taos_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static int taos_get_lux(void); +//static int taos_lux_filter(int raw_lux); +static int taos_device_name(unsigned char *bufp, char **device_name); +static int taos_prox_poll(struct taos_prox_info *prxp); +static void taos_prox_poll_timer_func(unsigned long param); +static void taos_prox_poll_timer_start(void); +//iVIZM +static int taos_als_threshold_set(void); +static int taos_prox_threshold_set(void); +static int taos_als_get_data(void); +static int taos_interrupts_clear(void); +//static unsigned int taos_poll(struct file *filp, poll_table *wait); +//static int taos_resume(struct i2c_client *client); +//static int taos_suspend(struct i2c_client *client,pm_message_t mesg); + + +DECLARE_WAIT_QUEUE_HEAD(waitqueue_read);//iVIZM + +#define ALS_PROX_DEBUG //iVIZM + +// first device number +static dev_t taos_dev_number; + +// workqueue struct +//static struct workqueue_struct *taos_wq; //iVIZM + +// class structure for this device +struct class *taos_class; + +// module device table +static struct i2c_device_id taos_idtable[] = { + {TAOS_DEVICE_NAME, 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, taos_idtable); + +// board and address info iVIZM +struct i2c_board_info taos_board_info[] = { + {I2C_BOARD_INFO(TAOS_DEVICE_ID, TAOS_DEVICE_ADDR2),}, +}; + +unsigned short const taos_addr_list[2] = {TAOS_DEVICE_ADDR2, I2C_CLIENT_END};//iVIZM + +// client and device +struct i2c_client *my_clientp; +struct i2c_client *bad_clientp[TAOS_MAX_NUM_DEVICES]; +static int num_bad = 0; +static int device_found = 0; +//iVIZM +static char pro_buf[4]; //iVIZM +static int mcount = 0; //iVIZM +static char als_buf[4]; //iVIZM +u16 status = 0; +static int ALS_ON; + +// driver definition +static struct i2c_driver taos_driver = { + .driver = { + .owner = THIS_MODULE, + .name = TAOS_DEVICE_NAME, + }, + .id_table = taos_idtable, + .probe = taos_probe, + //.resume = taos_resume,//iVIZM + //.suspend = taos_suspend,//iVIZM + .remove = __devexit_p(taos_remove), +}; + +// per-device data +struct taos_data { + struct i2c_client *client; + struct cdev cdev; + unsigned int addr; + struct input_dev *input_dev;//iVIZM + struct work_struct work;//iVIZM + struct wake_lock taos_wake_lock;//iVIZM + char taos_id; + char taos_name[TAOS_ID_NAME_SIZE]; + struct semaphore update_lock; + char valid; + //int working; + unsigned long last_updated; + struct regulator *reg_vdd; +} *taos_datap; + +// file operations +static struct file_operations taos_fops = { + .owner = THIS_MODULE, + .open = taos_open, + .release = taos_release, + .unlocked_ioctl = taos_ioctl, +}; + +// device configuration +struct taos_cfg *taos_cfgp; + +#if 1 //zwj add +static struct file_operations taos_fops_2 = { + .owner = THIS_MODULE, + .open = taos_open, + .release = taos_release, + .unlocked_ioctl = taos_ioctl, +}; + +static struct miscdevice taos_device = { + .minor = MISC_DYNAMIC_MINOR, + .name = TAOS_DEVICE_NAME, + .fops = &taos_fops_2, +}; +#endif + + +static u32 calibrate_target_param = 300000; +static u16 als_time_param = 200; +static u16 scale_factor_param = 1; +static u16 gain_trim_param = 512; +static u8 filter_history_param = 3; +static u8 filter_count_param = 1; +static u8 gain_param = 2; +static u16 prox_threshold_hi_param = 560; //120; +static u16 prox_threshold_lo_param = 500; //80; +static u16 als_threshold_hi_param = 3000;//iVIZM +static u16 als_threshold_lo_param = 10;//iVIZM +static u8 prox_int_time_param = 0xEE;//50ms +static u8 prox_adc_time_param = 0xFF; +static u8 prox_wait_time_param = 0xEE; +static u8 prox_intr_filter_param = 0x23; +static u8 prox_config_param = 0x00; +static u8 prox_pulse_cnt_param = 0x04; //0x01; +static u8 prox_gain_param = 0xA2; //0x22; + +// prox info +struct taos_prox_info prox_cal_info[20]; +struct taos_prox_info prox_cur_info; +struct taos_prox_info *prox_cur_infop = &prox_cur_info; +static u8 prox_history_hi = 0; +static u8 prox_history_lo = 0; +static struct timer_list prox_poll_timer; +static int prox_on = 0; +static int device_released = 0; +static u16 sat_als = 0; +static u16 sat_prox = 0; + +// device reg init values +u8 taos_triton_reg_init[16] = {0x00,0xFF,0XFF,0XFF,0X00,0X00,0XFF,0XFF,0X00,0X00,0XFF,0XFF,0X00,0X00,0X00,0X00}; + +// lux time scale +struct time_scale_factor { + u16 numerator; + u16 denominator; + u16 saturation; +}; +struct time_scale_factor TritonTime = {1, 0, 0}; +struct time_scale_factor *lux_timep = &TritonTime; + +// gain table +u8 taos_triton_gain_table[] = {1, 8, 16, 120}; + +// lux data + +struct lux_data { + u16 ratio; + u16 clear; + u16 ir; + +}; +struct lux_data TritonFN_lux_data[] = { + { 9830, 8320, 15360 }, + { 12452, 10554, 22797 }, + { 14746, 6234, 11430 }, + { 17695, 3968, 6400 }, + { 0, 0, 0 } +}; +struct lux_data *lux_tablep = TritonFN_lux_data; +static int lux_history[TAOS_FILTER_DEPTH] = {-ENODATA, -ENODATA, -ENODATA};//iVIZM + +static int tmd2711_pls_irq; + + + +static irqreturn_t taos_irq_handler(int irq, void *dev_id) //iVIZM +{ + //disable_irq_nosync(tmd2711_pls_irq); + //queue_work(taos_wq,&taos_datap->work); + //printk("^^^^^^^^^^^^^^^^^^ taos_irq_handler ^^^^^^^^^^^^^^^^^^^^\n"); + schedule_work(&taos_datap->work); + //enable_irq(tmd2711_pls_irq); + + return IRQ_HANDLED; +} + +static int taos_get_data(void)//iVIZM +{ + int ret = 0; + + //printk("^^^^^^^^^^^^^^^^^^ taos_get_data ^^^^^^^^^^^^^^^^^^^^\n"); + + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | 0x13)))) < 0) { + //printk(KERN_ERR "TAOS: i2c_smbus_write_byte(1) failed in taos_work_func()\n"); + return (ret); + } + status = i2c_smbus_read_byte(taos_datap->client); + // if (down_interruptible(&taos_datap->update_lock)) + // return -ERESTARTSYS; + printk(" kl# %s, line=%d, status=%x\n", __func__, __LINE__, status); + // status : 5:4 = pint:aint //It has a little error here + // 1:1 = pint:aint + // 1:0 = pint + // 0:1 = aint + if((status & 0x20) == 0x20) { // ps + ret = taos_prox_threshold_set(); + } else if((status & 0x10) == 0x10) { // as + taos_als_threshold_set(); + taos_als_get_data(); + } + //up(&taos_datap->update_lock); + return ret; +} + +static int taos_interrupts_clear(void)//iVIZM +{ + int ret = 0; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_CMD_SPL_FN|0x07)))) < 0) { + //printk(KERN_ERR "TAOS: i2c_smbus_write_byte(2) failed in taos_work_func()\n"); + return (ret); + } + return ret; +} +static void taos_work_func(struct work_struct * work) //iVIZM +{ + wake_lock(&taos_datap->taos_wake_lock); + printk("^^^^^^^^^^^^^^^^^^ taos_work_func ^^^^^^^^^^^^^^^^^^^^\n"); + taos_get_data(); + taos_interrupts_clear(); + wake_unlock(&taos_datap->taos_wake_lock); +} + +static int taos_als_get_data(void)//iVIZM +{ + int ret = 0; + u8 reg_val; + int lux_val = 0; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + //printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_data\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ((reg_val & (TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON)) != (TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON)) + return -ENODATA; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_STATUS)))) < 0) { + //printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_data\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ((reg_val & TAOS_TRITON_STATUS_ADCVALID) != TAOS_TRITON_STATUS_ADCVALID) + return -ENODATA; + if ((lux_val = taos_get_lux()) < 0) + printk(KERN_ERR "TAOS: call to taos_get_lux() returned error %d in ioctl als_data\n", lux_val); + printk("********** before taos_als_get_data ********* lux_val = %d \n",lux_val); + input_report_abs(taos_datap->input_dev,ABS_MISC,lux_val); + input_sync(taos_datap->input_dev); // zwj add test + return ret; +} + + +static int taos_als_threshold_set(void)//iVIZM +{ + int i,ret = 0; + u8 chdata[2]; + u16 ch0; + + printk("kl# %s, line=%d \n", __func__, __LINE__); + + for (i = 0; i < 2; i++) { + chdata[i] = (i2c_smbus_read_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CMD_WORD_BLK_RW | (TAOS_TRITON_ALS_CHAN0LO + i)))); + } + ch0 = chdata[0] + chdata[1]*256; + als_threshold_hi_param = (12*ch0)/10; + if (als_threshold_hi_param >= 65535) + als_threshold_hi_param = 65535; + als_threshold_lo_param = (8*ch0)/10; + als_buf[0] = als_threshold_lo_param & 0x0ff; + als_buf[1] = als_threshold_lo_param >> 8; + als_buf[2] = als_threshold_hi_param & 0x0ff; + als_buf[3] = als_threshold_hi_param >> 8; + + for( mcount=0; mcount<4; mcount++ ) { + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x04) + mcount, als_buf[mcount]))) < 0) { + //printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in taos als threshold set\n"); + return (ret); + } + } + return ret; +} + +static int taos_prox_threshold_set(void)//iVIZM +{ + int i,ret = 0; + u8 chdata[6]; + u16 proxdata = 0; + u16 cleardata = 0; + int data = 0; + + // int ch0,ch1; +// printk("kl# %s, line=%d \n", __func__, __LINE__); + + for (i = 0; i < 6; i++) { + chdata[i] = (i2c_smbus_read_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CMD_WORD_BLK_RW| (TAOS_TRITON_ALS_CHAN0LO + i)))); + } + cleardata = chdata[0] + chdata[1]*256; + proxdata = chdata[4] + chdata[5]*256; + printk("kl## = proxdata = %d, cleardata=%d\n",proxdata, cleardata); + + //ch0= i2c_smbus_read_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | 0x18 )); + //ch1= i2c_smbus_read_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | 0x19 )); + + //printk("================= proxdatatest = %d\n",ch1<<8 |ch0); + + if(prox_on || (proxdata < (taos_cfgp->prox_threshold_lo ))) + { + pro_buf[0] = 0x0; + pro_buf[1] = 0x0; + pro_buf[2] = taos_cfgp->prox_threshold_hi & 0x0ff; + pro_buf[3] = taos_cfgp->prox_threshold_hi >> 8; + data = 1; + printk(KERN_ERR "report pls data 1\n"); + input_report_abs(taos_datap->input_dev,ABS_DISTANCE,data); + input_sync(taos_datap->input_dev); + } + else if(proxdata > (taos_cfgp->prox_threshold_hi) ) + { + if (cleardata > ((sat_als*80)/100)){ + data = 0; + } + else{ + pro_buf[0] = taos_cfgp->prox_threshold_lo & 0x0ff; + pro_buf[1] = taos_cfgp->prox_threshold_lo >> 8; + pro_buf[2] = 0xff; + pro_buf[3] = 0xff; + data = 0; + } + input_report_abs(taos_datap->input_dev,ABS_DISTANCE,data); + + input_sync(taos_datap->input_dev); + } + + // printk("prox_threshold_hi=%d, prox_threshold_lo= %d\n",pro_buf[3]<<8 |pro_buf[2], pro_buf[1]<<8 |pro_buf[0]); + + for( mcount=0; mcount<4; mcount++ ) { + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x08) + mcount, pro_buf[mcount]))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in taos prox threshold set\n"); + return (ret); + } + } + + prox_on = 0; + return ret; +} + +static int taos_reg_init(struct i2c_client *clientp) +{ + int ret = 0; + int i = 0; + unsigned char buf[TAOS_MAX_DEVICE_REGS]; + char *device_name; + int chip_id; + + for (i = 0; i < TAOS_MAX_DEVICE_REGS; i++) { + if ((ret = (i2c_smbus_write_byte(clientp, (TAOS_TRITON_CMD_REG | (TAOS_TRITON_CNTRL + i))))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte() to control reg failed in taos_probe()\n"); + return(ret); + } + buf[i] = i2c_smbus_read_byte(clientp); + } + if ((ret = taos_device_name(buf, &device_name)) == 0) { + printk(KERN_ERR "TAOS: chip id that was read found mismatched by taos_device_name(), in taos_probe()\n"); + return -ENODEV; + } + if (strcmp(device_name, TAOS_DEVICE_ID)) { + printk(KERN_ERR "TAOS: chip id that was read does not match expected id in taos_probe()\n"); + return -ENODEV; + } + else { + printk(KERN_ERR "TAOS: chip id of %s that was read matches expected id in taos_probe()\n", device_name); + device_found = 1; + } + if ((ret = (i2c_smbus_write_byte(clientp, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte() to control reg failed in taos_probe()\n"); + return(ret); + } + strlcpy(clientp->name, TAOS_DEVICE_ID, I2C_NAME_SIZE); + strlcpy(taos_datap->taos_name, TAOS_DEVICE_ID, TAOS_ID_NAME_SIZE); + taos_datap->valid = 0; + if (!(taos_cfgp = kmalloc(sizeof(struct taos_cfg), GFP_KERNEL))) { + printk(KERN_ERR "TAOS: kmalloc for struct taos_cfg failed in taos_probe()\n"); + return -ENOMEM; + } + taos_cfgp->calibrate_target = calibrate_target_param; + taos_cfgp->als_time = als_time_param; + taos_cfgp->scale_factor = scale_factor_param; + taos_cfgp->gain_trim = gain_trim_param; + taos_cfgp->filter_history = filter_history_param; + taos_cfgp->filter_count = filter_count_param; + taos_cfgp->gain = gain_param; + taos_cfgp->als_threshold_hi = als_threshold_hi_param;//iVIZM + taos_cfgp->als_threshold_lo = als_threshold_lo_param;//iVIZM + taos_cfgp->prox_threshold_hi = prox_threshold_hi_param; + taos_cfgp->prox_threshold_lo = prox_threshold_lo_param; + taos_cfgp->prox_int_time = prox_int_time_param; + taos_cfgp->prox_adc_time = prox_adc_time_param; + taos_cfgp->prox_wait_time = prox_wait_time_param; + taos_cfgp->prox_intr_filter = prox_intr_filter_param; + taos_cfgp->prox_config = prox_config_param; + taos_cfgp->prox_pulse_cnt = prox_pulse_cnt_param; + taos_cfgp->prox_gain = prox_gain_param; + sat_als = (256 - taos_cfgp->prox_int_time) << 10; + sat_prox = (256 - taos_cfgp->prox_adc_time) << 10; + + /*dmobile ::power down for init ,Rambo liu*/ + printk("Rambo::light sensor will pwr down \n"); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x00), 0x00))) < 0) { + printk(KERN_ERR "TAOS:Rambo, i2c_smbus_write_byte_data failed in power down\n"); + return (ret); + } +} + +static void tmd2771_pls_pininit(int irq_pin) +{ + printk(KERN_INFO "%s [irq=%d]\n",__func__,irq_pin); + gpio_request(irq_pin, "tmd2771_pls irq"); + gpio_direction_input(irq_pin); +} + + +// client probe +static int taos_probe(struct i2c_client *clientp, const struct i2c_device_id *idp) { + int ret = 0; + int i = 0; + int chip_id; + int ch0,ch1,ch2,ch3,ch4,ch5; + struct regulator *reg_vdd; + //unsigned char buf[TAOS_MAX_DEVICE_REGS]; + //char *device_name; + struct i2c_adapter *adapter = to_i2c_adapter(clientp->dev.parent); + struct tmd2771_pls_platform_data *pdata = clientp->dev.platform_data; + + printk("kl# tmd2771 probe!!!!line=%d\n", __LINE__); + + //reg_vdd = regulator_get(NULL,REGU_NAMES_VDD28); + //regulator_enable(reg_vdd); + + if (!i2c_check_functionality(clientp->adapter, I2C_FUNC_I2C)) { + printk("%s: functionality check failed\n", __func__); + ret = -ENODEV; + goto exit_check_functionality_failed; + } + + taos_datap = kmalloc(sizeof(struct taos_data), GFP_KERNEL); + if (!taos_datap) { + printk(KERN_ERR "TAOS: kmalloc for struct taos_data failed in taos_init()\n"); + //return -ENOMEM; + ret = -ENOMEM; + goto exit_request_memory_failed; + } + memset(taos_datap, 0, sizeof(struct taos_data)); + taos_datap->reg_vdd = reg_vdd; + + // get chip id + chip_id = i2c_smbus_read_byte_data(clientp, (TAOS_TRITON_CMD_REG | (TAOS_TRITON_CNTRL + 0x12))); //iVIZM + printk(" toas1 chip_id = %d\n",chip_id); + if(chip_id < 0) { + ret = chip_id; + goto exit_request_memory_failed; + } + + tmd2771_pls_pininit(pdata->irq_gpio_number); + + + /*get irq*/ + clientp->irq = gpio_to_irq(pdata->irq_gpio_number); + + tmd2711_pls_irq = clientp->irq; + + + taos_datap->client = clientp; + i2c_set_clientdata(clientp, taos_datap); + INIT_WORK(&(taos_datap->work),taos_work_func); + mutex_init(&taos_datap->update_lock); + + // input devices + taos_datap->input_dev = input_allocate_device();//iVIZM + if (taos_datap->input_dev == NULL) { + ret = -ENOMEM; + goto exit_input_dev_allocate_failed; + } + + taos_datap->input_dev->name = TAOS_INPUT_NAME; + taos_datap->input_dev->id.bustype = BUS_I2C; + set_bit(EV_ABS,taos_datap->input_dev->evbit); + set_bit(ABS_MISC, taos_datap->input_dev->absbit); + set_bit(ABS_DISTANCE, taos_datap->input_dev->absbit); + /*for proximity*/ + input_set_abs_params(taos_datap->input_dev, ABS_DISTANCE, 0, 1, 0, 0); + /*for lightsensor*/ + input_set_abs_params(taos_datap->input_dev, ABS_MISC, 0, 100001, 0, 0); + + ret = input_register_device(taos_datap->input_dev); + if (ret < 0) { + printk("%s: input device regist failed\n", __func__); + goto exit_input_register_failed; + } + // chip init + if(taos_reg_init(clientp)<0) + { + printk("%s: device init failed\n", __func__); + ret=-1; + goto exit_device_init_failed; + } + + // create char device + if ((ret = (alloc_chrdev_region(&taos_dev_number, 0, TAOS_MAX_NUM_DEVICES, TAOS_DEVICE_NAME))) < 0) { + printk(KERN_ERR "TAOS: alloc_chrdev_region() failed in taos_init()\n"); + goto exit_chrdev_region_failed; + } + taos_class = class_create(THIS_MODULE, TAOS_DEVICE_NAME); + cdev_init(&taos_datap->cdev, &taos_fops); + taos_datap->cdev.owner = THIS_MODULE; + if ((ret = (cdev_add(&taos_datap->cdev, taos_dev_number, 1))) < 0) { + printk(KERN_ERR "TAOS: cdev_add() failed in taos_init()\n"); + goto exit_chrdev_add_failed; + } + wake_lock_init(&taos_datap->taos_wake_lock, WAKE_LOCK_SUSPEND, "taos-wake-lock"); + device_create(taos_class, NULL, MKDEV(MAJOR(taos_dev_number), 0), &taos_driver ,"taos"); + + + + // interrupt + ret = request_irq(tmd2711_pls_irq,taos_irq_handler, IRQ_TYPE_EDGE_FALLING, "taos_irq",taos_datap);//iVIZM + if (ret != 0) { + goto irq_request_err; + } + disable_irq(tmd2711_pls_irq);//iVIZM + for(i = 0; i < 50; i++) + taos_als_get_data(); + + printk("%s probe success!\n", __func__); + return (ret); + +irq_request_err: + device_destroy(taos_class, MKDEV(MAJOR(taos_dev_number), 0)); + gpio_free(pdata->irq_gpio_number); +exit_chrdev_add_failed: + cdev_del(&taos_datap->cdev); + class_destroy(taos_class); +exit_chrdev_region_failed: + unregister_chrdev_region(taos_dev_number, TAOS_MAX_NUM_DEVICES); +exit_device_init_failed: +exit_input_register_failed: + input_free_device(taos_datap->input_dev); +exit_input_dev_allocate_failed: +exit_device_register_failed: + kfree(taos_datap); +exit_request_memory_failed: +exit_check_functionality_failed: + //wake_lock_destroy(&pls_delayed_work_wake_lock); + printk("kl %s: Probe Fail!\n",__func__); + return ret; +} +# if 0 +//resume iVIZM +static int taos_resume(struct i2c_client *client) { + u8 reg_val = 0,reg_cntrl = 0; + int ret = -1; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in taos_resume\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ( taos_datap->working == 1) { + taos_datap->working = 0; + reg_cntrl = reg_val | TAOS_TRITON_CNTL_PWRON; + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), reg_cntrl))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_off\n"); + return (ret); + } + } + enable_irq(tmd2711_pls_irq); + return ret; +} + +//suspend iVIZM +static int taos_suspend(struct i2c_client *client, pm_message_t mesg) { + u8 reg_val = 0,reg_cntrl = 0; + int ret = -1; + disable_irq(tmd2711_pls_irq); + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in taos_resume\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if (reg_val & TAOS_TRITON_CNTL_PWRON) { + taos_datap->working = 1; + reg_cntrl = reg_val & (~TAOS_TRITON_CNTL_PWRON); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), reg_cntrl))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in taos_suspend\n"); + return (ret); + } + } + return ret; +} +#endif +// client remove +static int __devexit taos_remove(struct i2c_client *client) { + int ret = 0; + +#if 1 // zwj mv from taos_exit + unregister_chrdev_region(taos_dev_number, TAOS_MAX_NUM_DEVICES); + device_destroy(taos_class, MKDEV(MAJOR(taos_dev_number), 0)); + cdev_del(&taos_datap->cdev); + class_destroy(taos_class); + disable_irq(tmd2711_pls_irq);//iVIZM + //if (taos_wq) + // destroy_workqueue(taos_wq);//iVIZM + kfree(taos_datap); +#endif + + return (ret); +} + +// open +static int taos_open(struct inode *inode, struct file *file) { + struct taos_data *taos_datap; + int ret = 0; + + device_released = 0; + taos_datap = container_of(inode->i_cdev, struct taos_data, cdev); + if (strcmp(taos_datap->taos_name, TAOS_DEVICE_ID) != 0) { + printk(KERN_ERR "TAOS: device name incorrect during taos_open(), get %s\n", taos_datap->taos_name); + ret = -ENODEV; + } + + enable_irq(tmd2711_pls_irq);//iVIZM + return (ret); +} + +// release +static int taos_release(struct inode *inode, struct file *file) { + struct taos_data *taos_datap; + int ret = 0; + + device_released = 1; + prox_on = 0; + prox_history_hi = 0; + prox_history_lo = 0; + taos_datap = container_of(inode->i_cdev, struct taos_data, cdev); + if (strcmp(taos_datap->taos_name, TAOS_DEVICE_ID) != 0) { + printk(KERN_ERR "TAOS: device name incorrect during taos_release(), get %s\n", taos_datap->taos_name); + ret = -ENODEV; + } + disable_irq(tmd2711_pls_irq); + return (ret); +} + +static int taos_sensors_als_on(void) { + int ret = 0, i = 0; + u8 itime = 0, reg_val = 0, reg_cntrl = 0; + //int lux_val = 0, ret = 0, i = 0, tmp = 0; + +// printk("kl# %s, line=%d \n", __func__, __LINE__); + + for (i = 0; i < TAOS_FILTER_DEPTH; i++) + lux_history[i] = -ENODATA; + //taos_als_threshold_set();//iVIZM + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_CMD_SPL_FN|TAOS_TRITON_CMD_ALS_INTCLR)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_on\n"); + return (ret); + } + itime = (((taos_cfgp->als_time/50) * 18) - 1); + itime = (~itime); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_ALS_TIME), itime))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_INTERRUPT), taos_cfgp->prox_intr_filter))) < 0) {//golden + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_GAIN)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_on\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + reg_val = reg_val & 0xFC; + reg_val = reg_val | (taos_cfgp->gain & 0x03); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_GAIN), reg_val))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + //if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + // printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_calibrate\n"); + // return (ret); + //} + //reg_val = i2c_smbus_read_byte(taos_datap->client); + //reg_cntrl = reg_val | (TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_ALS_INT_ENBL); + reg_cntrl = (TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_ALS_INT_ENBL); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), reg_cntrl))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + + mdelay(100); + + taos_als_threshold_set();//iVIZM + return ret; +} + +// ioctls +//static int taos_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { +static int taos_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + int prox_sum = 0, prox_mean = 0, prox_max = 0; + int lux_val = 0, ret = 0, i = 0, tmp = 0; + u16 gain_trim_val = 0; + u8 reg_val = 0, reg_cntrl = 0; + int ret_check=0; + int ret_m=0; + u8 reg_val_temp=0; + + printk("k#### %s, line=%d \n", __func__, __LINE__); + + switch (cmd) { + case TAOS_IOCTL_SENSOR_CHECK: + reg_val_temp=0; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_CHECK failed\n"); + return (ret); + } + reg_val_temp = i2c_smbus_read_byte(taos_datap->client); + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_CHECK,prox_adc_time,%d~\n",reg_val_temp); + if ((reg_val_temp & 0xFF) == 0xF) + return -ENODATA; + + break; + + case TAOS_IOCTL_SENSOR_CONFIG: + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_CONFIG,test01~\n"); + ret = copy_from_user(taos_cfgp, (struct taos_cfg *)arg, sizeof(struct taos_cfg)); + if (ret) { + printk(KERN_ERR "TAOS: copy_from_user failed in ioctl config_set\n"); + return -ENODATA; + } + + break; + + case TAOS_IOCTL_SENSOR_ON: + ret=0; + reg_val=0; + ret_m=0; + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test01~\n"); + +#if 0 + /*decide the numbers of sensors*/ + ret_m=taos_sensor_mark(); + if(ret_m <0){ + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, error~~\n"); + return -1; + } + if(ret_m ==0){ + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, num >0 ~~\n"); + return 1; + } +#endif +#if 1 + /*Register init and turn off */ + for (i = 0; i < TAOS_FILTER_DEPTH; i++){ + /*Rambo ??*/ + lux_history[i] = -ENODATA; + } +#endif + +#if 0 + for (i = 0; i < sizeof(taos_triton_reg_init); i++){ + if(i !=11){ + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|(TAOS_TRITON_CNTRL +i)), taos_triton_reg_init[i]))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + } + } +#endif + /*ALS interrupt clear*/ + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_CMD_SPL_FN|TAOS_TRITON_CMD_ALS_INTCLR)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_on\n"); + return (ret); + } + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test02~\n"); + /*Register setting*/ +#if 0 + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x00), 0x00))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } +#endif + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test03,prox_int_time,%d~\n",taos_cfgp->prox_int_time); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_ALS_TIME), taos_cfgp->prox_int_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test04,prox_adc_time,%d~\n",taos_cfgp->prox_adc_time); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_PRX_TIME), taos_cfgp->prox_adc_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test05,prox_wait_time,%d~\n", taos_cfgp->prox_wait_time); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_WAIT_TIME), taos_cfgp->prox_wait_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test05-1,prox_intr_filter,%d~\n", taos_cfgp->prox_intr_filter); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_INTERRUPT), taos_cfgp->prox_intr_filter))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test06,prox_config,%d~\n",taos_cfgp->prox_config); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_PRX_CFG), taos_cfgp->prox_config))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test06,prox_pulse_cnt,%d~\n",taos_cfgp->prox_pulse_cnt); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_PRX_COUNT), taos_cfgp->prox_pulse_cnt))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + /*gain*/ +#if 0 + reg_val_temp=0; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_GAIN)))) < 0) { + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON failed in ioctl als_calibrate\n"); + return (ret); + } + reg_val_temp = i2c_smbus_read_byte(taos_datap->client); + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test07-1,gain_init&0xC,%d~\n",reg_val_temp&0xC); + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test07-2,prox_gain&0xFC,%d~\n",(taos_cfgp->prox_gain&0xFC)); + reg_val_temp=(taos_cfgp->prox_gain&0xFC)|(reg_val_temp&0xC); + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test07,%d~\n",reg_val_temp); +#endif + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_GAIN), taos_cfgp->prox_gain))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + /*turn on*/ +#if 0 + reg_val_temp=0; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON failed in ioctl als_calibrate\n"); + return (ret); + } + reg_val_temp = i2c_smbus_read_byte(taos_datap->client); + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test08-1,%d~\n",reg_val_temp); + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test08-2,%d~\n",(reg_val_temp&0x40)); + reg_val_temp=(0xF&0x3F)|(reg_val_temp&0x40); + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, test08,%d~\n",reg_val_temp); +#endif + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_CNTRL), 0xF))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + break; + + + case TAOS_IOCTL_SENSOR_OFF: + ret=0; + reg_val=0; + ret_check=0; + ret_m=0; + +#if 0 + /*chech the num of used sensor*/ + ret_check=taos_sensor_check(); + + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_OFF,test01~\n"); + if(ret_check <0){ + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_OFF, error~~\n"); + return -1; + } + if(ret_check ==0){ + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_ON, num >1 ~~\n"); + return 1; + } +#endif + /*turn off*/ + printk(KERN_ERR "TAOS: TAOS_IOCTL_SENSOR_OFF,test02~\n"); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x00), 0x00))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_off\n"); + return (ret); + } + + break; + case TAOS_IOCTL_ALS_ON: + printk("####################################\n"); + printk("######## TAOS IOCTL ALS ON #########\n"); + printk("####################################\n"); + + + printk("taos==== TAOS IOCTL ALS ON ===00==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_calibrate\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + + if ((reg_val & TAOS_TRITON_CNTL_PROX_DET_ENBL) == 0x0) { + printk("taos==== TAOS_IOCTL_ALS_ON ===TAOS_IOCTL_PROX_OFF\n"); + taos_sensors_als_on(); + } + + /* for (i = 0; i < TAOS_FILTER_DEPTH; i++) + lux_history[i] = -ENODATA; + //taos_als_threshold_set();//iVIZM + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_CMD_SPL_FN|TAOS_TRITON_CMD_ALS_INTCLR)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_on\n"); + return (ret); + } + itime = (((taos_cfgp->als_time/50) * 18) - 1); + itime = (~itime); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_ALS_TIME), itime))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|TAOS_TRITON_INTERRUPT), taos_cfgp->prox_intr_filter))) < 0) {//golden + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_GAIN)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_on\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + reg_val = reg_val & 0xFC; + reg_val = reg_val | (taos_cfgp->gain & 0x03); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_GAIN), reg_val))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_calibrate\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + reg_cntrl = reg_val | (TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_ALS_INT_ENBL); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), reg_cntrl))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + taos_als_threshold_set();//iVIZM + */ + ALS_ON = 1; + printk("taos==== TAOS IOCTL ALS ON ===11==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + return (ret); + break; + case TAOS_IOCTL_ALS_OFF: + printk("#####################################\n"); + printk("######## TAOS IOCTL ALS OFF #########\n"); + printk("#####################################\n"); + + printk("taos==== TAOS IOCTL ALS OFF ===00==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + + for (i = 0; i < TAOS_FILTER_DEPTH; i++) + lux_history[i] = -ENODATA; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_calibrate\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ((reg_val & TAOS_TRITON_CNTL_PROX_DET_ENBL) == 0x0) { + + printk("taos==== TAOS_IOCTL_ALS_OFF ===TAOS_IOCTL_PROX_OFF\n"); + + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), 0x00))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_off\n"); + return (ret); + } + cancel_work_sync(&taos_datap->work);//golden + // reg_cntrl = reg_val & (~TAOS_TRITON_CNTL_ALS_INT_ENBL); + // } else { + // reg_cntrl = reg_val & (~(TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_ALS_INT_ENBL)); + // printk("===========cancel_work_sync======als====\n"); + // cancel_work_sync(&taos_datap->work);//golden + // } + // if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), reg_cntrl))) < 0) { + // printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_off\n"); + // return (ret); + } + ALS_ON = 0; + printk("taos==== TAOS IOCTL ALS OFF ===11==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + return (ret); + break; + case TAOS_IOCTL_ALS_DATA: + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_data\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ((reg_val & (TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON)) != (TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_PWRON)) + return -ENODATA; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_STATUS)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_data\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ((reg_val & TAOS_TRITON_STATUS_ADCVALID) != TAOS_TRITON_STATUS_ADCVALID) + return -ENODATA; + if ((lux_val = taos_get_lux()) < 0) + printk(KERN_ERR "TAOS: call to taos_get_lux() returned error %d in ioctl als_data\n", lux_val); + //lux_val = taos_lux_filter(lux_val); + return (lux_val); + break; + case TAOS_IOCTL_ALS_CALIBRATE: + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_calibrate\n"); + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ((reg_val & 0x07) != 0x07) + return -ENODATA; + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_STATUS)))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_calibrate\n"); + + return (ret); + } + reg_val = i2c_smbus_read_byte(taos_datap->client); + if ((reg_val & 0x01) != 0x01) + return -ENODATA; + if ((lux_val = taos_get_lux()) < 0) { + printk(KERN_ERR "TAOS: call to lux_val() returned error %d in ioctl als_data\n", lux_val); + return (lux_val); + } + gain_trim_val = (u16)(((taos_cfgp->calibrate_target) * 512)/lux_val); + taos_cfgp->gain_trim = (int)gain_trim_val; + return ((int)gain_trim_val); + break; + case TAOS_IOCTL_CONFIG_GET: + ret = copy_to_user((struct taos_cfg *)arg, taos_cfgp, sizeof(struct taos_cfg)); + if (ret) { + printk(KERN_ERR "TAOS: copy_to_user failed in ioctl config_get\n"); + return -ENODATA; + } + return (ret); + break; + case TAOS_IOCTL_CONFIG_SET: + printk("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + printk("^^^^^^^^^ TAOS INCTL CONFIG SET ^^^^^^^\n"); + printk("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + ret = copy_from_user(taos_cfgp, (struct taos_cfg *)arg, sizeof(struct taos_cfg)); + if (ret) { + printk(KERN_ERR "TAOS: copy_from_user failed in ioctl config_set\n"); + return -ENODATA; + } + if(taos_cfgp->als_time < 50) + taos_cfgp->als_time = 50; + if(taos_cfgp->als_time > 650) + taos_cfgp->als_time = 650; + tmp = (taos_cfgp->als_time + 25)/50; + taos_cfgp->als_time = tmp*50; + sat_als = (256 - taos_cfgp->prox_int_time) << 10; + sat_prox = (256 - taos_cfgp->prox_adc_time) << 10; + break; + case TAOS_IOCTL_PROX_ON: + printk("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + printk("^^^^^^^^^ TAOS IOCTL PROX ON ^^^^^^^\n"); + printk("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + + printk("taos==== TAOS_IOCTL_PROX_ON ===00==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + + prox_on = 1; + + + reg_cntrl = TAOS_TRITON_CNTL_PROX_DET_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_PROX_INT_ENBL | + TAOS_TRITON_CNTL_ADC_ENBL | TAOS_TRITON_CNTL_WAIT_TMR_ENBL ; + + printk("taos==== TAOS_IOCTL_PROX_ON ===00==reg_cntrl=%x\n",reg_cntrl); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), reg_cntrl))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x01), taos_cfgp->prox_int_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x02), taos_cfgp->prox_adc_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x03), taos_cfgp->prox_wait_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x0C), taos_cfgp->prox_intr_filter))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x0D), taos_cfgp->prox_config))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x0E), taos_cfgp->prox_pulse_cnt))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x0F), taos_cfgp->prox_gain))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + //if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + // printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl prox_on\n"); + // return (ret); + //} + //reg_val = i2c_smbus_read_byte(taos_datap->client); + //reg_cntrl = reg_val | (TAOS_TRITON_CNTL_PROX_DET_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_PROX_INT_ENBL | TAOS_TRITON_CNTL_ADC_ENBL); + + mdelay(100); + + taos_prox_threshold_set();//iVIZM + + printk("taos==== TAOS_IOCTL_PROX_ON ===11==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + break; + case TAOS_IOCTL_PROX_OFF: + printk("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + printk("^^^^^^^^^ TAOS IOCTL PROX OFF ^^^^^^^\n"); + printk("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"); + + + printk("taos==== TAOS_IOCTL_PROX_OFF ===00==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + //if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + // printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl als_calibrate\n"); + // return (ret); + //} + //reg_val = i2c_smbus_read_byte(taos_datap->client); + //if (reg_val & TAOS_TRITON_CNTL_ALS_INT_ENBL) { + // reg_cntrl = reg_val & (~(TAOS_TRITON_CNTL_PROX_DET_ENBL | TAOS_TRITON_CNTL_PROX_INT_ENBL)); + //} else { + // reg_cntrl = reg_val & (~(TAOS_TRITON_CNTL_PROX_DET_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_PROX_INT_ENBL)); + //reg_cntrl = reg_val & (~(TAOS_TRITON_CNTL_PROX_DET_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_PROX_INT_ENBL)); + // printk("===========cancel_work_sync=====prox=====\n"); + // cancel_work_sync(&taos_datap->work);//golden + //} + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), 0x00))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_off\n"); + return (ret); + } + if (ALS_ON == 1) { + taos_sensors_als_on(); + } else { + cancel_work_sync(&taos_datap->work);//golden + } + prox_on = 0; + printk("taos==== TAOS_IOCTL_PROX_OFF ===11==ALS_ON=%d prox_on=%d\n",ALS_ON,prox_on); + break; + case TAOS_IOCTL_PROX_DATA: + //Rambo +#if 1 + if ((ret = taos_prox_poll(prox_cur_infop)) < 0) { + printk(KERN_ERR "TAOS: call to prox_poll failed in taos_prox_poll_timer_func()\n"); + return ret; + } +#endif + ret = copy_to_user((struct taos_prox_info *)arg, prox_cur_infop, sizeof(struct taos_prox_info)); + if (ret) { + printk(KERN_ERR "TAOS: copy_to_user failed in ioctl prox_data\n"); + return -ENODATA; + } + return (ret); + break; + case TAOS_IOCTL_PROX_EVENT: + if ((ret = taos_prox_poll(prox_cur_infop)) < 0) { + printk(KERN_ERR "TAOS: call to prox_poll failed in taos_prox_poll_timer_func()\n"); + return ret; + } + + return (prox_cur_infop->prox_event); + break; + case TAOS_IOCTL_PROX_CALIBRATE: + //if (prox_on) golden + //del_timer(&prox_poll_timer); + //else { + //printk(KERN_ERR "TAOS: ioctl prox_calibrate was called before ioctl prox_on was called\n"); + //return -EPERM; + //} + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x01), taos_cfgp->prox_int_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x02), taos_cfgp->prox_adc_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x03), taos_cfgp->prox_wait_time))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x0D), taos_cfgp->prox_config))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x0E), taos_cfgp->prox_pulse_cnt))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|0x0F), taos_cfgp->prox_gain))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + //if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL)))) < 0) { + // printk(KERN_ERR "TAOS: i2c_smbus_write_byte failed in ioctl prox_on\n"); + // return (ret); + //} + //reg_val = i2c_smbus_read_byte(taos_datap->client); + reg_cntrl = reg_val | (TAOS_TRITON_CNTL_PROX_DET_ENBL | TAOS_TRITON_CNTL_PWRON | TAOS_TRITON_CNTL_ADC_ENBL); + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CNTRL), reg_cntrl))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl prox_on\n"); + return (ret); + } + + prox_sum = 0; + prox_max = 0; + for (i = 0; i < 20; i++) { + if ((ret = taos_prox_poll(&prox_cal_info[i])) < 0) { + printk(KERN_ERR "TAOS: call to prox_poll failed in ioctl prox_calibrate\n"); + return (ret); + } + prox_sum += prox_cal_info[i].prox_data; + if (prox_cal_info[i].prox_data > prox_max) + prox_max = prox_cal_info[i].prox_data; + mdelay(100); + } + prox_mean = prox_sum/20; + taos_cfgp->prox_threshold_hi = ((((prox_max - prox_mean) * 200) + 50)/100) + prox_mean; + taos_cfgp->prox_threshold_lo = ((((prox_max - prox_mean) * 170) + 50)/100) + prox_mean; + printk("TAOS:------------ taos_cfgp->prox_threshold_hi = %d\n",taos_cfgp->prox_threshold_hi ); + printk("TAOS:------------ taos_cfgp->prox_threshold_lo = %d\n",taos_cfgp->prox_threshold_lo ); + // if (taos_cfgp->prox_threshold_lo < ((sat_prox*12)/100)) { + // taos_cfgp->prox_threshold_lo = ((sat_prox*12)/100); + // taos_cfgp->prox_threshold_hi = ((sat_prox*15)/100); + // } + for (i = 0; i < sizeof(taos_triton_reg_init); i++){ + if(i !=11){ + if ((ret = (i2c_smbus_write_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG|(TAOS_TRITON_CNTRL +i)), taos_triton_reg_init[i]))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte_data failed in ioctl als_on\n"); + return (ret); + } + } + } + + //taos_prox_poll_timer_start();//Rambo + break; + default: + return -EINVAL; + break; + } + return (ret); +} + +// read/calculate lux value +static int taos_get_lux(void) { + u16 raw_clear = 0, raw_ir = 0, raw_lux = 0; + u32 lux = 0; + u32 ratio = 0; + u8 dev_gain = 0; + u16 Tint = 0; + struct lux_data *p; + int ret = 0; + u8 chdata[4]; + int tmp = 0, i = 0; + + for (i = 0; i < 4; i++) { + if ((ret = (i2c_smbus_write_byte(taos_datap->client, (TAOS_TRITON_CMD_REG | (TAOS_TRITON_ALS_CHAN0LO + i))))) < 0) { + printk(KERN_ERR "TAOS: i2c_smbus_write_byte() to chan0/1/lo/hi reg failed in taos_get_lux()\n"); + return (ret); + } + chdata[i] = i2c_smbus_read_byte(taos_datap->client); + printk("ch(%d),data=%d\n",i,chdata[i]); + } + printk("ch0=%d\n",chdata[0]+chdata[1]*256); + printk("ch1=%d\n",chdata[2]+chdata[3]*256); + + tmp = (taos_cfgp->als_time + 25)/50; //if atime =100 tmp = (atime+25)/50=2.5 tine = 2.7*(256-atime)= 412.5 + TritonTime.numerator = 1; + TritonTime.denominator = tmp; + + tmp = 300 * taos_cfgp->als_time; //tmp = 300*atime 400 + if(tmp > 65535) + tmp = 65535; + TritonTime.saturation = tmp; + raw_clear = chdata[1]; + raw_clear <<= 8; + raw_clear |= chdata[0]; + raw_ir = chdata[3]; + raw_ir <<= 8; + raw_ir |= chdata[2]; + + raw_clear *= (taos_cfgp->scale_factor *11 ); + raw_ir *= (taos_cfgp->scale_factor *3 ); + + if(raw_ir > raw_clear) { + raw_lux = raw_ir; + raw_ir = raw_clear; + raw_clear = raw_lux; + } + dev_gain = taos_triton_gain_table[taos_cfgp->gain & 0x3]; + if(raw_clear >= lux_timep->saturation) + return(TAOS_MAX_LUX); + if(raw_ir >= lux_timep->saturation) + return(TAOS_MAX_LUX); + if(raw_clear == 0) + return(0); + if(dev_gain == 0 || dev_gain > 127) { + printk(KERN_ERR "TAOS: dev_gain = 0 or > 127 in taos_get_lux()\n"); + return -1; + } + if(lux_timep->denominator == 0) { + printk(KERN_ERR "TAOS: lux_timep->denominator = 0 in taos_get_lux()\n"); + return -1; + } + ratio = (raw_ir<<15)/raw_clear; + for (p = lux_tablep; p->ratio && p->ratio < ratio; p++); + if(!p->ratio) {//iVIZM + if(lux_history[0] < 0) + return 0; + else + return lux_history[0]; + } + Tint = taos_cfgp->als_time; + raw_clear = ((raw_clear*400 + (dev_gain>>1))/dev_gain + (Tint>>1))/Tint; + raw_ir = ((raw_ir*400 +(dev_gain>>1))/dev_gain + (Tint>>1))/Tint; + lux = ((raw_clear*(p->clear)) - (raw_ir*(p->ir))); + lux = (lux + 32768)/65536; + if(lux > TAOS_MAX_LUX) { + lux = TAOS_MAX_LUX; + } + //return(lux)*taos_cfgp->filter_count; + return(lux); +} + +/*static int taos_lux_filter(int lux) +{ + static u8 middle[] = {1,0,2,0,0,2,0,1}; + int index; + + lux_history[2] = lux_history[1]; + lux_history[1] = lux_history[0]; + lux_history[0] = lux; + + if(lux_history[2] < 0) { //iVIZM + if(lux_history[1] > 0) + return lux_history[1]; + else + return lux_history[0]; + } + index = 0; + if( lux_history[0] > lux_history[1] ) + index += 4; + if( lux_history[1] > lux_history[2] ) + index += 2; + if( lux_history[0] > lux_history[2] ) + index++; + return(lux_history[middle[index]]); +}*/ + + +// verify device +static int taos_device_name(unsigned char *bufp, char **device_name) { + if( (bufp[0x12]&0x00)!=0x00) + return(0); + //if(bufp[0x10]|bufp[0x1a]|bufp[0x1b]|bufp[0x1c]|bufp[0x1d]|bufp[0x1e]) + // return(0); + if(bufp[0x13]&0x0c) + return(0); + *device_name="tritonFN"; + return(1); +} + +// proximity poll +static int taos_prox_poll(struct taos_prox_info *prxp) { + //static int event = 0; + //u16 status = 0; + int i = 0, ret = 0; //wait_count = 0; + u8 chdata[6]; + + for (i = 0; i < 6; i++) { + chdata[i] = (i2c_smbus_read_byte_data(taos_datap->client, (TAOS_TRITON_CMD_REG | TAOS_TRITON_CMD_AUTO | (TAOS_TRITON_ALS_CHAN0LO + i)))); + } + prxp->prox_clear = chdata[1]; + prxp->prox_clear <<= 8; + prxp->prox_clear |= chdata[0]; + if (prxp->prox_clear > ((sat_als*80)/100)) + return -ENODATA; + prxp->prox_data = chdata[5]; + prxp->prox_data <<= 8; + prxp->prox_data |= chdata[4]; + + return (ret); +} + +// prox poll timer function +static void taos_prox_poll_timer_func(unsigned long param) { + int ret = 0; + + if (!device_released) { + if ((ret = taos_prox_poll(prox_cur_infop)) < 0) { + printk(KERN_ERR "TAOS: call to prox_poll failed in taos_prox_poll_timer_func()\n"); + return; + } + taos_prox_poll_timer_start(); + } + return; +} + +// start prox poll timer +static void taos_prox_poll_timer_start(void) { + init_timer(&prox_poll_timer); + prox_poll_timer.expires = jiffies + (HZ/10); + prox_poll_timer.function = taos_prox_poll_timer_func; + add_timer(&prox_poll_timer); + return; +} + +// driver init +static int __init taos_init(void) { + int ret = 0, i = 0, k = 0; + //struct i2c_adapter *my_adap; + + //tmd2711_pls_config_pins(); + //LDO_TurnOnLDO(LDO_LDO_VDD28); + //printk(KERN_ERR "TAOS:init()\n"); + + if ((ret = (i2c_add_driver(&taos_driver))) < 0) { + printk(KERN_ERR "TAOS: i2c_add_driver() failed in taos_init()\n"); + return (ret); + } + //printk(KERN_ERR "TAOS:end(ret [%d])\n ",ret); + return (ret); +} + +// driver exit +static void __exit taos_exit(void) { + i2c_del_driver(&taos_driver); +} + +MODULE_AUTHOR("John Koshi - Surya Software"); +MODULE_DESCRIPTION("TAOS ambient light and proximity sensor driver"); +MODULE_LICENSE("GPL"); + +module_init(taos_init); +module_exit(taos_exit); + diff --git a/drivers/input/misc/twl4030-pwrbutton.c b/drivers/input/misc/twl4030-pwrbutton.c new file mode 100644 index 00000000..b9a05fda --- /dev/null +++ b/drivers/input/misc/twl4030-pwrbutton.c @@ -0,0 +1,125 @@ +/** + * twl4030-pwrbutton.c - TWL4030 Power Button Input Driver + * + * Copyright (C) 2008-2009 Nokia Corporation + * + * Written by Peter De Schrijver <peter.de-schrijver@nokia.com> + * Several fixes by Felipe Balbi <felipe.balbi@nokia.com> + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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/init.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/i2c/twl.h> + +#define PWR_PWRON_IRQ (1 << 0) + +#define STS_HW_CONDITIONS 0xf + +static irqreturn_t powerbutton_irq(int irq, void *_pwr) +{ + struct input_dev *pwr = _pwr; + int err; + u8 value; + + err = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &value, STS_HW_CONDITIONS); + if (!err) { + pm_wakeup_event(pwr->dev.parent, 0); + input_report_key(pwr, KEY_POWER, value & PWR_PWRON_IRQ); + input_sync(pwr); + } else { + dev_err(pwr->dev.parent, "twl4030: i2c error %d while reading" + " TWL4030 PM_MASTER STS_HW_CONDITIONS register\n", err); + } + + return IRQ_HANDLED; +} + +static int __init twl4030_pwrbutton_probe(struct platform_device *pdev) +{ + struct input_dev *pwr; + int irq = platform_get_irq(pdev, 0); + int err; + + pwr = input_allocate_device(); + if (!pwr) { + dev_dbg(&pdev->dev, "Can't allocate power button\n"); + return -ENOMEM; + } + + pwr->evbit[0] = BIT_MASK(EV_KEY); + pwr->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + pwr->name = "twl4030_pwrbutton"; + pwr->phys = "twl4030_pwrbutton/input0"; + pwr->dev.parent = &pdev->dev; + + err = request_threaded_irq(irq, NULL, powerbutton_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "twl4030_pwrbutton", pwr); + if (err < 0) { + dev_dbg(&pdev->dev, "Can't get IRQ for pwrbutton: %d\n", err); + goto free_input_dev; + } + + err = input_register_device(pwr); + if (err) { + dev_dbg(&pdev->dev, "Can't register power button: %d\n", err); + goto free_irq; + } + + platform_set_drvdata(pdev, pwr); + + return 0; + +free_irq: + free_irq(irq, pwr); +free_input_dev: + input_free_device(pwr); + return err; +} + +static int __exit twl4030_pwrbutton_remove(struct platform_device *pdev) +{ + struct input_dev *pwr = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + free_irq(irq, pwr); + input_unregister_device(pwr); + + return 0; +} + +static struct platform_driver twl4030_pwrbutton_driver = { + .remove = __exit_p(twl4030_pwrbutton_remove), + .driver = { + .name = "twl4030_pwrbutton", + .owner = THIS_MODULE, + }, +}; + +module_platform_driver_probe(twl4030_pwrbutton_driver, + twl4030_pwrbutton_probe); + +MODULE_ALIAS("platform:twl4030_pwrbutton"); +MODULE_DESCRIPTION("Triton2 Power Button"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Peter De Schrijver <peter.de-schrijver@nokia.com>"); +MODULE_AUTHOR("Felipe Balbi <felipe.balbi@nokia.com>"); + diff --git a/drivers/input/misc/twl4030-vibra.c b/drivers/input/misc/twl4030-vibra.c new file mode 100644 index 00000000..68a5f331 --- /dev/null +++ b/drivers/input/misc/twl4030-vibra.c @@ -0,0 +1,263 @@ +/* + * twl4030-vibra.c - TWL4030 Vibrator driver + * + * Copyright (C) 2008-2010 Nokia Corporation + * + * Written by Henrik Saari <henrik.saari@nokia.com> + * Updates by Felipe Balbi <felipe.balbi@nokia.com> + * Input by Jari Vanhala <ext-jari.vanhala@nokia.com> + * + * 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/jiffies.h> +#include <linux/platform_device.h> +#include <linux/of.h> +#include <linux/workqueue.h> +#include <linux/i2c/twl.h> +#include <linux/mfd/twl4030-audio.h> +#include <linux/input.h> +#include <linux/slab.h> + +/* MODULE ID2 */ +#define LEDEN 0x00 + +/* ForceFeedback */ +#define EFFECT_DIR_180_DEG 0x8000 /* range is 0 - 0xFFFF */ + +struct vibra_info { + struct device *dev; + struct input_dev *input_dev; + + struct work_struct play_work; + + bool enabled; + int speed; + int direction; + + bool coexist; +}; + +static void vibra_disable_leds(void) +{ + u8 reg; + + /* Disable LEDA & LEDB, cannot be used with vibra (PWM) */ + twl_i2c_read_u8(TWL4030_MODULE_LED, ®, LEDEN); + reg &= ~0x03; + twl_i2c_write_u8(TWL4030_MODULE_LED, LEDEN, reg); +} + +/* Powers H-Bridge and enables audio clk */ +static void vibra_enable(struct vibra_info *info) +{ + u8 reg; + + twl4030_audio_enable_resource(TWL4030_AUDIO_RES_POWER); + + /* turn H-Bridge on */ + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, + ®, TWL4030_REG_VIBRA_CTL); + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + (reg | TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL); + + twl4030_audio_enable_resource(TWL4030_AUDIO_RES_APLL); + + info->enabled = true; +} + +static void vibra_disable(struct vibra_info *info) +{ + u8 reg; + + /* Power down H-Bridge */ + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, + ®, TWL4030_REG_VIBRA_CTL); + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + (reg & ~TWL4030_VIBRA_EN), TWL4030_REG_VIBRA_CTL); + + twl4030_audio_disable_resource(TWL4030_AUDIO_RES_APLL); + twl4030_audio_disable_resource(TWL4030_AUDIO_RES_POWER); + + info->enabled = false; +} + +static void vibra_play_work(struct work_struct *work) +{ + struct vibra_info *info = container_of(work, + struct vibra_info, play_work); + int dir; + int pwm; + u8 reg; + + dir = info->direction; + pwm = info->speed; + + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, + ®, TWL4030_REG_VIBRA_CTL); + if (pwm && (!info->coexist || !(reg & TWL4030_VIBRA_SEL))) { + + if (!info->enabled) + vibra_enable(info); + + /* set vibra rotation direction */ + twl_i2c_read_u8(TWL4030_MODULE_AUDIO_VOICE, + ®, TWL4030_REG_VIBRA_CTL); + reg = (dir) ? (reg | TWL4030_VIBRA_DIR) : + (reg & ~TWL4030_VIBRA_DIR); + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + reg, TWL4030_REG_VIBRA_CTL); + + /* set PWM, 1 = max, 255 = min */ + twl_i2c_write_u8(TWL4030_MODULE_AUDIO_VOICE, + 256 - pwm, TWL4030_REG_VIBRA_SET); + } else { + if (info->enabled) + vibra_disable(info); + } +} + +/*** Input/ForceFeedback ***/ + +static int vibra_play(struct input_dev *input, void *data, + struct ff_effect *effect) +{ + struct vibra_info *info = input_get_drvdata(input); + + info->speed = effect->u.rumble.strong_magnitude >> 8; + if (!info->speed) + info->speed = effect->u.rumble.weak_magnitude >> 9; + info->direction = effect->direction < EFFECT_DIR_180_DEG ? 0 : 1; + schedule_work(&info->play_work); + return 0; +} + +static void twl4030_vibra_close(struct input_dev *input) +{ + struct vibra_info *info = input_get_drvdata(input); + + cancel_work_sync(&info->play_work); + + if (info->enabled) + vibra_disable(info); +} + +/*** Module ***/ +#ifdef CONFIG_PM_SLEEP +static int twl4030_vibra_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vibra_info *info = platform_get_drvdata(pdev); + + if (info->enabled) + vibra_disable(info); + + return 0; +} + +static int twl4030_vibra_resume(struct device *dev) +{ + vibra_disable_leds(); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(twl4030_vibra_pm_ops, + twl4030_vibra_suspend, twl4030_vibra_resume); + +static bool twl4030_vibra_check_coexist(struct twl4030_vibra_data *pdata, + struct device_node *node) +{ + if (pdata && pdata->coexist) + return true; + + if (of_find_node_by_name(node, "codec")) + return true; + + return false; +} + +static int twl4030_vibra_probe(struct platform_device *pdev) +{ + struct twl4030_vibra_data *pdata = pdev->dev.platform_data; + struct device_node *twl4030_core_node = pdev->dev.parent->of_node; + struct vibra_info *info; + int ret; + + if (!pdata && !twl4030_core_node) { + dev_dbg(&pdev->dev, "platform_data not available\n"); + return -EINVAL; + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->dev = &pdev->dev; + info->coexist = twl4030_vibra_check_coexist(pdata, twl4030_core_node); + INIT_WORK(&info->play_work, vibra_play_work); + + info->input_dev = devm_input_allocate_device(&pdev->dev); + if (info->input_dev == NULL) { + dev_err(&pdev->dev, "couldn't allocate input device\n"); + return -ENOMEM; + } + + input_set_drvdata(info->input_dev, info); + + info->input_dev->name = "twl4030:vibrator"; + info->input_dev->id.version = 1; + info->input_dev->dev.parent = pdev->dev.parent; + info->input_dev->close = twl4030_vibra_close; + __set_bit(FF_RUMBLE, info->input_dev->ffbit); + + ret = input_ff_create_memless(info->input_dev, NULL, vibra_play); + if (ret < 0) { + dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n"); + return ret; + } + + ret = input_register_device(info->input_dev); + if (ret < 0) { + dev_dbg(&pdev->dev, "couldn't register input device\n"); + goto err_iff; + } + + vibra_disable_leds(); + + platform_set_drvdata(pdev, info); + return 0; + +err_iff: + input_ff_destroy(info->input_dev); + return ret; +} + +static struct platform_driver twl4030_vibra_driver = { + .probe = twl4030_vibra_probe, + .driver = { + .name = "twl4030-vibra", + .owner = THIS_MODULE, + .pm = &twl4030_vibra_pm_ops, + }, +}; +module_platform_driver(twl4030_vibra_driver); + +MODULE_ALIAS("platform:twl4030-vibra"); +MODULE_DESCRIPTION("TWL4030 Vibra driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nokia Corporation"); diff --git a/drivers/input/misc/twl6040-vibra.c b/drivers/input/misc/twl6040-vibra.c new file mode 100644 index 00000000..0c2dfc8e --- /dev/null +++ b/drivers/input/misc/twl6040-vibra.c @@ -0,0 +1,431 @@ +/* + * twl6040-vibra.c - TWL6040 Vibrator driver + * + * Author: Jorge Eduardo Candelaria <jorge.candelaria@ti.com> + * Author: Misael Lopez Cruz <misael.lopez@ti.com> + * + * Copyright: (C) 2011 Texas Instruments, Inc. + * + * Based on twl4030-vibra.c by Henrik Saari <henrik.saari@nokia.com> + * Felipe Balbi <felipe.balbi@nokia.com> + * Jari Vanhala <ext-javi.vanhala@nokia.com> + * + * 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/platform_device.h> +#include <linux/of.h> +#include <linux/workqueue.h> +#include <linux/input.h> +#include <linux/mfd/twl6040.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/regulator/consumer.h> + +#define EFFECT_DIR_180_DEG 0x8000 + +/* Recommended modulation index 85% */ +#define TWL6040_VIBRA_MOD 85 + +#define TWL6040_NUM_SUPPLIES 2 + +struct vibra_info { + struct device *dev; + struct input_dev *input_dev; + struct workqueue_struct *workqueue; + struct work_struct play_work; + struct mutex mutex; + int irq; + + bool enabled; + int weak_speed; + int strong_speed; + int direction; + + unsigned int vibldrv_res; + unsigned int vibrdrv_res; + unsigned int viblmotor_res; + unsigned int vibrmotor_res; + + struct regulator_bulk_data supplies[TWL6040_NUM_SUPPLIES]; + + struct twl6040 *twl6040; +}; + +static irqreturn_t twl6040_vib_irq_handler(int irq, void *data) +{ + struct vibra_info *info = data; + struct twl6040 *twl6040 = info->twl6040; + u8 status; + + status = twl6040_reg_read(twl6040, TWL6040_REG_STATUS); + if (status & TWL6040_VIBLOCDET) { + dev_warn(info->dev, "Left Vibrator overcurrent detected\n"); + twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLL, + TWL6040_VIBENA); + } + if (status & TWL6040_VIBROCDET) { + dev_warn(info->dev, "Right Vibrator overcurrent detected\n"); + twl6040_clear_bits(twl6040, TWL6040_REG_VIBCTLR, + TWL6040_VIBENA); + } + + return IRQ_HANDLED; +} + +static void twl6040_vibra_enable(struct vibra_info *info) +{ + struct twl6040 *twl6040 = info->twl6040; + int ret; + + ret = regulator_bulk_enable(ARRAY_SIZE(info->supplies), info->supplies); + if (ret) { + dev_err(info->dev, "failed to enable regulators %d\n", ret); + return; + } + + twl6040_power(info->twl6040, 1); + if (twl6040_get_revid(twl6040) <= TWL6040_REV_ES1_1) { + /* + * ERRATA: Disable overcurrent protection for at least + * 3ms when enabling vibrator drivers to avoid false + * overcurrent detection + */ + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, + TWL6040_VIBENA | TWL6040_VIBCTRL); + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, + TWL6040_VIBENA | TWL6040_VIBCTRL); + usleep_range(3000, 3500); + } + + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, + TWL6040_VIBENA); + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, + TWL6040_VIBENA); + + info->enabled = true; +} + +static void twl6040_vibra_disable(struct vibra_info *info) +{ + struct twl6040 *twl6040 = info->twl6040; + + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLL, 0x00); + twl6040_reg_write(twl6040, TWL6040_REG_VIBCTLR, 0x00); + twl6040_power(info->twl6040, 0); + + regulator_bulk_disable(ARRAY_SIZE(info->supplies), info->supplies); + + info->enabled = false; +} + +static u8 twl6040_vibra_code(int vddvib, int vibdrv_res, int motor_res, + int speed, int direction) +{ + int vpk, max_code; + u8 vibdat; + + /* output swing */ + vpk = (vddvib * motor_res * TWL6040_VIBRA_MOD) / + (100 * (vibdrv_res + motor_res)); + + /* 50mV per VIBDAT code step */ + max_code = vpk / 50; + if (max_code > TWL6040_VIBDAT_MAX) + max_code = TWL6040_VIBDAT_MAX; + + /* scale speed to max allowed code */ + vibdat = (u8)((speed * max_code) / USHRT_MAX); + + /* 2's complement for direction > 180 degrees */ + vibdat *= direction; + + return vibdat; +} + +static void twl6040_vibra_set_effect(struct vibra_info *info) +{ + struct twl6040 *twl6040 = info->twl6040; + u8 vibdatl, vibdatr; + int volt; + + /* weak motor */ + volt = regulator_get_voltage(info->supplies[0].consumer) / 1000; + vibdatl = twl6040_vibra_code(volt, info->vibldrv_res, + info->viblmotor_res, + info->weak_speed, info->direction); + + /* strong motor */ + volt = regulator_get_voltage(info->supplies[1].consumer) / 1000; + vibdatr = twl6040_vibra_code(volt, info->vibrdrv_res, + info->vibrmotor_res, + info->strong_speed, info->direction); + + twl6040_reg_write(twl6040, TWL6040_REG_VIBDATL, vibdatl); + twl6040_reg_write(twl6040, TWL6040_REG_VIBDATR, vibdatr); +} + +static void vibra_play_work(struct work_struct *work) +{ + struct vibra_info *info = container_of(work, + struct vibra_info, play_work); + + mutex_lock(&info->mutex); + + if (info->weak_speed || info->strong_speed) { + if (!info->enabled) + twl6040_vibra_enable(info); + + twl6040_vibra_set_effect(info); + } else if (info->enabled) + twl6040_vibra_disable(info); + + mutex_unlock(&info->mutex); +} + +static int vibra_play(struct input_dev *input, void *data, + struct ff_effect *effect) +{ + struct vibra_info *info = input_get_drvdata(input); + int ret; + + /* Do not allow effect, while the routing is set to use audio */ + ret = twl6040_get_vibralr_status(info->twl6040); + if (ret & TWL6040_VIBSEL) { + dev_info(&input->dev, "Vibra is configured for audio\n"); + return -EBUSY; + } + + info->weak_speed = effect->u.rumble.weak_magnitude; + info->strong_speed = effect->u.rumble.strong_magnitude; + info->direction = effect->direction < EFFECT_DIR_180_DEG ? 1 : -1; + + ret = queue_work(info->workqueue, &info->play_work); + if (!ret) { + dev_info(&input->dev, "work is already on queue\n"); + return ret; + } + + return 0; +} + +static void twl6040_vibra_close(struct input_dev *input) +{ + struct vibra_info *info = input_get_drvdata(input); + + cancel_work_sync(&info->play_work); + + mutex_lock(&info->mutex); + + if (info->enabled) + twl6040_vibra_disable(info); + + mutex_unlock(&info->mutex); +} + +#ifdef CONFIG_PM_SLEEP +static int twl6040_vibra_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vibra_info *info = platform_get_drvdata(pdev); + + mutex_lock(&info->mutex); + + if (info->enabled) + twl6040_vibra_disable(info); + + mutex_unlock(&info->mutex); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(twl6040_vibra_pm_ops, twl6040_vibra_suspend, NULL); + +static int twl6040_vibra_probe(struct platform_device *pdev) +{ + struct twl6040_vibra_data *pdata = pdev->dev.platform_data; + struct device *twl6040_core_dev = pdev->dev.parent; + struct device_node *twl6040_core_node = NULL; + struct vibra_info *info; + int vddvibl_uV = 0; + int vddvibr_uV = 0; + int ret; + +#ifdef CONFIG_OF + twl6040_core_node = of_find_node_by_name(twl6040_core_dev->of_node, + "vibra"); +#endif + + if (!pdata && !twl6040_core_node) { + dev_err(&pdev->dev, "platform_data not available\n"); + return -EINVAL; + } + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) { + dev_err(&pdev->dev, "couldn't allocate memory\n"); + return -ENOMEM; + } + + info->dev = &pdev->dev; + + info->twl6040 = dev_get_drvdata(pdev->dev.parent); + if (pdata) { + info->vibldrv_res = pdata->vibldrv_res; + info->vibrdrv_res = pdata->vibrdrv_res; + info->viblmotor_res = pdata->viblmotor_res; + info->vibrmotor_res = pdata->vibrmotor_res; + vddvibl_uV = pdata->vddvibl_uV; + vddvibr_uV = pdata->vddvibr_uV; + } else { + of_property_read_u32(twl6040_core_node, "ti,vibldrv-res", + &info->vibldrv_res); + of_property_read_u32(twl6040_core_node, "ti,vibrdrv-res", + &info->vibrdrv_res); + of_property_read_u32(twl6040_core_node, "ti,viblmotor-res", + &info->viblmotor_res); + of_property_read_u32(twl6040_core_node, "ti,vibrmotor-res", + &info->vibrmotor_res); + of_property_read_u32(twl6040_core_node, "ti,vddvibl-uV", + &vddvibl_uV); + of_property_read_u32(twl6040_core_node, "ti,vddvibr-uV", + &vddvibr_uV); + } + + if ((!info->vibldrv_res && !info->viblmotor_res) || + (!info->vibrdrv_res && !info->vibrmotor_res)) { + dev_err(info->dev, "invalid vibra driver/motor resistance\n"); + return -EINVAL; + } + + info->irq = platform_get_irq(pdev, 0); + if (info->irq < 0) { + dev_err(info->dev, "invalid irq\n"); + return -EINVAL; + } + + mutex_init(&info->mutex); + + ret = devm_request_threaded_irq(&pdev->dev, info->irq, NULL, + twl6040_vib_irq_handler, 0, + "twl6040_irq_vib", info); + if (ret) { + dev_err(info->dev, "VIB IRQ request failed: %d\n", ret); + return ret; + } + + info->supplies[0].supply = "vddvibl"; + info->supplies[1].supply = "vddvibr"; + /* + * When booted with Device tree the regulators are attached to the + * parent device (twl6040 MFD core) + */ + ret = regulator_bulk_get(pdata ? info->dev : twl6040_core_dev, + ARRAY_SIZE(info->supplies), info->supplies); + if (ret) { + dev_err(info->dev, "couldn't get regulators %d\n", ret); + return ret; + } + + if (vddvibl_uV) { + ret = regulator_set_voltage(info->supplies[0].consumer, + vddvibl_uV, vddvibl_uV); + if (ret) { + dev_err(info->dev, "failed to set VDDVIBL volt %d\n", + ret); + goto err_regulator; + } + } + + if (vddvibr_uV) { + ret = regulator_set_voltage(info->supplies[1].consumer, + vddvibr_uV, vddvibr_uV); + if (ret) { + dev_err(info->dev, "failed to set VDDVIBR volt %d\n", + ret); + goto err_regulator; + } + } + + INIT_WORK(&info->play_work, vibra_play_work); + + info->input_dev = input_allocate_device(); + if (info->input_dev == NULL) { + dev_err(info->dev, "couldn't allocate input device\n"); + ret = -ENOMEM; + goto err_regulator; + } + + input_set_drvdata(info->input_dev, info); + + info->input_dev->name = "twl6040:vibrator"; + info->input_dev->id.version = 1; + info->input_dev->dev.parent = pdev->dev.parent; + info->input_dev->close = twl6040_vibra_close; + __set_bit(FF_RUMBLE, info->input_dev->ffbit); + + ret = input_ff_create_memless(info->input_dev, NULL, vibra_play); + if (ret < 0) { + dev_err(info->dev, "couldn't register vibrator to FF\n"); + goto err_ialloc; + } + + ret = input_register_device(info->input_dev); + if (ret < 0) { + dev_err(info->dev, "couldn't register input device\n"); + goto err_iff; + } + + platform_set_drvdata(pdev, info); + + return 0; + +err_iff: + input_ff_destroy(info->input_dev); +err_ialloc: + input_free_device(info->input_dev); +err_regulator: + regulator_bulk_free(ARRAY_SIZE(info->supplies), info->supplies); + return ret; +} + +static int twl6040_vibra_remove(struct platform_device *pdev) +{ + struct vibra_info *info = platform_get_drvdata(pdev); + + input_unregister_device(info->input_dev); + regulator_bulk_free(ARRAY_SIZE(info->supplies), info->supplies); + + return 0; +} + +static struct platform_driver twl6040_vibra_driver = { + .probe = twl6040_vibra_probe, + .remove = twl6040_vibra_remove, + .driver = { + .name = "twl6040-vibra", + .owner = THIS_MODULE, + .pm = &twl6040_vibra_pm_ops, + }, +}; +module_platform_driver(twl6040_vibra_driver); + +MODULE_ALIAS("platform:twl6040-vibra"); +MODULE_DESCRIPTION("TWL6040 Vibra driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jorge Eduardo Candelaria <jorge.candelaria@ti.com>"); +MODULE_AUTHOR("Misael Lopez Cruz <misael.lopez@ti.com>"); diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c new file mode 100644 index 00000000..a0a4bbae --- /dev/null +++ b/drivers/input/misc/uinput.c @@ -0,0 +1,883 @@ +/* + * User level driver support for input subsystem + * + * Heavily based on evdev.c by Vojtech Pavlik + * + * 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 + * + * Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org> + * + * Changes/Revisions: + * 0.3 09/04/2006 (Anssi Hannula <anssi.hannula@gmail.com>) + * - updated ff support for the changes in kernel interface + * - added MODULE_VERSION + * 0.2 16/10/2004 (Micah Dowty <micah@navi.cx>) + * - added force feedback support + * - added UI_SET_PHYS + * 0.1 20/06/2002 + * - first public version + */ +#include <linux/poll.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/uinput.h> +#include <linux/input/mt.h> +#include "../input-compat.h" + +static int uinput_dev_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + struct uinput_device *udev = input_get_drvdata(dev); + + udev->buff[udev->head].type = type; + udev->buff[udev->head].code = code; + udev->buff[udev->head].value = value; + do_gettimeofday(&udev->buff[udev->head].time); + udev->head = (udev->head + 1) % UINPUT_BUFFER_SIZE; + + wake_up_interruptible(&udev->waitq); + + return 0; +} + +/* Atomically allocate an ID for the given request. Returns 0 on success. */ +static bool uinput_request_alloc_id(struct uinput_device *udev, + struct uinput_request *request) +{ + unsigned int id; + bool reserved = false; + + spin_lock(&udev->requests_lock); + + for (id = 0; id < UINPUT_NUM_REQUESTS; id++) { + if (!udev->requests[id]) { + request->id = id; + udev->requests[id] = request; + reserved = true; + break; + } + } + + spin_unlock(&udev->requests_lock); + return reserved; +} + +static struct uinput_request *uinput_request_find(struct uinput_device *udev, + unsigned int id) +{ + /* Find an input request, by ID. Returns NULL if the ID isn't valid. */ + if (id >= UINPUT_NUM_REQUESTS) + return NULL; + + return udev->requests[id]; +} + +static int uinput_request_reserve_slot(struct uinput_device *udev, + struct uinput_request *request) +{ + /* Allocate slot. If none are available right away, wait. */ + return wait_event_interruptible(udev->requests_waitq, + uinput_request_alloc_id(udev, request)); +} + +static void uinput_request_done(struct uinput_device *udev, + struct uinput_request *request) +{ + /* Mark slot as available */ + udev->requests[request->id] = NULL; + wake_up(&udev->requests_waitq); + + complete(&request->done); +} + +static int uinput_request_send(struct uinput_device *udev, + struct uinput_request *request) +{ + int retval; + + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + if (udev->state != UIST_CREATED) { + retval = -ENODEV; + goto out; + } + + init_completion(&request->done); + + /* + * Tell our userspace application about this new request + * by queueing an input event. + */ + uinput_dev_event(udev->dev, EV_UINPUT, request->code, request->id); + + out: + mutex_unlock(&udev->mutex); + return retval; +} + +static int uinput_request_submit(struct uinput_device *udev, + struct uinput_request *request) +{ + int error; + + error = uinput_request_reserve_slot(udev, request); + if (error) + return error; + + error = uinput_request_send(udev, request); + if (error) { + uinput_request_done(udev, request); + return error; + } + + wait_for_completion(&request->done); + return request->retval; +} + +/* + * Fail all outstanding requests so handlers don't wait for the userspace + * to finish processing them. + */ +static void uinput_flush_requests(struct uinput_device *udev) +{ + struct uinput_request *request; + int i; + + spin_lock(&udev->requests_lock); + + for (i = 0; i < UINPUT_NUM_REQUESTS; i++) { + request = udev->requests[i]; + if (request) { + request->retval = -ENODEV; + uinput_request_done(udev, request); + } + } + + spin_unlock(&udev->requests_lock); +} + +static void uinput_dev_set_gain(struct input_dev *dev, u16 gain) +{ + uinput_dev_event(dev, EV_FF, FF_GAIN, gain); +} + +static void uinput_dev_set_autocenter(struct input_dev *dev, u16 magnitude) +{ + uinput_dev_event(dev, EV_FF, FF_AUTOCENTER, magnitude); +} + +static int uinput_dev_playback(struct input_dev *dev, int effect_id, int value) +{ + return uinput_dev_event(dev, EV_FF, effect_id, value); +} + +static int uinput_dev_upload_effect(struct input_dev *dev, + struct ff_effect *effect, + struct ff_effect *old) +{ + struct uinput_device *udev = input_get_drvdata(dev); + struct uinput_request request; + + /* + * uinput driver does not currently support periodic effects with + * custom waveform since it does not have a way to pass buffer of + * samples (custom_data) to userspace. If ever there is a device + * supporting custom waveforms we would need to define an additional + * ioctl (UI_UPLOAD_SAMPLES) but for now we just bail out. + */ + if (effect->type == FF_PERIODIC && + effect->u.periodic.waveform == FF_CUSTOM) + return -EINVAL; + + request.code = UI_FF_UPLOAD; + request.u.upload.effect = effect; + request.u.upload.old = old; + + return uinput_request_submit(udev, &request); +} + +static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id) +{ + struct uinput_device *udev = input_get_drvdata(dev); + struct uinput_request request; + + if (!test_bit(EV_FF, dev->evbit)) + return -ENOSYS; + + request.code = UI_FF_ERASE; + request.u.effect_id = effect_id; + + return uinput_request_submit(udev, &request); +} + +static void uinput_destroy_device(struct uinput_device *udev) +{ + const char *name, *phys; + struct input_dev *dev = udev->dev; + enum uinput_state old_state = udev->state; + + udev->state = UIST_NEW_DEVICE; + + if (dev) { + name = dev->name; + phys = dev->phys; + if (old_state == UIST_CREATED) { + uinput_flush_requests(udev); + input_unregister_device(dev); + } else { + input_free_device(dev); + } + kfree(name); + kfree(phys); + udev->dev = NULL; + } +} + +static int uinput_create_device(struct uinput_device *udev) +{ + struct input_dev *dev = udev->dev; + int error; + + if (udev->state != UIST_SETUP_COMPLETE) { + printk(KERN_DEBUG "%s: write device info first\n", UINPUT_NAME); + return -EINVAL; + } + + if (udev->ff_effects_max) { + error = input_ff_create(dev, udev->ff_effects_max); + if (error) + goto fail1; + + dev->ff->upload = uinput_dev_upload_effect; + dev->ff->erase = uinput_dev_erase_effect; + dev->ff->playback = uinput_dev_playback; + dev->ff->set_gain = uinput_dev_set_gain; + dev->ff->set_autocenter = uinput_dev_set_autocenter; + } + + error = input_register_device(udev->dev); + if (error) + goto fail2; + + udev->state = UIST_CREATED; + + return 0; + + fail2: input_ff_destroy(dev); + fail1: uinput_destroy_device(udev); + return error; +} + +static int uinput_open(struct inode *inode, struct file *file) +{ + struct uinput_device *newdev; + + newdev = kzalloc(sizeof(struct uinput_device), GFP_KERNEL); + if (!newdev) + return -ENOMEM; + + mutex_init(&newdev->mutex); + spin_lock_init(&newdev->requests_lock); + init_waitqueue_head(&newdev->requests_waitq); + init_waitqueue_head(&newdev->waitq); + newdev->state = UIST_NEW_DEVICE; + + file->private_data = newdev; + nonseekable_open(inode, file); + + return 0; +} + +static int uinput_validate_absbits(struct input_dev *dev) +{ + unsigned int cnt; + int retval = 0; + + for (cnt = 0; cnt < ABS_CNT; cnt++) { + int min, max; + if (!test_bit(cnt, dev->absbit)) + continue; + + min = input_abs_get_min(dev, cnt); + max = input_abs_get_max(dev, cnt); + + if ((min != 0 || max != 0) && max <= min) { + printk(KERN_DEBUG + "%s: invalid abs[%02x] min:%d max:%d\n", + UINPUT_NAME, cnt, + input_abs_get_min(dev, cnt), + input_abs_get_max(dev, cnt)); + retval = -EINVAL; + break; + } + + if (input_abs_get_flat(dev, cnt) > + input_abs_get_max(dev, cnt) - input_abs_get_min(dev, cnt)) { + printk(KERN_DEBUG + "%s: abs_flat #%02x out of range: %d " + "(min:%d/max:%d)\n", + UINPUT_NAME, cnt, + input_abs_get_flat(dev, cnt), + input_abs_get_min(dev, cnt), + input_abs_get_max(dev, cnt)); + retval = -EINVAL; + break; + } + } + return retval; +} + +static int uinput_allocate_device(struct uinput_device *udev) +{ + udev->dev = input_allocate_device(); + if (!udev->dev) + return -ENOMEM; + + udev->dev->event = uinput_dev_event; + input_set_drvdata(udev->dev, udev); + + return 0; +} + +static int uinput_setup_device(struct uinput_device *udev, + const char __user *buffer, size_t count) +{ + struct uinput_user_dev *user_dev; + struct input_dev *dev; + int i; + int retval; + + if (count != sizeof(struct uinput_user_dev)) + return -EINVAL; + + if (!udev->dev) { + retval = uinput_allocate_device(udev); + if (retval) + return retval; + } + + dev = udev->dev; + + user_dev = memdup_user(buffer, sizeof(struct uinput_user_dev)); + if (IS_ERR(user_dev)) + return PTR_ERR(user_dev); + + udev->ff_effects_max = user_dev->ff_effects_max; + + /* Ensure name is filled in */ + if (!user_dev->name[0]) { + retval = -EINVAL; + goto exit; + } + + kfree(dev->name); + dev->name = kstrndup(user_dev->name, UINPUT_MAX_NAME_SIZE, + GFP_KERNEL); + if (!dev->name) { + retval = -ENOMEM; + goto exit; + } + + dev->id.bustype = user_dev->id.bustype; + dev->id.vendor = user_dev->id.vendor; + dev->id.product = user_dev->id.product; + dev->id.version = user_dev->id.version; + + for (i = 0; i < ABS_CNT; i++) { + input_abs_set_max(dev, i, user_dev->absmax[i]); + input_abs_set_min(dev, i, user_dev->absmin[i]); + input_abs_set_fuzz(dev, i, user_dev->absfuzz[i]); + input_abs_set_flat(dev, i, user_dev->absflat[i]); + } + + /* check if absmin/absmax/absfuzz/absflat are filled as + * told in Documentation/input/input-programming.txt */ + if (test_bit(EV_ABS, dev->evbit)) { + retval = uinput_validate_absbits(dev); + if (retval < 0) + goto exit; + if (test_bit(ABS_MT_SLOT, dev->absbit)) { + int nslot = input_abs_get_max(dev, ABS_MT_SLOT) + 1; + input_mt_init_slots(dev, nslot, 0); + } else if (test_bit(ABS_MT_POSITION_X, dev->absbit)) { + input_set_events_per_packet(dev, 60); + } + } + + udev->state = UIST_SETUP_COMPLETE; + retval = count; + + exit: + kfree(user_dev); + return retval; +} + +static ssize_t uinput_inject_event(struct uinput_device *udev, + const char __user *buffer, size_t count) +{ + struct input_event ev; + + if (count < input_event_size()) + return -EINVAL; + + if (input_event_from_user(buffer, &ev)) + return -EFAULT; + + input_event(udev->dev, ev.type, ev.code, ev.value); + + return input_event_size(); +} + +static ssize_t uinput_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct uinput_device *udev = file->private_data; + int retval; + + if (count == 0) + return 0; + + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + retval = udev->state == UIST_CREATED ? + uinput_inject_event(udev, buffer, count) : + uinput_setup_device(udev, buffer, count); + + mutex_unlock(&udev->mutex); + + return retval; +} + +static bool uinput_fetch_next_event(struct uinput_device *udev, + struct input_event *event) +{ + bool have_event; + + spin_lock_irq(&udev->dev->event_lock); + + have_event = udev->head != udev->tail; + if (have_event) { + *event = udev->buff[udev->tail]; + udev->tail = (udev->tail + 1) % UINPUT_BUFFER_SIZE; + } + + spin_unlock_irq(&udev->dev->event_lock); + + return have_event; +} + +static ssize_t uinput_events_to_user(struct uinput_device *udev, + char __user *buffer, size_t count) +{ + struct input_event event; + size_t read = 0; + + while (read + input_event_size() <= count && + uinput_fetch_next_event(udev, &event)) { + + if (input_event_to_user(buffer + read, &event)) + return -EFAULT; + + read += input_event_size(); + } + + return read; +} + +static ssize_t uinput_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct uinput_device *udev = file->private_data; + ssize_t retval; + + if (count != 0 && count < input_event_size()) + return -EINVAL; + + do { + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + if (udev->state != UIST_CREATED) + retval = -ENODEV; + else if (udev->head == udev->tail && + (file->f_flags & O_NONBLOCK)) + retval = -EAGAIN; + else + retval = uinput_events_to_user(udev, buffer, count); + + mutex_unlock(&udev->mutex); + + if (retval || count == 0) + break; + + if (!(file->f_flags & O_NONBLOCK)) + retval = wait_event_interruptible(udev->waitq, + udev->head != udev->tail || + udev->state != UIST_CREATED); + } while (retval == 0); + + return retval; +} + +static unsigned int uinput_poll(struct file *file, poll_table *wait) +{ + struct uinput_device *udev = file->private_data; + + poll_wait(file, &udev->waitq, wait); + + if (udev->head != udev->tail) + return POLLIN | POLLRDNORM; + + return 0; +} + +static int uinput_release(struct inode *inode, struct file *file) +{ + struct uinput_device *udev = file->private_data; + + uinput_destroy_device(udev); + kfree(udev); + + return 0; +} + +#ifdef CONFIG_COMPAT +struct uinput_ff_upload_compat { + __u32 request_id; + __s32 retval; + struct ff_effect_compat effect; + struct ff_effect_compat old; +}; + +static int uinput_ff_upload_to_user(char __user *buffer, + const struct uinput_ff_upload *ff_up) +{ + if (INPUT_COMPAT_TEST) { + struct uinput_ff_upload_compat ff_up_compat; + + ff_up_compat.request_id = ff_up->request_id; + ff_up_compat.retval = ff_up->retval; + /* + * It so happens that the pointer that gives us the trouble + * is the last field in the structure. Since we don't support + * custom waveforms in uinput anyway we can just copy the whole + * thing (to the compat size) and ignore the pointer. + */ + memcpy(&ff_up_compat.effect, &ff_up->effect, + sizeof(struct ff_effect_compat)); + memcpy(&ff_up_compat.old, &ff_up->old, + sizeof(struct ff_effect_compat)); + + if (copy_to_user(buffer, &ff_up_compat, + sizeof(struct uinput_ff_upload_compat))) + return -EFAULT; + } else { + if (copy_to_user(buffer, ff_up, + sizeof(struct uinput_ff_upload))) + return -EFAULT; + } + + return 0; +} + +static int uinput_ff_upload_from_user(const char __user *buffer, + struct uinput_ff_upload *ff_up) +{ + if (INPUT_COMPAT_TEST) { + struct uinput_ff_upload_compat ff_up_compat; + + if (copy_from_user(&ff_up_compat, buffer, + sizeof(struct uinput_ff_upload_compat))) + return -EFAULT; + + ff_up->request_id = ff_up_compat.request_id; + ff_up->retval = ff_up_compat.retval; + memcpy(&ff_up->effect, &ff_up_compat.effect, + sizeof(struct ff_effect_compat)); + memcpy(&ff_up->old, &ff_up_compat.old, + sizeof(struct ff_effect_compat)); + + } else { + if (copy_from_user(ff_up, buffer, + sizeof(struct uinput_ff_upload))) + return -EFAULT; + } + + return 0; +} + +#else + +static int uinput_ff_upload_to_user(char __user *buffer, + const struct uinput_ff_upload *ff_up) +{ + if (copy_to_user(buffer, ff_up, sizeof(struct uinput_ff_upload))) + return -EFAULT; + + return 0; +} + +static int uinput_ff_upload_from_user(const char __user *buffer, + struct uinput_ff_upload *ff_up) +{ + if (copy_from_user(ff_up, buffer, sizeof(struct uinput_ff_upload))) + return -EFAULT; + + return 0; +} + +#endif + +#define uinput_set_bit(_arg, _bit, _max) \ +({ \ + int __ret = 0; \ + if (udev->state == UIST_CREATED) \ + __ret = -EINVAL; \ + else if ((_arg) > (_max)) \ + __ret = -EINVAL; \ + else set_bit((_arg), udev->dev->_bit); \ + __ret; \ +}) + +static long uinput_ioctl_handler(struct file *file, unsigned int cmd, + unsigned long arg, void __user *p) +{ + int retval; + struct uinput_device *udev = file->private_data; + struct uinput_ff_upload ff_up; + struct uinput_ff_erase ff_erase; + struct uinput_request *req; + char *phys; + + retval = mutex_lock_interruptible(&udev->mutex); + if (retval) + return retval; + + if (!udev->dev) { + retval = uinput_allocate_device(udev); + if (retval) + goto out; + } + + switch (cmd) { + case UI_DEV_CREATE: + retval = uinput_create_device(udev); + break; + + case UI_DEV_DESTROY: + uinput_destroy_device(udev); + break; + + case UI_SET_EVBIT: + retval = uinput_set_bit(arg, evbit, EV_MAX); + break; + + case UI_SET_KEYBIT: + retval = uinput_set_bit(arg, keybit, KEY_MAX); + break; + + case UI_SET_RELBIT: + retval = uinput_set_bit(arg, relbit, REL_MAX); + break; + + case UI_SET_ABSBIT: + retval = uinput_set_bit(arg, absbit, ABS_MAX); + break; + + case UI_SET_MSCBIT: + retval = uinput_set_bit(arg, mscbit, MSC_MAX); + break; + + case UI_SET_LEDBIT: + retval = uinput_set_bit(arg, ledbit, LED_MAX); + break; + + case UI_SET_SNDBIT: + retval = uinput_set_bit(arg, sndbit, SND_MAX); + break; + + case UI_SET_FFBIT: + retval = uinput_set_bit(arg, ffbit, FF_MAX); + break; + + case UI_SET_SWBIT: + retval = uinput_set_bit(arg, swbit, SW_MAX); + break; + + case UI_SET_PROPBIT: + retval = uinput_set_bit(arg, propbit, INPUT_PROP_MAX); + break; + + case UI_SET_PHYS: + if (udev->state == UIST_CREATED) { + retval = -EINVAL; + goto out; + } + + phys = strndup_user(p, 1024); + if (IS_ERR(phys)) { + retval = PTR_ERR(phys); + goto out; + } + + kfree(udev->dev->phys); + udev->dev->phys = phys; + break; + + case UI_BEGIN_FF_UPLOAD: + retval = uinput_ff_upload_from_user(p, &ff_up); + if (retval) + break; + + req = uinput_request_find(udev, ff_up.request_id); + if (!req || req->code != UI_FF_UPLOAD || + !req->u.upload.effect) { + retval = -EINVAL; + break; + } + + ff_up.retval = 0; + ff_up.effect = *req->u.upload.effect; + if (req->u.upload.old) + ff_up.old = *req->u.upload.old; + else + memset(&ff_up.old, 0, sizeof(struct ff_effect)); + + retval = uinput_ff_upload_to_user(p, &ff_up); + break; + + case UI_BEGIN_FF_ERASE: + if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) { + retval = -EFAULT; + break; + } + + req = uinput_request_find(udev, ff_erase.request_id); + if (!req || req->code != UI_FF_ERASE) { + retval = -EINVAL; + break; + } + + ff_erase.retval = 0; + ff_erase.effect_id = req->u.effect_id; + if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) { + retval = -EFAULT; + break; + } + + break; + + case UI_END_FF_UPLOAD: + retval = uinput_ff_upload_from_user(p, &ff_up); + if (retval) + break; + + req = uinput_request_find(udev, ff_up.request_id); + if (!req || req->code != UI_FF_UPLOAD || + !req->u.upload.effect) { + retval = -EINVAL; + break; + } + + req->retval = ff_up.retval; + uinput_request_done(udev, req); + break; + + case UI_END_FF_ERASE: + if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) { + retval = -EFAULT; + break; + } + + req = uinput_request_find(udev, ff_erase.request_id); + if (!req || req->code != UI_FF_ERASE) { + retval = -EINVAL; + break; + } + + req->retval = ff_erase.retval; + uinput_request_done(udev, req); + break; + + default: + retval = -EINVAL; + } + + out: + mutex_unlock(&udev->mutex); + return retval; +} + +static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + return uinput_ioctl_handler(file, cmd, arg, (void __user *)arg); +} + +#ifdef CONFIG_COMPAT +static long uinput_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) +{ + return uinput_ioctl_handler(file, cmd, arg, compat_ptr(arg)); +} +#endif + +static const struct file_operations uinput_fops = { + .owner = THIS_MODULE, + .open = uinput_open, + .release = uinput_release, + .read = uinput_read, + .write = uinput_write, + .poll = uinput_poll, + .unlocked_ioctl = uinput_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = uinput_compat_ioctl, +#endif + .llseek = no_llseek, +}; + +static struct miscdevice uinput_misc = { + .fops = &uinput_fops, + .minor = UINPUT_MINOR, + .name = UINPUT_NAME, +}; +MODULE_ALIAS_MISCDEV(UINPUT_MINOR); +MODULE_ALIAS("devname:" UINPUT_NAME); + +static int __init uinput_init(void) +{ + return misc_register(&uinput_misc); +} + +static void __exit uinput_exit(void) +{ + misc_deregister(&uinput_misc); +} + +MODULE_AUTHOR("Aristeu Sergio Rozanski Filho"); +MODULE_DESCRIPTION("User level driver support for input subsystem"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("0.3"); + +module_init(uinput_init); +module_exit(uinput_exit); diff --git a/drivers/input/misc/wistron_btns.c b/drivers/input/misc/wistron_btns.c new file mode 100644 index 00000000..56536f4b --- /dev/null +++ b/drivers/input/misc/wistron_btns.c @@ -0,0 +1,1389 @@ +/* + * Wistron laptop button driver + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + * + * You can redistribute and/or modify this program 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., + * 59 Temple Place Suite 330, Boston, MA 02111-1307, USA. + */ +#include <linux/io.h> +#include <linux/dmi.h> +#include <linux/init.h> +#include <linux/input-polldev.h> +#include <linux/input/sparse-keymap.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/mc146818rtc.h> +#include <linux/module.h> +#include <linux/preempt.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/leds.h> + +/* How often we poll keys - msecs */ +#define POLL_INTERVAL_DEFAULT 500 /* when idle */ +#define POLL_INTERVAL_BURST 100 /* when a key was recently pressed */ + +/* BIOS subsystem IDs */ +#define WIFI 0x35 +#define BLUETOOTH 0x34 +#define MAIL_LED 0x31 + +MODULE_AUTHOR("Miloslav Trmac <mitr@volny.cz>"); +MODULE_DESCRIPTION("Wistron laptop button driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.3"); + +static bool force; /* = 0; */ +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Load even if computer is not in database"); + +static char *keymap_name; /* = NULL; */ +module_param_named(keymap, keymap_name, charp, 0); +MODULE_PARM_DESC(keymap, "Keymap name, if it can't be autodetected [generic, 1557/MS2141]"); + +static struct platform_device *wistron_device; + + /* BIOS interface implementation */ + +static void __iomem *bios_entry_point; /* BIOS routine entry point */ +static void __iomem *bios_code_map_base; +static void __iomem *bios_data_map_base; + +static u8 cmos_address; + +struct regs { + u32 eax, ebx, ecx; +}; + +static void call_bios(struct regs *regs) +{ + unsigned long flags; + + preempt_disable(); + local_irq_save(flags); + asm volatile ("pushl %%ebp;" + "movl %7, %%ebp;" + "call *%6;" + "popl %%ebp" + : "=a" (regs->eax), "=b" (regs->ebx), "=c" (regs->ecx) + : "0" (regs->eax), "1" (regs->ebx), "2" (regs->ecx), + "m" (bios_entry_point), "m" (bios_data_map_base) + : "edx", "edi", "esi", "memory"); + local_irq_restore(flags); + preempt_enable(); +} + +static ssize_t __init locate_wistron_bios(void __iomem *base) +{ + static unsigned char __initdata signature[] = + { 0x42, 0x21, 0x55, 0x30 }; + ssize_t offset; + + for (offset = 0; offset < 0x10000; offset += 0x10) { + if (check_signature(base + offset, signature, + sizeof(signature)) != 0) + return offset; + } + return -1; +} + +static int __init map_bios(void) +{ + void __iomem *base; + ssize_t offset; + u32 entry_point; + + base = ioremap(0xF0000, 0x10000); /* Can't fail */ + offset = locate_wistron_bios(base); + if (offset < 0) { + printk(KERN_ERR "wistron_btns: BIOS entry point not found\n"); + iounmap(base); + return -ENODEV; + } + + entry_point = readl(base + offset + 5); + printk(KERN_DEBUG + "wistron_btns: BIOS signature found at %p, entry point %08X\n", + base + offset, entry_point); + + if (entry_point >= 0xF0000) { + bios_code_map_base = base; + bios_entry_point = bios_code_map_base + (entry_point & 0xFFFF); + } else { + iounmap(base); + bios_code_map_base = ioremap(entry_point & ~0x3FFF, 0x4000); + if (bios_code_map_base == NULL) { + printk(KERN_ERR + "wistron_btns: Can't map BIOS code at %08X\n", + entry_point & ~0x3FFF); + goto err; + } + bios_entry_point = bios_code_map_base + (entry_point & 0x3FFF); + } + /* The Windows driver maps 0x10000 bytes, we keep only one page... */ + bios_data_map_base = ioremap(0x400, 0xc00); + if (bios_data_map_base == NULL) { + printk(KERN_ERR "wistron_btns: Can't map BIOS data\n"); + goto err_code; + } + return 0; + +err_code: + iounmap(bios_code_map_base); +err: + return -ENOMEM; +} + +static inline void unmap_bios(void) +{ + iounmap(bios_code_map_base); + iounmap(bios_data_map_base); +} + + /* BIOS calls */ + +static u16 bios_pop_queue(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x061C; + regs.ecx = 0x0000; + call_bios(®s); + + return regs.eax; +} + +static void bios_attach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x012E; + call_bios(®s); +} + +static void bios_detach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x002E; + call_bios(®s); +} + +static u8 bios_get_cmos_address(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x051C; + call_bios(®s); + + return regs.ecx; +} + +static u16 bios_get_default_setting(u8 subsys) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x0200 | subsys; + call_bios(®s); + + return regs.eax; +} + +static void bios_set_state(u8 subsys, int enable) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = (enable ? 0x0100 : 0x0000) | subsys; + call_bios(®s); +} + +/* Hardware database */ + +#define KE_WIFI (KE_LAST + 1) +#define KE_BLUETOOTH (KE_LAST + 2) + +#define FE_MAIL_LED 0x01 +#define FE_WIFI_LED 0x02 +#define FE_UNTESTED 0x80 + +static struct key_entry *keymap; /* = NULL; Current key map */ +static bool have_wifi; +static bool have_bluetooth; +static int leds_present; /* bitmask of leds present */ + +static int __init dmi_matched(const struct dmi_system_id *dmi) +{ + const struct key_entry *key; + + keymap = dmi->driver_data; + for (key = keymap; key->type != KE_END; key++) { + if (key->type == KE_WIFI) + have_wifi = true; + else if (key->type == KE_BLUETOOTH) + have_bluetooth = true; + } + leds_present = key->code & (FE_MAIL_LED | FE_WIFI_LED); + + return 1; +} + +static struct key_entry keymap_empty[] __initdata = { + { KE_END, 0 } +}; + +static struct key_entry keymap_fs_amilo_pro_v2000[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 } +}; + +static struct key_entry keymap_fs_amilo_pro_v3505[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, /* Fn+F1 */ + { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Fn+F4 */ + { KE_BLUETOOTH, 0x30 }, /* Fn+F10 */ + { KE_KEY, 0x31, {KEY_MAIL} }, /* mail button */ + { KE_KEY, 0x36, {KEY_WWW} }, /* www button */ + { KE_WIFI, 0x78 }, /* satellite dish button */ + { KE_END, 0 } +}; + +static struct key_entry keymap_fujitsu_n3510[] __initdata = { + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x71, {KEY_STOPCD} }, + { KE_KEY, 0x72, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x74, {KEY_REWIND} }, + { KE_KEY, 0x78, {KEY_FORWARD} }, + { KE_END, 0 } +}; + +static struct key_entry keymap_wistron_ms2111[] __initdata = { + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, FE_MAIL_LED } +}; + +static struct key_entry keymap_wistron_md40100[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x37, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_ms2141[] __initdata = { + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 } +}; + +static struct key_entry keymap_acer_aspire_1500[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x49, {KEY_CONFIG} }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_UNTESTED } +}; + +static struct key_entry keymap_acer_aspire_1600[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x49, {KEY_CONFIG} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +/* 3020 has been tested */ +static struct key_entry keymap_acer_aspire_5020[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */ + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x6a, {KEY_CONFIG} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_2410[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x6d, {KEY_POWER} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x6a, {KEY_CONFIG} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_110[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_SW, 0x4a, {.sw = {SW_LID, 1}} }, /* lid close */ + { KE_SW, 0x4b, {.sw = {SW_LID, 0}} }, /* lid open */ + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_300[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_380[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, /* not 370 */ + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +/* unusual map */ +static struct key_entry keymap_acer_travelmate_220[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_MAIL} }, + { KE_KEY, 0x12, {KEY_WWW} }, + { KE_KEY, 0x13, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_PROG1} }, + { KE_END, FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_230[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_240[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_BLUETOOTH, 0x44 }, + { KE_WIFI, 0x30 }, + { KE_END, FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_350[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_MAIL} }, + { KE_KEY, 0x14, {KEY_PROG3} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_acer_travelmate_360[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_MAIL} }, + { KE_KEY, 0x14, {KEY_PROG3} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_KEY, 0x40, {KEY_WLAN} }, + { KE_END, FE_WIFI_LED | FE_UNTESTED } /* no mail led */ +}; + +/* Wifi subsystem only activates the led. Therefore we need to pass + * wifi event as a normal key, then userspace can really change the wifi state. + * TODO we need to export led state to userspace (wifi and mail) */ +static struct key_entry keymap_acer_travelmate_610[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x14, {KEY_MAIL} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_KEY, 0x40, {KEY_WLAN} }, + { KE_END, FE_MAIL_LED | FE_WIFI_LED } +}; + +static struct key_entry keymap_acer_travelmate_630[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x08, {KEY_MUTE} }, /* not 620 */ + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_aopen_1559as[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x06, {KEY_PROG3} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 }, +}; + +static struct key_entry keymap_fs_amilo_d88x0[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_END, FE_MAIL_LED | FE_WIFI_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_md2900[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_END, FE_MAIL_LED | FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_md96500[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */ + { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, FE_UNTESTED } +}; + +static struct key_entry keymap_wistron_generic[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x02, {KEY_CONFIG} }, + { KE_KEY, 0x03, {KEY_POWER} }, + { KE_KEY, 0x05, {KEY_SWITCHVIDEOMODE} }, /* Display selection */ + { KE_KEY, 0x06, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_KEY, 0x08, {KEY_MUTE} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_KEY, 0x13, {KEY_PROG3} }, + { KE_KEY, 0x14, {KEY_MAIL} }, + { KE_KEY, 0x15, {KEY_WWW} }, + { KE_KEY, 0x20, {KEY_VOLUMEUP} }, + { KE_KEY, 0x21, {KEY_VOLUMEDOWN} }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_KEY, 0x37, {KEY_DISPLAYTOGGLE} }, /* Display on/off */ + { KE_KEY, 0x40, {KEY_WLAN} }, + { KE_KEY, 0x49, {KEY_CONFIG} }, + { KE_SW, 0x4a, {.sw = {SW_LID, 1}} }, /* lid close */ + { KE_SW, 0x4b, {.sw = {SW_LID, 0}} }, /* lid open */ + { KE_KEY, 0x6a, {KEY_CONFIG} }, + { KE_KEY, 0x6d, {KEY_POWER} }, + { KE_KEY, 0x71, {KEY_STOPCD} }, + { KE_KEY, 0x72, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x74, {KEY_REWIND} }, + { KE_KEY, 0x78, {KEY_FORWARD} }, + { KE_WIFI, 0x30 }, + { KE_BLUETOOTH, 0x44 }, + { KE_END, 0 } +}; + +static struct key_entry keymap_aopen_1557[] __initdata = { + { KE_KEY, 0x01, {KEY_HELP} }, + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 } +}; + +static struct key_entry keymap_prestigio[] __initdata = { + { KE_KEY, 0x11, {KEY_PROG1} }, + { KE_KEY, 0x12, {KEY_PROG2} }, + { KE_WIFI, 0x30 }, + { KE_KEY, 0x22, {KEY_REWIND} }, + { KE_KEY, 0x23, {KEY_FORWARD} }, + { KE_KEY, 0x24, {KEY_PLAYPAUSE} }, + { KE_KEY, 0x25, {KEY_STOPCD} }, + { KE_KEY, 0x31, {KEY_MAIL} }, + { KE_KEY, 0x36, {KEY_WWW} }, + { KE_END, 0 } +}; + + +/* + * If your machine is not here (which is currently rather likely), please send + * a list of buttons and their key codes (reported when loading this module + * with force=1) and the output of dmidecode to $MODULE_AUTHOR. + */ +static const struct dmi_system_id __initconst dmi_ids[] = { + { + /* Fujitsu-Siemens Amilo Pro V2000 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro V2000"), + }, + .driver_data = keymap_fs_amilo_pro_v2000 + }, + { + /* Fujitsu-Siemens Amilo Pro Edition V3505 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro Edition V3505"), + }, + .driver_data = keymap_fs_amilo_pro_v3505 + }, + { + /* Fujitsu-Siemens Amilo M7400 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO M "), + }, + .driver_data = keymap_fs_amilo_pro_v2000 + }, + { + /* Maxdata Pro 7000 DX */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MAXDATA"), + DMI_MATCH(DMI_PRODUCT_NAME, "Pro 7000"), + }, + .driver_data = keymap_fs_amilo_pro_v2000 + }, + { + /* Fujitsu N3510 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU"), + DMI_MATCH(DMI_PRODUCT_NAME, "N3510"), + }, + .driver_data = keymap_fujitsu_n3510 + }, + { + /* Acer Aspire 1500 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1500"), + }, + .driver_data = keymap_acer_aspire_1500 + }, + { + /* Acer Aspire 1600 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1600"), + }, + .driver_data = keymap_acer_aspire_1600 + }, + { + /* Acer Aspire 3020 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3020"), + }, + .driver_data = keymap_acer_aspire_5020 + }, + { + /* Acer Aspire 5020 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5020"), + }, + .driver_data = keymap_acer_aspire_5020 + }, + { + /* Acer TravelMate 2100 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2100"), + }, + .driver_data = keymap_acer_aspire_5020 + }, + { + /* Acer TravelMate 2410 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2410"), + }, + .driver_data = keymap_acer_travelmate_2410 + }, + { + /* Acer TravelMate C300 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C300"), + }, + .driver_data = keymap_acer_travelmate_300 + }, + { + /* Acer TravelMate C100 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C100"), + }, + .driver_data = keymap_acer_travelmate_300 + }, + { + /* Acer TravelMate C110 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate C110"), + }, + .driver_data = keymap_acer_travelmate_110 + }, + { + /* Acer TravelMate 380 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 380"), + }, + .driver_data = keymap_acer_travelmate_380 + }, + { + /* Acer TravelMate 370 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 370"), + }, + .driver_data = keymap_acer_travelmate_380 /* keyboard minus 1 key */ + }, + { + /* Acer TravelMate 220 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 220"), + }, + .driver_data = keymap_acer_travelmate_220 + }, + { + /* Acer TravelMate 260 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 260"), + }, + .driver_data = keymap_acer_travelmate_220 + }, + { + /* Acer TravelMate 230 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 230"), + /* acerhk looks for "TravelMate F4..." ?! */ + }, + .driver_data = keymap_acer_travelmate_230 + }, + { + /* Acer TravelMate 280 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 280"), + }, + .driver_data = keymap_acer_travelmate_230 + }, + { + /* Acer TravelMate 240 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 240"), + }, + .driver_data = keymap_acer_travelmate_240 + }, + { + /* Acer TravelMate 250 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 250"), + }, + .driver_data = keymap_acer_travelmate_240 + }, + { + /* Acer TravelMate 2424NWXCi */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2420"), + }, + .driver_data = keymap_acer_travelmate_240 + }, + { + /* Acer TravelMate 350 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 350"), + }, + .driver_data = keymap_acer_travelmate_350 + }, + { + /* Acer TravelMate 360 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 360"), + }, + .driver_data = keymap_acer_travelmate_360 + }, + { + /* Acer TravelMate 610 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "ACER"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 610"), + }, + .driver_data = keymap_acer_travelmate_610 + }, + { + /* Acer TravelMate 620 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 620"), + }, + .driver_data = keymap_acer_travelmate_630 + }, + { + /* Acer TravelMate 630 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 630"), + }, + .driver_data = keymap_acer_travelmate_630 + }, + { + /* AOpen 1559AS */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "E2U"), + DMI_MATCH(DMI_BOARD_NAME, "E2U"), + }, + .driver_data = keymap_aopen_1559as + }, + { + /* Medion MD 9783 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"), + DMI_MATCH(DMI_PRODUCT_NAME, "MD 9783"), + }, + .driver_data = keymap_wistron_ms2111 + }, + { + /* Medion MD 40100 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"), + DMI_MATCH(DMI_PRODUCT_NAME, "WID2000"), + }, + .driver_data = keymap_wistron_md40100 + }, + { + /* Medion MD 2900 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONNB"), + DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2000"), + }, + .driver_data = keymap_wistron_md2900 + }, + { + /* Medion MD 42200 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Medion"), + DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2030"), + }, + .driver_data = keymap_fs_amilo_pro_v2000 + }, + { + /* Medion MD 96500 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONPC"), + DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2040"), + }, + .driver_data = keymap_wistron_md96500 + }, + { + /* Medion MD 95400 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "MEDIONPC"), + DMI_MATCH(DMI_PRODUCT_NAME, "WIM 2050"), + }, + .driver_data = keymap_wistron_md96500 + }, + { + /* Fujitsu Siemens Amilo D7820 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), /* not sure */ + DMI_MATCH(DMI_PRODUCT_NAME, "Amilo D"), + }, + .driver_data = keymap_fs_amilo_d88x0 + }, + { + /* Fujitsu Siemens Amilo D88x0 */ + .callback = dmi_matched, + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO D"), + }, + .driver_data = keymap_fs_amilo_d88x0 + }, + { NULL, } +}; + +/* Copy the good keymap, as the original ones are free'd */ +static int __init copy_keymap(void) +{ + const struct key_entry *key; + struct key_entry *new_keymap; + unsigned int length = 1; + + for (key = keymap; key->type != KE_END; key++) + length++; + + new_keymap = kmemdup(keymap, length * sizeof(struct key_entry), + GFP_KERNEL); + if (!new_keymap) + return -ENOMEM; + + keymap = new_keymap; + + return 0; +} + +static int __init select_keymap(void) +{ + dmi_check_system(dmi_ids); + if (keymap_name != NULL) { + if (strcmp (keymap_name, "1557/MS2141") == 0) + keymap = keymap_wistron_ms2141; + else if (strcmp (keymap_name, "aopen1557") == 0) + keymap = keymap_aopen_1557; + else if (strcmp (keymap_name, "prestigio") == 0) + keymap = keymap_prestigio; + else if (strcmp (keymap_name, "generic") == 0) + keymap = keymap_wistron_generic; + else { + printk(KERN_ERR "wistron_btns: Keymap unknown\n"); + return -EINVAL; + } + } + if (keymap == NULL) { + if (!force) { + printk(KERN_ERR "wistron_btns: System unknown\n"); + return -ENODEV; + } + keymap = keymap_empty; + } + + return copy_keymap(); +} + + /* Input layer interface */ + +static struct input_polled_dev *wistron_idev; +static unsigned long jiffies_last_press; +static bool wifi_enabled; +static bool bluetooth_enabled; + + /* led management */ +static void wistron_mail_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + bios_set_state(MAIL_LED, (value != LED_OFF) ? 1 : 0); +} + +/* same as setting up wifi card, but for laptops on which the led is managed */ +static void wistron_wifi_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + bios_set_state(WIFI, (value != LED_OFF) ? 1 : 0); +} + +static struct led_classdev wistron_mail_led = { + .name = "wistron:green:mail", + .brightness_set = wistron_mail_led_set, +}; + +static struct led_classdev wistron_wifi_led = { + .name = "wistron:red:wifi", + .brightness_set = wistron_wifi_led_set, +}; + +static void wistron_led_init(struct device *parent) +{ + if (leds_present & FE_WIFI_LED) { + u16 wifi = bios_get_default_setting(WIFI); + if (wifi & 1) { + wistron_wifi_led.brightness = (wifi & 2) ? LED_FULL : LED_OFF; + if (led_classdev_register(parent, &wistron_wifi_led)) + leds_present &= ~FE_WIFI_LED; + else + bios_set_state(WIFI, wistron_wifi_led.brightness); + + } else + leds_present &= ~FE_WIFI_LED; + } + + if (leds_present & FE_MAIL_LED) { + /* bios_get_default_setting(MAIL) always retuns 0, so just turn the led off */ + wistron_mail_led.brightness = LED_OFF; + if (led_classdev_register(parent, &wistron_mail_led)) + leds_present &= ~FE_MAIL_LED; + else + bios_set_state(MAIL_LED, wistron_mail_led.brightness); + } +} + +static void wistron_led_remove(void) +{ + if (leds_present & FE_MAIL_LED) + led_classdev_unregister(&wistron_mail_led); + + if (leds_present & FE_WIFI_LED) + led_classdev_unregister(&wistron_wifi_led); +} + +static inline void wistron_led_suspend(void) +{ + if (leds_present & FE_MAIL_LED) + led_classdev_suspend(&wistron_mail_led); + + if (leds_present & FE_WIFI_LED) + led_classdev_suspend(&wistron_wifi_led); +} + +static inline void wistron_led_resume(void) +{ + if (leds_present & FE_MAIL_LED) + led_classdev_resume(&wistron_mail_led); + + if (leds_present & FE_WIFI_LED) + led_classdev_resume(&wistron_wifi_led); +} + +static void handle_key(u8 code) +{ + const struct key_entry *key = + sparse_keymap_entry_from_scancode(wistron_idev->input, code); + + if (key) { + switch (key->type) { + case KE_WIFI: + if (have_wifi) { + wifi_enabled = !wifi_enabled; + bios_set_state(WIFI, wifi_enabled); + } + break; + + case KE_BLUETOOTH: + if (have_bluetooth) { + bluetooth_enabled = !bluetooth_enabled; + bios_set_state(BLUETOOTH, bluetooth_enabled); + } + break; + + default: + sparse_keymap_report_entry(wistron_idev->input, + key, 1, true); + break; + } + jiffies_last_press = jiffies; + } else + printk(KERN_NOTICE + "wistron_btns: Unknown key code %02X\n", code); +} + +static void poll_bios(bool discard) +{ + u8 qlen; + u16 val; + + for (;;) { + qlen = CMOS_READ(cmos_address); + if (qlen == 0) + break; + val = bios_pop_queue(); + if (val != 0 && !discard) + handle_key((u8)val); + } +} + +static void wistron_flush(struct input_polled_dev *dev) +{ + /* Flush stale event queue */ + poll_bios(true); +} + +static void wistron_poll(struct input_polled_dev *dev) +{ + poll_bios(false); + + /* Increase poll frequency if user is currently pressing keys (< 2s ago) */ + if (time_before(jiffies, jiffies_last_press + 2 * HZ)) + dev->poll_interval = POLL_INTERVAL_BURST; + else + dev->poll_interval = POLL_INTERVAL_DEFAULT; +} + +static int wistron_setup_keymap(struct input_dev *dev, + struct key_entry *entry) +{ + switch (entry->type) { + + /* if wifi or bluetooth are not available, create normal keys */ + case KE_WIFI: + if (!have_wifi) { + entry->type = KE_KEY; + entry->keycode = KEY_WLAN; + } + break; + + case KE_BLUETOOTH: + if (!have_bluetooth) { + entry->type = KE_KEY; + entry->keycode = KEY_BLUETOOTH; + } + break; + + case KE_END: + if (entry->code & FE_UNTESTED) + printk(KERN_WARNING "Untested laptop multimedia keys, " + "please report success or failure to " + "eric.piel@tremplin-utc.net\n"); + break; + } + + return 0; +} + +static int setup_input_dev(void) +{ + struct input_dev *input_dev; + int error; + + wistron_idev = input_allocate_polled_device(); + if (!wistron_idev) + return -ENOMEM; + + wistron_idev->open = wistron_flush; + wistron_idev->poll = wistron_poll; + wistron_idev->poll_interval = POLL_INTERVAL_DEFAULT; + + input_dev = wistron_idev->input; + input_dev->name = "Wistron laptop buttons"; + input_dev->phys = "wistron/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &wistron_device->dev; + + error = sparse_keymap_setup(input_dev, keymap, wistron_setup_keymap); + if (error) + goto err_free_dev; + + error = input_register_polled_device(wistron_idev); + if (error) + goto err_free_keymap; + + return 0; + + err_free_keymap: + sparse_keymap_free(input_dev); + err_free_dev: + input_free_polled_device(wistron_idev); + return error; +} + +/* Driver core */ + +static int wistron_probe(struct platform_device *dev) +{ + int err; + + bios_attach(); + cmos_address = bios_get_cmos_address(); + + if (have_wifi) { + u16 wifi = bios_get_default_setting(WIFI); + if (wifi & 1) + wifi_enabled = wifi & 2; + else + have_wifi = 0; + + if (have_wifi) + bios_set_state(WIFI, wifi_enabled); + } + + if (have_bluetooth) { + u16 bt = bios_get_default_setting(BLUETOOTH); + if (bt & 1) + bluetooth_enabled = bt & 2; + else + have_bluetooth = false; + + if (have_bluetooth) + bios_set_state(BLUETOOTH, bluetooth_enabled); + } + + wistron_led_init(&dev->dev); + + err = setup_input_dev(); + if (err) { + bios_detach(); + return err; + } + + return 0; +} + +static int wistron_remove(struct platform_device *dev) +{ + wistron_led_remove(); + input_unregister_polled_device(wistron_idev); + sparse_keymap_free(wistron_idev->input); + input_free_polled_device(wistron_idev); + bios_detach(); + + return 0; +} + +#ifdef CONFIG_PM +static int wistron_suspend(struct device *dev) +{ + if (have_wifi) + bios_set_state(WIFI, 0); + + if (have_bluetooth) + bios_set_state(BLUETOOTH, 0); + + wistron_led_suspend(); + + return 0; +} + +static int wistron_resume(struct device *dev) +{ + if (have_wifi) + bios_set_state(WIFI, wifi_enabled); + + if (have_bluetooth) + bios_set_state(BLUETOOTH, bluetooth_enabled); + + wistron_led_resume(); + + poll_bios(true); + + return 0; +} + +static const struct dev_pm_ops wistron_pm_ops = { + .suspend = wistron_suspend, + .resume = wistron_resume, + .poweroff = wistron_suspend, + .restore = wistron_resume, +}; +#endif + +static struct platform_driver wistron_driver = { + .driver = { + .name = "wistron-bios", + .owner = THIS_MODULE, +#ifdef CONFIG_PM + .pm = &wistron_pm_ops, +#endif + }, + .probe = wistron_probe, + .remove = wistron_remove, +}; + +static int __init wb_module_init(void) +{ + int err; + + err = select_keymap(); + if (err) + return err; + + err = map_bios(); + if (err) + goto err_free_keymap; + + err = platform_driver_register(&wistron_driver); + if (err) + goto err_unmap_bios; + + wistron_device = platform_device_alloc("wistron-bios", -1); + if (!wistron_device) { + err = -ENOMEM; + goto err_unregister_driver; + } + + err = platform_device_add(wistron_device); + if (err) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(wistron_device); + err_unregister_driver: + platform_driver_unregister(&wistron_driver); + err_unmap_bios: + unmap_bios(); + err_free_keymap: + kfree(keymap); + + return err; +} + +static void __exit wb_module_exit(void) +{ + platform_device_unregister(wistron_device); + platform_driver_unregister(&wistron_driver); + unmap_bios(); + kfree(keymap); +} + +module_init(wb_module_init); +module_exit(wb_module_exit); diff --git a/drivers/input/misc/wm831x-on.c b/drivers/input/misc/wm831x-on.c new file mode 100644 index 00000000..caa2c406 --- /dev/null +++ b/drivers/input/misc/wm831x-on.c @@ -0,0 +1,151 @@ +/** + * wm831x-on.c - WM831X ON pin driver + * + * Copyright (C) 2009 Wolfson Microelectronics plc + * + * This file is subject to the terms and conditions of the GNU General + * Public License. See the file "COPYING" in the main directory of this + * archive for more details. + * + * 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/init.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/mfd/wm831x/core.h> + +struct wm831x_on { + struct input_dev *dev; + struct delayed_work work; + struct wm831x *wm831x; +}; + +/* + * The chip gives us an interrupt when the ON pin is asserted but we + * then need to poll to see when the pin is deasserted. + */ +static void wm831x_poll_on(struct work_struct *work) +{ + struct wm831x_on *wm831x_on = container_of(work, struct wm831x_on, + work.work); + struct wm831x *wm831x = wm831x_on->wm831x; + int poll, ret; + + ret = wm831x_reg_read(wm831x, WM831X_ON_PIN_CONTROL); + if (ret >= 0) { + poll = !(ret & WM831X_ON_PIN_STS); + + input_report_key(wm831x_on->dev, KEY_POWER, poll); + input_sync(wm831x_on->dev); + } else { + dev_err(wm831x->dev, "Failed to read ON status: %d\n", ret); + poll = 1; + } + + if (poll) + schedule_delayed_work(&wm831x_on->work, 100); +} + +static irqreturn_t wm831x_on_irq(int irq, void *data) +{ + struct wm831x_on *wm831x_on = data; + + schedule_delayed_work(&wm831x_on->work, 0); + + return IRQ_HANDLED; +} + +static int wm831x_on_probe(struct platform_device *pdev) +{ + struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent); + struct wm831x_on *wm831x_on; + int irq = wm831x_irq(wm831x, platform_get_irq(pdev, 0)); + int ret; + + wm831x_on = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_on), + GFP_KERNEL); + if (!wm831x_on) { + dev_err(&pdev->dev, "Can't allocate data\n"); + return -ENOMEM; + } + + wm831x_on->wm831x = wm831x; + INIT_DELAYED_WORK(&wm831x_on->work, wm831x_poll_on); + + wm831x_on->dev = devm_input_allocate_device(&pdev->dev); + if (!wm831x_on->dev) { + dev_err(&pdev->dev, "Can't allocate input dev\n"); + ret = -ENOMEM; + goto err; + } + + wm831x_on->dev->evbit[0] = BIT_MASK(EV_KEY); + wm831x_on->dev->keybit[BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER); + wm831x_on->dev->name = "wm831x_on"; + wm831x_on->dev->phys = "wm831x_on/input0"; + wm831x_on->dev->dev.parent = &pdev->dev; + + ret = request_threaded_irq(irq, NULL, wm831x_on_irq, + IRQF_TRIGGER_RISING, "wm831x_on", + wm831x_on); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to request IRQ: %d\n", ret); + goto err_input_dev; + } + ret = input_register_device(wm831x_on->dev); + if (ret) { + dev_dbg(&pdev->dev, "Can't register input device: %d\n", ret); + goto err_irq; + } + + platform_set_drvdata(pdev, wm831x_on); + + return 0; + +err_irq: + free_irq(irq, wm831x_on); +err_input_dev: +err: + return ret; +} + +static int wm831x_on_remove(struct platform_device *pdev) +{ + struct wm831x_on *wm831x_on = platform_get_drvdata(pdev); + int irq = platform_get_irq(pdev, 0); + + free_irq(irq, wm831x_on); + cancel_delayed_work_sync(&wm831x_on->work); + + return 0; +} + +static struct platform_driver wm831x_on_driver = { + .probe = wm831x_on_probe, + .remove = wm831x_on_remove, + .driver = { + .name = "wm831x-on", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(wm831x_on_driver); + +MODULE_ALIAS("platform:wm831x-on"); +MODULE_DESCRIPTION("WM831x ON pin"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>"); + diff --git a/drivers/input/misc/xen-kbdfront.c b/drivers/input/misc/xen-kbdfront.c new file mode 100644 index 00000000..e21c1816 --- /dev/null +++ b/drivers/input/misc/xen-kbdfront.c @@ -0,0 +1,396 @@ +/* + * Xen para-virtual input device + * + * Copyright (C) 2005 Anthony Liguori <aliguori@us.ibm.com> + * Copyright (C) 2006-2008 Red Hat, Inc., Markus Armbruster <armbru@redhat.com> + * + * Based on linux/drivers/input/mouse/sermouse.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/input.h> +#include <linux/slab.h> + +#include <asm/xen/hypervisor.h> + +#include <xen/xen.h> +#include <xen/events.h> +#include <xen/page.h> +#include <xen/grant_table.h> +#include <xen/interface/grant_table.h> +#include <xen/interface/io/fbif.h> +#include <xen/interface/io/kbdif.h> +#include <xen/xenbus.h> + +struct xenkbd_info { + struct input_dev *kbd; + struct input_dev *ptr; + struct xenkbd_page *page; + int gref; + int irq; + struct xenbus_device *xbdev; + char phys[32]; +}; + +static int xenkbd_remove(struct xenbus_device *); +static int xenkbd_connect_backend(struct xenbus_device *, struct xenkbd_info *); +static void xenkbd_disconnect_backend(struct xenkbd_info *); + +/* + * Note: if you need to send out events, see xenfb_do_update() for how + * to do that. + */ + +static irqreturn_t input_handler(int rq, void *dev_id) +{ + struct xenkbd_info *info = dev_id; + struct xenkbd_page *page = info->page; + __u32 cons, prod; + + prod = page->in_prod; + if (prod == page->in_cons) + return IRQ_HANDLED; + rmb(); /* ensure we see ring contents up to prod */ + for (cons = page->in_cons; cons != prod; cons++) { + union xenkbd_in_event *event; + struct input_dev *dev; + event = &XENKBD_IN_RING_REF(page, cons); + + dev = info->ptr; + switch (event->type) { + case XENKBD_TYPE_MOTION: + input_report_rel(dev, REL_X, event->motion.rel_x); + input_report_rel(dev, REL_Y, event->motion.rel_y); + if (event->motion.rel_z) + input_report_rel(dev, REL_WHEEL, + -event->motion.rel_z); + break; + case XENKBD_TYPE_KEY: + dev = NULL; + if (test_bit(event->key.keycode, info->kbd->keybit)) + dev = info->kbd; + if (test_bit(event->key.keycode, info->ptr->keybit)) + dev = info->ptr; + if (dev) + input_report_key(dev, event->key.keycode, + event->key.pressed); + else + pr_warning("unhandled keycode 0x%x\n", + event->key.keycode); + break; + case XENKBD_TYPE_POS: + input_report_abs(dev, ABS_X, event->pos.abs_x); + input_report_abs(dev, ABS_Y, event->pos.abs_y); + if (event->pos.rel_z) + input_report_rel(dev, REL_WHEEL, + -event->pos.rel_z); + break; + } + if (dev) + input_sync(dev); + } + mb(); /* ensure we got ring contents */ + page->in_cons = cons; + notify_remote_via_irq(info->irq); + + return IRQ_HANDLED; +} + +static int xenkbd_probe(struct xenbus_device *dev, + const struct xenbus_device_id *id) +{ + int ret, i, abs; + struct xenkbd_info *info; + struct input_dev *kbd, *ptr; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) { + xenbus_dev_fatal(dev, -ENOMEM, "allocating info structure"); + return -ENOMEM; + } + dev_set_drvdata(&dev->dev, info); + info->xbdev = dev; + info->irq = -1; + info->gref = -1; + snprintf(info->phys, sizeof(info->phys), "xenbus/%s", dev->nodename); + + info->page = (void *)__get_free_page(GFP_KERNEL | __GFP_ZERO); + if (!info->page) + goto error_nomem; + + if (xenbus_scanf(XBT_NIL, dev->otherend, "feature-abs-pointer", "%d", &abs) < 0) + abs = 0; + if (abs) + xenbus_printf(XBT_NIL, dev->nodename, "request-abs-pointer", "1"); + + /* keyboard */ + kbd = input_allocate_device(); + if (!kbd) + goto error_nomem; + kbd->name = "Xen Virtual Keyboard"; + kbd->phys = info->phys; + kbd->id.bustype = BUS_PCI; + kbd->id.vendor = 0x5853; + kbd->id.product = 0xffff; + + __set_bit(EV_KEY, kbd->evbit); + for (i = KEY_ESC; i < KEY_UNKNOWN; i++) + __set_bit(i, kbd->keybit); + for (i = KEY_OK; i < KEY_MAX; i++) + __set_bit(i, kbd->keybit); + + ret = input_register_device(kbd); + if (ret) { + input_free_device(kbd); + xenbus_dev_fatal(dev, ret, "input_register_device(kbd)"); + goto error; + } + info->kbd = kbd; + + /* pointing device */ + ptr = input_allocate_device(); + if (!ptr) + goto error_nomem; + ptr->name = "Xen Virtual Pointer"; + ptr->phys = info->phys; + ptr->id.bustype = BUS_PCI; + ptr->id.vendor = 0x5853; + ptr->id.product = 0xfffe; + + if (abs) { + __set_bit(EV_ABS, ptr->evbit); + input_set_abs_params(ptr, ABS_X, 0, XENFB_WIDTH, 0, 0); + input_set_abs_params(ptr, ABS_Y, 0, XENFB_HEIGHT, 0, 0); + } else { + input_set_capability(ptr, EV_REL, REL_X); + input_set_capability(ptr, EV_REL, REL_Y); + } + input_set_capability(ptr, EV_REL, REL_WHEEL); + + __set_bit(EV_KEY, ptr->evbit); + for (i = BTN_LEFT; i <= BTN_TASK; i++) + __set_bit(i, ptr->keybit); + + ret = input_register_device(ptr); + if (ret) { + input_free_device(ptr); + xenbus_dev_fatal(dev, ret, "input_register_device(ptr)"); + goto error; + } + info->ptr = ptr; + + ret = xenkbd_connect_backend(dev, info); + if (ret < 0) + goto error; + + return 0; + + error_nomem: + ret = -ENOMEM; + xenbus_dev_fatal(dev, ret, "allocating device memory"); + error: + xenkbd_remove(dev); + return ret; +} + +static int xenkbd_resume(struct xenbus_device *dev) +{ + struct xenkbd_info *info = dev_get_drvdata(&dev->dev); + + xenkbd_disconnect_backend(info); + memset(info->page, 0, PAGE_SIZE); + return xenkbd_connect_backend(dev, info); +} + +static int xenkbd_remove(struct xenbus_device *dev) +{ + struct xenkbd_info *info = dev_get_drvdata(&dev->dev); + + xenkbd_disconnect_backend(info); + if (info->kbd) + input_unregister_device(info->kbd); + if (info->ptr) + input_unregister_device(info->ptr); + free_page((unsigned long)info->page); + kfree(info); + return 0; +} + +static int xenkbd_connect_backend(struct xenbus_device *dev, + struct xenkbd_info *info) +{ + int ret, evtchn; + struct xenbus_transaction xbt; + + ret = gnttab_grant_foreign_access(dev->otherend_id, + virt_to_mfn(info->page), 0); + if (ret < 0) + return ret; + info->gref = ret; + + ret = xenbus_alloc_evtchn(dev, &evtchn); + if (ret) + goto error_grant; + ret = bind_evtchn_to_irqhandler(evtchn, input_handler, + 0, dev->devicetype, info); + if (ret < 0) { + xenbus_dev_fatal(dev, ret, "bind_evtchn_to_irqhandler"); + goto error_evtchan; + } + info->irq = ret; + + again: + ret = xenbus_transaction_start(&xbt); + if (ret) { + xenbus_dev_fatal(dev, ret, "starting transaction"); + goto error_irqh; + } + ret = xenbus_printf(xbt, dev->nodename, "page-ref", "%lu", + virt_to_mfn(info->page)); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "page-gref", "%u", info->gref); + if (ret) + goto error_xenbus; + ret = xenbus_printf(xbt, dev->nodename, "event-channel", "%u", + evtchn); + if (ret) + goto error_xenbus; + ret = xenbus_transaction_end(xbt, 0); + if (ret) { + if (ret == -EAGAIN) + goto again; + xenbus_dev_fatal(dev, ret, "completing transaction"); + goto error_irqh; + } + + xenbus_switch_state(dev, XenbusStateInitialised); + return 0; + + error_xenbus: + xenbus_transaction_end(xbt, 1); + xenbus_dev_fatal(dev, ret, "writing xenstore"); + error_irqh: + unbind_from_irqhandler(info->irq, info); + info->irq = -1; + error_evtchan: + xenbus_free_evtchn(dev, evtchn); + error_grant: + gnttab_end_foreign_access_ref(info->gref, 0); + info->gref = -1; + return ret; +} + +static void xenkbd_disconnect_backend(struct xenkbd_info *info) +{ + if (info->irq >= 0) + unbind_from_irqhandler(info->irq, info); + info->irq = -1; + if (info->gref >= 0) + gnttab_end_foreign_access_ref(info->gref, 0); + info->gref = -1; +} + +static void xenkbd_backend_changed(struct xenbus_device *dev, + enum xenbus_state backend_state) +{ + struct xenkbd_info *info = dev_get_drvdata(&dev->dev); + int ret, val; + + switch (backend_state) { + case XenbusStateInitialising: + case XenbusStateInitialised: + case XenbusStateReconfiguring: + case XenbusStateReconfigured: + case XenbusStateUnknown: + break; + + case XenbusStateInitWait: +InitWait: + ret = xenbus_scanf(XBT_NIL, info->xbdev->otherend, + "feature-abs-pointer", "%d", &val); + if (ret < 0) + val = 0; + if (val) { + ret = xenbus_printf(XBT_NIL, info->xbdev->nodename, + "request-abs-pointer", "1"); + if (ret) + pr_warning("xenkbd: can't request abs-pointer"); + } + + xenbus_switch_state(dev, XenbusStateConnected); + break; + + case XenbusStateConnected: + /* + * Work around xenbus race condition: If backend goes + * through InitWait to Connected fast enough, we can + * get Connected twice here. + */ + if (dev->state != XenbusStateConnected) + goto InitWait; /* no InitWait seen yet, fudge it */ + + /* Set input abs params to match backend screen res */ + if (xenbus_scanf(XBT_NIL, info->xbdev->otherend, + "width", "%d", &val) > 0) + input_set_abs_params(info->ptr, ABS_X, 0, val, 0, 0); + + if (xenbus_scanf(XBT_NIL, info->xbdev->otherend, + "height", "%d", &val) > 0) + input_set_abs_params(info->ptr, ABS_Y, 0, val, 0, 0); + + break; + + case XenbusStateClosed: + if (dev->state == XenbusStateClosed) + break; + /* Missed the backend's CLOSING state -- fallthrough */ + case XenbusStateClosing: + xenbus_frontend_closed(dev); + break; + } +} + +static const struct xenbus_device_id xenkbd_ids[] = { + { "vkbd" }, + { "" } +}; + +static DEFINE_XENBUS_DRIVER(xenkbd, , + .probe = xenkbd_probe, + .remove = xenkbd_remove, + .resume = xenkbd_resume, + .otherend_changed = xenkbd_backend_changed, +); + +static int __init xenkbd_init(void) +{ + if (!xen_domain()) + return -ENODEV; + + /* Nothing to do if running in dom0. */ + if (xen_initial_domain()) + return -ENODEV; + + return xenbus_register_frontend(&xenkbd_driver); +} + +static void __exit xenkbd_cleanup(void) +{ + xenbus_unregister_driver(&xenkbd_driver); +} + +module_init(xenkbd_init); +module_exit(xenkbd_cleanup); + +MODULE_DESCRIPTION("Xen virtual keyboard/pointer device frontend"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("xen:vkbd"); diff --git a/drivers/input/misc/yealink.c b/drivers/input/misc/yealink.c new file mode 100644 index 00000000..285a5bd6 --- /dev/null +++ b/drivers/input/misc/yealink.c @@ -0,0 +1,1008 @@ +/* + * drivers/usb/input/yealink.c + * + * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +/* + * Description: + * Driver for the USB-P1K voip usb phone. + * This device is produced by Yealink Network Technology Co Ltd + * but may be branded under several names: + * - Yealink usb-p1k + * - Tiptel 115 + * - ... + * + * This driver is based on: + * - the usbb2k-api http://savannah.nongnu.org/projects/usbb2k-api/ + * - information from http://memeteau.free.fr/usbb2k + * - the xpad-driver drivers/input/joystick/xpad.c + * + * Thanks to: + * - Olivier Vandorpe, for providing the usbb2k-api. + * - Martin Diehl, for spotting my memory allocation bug. + * + * History: + * 20050527 henk First version, functional keyboard. Keyboard events + * will pop-up on the ../input/eventX bus. + * 20050531 henk Added led, LCD, dialtone and sysfs interface. + * 20050610 henk Cleanups, make it ready for public consumption. + * 20050630 henk Cleanups, fixes in response to comments. + * 20050701 henk sysfs write serialisation, fix potential unload races + * 20050801 henk Added ringtone, restructure USB + * 20050816 henk Merge 2.6.13-rc6 + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/rwsem.h> +#include <linux/usb/input.h> +#include <linux/map_to_7segment.h> + +#include "yealink.h" + +#define DRIVER_VERSION "yld-20051230" +#define DRIVER_AUTHOR "Henk Vergonet" +#define DRIVER_DESC "Yealink phone driver" + +#define YEALINK_POLLING_FREQUENCY 10 /* in [Hz] */ + +struct yld_status { + u8 lcd[24]; + u8 led; + u8 dialtone; + u8 ringtone; + u8 keynum; +} __attribute__ ((packed)); + +/* + * Register the LCD segment and icon map + */ +#define _LOC(k,l) { .a = (k), .m = (l) } +#define _SEG(t, a, am, b, bm, c, cm, d, dm, e, em, f, fm, g, gm) \ + { .type = (t), \ + .u = { .s = { _LOC(a, am), _LOC(b, bm), _LOC(c, cm), \ + _LOC(d, dm), _LOC(e, em), _LOC(g, gm), \ + _LOC(f, fm) } } } +#define _PIC(t, h, hm, n) \ + { .type = (t), \ + .u = { .p = { .name = (n), .a = (h), .m = (hm) } } } + +static const struct lcd_segment_map { + char type; + union { + struct pictogram_map { + u8 a,m; + char name[10]; + } p; + struct segment_map { + u8 a,m; + } s[7]; + } u; +} lcdMap[] = { +#include "yealink.h" +}; + +struct yealink_dev { + struct input_dev *idev; /* input device */ + struct usb_device *udev; /* usb device */ + struct usb_interface *intf; /* usb interface */ + + /* irq input channel */ + struct yld_ctl_packet *irq_data; + dma_addr_t irq_dma; + struct urb *urb_irq; + + /* control output channel */ + struct yld_ctl_packet *ctl_data; + dma_addr_t ctl_dma; + struct usb_ctrlrequest *ctl_req; + struct urb *urb_ctl; + + char phys[64]; /* physical device path */ + + u8 lcdMap[ARRAY_SIZE(lcdMap)]; /* state of LCD, LED ... */ + int key_code; /* last reported key */ + + unsigned int shutdown:1; + + int stat_ix; + union { + struct yld_status s; + u8 b[sizeof(struct yld_status)]; + } master, copy; +}; + + +/******************************************************************************* + * Yealink lcd interface + ******************************************************************************/ + +/* + * Register a default 7 segment character set + */ +static SEG7_DEFAULT_MAP(map_seg7); + + /* Display a char, + * char '\9' and '\n' are placeholders and do not overwrite the original text. + * A space will always hide an icon. + */ +static int setChar(struct yealink_dev *yld, int el, int chr) +{ + int i, a, m, val; + + if (el >= ARRAY_SIZE(lcdMap)) + return -EINVAL; + + if (chr == '\t' || chr == '\n') + return 0; + + yld->lcdMap[el] = chr; + + if (lcdMap[el].type == '.') { + a = lcdMap[el].u.p.a; + m = lcdMap[el].u.p.m; + if (chr != ' ') + yld->master.b[a] |= m; + else + yld->master.b[a] &= ~m; + return 0; + } + + val = map_to_seg7(&map_seg7, chr); + for (i = 0; i < ARRAY_SIZE(lcdMap[0].u.s); i++) { + m = lcdMap[el].u.s[i].m; + + if (m == 0) + continue; + + a = lcdMap[el].u.s[i].a; + if (val & 1) + yld->master.b[a] |= m; + else + yld->master.b[a] &= ~m; + val = val >> 1; + } + return 0; +}; + +/******************************************************************************* + * Yealink key interface + ******************************************************************************/ + +/* Map device buttons to internal key events. + * + * USB-P1K button layout: + * + * up + * IN OUT + * down + * + * pickup C hangup + * 1 2 3 + * 4 5 6 + * 7 8 9 + * * 0 # + * + * The "up" and "down" keys, are symbolised by arrows on the button. + * The "pickup" and "hangup" keys are symbolised by a green and red phone + * on the button. + */ +static int map_p1k_to_key(int scancode) +{ + switch(scancode) { /* phone key: */ + case 0x23: return KEY_LEFT; /* IN */ + case 0x33: return KEY_UP; /* up */ + case 0x04: return KEY_RIGHT; /* OUT */ + case 0x24: return KEY_DOWN; /* down */ + case 0x03: return KEY_ENTER; /* pickup */ + case 0x14: return KEY_BACKSPACE; /* C */ + case 0x13: return KEY_ESC; /* hangup */ + case 0x00: return KEY_1; /* 1 */ + case 0x01: return KEY_2; /* 2 */ + case 0x02: return KEY_3; /* 3 */ + case 0x10: return KEY_4; /* 4 */ + case 0x11: return KEY_5; /* 5 */ + case 0x12: return KEY_6; /* 6 */ + case 0x20: return KEY_7; /* 7 */ + case 0x21: return KEY_8; /* 8 */ + case 0x22: return KEY_9; /* 9 */ + case 0x30: return KEY_KPASTERISK; /* * */ + case 0x31: return KEY_0; /* 0 */ + case 0x32: return KEY_LEFTSHIFT | + KEY_3 << 8; /* # */ + } + return -EINVAL; +} + +/* Completes a request by converting the data into events for the + * input subsystem. + * + * The key parameter can be cascaded: key2 << 8 | key1 + */ +static void report_key(struct yealink_dev *yld, int key) +{ + struct input_dev *idev = yld->idev; + + if (yld->key_code >= 0) { + /* old key up */ + input_report_key(idev, yld->key_code & 0xff, 0); + if (yld->key_code >> 8) + input_report_key(idev, yld->key_code >> 8, 0); + } + + yld->key_code = key; + if (key >= 0) { + /* new valid key */ + input_report_key(idev, key & 0xff, 1); + if (key >> 8) + input_report_key(idev, key >> 8, 1); + } + input_sync(idev); +} + +/******************************************************************************* + * Yealink usb communication interface + ******************************************************************************/ + +static int yealink_cmd(struct yealink_dev *yld, struct yld_ctl_packet *p) +{ + u8 *buf = (u8 *)p; + int i; + u8 sum = 0; + + for(i=0; i<USB_PKT_LEN-1; i++) + sum -= buf[i]; + p->sum = sum; + return usb_control_msg(yld->udev, + usb_sndctrlpipe(yld->udev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 0x200, 3, + p, sizeof(*p), + USB_CTRL_SET_TIMEOUT); +} + +static u8 default_ringtone[] = { + 0xEF, /* volume [0-255] */ + 0xFB, 0x1E, 0x00, 0x0C, /* 1250 [hz], 12/100 [s] */ + 0xFC, 0x18, 0x00, 0x0C, /* 1000 [hz], 12/100 [s] */ + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFB, 0x1E, 0x00, 0x0C, + 0xFC, 0x18, 0x00, 0x0C, + 0xFF, 0xFF, 0x01, 0x90, /* silent, 400/100 [s] */ + 0x00, 0x00 /* end of sequence */ +}; + +static int yealink_set_ringtone(struct yealink_dev *yld, u8 *buf, size_t size) +{ + struct yld_ctl_packet *p = yld->ctl_data; + int ix, len; + + if (size <= 0) + return -EINVAL; + + /* Set the ringtone volume */ + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_RING_VOLUME; + yld->ctl_data->size = 1; + yld->ctl_data->data[0] = buf[0]; + yealink_cmd(yld, p); + + buf++; + size--; + + p->cmd = CMD_RING_NOTE; + ix = 0; + while (size != ix) { + len = size - ix; + if (len > sizeof(p->data)) + len = sizeof(p->data); + p->size = len; + p->offset = cpu_to_be16(ix); + memcpy(p->data, &buf[ix], len); + yealink_cmd(yld, p); + ix += len; + } + return 0; +} + +/* keep stat_master & stat_copy in sync. + */ +static int yealink_do_idle_tasks(struct yealink_dev *yld) +{ + u8 val; + int i, ix, len; + + ix = yld->stat_ix; + + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_KEYPRESS; + yld->ctl_data->size = 1; + yld->ctl_data->sum = 0xff - CMD_KEYPRESS; + + /* If state update pointer wraps do a KEYPRESS first. */ + if (ix >= sizeof(yld->master)) { + yld->stat_ix = 0; + return 0; + } + + /* find update candidates: copy != master */ + do { + val = yld->master.b[ix]; + if (val != yld->copy.b[ix]) + goto send_update; + } while (++ix < sizeof(yld->master)); + + /* nothing todo, wait a bit and poll for a KEYPRESS */ + yld->stat_ix = 0; + /* TODO how can we wait abit. ?? + * msleep_interruptible(1000 / YEALINK_POLLING_FREQUENCY); + */ + return 0; + +send_update: + + /* Setup an appropriate update request */ + yld->copy.b[ix] = val; + yld->ctl_data->data[0] = val; + + switch(ix) { + case offsetof(struct yld_status, led): + yld->ctl_data->cmd = CMD_LED; + yld->ctl_data->sum = -1 - CMD_LED - val; + break; + case offsetof(struct yld_status, dialtone): + yld->ctl_data->cmd = CMD_DIALTONE; + yld->ctl_data->sum = -1 - CMD_DIALTONE - val; + break; + case offsetof(struct yld_status, ringtone): + yld->ctl_data->cmd = CMD_RINGTONE; + yld->ctl_data->sum = -1 - CMD_RINGTONE - val; + break; + case offsetof(struct yld_status, keynum): + val--; + val &= 0x1f; + yld->ctl_data->cmd = CMD_SCANCODE; + yld->ctl_data->offset = cpu_to_be16(val); + yld->ctl_data->data[0] = 0; + yld->ctl_data->sum = -1 - CMD_SCANCODE - val; + break; + default: + len = sizeof(yld->master.s.lcd) - ix; + if (len > sizeof(yld->ctl_data->data)) + len = sizeof(yld->ctl_data->data); + + /* Combine up to <len> consecutive LCD bytes in a singe request + */ + yld->ctl_data->cmd = CMD_LCD; + yld->ctl_data->offset = cpu_to_be16(ix); + yld->ctl_data->size = len; + yld->ctl_data->sum = -CMD_LCD - ix - val - len; + for(i=1; i<len; i++) { + ix++; + val = yld->master.b[ix]; + yld->copy.b[ix] = val; + yld->ctl_data->data[i] = val; + yld->ctl_data->sum -= val; + } + } + yld->stat_ix = ix + 1; + return 1; +} + +/* Decide on how to handle responses + * + * The state transition diagram is somethhing like: + * + * syncState<--+ + * | | + * | idle + * \|/ | + * init --ok--> waitForKey --ok--> getKey + * ^ ^ | + * | +-------ok-------+ + * error,start + * + */ +static void urb_irq_callback(struct urb *urb) +{ + struct yealink_dev *yld = urb->context; + int ret, status = urb->status; + + if (status) + dev_err(&yld->intf->dev, "%s - urb status %d\n", + __func__, status); + + switch (yld->irq_data->cmd) { + case CMD_KEYPRESS: + + yld->master.s.keynum = yld->irq_data->data[0]; + break; + + case CMD_SCANCODE: + dev_dbg(&yld->intf->dev, "get scancode %x\n", + yld->irq_data->data[0]); + + report_key(yld, map_p1k_to_key(yld->irq_data->data[0])); + break; + + default: + dev_err(&yld->intf->dev, "unexpected response %x\n", + yld->irq_data->cmd); + } + + yealink_do_idle_tasks(yld); + + if (!yld->shutdown) { + ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC); + if (ret && ret != -EPERM) + dev_err(&yld->intf->dev, + "%s - usb_submit_urb failed %d\n", + __func__, ret); + } +} + +static void urb_ctl_callback(struct urb *urb) +{ + struct yealink_dev *yld = urb->context; + int ret = 0, status = urb->status; + + if (status) + dev_err(&yld->intf->dev, "%s - urb status %d\n", + __func__, status); + + switch (yld->ctl_data->cmd) { + case CMD_KEYPRESS: + case CMD_SCANCODE: + /* ask for a response */ + if (!yld->shutdown) + ret = usb_submit_urb(yld->urb_irq, GFP_ATOMIC); + break; + default: + /* send new command */ + yealink_do_idle_tasks(yld); + if (!yld->shutdown) + ret = usb_submit_urb(yld->urb_ctl, GFP_ATOMIC); + break; + } + + if (ret && ret != -EPERM) + dev_err(&yld->intf->dev, "%s - usb_submit_urb failed %d\n", + __func__, ret); +} + +/******************************************************************************* + * input event interface + ******************************************************************************/ + +/* TODO should we issue a ringtone on a SND_BELL event? +static int input_ev(struct input_dev *dev, unsigned int type, + unsigned int code, int value) +{ + + if (type != EV_SND) + return -EINVAL; + + switch (code) { + case SND_BELL: + case SND_TONE: + break; + default: + return -EINVAL; + } + + return 0; +} +*/ + +static int input_open(struct input_dev *dev) +{ + struct yealink_dev *yld = input_get_drvdata(dev); + int i, ret; + + dev_dbg(&yld->intf->dev, "%s\n", __func__); + + /* force updates to device */ + for (i = 0; i<sizeof(yld->master); i++) + yld->copy.b[i] = ~yld->master.b[i]; + yld->key_code = -1; /* no keys pressed */ + + yealink_set_ringtone(yld, default_ringtone, sizeof(default_ringtone)); + + /* issue INIT */ + memset(yld->ctl_data, 0, sizeof(*(yld->ctl_data))); + yld->ctl_data->cmd = CMD_INIT; + yld->ctl_data->size = 10; + yld->ctl_data->sum = 0x100-CMD_INIT-10; + if ((ret = usb_submit_urb(yld->urb_ctl, GFP_KERNEL)) != 0) { + dev_dbg(&yld->intf->dev, + "%s - usb_submit_urb failed with result %d\n", + __func__, ret); + return ret; + } + return 0; +} + +static void input_close(struct input_dev *dev) +{ + struct yealink_dev *yld = input_get_drvdata(dev); + + yld->shutdown = 1; + /* + * Make sure the flag is seen by other CPUs before we start + * killing URBs so new URBs won't be submitted + */ + smp_wmb(); + + usb_kill_urb(yld->urb_ctl); + usb_kill_urb(yld->urb_irq); + + yld->shutdown = 0; + smp_wmb(); +} + +/******************************************************************************* + * sysfs interface + ******************************************************************************/ + +static DECLARE_RWSEM(sysfs_rwsema); + +/* Interface to the 7-segments translation table aka. char set. + */ +static ssize_t show_map(struct device *dev, struct device_attribute *attr, + char *buf) +{ + memcpy(buf, &map_seg7, sizeof(map_seg7)); + return sizeof(map_seg7); +} + +static ssize_t store_map(struct device *dev, struct device_attribute *attr, + const char *buf, size_t cnt) +{ + if (cnt != sizeof(map_seg7)) + return -EINVAL; + memcpy(&map_seg7, buf, sizeof(map_seg7)); + return sizeof(map_seg7); +} + +/* Interface to the LCD. + */ + +/* Reading /sys/../lineX will return the format string with its settings: + * + * Example: + * cat ./line3 + * 888888888888 + * Linux Rocks! + */ +static ssize_t show_line(struct device *dev, char *buf, int a, int b) +{ + struct yealink_dev *yld; + int i; + + down_read(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_read(&sysfs_rwsema); + return -ENODEV; + } + + for (i = a; i < b; i++) + *buf++ = lcdMap[i].type; + *buf++ = '\n'; + for (i = a; i < b; i++) + *buf++ = yld->lcdMap[i]; + *buf++ = '\n'; + *buf = 0; + + up_read(&sysfs_rwsema); + return 3 + ((b - a) << 1); +} + +static ssize_t show_line1(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE1_OFFSET, LCD_LINE2_OFFSET); +} + +static ssize_t show_line2(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE2_OFFSET, LCD_LINE3_OFFSET); +} + +static ssize_t show_line3(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_line(dev, buf, LCD_LINE3_OFFSET, LCD_LINE4_OFFSET); +} + +/* Writing to /sys/../lineX will set the coresponding LCD line. + * - Excess characters are ignored. + * - If less characters are written than allowed, the remaining digits are + * unchanged. + * - The '\n' or '\t' char is a placeholder, it does not overwrite the + * original content. + */ +static ssize_t store_line(struct device *dev, const char *buf, size_t count, + int el, size_t len) +{ + struct yealink_dev *yld; + int i; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + if (len > count) + len = count; + for (i = 0; i < len; i++) + setChar(yld, el++, buf[i]); + + up_write(&sysfs_rwsema); + return count; +} + +static ssize_t store_line1(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE1_OFFSET, LCD_LINE1_SIZE); +} + +static ssize_t store_line2(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE2_OFFSET, LCD_LINE2_SIZE); +} + +static ssize_t store_line3(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return store_line(dev, buf, count, LCD_LINE3_OFFSET, LCD_LINE3_SIZE); +} + +/* Interface to visible and audible "icons", these include: + * pictures on the LCD, the LED, and the dialtone signal. + */ + +/* Get a list of "switchable elements" with their current state. */ +static ssize_t get_icons(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct yealink_dev *yld; + int i, ret = 1; + + down_read(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_read(&sysfs_rwsema); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) { + if (lcdMap[i].type != '.') + continue; + ret += sprintf(&buf[ret], "%s %s\n", + yld->lcdMap[i] == ' ' ? " " : "on", + lcdMap[i].u.p.name); + } + up_read(&sysfs_rwsema); + return ret; +} + +/* Change the visibility of a particular element. */ +static ssize_t set_icon(struct device *dev, const char *buf, size_t count, + int chr) +{ + struct yealink_dev *yld; + int i; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) { + if (lcdMap[i].type != '.') + continue; + if (strncmp(buf, lcdMap[i].u.p.name, count) == 0) { + setChar(yld, i, chr); + break; + } + } + + up_write(&sysfs_rwsema); + return count; +} + +static ssize_t show_icon(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return set_icon(dev, buf, count, buf[0]); +} + +static ssize_t hide_icon(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + return set_icon(dev, buf, count, ' '); +} + +/* Upload a ringtone to the device. + */ + +/* Stores raw ringtone data in the phone */ +static ssize_t store_ringtone(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct yealink_dev *yld; + + down_write(&sysfs_rwsema); + yld = dev_get_drvdata(dev); + if (yld == NULL) { + up_write(&sysfs_rwsema); + return -ENODEV; + } + + /* TODO locking with async usb control interface??? */ + yealink_set_ringtone(yld, (char *)buf, count); + up_write(&sysfs_rwsema); + return count; +} + +#define _M444 S_IRUGO +#define _M664 S_IRUGO|S_IWUSR|S_IWGRP +#define _M220 S_IWUSR|S_IWGRP + +static DEVICE_ATTR(map_seg7 , _M664, show_map , store_map ); +static DEVICE_ATTR(line1 , _M664, show_line1 , store_line1 ); +static DEVICE_ATTR(line2 , _M664, show_line2 , store_line2 ); +static DEVICE_ATTR(line3 , _M664, show_line3 , store_line3 ); +static DEVICE_ATTR(get_icons , _M444, get_icons , NULL ); +static DEVICE_ATTR(show_icon , _M220, NULL , show_icon ); +static DEVICE_ATTR(hide_icon , _M220, NULL , hide_icon ); +static DEVICE_ATTR(ringtone , _M220, NULL , store_ringtone); + +static struct attribute *yld_attributes[] = { + &dev_attr_line1.attr, + &dev_attr_line2.attr, + &dev_attr_line3.attr, + &dev_attr_get_icons.attr, + &dev_attr_show_icon.attr, + &dev_attr_hide_icon.attr, + &dev_attr_map_seg7.attr, + &dev_attr_ringtone.attr, + NULL +}; + +static struct attribute_group yld_attr_group = { + .attrs = yld_attributes +}; + +/******************************************************************************* + * Linux interface and usb initialisation + ******************************************************************************/ + +struct driver_info { + char *name; +}; + +static const struct driver_info info_P1K = { + .name = "Yealink usb-p1k", +}; + +static const struct usb_device_id usb_table [] = { + { + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | + USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x6993, + .idProduct = 0xb001, + .bInterfaceClass = USB_CLASS_HID, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .driver_info = (kernel_ulong_t)&info_P1K + }, + { } +}; + +static int usb_cleanup(struct yealink_dev *yld, int err) +{ + if (yld == NULL) + return err; + + if (yld->idev) { + if (err) + input_free_device(yld->idev); + else + input_unregister_device(yld->idev); + } + + usb_free_urb(yld->urb_irq); + usb_free_urb(yld->urb_ctl); + + kfree(yld->ctl_req); + usb_free_coherent(yld->udev, USB_PKT_LEN, yld->ctl_data, yld->ctl_dma); + usb_free_coherent(yld->udev, USB_PKT_LEN, yld->irq_data, yld->irq_dma); + + kfree(yld); + return err; +} + +static void usb_disconnect(struct usb_interface *intf) +{ + struct yealink_dev *yld; + + down_write(&sysfs_rwsema); + yld = usb_get_intfdata(intf); + sysfs_remove_group(&intf->dev.kobj, &yld_attr_group); + usb_set_intfdata(intf, NULL); + up_write(&sysfs_rwsema); + + usb_cleanup(yld, 0); +} + +static int usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev (intf); + struct driver_info *nfo = (struct driver_info *)id->driver_info; + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *endpoint; + struct yealink_dev *yld; + struct input_dev *input_dev; + int ret, pipe, i; + + interface = intf->cur_altsetting; + endpoint = &interface->endpoint[0].desc; + if (!usb_endpoint_is_int_in(endpoint)) + return -ENODEV; + + yld = kzalloc(sizeof(struct yealink_dev), GFP_KERNEL); + if (!yld) + return -ENOMEM; + + yld->udev = udev; + yld->intf = intf; + + yld->idev = input_dev = input_allocate_device(); + if (!input_dev) + return usb_cleanup(yld, -ENOMEM); + + /* allocate usb buffers */ + yld->irq_data = usb_alloc_coherent(udev, USB_PKT_LEN, + GFP_ATOMIC, &yld->irq_dma); + if (yld->irq_data == NULL) + return usb_cleanup(yld, -ENOMEM); + + yld->ctl_data = usb_alloc_coherent(udev, USB_PKT_LEN, + GFP_ATOMIC, &yld->ctl_dma); + if (!yld->ctl_data) + return usb_cleanup(yld, -ENOMEM); + + yld->ctl_req = kmalloc(sizeof(*(yld->ctl_req)), GFP_KERNEL); + if (yld->ctl_req == NULL) + return usb_cleanup(yld, -ENOMEM); + + /* allocate urb structures */ + yld->urb_irq = usb_alloc_urb(0, GFP_KERNEL); + if (yld->urb_irq == NULL) + return usb_cleanup(yld, -ENOMEM); + + yld->urb_ctl = usb_alloc_urb(0, GFP_KERNEL); + if (yld->urb_ctl == NULL) + return usb_cleanup(yld, -ENOMEM); + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + ret = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + if (ret != USB_PKT_LEN) + dev_err(&intf->dev, "invalid payload size %d, expected %zd\n", + ret, USB_PKT_LEN); + + /* initialise irq urb */ + usb_fill_int_urb(yld->urb_irq, udev, pipe, yld->irq_data, + USB_PKT_LEN, + urb_irq_callback, + yld, endpoint->bInterval); + yld->urb_irq->transfer_dma = yld->irq_dma; + yld->urb_irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + yld->urb_irq->dev = udev; + + /* initialise ctl urb */ + yld->ctl_req->bRequestType = USB_TYPE_CLASS | USB_RECIP_INTERFACE | + USB_DIR_OUT; + yld->ctl_req->bRequest = USB_REQ_SET_CONFIGURATION; + yld->ctl_req->wValue = cpu_to_le16(0x200); + yld->ctl_req->wIndex = cpu_to_le16(interface->desc.bInterfaceNumber); + yld->ctl_req->wLength = cpu_to_le16(USB_PKT_LEN); + + usb_fill_control_urb(yld->urb_ctl, udev, usb_sndctrlpipe(udev, 0), + (void *)yld->ctl_req, yld->ctl_data, USB_PKT_LEN, + urb_ctl_callback, yld); + yld->urb_ctl->transfer_dma = yld->ctl_dma; + yld->urb_ctl->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + yld->urb_ctl->dev = udev; + + /* find out the physical bus location */ + usb_make_path(udev, yld->phys, sizeof(yld->phys)); + strlcat(yld->phys, "/input0", sizeof(yld->phys)); + + /* register settings for the input device */ + input_dev->name = nfo->name; + input_dev->phys = yld->phys; + usb_to_input_id(udev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + + input_set_drvdata(input_dev, yld); + + input_dev->open = input_open; + input_dev->close = input_close; + /* input_dev->event = input_ev; TODO */ + + /* register available key events */ + input_dev->evbit[0] = BIT_MASK(EV_KEY); + for (i = 0; i < 256; i++) { + int k = map_p1k_to_key(i); + if (k >= 0) { + set_bit(k & 0xff, input_dev->keybit); + if (k >> 8) + set_bit(k >> 8, input_dev->keybit); + } + } + + ret = input_register_device(yld->idev); + if (ret) + return usb_cleanup(yld, ret); + + usb_set_intfdata(intf, yld); + + /* clear visible elements */ + for (i = 0; i < ARRAY_SIZE(lcdMap); i++) + setChar(yld, i, ' '); + + /* display driver version on LCD line 3 */ + store_line3(&intf->dev, NULL, + DRIVER_VERSION, sizeof(DRIVER_VERSION)); + + /* Register sysfs hooks (don't care about failure) */ + ret = sysfs_create_group(&intf->dev.kobj, &yld_attr_group); + return 0; +} + +static struct usb_driver yealink_driver = { + .name = "yealink", + .probe = usb_probe, + .disconnect = usb_disconnect, + .id_table = usb_table, +}; + +module_usb_driver(yealink_driver); + +MODULE_DEVICE_TABLE (usb, usb_table); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/input/misc/yealink.h b/drivers/input/misc/yealink.h new file mode 100644 index 00000000..1e0f5239 --- /dev/null +++ b/drivers/input/misc/yealink.h @@ -0,0 +1,220 @@ +/* + * drivers/usb/input/yealink.h + * + * Copyright (c) 2005 Henk Vergonet <Henk.Vergonet@gmail.com> + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef INPUT_YEALINK_H +#define INPUT_YEALINK_H + +/* Using the control channel on interface 3 various aspects of the phone + * can be controlled like LCD, LED, dialtone and the ringtone. + */ + +struct yld_ctl_packet { + u8 cmd; /* command code, see below */ + u8 size; /* 1-11, size of used data bytes. */ + u16 offset; /* internal packet offset */ + u8 data[11]; + s8 sum; /* negative sum of 15 preceding bytes */ +} __attribute__ ((packed)); + +#define USB_PKT_LEN sizeof(struct yld_ctl_packet) + +/* The following yld_ctl_packet's are available: */ + +/* Init registers + * + * cmd 0x8e + * size 10 + * offset 0 + * data 0,0,0,0.... + */ +#define CMD_INIT 0x8e + +/* Request key scan + * + * cmd 0x80 + * size 1 + * offset 0 + * data[0] on return returns the key number, if it changes there's a new + * key pressed. + */ +#define CMD_KEYPRESS 0x80 + +/* Request scancode + * + * cmd 0x81 + * size 1 + * offset key number [0-1f] + * data[0] on return returns the scancode + */ +#define CMD_SCANCODE 0x81 + +/* Set LCD + * + * cmd 0x04 + * size 1-11 + * offset 0-23 + * data segment bits + */ +#define CMD_LCD 0x04 + +/* Set led + * + * cmd 0x05 + * size 1 + * offset 0 + * data[0] 0 OFF / 1 ON + */ +#define CMD_LED 0x05 + +/* Set ringtone volume + * + * cmd 0x11 + * size 1 + * offset 0 + * data[0] 0-0xff volume + */ +#define CMD_RING_VOLUME 0x11 + +/* Set ringtone notes + * + * cmd 0x02 + * size 1-11 + * offset 0-> + * data binary representation LE16(-freq), LE16(duration) .... + */ +#define CMD_RING_NOTE 0x02 + +/* Sound ringtone via the speaker on the back + * + * cmd 0x03 + * size 1 + * offset 0 + * data[0] 0 OFF / 0x24 ON + */ +#define CMD_RINGTONE 0x03 + +/* Sound dial tone via the ear speaker + * + * cmd 0x09 + * size 1 + * offset 0 + * data[0] 0 OFF / 1 ON + */ +#define CMD_DIALTONE 0x09 + +#endif /* INPUT_YEALINK_H */ + + +#if defined(_SEG) && defined(_PIC) +/* This table maps the LCD segments onto individual bit positions in the + * yld_status struct. + */ + +/* LCD, each segment must be driven separately. + * + * Layout: + * + * |[] [][] [][] [][] in |[][] + * |[] M [][] D [][] : [][] out |[][] + * store + * + * NEW REP SU MO TU WE TH FR SA + * + * [] [] [] [] [] [] [] [] [] [] [] [] + * [] [] [] [] [] [] [] [] [] [] [] [] + */ + +/* Line 1 + * Format : 18.e8.M8.88...188 + * Icon names : M D : IN OUT STORE + */ +#define LCD_LINE1_OFFSET 0 +#define LCD_LINE1_SIZE 17 + +/* Note: first g then f => ! ! */ +/* _SEG( type a b c d e g f ) */ + _SEG('1', 0,0 , 22,2 , 22,2 , 0,0 , 0,0 , 0,0 , 0,0 ), + _SEG('8', 20,1 , 20,2 , 20,4 , 20,8 , 21,4 , 21,2 , 21,1 ), + _PIC('.', 22,1 , "M" ), + _SEG('e', 18,1 , 18,2 , 18,4 , 18,1 , 19,2 , 18,1 , 19,1 ), + _SEG('8', 16,1 , 16,2 , 16,4 , 16,8 , 17,4 , 17,2 , 17,1 ), + _PIC('.', 15,8 , "D" ), + _SEG('M', 14,1 , 14,2 , 14,4 , 14,1 , 15,4 , 15,2 , 15,1 ), + _SEG('8', 12,1 , 12,2 , 12,4 , 12,8 , 13,4 , 13,2 , 13,1 ), + _PIC('.', 11,8 , ":" ), + _SEG('8', 10,1 , 10,2 , 10,4 , 10,8 , 11,4 , 11,2 , 11,1 ), + _SEG('8', 8,1 , 8,2 , 8,4 , 8,8 , 9,4 , 9,2 , 9,1 ), + _PIC('.', 7,1 , "IN" ), + _PIC('.', 7,2 , "OUT" ), + _PIC('.', 7,4 , "STORE" ), + _SEG('1', 0,0 , 5,1 , 5,1 , 0,0 , 0,0 , 0,0 , 0,0 ), + _SEG('8', 4,1 , 4,2 , 4,4 , 4,8 , 5,8 , 5,4 , 5,2 ), + _SEG('8', 2,1 , 2,2 , 2,4 , 2,8 , 3,4 , 3,2 , 3,1 ), + +/* Line 2 + * Format : ......... + * Pict. name : NEW REP SU MO TU WE TH FR SA + */ +#define LCD_LINE2_OFFSET LCD_LINE1_OFFSET + LCD_LINE1_SIZE +#define LCD_LINE2_SIZE 9 + + _PIC('.', 23,2 , "NEW" ), + _PIC('.', 23,4 , "REP" ), + _PIC('.', 1,8 , "SU" ), + _PIC('.', 1,4 , "MO" ), + _PIC('.', 1,2 , "TU" ), + _PIC('.', 1,1 , "WE" ), + _PIC('.', 0,1 , "TH" ), + _PIC('.', 0,2 , "FR" ), + _PIC('.', 0,4 , "SA" ), + +/* Line 3 + * Format : 888888888888 + */ +#define LCD_LINE3_OFFSET LCD_LINE2_OFFSET + LCD_LINE2_SIZE +#define LCD_LINE3_SIZE 12 + + _SEG('8', 22,16, 22,32, 22,64, 22,128, 23,128, 23,64, 23,32 ), + _SEG('8', 20,16, 20,32, 20,64, 20,128, 21,128, 21,64, 21,32 ), + _SEG('8', 18,16, 18,32, 18,64, 18,128, 19,128, 19,64, 19,32 ), + _SEG('8', 16,16, 16,32, 16,64, 16,128, 17,128, 17,64, 17,32 ), + _SEG('8', 14,16, 14,32, 14,64, 14,128, 15,128, 15,64, 15,32 ), + _SEG('8', 12,16, 12,32, 12,64, 12,128, 13,128, 13,64, 13,32 ), + _SEG('8', 10,16, 10,32, 10,64, 10,128, 11,128, 11,64, 11,32 ), + _SEG('8', 8,16, 8,32, 8,64, 8,128, 9,128, 9,64, 9,32 ), + _SEG('8', 6,16, 6,32, 6,64, 6,128, 7,128, 7,64, 7,32 ), + _SEG('8', 4,16, 4,32, 4,64, 4,128, 5,128, 5,64, 5,32 ), + _SEG('8', 2,16, 2,32, 2,64, 2,128, 3,128, 3,64, 3,32 ), + _SEG('8', 0,16, 0,32, 0,64, 0,128, 1,128, 1,64, 1,32 ), + +/* Line 4 + * + * The LED, DIALTONE and RINGTONE are implemented as icons and use the same + * sysfs interface. + */ +#define LCD_LINE4_OFFSET LCD_LINE3_OFFSET + LCD_LINE3_SIZE + + _PIC('.', offsetof(struct yld_status, led) , 0x01, "LED" ), + _PIC('.', offsetof(struct yld_status, dialtone) , 0x01, "DIALTONE" ), + _PIC('.', offsetof(struct yld_status, ringtone) , 0x24, "RINGTONE" ), + +#undef _SEG +#undef _PIC +#endif /* _SEG && _PIC */ |
