Initial commit: Linux kernel 4.19 for Unplugged OS

This commit is contained in:
ziv committed 2025-10-06 13:52:42 +00:00
commit d30235b04c
80519 files changed
+35800681

No files matched your search

+166
View File
@@ -0,0 +1,166 @@
/*
* 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);
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",
.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");
+147
View File
@@ -0,0 +1,147 @@
/*
* 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>
#include <linux/device.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 = devm_kzalloc(&pdev->dev, 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 = devm_input_allocate_device(&pdev->dev);
if (!info->idev) {
dev_err(chip->dev, "Failed to allocate input dev\n");
return -ENOMEM;
}
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);
return ret;
}
ret = devm_request_threaded_irq(&pdev->dev, 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);
return ret;
}
platform_set_drvdata(pdev, info);
device_init_wakeup(&pdev->dev, 1);
return 0;
}
static int __maybe_unused 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 __maybe_unused 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;
}
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",
.pm = &pm860x_onkey_pm_ops,
},
.probe = pm860x_onkey_probe,
};
module_platform_driver(pm860x_onkey_driver);
MODULE_DESCRIPTION("Marvell 88PM860x ONKEY driver");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
MODULE_LICENSE("GPL");
+854
View File
@@ -0,0 +1,854 @@
#
# 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_ATMEL_CAPTOUCH
tristate "Atmel Capacitive Touch Button Driver"
depends on OF || COMPILE_TEST
depends on I2C
help
Say Y here if an Atmel Capacitive Touch Button device which
implements "captouch" protocol is connected to I2C bus. Typically
this device consists of Atmel Touch sensor controlled by AtMegaXX
MCU running firmware based on Qtouch library.
One should find "atmel,captouch" node in the board specific DTS.
To compile this driver as a module, choose M here: the
module will be called atmel_captouch.
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_E3X0_BUTTON
tristate "NI Ettus Research USRP E3xx Button support."
default n
help
Say Y here to enable support for the NI Ettus Research
USRP E3xx Button.
To compile this driver as a module, choose M here: the
module will be called e3x0_button.
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_PM8941_PWRKEY
tristate "Qualcomm PM8941 power key support"
depends on MFD_SPMI_PMIC
help
Say Y here if you want support for the power key usually found
on boards using a Qualcomm PM8941 compatible PMIC.
If unsure, say Y.
To compile this driver as a module, choose M here: the module
will be called pm8941-pwrkey.
config INPUT_PM8XXX_VIBRATOR
tristate "Qualcomm PM8XXX vibrator support"
depends on MFD_PM8XXX || MFD_SPMI_PMIC
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_MAX77693_HAPTIC
tristate "MAXIM MAX77693/MAX77843 haptic controller support"
depends on (MFD_MAX77693 || MFD_MAX77843) && PWM
select INPUT_FF_MEMLESS
help
This option enables support for the haptic controller on
MAXIM MAX77693 and MAX77843 chips.
To compile this driver as module, choose M here: the
module will be called max77693-haptic.
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 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_MC13XXX
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_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 || COMPILE_TEST
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_BEEPER
tristate "Generic GPIO Beeper support"
depends on GPIOLIB || COMPILE_TEST
help
Say Y here if you have a beeper connected to a GPIO pin.
To compile this driver as a module, choose M here: the
module will be called gpio-beeper.
config INPUT_GPIO_DECODER
tristate "Polled GPIO Decoder Input driver"
depends on GPIOLIB || COMPILE_TEST
select INPUT_POLLDEV
help
Say Y here if you want driver to read status of multiple GPIO
lines and report the encoded value as an absolute integer to
input subsystem.
To compile this driver as a module, choose M here: the module
will be called gpio_decoder.
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_CPCAP_PWRBUTTON
tristate "CPCAP OnKey"
depends on MFD_CPCAP
help
Say Y here if you want to enable power key reporting via the
Motorola CPCAP chip.
To compile this driver as a module, choose M here. The module will
be called cpcap-pwrbutton.
config INPUT_WISTRON_BTNS
tristate "x86 Wistron laptop button interface"
depends on X86_32
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_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/devices/yealink.rst>.
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_REGULATOR_HAPTIC
tristate "Regulator haptics support"
depends on REGULATOR
select INPUT_FF_MEMLESS
help
This option enables device driver support for the haptic controlled
by a regulator. This driver supports ff-memless interface
from input framework.
To compile this driver as a module, choose M here: the
module will be called regulator-haptic.
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_TPS65218_PWRBUTTON
tristate "TPS65218 Power button driver"
depends on (MFD_TPS65217 || MFD_TPS65218)
help
Say Y here if you want to enable power button reporting for
TPS65217 and TPS65218 Power Management IC devices.
To compile this driver as a module, choose M here. The module will
be called tps65218-pwrbutton.
config INPUT_AXP20X_PEK
tristate "X-Powers AXP20X power button driver"
depends on MFD_AXP20X
help
Say Y here if you want to enable power key reporting via the
AXP20X PMIC.
To compile this driver as a module, choose M here. The module will
be called axp20x-pek.
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 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_PALMAS_PWRBUTTON
tristate "Palmas Power button Driver"
depends on MFD_PALMAS
help
Say Y here if you want to enable power key reporting via the
Palmas family of PMICs.
To compile this driver as a module, choose M here. The module will
be called palmas_pwrbutton.
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 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_PWM_VIBRA
tristate "PWM vibrator support"
depends on PWM
select INPUT_FF_MEMLESS
help
Say Y here to get support for PWM based vibrator devices.
If unsure, say N.
To compile this driver as a module, choose M here: the module will be
called pwm-vibra.
config INPUT_RK805_PWRKEY
tristate "Rockchip RK805 PMIC power key support"
depends on MFD_RK808
help
Select this option to enable power key driver for RK805.
If unsure, say N.
To compile this driver as a module, choose M here: the module will be
called rk805_pwrkey.
config INPUT_GPIO_ROTARY_ENCODER
tristate "Rotary encoders connected to GPIO pins"
depends on GPIOLIB || COMPILE_TEST
help
Say Y here to add support for rotary encoders connected to GPIO lines.
Check file:Documentation/input/devices/rotary-encoder.rst 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_DA9063_ONKEY
tristate "Dialog DA9063/62/61 OnKey"
depends on MFD_DA9063 || MFD_DA9062
help
Support the ONKEY of Dialog DA9063, DA9062 and DA9061 Power
Management ICs as an input device capable of reporting the
power button status.
To compile this driver as a module, choose M here: the module
will be called da9063_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_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_SIRFSOC_ONKEY
tristate "CSR SiRFSoC power on/off/suspend key support"
depends on ARCH_SIRF && OF
default y
help
Say Y here if you want to support for the SiRFSoC power on/off/suspend key
in Linux, after you press the onkey, system will suspend.
If unsure, say N.
config INPUT_IDEAPAD_SLIDEBAR
tristate "IdeaPad Laptop Slidebar"
depends on INPUT
depends on SERIO_I8042
help
Say Y here if you have an IdeaPad laptop with a slidebar.
To compile this driver as a module, choose M here: the
module will be called ideapad_slidebar.
config INPUT_SOC_BUTTON_ARRAY
tristate "Windows-compatible SoC Button Array"
depends on KEYBOARD_GPIO
help
Say Y here if you have a SoC-based tablet that originally
runs Windows 8.
To compile this driver as a module, choose M here: the
module will be called soc_button_array.
config INPUT_DRV260X_HAPTICS
tristate "TI DRV260X haptics support"
depends on INPUT && I2C
depends on GPIOLIB || COMPILE_TEST
select INPUT_FF_MEMLESS
select REGMAP_I2C
help
Say Y to enable support for the TI DRV260X haptics driver.
To compile this driver as a module, choose M here: the
module will be called drv260x-haptics.
config INPUT_DRV2665_HAPTICS
tristate "TI DRV2665 haptics support"
depends on INPUT && I2C
select INPUT_FF_MEMLESS
select REGMAP_I2C
help
Say Y to enable support for the TI DRV2665 haptics driver.
To compile this driver as a module, choose M here: the
module will be called drv2665-haptics.
config INPUT_DRV2667_HAPTICS
tristate "TI DRV2667 haptics support"
depends on INPUT && I2C
select INPUT_FF_MEMLESS
select REGMAP_I2C
help
Say Y to enable support for the TI DRV2667 haptics driver.
To compile this driver as a module, choose M here: the
module will be called drv2667-haptics.
config INPUT_HISI_POWERKEY
tristate "Hisilicon PMIC ONKEY support"
depends on ARCH_HISI || COMPILE_TEST
help
Say Y to enable support for PMIC ONKEY.
To compile this driver as a module, choose M here: the
module will be called hisi_powerkey.
config INPUT_RAVE_SP_PWRBUTTON
tristate "RAVE SP Power button Driver"
depends on RAVE_SP_CORE
help
Say Y here if you want to enable power key reporting from RAVE SP
To compile this driver as a module, choose M here: the
module will be called rave-sp-pwrbutton.
config INPUT_SC27XX_VIBRA
tristate "Spreadtrum sc27xx vibrator support"
depends on MFD_SC27XX_PMIC || COMPILE_TEST
select INPUT_FF_MEMLESS
help
This option enables support for Spreadtrum sc27xx vibrator driver.
To compile this driver as a module, choose M here. The module will
be called sc27xx_vibra.
endif
+83
View File
@@ -0,0 +1,83 @@
# SPDX-License-Identifier: GPL-2.0
#
# 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_ATMEL_CAPTOUCH) += atmel_captouch.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_CPCAP_PWRBUTTON) += cpcap-pwrbutton.o
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
obj-$(CONFIG_INPUT_DA9063_ONKEY) += da9063_onkey.o
obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
obj-$(CONFIG_INPUT_E3X0_BUTTON) += e3x0-button.o
obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.o
obj-$(CONFIG_INPUT_DRV2665_HAPTICS) += drv2665.o
obj-$(CONFIG_INPUT_DRV2667_HAPTICS) += drv2667.o
obj-$(CONFIG_INPUT_GP2A) += gp2ap002a00f.o
obj-$(CONFIG_INPUT_GPIO_BEEPER) += gpio-beeper.o
obj-$(CONFIG_INPUT_GPIO_DECODER) += gpio_decoder.o
obj-$(CONFIG_INPUT_HISI_POWERKEY) += hisi_powerkey.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_KEYSPAN_REMOTE) += keyspan_remote.o
obj-$(CONFIG_INPUT_KXTJ9) += kxtj9.o
obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
obj-$(CONFIG_INPUT_MAX77693_HAPTIC) += max77693-haptic.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_PALMAS_PWRBUTTON) += palmas-pwrbutton.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_PM8941_PWRKEY) += pm8941-pwrkey.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_PWM_VIBRA) += pwm-vibra.o
obj-$(CONFIG_INPUT_RAVE_SP_PWRBUTTON) += rave-sp-pwrbutton.o
obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
obj-$(CONFIG_INPUT_REGULATOR_HAPTIC) += regulator-haptic.o
obj-$(CONFIG_INPUT_RETU_PWRBUTTON) += retu-pwrbutton.o
obj-$(CONFIG_INPUT_AXP20X_PEK) += axp20x-pek.o
obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
obj-$(CONFIG_INPUT_RK805_PWRKEY) += rk805-pwrkey.o
obj-$(CONFIG_INPUT_SC27XX_VIBRA) += sc27xx-vibra.o
obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
obj-$(CONFIG_INPUT_SIRFSOC_ONKEY) += sirfsoc-onkey.o
obj-$(CONFIG_INPUT_SOC_BUTTON_ARRAY) += soc_button_array.o
obj-$(CONFIG_INPUT_SPARCSPKR) += sparcspkr.o
obj-$(CONFIG_INPUT_TPS65218_PWRBUTTON) += tps65218-pwrbutton.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_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o
+134
View File
@@ -0,0 +1,134 @@
/*
* 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/device.h>
#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 = devm_kzalloc(&pdev->dev, sizeof(struct ab8500_ponkey),
GFP_KERNEL);
if (!ponkey)
return -ENOMEM;
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
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 = devm_request_any_context_irq(&pdev->dev, 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);
return error;
}
error = devm_request_any_context_irq(&pdev->dev, 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);
return error;
}
error = input_register_device(ponkey->idev);
if (error) {
dev_err(ab8500->dev, "Can't register input device: %d\n", error);
return error;
}
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id ab8500_ponkey_match[] = {
{ .compatible = "stericsson,ab8500-ponkey", },
{}
};
MODULE_DEVICE_TABLE(of, ab8500_ponkey_match);
#endif
static struct platform_driver ab8500_ponkey_driver = {
.driver = {
.name = "ab8500-poweron-key",
.of_match_table = of_match_ptr(ab8500_ponkey_match),
},
.probe = ab8500_ponkey_probe,
};
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");
+111
View File
@@ -0,0 +1,111 @@
/*
* 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"
static int __maybe_unused ad714x_i2c_suspend(struct device *dev)
{
return ad714x_disable(i2c_get_clientdata(to_i2c_client(dev)));
}
static int __maybe_unused ad714x_i2c_resume(struct device *dev)
{
return ad714x_enable(i2c_get_clientdata(to_i2c_client(dev)));
}
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 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,
.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");
+116
View File
@@ -0,0 +1,116 @@
/*
* 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)
static int __maybe_unused ad714x_spi_suspend(struct device *dev)
{
return ad714x_disable(spi_get_drvdata(to_spi_device(dev)));
}
static int __maybe_unused ad714x_spi_resume(struct device *dev)
{
return ad714x_enable(spi_get_drvdata(to_spi_device(dev)));
}
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 struct spi_driver ad714x_spi_driver = {
.driver = {
.name = "ad714x_captouch",
.pm = &ad714x_spi_pm,
},
.probe = ad714x_spi_probe,
};
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");
File diff suppressed because it is too large. Load diff
+54
View File
@@ -0,0 +1,54 @@
/*
* 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);
#endif
+171
View File
@@ -0,0 +1,171 @@
/*
* 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/of.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, &reg, 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);
}
static int __maybe_unused 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 __maybe_unused 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;
}
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 const struct of_device_id adxl34x_of_id[] = {
/*
* The ADXL346 is backward-compatible with the ADXL345. Differences are
* handled by runtime detection of the device model, there's thus no
* need for listing the "adi,adxl346" compatible value explicitly.
*/
{ .compatible = "adi,adxl345", },
/*
* Deprecated, DT nodes should use one or more of the device-specific
* compatible values "adi,adxl345" and "adi,adxl346".
*/
{ .compatible = "adi,adxl34x", },
{ }
};
MODULE_DEVICE_TABLE(of, adxl34x_of_id);
static struct i2c_driver adxl34x_driver = {
.driver = {
.name = "adxl34x",
.pm = &adxl34x_i2c_pm,
.of_match_table = adxl34x_of_id,
},
.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");
+133
View File
@@ -0,0 +1,133 @@
/*
* 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, &reg, 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);
}
static int __maybe_unused 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 __maybe_unused 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;
}
static SIMPLE_DEV_PM_OPS(adxl34x_spi_pm, adxl34x_spi_suspend,
adxl34x_spi_resume);
static struct spi_driver adxl34x_driver = {
.driver = {
.name = "adxl34x",
.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");
+913
View File
@@ -0,0 +1,913 @@
/*
* 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/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) & 0x30) >> 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;
int 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_get_platdata(dev);
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) == FIFO_BYPASS)
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");
+30
View File
@@ -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
+349
View File
@@ -0,0 +1,349 @@
/*
* 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 "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, skipping 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_warn(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_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*");
MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*");
+218
View File
@@ -0,0 +1,218 @@
/*
* 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 snd_soc_component *component =
snd_soc_dapm_to_component(arizona->dapm);
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;
}
ret = snd_soc_component_enable_pin(component, "HAPTICS");
if (ret != 0) {
dev_err(arizona->dev, "Failed to start HAPTICS: %d\n",
ret);
return;
}
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 */
ret = snd_soc_component_disable_pin(component, "HAPTICS");
if (ret != 0) {
dev_err(arizona->dev, "Failed to disable HAPTICS: %d\n",
ret);
return;
}
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, 0);
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 snd_soc_component *component;
cancel_work_sync(&haptics->work);
if (haptics->arizona->dapm) {
component = snd_soc_dapm_to_component(haptics->arizona->dapm);
snd_soc_component_disable_pin(component, "HAPTICS");
}
}
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 = devm_input_allocate_device(&pdev->dev);
if (!haptics->input_dev) {
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->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);
return ret;
}
ret = input_register_device(haptics->input_dev);
if (ret < 0) {
dev_err(arizona->dev, "couldn't register input device: %d\n",
ret);
return ret;
}
return 0;
}
static struct platform_driver arizona_haptics_driver = {
.probe = arizona_haptics_probe,
.driver = {
.name = "arizona-haptics",
},
};
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>");
File diff suppressed because it is too large. Load diff
+158
View File
@@ -0,0 +1,158 @@
/*
* 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/input.h>
#include <linux/types.h>
#include <linux/acpi.h>
#include <linux/uaccess.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");
+289
View File
@@ -0,0 +1,289 @@
/*
* Atmel Atmegaxx Capacitive Touch Button Driver
*
* Copyright (C) 2016 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.
*/
/*
* It's irrelevant that the HW used to develop captouch driver is based
* on Atmega88PA part and uses QtouchADC parts for sensing touch.
* Calling this driver "captouch" is an arbitrary way to distinguish
* the protocol this driver supported by other atmel/qtouch drivers.
*
* Captouch driver supports a newer/different version of the I2C
* registers/commands than the qt1070.c driver.
* Don't let the similarity of the general driver structure fool you.
*
* For raw i2c access from userspace, use i2cset/i2cget
* to poke at /dev/i2c-N devices.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
/* Maximum number of buttons supported */
#define MAX_NUM_OF_BUTTONS 8
/* Registers */
#define REG_KEY1_THRESHOLD 0x02
#define REG_KEY2_THRESHOLD 0x03
#define REG_KEY3_THRESHOLD 0x04
#define REG_KEY4_THRESHOLD 0x05
#define REG_KEY1_REF_H 0x20
#define REG_KEY1_REF_L 0x21
#define REG_KEY2_REF_H 0x22
#define REG_KEY2_REF_L 0x23
#define REG_KEY3_REF_H 0x24
#define REG_KEY3_REF_L 0x25
#define REG_KEY4_REF_H 0x26
#define REG_KEY4_REF_L 0x27
#define REG_KEY1_DLT_H 0x30
#define REG_KEY1_DLT_L 0x31
#define REG_KEY2_DLT_H 0x32
#define REG_KEY2_DLT_L 0x33
#define REG_KEY3_DLT_H 0x34
#define REG_KEY3_DLT_L 0x35
#define REG_KEY4_DLT_H 0x36
#define REG_KEY4_DLT_L 0x37
#define REG_KEY_STATE 0x3C
/*
* @i2c_client: I2C slave device client pointer
* @input: Input device pointer
* @num_btn: Number of buttons
* @keycodes: map of button# to KeyCode
* @prev_btn: Previous key state to detect button "press" or "release"
* @xfer_buf: I2C transfer buffer
*/
struct atmel_captouch_device {
struct i2c_client *client;
struct input_dev *input;
u32 num_btn;
u32 keycodes[MAX_NUM_OF_BUTTONS];
u8 prev_btn;
u8 xfer_buf[8] ____cacheline_aligned;
};
/*
* Read from I2C slave device
* The protocol is that the client has to provide both the register address
* and the length, and while reading back the device would prepend the data
* with address and length for verification.
*/
static int atmel_read(struct atmel_captouch_device *capdev,
u8 reg, u8 *data, size_t len)
{
struct i2c_client *client = capdev->client;
struct device *dev = &client->dev;
struct i2c_msg msg[2];
int err;
if (len > sizeof(capdev->xfer_buf) - 2)
return -EINVAL;
capdev->xfer_buf[0] = reg;
capdev->xfer_buf[1] = len;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = capdev->xfer_buf;
msg[0].len = 2;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = capdev->xfer_buf;
msg[1].len = len + 2;
err = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
if (err != ARRAY_SIZE(msg))
return err < 0 ? err : -EIO;
if (capdev->xfer_buf[0] != reg) {
dev_err(dev,
"I2C read error: register address does not match (%#02x vs %02x)\n",
capdev->xfer_buf[0], reg);
return -ECOMM;
}
memcpy(data, &capdev->xfer_buf[2], len);
return 0;
}
/*
* Handle interrupt and report the key changes to the input system.
* Multi-touch can be supported; however, it really depends on whether
* the device can multi-touch.
*/
static irqreturn_t atmel_captouch_isr(int irq, void *data)
{
struct atmel_captouch_device *capdev = data;
struct device *dev = &capdev->client->dev;
int error;
int i;
u8 new_btn;
u8 changed_btn;
error = atmel_read(capdev, REG_KEY_STATE, &new_btn, 1);
if (error) {
dev_err(dev, "failed to read button state: %d\n", error);
goto out;
}
dev_dbg(dev, "%s: button state %#02x\n", __func__, new_btn);
changed_btn = new_btn ^ capdev->prev_btn;
capdev->prev_btn = new_btn;
for (i = 0; i < capdev->num_btn; i++) {
if (changed_btn & BIT(i))
input_report_key(capdev->input,
capdev->keycodes[i],
new_btn & BIT(i));
}
input_sync(capdev->input);
out:
return IRQ_HANDLED;
}
/*
* Probe function to setup the device, input system and interrupt
*/
static int atmel_captouch_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct atmel_captouch_device *capdev;
struct device *dev = &client->dev;
struct device_node *node;
int i;
int err;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA |
I2C_FUNC_SMBUS_WORD_DATA |
I2C_FUNC_SMBUS_I2C_BLOCK)) {
dev_err(dev, "needed i2c functionality is not supported\n");
return -EINVAL;
}
capdev = devm_kzalloc(dev, sizeof(*capdev), GFP_KERNEL);
if (!capdev)
return -ENOMEM;
capdev->client = client;
err = atmel_read(capdev, REG_KEY_STATE,
&capdev->prev_btn, sizeof(capdev->prev_btn));
if (err) {
dev_err(dev, "failed to read initial button state: %d\n", err);
return err;
}
capdev->input = devm_input_allocate_device(dev);
if (!capdev->input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
capdev->input->id.bustype = BUS_I2C;
capdev->input->id.product = 0x880A;
capdev->input->id.version = 0;
capdev->input->name = "ATMegaXX Capacitive Button Controller";
__set_bit(EV_KEY, capdev->input->evbit);
node = dev->of_node;
if (!node) {
dev_err(dev, "failed to find matching node in device tree\n");
return -EINVAL;
}
if (of_property_read_bool(node, "autorepeat"))
__set_bit(EV_REP, capdev->input->evbit);
capdev->num_btn = of_property_count_u32_elems(node, "linux,keymap");
if (capdev->num_btn > MAX_NUM_OF_BUTTONS)
capdev->num_btn = MAX_NUM_OF_BUTTONS;
err = of_property_read_u32_array(node, "linux,keycodes",
capdev->keycodes,
capdev->num_btn);
if (err) {
dev_err(dev,
"failed to read linux,keycode property: %d\n", err);
return err;
}
for (i = 0; i < capdev->num_btn; i++)
__set_bit(capdev->keycodes[i], capdev->input->keybit);
capdev->input->keycode = capdev->keycodes;
capdev->input->keycodesize = sizeof(capdev->keycodes[0]);
capdev->input->keycodemax = capdev->num_btn;
err = input_register_device(capdev->input);
if (err)
return err;
err = devm_request_threaded_irq(dev, client->irq,
NULL, atmel_captouch_isr,
IRQF_ONESHOT,
"atmel_captouch", capdev);
if (err) {
dev_err(dev, "failed to request irq %d: %d\n",
client->irq, err);
return err;
}
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id atmel_captouch_of_id[] = {
{
.compatible = "atmel,captouch",
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, atmel_captouch_of_id);
#endif
static const struct i2c_device_id atmel_captouch_id[] = {
{ "atmel_captouch", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, atmel_captouch_id);
static struct i2c_driver atmel_captouch_driver = {
.probe = atmel_captouch_probe,
.id_table = atmel_captouch_id,
.driver = {
.name = "atmel_captouch",
.of_match_table = of_match_ptr(atmel_captouch_of_id),
},
};
module_i2c_driver(atmel_captouch_driver);
/* Module information */
MODULE_AUTHOR("Hung-yu Wu <hywu@google.com>");
MODULE_DESCRIPTION("Atmel ATmegaXX Capacitance Touch Sensor I2C Driver");
MODULE_LICENSE("GPL v2");
+420
View File
@@ -0,0 +1,420 @@
/*
* axp20x power button driver.
*
* Copyright (C) 2013 Carlo Caione <carlo@caione.org>
*
* 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/acpi.h>
#include <linux/errno.h>
#include <linux/irq.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/axp20x.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#define AXP20X_PEK_STARTUP_MASK (0xc0)
#define AXP20X_PEK_SHUTDOWN_MASK (0x03)
struct axp20x_info {
const struct axp20x_time *startup_time;
unsigned int startup_mask;
const struct axp20x_time *shutdown_time;
unsigned int shutdown_mask;
};
struct axp20x_pek {
struct axp20x_dev *axp20x;
struct input_dev *input;
struct axp20x_info *info;
int irq_dbr;
int irq_dbf;
};
struct axp20x_time {
unsigned int time;
unsigned int idx;
};
static const struct axp20x_time startup_time[] = {
{ .time = 128, .idx = 0 },
{ .time = 1000, .idx = 2 },
{ .time = 3000, .idx = 1 },
{ .time = 2000, .idx = 3 },
};
static const struct axp20x_time axp221_startup_time[] = {
{ .time = 128, .idx = 0 },
{ .time = 1000, .idx = 1 },
{ .time = 2000, .idx = 2 },
{ .time = 3000, .idx = 3 },
};
static const struct axp20x_time shutdown_time[] = {
{ .time = 4000, .idx = 0 },
{ .time = 6000, .idx = 1 },
{ .time = 8000, .idx = 2 },
{ .time = 10000, .idx = 3 },
};
static const struct axp20x_info axp20x_info = {
.startup_time = startup_time,
.startup_mask = AXP20X_PEK_STARTUP_MASK,
.shutdown_time = shutdown_time,
.shutdown_mask = AXP20X_PEK_SHUTDOWN_MASK,
};
static const struct axp20x_info axp221_info = {
.startup_time = axp221_startup_time,
.startup_mask = AXP20X_PEK_STARTUP_MASK,
.shutdown_time = shutdown_time,
.shutdown_mask = AXP20X_PEK_SHUTDOWN_MASK,
};
static ssize_t axp20x_show_attr(struct device *dev,
const struct axp20x_time *time,
unsigned int mask, char *buf)
{
struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
unsigned int val;
int ret, i;
ret = regmap_read(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY, &val);
if (ret != 0)
return ret;
val &= mask;
val >>= ffs(mask) - 1;
for (i = 0; i < 4; i++)
if (val == time[i].idx)
val = time[i].time;
return sprintf(buf, "%u\n", val);
}
static ssize_t axp20x_show_attr_startup(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
return axp20x_show_attr(dev, axp20x_pek->info->startup_time,
axp20x_pek->info->startup_mask, buf);
}
static ssize_t axp20x_show_attr_shutdown(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
return axp20x_show_attr(dev, axp20x_pek->info->shutdown_time,
axp20x_pek->info->shutdown_mask, buf);
}
static ssize_t axp20x_store_attr(struct device *dev,
const struct axp20x_time *time,
unsigned int mask, const char *buf,
size_t count)
{
struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
char val_str[20];
size_t len;
int ret, i;
unsigned int val, idx = 0;
unsigned int best_err = UINT_MAX;
val_str[sizeof(val_str) - 1] = '\0';
strncpy(val_str, buf, sizeof(val_str) - 1);
len = strlen(val_str);
if (len && val_str[len - 1] == '\n')
val_str[len - 1] = '\0';
ret = kstrtouint(val_str, 10, &val);
if (ret)
return ret;
for (i = 3; i >= 0; i--) {
unsigned int err;
err = abs(time[i].time - val);
if (err < best_err) {
best_err = err;
idx = time[i].idx;
}
if (!err)
break;
}
idx <<= ffs(mask) - 1;
ret = regmap_update_bits(axp20x_pek->axp20x->regmap, AXP20X_PEK_KEY,
mask, idx);
if (ret != 0)
return -EINVAL;
return count;
}
static ssize_t axp20x_store_attr_startup(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
return axp20x_store_attr(dev, axp20x_pek->info->startup_time,
axp20x_pek->info->startup_mask, buf, count);
}
static ssize_t axp20x_store_attr_shutdown(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
return axp20x_store_attr(dev, axp20x_pek->info->shutdown_time,
axp20x_pek->info->shutdown_mask, buf, count);
}
DEVICE_ATTR(startup, 0644, axp20x_show_attr_startup, axp20x_store_attr_startup);
DEVICE_ATTR(shutdown, 0644, axp20x_show_attr_shutdown,
axp20x_store_attr_shutdown);
static struct attribute *axp20x_attributes[] = {
&dev_attr_startup.attr,
&dev_attr_shutdown.attr,
NULL,
};
static const struct attribute_group axp20x_attribute_group = {
.attrs = axp20x_attributes,
};
static irqreturn_t axp20x_pek_irq(int irq, void *pwr)
{
struct input_dev *idev = pwr;
struct axp20x_pek *axp20x_pek = input_get_drvdata(idev);
/*
* The power-button is connected to ground so a falling edge (dbf)
* means it is pressed.
*/
if (irq == axp20x_pek->irq_dbf)
input_report_key(idev, KEY_POWER, true);
else if (irq == axp20x_pek->irq_dbr)
input_report_key(idev, KEY_POWER, false);
input_sync(idev);
return IRQ_HANDLED;
}
static int axp20x_pek_probe_input_device(struct axp20x_pek *axp20x_pek,
struct platform_device *pdev)
{
struct axp20x_dev *axp20x = axp20x_pek->axp20x;
struct input_dev *idev;
int error;
axp20x_pek->irq_dbr = platform_get_irq_byname(pdev, "PEK_DBR");
if (axp20x_pek->irq_dbr < 0) {
dev_err(&pdev->dev, "No IRQ for PEK_DBR, error=%d\n",
axp20x_pek->irq_dbr);
return axp20x_pek->irq_dbr;
}
axp20x_pek->irq_dbr = regmap_irq_get_virq(axp20x->regmap_irqc,
axp20x_pek->irq_dbr);
axp20x_pek->irq_dbf = platform_get_irq_byname(pdev, "PEK_DBF");
if (axp20x_pek->irq_dbf < 0) {
dev_err(&pdev->dev, "No IRQ for PEK_DBF, error=%d\n",
axp20x_pek->irq_dbf);
return axp20x_pek->irq_dbf;
}
axp20x_pek->irq_dbf = regmap_irq_get_virq(axp20x->regmap_irqc,
axp20x_pek->irq_dbf);
axp20x_pek->input = devm_input_allocate_device(&pdev->dev);
if (!axp20x_pek->input)
return -ENOMEM;
idev = axp20x_pek->input;
idev->name = "axp20x-pek";
idev->phys = "m1kbd/input2";
idev->dev.parent = &pdev->dev;
input_set_capability(idev, EV_KEY, KEY_POWER);
input_set_drvdata(idev, axp20x_pek);
error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbr,
axp20x_pek_irq, 0,
"axp20x-pek-dbr", idev);
if (error < 0) {
dev_err(&pdev->dev, "Failed to request dbr IRQ#%d: %d\n",
axp20x_pek->irq_dbr, error);
return error;
}
error = devm_request_any_context_irq(&pdev->dev, axp20x_pek->irq_dbf,
axp20x_pek_irq, 0,
"axp20x-pek-dbf", idev);
if (error < 0) {
dev_err(&pdev->dev, "Failed to request dbf IRQ#%d: %d\n",
axp20x_pek->irq_dbf, error);
return error;
}
error = input_register_device(idev);
if (error) {
dev_err(&pdev->dev, "Can't register input device: %d\n",
error);
return error;
}
if (axp20x_pek->axp20x->variant == AXP288_ID)
enable_irq_wake(axp20x_pek->irq_dbr);
return 0;
}
#ifdef CONFIG_ACPI
static bool axp20x_pek_should_register_input(struct axp20x_pek *axp20x_pek,
struct platform_device *pdev)
{
unsigned long long hrv = 0;
acpi_status status;
if (IS_ENABLED(CONFIG_INPUT_SOC_BUTTON_ARRAY) &&
axp20x_pek->axp20x->variant == AXP288_ID) {
status = acpi_evaluate_integer(ACPI_HANDLE(pdev->dev.parent),
"_HRV", NULL, &hrv);
if (ACPI_FAILURE(status))
dev_err(&pdev->dev, "Failed to get PMIC hardware revision\n");
/*
* On Cherry Trail platforms (hrv == 3), do not register the
* input device if there is an "INTCFD9" or "ACPI0011" gpio
* button ACPI device, as that handles the power button too,
* and otherwise we end up reporting all presses twice.
*/
if (hrv == 3 && (acpi_dev_present("INTCFD9", NULL, -1) ||
acpi_dev_present("ACPI0011", NULL, -1)))
return false;
}
return true;
}
#else
static bool axp20x_pek_should_register_input(struct axp20x_pek *axp20x_pek,
struct platform_device *pdev)
{
return true;
}
#endif
static int axp20x_pek_probe(struct platform_device *pdev)
{
struct axp20x_pek *axp20x_pek;
const struct platform_device_id *match = platform_get_device_id(pdev);
int error;
if (!match) {
dev_err(&pdev->dev, "Failed to get platform_device_id\n");
return -EINVAL;
}
axp20x_pek = devm_kzalloc(&pdev->dev, sizeof(struct axp20x_pek),
GFP_KERNEL);
if (!axp20x_pek)
return -ENOMEM;
axp20x_pek->axp20x = dev_get_drvdata(pdev->dev.parent);
if (axp20x_pek_should_register_input(axp20x_pek, pdev)) {
error = axp20x_pek_probe_input_device(axp20x_pek, pdev);
if (error)
return error;
}
axp20x_pek->info = (struct axp20x_info *)match->driver_data;
error = devm_device_add_group(&pdev->dev, &axp20x_attribute_group);
if (error) {
dev_err(&pdev->dev, "Failed to create sysfs attributes: %d\n",
error);
return error;
}
platform_set_drvdata(pdev, axp20x_pek);
return 0;
}
static int __maybe_unused axp20x_pek_resume_noirq(struct device *dev)
{
struct axp20x_pek *axp20x_pek = dev_get_drvdata(dev);
if (axp20x_pek->axp20x->variant != AXP288_ID)
return 0;
/*
* Clear interrupts from button presses during suspend, to avoid
* a wakeup power-button press getting reported to userspace.
*/
regmap_write(axp20x_pek->axp20x->regmap,
AXP20X_IRQ1_STATE + AXP288_IRQ_POKN / 8,
BIT(AXP288_IRQ_POKN % 8));
return 0;
}
static const struct dev_pm_ops axp20x_pek_pm_ops = {
#ifdef CONFIG_PM_SLEEP
.resume_noirq = axp20x_pek_resume_noirq,
#endif
};
static const struct platform_device_id axp_pek_id_match[] = {
{
.name = "axp20x-pek",
.driver_data = (kernel_ulong_t)&axp20x_info,
},
{
.name = "axp221-pek",
.driver_data = (kernel_ulong_t)&axp221_info,
},
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, axp_pek_id_match);
static struct platform_driver axp20x_pek_driver = {
.probe = axp20x_pek_probe,
.id_table = axp_pek_id_match,
.driver = {
.name = "axp20x-pek",
.pm = &axp20x_pek_pm_ops,
},
};
module_platform_driver(axp20x_pek_driver);
MODULE_DESCRIPTION("axp20x Power Button");
MODULE_AUTHOR("Carlo Caione <carlo@caione.org>");
MODULE_LICENSE("GPL");
+668
View File
@@ -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 const 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)
usleep_range(2000, 2100);
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;
usleep_range(2000, 2100);
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);
x = sign_extend32(x, 9);
y = sign_extend32(y, 9);
z = sign_extend32(z, 9);
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);
bma150->input = idev;
error = input_register_device(idev);
if (error) {
input_free_device(idev);
return error;
}
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);
bma150->input_polled = ipoll_dev;
bma150->input = ipoll_dev->input;
error = input_register_polled_device(ipoll_dev);
if (error) {
input_free_polled_device(ipoll_dev);
return error;
}
return 0;
}
static int bma150_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
const struct bma150_platform_data *pdata =
dev_get_platdata(&client->dev);
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 = {
.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");
+956
View File
@@ -0,0 +1,956 @@
/*
* 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 15 special key combinations */
KEYMAP_SIZE = 256 + 15,
};
/* 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_MICMUTE;
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);
}
/*
* Converts data of special key presses (volume, mute) into events
* for the input subsystem, sends press-n-release for mute keys.
*/
static void cm109_report_special(struct cm109_dev *dev)
{
static const u8 autorelease = RECORD_MUTE | PLAYBACK_MUTE;
struct input_dev *idev = dev->idev;
u8 data = dev->irq_data->byte[HID_IR0];
unsigned short keycode;
int i;
for (i = 0; i < 4; i++) {
keycode = dev->keymap[0xff + BIT(i)];
if (keycode == KEY_RESERVED)
continue;
input_report_key(idev, keycode, data & BIT(i));
if (data & autorelease & BIT(i)) {
input_sync(idev);
input_report_key(idev, keycode, 0);
}
}
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;
unsigned long flags;
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_ratelimited(&dev->intf->dev, "%s: urb status %d\n",
__func__, status);
goto out;
}
/* Special keys */
cm109_report_special(dev);
/* 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_irqsave(&dev->ctl_submit_lock, flags);
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_irqrestore(&dev->ctl_submit_lock, flags);
}
static void cm109_urb_ctl_callback(struct urb *urb)
{
struct cm109_dev *dev = urb->context;
const int status = urb->status;
int error;
unsigned long flags;
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) {
if (status == -ESHUTDOWN)
return;
dev_err_ratelimited(&dev->intf->dev, "%s: urb status %d\n",
__func__, status);
}
spin_lock_irqsave(&dev->ctl_submit_lock, flags);
dev->ctl_urb_pending = 0;
if (likely(!dev->shutdown)) {
if (dev->buzzer_pending || status) {
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_irqrestore(&dev->ctl_submit_lock, flags);
}
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;
dev->ctl_urb_pending = 1;
error = usb_submit_urb(dev->urb_ctl, GFP_KERNEL);
if (error) {
dev->ctl_urb_pending = 0;
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;
if (interface->desc.bNumEndpoints < 1)
return -ENODEV;
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");
+399
View File
@@ -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_get_platdata(dev);
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>");
+42
View File
@@ -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
+131
View File
@@ -0,0 +1,131 @@
/*
* 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",
#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>");
+162
View File
@@ -0,0 +1,162 @@
/*
* 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/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);
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);
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",
},
};
module_platform_driver(cobalt_buttons_driver);
+117
View File
@@ -0,0 +1,117 @@
/**
* CPCAP Power Button Input Driver
*
* Copyright (C) 2017 Sebastian Reichel <sre@kernel.org>
*
* 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/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mfd/motorola-cpcap.h>
#define CPCAP_IRQ_ON 23
#define CPCAP_IRQ_ON_BITMASK (1 << (CPCAP_IRQ_ON % 16))
struct cpcap_power_button {
struct regmap *regmap;
struct input_dev *idev;
struct device *dev;
};
static irqreturn_t powerbutton_irq(int irq, void *_button)
{
struct cpcap_power_button *button = _button;
int val;
val = cpcap_sense_virq(button->regmap, irq);
if (val < 0) {
dev_err(button->dev, "irq read failed: %d", val);
return IRQ_HANDLED;
}
pm_wakeup_event(button->dev, 0);
input_report_key(button->idev, KEY_POWER, val);
input_sync(button->idev);
return IRQ_HANDLED;
}
static int cpcap_power_button_probe(struct platform_device *pdev)
{
struct cpcap_power_button *button;
int irq = platform_get_irq(pdev, 0);
int err;
button = devm_kmalloc(&pdev->dev, sizeof(*button), GFP_KERNEL);
if (!button)
return -ENOMEM;
button->idev = devm_input_allocate_device(&pdev->dev);
if (!button->idev)
return -ENOMEM;
button->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!button->regmap)
return -ENODEV;
button->dev = &pdev->dev;
button->idev->name = "cpcap-pwrbutton";
button->idev->phys = "cpcap-pwrbutton/input0";
button->idev->dev.parent = button->dev;
input_set_capability(button->idev, EV_KEY, KEY_POWER);
err = devm_request_threaded_irq(&pdev->dev, irq, NULL,
powerbutton_irq, IRQF_ONESHOT, "cpcap_pwrbutton", button);
if (err < 0) {
dev_err(&pdev->dev, "IRQ request failed: %d\n", err);
return err;
}
err = input_register_device(button->idev);
if (err) {
dev_err(&pdev->dev, "Input register failed: %d\n", err);
return err;
}
device_init_wakeup(&pdev->dev, true);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id cpcap_pwrbutton_dt_match_table[] = {
{ .compatible = "motorola,cpcap-pwrbutton" },
{},
};
MODULE_DEVICE_TABLE(of, cpcap_pwrbutton_dt_match_table);
#endif
static struct platform_driver cpcap_power_button_driver = {
.probe = cpcap_power_button_probe,
.driver = {
.name = "cpcap-pwrbutton",
.of_match_table = of_match_ptr(cpcap_pwrbutton_dt_match_table),
},
};
module_platform_driver(cpcap_power_button_driver);
MODULE_ALIAS("platform:cpcap-pwrbutton");
MODULE_DESCRIPTION("CPCAP Power Button");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+159
View File
@@ -0,0 +1,159 @@
/*
* 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/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 ret;
ret = da9052_reg_read(onkey->da9052, DA9052_STATUS_A_REG);
if (ret < 0) {
dev_err(onkey->da9052->dev,
"Failed to read onkey event err=%d\n", ret);
} else {
/*
* Since interrupt for deassertion of ONKEY pin is not
* generated, onkey event state determines the onkey
* button state.
*/
bool pressed = !(ret & DA9052_STATUSA_NONKEY);
input_report_key(onkey->input, KEY_POWER, pressed);
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 (pressed)
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",
},
};
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");
+168
View File
@@ -0,0 +1,168 @@
/*
* 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/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 released.
*/
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);
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",
},
};
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");
+302
View File
@@ -0,0 +1,302 @@
/*
* OnKey device driver for DA9063, DA9062 and DA9061 PMICs
* Copyright (C) 2015 Dialog Semiconductor Ltd.
*
* 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/module.h>
#include <linux/errno.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/workqueue.h>
#include <linux/regmap.h>
#include <linux/of.h>
#include <linux/mfd/da9063/core.h>
#include <linux/mfd/da9063/pdata.h>
#include <linux/mfd/da9063/registers.h>
#include <linux/mfd/da9062/core.h>
#include <linux/mfd/da9062/registers.h>
struct da906x_chip_config {
/* REGS */
int onkey_status;
int onkey_pwr_signalling;
int onkey_fault_log;
int onkey_shutdown;
/* MASKS */
int onkey_nonkey_mask;
int onkey_nonkey_lock_mask;
int onkey_key_reset_mask;
int onkey_shutdown_mask;
/* NAMES */
const char *name;
};
struct da9063_onkey {
struct delayed_work work;
struct input_dev *input;
struct device *dev;
struct regmap *regmap;
const struct da906x_chip_config *config;
char phys[32];
bool key_power;
};
static const struct da906x_chip_config da9063_regs = {
/* REGS */
.onkey_status = DA9063_REG_STATUS_A,
.onkey_pwr_signalling = DA9063_REG_CONTROL_B,
.onkey_fault_log = DA9063_REG_FAULT_LOG,
.onkey_shutdown = DA9063_REG_CONTROL_F,
/* MASKS */
.onkey_nonkey_mask = DA9063_NONKEY,
.onkey_nonkey_lock_mask = DA9063_NONKEY_LOCK,
.onkey_key_reset_mask = DA9063_KEY_RESET,
.onkey_shutdown_mask = DA9063_SHUTDOWN,
/* NAMES */
.name = DA9063_DRVNAME_ONKEY,
};
static const struct da906x_chip_config da9062_regs = {
/* REGS */
.onkey_status = DA9062AA_STATUS_A,
.onkey_pwr_signalling = DA9062AA_CONTROL_B,
.onkey_fault_log = DA9062AA_FAULT_LOG,
.onkey_shutdown = DA9062AA_CONTROL_F,
/* MASKS */
.onkey_nonkey_mask = DA9062AA_NONKEY_MASK,
.onkey_nonkey_lock_mask = DA9062AA_NONKEY_LOCK_MASK,
.onkey_key_reset_mask = DA9062AA_KEY_RESET_MASK,
.onkey_shutdown_mask = DA9062AA_SHUTDOWN_MASK,
/* NAMES */
.name = "da9062-onkey",
};
static const struct of_device_id da9063_compatible_reg_id_table[] = {
{ .compatible = "dlg,da9063-onkey", .data = &da9063_regs },
{ .compatible = "dlg,da9062-onkey", .data = &da9062_regs },
{ },
};
MODULE_DEVICE_TABLE(of, da9063_compatible_reg_id_table);
static void da9063_poll_on(struct work_struct *work)
{
struct da9063_onkey *onkey = container_of(work,
struct da9063_onkey,
work.work);
const struct da906x_chip_config *config = onkey->config;
unsigned int val;
int fault_log = 0;
bool poll = true;
int error;
/* Poll to see when the pin is released */
error = regmap_read(onkey->regmap,
config->onkey_status,
&val);
if (error) {
dev_err(onkey->dev,
"Failed to read ON status: %d\n", error);
goto err_poll;
}
if (!(val & config->onkey_nonkey_mask)) {
error = regmap_update_bits(onkey->regmap,
config->onkey_pwr_signalling,
config->onkey_nonkey_lock_mask,
0);
if (error) {
dev_err(onkey->dev,
"Failed to reset the Key Delay %d\n", error);
goto err_poll;
}
input_report_key(onkey->input, KEY_POWER, 0);
input_sync(onkey->input);
poll = false;
}
/*
* If the fault log KEY_RESET is detected, then clear it
* and shut down the system.
*/
error = regmap_read(onkey->regmap,
config->onkey_fault_log,
&fault_log);
if (error) {
dev_warn(&onkey->input->dev,
"Cannot read FAULT_LOG: %d\n", error);
} else if (fault_log & config->onkey_key_reset_mask) {
error = regmap_write(onkey->regmap,
config->onkey_fault_log,
config->onkey_key_reset_mask);
if (error) {
dev_warn(&onkey->input->dev,
"Cannot reset KEY_RESET fault log: %d\n",
error);
} else {
/* at this point we do any S/W housekeeping
* and then send shutdown command
*/
dev_dbg(&onkey->input->dev,
"Sending SHUTDOWN to PMIC ...\n");
error = regmap_write(onkey->regmap,
config->onkey_shutdown,
config->onkey_shutdown_mask);
if (error)
dev_err(&onkey->input->dev,
"Cannot SHUTDOWN PMIC: %d\n",
error);
}
}
err_poll:
if (poll)
schedule_delayed_work(&onkey->work, msecs_to_jiffies(50));
}
static irqreturn_t da9063_onkey_irq_handler(int irq, void *data)
{
struct da9063_onkey *onkey = data;
const struct da906x_chip_config *config = onkey->config;
unsigned int val;
int error;
error = regmap_read(onkey->regmap,
config->onkey_status,
&val);
if (onkey->key_power && !error && (val & config->onkey_nonkey_mask)) {
input_report_key(onkey->input, KEY_POWER, 1);
input_sync(onkey->input);
schedule_delayed_work(&onkey->work, 0);
dev_dbg(onkey->dev, "KEY_POWER long press.\n");
} else {
input_report_key(onkey->input, KEY_POWER, 1);
input_sync(onkey->input);
input_report_key(onkey->input, KEY_POWER, 0);
input_sync(onkey->input);
dev_dbg(onkey->dev, "KEY_POWER short press.\n");
}
return IRQ_HANDLED;
}
static void da9063_cancel_poll(void *data)
{
struct da9063_onkey *onkey = data;
cancel_delayed_work_sync(&onkey->work);
}
static int da9063_onkey_probe(struct platform_device *pdev)
{
struct da9063 *da9063 = dev_get_drvdata(pdev->dev.parent);
struct da9063_pdata *pdata = dev_get_platdata(da9063->dev);
struct da9063_onkey *onkey;
const struct of_device_id *match;
int irq;
int error;
match = of_match_node(da9063_compatible_reg_id_table,
pdev->dev.of_node);
if (!match)
return -ENXIO;
onkey = devm_kzalloc(&pdev->dev, sizeof(struct da9063_onkey),
GFP_KERNEL);
if (!onkey) {
dev_err(&pdev->dev, "Failed to allocate memory.\n");
return -ENOMEM;
}
onkey->config = match->data;
onkey->dev = &pdev->dev;
onkey->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!onkey->regmap) {
dev_err(&pdev->dev, "Parent regmap unavailable.\n");
return -ENXIO;
}
if (pdata)
onkey->key_power = pdata->key_power;
else
onkey->key_power =
!of_property_read_bool(pdev->dev.of_node,
"dlg,disable-key-power");
onkey->input = devm_input_allocate_device(&pdev->dev);
if (!onkey->input) {
dev_err(&pdev->dev, "Failed to allocated input device.\n");
return -ENOMEM;
}
onkey->input->name = onkey->config->name;
snprintf(onkey->phys, sizeof(onkey->phys), "%s/input0",
onkey->config->name);
onkey->input->phys = onkey->phys;
onkey->input->dev.parent = &pdev->dev;
input_set_capability(onkey->input, EV_KEY, KEY_POWER);
INIT_DELAYED_WORK(&onkey->work, da9063_poll_on);
error = devm_add_action(&pdev->dev, da9063_cancel_poll, onkey);
if (error) {
dev_err(&pdev->dev,
"Failed to add cancel poll action: %d\n",
error);
return error;
}
irq = platform_get_irq_byname(pdev, "ONKEY");
if (irq < 0) {
error = irq;
dev_err(&pdev->dev, "Failed to get platform IRQ: %d\n", error);
return error;
}
error = devm_request_threaded_irq(&pdev->dev, irq,
NULL, da9063_onkey_irq_handler,
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
"ONKEY", onkey);
if (error) {
dev_err(&pdev->dev,
"Failed to request IRQ %d: %d\n", irq, error);
return error;
}
error = input_register_device(onkey->input);
if (error) {
dev_err(&pdev->dev,
"Failed to register input device: %d\n", error);
return error;
}
return 0;
}
static struct platform_driver da9063_onkey_driver = {
.probe = da9063_onkey_probe,
.driver = {
.name = DA9063_DRVNAME_ONKEY,
.of_match_table = da9063_compatible_reg_id_table,
},
};
module_platform_driver(da9063_onkey_driver);
MODULE_AUTHOR("S Twiss <stwiss.opensource@diasemi.com>");
MODULE_DESCRIPTION("Onkey device driver for Dialog DA9063, DA9062 and DA9061");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:" DA9063_DRVNAME_ONKEY);
+242
View File
@@ -0,0 +1,242 @@
/*
* 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/slab.h>
#include <linux/input.h>
#include <linux/input/sparse-keymap.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/mfd/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;
};
/* 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 irq;
int error;
keys = devm_kzalloc(&pdev->dev, sizeof (*keys), GFP_KERNEL);
if (!keys)
return -ENOMEM;
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
keys->dev = &pdev->dev;
keys->input = input;
input->name = "DM355 EVM Controls";
input->phys = "dm355evm/input0";
input->id.bustype = BUS_I2C;
input->id.product = 0x0355;
input->id.version = dm355evm_msp_read(DM355EVM_MSP_FIRMREV);
error = sparse_keymap_setup(input, dm355evm_keys, NULL);
if (error)
return error;
/* REVISIT: flush the event queue? */
/* set up "threaded IRQ handler" */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
error = devm_request_threaded_irq(&pdev->dev, irq,
NULL, dm355evm_keys_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
dev_name(&pdev->dev), keys);
if (error)
return error;
/* register */
error = input_register_device(input);
if (error)
return error;
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,
.driver = {
.name = "dm355evm_keys",
},
};
module_platform_driver(dm355evm_keys_driver);
MODULE_LICENSE("GPL");
+677
View File
@@ -0,0 +1,677 @@
/*
* DRV260X haptics driver family
*
* Author: Dan Murphy <dmurphy@ti.com>
*
* Copyright: (C) 2014 Texas Instruments, Inc.
*
* 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.
*/
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/regulator/consumer.h>
#include <dt-bindings/input/ti-drv260x.h>
#define DRV260X_STATUS 0x0
#define DRV260X_MODE 0x1
#define DRV260X_RT_PB_IN 0x2
#define DRV260X_LIB_SEL 0x3
#define DRV260X_WV_SEQ_1 0x4
#define DRV260X_WV_SEQ_2 0x5
#define DRV260X_WV_SEQ_3 0x6
#define DRV260X_WV_SEQ_4 0x7
#define DRV260X_WV_SEQ_5 0x8
#define DRV260X_WV_SEQ_6 0x9
#define DRV260X_WV_SEQ_7 0xa
#define DRV260X_WV_SEQ_8 0xb
#define DRV260X_GO 0xc
#define DRV260X_OVERDRIVE_OFF 0xd
#define DRV260X_SUSTAIN_P_OFF 0xe
#define DRV260X_SUSTAIN_N_OFF 0xf
#define DRV260X_BRAKE_OFF 0x10
#define DRV260X_A_TO_V_CTRL 0x11
#define DRV260X_A_TO_V_MIN_INPUT 0x12
#define DRV260X_A_TO_V_MAX_INPUT 0x13
#define DRV260X_A_TO_V_MIN_OUT 0x14
#define DRV260X_A_TO_V_MAX_OUT 0x15
#define DRV260X_RATED_VOLT 0x16
#define DRV260X_OD_CLAMP_VOLT 0x17
#define DRV260X_CAL_COMP 0x18
#define DRV260X_CAL_BACK_EMF 0x19
#define DRV260X_FEEDBACK_CTRL 0x1a
#define DRV260X_CTRL1 0x1b
#define DRV260X_CTRL2 0x1c
#define DRV260X_CTRL3 0x1d
#define DRV260X_CTRL4 0x1e
#define DRV260X_CTRL5 0x1f
#define DRV260X_LRA_LOOP_PERIOD 0x20
#define DRV260X_VBAT_MON 0x21
#define DRV260X_LRA_RES_PERIOD 0x22
#define DRV260X_MAX_REG 0x23
#define DRV260X_GO_BIT 0x01
/* Library Selection */
#define DRV260X_LIB_SEL_MASK 0x07
#define DRV260X_LIB_SEL_RAM 0x0
#define DRV260X_LIB_SEL_OD 0x1
#define DRV260X_LIB_SEL_40_60 0x2
#define DRV260X_LIB_SEL_60_80 0x3
#define DRV260X_LIB_SEL_100_140 0x4
#define DRV260X_LIB_SEL_140_PLUS 0x5
#define DRV260X_LIB_SEL_HIZ_MASK 0x10
#define DRV260X_LIB_SEL_HIZ_EN 0x01
#define DRV260X_LIB_SEL_HIZ_DIS 0
/* Mode register */
#define DRV260X_STANDBY (1 << 6)
#define DRV260X_STANDBY_MASK 0x40
#define DRV260X_INTERNAL_TRIGGER 0x00
#define DRV260X_EXT_TRIGGER_EDGE 0x01
#define DRV260X_EXT_TRIGGER_LEVEL 0x02
#define DRV260X_PWM_ANALOG_IN 0x03
#define DRV260X_AUDIOHAPTIC 0x04
#define DRV260X_RT_PLAYBACK 0x05
#define DRV260X_DIAGNOSTICS 0x06
#define DRV260X_AUTO_CAL 0x07
/* Audio to Haptics Control */
#define DRV260X_AUDIO_HAPTICS_PEAK_10MS (0 << 2)
#define DRV260X_AUDIO_HAPTICS_PEAK_20MS (1 << 2)
#define DRV260X_AUDIO_HAPTICS_PEAK_30MS (2 << 2)
#define DRV260X_AUDIO_HAPTICS_PEAK_40MS (3 << 2)
#define DRV260X_AUDIO_HAPTICS_FILTER_100HZ 0x00
#define DRV260X_AUDIO_HAPTICS_FILTER_125HZ 0x01
#define DRV260X_AUDIO_HAPTICS_FILTER_150HZ 0x02
#define DRV260X_AUDIO_HAPTICS_FILTER_200HZ 0x03
/* Min/Max Input/Output Voltages */
#define DRV260X_AUDIO_HAPTICS_MIN_IN_VOLT 0x19
#define DRV260X_AUDIO_HAPTICS_MAX_IN_VOLT 0x64
#define DRV260X_AUDIO_HAPTICS_MIN_OUT_VOLT 0x19
#define DRV260X_AUDIO_HAPTICS_MAX_OUT_VOLT 0xFF
/* Feedback register */
#define DRV260X_FB_REG_ERM_MODE 0x7f
#define DRV260X_FB_REG_LRA_MODE (1 << 7)
#define DRV260X_BRAKE_FACTOR_MASK 0x1f
#define DRV260X_BRAKE_FACTOR_2X (1 << 0)
#define DRV260X_BRAKE_FACTOR_3X (2 << 4)
#define DRV260X_BRAKE_FACTOR_4X (3 << 4)
#define DRV260X_BRAKE_FACTOR_6X (4 << 4)
#define DRV260X_BRAKE_FACTOR_8X (5 << 4)
#define DRV260X_BRAKE_FACTOR_16 (6 << 4)
#define DRV260X_BRAKE_FACTOR_DIS (7 << 4)
#define DRV260X_LOOP_GAIN_LOW 0xf3
#define DRV260X_LOOP_GAIN_MED (1 << 2)
#define DRV260X_LOOP_GAIN_HIGH (2 << 2)
#define DRV260X_LOOP_GAIN_VERY_HIGH (3 << 2)
#define DRV260X_BEMF_GAIN_0 0xfc
#define DRV260X_BEMF_GAIN_1 (1 << 0)
#define DRV260X_BEMF_GAIN_2 (2 << 0)
#define DRV260X_BEMF_GAIN_3 (3 << 0)
/* Control 1 register */
#define DRV260X_AC_CPLE_EN (1 << 5)
#define DRV260X_STARTUP_BOOST (1 << 7)
/* Control 2 register */
#define DRV260X_IDISS_TIME_45 0
#define DRV260X_IDISS_TIME_75 (1 << 0)
#define DRV260X_IDISS_TIME_150 (1 << 1)
#define DRV260X_IDISS_TIME_225 0x03
#define DRV260X_BLANK_TIME_45 (0 << 2)
#define DRV260X_BLANK_TIME_75 (1 << 2)
#define DRV260X_BLANK_TIME_150 (2 << 2)
#define DRV260X_BLANK_TIME_225 (3 << 2)
#define DRV260X_SAMP_TIME_150 (0 << 4)
#define DRV260X_SAMP_TIME_200 (1 << 4)
#define DRV260X_SAMP_TIME_250 (2 << 4)
#define DRV260X_SAMP_TIME_300 (3 << 4)
#define DRV260X_BRAKE_STABILIZER (1 << 6)
#define DRV260X_UNIDIR_IN (0 << 7)
#define DRV260X_BIDIR_IN (1 << 7)
/* Control 3 Register */
#define DRV260X_LRA_OPEN_LOOP (1 << 0)
#define DRV260X_ANANLOG_IN (1 << 1)
#define DRV260X_LRA_DRV_MODE (1 << 2)
#define DRV260X_RTP_UNSIGNED_DATA (1 << 3)
#define DRV260X_SUPPLY_COMP_DIS (1 << 4)
#define DRV260X_ERM_OPEN_LOOP (1 << 5)
#define DRV260X_NG_THRESH_0 (0 << 6)
#define DRV260X_NG_THRESH_2 (1 << 6)
#define DRV260X_NG_THRESH_4 (2 << 6)
#define DRV260X_NG_THRESH_8 (3 << 6)
/* Control 4 Register */
#define DRV260X_AUTOCAL_TIME_150MS (0 << 4)
#define DRV260X_AUTOCAL_TIME_250MS (1 << 4)
#define DRV260X_AUTOCAL_TIME_500MS (2 << 4)
#define DRV260X_AUTOCAL_TIME_1000MS (3 << 4)
/**
* struct drv260x_data -
* @input_dev - Pointer to the input device
* @client - Pointer to the I2C client
* @regmap - Register map of the device
* @work - Work item used to off load the enable/disable of the vibration
* @enable_gpio - Pointer to the gpio used for enable/disabling
* @regulator - Pointer to the regulator for the IC
* @magnitude - Magnitude of the vibration event
* @mode - The operating mode of the IC (LRA_NO_CAL, ERM or LRA)
* @library - The vibration library to be used
* @rated_voltage - The rated_voltage of the actuator
* @overdriver_voltage - The over drive voltage of the actuator
**/
struct drv260x_data {
struct input_dev *input_dev;
struct i2c_client *client;
struct regmap *regmap;
struct work_struct work;
struct gpio_desc *enable_gpio;
struct regulator *regulator;
u32 magnitude;
u32 mode;
u32 library;
int rated_voltage;
int overdrive_voltage;
};
static const struct reg_default drv260x_reg_defs[] = {
{ DRV260X_STATUS, 0xe0 },
{ DRV260X_MODE, 0x40 },
{ DRV260X_RT_PB_IN, 0x00 },
{ DRV260X_LIB_SEL, 0x00 },
{ DRV260X_WV_SEQ_1, 0x01 },
{ DRV260X_WV_SEQ_2, 0x00 },
{ DRV260X_WV_SEQ_3, 0x00 },
{ DRV260X_WV_SEQ_4, 0x00 },
{ DRV260X_WV_SEQ_5, 0x00 },
{ DRV260X_WV_SEQ_6, 0x00 },
{ DRV260X_WV_SEQ_7, 0x00 },
{ DRV260X_WV_SEQ_8, 0x00 },
{ DRV260X_GO, 0x00 },
{ DRV260X_OVERDRIVE_OFF, 0x00 },
{ DRV260X_SUSTAIN_P_OFF, 0x00 },
{ DRV260X_SUSTAIN_N_OFF, 0x00 },
{ DRV260X_BRAKE_OFF, 0x00 },
{ DRV260X_A_TO_V_CTRL, 0x05 },
{ DRV260X_A_TO_V_MIN_INPUT, 0x19 },
{ DRV260X_A_TO_V_MAX_INPUT, 0xff },
{ DRV260X_A_TO_V_MIN_OUT, 0x19 },
{ DRV260X_A_TO_V_MAX_OUT, 0xff },
{ DRV260X_RATED_VOLT, 0x3e },
{ DRV260X_OD_CLAMP_VOLT, 0x8c },
{ DRV260X_CAL_COMP, 0x0c },
{ DRV260X_CAL_BACK_EMF, 0x6c },
{ DRV260X_FEEDBACK_CTRL, 0x36 },
{ DRV260X_CTRL1, 0x93 },
{ DRV260X_CTRL2, 0xfa },
{ DRV260X_CTRL3, 0xa0 },
{ DRV260X_CTRL4, 0x20 },
{ DRV260X_CTRL5, 0x80 },
{ DRV260X_LRA_LOOP_PERIOD, 0x33 },
{ DRV260X_VBAT_MON, 0x00 },
{ DRV260X_LRA_RES_PERIOD, 0x00 },
};
#define DRV260X_DEF_RATED_VOLT 0x90
#define DRV260X_DEF_OD_CLAMP_VOLT 0x90
/**
* Rated and Overdriver Voltages:
* Calculated using the formula r = v * 255 / 5.6
* where r is what will be written to the register
* and v is the rated or overdriver voltage of the actuator
**/
static int drv260x_calculate_voltage(unsigned int voltage)
{
return (voltage * 255 / 5600);
}
static void drv260x_worker(struct work_struct *work)
{
struct drv260x_data *haptics = container_of(work, struct drv260x_data, work);
int error;
gpiod_set_value(haptics->enable_gpio, 1);
/* Data sheet says to wait 250us before trying to communicate */
udelay(250);
error = regmap_write(haptics->regmap,
DRV260X_MODE, DRV260X_RT_PLAYBACK);
if (error) {
dev_err(&haptics->client->dev,
"Failed to write set mode: %d\n", error);
} else {
error = regmap_write(haptics->regmap,
DRV260X_RT_PB_IN, haptics->magnitude);
if (error)
dev_err(&haptics->client->dev,
"Failed to set magnitude: %d\n", error);
}
}
static int drv260x_haptics_play(struct input_dev *input, void *data,
struct ff_effect *effect)
{
struct drv260x_data *haptics = input_get_drvdata(input);
haptics->mode = DRV260X_LRA_NO_CAL_MODE;
if (effect->u.rumble.strong_magnitude > 0)
haptics->magnitude = effect->u.rumble.strong_magnitude;
else if (effect->u.rumble.weak_magnitude > 0)
haptics->magnitude = effect->u.rumble.weak_magnitude;
else
haptics->magnitude = 0;
schedule_work(&haptics->work);
return 0;
}
static void drv260x_close(struct input_dev *input)
{
struct drv260x_data *haptics = input_get_drvdata(input);
int error;
cancel_work_sync(&haptics->work);
error = regmap_write(haptics->regmap, DRV260X_MODE, DRV260X_STANDBY);
if (error)
dev_err(&haptics->client->dev,
"Failed to enter standby mode: %d\n", error);
gpiod_set_value(haptics->enable_gpio, 0);
}
static const struct reg_sequence drv260x_lra_cal_regs[] = {
{ DRV260X_MODE, DRV260X_AUTO_CAL },
{ DRV260X_CTRL3, DRV260X_NG_THRESH_2 },
{ DRV260X_FEEDBACK_CTRL, DRV260X_FB_REG_LRA_MODE |
DRV260X_BRAKE_FACTOR_4X | DRV260X_LOOP_GAIN_HIGH },
};
static const struct reg_sequence drv260x_lra_init_regs[] = {
{ DRV260X_MODE, DRV260X_RT_PLAYBACK },
{ DRV260X_A_TO_V_CTRL, DRV260X_AUDIO_HAPTICS_PEAK_20MS |
DRV260X_AUDIO_HAPTICS_FILTER_125HZ },
{ DRV260X_A_TO_V_MIN_INPUT, DRV260X_AUDIO_HAPTICS_MIN_IN_VOLT },
{ DRV260X_A_TO_V_MAX_INPUT, DRV260X_AUDIO_HAPTICS_MAX_IN_VOLT },
{ DRV260X_A_TO_V_MIN_OUT, DRV260X_AUDIO_HAPTICS_MIN_OUT_VOLT },
{ DRV260X_A_TO_V_MAX_OUT, DRV260X_AUDIO_HAPTICS_MAX_OUT_VOLT },
{ DRV260X_FEEDBACK_CTRL, DRV260X_FB_REG_LRA_MODE |
DRV260X_BRAKE_FACTOR_2X | DRV260X_LOOP_GAIN_MED |
DRV260X_BEMF_GAIN_3 },
{ DRV260X_CTRL1, DRV260X_STARTUP_BOOST },
{ DRV260X_CTRL2, DRV260X_SAMP_TIME_250 },
{ DRV260X_CTRL3, DRV260X_NG_THRESH_2 | DRV260X_ANANLOG_IN },
{ DRV260X_CTRL4, DRV260X_AUTOCAL_TIME_500MS },
};
static const struct reg_sequence drv260x_erm_cal_regs[] = {
{ DRV260X_MODE, DRV260X_AUTO_CAL },
{ DRV260X_A_TO_V_MIN_INPUT, DRV260X_AUDIO_HAPTICS_MIN_IN_VOLT },
{ DRV260X_A_TO_V_MAX_INPUT, DRV260X_AUDIO_HAPTICS_MAX_IN_VOLT },
{ DRV260X_A_TO_V_MIN_OUT, DRV260X_AUDIO_HAPTICS_MIN_OUT_VOLT },
{ DRV260X_A_TO_V_MAX_OUT, DRV260X_AUDIO_HAPTICS_MAX_OUT_VOLT },
{ DRV260X_FEEDBACK_CTRL, DRV260X_BRAKE_FACTOR_3X |
DRV260X_LOOP_GAIN_MED | DRV260X_BEMF_GAIN_2 },
{ DRV260X_CTRL1, DRV260X_STARTUP_BOOST },
{ DRV260X_CTRL2, DRV260X_SAMP_TIME_250 | DRV260X_BLANK_TIME_75 |
DRV260X_IDISS_TIME_75 },
{ DRV260X_CTRL3, DRV260X_NG_THRESH_2 | DRV260X_ERM_OPEN_LOOP },
{ DRV260X_CTRL4, DRV260X_AUTOCAL_TIME_500MS },
};
static int drv260x_init(struct drv260x_data *haptics)
{
int error;
unsigned int cal_buf;
error = regmap_write(haptics->regmap,
DRV260X_RATED_VOLT, haptics->rated_voltage);
if (error) {
dev_err(&haptics->client->dev,
"Failed to write DRV260X_RATED_VOLT register: %d\n",
error);
return error;
}
error = regmap_write(haptics->regmap,
DRV260X_OD_CLAMP_VOLT, haptics->overdrive_voltage);
if (error) {
dev_err(&haptics->client->dev,
"Failed to write DRV260X_OD_CLAMP_VOLT register: %d\n",
error);
return error;
}
switch (haptics->mode) {
case DRV260X_LRA_MODE:
error = regmap_register_patch(haptics->regmap,
drv260x_lra_cal_regs,
ARRAY_SIZE(drv260x_lra_cal_regs));
if (error) {
dev_err(&haptics->client->dev,
"Failed to write LRA calibration registers: %d\n",
error);
return error;
}
break;
case DRV260X_ERM_MODE:
error = regmap_register_patch(haptics->regmap,
drv260x_erm_cal_regs,
ARRAY_SIZE(drv260x_erm_cal_regs));
if (error) {
dev_err(&haptics->client->dev,
"Failed to write ERM calibration registers: %d\n",
error);
return error;
}
error = regmap_update_bits(haptics->regmap, DRV260X_LIB_SEL,
DRV260X_LIB_SEL_MASK,
haptics->library);
if (error) {
dev_err(&haptics->client->dev,
"Failed to write DRV260X_LIB_SEL register: %d\n",
error);
return error;
}
break;
default:
error = regmap_register_patch(haptics->regmap,
drv260x_lra_init_regs,
ARRAY_SIZE(drv260x_lra_init_regs));
if (error) {
dev_err(&haptics->client->dev,
"Failed to write LRA init registers: %d\n",
error);
return error;
}
error = regmap_update_bits(haptics->regmap, DRV260X_LIB_SEL,
DRV260X_LIB_SEL_MASK,
haptics->library);
if (error) {
dev_err(&haptics->client->dev,
"Failed to write DRV260X_LIB_SEL register: %d\n",
error);
return error;
}
/* No need to set GO bit here */
return 0;
}
error = regmap_write(haptics->regmap, DRV260X_GO, DRV260X_GO_BIT);
if (error) {
dev_err(&haptics->client->dev,
"Failed to write GO register: %d\n",
error);
return error;
}
do {
error = regmap_read(haptics->regmap, DRV260X_GO, &cal_buf);
if (error) {
dev_err(&haptics->client->dev,
"Failed to read GO register: %d\n",
error);
return error;
}
} while (cal_buf == DRV260X_GO_BIT);
return 0;
}
static const struct regmap_config drv260x_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = DRV260X_MAX_REG,
.reg_defaults = drv260x_reg_defs,
.num_reg_defaults = ARRAY_SIZE(drv260x_reg_defs),
.cache_type = REGCACHE_NONE,
};
static int drv260x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct drv260x_data *haptics;
u32 voltage;
int error;
haptics = devm_kzalloc(dev, sizeof(*haptics), GFP_KERNEL);
if (!haptics)
return -ENOMEM;
error = device_property_read_u32(dev, "mode", &haptics->mode);
if (error) {
dev_err(dev, "Can't fetch 'mode' property: %d\n", error);
return error;
}
if (haptics->mode < DRV260X_LRA_MODE ||
haptics->mode > DRV260X_ERM_MODE) {
dev_err(dev, "Vibrator mode is invalid: %i\n", haptics->mode);
return -EINVAL;
}
error = device_property_read_u32(dev, "library-sel", &haptics->library);
if (error) {
dev_err(dev, "Can't fetch 'library-sel' property: %d\n", error);
return error;
}
if (haptics->library < DRV260X_LIB_EMPTY ||
haptics->library > DRV260X_ERM_LIB_F) {
dev_err(dev,
"Library value is invalid: %i\n", haptics->library);
return -EINVAL;
}
if (haptics->mode == DRV260X_LRA_MODE &&
haptics->library != DRV260X_LIB_EMPTY &&
haptics->library != DRV260X_LIB_LRA) {
dev_err(dev, "LRA Mode with ERM Library mismatch\n");
return -EINVAL;
}
if (haptics->mode == DRV260X_ERM_MODE &&
(haptics->library == DRV260X_LIB_EMPTY ||
haptics->library == DRV260X_LIB_LRA)) {
dev_err(dev, "ERM Mode with LRA Library mismatch\n");
return -EINVAL;
}
error = device_property_read_u32(dev, "vib-rated-mv", &voltage);
haptics->rated_voltage = error ? DRV260X_DEF_RATED_VOLT :
drv260x_calculate_voltage(voltage);
error = device_property_read_u32(dev, "vib-overdrive-mv", &voltage);
haptics->overdrive_voltage = error ? DRV260X_DEF_OD_CLAMP_VOLT :
drv260x_calculate_voltage(voltage);
haptics->regulator = devm_regulator_get(dev, "vbat");
if (IS_ERR(haptics->regulator)) {
error = PTR_ERR(haptics->regulator);
dev_err(dev, "unable to get regulator, error: %d\n", error);
return error;
}
haptics->enable_gpio = devm_gpiod_get_optional(dev, "enable",
GPIOD_OUT_HIGH);
if (IS_ERR(haptics->enable_gpio))
return PTR_ERR(haptics->enable_gpio);
haptics->input_dev = devm_input_allocate_device(dev);
if (!haptics->input_dev) {
dev_err(dev, "Failed to allocate input device\n");
return -ENOMEM;
}
haptics->input_dev->name = "drv260x:haptics";
haptics->input_dev->close = drv260x_close;
input_set_drvdata(haptics->input_dev, haptics);
input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
error = input_ff_create_memless(haptics->input_dev, NULL,
drv260x_haptics_play);
if (error) {
dev_err(dev, "input_ff_create() failed: %d\n", error);
return error;
}
INIT_WORK(&haptics->work, drv260x_worker);
haptics->client = client;
i2c_set_clientdata(client, haptics);
haptics->regmap = devm_regmap_init_i2c(client, &drv260x_regmap_config);
if (IS_ERR(haptics->regmap)) {
error = PTR_ERR(haptics->regmap);
dev_err(dev, "Failed to allocate register map: %d\n", error);
return error;
}
error = drv260x_init(haptics);
if (error) {
dev_err(dev, "Device init failed: %d\n", error);
return error;
}
error = input_register_device(haptics->input_dev);
if (error) {
dev_err(dev, "couldn't register input device: %d\n", error);
return error;
}
return 0;
}
static int __maybe_unused drv260x_suspend(struct device *dev)
{
struct drv260x_data *haptics = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&haptics->input_dev->mutex);
if (haptics->input_dev->users) {
ret = regmap_update_bits(haptics->regmap,
DRV260X_MODE,
DRV260X_STANDBY_MASK,
DRV260X_STANDBY);
if (ret) {
dev_err(dev, "Failed to set standby mode\n");
goto out;
}
gpiod_set_value(haptics->enable_gpio, 0);
ret = regulator_disable(haptics->regulator);
if (ret) {
dev_err(dev, "Failed to disable regulator\n");
regmap_update_bits(haptics->regmap,
DRV260X_MODE,
DRV260X_STANDBY_MASK, 0);
}
}
out:
mutex_unlock(&haptics->input_dev->mutex);
return ret;
}
static int __maybe_unused drv260x_resume(struct device *dev)
{
struct drv260x_data *haptics = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&haptics->input_dev->mutex);
if (haptics->input_dev->users) {
ret = regulator_enable(haptics->regulator);
if (ret) {
dev_err(dev, "Failed to enable regulator\n");
goto out;
}
ret = regmap_update_bits(haptics->regmap,
DRV260X_MODE,
DRV260X_STANDBY_MASK, 0);
if (ret) {
dev_err(dev, "Failed to unset standby mode\n");
regulator_disable(haptics->regulator);
goto out;
}
gpiod_set_value(haptics->enable_gpio, 1);
}
out:
mutex_unlock(&haptics->input_dev->mutex);
return ret;
}
static SIMPLE_DEV_PM_OPS(drv260x_pm_ops, drv260x_suspend, drv260x_resume);
static const struct i2c_device_id drv260x_id[] = {
{ "drv2605l", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, drv260x_id);
static const struct of_device_id drv260x_of_match[] = {
{ .compatible = "ti,drv2604", },
{ .compatible = "ti,drv2604l", },
{ .compatible = "ti,drv2605", },
{ .compatible = "ti,drv2605l", },
{ }
};
MODULE_DEVICE_TABLE(of, drv260x_of_match);
static struct i2c_driver drv260x_driver = {
.probe = drv260x_probe,
.driver = {
.name = "drv260x-haptics",
.of_match_table = drv260x_of_match,
.pm = &drv260x_pm_ops,
},
.id_table = drv260x_id,
};
module_i2c_driver(drv260x_driver);
MODULE_DESCRIPTION("TI DRV260x haptics driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+321
View File
@@ -0,0 +1,321 @@
/*
* DRV2665 haptics driver family
*
* Author: Dan Murphy <dmurphy@ti.com>
*
* Copyright: (C) 2015 Texas Instruments, Inc.
*
* 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.
*/
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/regulator/consumer.h>
/* Contol registers */
#define DRV2665_STATUS 0x00
#define DRV2665_CTRL_1 0x01
#define DRV2665_CTRL_2 0x02
#define DRV2665_FIFO 0x0b
/* Status Register */
#define DRV2665_FIFO_FULL BIT(0)
#define DRV2665_FIFO_EMPTY BIT(1)
/* Control 1 Register */
#define DRV2665_25_VPP_GAIN 0x00
#define DRV2665_50_VPP_GAIN 0x01
#define DRV2665_75_VPP_GAIN 0x02
#define DRV2665_100_VPP_GAIN 0x03
#define DRV2665_DIGITAL_IN 0xfc
#define DRV2665_ANALOG_IN BIT(2)
/* Control 2 Register */
#define DRV2665_BOOST_EN BIT(1)
#define DRV2665_STANDBY BIT(6)
#define DRV2665_DEV_RST BIT(7)
#define DRV2665_5_MS_IDLE_TOUT 0x00
#define DRV2665_10_MS_IDLE_TOUT 0x04
#define DRV2665_15_MS_IDLE_TOUT 0x08
#define DRV2665_20_MS_IDLE_TOUT 0x0c
/**
* struct drv2665_data -
* @input_dev - Pointer to the input device
* @client - Pointer to the I2C client
* @regmap - Register map of the device
* @work - Work item used to off load the enable/disable of the vibration
* @regulator - Pointer to the regulator for the IC
*/
struct drv2665_data {
struct input_dev *input_dev;
struct i2c_client *client;
struct regmap *regmap;
struct work_struct work;
struct regulator *regulator;
};
/* 8kHz Sine wave to stream to the FIFO */
static const u8 drv2665_sine_wave_form[] = {
0x00, 0x10, 0x20, 0x2e, 0x3c, 0x48, 0x53, 0x5b, 0x61, 0x65, 0x66,
0x65, 0x61, 0x5b, 0x53, 0x48, 0x3c, 0x2e, 0x20, 0x10,
0x00, 0xf0, 0xe0, 0xd2, 0xc4, 0xb8, 0xad, 0xa5, 0x9f, 0x9b, 0x9a,
0x9b, 0x9f, 0xa5, 0xad, 0xb8, 0xc4, 0xd2, 0xe0, 0xf0, 0x00,
};
static const struct reg_default drv2665_reg_defs[] = {
{ DRV2665_STATUS, 0x02 },
{ DRV2665_CTRL_1, 0x28 },
{ DRV2665_CTRL_2, 0x40 },
{ DRV2665_FIFO, 0x00 },
};
static void drv2665_worker(struct work_struct *work)
{
struct drv2665_data *haptics =
container_of(work, struct drv2665_data, work);
unsigned int read_buf;
int error;
error = regmap_read(haptics->regmap, DRV2665_STATUS, &read_buf);
if (error) {
dev_err(&haptics->client->dev,
"Failed to read status: %d\n", error);
return;
}
if (read_buf & DRV2665_FIFO_EMPTY) {
error = regmap_bulk_write(haptics->regmap,
DRV2665_FIFO,
drv2665_sine_wave_form,
ARRAY_SIZE(drv2665_sine_wave_form));
if (error) {
dev_err(&haptics->client->dev,
"Failed to write FIFO: %d\n", error);
return;
}
}
}
static int drv2665_haptics_play(struct input_dev *input, void *data,
struct ff_effect *effect)
{
struct drv2665_data *haptics = input_get_drvdata(input);
schedule_work(&haptics->work);
return 0;
}
static void drv2665_close(struct input_dev *input)
{
struct drv2665_data *haptics = input_get_drvdata(input);
int error;
cancel_work_sync(&haptics->work);
error = regmap_update_bits(haptics->regmap, DRV2665_CTRL_2,
DRV2665_STANDBY, DRV2665_STANDBY);
if (error)
dev_err(&haptics->client->dev,
"Failed to enter standby mode: %d\n", error);
}
static const struct reg_sequence drv2665_init_regs[] = {
{ DRV2665_CTRL_2, 0 | DRV2665_10_MS_IDLE_TOUT },
{ DRV2665_CTRL_1, DRV2665_25_VPP_GAIN },
};
static int drv2665_init(struct drv2665_data *haptics)
{
int error;
error = regmap_register_patch(haptics->regmap,
drv2665_init_regs,
ARRAY_SIZE(drv2665_init_regs));
if (error) {
dev_err(&haptics->client->dev,
"Failed to write init registers: %d\n",
error);
return error;
}
return 0;
}
static const struct regmap_config drv2665_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = DRV2665_FIFO,
.reg_defaults = drv2665_reg_defs,
.num_reg_defaults = ARRAY_SIZE(drv2665_reg_defs),
.cache_type = REGCACHE_NONE,
};
static int drv2665_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct drv2665_data *haptics;
int error;
haptics = devm_kzalloc(&client->dev, sizeof(*haptics), GFP_KERNEL);
if (!haptics)
return -ENOMEM;
haptics->regulator = devm_regulator_get(&client->dev, "vbat");
if (IS_ERR(haptics->regulator)) {
error = PTR_ERR(haptics->regulator);
dev_err(&client->dev,
"unable to get regulator, error: %d\n", error);
return error;
}
haptics->input_dev = devm_input_allocate_device(&client->dev);
if (!haptics->input_dev) {
dev_err(&client->dev, "Failed to allocate input device\n");
return -ENOMEM;
}
haptics->input_dev->name = "drv2665:haptics";
haptics->input_dev->dev.parent = client->dev.parent;
haptics->input_dev->close = drv2665_close;
input_set_drvdata(haptics->input_dev, haptics);
input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
error = input_ff_create_memless(haptics->input_dev, NULL,
drv2665_haptics_play);
if (error) {
dev_err(&client->dev, "input_ff_create() failed: %d\n",
error);
return error;
}
INIT_WORK(&haptics->work, drv2665_worker);
haptics->client = client;
i2c_set_clientdata(client, haptics);
haptics->regmap = devm_regmap_init_i2c(client, &drv2665_regmap_config);
if (IS_ERR(haptics->regmap)) {
error = PTR_ERR(haptics->regmap);
dev_err(&client->dev, "Failed to allocate register map: %d\n",
error);
return error;
}
error = drv2665_init(haptics);
if (error) {
dev_err(&client->dev, "Device init failed: %d\n", error);
return error;
}
error = input_register_device(haptics->input_dev);
if (error) {
dev_err(&client->dev, "couldn't register input device: %d\n",
error);
return error;
}
return 0;
}
static int __maybe_unused drv2665_suspend(struct device *dev)
{
struct drv2665_data *haptics = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&haptics->input_dev->mutex);
if (haptics->input_dev->users) {
ret = regmap_update_bits(haptics->regmap, DRV2665_CTRL_2,
DRV2665_STANDBY, DRV2665_STANDBY);
if (ret) {
dev_err(dev, "Failed to set standby mode\n");
regulator_disable(haptics->regulator);
goto out;
}
ret = regulator_disable(haptics->regulator);
if (ret) {
dev_err(dev, "Failed to disable regulator\n");
regmap_update_bits(haptics->regmap,
DRV2665_CTRL_2,
DRV2665_STANDBY, 0);
}
}
out:
mutex_unlock(&haptics->input_dev->mutex);
return ret;
}
static int __maybe_unused drv2665_resume(struct device *dev)
{
struct drv2665_data *haptics = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&haptics->input_dev->mutex);
if (haptics->input_dev->users) {
ret = regulator_enable(haptics->regulator);
if (ret) {
dev_err(dev, "Failed to enable regulator\n");
goto out;
}
ret = regmap_update_bits(haptics->regmap, DRV2665_CTRL_2,
DRV2665_STANDBY, 0);
if (ret) {
dev_err(dev, "Failed to unset standby mode\n");
regulator_disable(haptics->regulator);
goto out;
}
}
out:
mutex_unlock(&haptics->input_dev->mutex);
return ret;
}
static SIMPLE_DEV_PM_OPS(drv2665_pm_ops, drv2665_suspend, drv2665_resume);
static const struct i2c_device_id drv2665_id[] = {
{ "drv2665", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, drv2665_id);
#ifdef CONFIG_OF
static const struct of_device_id drv2665_of_match[] = {
{ .compatible = "ti,drv2665", },
{ }
};
MODULE_DEVICE_TABLE(of, drv2665_of_match);
#endif
static struct i2c_driver drv2665_driver = {
.probe = drv2665_probe,
.driver = {
.name = "drv2665-haptics",
.of_match_table = of_match_ptr(drv2665_of_match),
.pm = &drv2665_pm_ops,
},
.id_table = drv2665_id,
};
module_i2c_driver(drv2665_driver);
MODULE_DESCRIPTION("TI DRV2665 haptics driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+496
View File
@@ -0,0 +1,496 @@
/*
* DRV2667 haptics driver family
*
* Author: Dan Murphy <dmurphy@ti.com>
*
* Copyright: (C) 2014 Texas Instruments, Inc.
*
* 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.
*/
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/regulator/consumer.h>
/* Contol registers */
#define DRV2667_STATUS 0x00
#define DRV2667_CTRL_1 0x01
#define DRV2667_CTRL_2 0x02
/* Waveform sequencer */
#define DRV2667_WV_SEQ_0 0x03
#define DRV2667_WV_SEQ_1 0x04
#define DRV2667_WV_SEQ_2 0x05
#define DRV2667_WV_SEQ_3 0x06
#define DRV2667_WV_SEQ_4 0x07
#define DRV2667_WV_SEQ_5 0x08
#define DRV2667_WV_SEQ_6 0x09
#define DRV2667_WV_SEQ_7 0x0A
#define DRV2667_FIFO 0x0B
#define DRV2667_PAGE 0xFF
#define DRV2667_MAX_REG DRV2667_PAGE
#define DRV2667_PAGE_0 0x00
#define DRV2667_PAGE_1 0x01
#define DRV2667_PAGE_2 0x02
#define DRV2667_PAGE_3 0x03
#define DRV2667_PAGE_4 0x04
#define DRV2667_PAGE_5 0x05
#define DRV2667_PAGE_6 0x06
#define DRV2667_PAGE_7 0x07
#define DRV2667_PAGE_8 0x08
/* RAM fields */
#define DRV2667_RAM_HDR_SZ 0x0
/* RAM Header addresses */
#define DRV2667_RAM_START_HI 0x01
#define DRV2667_RAM_START_LO 0x02
#define DRV2667_RAM_STOP_HI 0x03
#define DRV2667_RAM_STOP_LO 0x04
#define DRV2667_RAM_REPEAT_CT 0x05
/* RAM data addresses */
#define DRV2667_RAM_AMP 0x06
#define DRV2667_RAM_FREQ 0x07
#define DRV2667_RAM_DURATION 0x08
#define DRV2667_RAM_ENVELOPE 0x09
/* Control 1 Register */
#define DRV2667_25_VPP_GAIN 0x00
#define DRV2667_50_VPP_GAIN 0x01
#define DRV2667_75_VPP_GAIN 0x02
#define DRV2667_100_VPP_GAIN 0x03
#define DRV2667_DIGITAL_IN 0xfc
#define DRV2667_ANALOG_IN (1 << 2)
/* Control 2 Register */
#define DRV2667_GO (1 << 0)
#define DRV2667_STANDBY (1 << 6)
#define DRV2667_DEV_RST (1 << 7)
/* RAM Envelope settings */
#define DRV2667_NO_ENV 0x00
#define DRV2667_32_MS_ENV 0x01
#define DRV2667_64_MS_ENV 0x02
#define DRV2667_96_MS_ENV 0x03
#define DRV2667_128_MS_ENV 0x04
#define DRV2667_160_MS_ENV 0x05
#define DRV2667_192_MS_ENV 0x06
#define DRV2667_224_MS_ENV 0x07
#define DRV2667_256_MS_ENV 0x08
#define DRV2667_512_MS_ENV 0x09
#define DRV2667_768_MS_ENV 0x0a
#define DRV2667_1024_MS_ENV 0x0b
#define DRV2667_1280_MS_ENV 0x0c
#define DRV2667_1536_MS_ENV 0x0d
#define DRV2667_1792_MS_ENV 0x0e
#define DRV2667_2048_MS_ENV 0x0f
/**
* struct drv2667_data -
* @input_dev - Pointer to the input device
* @client - Pointer to the I2C client
* @regmap - Register map of the device
* @work - Work item used to off load the enable/disable of the vibration
* @regulator - Pointer to the regulator for the IC
* @magnitude - Magnitude of the vibration event
**/
struct drv2667_data {
struct input_dev *input_dev;
struct i2c_client *client;
struct regmap *regmap;
struct work_struct work;
struct regulator *regulator;
u32 page;
u32 magnitude;
u32 frequency;
};
static const struct reg_default drv2667_reg_defs[] = {
{ DRV2667_STATUS, 0x02 },
{ DRV2667_CTRL_1, 0x28 },
{ DRV2667_CTRL_2, 0x40 },
{ DRV2667_WV_SEQ_0, 0x00 },
{ DRV2667_WV_SEQ_1, 0x00 },
{ DRV2667_WV_SEQ_2, 0x00 },
{ DRV2667_WV_SEQ_3, 0x00 },
{ DRV2667_WV_SEQ_4, 0x00 },
{ DRV2667_WV_SEQ_5, 0x00 },
{ DRV2667_WV_SEQ_6, 0x00 },
{ DRV2667_WV_SEQ_7, 0x00 },
{ DRV2667_FIFO, 0x00 },
{ DRV2667_PAGE, 0x00 },
};
static int drv2667_set_waveform_freq(struct drv2667_data *haptics)
{
unsigned int read_buf;
int freq;
int error;
/* Per the data sheet:
* Sinusoid Frequency (Hz) = 7.8125 x Frequency
*/
freq = (haptics->frequency * 1000) / 78125;
if (freq <= 0) {
dev_err(&haptics->client->dev,
"ERROR: Frequency calculated to %i\n", freq);
return -EINVAL;
}
error = regmap_read(haptics->regmap, DRV2667_PAGE, &read_buf);
if (error) {
dev_err(&haptics->client->dev,
"Failed to read the page number: %d\n", error);
return -EIO;
}
if (read_buf == DRV2667_PAGE_0 ||
haptics->page != read_buf) {
error = regmap_write(haptics->regmap,
DRV2667_PAGE, haptics->page);
if (error) {
dev_err(&haptics->client->dev,
"Failed to set the page: %d\n", error);
return -EIO;
}
}
error = regmap_write(haptics->regmap, DRV2667_RAM_FREQ, freq);
if (error)
dev_err(&haptics->client->dev,
"Failed to set the frequency: %d\n", error);
/* Reset back to original page */
if (read_buf == DRV2667_PAGE_0 ||
haptics->page != read_buf) {
error = regmap_write(haptics->regmap, DRV2667_PAGE, read_buf);
if (error) {
dev_err(&haptics->client->dev,
"Failed to set the page: %d\n", error);
return -EIO;
}
}
return error;
}
static void drv2667_worker(struct work_struct *work)
{
struct drv2667_data *haptics = container_of(work, struct drv2667_data, work);
int error;
if (haptics->magnitude) {
error = regmap_write(haptics->regmap,
DRV2667_PAGE, haptics->page);
if (error) {
dev_err(&haptics->client->dev,
"Failed to set the page: %d\n", error);
return;
}
error = regmap_write(haptics->regmap, DRV2667_RAM_AMP,
haptics->magnitude);
if (error) {
dev_err(&haptics->client->dev,
"Failed to set the amplitude: %d\n", error);
return;
}
error = regmap_write(haptics->regmap,
DRV2667_PAGE, DRV2667_PAGE_0);
if (error) {
dev_err(&haptics->client->dev,
"Failed to set the page: %d\n", error);
return;
}
error = regmap_write(haptics->regmap,
DRV2667_CTRL_2, DRV2667_GO);
if (error) {
dev_err(&haptics->client->dev,
"Failed to set the GO bit: %d\n", error);
}
} else {
error = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
DRV2667_GO, 0);
if (error) {
dev_err(&haptics->client->dev,
"Failed to unset the GO bit: %d\n", error);
}
}
}
static int drv2667_haptics_play(struct input_dev *input, void *data,
struct ff_effect *effect)
{
struct drv2667_data *haptics = input_get_drvdata(input);
if (effect->u.rumble.strong_magnitude > 0)
haptics->magnitude = effect->u.rumble.strong_magnitude;
else if (effect->u.rumble.weak_magnitude > 0)
haptics->magnitude = effect->u.rumble.weak_magnitude;
else
haptics->magnitude = 0;
schedule_work(&haptics->work);
return 0;
}
static void drv2667_close(struct input_dev *input)
{
struct drv2667_data *haptics = input_get_drvdata(input);
int error;
cancel_work_sync(&haptics->work);
error = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
DRV2667_STANDBY, DRV2667_STANDBY);
if (error)
dev_err(&haptics->client->dev,
"Failed to enter standby mode: %d\n", error);
}
static const struct reg_sequence drv2667_init_regs[] = {
{ DRV2667_CTRL_2, 0 },
{ DRV2667_CTRL_1, DRV2667_25_VPP_GAIN },
{ DRV2667_WV_SEQ_0, 1 },
{ DRV2667_WV_SEQ_1, 0 }
};
static const struct reg_sequence drv2667_page1_init[] = {
{ DRV2667_RAM_HDR_SZ, 0x05 },
{ DRV2667_RAM_START_HI, 0x80 },
{ DRV2667_RAM_START_LO, 0x06 },
{ DRV2667_RAM_STOP_HI, 0x00 },
{ DRV2667_RAM_STOP_LO, 0x09 },
{ DRV2667_RAM_REPEAT_CT, 0 },
{ DRV2667_RAM_DURATION, 0x05 },
{ DRV2667_RAM_ENVELOPE, DRV2667_NO_ENV },
{ DRV2667_RAM_AMP, 0x60 },
};
static int drv2667_init(struct drv2667_data *haptics)
{
int error;
/* Set default haptic frequency to 195Hz on Page 1*/
haptics->frequency = 195;
haptics->page = DRV2667_PAGE_1;
error = regmap_register_patch(haptics->regmap,
drv2667_init_regs,
ARRAY_SIZE(drv2667_init_regs));
if (error) {
dev_err(&haptics->client->dev,
"Failed to write init registers: %d\n",
error);
return error;
}
error = regmap_write(haptics->regmap, DRV2667_PAGE, haptics->page);
if (error) {
dev_err(&haptics->client->dev, "Failed to set page: %d\n",
error);
goto error_out;
}
error = drv2667_set_waveform_freq(haptics);
if (error)
goto error_page;
error = regmap_register_patch(haptics->regmap,
drv2667_page1_init,
ARRAY_SIZE(drv2667_page1_init));
if (error) {
dev_err(&haptics->client->dev,
"Failed to write page registers: %d\n",
error);
return error;
}
error = regmap_write(haptics->regmap, DRV2667_PAGE, DRV2667_PAGE_0);
return error;
error_page:
regmap_write(haptics->regmap, DRV2667_PAGE, DRV2667_PAGE_0);
error_out:
return error;
}
static const struct regmap_config drv2667_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = DRV2667_MAX_REG,
.reg_defaults = drv2667_reg_defs,
.num_reg_defaults = ARRAY_SIZE(drv2667_reg_defs),
.cache_type = REGCACHE_NONE,
};
static int drv2667_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct drv2667_data *haptics;
int error;
haptics = devm_kzalloc(&client->dev, sizeof(*haptics), GFP_KERNEL);
if (!haptics)
return -ENOMEM;
haptics->regulator = devm_regulator_get(&client->dev, "vbat");
if (IS_ERR(haptics->regulator)) {
error = PTR_ERR(haptics->regulator);
dev_err(&client->dev,
"unable to get regulator, error: %d\n", error);
return error;
}
haptics->input_dev = devm_input_allocate_device(&client->dev);
if (!haptics->input_dev) {
dev_err(&client->dev, "Failed to allocate input device\n");
return -ENOMEM;
}
haptics->input_dev->name = "drv2667:haptics";
haptics->input_dev->dev.parent = client->dev.parent;
haptics->input_dev->close = drv2667_close;
input_set_drvdata(haptics->input_dev, haptics);
input_set_capability(haptics->input_dev, EV_FF, FF_RUMBLE);
error = input_ff_create_memless(haptics->input_dev, NULL,
drv2667_haptics_play);
if (error) {
dev_err(&client->dev, "input_ff_create() failed: %d\n",
error);
return error;
}
INIT_WORK(&haptics->work, drv2667_worker);
haptics->client = client;
i2c_set_clientdata(client, haptics);
haptics->regmap = devm_regmap_init_i2c(client, &drv2667_regmap_config);
if (IS_ERR(haptics->regmap)) {
error = PTR_ERR(haptics->regmap);
dev_err(&client->dev, "Failed to allocate register map: %d\n",
error);
return error;
}
error = drv2667_init(haptics);
if (error) {
dev_err(&client->dev, "Device init failed: %d\n", error);
return error;
}
error = input_register_device(haptics->input_dev);
if (error) {
dev_err(&client->dev, "couldn't register input device: %d\n",
error);
return error;
}
return 0;
}
static int __maybe_unused drv2667_suspend(struct device *dev)
{
struct drv2667_data *haptics = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&haptics->input_dev->mutex);
if (haptics->input_dev->users) {
ret = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
DRV2667_STANDBY, DRV2667_STANDBY);
if (ret) {
dev_err(dev, "Failed to set standby mode\n");
regulator_disable(haptics->regulator);
goto out;
}
ret = regulator_disable(haptics->regulator);
if (ret) {
dev_err(dev, "Failed to disable regulator\n");
regmap_update_bits(haptics->regmap,
DRV2667_CTRL_2,
DRV2667_STANDBY, 0);
}
}
out:
mutex_unlock(&haptics->input_dev->mutex);
return ret;
}
static int __maybe_unused drv2667_resume(struct device *dev)
{
struct drv2667_data *haptics = dev_get_drvdata(dev);
int ret = 0;
mutex_lock(&haptics->input_dev->mutex);
if (haptics->input_dev->users) {
ret = regulator_enable(haptics->regulator);
if (ret) {
dev_err(dev, "Failed to enable regulator\n");
goto out;
}
ret = regmap_update_bits(haptics->regmap, DRV2667_CTRL_2,
DRV2667_STANDBY, 0);
if (ret) {
dev_err(dev, "Failed to unset standby mode\n");
regulator_disable(haptics->regulator);
goto out;
}
}
out:
mutex_unlock(&haptics->input_dev->mutex);
return ret;
}
static SIMPLE_DEV_PM_OPS(drv2667_pm_ops, drv2667_suspend, drv2667_resume);
static const struct i2c_device_id drv2667_id[] = {
{ "drv2667", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, drv2667_id);
#ifdef CONFIG_OF
static const struct of_device_id drv2667_of_match[] = {
{ .compatible = "ti,drv2667", },
{ }
};
MODULE_DEVICE_TABLE(of, drv2667_of_match);
#endif
static struct i2c_driver drv2667_driver = {
.probe = drv2667_probe,
.driver = {
.name = "drv2667-haptics",
.of_match_table = of_match_ptr(drv2667_of_match),
.pm = &drv2667_pm_ops,
},
.id_table = drv2667_id,
};
module_i2c_driver(drv2667_driver);
MODULE_DESCRIPTION("TI DRV2667 haptics driver");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+149
View File
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2014, National Instruments Corp. All rights reserved.
*
* Driver for NI Ettus Research USRP E3x0 Button 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; 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.
*/
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/of.h>
#include <linux/slab.h>
static irqreturn_t e3x0_button_release_handler(int irq, void *data)
{
struct input_dev *idev = data;
input_report_key(idev, KEY_POWER, 0);
input_sync(idev);
return IRQ_HANDLED;
}
static irqreturn_t e3x0_button_press_handler(int irq, void *data)
{
struct input_dev *idev = data;
input_report_key(idev, KEY_POWER, 1);
pm_wakeup_event(idev->dev.parent, 0);
input_sync(idev);
return IRQ_HANDLED;
}
static int __maybe_unused e3x0_button_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
if (device_may_wakeup(dev))
enable_irq_wake(platform_get_irq_byname(pdev, "press"));
return 0;
}
static int __maybe_unused e3x0_button_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
if (device_may_wakeup(dev))
disable_irq_wake(platform_get_irq_byname(pdev, "press"));
return 0;
}
static SIMPLE_DEV_PM_OPS(e3x0_button_pm_ops,
e3x0_button_suspend, e3x0_button_resume);
static int e3x0_button_probe(struct platform_device *pdev)
{
struct input_dev *input;
int irq_press, irq_release;
int error;
irq_press = platform_get_irq_byname(pdev, "press");
if (irq_press < 0) {
dev_err(&pdev->dev, "No IRQ for 'press', error=%d\n",
irq_press);
return irq_press;
}
irq_release = platform_get_irq_byname(pdev, "release");
if (irq_release < 0) {
dev_err(&pdev->dev, "No IRQ for 'release', error=%d\n",
irq_release);
return irq_release;
}
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
input->name = "NI Ettus Research USRP E3x0 Button Driver";
input->phys = "e3x0_button/input0";
input->dev.parent = &pdev->dev;
input_set_capability(input, EV_KEY, KEY_POWER);
error = devm_request_irq(&pdev->dev, irq_press,
e3x0_button_press_handler, 0,
"e3x0-button", input);
if (error) {
dev_err(&pdev->dev, "Failed to request 'press' IRQ#%d: %d\n",
irq_press, error);
return error;
}
error = devm_request_irq(&pdev->dev, irq_release,
e3x0_button_release_handler, 0,
"e3x0-button", input);
if (error) {
dev_err(&pdev->dev, "Failed to request 'release' IRQ#%d: %d\n",
irq_release, error);
return error;
}
error = input_register_device(input);
if (error) {
dev_err(&pdev->dev, "Can't register input device: %d\n", error);
return error;
}
device_init_wakeup(&pdev->dev, 1);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id e3x0_button_match[] = {
{ .compatible = "ettus,e3x0-button", },
{ }
};
MODULE_DEVICE_TABLE(of, e3x0_button_match);
#endif
static struct platform_driver e3x0_button_driver = {
.driver = {
.name = "e3x0-button",
.of_match_table = of_match_ptr(e3x0_button_match),
.pm = &e3x0_button_pm_ops,
},
.probe = e3x0_button_probe,
};
module_platform_driver(e3x0_button_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Moritz Fischer <moritz.fischer@ettus.com>");
MODULE_DESCRIPTION("NI Ettus Research USRP E3x0 Button driver");
MODULE_ALIAS("platform:e3x0-button");
+284
View File
@@ -0,0 +1,284 @@
/*
* 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 = dev_get_platdata(&client->dev);
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;
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;
}
static int __maybe_unused 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 __maybe_unused 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;
}
static SIMPLE_DEV_PM_OPS(gp2a_pm, gp2a_suspend, gp2a_resume);
static const struct i2c_device_id gp2a_i2c_id[] = {
{ GP2A_I2C_NAME, 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, gp2a_i2c_id);
static struct i2c_driver gp2a_i2c_driver = {
.driver = {
.name = GP2A_I2C_NAME,
.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");
+118
View File
@@ -0,0 +1,118 @@
/*
* Generic GPIO beeper driver
*
* Copyright (C) 2013-2014 Alexander Shiyan <shc_work@mail.ru>
*
* 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/input.h>
#include <linux/module.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>
#define BEEPER_MODNAME "gpio-beeper"
struct gpio_beeper {
struct work_struct work;
struct gpio_desc *desc;
bool beeping;
};
static void gpio_beeper_toggle(struct gpio_beeper *beep, bool on)
{
gpiod_set_value_cansleep(beep->desc, on);
}
static void gpio_beeper_work(struct work_struct *work)
{
struct gpio_beeper *beep = container_of(work, struct gpio_beeper, work);
gpio_beeper_toggle(beep, beep->beeping);
}
static int gpio_beeper_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
struct gpio_beeper *beep = input_get_drvdata(dev);
if (type != EV_SND || code != SND_BELL)
return -ENOTSUPP;
if (value < 0)
return -EINVAL;
beep->beeping = value;
/* Schedule work to actually turn the beeper on or off */
schedule_work(&beep->work);
return 0;
}
static void gpio_beeper_close(struct input_dev *input)
{
struct gpio_beeper *beep = input_get_drvdata(input);
cancel_work_sync(&beep->work);
gpio_beeper_toggle(beep, false);
}
static int gpio_beeper_probe(struct platform_device *pdev)
{
struct gpio_beeper *beep;
struct input_dev *input;
beep = devm_kzalloc(&pdev->dev, sizeof(*beep), GFP_KERNEL);
if (!beep)
return -ENOMEM;
beep->desc = devm_gpiod_get(&pdev->dev, NULL, GPIOD_OUT_LOW);
if (IS_ERR(beep->desc))
return PTR_ERR(beep->desc);
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
INIT_WORK(&beep->work, gpio_beeper_work);
input->name = pdev->name;
input->id.bustype = BUS_HOST;
input->id.vendor = 0x0001;
input->id.product = 0x0001;
input->id.version = 0x0100;
input->close = gpio_beeper_close;
input->event = gpio_beeper_event;
input_set_capability(input, EV_SND, SND_BELL);
input_set_drvdata(input, beep);
return input_register_device(input);
}
#ifdef CONFIG_OF
static const struct of_device_id gpio_beeper_of_match[] = {
{ .compatible = BEEPER_MODNAME, },
{ }
};
MODULE_DEVICE_TABLE(of, gpio_beeper_of_match);
#endif
static struct platform_driver gpio_beeper_platform_driver = {
.driver = {
.name = BEEPER_MODNAME,
.of_match_table = of_match_ptr(gpio_beeper_of_match),
},
.probe = gpio_beeper_probe,
};
module_platform_driver(gpio_beeper_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>");
MODULE_DESCRIPTION("Generic GPIO beeper driver");
+136
View File
@@ -0,0 +1,136 @@
/*
* Copyright (C) 2016 Texas Instruments Incorporated - http://www.ti.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 program is distributed "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* A generic driver to read multiple gpio lines and translate the
* encoded numeric value into an input event.
*/
#include <linux/device.h>
#include <linux/gpio/consumer.h>
#include <linux/input.h>
#include <linux/input-polldev.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
struct gpio_decoder {
struct input_polled_dev *poll_dev;
struct gpio_descs *input_gpios;
struct device *dev;
u32 axis;
u32 last_stable;
};
static int gpio_decoder_get_gpios_state(struct gpio_decoder *decoder)
{
struct gpio_descs *gpios = decoder->input_gpios;
unsigned int ret = 0;
int i, val;
for (i = 0; i < gpios->ndescs; i++) {
val = gpiod_get_value_cansleep(gpios->desc[i]);
if (val < 0) {
dev_err(decoder->dev,
"Error reading gpio %d: %d\n",
desc_to_gpio(gpios->desc[i]), val);
return val;
}
val = !!val;
ret = (ret << 1) | val;
}
return ret;
}
static void gpio_decoder_poll_gpios(struct input_polled_dev *poll_dev)
{
struct gpio_decoder *decoder = poll_dev->private;
int state;
state = gpio_decoder_get_gpios_state(decoder);
if (state >= 0 && state != decoder->last_stable) {
input_report_abs(poll_dev->input, decoder->axis, state);
input_sync(poll_dev->input);
decoder->last_stable = state;
}
}
static int gpio_decoder_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct gpio_decoder *decoder;
struct input_polled_dev *poll_dev;
u32 max;
int err;
decoder = devm_kzalloc(dev, sizeof(struct gpio_decoder), GFP_KERNEL);
if (!decoder)
return -ENOMEM;
device_property_read_u32(dev, "linux,axis", &decoder->axis);
decoder->input_gpios = devm_gpiod_get_array(dev, NULL, GPIOD_IN);
if (IS_ERR(decoder->input_gpios)) {
dev_err(dev, "unable to acquire input gpios\n");
return PTR_ERR(decoder->input_gpios);
}
if (decoder->input_gpios->ndescs < 2) {
dev_err(dev, "not enough gpios found\n");
return -EINVAL;
}
if (device_property_read_u32(dev, "decoder-max-value", &max))
max = (1U << decoder->input_gpios->ndescs) - 1;
decoder->dev = dev;
poll_dev = devm_input_allocate_polled_device(decoder->dev);
if (!poll_dev)
return -ENOMEM;
poll_dev->private = decoder;
poll_dev->poll = gpio_decoder_poll_gpios;
decoder->poll_dev = poll_dev;
poll_dev->input->name = pdev->name;
poll_dev->input->id.bustype = BUS_HOST;
input_set_abs_params(poll_dev->input, decoder->axis, 0, max, 0, 0);
err = input_register_polled_device(poll_dev);
if (err) {
dev_err(dev, "failed to register polled device\n");
return err;
}
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id gpio_decoder_of_match[] = {
{ .compatible = "gpio-decoder", },
{ },
};
MODULE_DEVICE_TABLE(of, gpio_decoder_of_match);
#endif
static struct platform_driver gpio_decoder_driver = {
.probe = gpio_decoder_probe,
.driver = {
.name = "gpio-decoder",
.of_match_table = of_match_ptr(gpio_decoder_of_match),
}
};
module_platform_driver(gpio_decoder_driver);
MODULE_DESCRIPTION("GPIO decoder input driver");
MODULE_AUTHOR("Vignesh R <vigneshr@ti.com>");
MODULE_LICENSE("GPL v2");
+133
View File
@@ -0,0 +1,133 @@
/*
* Hisilicon PMIC powerkey driver
*
* Copyright (C) 2013 Hisilicon Ltd.
* Copyright (C) 2015, 2016 Linaro Ltd.
*
* 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/platform_device.h>
#include <linux/interrupt.h>
#include <linux/reboot.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_irq.h>
#include <linux/input.h>
#include <linux/slab.h>
/* the held interrupt will trigger after 4 seconds */
#define MAX_HELD_TIME (4 * MSEC_PER_SEC)
static irqreturn_t hi65xx_power_press_isr(int irq, void *q)
{
struct input_dev *input = q;
pm_wakeup_event(input->dev.parent, MAX_HELD_TIME);
input_report_key(input, KEY_POWER, 1);
input_sync(input);
return IRQ_HANDLED;
}
static irqreturn_t hi65xx_power_release_isr(int irq, void *q)
{
struct input_dev *input = q;
pm_wakeup_event(input->dev.parent, MAX_HELD_TIME);
input_report_key(input, KEY_POWER, 0);
input_sync(input);
return IRQ_HANDLED;
}
static irqreturn_t hi65xx_restart_toggle_isr(int irq, void *q)
{
struct input_dev *input = q;
int value = test_bit(KEY_RESTART, input->key);
pm_wakeup_event(input->dev.parent, MAX_HELD_TIME);
input_report_key(input, KEY_RESTART, !value);
input_sync(input);
return IRQ_HANDLED;
}
static const struct {
const char *name;
irqreturn_t (*handler)(int irq, void *q);
} hi65xx_irq_info[] = {
{ "down", hi65xx_power_press_isr },
{ "up", hi65xx_power_release_isr },
{ "hold 4s", hi65xx_restart_toggle_isr },
};
static int hi65xx_powerkey_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct input_dev *input;
int irq, i, error;
input = devm_input_allocate_device(dev);
if (!input) {
dev_err(dev, "failed to allocate input device\n");
return -ENOMEM;
}
input->phys = "hisi_on/input0";
input->name = "HISI 65xx PowerOn Key";
input_set_capability(input, EV_KEY, KEY_POWER);
input_set_capability(input, EV_KEY, KEY_RESTART);
for (i = 0; i < ARRAY_SIZE(hi65xx_irq_info); i++) {
irq = platform_get_irq_byname(pdev, hi65xx_irq_info[i].name);
if (irq < 0) {
error = irq;
dev_err(dev, "couldn't get irq %s: %d\n",
hi65xx_irq_info[i].name, error);
return error;
}
error = devm_request_any_context_irq(dev, irq,
hi65xx_irq_info[i].handler,
IRQF_ONESHOT,
hi65xx_irq_info[i].name,
input);
if (error < 0) {
dev_err(dev, "couldn't request irq %s: %d\n",
hi65xx_irq_info[i].name, error);
return error;
}
}
error = input_register_device(input);
if (error) {
dev_err(dev, "failed to register input device: %d\n", error);
return error;
}
device_init_wakeup(dev, 1);
return 0;
}
static struct platform_driver hi65xx_powerkey_driver = {
.driver = {
.name = "hi65xx-powerkey",
},
.probe = hi65xx_powerkey_probe,
};
module_platform_driver(hi65xx_powerkey_driver);
MODULE_AUTHOR("Zhiliang Xue <xuezhiliang@huawei.com");
MODULE_DESCRIPTION("Hisi PMIC Power key driver");
MODULE_LICENSE("GPL v2");
+721
View File
@@ -0,0 +1,721 @@
/*
* 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)) {
up(&i8042tregs);
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 timespec64 *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_nsec = (long)(tenms % 100) * 10000 * 1000;
res->tv_sec = (tenms / 100) + (time64_t)days * 86400;
return 0;
}
/* Read the i8042 fast handshake timer */
static inline int hp_sdc_rtc_read_fhs(struct timespec64 *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_nsec = (long)(tenms % 100) * 10000 * 1000;
res->tv_sec = (time64_t)(tenms / 100);
return 0;
}
/* Read the i8042 match timer (a.k.a. alarm) */
static inline int hp_sdc_rtc_read_mt(struct timespec64 *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_nsec = (long)(tenms % 100) * 10000 * 1000;
res->tv_sec = (time64_t)(tenms / 100);
return 0;
}
/* Read the i8042 delay timer */
static inline int hp_sdc_rtc_read_dt(struct timespec64 *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_nsec = (long)(tenms % 100) * 10000 * 1000;
res->tv_sec = (time64_t)(tenms / 100);
return 0;
}
/* Read the i8042 cycle timer (a.k.a. periodic) */
static inline int hp_sdc_rtc_read_ct(struct timespec64 *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_nsec = (long)(tenms % 100) * 10000 * 1000;
res->tv_sec = (time64_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 __poll_t hp_sdc_rtc_poll(struct file *file, poll_table *wait)
{
unsigned long l;
l = 0;
if (l != 0)
return EPOLLIN | EPOLLRDNORM;
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 timespec64 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: %lld.%02ld seconds\n",
(s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
}
if (hp_sdc_rtc_read_fhs(&tv)) {
seq_puts(m, "handshake\t: READ FAILED!\n");
} else {
seq_printf(m, "handshake\t: %lld.%02ld seconds\n",
(s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
}
if (hp_sdc_rtc_read_mt(&tv)) {
seq_puts(m, "alarm\t\t: READ FAILED!\n");
} else {
seq_printf(m, "alarm\t\t: %lld.%02ld seconds\n",
(s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
}
if (hp_sdc_rtc_read_dt(&tv)) {
seq_puts(m, "delay\t\t: READ FAILED!\n");
} else {
seq_printf(m, "delay\t\t: %lld.%02ld seconds\n",
(s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
}
if (hp_sdc_rtc_read_ct(&tv)) {
seq_puts(m, "periodic\t: READ FAILED!\n");
} else {
seq_printf(m, "periodic\t: %lld.%02ld seconds\n",
(s64)tv.tv_sec, (long)tv.tv_nsec/1000000L);
}
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_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_single("driver/rtc", 0, NULL, hp_sdc_rtc_proc_show);
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);
+357
View File
@@ -0,0 +1,357 @@
/*
* Input driver for slidebars on some Lenovo IdeaPad laptops
*
* Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com>
*
* Reverse-engineered from Lenovo SlideNav software (SBarHook.dll).
*
* 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.
*
* Trademarks are the property of their respective owners.
*/
/*
* Currently tested and works on:
* Lenovo IdeaPad Y550
* Lenovo IdeaPad Y550P
*
* Other models can be added easily. To test,
* load with 'force' parameter set 'true'.
*
* LEDs blinking and input mode are managed via sysfs,
* (hex, unsigned byte value):
* /sys/devices/platform/ideapad_slidebar/slidebar_mode
*
* The value is in byte range, however, I only figured out
* how bits 0b10011001 work. Some other bits, probably,
* are meaningfull too.
*
* Possible states:
*
* STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL
*
* Meaning:
* released touched
* STD 'heartbeat' lights follow the finger
* ONMOV no lights lights follow the finger
* LAST at last pos lights follow the finger
* OFF no lights no lights
*
* INT all input events are generated, interrupts are used
* POLL no input events by default, to get them,
* send 0b10000000 (read below)
*
* Commands: write
*
* All | 0b01001 -> STD_INT
* possible | 0b10001 -> ONMOV_INT
* states | 0b01000 -> OFF_INT
*
* | 0b0 -> LAST_POLL
* STD_INT or ONMOV_INT |
* | 0b1 -> STD_INT
*
* | 0b0 -> OFF_POLL
* OFF_INT or OFF_POLL |
* | 0b1 -> OFF_INT
*
* Any state | 0b10000000 -> if the slidebar has updated data,
* produce one input event (last position),
* switch to respective POLL mode
* (like 0x0), if not in POLL mode yet.
*
* Get current state: read
*
* masked by 0x11 read value means:
*
* 0x00 LAST
* 0x01 STD
* 0x10 OFF
* 0x11 ONMOV
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/dmi.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/i8042.h>
#include <linux/serio.h>
#define IDEAPAD_BASE 0xff29
static bool force;
module_param(force, bool, 0);
MODULE_PARM_DESC(force, "Force driver load, ignore DMI data");
static DEFINE_SPINLOCK(io_lock);
static struct input_dev *slidebar_input_dev;
static struct platform_device *slidebar_platform_dev;
static u8 slidebar_pos_get(void)
{
u8 res;
unsigned long flags;
spin_lock_irqsave(&io_lock, flags);
outb(0xf4, 0xff29);
outb(0xbf, 0xff2a);
res = inb(0xff2b);
spin_unlock_irqrestore(&io_lock, flags);
return res;
}
static u8 slidebar_mode_get(void)
{
u8 res;
unsigned long flags;
spin_lock_irqsave(&io_lock, flags);
outb(0xf7, 0xff29);
outb(0x8b, 0xff2a);
res = inb(0xff2b);
spin_unlock_irqrestore(&io_lock, flags);
return res;
}
static void slidebar_mode_set(u8 mode)
{
unsigned long flags;
spin_lock_irqsave(&io_lock, flags);
outb(0xf7, 0xff29);
outb(0x8b, 0xff2a);
outb(mode, 0xff2b);
spin_unlock_irqrestore(&io_lock, flags);
}
static bool slidebar_i8042_filter(unsigned char data, unsigned char str,
struct serio *port)
{
static bool extended = false;
/* We are only interested in data coming form KBC port */
if (str & I8042_STR_AUXDATA)
return false;
/* Scancodes: e03b on move, e0bb on release. */
if (data == 0xe0) {
extended = true;
return true;
}
if (!extended)
return false;
extended = false;
if (likely((data & 0x7f) != 0x3b)) {
serio_interrupt(port, 0xe0, 0);
return false;
}
if (data & 0x80) {
input_report_key(slidebar_input_dev, BTN_TOUCH, 0);
} else {
input_report_key(slidebar_input_dev, BTN_TOUCH, 1);
input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get());
}
input_sync(slidebar_input_dev);
return true;
}
static ssize_t show_slidebar_mode(struct device *dev,
struct device_attribute *attr,
char *buf)
{
return sprintf(buf, "%x\n", slidebar_mode_get());
}
static ssize_t store_slidebar_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
u8 mode;
int error;
error = kstrtou8(buf, 0, &mode);
if (error)
return error;
slidebar_mode_set(mode);
return count;
}
static DEVICE_ATTR(slidebar_mode, S_IWUSR | S_IRUGO,
show_slidebar_mode, store_slidebar_mode);
static struct attribute *ideapad_attrs[] = {
&dev_attr_slidebar_mode.attr,
NULL
};
static struct attribute_group ideapad_attr_group = {
.attrs = ideapad_attrs
};
static const struct attribute_group *ideapad_attr_groups[] = {
&ideapad_attr_group,
NULL
};
static int __init ideapad_probe(struct platform_device* pdev)
{
int err;
if (!request_region(IDEAPAD_BASE, 3, "ideapad_slidebar")) {
dev_err(&pdev->dev, "IO ports are busy\n");
return -EBUSY;
}
slidebar_input_dev = input_allocate_device();
if (!slidebar_input_dev) {
dev_err(&pdev->dev, "Failed to allocate input device\n");
err = -ENOMEM;
goto err_release_ports;
}
slidebar_input_dev->name = "IdeaPad Slidebar";
slidebar_input_dev->id.bustype = BUS_HOST;
slidebar_input_dev->dev.parent = &pdev->dev;
input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH);
input_set_capability(slidebar_input_dev, EV_ABS, ABS_X);
input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0);
err = i8042_install_filter(slidebar_i8042_filter);
if (err) {
dev_err(&pdev->dev,
"Failed to install i8042 filter: %d\n", err);
goto err_free_dev;
}
err = input_register_device(slidebar_input_dev);
if (err) {
dev_err(&pdev->dev,
"Failed to register input device: %d\n", err);
goto err_remove_filter;
}
return 0;
err_remove_filter:
i8042_remove_filter(slidebar_i8042_filter);
err_free_dev:
input_free_device(slidebar_input_dev);
err_release_ports:
release_region(IDEAPAD_BASE, 3);
return err;
}
static int ideapad_remove(struct platform_device *pdev)
{
i8042_remove_filter(slidebar_i8042_filter);
input_unregister_device(slidebar_input_dev);
release_region(IDEAPAD_BASE, 3);
return 0;
}
static struct platform_driver slidebar_drv = {
.driver = {
.name = "ideapad_slidebar",
},
.remove = ideapad_remove,
};
static int __init ideapad_dmi_check(const struct dmi_system_id *id)
{
pr_info("Laptop model '%s'\n", id->ident);
return 1;
}
static const struct dmi_system_id ideapad_dmi[] __initconst = {
{
.ident = "Lenovo IdeaPad Y550",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20017"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550")
},
.callback = ideapad_dmi_check
},
{
.ident = "Lenovo IdeaPad Y550P",
.matches = {
DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"),
DMI_MATCH(DMI_PRODUCT_NAME, "20035"),
DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P")
},
.callback = ideapad_dmi_check
},
{ NULL, }
};
MODULE_DEVICE_TABLE(dmi, ideapad_dmi);
static int __init slidebar_init(void)
{
int err;
if (!force && !dmi_check_system(ideapad_dmi)) {
pr_err("DMI does not match\n");
return -ENODEV;
}
slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1);
if (!slidebar_platform_dev) {
pr_err("Not enough memory\n");
return -ENOMEM;
}
slidebar_platform_dev->dev.groups = ideapad_attr_groups;
err = platform_device_add(slidebar_platform_dev);
if (err) {
pr_err("Failed to register platform device\n");
goto err_free_dev;
}
err = platform_driver_probe(&slidebar_drv, ideapad_probe);
if (err) {
pr_err("Failed to register platform driver\n");
goto err_delete_dev;
}
return 0;
err_delete_dev:
platform_device_del(slidebar_platform_dev);
err_free_dev:
platform_device_put(slidebar_platform_dev);
return err;
}
static void __exit slidebar_exit(void)
{
platform_device_unregister(slidebar_platform_dev);
platform_driver_unregister(&slidebar_drv);
}
module_init(slidebar_init);
module_exit(slidebar_exit);
MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>");
MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops");
MODULE_LICENSE("GPL");
File diff suppressed because it is too large. Load diff
+177
View File
@@ -0,0 +1,177 @@
/*
* 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 <linux/gpio.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_direction_output(pin, 0);
*IXP4XX_OSRT2 = (count & ~IXP4XX_OST_RELOAD_MASK) | IXP4XX_OST_ENABLE;
} else {
gpio_direction_output(pin, 1);
gpio_direction_input(pin);
*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)
{
unsigned int pin = (unsigned int) dev_id;
/* clear interrupt */
*IXP4XX_OSST = IXP4XX_OSST_TIMER_2_PEND;
/* flip the beeper output */
gpio_set_value(pin, !gpio_get_value(pin));
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 = gpio_request(dev->id, "ixp4-beeper");
if (err)
goto err_free_device;
err = request_irq(IRQ_IXP4XX_TIMER2, &ixp4xx_spkr_interrupt,
IRQF_NO_SUSPEND, "ixp4xx-beeper",
(void *) dev->id);
if (err)
goto err_free_gpio;
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, (void *)dev->id);
err_free_gpio:
gpio_free(dev->id);
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);
/* turn the speaker off */
disable_irq(IRQ_IXP4XX_TIMER2);
ixp4xx_spkr_control(pin, 0);
free_irq(IRQ_IXP4XX_TIMER2, (void *)dev->id);
gpio_free(dev->id);
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",
},
.probe = ixp4xx_spkr_probe,
.remove = ixp4xx_spkr_remove,
.shutdown = ixp4xx_spkr_shutdown,
};
module_platform_driver(ixp4xx_spkr_platform_driver);
+593
View File
@@ -0,0 +1,593 @@
/*
* 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/slab.h>
#include <linux/module.h>
#include <linux/usb/input.h>
/* 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 const 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,
USB_CTRL_SET_TIMEOUT);
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,
USB_CTRL_SET_TIMEOUT);
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,
USB_CTRL_SET_TIMEOUT);
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;
}
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_KERNEL, &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("Michael Downey <downey@zymeta.com>");
MODULE_DESCRIPTION("Driver for the USB Keyspan remote control.");
MODULE_LICENSE("GPL");
+671
View File
@@ -0,0 +1,671 @@
/*
* 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 =
dev_get_platdata(&client->dev);
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;
}
static int __maybe_unused 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 __maybe_unused 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;
mutex_lock(&input_dev->mutex);
if (input_dev->users)
kxtj9_enable(tj9);
mutex_unlock(&input_dev->mutex);
return 0;
}
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,
.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");
+149
View File
@@ -0,0 +1,149 @@
/*
* 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);
/* 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",
},
.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);
+432
View File
@@ -0,0 +1,432 @@
/*
* MAXIM MAX77693/MAX77843 Haptic device driver
*
* Copyright (C) 2014,2015 Samsung Electronics
* Jaewon Kim <jaewon02.kim@samsung.com>
* Krzysztof Kozlowski <krzk@kernel.org>
*
* 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.
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/input.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/mfd/max77693.h>
#include <linux/mfd/max77693-common.h>
#include <linux/mfd/max77693-private.h>
#include <linux/mfd/max77843-private.h>
#define MAX_MAGNITUDE_SHIFT 16
enum max77693_haptic_motor_type {
MAX77693_HAPTIC_ERM = 0,
MAX77693_HAPTIC_LRA,
};
enum max77693_haptic_pulse_mode {
MAX77693_HAPTIC_EXTERNAL_MODE = 0,
MAX77693_HAPTIC_INTERNAL_MODE,
};
enum max77693_haptic_pwm_divisor {
MAX77693_HAPTIC_PWM_DIVISOR_32 = 0,
MAX77693_HAPTIC_PWM_DIVISOR_64,
MAX77693_HAPTIC_PWM_DIVISOR_128,
MAX77693_HAPTIC_PWM_DIVISOR_256,
};
struct max77693_haptic {
enum max77693_types dev_type;
struct regmap *regmap_pmic;
struct regmap *regmap_haptic;
struct device *dev;
struct input_dev *input_dev;
struct pwm_device *pwm_dev;
struct regulator *motor_reg;
bool enabled;
bool suspend_state;
unsigned int magnitude;
unsigned int pwm_duty;
enum max77693_haptic_motor_type type;
enum max77693_haptic_pulse_mode mode;
struct work_struct work;
};
static int max77693_haptic_set_duty_cycle(struct max77693_haptic *haptic)
{
struct pwm_args pargs;
int delta;
int error;
pwm_get_args(haptic->pwm_dev, &pargs);
delta = (pargs.period + haptic->pwm_duty) / 2;
error = pwm_config(haptic->pwm_dev, delta, pargs.period);
if (error) {
dev_err(haptic->dev, "failed to configure pwm: %d\n", error);
return error;
}
return 0;
}
static int max77843_haptic_bias(struct max77693_haptic *haptic, bool on)
{
int error;
if (haptic->dev_type != TYPE_MAX77843)
return 0;
error = regmap_update_bits(haptic->regmap_haptic,
MAX77843_SYS_REG_MAINCTRL1,
MAX77843_MAINCTRL1_BIASEN_MASK,
on << MAINCTRL1_BIASEN_SHIFT);
if (error) {
dev_err(haptic->dev, "failed to %s bias: %d\n",
on ? "enable" : "disable", error);
return error;
}
return 0;
}
static int max77693_haptic_configure(struct max77693_haptic *haptic,
bool enable)
{
unsigned int value, config_reg;
int error;
switch (haptic->dev_type) {
case TYPE_MAX77693:
value = ((haptic->type << MAX77693_CONFIG2_MODE) |
(enable << MAX77693_CONFIG2_MEN) |
(haptic->mode << MAX77693_CONFIG2_HTYP) |
MAX77693_HAPTIC_PWM_DIVISOR_128);
config_reg = MAX77693_HAPTIC_REG_CONFIG2;
break;
case TYPE_MAX77843:
value = (haptic->type << MCONFIG_MODE_SHIFT) |
(enable << MCONFIG_MEN_SHIFT) |
MAX77693_HAPTIC_PWM_DIVISOR_128;
config_reg = MAX77843_HAP_REG_MCONFIG;
break;
default:
return -EINVAL;
}
error = regmap_write(haptic->regmap_haptic,
config_reg, value);
if (error) {
dev_err(haptic->dev,
"failed to update haptic config: %d\n", error);
return error;
}
return 0;
}
static int max77693_haptic_lowsys(struct max77693_haptic *haptic, bool enable)
{
int error;
if (haptic->dev_type != TYPE_MAX77693)
return 0;
error = regmap_update_bits(haptic->regmap_pmic,
MAX77693_PMIC_REG_LSCNFG,
MAX77693_PMIC_LOW_SYS_MASK,
enable << MAX77693_PMIC_LOW_SYS_SHIFT);
if (error) {
dev_err(haptic->dev, "cannot update pmic regmap: %d\n", error);
return error;
}
return 0;
}
static void max77693_haptic_enable(struct max77693_haptic *haptic)
{
int error;
if (haptic->enabled)
return;
error = pwm_enable(haptic->pwm_dev);
if (error) {
dev_err(haptic->dev,
"failed to enable haptic pwm device: %d\n", error);
return;
}
error = max77693_haptic_lowsys(haptic, true);
if (error)
goto err_enable_lowsys;
error = max77693_haptic_configure(haptic, true);
if (error)
goto err_enable_config;
haptic->enabled = true;
return;
err_enable_config:
max77693_haptic_lowsys(haptic, false);
err_enable_lowsys:
pwm_disable(haptic->pwm_dev);
}
static void max77693_haptic_disable(struct max77693_haptic *haptic)
{
int error;
if (!haptic->enabled)
return;
error = max77693_haptic_configure(haptic, false);
if (error)
return;
error = max77693_haptic_lowsys(haptic, false);
if (error)
goto err_disable_lowsys;
pwm_disable(haptic->pwm_dev);
haptic->enabled = false;
return;
err_disable_lowsys:
max77693_haptic_configure(haptic, true);
}
static void max77693_haptic_play_work(struct work_struct *work)
{
struct max77693_haptic *haptic =
container_of(work, struct max77693_haptic, work);
int error;
error = max77693_haptic_set_duty_cycle(haptic);
if (error) {
dev_err(haptic->dev, "failed to set duty cycle: %d\n", error);
return;
}
if (haptic->magnitude)
max77693_haptic_enable(haptic);
else
max77693_haptic_disable(haptic);
}
static int max77693_haptic_play_effect(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct max77693_haptic *haptic = input_get_drvdata(dev);
struct pwm_args pargs;
u64 period_mag_multi;
haptic->magnitude = effect->u.rumble.strong_magnitude;
if (!haptic->magnitude)
haptic->magnitude = effect->u.rumble.weak_magnitude;
/*
* The magnitude comes from force-feedback interface.
* The formula to convert magnitude to pwm_duty as follows:
* - pwm_duty = (magnitude * pwm_period) / MAX_MAGNITUDE(0xFFFF)
*/
pwm_get_args(haptic->pwm_dev, &pargs);
period_mag_multi = (u64)pargs.period * haptic->magnitude;
haptic->pwm_duty = (unsigned int)(period_mag_multi >>
MAX_MAGNITUDE_SHIFT);
schedule_work(&haptic->work);
return 0;
}
static int max77693_haptic_open(struct input_dev *dev)
{
struct max77693_haptic *haptic = input_get_drvdata(dev);
int error;
error = max77843_haptic_bias(haptic, true);
if (error)
return error;
error = regulator_enable(haptic->motor_reg);
if (error) {
dev_err(haptic->dev,
"failed to enable regulator: %d\n", error);
return error;
}
return 0;
}
static void max77693_haptic_close(struct input_dev *dev)
{
struct max77693_haptic *haptic = input_get_drvdata(dev);
int error;
cancel_work_sync(&haptic->work);
max77693_haptic_disable(haptic);
error = regulator_disable(haptic->motor_reg);
if (error)
dev_err(haptic->dev,
"failed to disable regulator: %d\n", error);
max77843_haptic_bias(haptic, false);
}
static int max77693_haptic_probe(struct platform_device *pdev)
{
struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent);
struct max77693_haptic *haptic;
int error;
haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
if (!haptic)
return -ENOMEM;
haptic->regmap_pmic = max77693->regmap;
haptic->dev = &pdev->dev;
haptic->type = MAX77693_HAPTIC_LRA;
haptic->mode = MAX77693_HAPTIC_EXTERNAL_MODE;
haptic->suspend_state = false;
/* Variant-specific init */
haptic->dev_type = platform_get_device_id(pdev)->driver_data;
switch (haptic->dev_type) {
case TYPE_MAX77693:
haptic->regmap_haptic = max77693->regmap_haptic;
break;
case TYPE_MAX77843:
haptic->regmap_haptic = max77693->regmap;
break;
default:
dev_err(&pdev->dev, "unsupported device type: %u\n",
haptic->dev_type);
return -EINVAL;
}
INIT_WORK(&haptic->work, max77693_haptic_play_work);
/* Get pwm and regulatot for haptic device */
haptic->pwm_dev = devm_pwm_get(&pdev->dev, NULL);
if (IS_ERR(haptic->pwm_dev)) {
dev_err(&pdev->dev, "failed to get pwm device\n");
return PTR_ERR(haptic->pwm_dev);
}
/*
* FIXME: pwm_apply_args() should be removed when switching to the
* atomic PWM API.
*/
pwm_apply_args(haptic->pwm_dev);
haptic->motor_reg = devm_regulator_get(&pdev->dev, "haptic");
if (IS_ERR(haptic->motor_reg)) {
dev_err(&pdev->dev, "failed to get regulator\n");
return PTR_ERR(haptic->motor_reg);
}
/* Initialize input device for haptic device */
haptic->input_dev = devm_input_allocate_device(&pdev->dev);
if (!haptic->input_dev) {
dev_err(&pdev->dev, "failed to allocate input device\n");
return -ENOMEM;
}
haptic->input_dev->name = "max77693-haptic";
haptic->input_dev->id.version = 1;
haptic->input_dev->dev.parent = &pdev->dev;
haptic->input_dev->open = max77693_haptic_open;
haptic->input_dev->close = max77693_haptic_close;
input_set_drvdata(haptic->input_dev, haptic);
input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE);
error = input_ff_create_memless(haptic->input_dev, NULL,
max77693_haptic_play_effect);
if (error) {
dev_err(&pdev->dev, "failed to create force-feedback\n");
return error;
}
error = input_register_device(haptic->input_dev);
if (error) {
dev_err(&pdev->dev, "failed to register input device\n");
return error;
}
platform_set_drvdata(pdev, haptic);
return 0;
}
static int __maybe_unused max77693_haptic_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct max77693_haptic *haptic = platform_get_drvdata(pdev);
if (haptic->enabled) {
max77693_haptic_disable(haptic);
haptic->suspend_state = true;
}
return 0;
}
static int __maybe_unused max77693_haptic_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct max77693_haptic *haptic = platform_get_drvdata(pdev);
if (haptic->suspend_state) {
max77693_haptic_enable(haptic);
haptic->suspend_state = false;
}
return 0;
}
static SIMPLE_DEV_PM_OPS(max77693_haptic_pm_ops,
max77693_haptic_suspend, max77693_haptic_resume);
static const struct platform_device_id max77693_haptic_id[] = {
{ "max77693-haptic", TYPE_MAX77693 },
{ "max77843-haptic", TYPE_MAX77843 },
{},
};
MODULE_DEVICE_TABLE(platform, max77693_haptic_id);
static struct platform_driver max77693_haptic_driver = {
.driver = {
.name = "max77693-haptic",
.pm = &max77693_haptic_pm_ops,
},
.probe = max77693_haptic_probe,
.id_table = max77693_haptic_id,
};
module_platform_driver(max77693_haptic_driver);
MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>");
MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");
MODULE_DESCRIPTION("MAXIM 77693/77843 Haptic driver");
MODULE_ALIAS("platform:max77693-haptic");
MODULE_LICENSE("GPL");
+177
View File
@@ -0,0 +1,177 @@
/**
* 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>
#include <linux/device.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 = devm_kzalloc(&pdev->dev, sizeof(struct max8925_onkey_info),
GFP_KERNEL);
if (!info)
return -ENOMEM;
input = devm_input_allocate_device(&pdev->dev);
if (!input)
return -ENOMEM;
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 = devm_request_threaded_irq(&pdev->dev, 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);
return error;
}
error = devm_request_threaded_irq(&pdev->dev, 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);
return error;
}
error = input_register_device(info->idev);
if (error) {
dev_err(chip->dev, "Can't register input device: %d\n", error);
return error;
}
platform_set_drvdata(pdev, info);
device_init_wakeup(&pdev->dev, 1);
return 0;
}
static int __maybe_unused 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 __maybe_unused 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;
}
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",
.pm = &max8925_onkey_pm_ops,
},
.probe = max8925_onkey_probe,
};
module_platform_driver(max8925_onkey_driver);
MODULE_DESCRIPTION("Maxim MAX8925 ONKEY driver");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
MODULE_LICENSE("GPL");
+420
View File
@@ -0,0 +1,420 @@
/*
* 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/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) {
error = regulator_enable(chip->regulator);
if (error) {
dev_err(chip->dev, "Failed to enable regulator\n");
goto out;
}
max8997_haptic_configure(chip);
if (chip->mode == MAX8997_EXTERNAL_MODE) {
error = pwm_enable(chip->pwm);
if (error) {
dev_err(chip->dev, "Failed to enable PWM\n");
regulator_disable(chip->regulator);
goto out;
}
}
chip->enabled = true;
}
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 = NULL;
struct max8997_haptic *chip;
struct input_dev *input_dev;
int error;
if (pdata)
haptic_pdata = pdata->haptic_pdata;
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;
}
/*
* FIXME: pwm_apply_args() should be removed when switching to
* the atomic PWM API.
*/
pwm_apply_args(chip->pwm);
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;
}
static int __maybe_unused 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;
}
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(platform, max8997_haptic_id);
static struct platform_driver max8997_haptic_driver = {
.driver = {
.name = "max8997-haptic",
.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_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
MODULE_DESCRIPTION("max8997_haptic driver");
MODULE_LICENSE("GPL");
+269
View File
@@ -0,0 +1,269 @@
/**
* 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/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);
return 0;
}
static struct platform_driver mc13783_pwrbutton_driver = {
.probe = mc13783_pwrbutton_probe,
.remove = mc13783_pwrbutton_remove,
.driver = {
.name = "mc13783-pwrbutton",
},
};
module_platform_driver(mc13783_pwrbutton_driver);
MODULE_ALIAS("platform:mc13783-pwrbutton");
MODULE_DESCRIPTION("MC13783 Power Button");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Philippe Retornaz");
+236
View File
@@ -0,0 +1,236 @@
/*
* 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 = devm_kzalloc(&c->dev, sizeof(*m), GFP_KERNEL);
if (!m)
return -ENOMEM;
idev = devm_input_allocate_polled_device(&c->dev);
if (!idev)
return -ENOMEM;
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");
return err;
}
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,
.of_match_table = mma8450_dt_ids,
},
.probe = mma8450_probe,
.id_table = mma8450_id,
};
module_i2c_driver(mma8450_driver);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MMA8450 3-Axis Accelerometer Driver");
MODULE_LICENSE("GPL");
+329
View File
@@ -0,0 +1,329 @@
/*
* Texas Instruments' Palmas Power Button Input Driver
*
* Copyright (C) 2012-2014 Texas Instruments Incorporated - http://www.ti.com/
* Girish S Ghongdemath
* Nishanth Menon
*
* 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 "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/palmas.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#define PALMAS_LPK_TIME_MASK 0x0c
#define PALMAS_PWRON_DEBOUNCE_MASK 0x03
#define PALMAS_PWR_KEY_Q_TIME_MS 20
/**
* struct palmas_pwron - Palmas power on data
* @palmas: pointer to palmas device
* @input_dev: pointer to input device
* @input_work: work for detecting release of key
* @irq: irq that we are hooked on to
*/
struct palmas_pwron {
struct palmas *palmas;
struct input_dev *input_dev;
struct delayed_work input_work;
int irq;
};
/**
* struct palmas_pwron_config - configuration of palmas power on
* @long_press_time_val: value for long press h/w shutdown event
* @pwron_debounce_val: value for debounce of power button
*/
struct palmas_pwron_config {
u8 long_press_time_val;
u8 pwron_debounce_val;
};
/**
* palmas_power_button_work() - Detects the button release event
* @work: work item to detect button release
*/
static void palmas_power_button_work(struct work_struct *work)
{
struct palmas_pwron *pwron = container_of(work,
struct palmas_pwron,
input_work.work);
struct input_dev *input_dev = pwron->input_dev;
unsigned int reg;
int error;
error = palmas_read(pwron->palmas, PALMAS_INTERRUPT_BASE,
PALMAS_INT1_LINE_STATE, &reg);
if (error) {
dev_err(input_dev->dev.parent,
"Cannot read palmas PWRON status: %d\n", error);
} else if (reg & BIT(1)) {
/* The button is released, report event. */
input_report_key(input_dev, KEY_POWER, 0);
input_sync(input_dev);
} else {
/* The button is still depressed, keep checking. */
schedule_delayed_work(&pwron->input_work,
msecs_to_jiffies(PALMAS_PWR_KEY_Q_TIME_MS));
}
}
/**
* pwron_irq() - button press isr
* @irq: irq
* @palmas_pwron: pwron struct
*
* Return: IRQ_HANDLED
*/
static irqreturn_t pwron_irq(int irq, void *palmas_pwron)
{
struct palmas_pwron *pwron = palmas_pwron;
struct input_dev *input_dev = pwron->input_dev;
input_report_key(input_dev, KEY_POWER, 1);
pm_wakeup_event(input_dev->dev.parent, 0);
input_sync(input_dev);
mod_delayed_work(system_wq, &pwron->input_work,
msecs_to_jiffies(PALMAS_PWR_KEY_Q_TIME_MS));
return IRQ_HANDLED;
}
/**
* palmas_pwron_params_ofinit() - device tree parameter parser
* @dev: palmas button device
* @config: configuration params that this fills up
*/
static void palmas_pwron_params_ofinit(struct device *dev,
struct palmas_pwron_config *config)
{
struct device_node *np;
u32 val;
int i, error;
u8 lpk_times[] = { 6, 8, 10, 12 };
int pwr_on_deb_ms[] = { 15, 100, 500, 1000 };
memset(config, 0, sizeof(*config));
/* Default config parameters */
config->long_press_time_val = ARRAY_SIZE(lpk_times) - 1;
np = dev->of_node;
if (!np)
return;
error = of_property_read_u32(np, "ti,palmas-long-press-seconds", &val);
if (!error) {
for (i = 0; i < ARRAY_SIZE(lpk_times); i++) {
if (val <= lpk_times[i]) {
config->long_press_time_val = i;
break;
}
}
}
error = of_property_read_u32(np,
"ti,palmas-pwron-debounce-milli-seconds",
&val);
if (!error) {
for (i = 0; i < ARRAY_SIZE(pwr_on_deb_ms); i++) {
if (val <= pwr_on_deb_ms[i]) {
config->pwron_debounce_val = i;
break;
}
}
}
dev_info(dev, "h/w controlled shutdown duration=%d seconds\n",
lpk_times[config->long_press_time_val]);
}
/**
* palmas_pwron_probe() - probe
* @pdev: platform device for the button
*
* Return: 0 for successful probe else appropriate error
*/
static int palmas_pwron_probe(struct platform_device *pdev)
{
struct palmas *palmas = dev_get_drvdata(pdev->dev.parent);
struct device *dev = &pdev->dev;
struct input_dev *input_dev;
struct palmas_pwron *pwron;
struct palmas_pwron_config config;
int val;
int error;
palmas_pwron_params_ofinit(dev, &config);
pwron = kzalloc(sizeof(*pwron), GFP_KERNEL);
if (!pwron)
return -ENOMEM;
input_dev = input_allocate_device();
if (!input_dev) {
dev_err(dev, "Can't allocate power button\n");
error = -ENOMEM;
goto err_free_mem;
}
input_dev->name = "palmas_pwron";
input_dev->phys = "palmas_pwron/input0";
input_dev->dev.parent = dev;
input_set_capability(input_dev, EV_KEY, KEY_POWER);
/*
* Setup default hardware shutdown option (long key press)
* and debounce.
*/
val = config.long_press_time_val << __ffs(PALMAS_LPK_TIME_MASK);
val |= config.pwron_debounce_val << __ffs(PALMAS_PWRON_DEBOUNCE_MASK);
error = palmas_update_bits(palmas, PALMAS_PMU_CONTROL_BASE,
PALMAS_LONG_PRESS_KEY,
PALMAS_LPK_TIME_MASK |
PALMAS_PWRON_DEBOUNCE_MASK,
val);
if (error) {
dev_err(dev, "LONG_PRESS_KEY_UPDATE failed: %d\n", error);
goto err_free_input;
}
pwron->palmas = palmas;
pwron->input_dev = input_dev;
INIT_DELAYED_WORK(&pwron->input_work, palmas_power_button_work);
pwron->irq = platform_get_irq(pdev, 0);
error = request_threaded_irq(pwron->irq, NULL, pwron_irq,
IRQF_TRIGGER_HIGH |
IRQF_TRIGGER_LOW |
IRQF_ONESHOT,
dev_name(dev), pwron);
if (error) {
dev_err(dev, "Can't get IRQ for pwron: %d\n", error);
goto err_free_input;
}
error = input_register_device(input_dev);
if (error) {
dev_err(dev, "Can't register power button: %d\n", error);
goto err_free_irq;
}
platform_set_drvdata(pdev, pwron);
device_init_wakeup(dev, true);
return 0;
err_free_irq:
cancel_delayed_work_sync(&pwron->input_work);
free_irq(pwron->irq, pwron);
err_free_input:
input_free_device(input_dev);
err_free_mem:
kfree(pwron);
return error;
}
/**
* palmas_pwron_remove() - Cleanup on removal
* @pdev: platform device for the button
*
* Return: 0
*/
static int palmas_pwron_remove(struct platform_device *pdev)
{
struct palmas_pwron *pwron = platform_get_drvdata(pdev);
free_irq(pwron->irq, pwron);
cancel_delayed_work_sync(&pwron->input_work);
input_unregister_device(pwron->input_dev);
kfree(pwron);
return 0;
}
/**
* palmas_pwron_suspend() - suspend handler
* @dev: power button device
*
* Cancel all pending work items for the power button, setup irq for wakeup
*
* Return: 0
*/
static int __maybe_unused palmas_pwron_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct palmas_pwron *pwron = platform_get_drvdata(pdev);
cancel_delayed_work_sync(&pwron->input_work);
if (device_may_wakeup(dev))
enable_irq_wake(pwron->irq);
return 0;
}
/**
* palmas_pwron_resume() - resume handler
* @dev: power button device
*
* Just disable the wakeup capability of irq here.
*
* Return: 0
*/
static int __maybe_unused palmas_pwron_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct palmas_pwron *pwron = platform_get_drvdata(pdev);
if (device_may_wakeup(dev))
disable_irq_wake(pwron->irq);
return 0;
}
static SIMPLE_DEV_PM_OPS(palmas_pwron_pm,
palmas_pwron_suspend, palmas_pwron_resume);
#ifdef CONFIG_OF
static const struct of_device_id of_palmas_pwr_match[] = {
{ .compatible = "ti,palmas-pwrbutton" },
{ },
};
MODULE_DEVICE_TABLE(of, of_palmas_pwr_match);
#endif
static struct platform_driver palmas_pwron_driver = {
.probe = palmas_pwron_probe,
.remove = palmas_pwron_remove,
.driver = {
.name = "palmas_pwrbutton",
.of_match_table = of_match_ptr(of_palmas_pwr_match),
.pm = &palmas_pwron_pm,
},
};
module_platform_driver(palmas_pwron_driver);
MODULE_ALIAS("platform:palmas-pwrbutton");
MODULE_DESCRIPTION("Palmas Power Button");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Texas Instruments Inc.");
+131
View File
@@ -0,0 +1,131 @@
/*
* 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/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",
}
};
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");
+120
View File
@@ -0,0 +1,120 @@
/* 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/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");
+224
View File
@@ -0,0 +1,224 @@
/*
* 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/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++) {
if (lp->btncode[i] <= KEY_MAX) {
lp->btncode[i] = pcf8574_kp_btncode[i];
__set_bit(lp->btncode[i], idev->keybit);
}
}
__clear_bit(KEY_RESERVED, 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,
#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");
+140
View File
@@ -0,0 +1,140 @@
/*
* 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/input.h>
#include <linux/platform_device.h>
#include <linux/timex.h>
#include <linux/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 -EINVAL;
switch (code) {
case SND_BELL:
if (value)
value = 1000;
case SND_TONE:
break;
default:
return -EINVAL;
}
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);
/* 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",
.pm = &pcspkr_pm_ops,
},
.probe = pcspkr_probe,
.remove = pcspkr_remove,
.shutdown = pcspkr_shutdown,
};
module_platform_driver(pcspkr_platform_driver);
+336
View File
@@ -0,0 +1,336 @@
/*
* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
* Copyright (c) 2014, Sony Mobile Communications Inc.
*
* 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/delay.h>
#include <linux/errno.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/log2.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#define PON_REV2 0x01
#define PON_RT_STS 0x10
#define PON_KPDPWR_N_SET BIT(0)
#define PON_RESIN_N_SET BIT(1)
#define PON_PS_HOLD_RST_CTL 0x5a
#define PON_PS_HOLD_RST_CTL2 0x5b
#define PON_PS_HOLD_ENABLE BIT(7)
#define PON_PS_HOLD_TYPE_MASK 0x0f
#define PON_PS_HOLD_TYPE_SHUTDOWN 4
#define PON_PS_HOLD_TYPE_HARD_RESET 7
#define PON_PULL_CTL 0x70
#define PON_KPDPWR_PULL_UP BIT(1)
#define PON_RESIN_PULL_UP BIT(0)
#define PON_DBC_CTL 0x71
#define PON_DBC_DELAY_MASK 0x7
struct pm8941_data {
unsigned int pull_up_bit;
unsigned int status_bit;
};
struct pm8941_pwrkey {
struct device *dev;
int irq;
u32 baseaddr;
struct regmap *regmap;
struct input_dev *input;
unsigned int revision;
struct notifier_block reboot_notifier;
u32 code;
const struct pm8941_data *data;
};
static int pm8941_reboot_notify(struct notifier_block *nb,
unsigned long code, void *unused)
{
struct pm8941_pwrkey *pwrkey = container_of(nb, struct pm8941_pwrkey,
reboot_notifier);
unsigned int enable_reg;
unsigned int reset_type;
int error;
/* PMICs with revision 0 have the enable bit in same register as ctrl */
if (pwrkey->revision == 0)
enable_reg = PON_PS_HOLD_RST_CTL;
else
enable_reg = PON_PS_HOLD_RST_CTL2;
error = regmap_update_bits(pwrkey->regmap,
pwrkey->baseaddr + enable_reg,
PON_PS_HOLD_ENABLE,
0);
if (error)
dev_err(pwrkey->dev,
"unable to clear ps hold reset enable: %d\n",
error);
/*
* Updates of PON_PS_HOLD_ENABLE requires 3 sleep cycles between
* writes.
*/
usleep_range(100, 1000);
switch (code) {
case SYS_HALT:
case SYS_POWER_OFF:
reset_type = PON_PS_HOLD_TYPE_SHUTDOWN;
break;
case SYS_RESTART:
default:
reset_type = PON_PS_HOLD_TYPE_HARD_RESET;
break;
}
error = regmap_update_bits(pwrkey->regmap,
pwrkey->baseaddr + PON_PS_HOLD_RST_CTL,
PON_PS_HOLD_TYPE_MASK,
reset_type);
if (error)
dev_err(pwrkey->dev, "unable to set ps hold reset type: %d\n",
error);
error = regmap_update_bits(pwrkey->regmap,
pwrkey->baseaddr + enable_reg,
PON_PS_HOLD_ENABLE,
PON_PS_HOLD_ENABLE);
if (error)
dev_err(pwrkey->dev, "unable to re-set enable: %d\n", error);
return NOTIFY_DONE;
}
static irqreturn_t pm8941_pwrkey_irq(int irq, void *_data)
{
struct pm8941_pwrkey *pwrkey = _data;
unsigned int sts;
int error;
error = regmap_read(pwrkey->regmap,
pwrkey->baseaddr + PON_RT_STS, &sts);
if (error)
return IRQ_HANDLED;
input_report_key(pwrkey->input, pwrkey->code,
sts & pwrkey->data->status_bit);
input_sync(pwrkey->input);
return IRQ_HANDLED;
}
static int __maybe_unused pm8941_pwrkey_suspend(struct device *dev)
{
struct pm8941_pwrkey *pwrkey = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
enable_irq_wake(pwrkey->irq);
return 0;
}
static int __maybe_unused pm8941_pwrkey_resume(struct device *dev)
{
struct pm8941_pwrkey *pwrkey = dev_get_drvdata(dev);
if (device_may_wakeup(dev))
disable_irq_wake(pwrkey->irq);
return 0;
}
static SIMPLE_DEV_PM_OPS(pm8941_pwr_key_pm_ops,
pm8941_pwrkey_suspend, pm8941_pwrkey_resume);
static int pm8941_pwrkey_probe(struct platform_device *pdev)
{
struct pm8941_pwrkey *pwrkey;
bool pull_up;
struct device *parent;
u32 req_delay;
int error;
if (of_property_read_u32(pdev->dev.of_node, "debounce", &req_delay))
req_delay = 15625;
if (req_delay > 2000000 || req_delay == 0) {
dev_err(&pdev->dev, "invalid debounce time: %u\n", req_delay);
return -EINVAL;
}
pull_up = of_property_read_bool(pdev->dev.of_node, "bias-pull-up");
pwrkey = devm_kzalloc(&pdev->dev, sizeof(*pwrkey), GFP_KERNEL);
if (!pwrkey)
return -ENOMEM;
pwrkey->dev = &pdev->dev;
pwrkey->data = of_device_get_match_data(&pdev->dev);
parent = pdev->dev.parent;
pwrkey->regmap = dev_get_regmap(parent, NULL);
if (!pwrkey->regmap) {
/*
* We failed to get regmap for parent. Let's see if we are
* a child of pon node and read regmap and reg from its
* parent.
*/
pwrkey->regmap = dev_get_regmap(parent->parent, NULL);
if (!pwrkey->regmap) {
dev_err(&pdev->dev, "failed to locate regmap\n");
return -ENODEV;
}
error = of_property_read_u32(parent->of_node,
"reg", &pwrkey->baseaddr);
} else {
error = of_property_read_u32(pdev->dev.of_node, "reg",
&pwrkey->baseaddr);
}
if (error)
return error;
pwrkey->irq = platform_get_irq(pdev, 0);
if (pwrkey->irq < 0) {
dev_err(&pdev->dev, "failed to get irq\n");
return pwrkey->irq;
}
error = regmap_read(pwrkey->regmap, pwrkey->baseaddr + PON_REV2,
&pwrkey->revision);
if (error) {
dev_err(&pdev->dev, "failed to set debounce: %d\n", error);
return error;
}
error = of_property_read_u32(pdev->dev.of_node, "linux,code",
&pwrkey->code);
if (error) {
dev_dbg(&pdev->dev,
"no linux,code assuming power (%d)\n", error);
pwrkey->code = KEY_POWER;
}
pwrkey->input = devm_input_allocate_device(&pdev->dev);
if (!pwrkey->input) {
dev_dbg(&pdev->dev, "unable to allocate input device\n");
return -ENOMEM;
}
input_set_capability(pwrkey->input, EV_KEY, pwrkey->code);
pwrkey->input->name = "pm8941_pwrkey";
pwrkey->input->phys = "pm8941_pwrkey/input0";
req_delay = (req_delay << 6) / USEC_PER_SEC;
req_delay = ilog2(req_delay);
error = regmap_update_bits(pwrkey->regmap,
pwrkey->baseaddr + PON_DBC_CTL,
PON_DBC_DELAY_MASK,
req_delay);
if (error) {
dev_err(&pdev->dev, "failed to set debounce: %d\n", error);
return error;
}
error = regmap_update_bits(pwrkey->regmap,
pwrkey->baseaddr + PON_PULL_CTL,
pwrkey->data->pull_up_bit,
pull_up ? pwrkey->data->pull_up_bit : 0);
if (error) {
dev_err(&pdev->dev, "failed to set pull: %d\n", error);
return error;
}
error = devm_request_threaded_irq(&pdev->dev, pwrkey->irq,
NULL, pm8941_pwrkey_irq,
IRQF_ONESHOT,
"pm8941_pwrkey", pwrkey);
if (error) {
dev_err(&pdev->dev, "failed requesting IRQ: %d\n", error);
return error;
}
error = input_register_device(pwrkey->input);
if (error) {
dev_err(&pdev->dev, "failed to register input device: %d\n",
error);
return error;
}
pwrkey->reboot_notifier.notifier_call = pm8941_reboot_notify,
error = register_reboot_notifier(&pwrkey->reboot_notifier);
if (error) {
dev_err(&pdev->dev, "failed to register reboot notifier: %d\n",
error);
return error;
}
platform_set_drvdata(pdev, pwrkey);
device_init_wakeup(&pdev->dev, 1);
return 0;
}
static int pm8941_pwrkey_remove(struct platform_device *pdev)
{
struct pm8941_pwrkey *pwrkey = platform_get_drvdata(pdev);
unregister_reboot_notifier(&pwrkey->reboot_notifier);
return 0;
}
static const struct pm8941_data pwrkey_data = {
.pull_up_bit = PON_KPDPWR_PULL_UP,
.status_bit = PON_KPDPWR_N_SET,
};
static const struct pm8941_data resin_data = {
.pull_up_bit = PON_RESIN_PULL_UP,
.status_bit = PON_RESIN_N_SET,
};
static const struct of_device_id pm8941_pwr_key_id_table[] = {
{ .compatible = "qcom,pm8941-pwrkey", .data = &pwrkey_data },
{ .compatible = "qcom,pm8941-resin", .data = &resin_data },
{ }
};
MODULE_DEVICE_TABLE(of, pm8941_pwr_key_id_table);
static struct platform_driver pm8941_pwrkey_driver = {
.probe = pm8941_pwrkey_probe,
.remove = pm8941_pwrkey_remove,
.driver = {
.name = "pm8941-pwrkey",
.pm = &pm8941_pwr_key_pm_ops,
.of_match_table = of_match_ptr(pm8941_pwr_key_id_table),
},
};
module_platform_driver(pm8941_pwrkey_driver);
MODULE_DESCRIPTION("PM8941 Power Key driver");
MODULE_LICENSE("GPL v2");
+270
View File
@@ -0,0 +1,270 @@
/* 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/errno.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
#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_regs {
unsigned int enable_addr;
unsigned int enable_mask;
unsigned int drv_addr;
unsigned int drv_mask;
unsigned int drv_shift;
unsigned int drv_en_manual_mask;
};
static const struct pm8xxx_regs pm8058_regs = {
.drv_addr = 0x4A,
.drv_mask = 0xf8,
.drv_shift = 3,
.drv_en_manual_mask = 0xfc,
};
static struct pm8xxx_regs pm8916_regs = {
.enable_addr = 0xc046,
.enable_mask = BIT(7),
.drv_addr = 0xc041,
.drv_mask = 0x1F,
.drv_shift = 0,
.drv_en_manual_mask = 0,
};
/**
* struct pm8xxx_vib - structure to hold vibrator data
* @vib_input_dev: input device supporting force feedback
* @work: work structure to set the vibration parameters
* @regmap: regmap for register read/write
* @regs: registers' info
* @speed: speed of vibration set from userland
* @active: state of vibrator
* @level: level of vibration to set in the chip
* @reg_vib_drv: regs->drv_addr register value
*/
struct pm8xxx_vib {
struct input_dev *vib_input_dev;
struct work_struct work;
struct regmap *regmap;
const struct pm8xxx_regs *regs;
int speed;
int level;
bool active;
u8 reg_vib_drv;
};
/**
* 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;
unsigned int val = vib->reg_vib_drv;
const struct pm8xxx_regs *regs = vib->regs;
if (on)
val |= (vib->level << regs->drv_shift) & regs->drv_mask;
else
val &= ~regs->drv_mask;
rc = regmap_write(vib->regmap, regs->drv_addr, val);
if (rc < 0)
return rc;
vib->reg_vib_drv = val;
if (regs->enable_mask)
rc = regmap_update_bits(vib->regmap, regs->enable_addr,
regs->enable_mask, on ? ~0 : 0);
return rc;
}
/**
* 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);
const struct pm8xxx_regs *regs = vib->regs;
int rc;
unsigned int val;
rc = regmap_read(vib->regmap, regs->drv_addr, &val);
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;
unsigned int val;
const struct pm8xxx_regs *regs;
vib = devm_kzalloc(&pdev->dev, sizeof(*vib), GFP_KERNEL);
if (!vib)
return -ENOMEM;
vib->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!vib->regmap)
return -ENODEV;
input_dev = devm_input_allocate_device(&pdev->dev);
if (!input_dev)
return -ENOMEM;
INIT_WORK(&vib->work, pm8xxx_work_handler);
vib->vib_input_dev = input_dev;
regs = of_device_get_match_data(&pdev->dev);
/* operate in manual mode */
error = regmap_read(vib->regmap, regs->drv_addr, &val);
if (error < 0)
return error;
val &= regs->drv_en_manual_mask;
error = regmap_write(vib->regmap, regs->drv_addr, val);
if (error < 0)
return error;
vib->regs = regs;
vib->reg_vib_drv = val;
input_dev->name = "pm8xxx_vib_ffmemless";
input_dev->id.version = 1;
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");
return error;
}
error = input_register_device(input_dev);
if (error) {
dev_err(&pdev->dev, "couldn't register input device\n");
return error;
}
platform_set_drvdata(pdev, vib);
return 0;
}
static int __maybe_unused 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;
}
static SIMPLE_DEV_PM_OPS(pm8xxx_vib_pm_ops, pm8xxx_vib_suspend, NULL);
static const struct of_device_id pm8xxx_vib_id_table[] = {
{ .compatible = "qcom,pm8058-vib", .data = &pm8058_regs },
{ .compatible = "qcom,pm8921-vib", .data = &pm8058_regs },
{ .compatible = "qcom,pm8916-vib", .data = &pm8916_regs },
{ }
};
MODULE_DEVICE_TABLE(of, pm8xxx_vib_id_table);
static struct platform_driver pm8xxx_vib_driver = {
.probe = pm8xxx_vib_probe,
.driver = {
.name = "pm8xxx-vib",
.pm = &pm8xxx_vib_pm_ops,
.of_match_table = pm8xxx_vib_id_table,
},
};
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>");
+462
View File
@@ -0,0 +1,462 @@
/* 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/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/regmap.h>
#include <linux/log2.h>
#include <linux/of.h>
#include <linux/of_device.h>
#define PON_CNTL_1 0x1C
#define PON_CNTL_PULL_UP BIT(7)
#define PON_CNTL_TRIG_DELAY_MASK (0x7)
#define PON_CNTL_1_PULL_UP_EN 0xe0
#define PON_CNTL_1_USB_PWR_EN 0x10
#define PON_CNTL_1_WD_EN_RESET 0x08
#define PM8058_SLEEP_CTRL 0x02b
#define PM8921_SLEEP_CTRL 0x10a
#define SLEEP_CTRL_SMPL_EN_RESET 0x04
/* Regulator master enable addresses */
#define REG_PM8058_VREG_EN_MSM 0x018
#define REG_PM8058_VREG_EN_GRP_5_4 0x1c8
/* Regulator control registers for shutdown/reset */
#define PM8058_S0_CTRL 0x004
#define PM8058_S1_CTRL 0x005
#define PM8058_S3_CTRL 0x111
#define PM8058_L21_CTRL 0x120
#define PM8058_L22_CTRL 0x121
#define PM8058_REGULATOR_ENABLE_MASK 0x80
#define PM8058_REGULATOR_ENABLE 0x80
#define PM8058_REGULATOR_DISABLE 0x00
#define PM8058_REGULATOR_PULL_DOWN_MASK 0x40
#define PM8058_REGULATOR_PULL_DOWN_EN 0x40
/* Buck CTRL register */
#define PM8058_SMPS_LEGACY_VREF_SEL 0x20
#define PM8058_SMPS_LEGACY_VPROG_MASK 0x1f
#define PM8058_SMPS_ADVANCED_BAND_MASK 0xC0
#define PM8058_SMPS_ADVANCED_BAND_SHIFT 6
#define PM8058_SMPS_ADVANCED_VPROG_MASK 0x3f
/* Buck TEST2 registers for shutdown/reset */
#define PM8058_S0_TEST2 0x084
#define PM8058_S1_TEST2 0x085
#define PM8058_S3_TEST2 0x11a
#define PM8058_REGULATOR_BANK_WRITE 0x80
#define PM8058_REGULATOR_BANK_MASK 0x70
#define PM8058_REGULATOR_BANK_SHIFT 4
#define PM8058_REGULATOR_BANK_SEL(n) ((n) << PM8058_REGULATOR_BANK_SHIFT)
/* Buck TEST2 register bank 1 */
#define PM8058_SMPS_LEGACY_VLOW_SEL 0x01
/* Buck TEST2 register bank 7 */
#define PM8058_SMPS_ADVANCED_MODE_MASK 0x02
#define PM8058_SMPS_ADVANCED_MODE 0x02
#define PM8058_SMPS_LEGACY_MODE 0x00
/**
* struct pmic8xxx_pwrkey - pmic8xxx pwrkey information
* @key_press_irq: key press irq number
* @regmap: device regmap
* @shutdown_fn: shutdown configuration function
*/
struct pmic8xxx_pwrkey {
int key_press_irq;
struct regmap *regmap;
int (*shutdown_fn)(struct pmic8xxx_pwrkey *, bool);
};
static irqreturn_t pwrkey_press_irq(int irq, void *_pwr)
{
struct input_dev *pwr = _pwr;
input_report_key(pwr, KEY_POWER, 1);
input_sync(pwr);
return IRQ_HANDLED;
}
static irqreturn_t pwrkey_release_irq(int irq, void *_pwr)
{
struct input_dev *pwr = _pwr;
input_report_key(pwr, KEY_POWER, 0);
input_sync(pwr);
return IRQ_HANDLED;
}
static int __maybe_unused 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 __maybe_unused 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;
}
static SIMPLE_DEV_PM_OPS(pm8xxx_pwr_key_pm_ops,
pmic8xxx_pwrkey_suspend, pmic8xxx_pwrkey_resume);
static void pmic8xxx_pwrkey_shutdown(struct platform_device *pdev)
{
struct pmic8xxx_pwrkey *pwrkey = platform_get_drvdata(pdev);
int error;
u8 mask, val;
bool reset = system_state == SYSTEM_RESTART;
if (pwrkey->shutdown_fn) {
error = pwrkey->shutdown_fn(pwrkey, reset);
if (error)
return;
}
/*
* Select action to perform (reset or shutdown) when PS_HOLD goes low.
* Also ensure that KPD, CBL0, and CBL1 pull ups are enabled and that
* USB charging is enabled.
*/
mask = PON_CNTL_1_PULL_UP_EN | PON_CNTL_1_USB_PWR_EN;
mask |= PON_CNTL_1_WD_EN_RESET;
val = mask;
if (!reset)
val &= ~PON_CNTL_1_WD_EN_RESET;
regmap_update_bits(pwrkey->regmap, PON_CNTL_1, mask, val);
}
/*
* Set an SMPS regulator to be disabled in its CTRL register, but enabled
* in the master enable register. Also set it's pull down enable bit.
* Take care to make sure that the output voltage doesn't change if switching
* from advanced mode to legacy mode.
*/
static int pm8058_disable_smps_locally_set_pull_down(struct regmap *regmap,
u16 ctrl_addr, u16 test2_addr, u16 master_enable_addr,
u8 master_enable_bit)
{
int error;
u8 vref_sel, vlow_sel, band, vprog, bank;
unsigned int reg;
bank = PM8058_REGULATOR_BANK_SEL(7);
error = regmap_write(regmap, test2_addr, bank);
if (error)
return error;
error = regmap_read(regmap, test2_addr, &reg);
if (error)
return error;
reg &= PM8058_SMPS_ADVANCED_MODE_MASK;
/* Check if in advanced mode. */
if (reg == PM8058_SMPS_ADVANCED_MODE) {
/* Determine current output voltage. */
error = regmap_read(regmap, ctrl_addr, &reg);
if (error)
return error;
band = reg & PM8058_SMPS_ADVANCED_BAND_MASK;
band >>= PM8058_SMPS_ADVANCED_BAND_SHIFT;
switch (band) {
case 3:
vref_sel = 0;
vlow_sel = 0;
break;
case 2:
vref_sel = PM8058_SMPS_LEGACY_VREF_SEL;
vlow_sel = 0;
break;
case 1:
vref_sel = PM8058_SMPS_LEGACY_VREF_SEL;
vlow_sel = PM8058_SMPS_LEGACY_VLOW_SEL;
break;
default:
pr_err("%s: regulator already disabled\n", __func__);
return -EPERM;
}
vprog = reg & PM8058_SMPS_ADVANCED_VPROG_MASK;
/* Round up if fine step is in use. */
vprog = (vprog + 1) >> 1;
if (vprog > PM8058_SMPS_LEGACY_VPROG_MASK)
vprog = PM8058_SMPS_LEGACY_VPROG_MASK;
/* Set VLOW_SEL bit. */
bank = PM8058_REGULATOR_BANK_SEL(1);
error = regmap_write(regmap, test2_addr, bank);
if (error)
return error;
error = regmap_update_bits(regmap, test2_addr,
PM8058_REGULATOR_BANK_WRITE | PM8058_REGULATOR_BANK_MASK
| PM8058_SMPS_LEGACY_VLOW_SEL,
PM8058_REGULATOR_BANK_WRITE |
PM8058_REGULATOR_BANK_SEL(1) | vlow_sel);
if (error)
return error;
/* Switch to legacy mode */
bank = PM8058_REGULATOR_BANK_SEL(7);
error = regmap_write(regmap, test2_addr, bank);
if (error)
return error;
error = regmap_update_bits(regmap, test2_addr,
PM8058_REGULATOR_BANK_WRITE |
PM8058_REGULATOR_BANK_MASK |
PM8058_SMPS_ADVANCED_MODE_MASK,
PM8058_REGULATOR_BANK_WRITE |
PM8058_REGULATOR_BANK_SEL(7) |
PM8058_SMPS_LEGACY_MODE);
if (error)
return error;
/* Enable locally, enable pull down, keep voltage the same. */
error = regmap_update_bits(regmap, ctrl_addr,
PM8058_REGULATOR_ENABLE_MASK |
PM8058_REGULATOR_PULL_DOWN_MASK |
PM8058_SMPS_LEGACY_VREF_SEL |
PM8058_SMPS_LEGACY_VPROG_MASK,
PM8058_REGULATOR_ENABLE | PM8058_REGULATOR_PULL_DOWN_EN
| vref_sel | vprog);
if (error)
return error;
}
/* Enable in master control register. */
error = regmap_update_bits(regmap, master_enable_addr,
master_enable_bit, master_enable_bit);
if (error)
return error;
/* Disable locally and enable pull down. */
return regmap_update_bits(regmap, ctrl_addr,
PM8058_REGULATOR_ENABLE_MASK | PM8058_REGULATOR_PULL_DOWN_MASK,
PM8058_REGULATOR_DISABLE | PM8058_REGULATOR_PULL_DOWN_EN);
}
static int pm8058_disable_ldo_locally_set_pull_down(struct regmap *regmap,
u16 ctrl_addr, u16 master_enable_addr, u8 master_enable_bit)
{
int error;
/* Enable LDO in master control register. */
error = regmap_update_bits(regmap, master_enable_addr,
master_enable_bit, master_enable_bit);
if (error)
return error;
/* Disable LDO in CTRL register and set pull down */
return regmap_update_bits(regmap, ctrl_addr,
PM8058_REGULATOR_ENABLE_MASK | PM8058_REGULATOR_PULL_DOWN_MASK,
PM8058_REGULATOR_DISABLE | PM8058_REGULATOR_PULL_DOWN_EN);
}
static int pm8058_pwrkey_shutdown(struct pmic8xxx_pwrkey *pwrkey, bool reset)
{
int error;
struct regmap *regmap = pwrkey->regmap;
u8 mask, val;
/* When shutting down, enable active pulldowns on important rails. */
if (!reset) {
/* Disable SMPS's 0,1,3 locally and set pulldown enable bits. */
pm8058_disable_smps_locally_set_pull_down(regmap,
PM8058_S0_CTRL, PM8058_S0_TEST2,
REG_PM8058_VREG_EN_MSM, BIT(7));
pm8058_disable_smps_locally_set_pull_down(regmap,
PM8058_S1_CTRL, PM8058_S1_TEST2,
REG_PM8058_VREG_EN_MSM, BIT(6));
pm8058_disable_smps_locally_set_pull_down(regmap,
PM8058_S3_CTRL, PM8058_S3_TEST2,
REG_PM8058_VREG_EN_GRP_5_4, BIT(7) | BIT(4));
/* Disable LDO 21 locally and set pulldown enable bit. */
pm8058_disable_ldo_locally_set_pull_down(regmap,
PM8058_L21_CTRL, REG_PM8058_VREG_EN_GRP_5_4,
BIT(1));
}
/*
* Fix-up: Set regulator LDO22 to 1.225 V in high power mode. Leave its
* pull-down state intact. This ensures a safe shutdown.
*/
error = regmap_update_bits(regmap, PM8058_L22_CTRL, 0xbf, 0x93);
if (error)
return error;
/* Enable SMPL if resetting is desired */
mask = SLEEP_CTRL_SMPL_EN_RESET;
val = 0;
if (reset)
val = mask;
return regmap_update_bits(regmap, PM8058_SLEEP_CTRL, mask, val);
}
static int pm8921_pwrkey_shutdown(struct pmic8xxx_pwrkey *pwrkey, bool reset)
{
struct regmap *regmap = pwrkey->regmap;
u8 mask = SLEEP_CTRL_SMPL_EN_RESET;
u8 val = 0;
/* Enable SMPL if resetting is desired */
if (reset)
val = mask;
return regmap_update_bits(regmap, PM8921_SLEEP_CTRL, mask, val);
}
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;
unsigned int pon_cntl;
struct regmap *regmap;
struct pmic8xxx_pwrkey *pwrkey;
u32 kpd_delay;
bool pull_up;
if (of_property_read_u32(pdev->dev.of_node, "debounce", &kpd_delay))
kpd_delay = 15625;
/* Valid range of pwr key trigger delay is 1/64 sec to 2 seconds. */
if (kpd_delay > USEC_PER_SEC * 2 || kpd_delay < USEC_PER_SEC / 64) {
dev_err(&pdev->dev, "invalid power key trigger delay\n");
return -EINVAL;
}
pull_up = of_property_read_bool(pdev->dev.of_node, "pull-up");
regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!regmap) {
dev_err(&pdev->dev, "failed to locate regmap for the device\n");
return -ENODEV;
}
pwrkey = devm_kzalloc(&pdev->dev, sizeof(*pwrkey), GFP_KERNEL);
if (!pwrkey)
return -ENOMEM;
pwrkey->shutdown_fn = of_device_get_match_data(&pdev->dev);
pwrkey->regmap = regmap;
pwrkey->key_press_irq = key_press_irq;
pwr = devm_input_allocate_device(&pdev->dev);
if (!pwr) {
dev_dbg(&pdev->dev, "Can't allocate power button\n");
return -ENOMEM;
}
input_set_capability(pwr, EV_KEY, KEY_POWER);
pwr->name = "pmic8xxx_pwrkey";
pwr->phys = "pmic8xxx_pwrkey/input0";
delay = (kpd_delay << 6) / USEC_PER_SEC;
delay = ilog2(delay);
err = regmap_read(regmap, PON_CNTL_1, &pon_cntl);
if (err < 0) {
dev_err(&pdev->dev, "failed reading PON_CNTL_1 err=%d\n", err);
return err;
}
pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK;
pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK);
if (pull_up)
pon_cntl |= PON_CNTL_PULL_UP;
else
pon_cntl &= ~PON_CNTL_PULL_UP;
err = regmap_write(regmap, PON_CNTL_1, pon_cntl);
if (err < 0) {
dev_err(&pdev->dev, "failed writing PON_CNTL_1 err=%d\n", err);
return err;
}
err = devm_request_irq(&pdev->dev, key_press_irq, pwrkey_press_irq,
IRQF_TRIGGER_RISING,
"pmic8xxx_pwrkey_press", pwr);
if (err) {
dev_err(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
key_press_irq, err);
return err;
}
err = devm_request_irq(&pdev->dev, key_release_irq, pwrkey_release_irq,
IRQF_TRIGGER_RISING,
"pmic8xxx_pwrkey_release", pwr);
if (err) {
dev_err(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
key_release_irq, err);
return err;
}
err = input_register_device(pwr);
if (err) {
dev_err(&pdev->dev, "Can't register power key: %d\n", err);
return err;
}
platform_set_drvdata(pdev, pwrkey);
device_init_wakeup(&pdev->dev, 1);
return 0;
}
static const struct of_device_id pm8xxx_pwr_key_id_table[] = {
{ .compatible = "qcom,pm8058-pwrkey", .data = &pm8058_pwrkey_shutdown },
{ .compatible = "qcom,pm8921-pwrkey", .data = &pm8921_pwrkey_shutdown },
{ }
};
MODULE_DEVICE_TABLE(of, pm8xxx_pwr_key_id_table);
static struct platform_driver pmic8xxx_pwrkey_driver = {
.probe = pmic8xxx_pwrkey_probe,
.shutdown = pmic8xxx_pwrkey_shutdown,
.driver = {
.name = "pm8xxx-pwrkey",
.pm = &pm8xxx_pwr_key_pm_ops,
.of_match_table = pm8xxx_pwr_key_id_table,
},
};
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>");
+455
View File
@@ -0,0 +1,455 @@
/*
* 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/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_KERNEL, &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;
if (interface->desc.bNumEndpoints < 1)
return -EINVAL;
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 const 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");
+271
View File
@@ -0,0 +1,271 @@
/*
* 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/regulator/consumer.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
struct pwm_beeper {
struct input_dev *input;
struct pwm_device *pwm;
struct regulator *amplifier;
struct work_struct work;
unsigned long period;
unsigned int bell_frequency;
bool suspended;
bool amplifier_on;
};
#define HZ_TO_NANOSECONDS(x) (1000000000UL/(x))
static int pwm_beeper_on(struct pwm_beeper *beeper, unsigned long period)
{
struct pwm_state state;
int error;
pwm_get_state(beeper->pwm, &state);
state.enabled = true;
state.period = period;
pwm_set_relative_duty_cycle(&state, 50, 100);
error = pwm_apply_state(beeper->pwm, &state);
if (error)
return error;
if (!beeper->amplifier_on) {
error = regulator_enable(beeper->amplifier);
if (error) {
pwm_disable(beeper->pwm);
return error;
}
beeper->amplifier_on = true;
}
return 0;
}
static void pwm_beeper_off(struct pwm_beeper *beeper)
{
if (beeper->amplifier_on) {
regulator_disable(beeper->amplifier);
beeper->amplifier_on = false;
}
pwm_disable(beeper->pwm);
}
static void pwm_beeper_work(struct work_struct *work)
{
struct pwm_beeper *beeper = container_of(work, struct pwm_beeper, work);
unsigned long period = READ_ONCE(beeper->period);
if (period)
pwm_beeper_on(beeper, period);
else
pwm_beeper_off(beeper);
}
static int pwm_beeper_event(struct input_dev *input,
unsigned int type, unsigned int code, int value)
{
struct pwm_beeper *beeper = input_get_drvdata(input);
if (type != EV_SND || value < 0)
return -EINVAL;
switch (code) {
case SND_BELL:
value = value ? beeper->bell_frequency : 0;
break;
case SND_TONE:
break;
default:
return -EINVAL;
}
if (value == 0)
beeper->period = 0;
else
beeper->period = HZ_TO_NANOSECONDS(value);
if (!beeper->suspended)
schedule_work(&beeper->work);
return 0;
}
static void pwm_beeper_stop(struct pwm_beeper *beeper)
{
cancel_work_sync(&beeper->work);
pwm_beeper_off(beeper);
}
static void pwm_beeper_close(struct input_dev *input)
{
struct pwm_beeper *beeper = input_get_drvdata(input);
pwm_beeper_stop(beeper);
}
static int pwm_beeper_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct pwm_beeper *beeper;
struct pwm_state state;
u32 bell_frequency;
int error;
beeper = devm_kzalloc(dev, sizeof(*beeper), GFP_KERNEL);
if (!beeper)
return -ENOMEM;
beeper->pwm = devm_pwm_get(dev, NULL);
if (IS_ERR(beeper->pwm)) {
error = PTR_ERR(beeper->pwm);
if (error != -EPROBE_DEFER)
dev_err(dev, "Failed to request PWM device: %d\n",
error);
return error;
}
/* Sync up PWM state and ensure it is off. */
pwm_init_state(beeper->pwm, &state);
state.enabled = false;
error = pwm_apply_state(beeper->pwm, &state);
if (error) {
dev_err(dev, "failed to apply initial PWM state: %d\n",
error);
return error;
}
beeper->amplifier = devm_regulator_get(dev, "amp");
if (IS_ERR(beeper->amplifier)) {
error = PTR_ERR(beeper->amplifier);
if (error != -EPROBE_DEFER)
dev_err(dev, "Failed to get 'amp' regulator: %d\n",
error);
return error;
}
INIT_WORK(&beeper->work, pwm_beeper_work);
error = device_property_read_u32(dev, "beeper-hz", &bell_frequency);
if (error) {
bell_frequency = 1000;
dev_dbg(dev,
"failed to parse 'beeper-hz' property, using default: %uHz\n",
bell_frequency);
}
beeper->bell_frequency = bell_frequency;
beeper->input = devm_input_allocate_device(dev);
if (!beeper->input) {
dev_err(dev, "Failed to allocate input device\n");
return -ENOMEM;
}
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;
input_set_capability(beeper->input, EV_SND, SND_TONE);
input_set_capability(beeper->input, EV_SND, SND_BELL);
beeper->input->event = pwm_beeper_event;
beeper->input->close = pwm_beeper_close;
input_set_drvdata(beeper->input, beeper);
error = input_register_device(beeper->input);
if (error) {
dev_err(dev, "Failed to register input device: %d\n", error);
return error;
}
platform_set_drvdata(pdev, beeper);
return 0;
}
static int __maybe_unused pwm_beeper_suspend(struct device *dev)
{
struct pwm_beeper *beeper = dev_get_drvdata(dev);
/*
* Spinlock is taken here is not to protect write to
* beeper->suspended, but to ensure that pwm_beeper_event
* does not re-submit work once flag is set.
*/
spin_lock_irq(&beeper->input->event_lock);
beeper->suspended = true;
spin_unlock_irq(&beeper->input->event_lock);
pwm_beeper_stop(beeper);
return 0;
}
static int __maybe_unused pwm_beeper_resume(struct device *dev)
{
struct pwm_beeper *beeper = dev_get_drvdata(dev);
spin_lock_irq(&beeper->input->event_lock);
beeper->suspended = false;
spin_unlock_irq(&beeper->input->event_lock);
/* Let worker figure out if we should resume beeping */
schedule_work(&beeper->work);
return 0;
}
static SIMPLE_DEV_PM_OPS(pwm_beeper_pm_ops,
pwm_beeper_suspend, pwm_beeper_resume);
#ifdef CONFIG_OF
static const struct of_device_id pwm_beeper_match[] = {
{ .compatible = "pwm-beeper", },
{ },
};
MODULE_DEVICE_TABLE(of, pwm_beeper_match);
#endif
static struct platform_driver pwm_beeper_driver = {
.probe = pwm_beeper_probe,
.driver = {
.name = "pwm-beeper",
.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");
+274
View File
@@ -0,0 +1,274 @@
/*
* PWM vibrator driver
*
* Copyright (C) 2017 Collabora Ltd.
*
* Based on previous work from:
* Copyright (C) 2012 Dmitry Torokhov <dmitry.torokhov@gmail.com>
*
* Based on PWM beeper driver:
* Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.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.
*/
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
struct pwm_vibrator {
struct input_dev *input;
struct pwm_device *pwm;
struct pwm_device *pwm_dir;
struct regulator *vcc;
struct work_struct play_work;
u16 level;
u32 direction_duty_cycle;
bool vcc_on;
};
static int pwm_vibrator_start(struct pwm_vibrator *vibrator)
{
struct device *pdev = vibrator->input->dev.parent;
struct pwm_state state;
int err;
if (!vibrator->vcc_on) {
err = regulator_enable(vibrator->vcc);
if (err) {
dev_err(pdev, "failed to enable regulator: %d", err);
return err;
}
vibrator->vcc_on = true;
}
pwm_get_state(vibrator->pwm, &state);
pwm_set_relative_duty_cycle(&state, vibrator->level, 0xffff);
state.enabled = true;
err = pwm_apply_state(vibrator->pwm, &state);
if (err) {
dev_err(pdev, "failed to apply pwm state: %d", err);
return err;
}
if (vibrator->pwm_dir) {
pwm_get_state(vibrator->pwm_dir, &state);
state.duty_cycle = vibrator->direction_duty_cycle;
state.enabled = true;
err = pwm_apply_state(vibrator->pwm_dir, &state);
if (err) {
dev_err(pdev, "failed to apply dir-pwm state: %d", err);
pwm_disable(vibrator->pwm);
return err;
}
}
return 0;
}
static void pwm_vibrator_stop(struct pwm_vibrator *vibrator)
{
if (vibrator->pwm_dir)
pwm_disable(vibrator->pwm_dir);
pwm_disable(vibrator->pwm);
if (vibrator->vcc_on) {
regulator_disable(vibrator->vcc);
vibrator->vcc_on = false;
}
}
static void pwm_vibrator_play_work(struct work_struct *work)
{
struct pwm_vibrator *vibrator = container_of(work,
struct pwm_vibrator, play_work);
if (vibrator->level)
pwm_vibrator_start(vibrator);
else
pwm_vibrator_stop(vibrator);
}
static int pwm_vibrator_play_effect(struct input_dev *dev, void *data,
struct ff_effect *effect)
{
struct pwm_vibrator *vibrator = input_get_drvdata(dev);
vibrator->level = effect->u.rumble.strong_magnitude;
if (!vibrator->level)
vibrator->level = effect->u.rumble.weak_magnitude;
schedule_work(&vibrator->play_work);
return 0;
}
static void pwm_vibrator_close(struct input_dev *input)
{
struct pwm_vibrator *vibrator = input_get_drvdata(input);
cancel_work_sync(&vibrator->play_work);
pwm_vibrator_stop(vibrator);
}
static int pwm_vibrator_probe(struct platform_device *pdev)
{
struct pwm_vibrator *vibrator;
struct pwm_state state;
int err;
vibrator = devm_kzalloc(&pdev->dev, sizeof(*vibrator), GFP_KERNEL);
if (!vibrator)
return -ENOMEM;
vibrator->input = devm_input_allocate_device(&pdev->dev);
if (!vibrator->input)
return -ENOMEM;
vibrator->vcc = devm_regulator_get(&pdev->dev, "vcc");
err = PTR_ERR_OR_ZERO(vibrator->vcc);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(&pdev->dev, "Failed to request regulator: %d",
err);
return err;
}
vibrator->pwm = devm_pwm_get(&pdev->dev, "enable");
err = PTR_ERR_OR_ZERO(vibrator->pwm);
if (err) {
if (err != -EPROBE_DEFER)
dev_err(&pdev->dev, "Failed to request main pwm: %d",
err);
return err;
}
INIT_WORK(&vibrator->play_work, pwm_vibrator_play_work);
/* Sync up PWM state and ensure it is off. */
pwm_init_state(vibrator->pwm, &state);
state.enabled = false;
err = pwm_apply_state(vibrator->pwm, &state);
if (err) {
dev_err(&pdev->dev, "failed to apply initial PWM state: %d",
err);
return err;
}
vibrator->pwm_dir = devm_pwm_get(&pdev->dev, "direction");
err = PTR_ERR_OR_ZERO(vibrator->pwm_dir);
switch (err) {
case 0:
/* Sync up PWM state and ensure it is off. */
pwm_init_state(vibrator->pwm_dir, &state);
state.enabled = false;
err = pwm_apply_state(vibrator->pwm_dir, &state);
if (err) {
dev_err(&pdev->dev, "failed to apply initial PWM state: %d",
err);
return err;
}
vibrator->direction_duty_cycle =
pwm_get_period(vibrator->pwm_dir) / 2;
device_property_read_u32(&pdev->dev, "direction-duty-cycle-ns",
&vibrator->direction_duty_cycle);
break;
case -ENODATA:
/* Direction PWM is optional */
vibrator->pwm_dir = NULL;
break;
default:
dev_err(&pdev->dev, "Failed to request direction pwm: %d", err);
/* Fall through */
case -EPROBE_DEFER:
return err;
}
vibrator->input->name = "pwm-vibrator";
vibrator->input->id.bustype = BUS_HOST;
vibrator->input->dev.parent = &pdev->dev;
vibrator->input->close = pwm_vibrator_close;
input_set_drvdata(vibrator->input, vibrator);
input_set_capability(vibrator->input, EV_FF, FF_RUMBLE);
err = input_ff_create_memless(vibrator->input, NULL,
pwm_vibrator_play_effect);
if (err) {
dev_err(&pdev->dev, "Couldn't create FF dev: %d", err);
return err;
}
err = input_register_device(vibrator->input);
if (err) {
dev_err(&pdev->dev, "Couldn't register input dev: %d", err);
return err;
}
platform_set_drvdata(pdev, vibrator);
return 0;
}
static int __maybe_unused pwm_vibrator_suspend(struct device *dev)
{
struct pwm_vibrator *vibrator = dev_get_drvdata(dev);
cancel_work_sync(&vibrator->play_work);
if (vibrator->level)
pwm_vibrator_stop(vibrator);
return 0;
}
static int __maybe_unused pwm_vibrator_resume(struct device *dev)
{
struct pwm_vibrator *vibrator = dev_get_drvdata(dev);
if (vibrator->level)
pwm_vibrator_start(vibrator);
return 0;
}
static SIMPLE_DEV_PM_OPS(pwm_vibrator_pm_ops,
pwm_vibrator_suspend, pwm_vibrator_resume);
#ifdef CONFIG_OF
static const struct of_device_id pwm_vibra_dt_match_table[] = {
{ .compatible = "pwm-vibrator" },
{},
};
MODULE_DEVICE_TABLE(of, pwm_vibra_dt_match_table);
#endif
static struct platform_driver pwm_vibrator_driver = {
.probe = pwm_vibrator_probe,
.driver = {
.name = "pwm-vibrator",
.pm = &pwm_vibrator_pm_ops,
.of_match_table = of_match_ptr(pwm_vibra_dt_match_table),
},
};
module_platform_driver(pwm_vibrator_driver);
MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
MODULE_DESCRIPTION("PWM vibrator driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:pwm-vibrator");
+94
View File
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-2.0+
//
// Power Button driver for RAVE SP
//
// Copyright (C) 2017 Zodiac Inflight Innovations
//
//
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mfd/rave-sp.h>
#include <linux/platform_device.h>
#define RAVE_SP_EVNT_BUTTON_PRESS (RAVE_SP_EVNT_BASE + 0x00)
struct rave_sp_power_button {
struct input_dev *idev;
struct notifier_block nb;
};
static int rave_sp_power_button_event(struct notifier_block *nb,
unsigned long action, void *data)
{
struct rave_sp_power_button *pb =
container_of(nb, struct rave_sp_power_button, nb);
const u8 event = rave_sp_action_unpack_event(action);
const u8 value = rave_sp_action_unpack_value(action);
struct input_dev *idev = pb->idev;
if (event == RAVE_SP_EVNT_BUTTON_PRESS) {
input_report_key(idev, KEY_POWER, value);
input_sync(idev);
return NOTIFY_STOP;
}
return NOTIFY_DONE;
}
static int rave_sp_pwrbutton_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rave_sp_power_button *pb;
struct input_dev *idev;
int error;
pb = devm_kzalloc(dev, sizeof(*pb), GFP_KERNEL);
if (!pb)
return -ENOMEM;
idev = devm_input_allocate_device(dev);
if (!idev)
return -ENOMEM;
idev->name = pdev->name;
input_set_capability(idev, EV_KEY, KEY_POWER);
error = input_register_device(idev);
if (error)
return error;
pb->idev = idev;
pb->nb.notifier_call = rave_sp_power_button_event;
pb->nb.priority = 128;
error = devm_rave_sp_register_event_notifier(dev, &pb->nb);
if (error)
return error;
return 0;
}
static const struct of_device_id rave_sp_pwrbutton_of_match[] = {
{ .compatible = "zii,rave-sp-pwrbutton" },
{}
};
static struct platform_driver rave_sp_pwrbutton_driver = {
.probe = rave_sp_pwrbutton_probe,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = rave_sp_pwrbutton_of_match,
},
};
module_platform_driver(rave_sp_pwrbutton_driver);
MODULE_DEVICE_TABLE(of, rave_sp_pwrbutton_of_match);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Andrey Vostrikov <andrey.vostrikov@cogentembedded.com>");
MODULE_AUTHOR("Nikita Yushchenko <nikita.yoush@cogentembedded.com>");
MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
MODULE_DESCRIPTION("RAVE SP Power Button driver");
+107
View File
@@ -0,0 +1,107 @@
/*
* 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 <linux/gpio.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);
return 0;
}
static struct platform_driver rb532_button_driver = {
.probe = rb532_button_probe,
.remove = rb532_button_remove,
.driver = {
.name = DRV_NAME,
},
};
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);
+267
View File
@@ -0,0 +1,267 @@
/*
* Regulator haptic driver
*
* Copyright (c) 2014 Samsung Electronics Co., Ltd.
* Author: Jaewon Kim <jaewon02.kim@samsung.com>
* Author: Hyunhee Kim <hyunhee.kim@samsung.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/input.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_data/regulator-haptic.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
#define MAX_MAGNITUDE_SHIFT 16
struct regulator_haptic {
struct device *dev;
struct input_dev *input_dev;
struct regulator *regulator;
struct work_struct work;
struct mutex mutex;
bool active;
bool suspended;
unsigned int max_volt;
unsigned int min_volt;
unsigned int magnitude;
};
static int regulator_haptic_toggle(struct regulator_haptic *haptic, bool on)
{
int error;
if (haptic->active != on) {
error = on ? regulator_enable(haptic->regulator) :
regulator_disable(haptic->regulator);
if (error) {
dev_err(haptic->dev,
"failed to switch regulator %s: %d\n",
on ? "on" : "off", error);
return error;
}
haptic->active = on;
}
return 0;
}
static int regulator_haptic_set_voltage(struct regulator_haptic *haptic,
unsigned int magnitude)
{
u64 volt_mag_multi;
unsigned int intensity;
int error;
volt_mag_multi = (u64)(haptic->max_volt - haptic->min_volt) * magnitude;
intensity = (unsigned int)(volt_mag_multi >> MAX_MAGNITUDE_SHIFT);
error = regulator_set_voltage(haptic->regulator,
intensity + haptic->min_volt,
haptic->max_volt);
if (error) {
dev_err(haptic->dev, "cannot set regulator voltage to %d: %d\n",
intensity + haptic->min_volt, error);
return error;
}
regulator_haptic_toggle(haptic, !!magnitude);
return 0;
}
static void regulator_haptic_work(struct work_struct *work)
{
struct regulator_haptic *haptic = container_of(work,
struct regulator_haptic, work);
mutex_lock(&haptic->mutex);
if (!haptic->suspended)
regulator_haptic_set_voltage(haptic, haptic->magnitude);
mutex_unlock(&haptic->mutex);
}
static int regulator_haptic_play_effect(struct input_dev *input, void *data,
struct ff_effect *effect)
{
struct regulator_haptic *haptic = input_get_drvdata(input);
haptic->magnitude = effect->u.rumble.strong_magnitude;
if (!haptic->magnitude)
haptic->magnitude = effect->u.rumble.weak_magnitude;
schedule_work(&haptic->work);
return 0;
}
static void regulator_haptic_close(struct input_dev *input)
{
struct regulator_haptic *haptic = input_get_drvdata(input);
cancel_work_sync(&haptic->work);
regulator_haptic_set_voltage(haptic, 0);
}
static int __maybe_unused
regulator_haptic_parse_dt(struct device *dev, struct regulator_haptic *haptic)
{
struct device_node *node;
int error;
node = dev->of_node;
if(!node) {
dev_err(dev, "Missing device tree data\n");
return -EINVAL;
}
error = of_property_read_u32(node, "max-microvolt", &haptic->max_volt);
if (error) {
dev_err(dev, "cannot parse max-microvolt\n");
return error;
}
error = of_property_read_u32(node, "min-microvolt", &haptic->min_volt);
if (error) {
dev_err(dev, "cannot parse min-microvolt\n");
return error;
}
return 0;
}
static int regulator_haptic_probe(struct platform_device *pdev)
{
const struct regulator_haptic_data *pdata = dev_get_platdata(&pdev->dev);
struct regulator_haptic *haptic;
struct input_dev *input_dev;
int error;
haptic = devm_kzalloc(&pdev->dev, sizeof(*haptic), GFP_KERNEL);
if (!haptic)
return -ENOMEM;
platform_set_drvdata(pdev, haptic);
haptic->dev = &pdev->dev;
mutex_init(&haptic->mutex);
INIT_WORK(&haptic->work, regulator_haptic_work);
if (pdata) {
haptic->max_volt = pdata->max_volt;
haptic->min_volt = pdata->min_volt;
} else if (IS_ENABLED(CONFIG_OF)) {
error = regulator_haptic_parse_dt(&pdev->dev, haptic);
if (error)
return error;
} else {
dev_err(&pdev->dev, "Missing platform data\n");
return -EINVAL;
}
haptic->regulator = devm_regulator_get_exclusive(&pdev->dev, "haptic");
if (IS_ERR(haptic->regulator)) {
dev_err(&pdev->dev, "failed to get regulator\n");
return PTR_ERR(haptic->regulator);
}
input_dev = devm_input_allocate_device(&pdev->dev);
if (!input_dev)
return -ENOMEM;
haptic->input_dev = input_dev;
haptic->input_dev->name = "regulator-haptic";
haptic->input_dev->dev.parent = &pdev->dev;
haptic->input_dev->close = regulator_haptic_close;
input_set_drvdata(haptic->input_dev, haptic);
input_set_capability(haptic->input_dev, EV_FF, FF_RUMBLE);
error = input_ff_create_memless(input_dev, NULL,
regulator_haptic_play_effect);
if (error) {
dev_err(&pdev->dev, "failed to create force-feedback\n");
return error;
}
error = input_register_device(haptic->input_dev);
if (error) {
dev_err(&pdev->dev, "failed to register input device\n");
return error;
}
return 0;
}
static int __maybe_unused regulator_haptic_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct regulator_haptic *haptic = platform_get_drvdata(pdev);
int error;
error = mutex_lock_interruptible(&haptic->mutex);
if (error)
return error;
regulator_haptic_set_voltage(haptic, 0);
haptic->suspended = true;
mutex_unlock(&haptic->mutex);
return 0;
}
static int __maybe_unused regulator_haptic_resume(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct regulator_haptic *haptic = platform_get_drvdata(pdev);
unsigned int magnitude;
mutex_lock(&haptic->mutex);
haptic->suspended = false;
magnitude = READ_ONCE(haptic->magnitude);
if (magnitude)
regulator_haptic_set_voltage(haptic, magnitude);
mutex_unlock(&haptic->mutex);
return 0;
}
static SIMPLE_DEV_PM_OPS(regulator_haptic_pm_ops,
regulator_haptic_suspend, regulator_haptic_resume);
static const struct of_device_id regulator_haptic_dt_match[] = {
{ .compatible = "regulator-haptic" },
{ /* sentinel */ },
};
MODULE_DEVICE_TABLE(of, regulator_haptic_dt_match);
static struct platform_driver regulator_haptic_driver = {
.probe = regulator_haptic_probe,
.driver = {
.name = "regulator-haptic",
.of_match_table = regulator_haptic_dt_match,
.pm = &regulator_haptic_pm_ops,
},
};
module_platform_driver(regulator_haptic_driver);
MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>");
MODULE_AUTHOR("Hyunhee Kim <hyunhee.kim@samsung.com>");
MODULE_DESCRIPTION("Regulator haptic driver");
MODULE_LICENSE("GPL");
+92
View File
@@ -0,0 +1,92 @@
/*
* 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/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,
IRQF_ONESHOT,
"retu-pwrbutton", idev);
if (error)
return error;
error = input_register_device(idev);
if (error)
return error;
return 0;
}
static struct platform_driver retu_pwrbutton_driver = {
.probe = retu_pwrbutton_probe,
.driver = {
.name = "retu-pwrbutton",
},
};
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");
+111
View File
@@ -0,0 +1,111 @@
/*
* Rockchip RK805 PMIC Power Key driver
*
* Copyright (c) 2017, Fuzhou Rockchip Electronics Co., Ltd
*
* Author: Joseph Chen <chenjh@rock-chips.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/errno.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
static irqreturn_t pwrkey_fall_irq(int irq, void *_pwr)
{
struct input_dev *pwr = _pwr;
input_report_key(pwr, KEY_POWER, 1);
input_sync(pwr);
return IRQ_HANDLED;
}
static irqreturn_t pwrkey_rise_irq(int irq, void *_pwr)
{
struct input_dev *pwr = _pwr;
input_report_key(pwr, KEY_POWER, 0);
input_sync(pwr);
return IRQ_HANDLED;
}
static int rk805_pwrkey_probe(struct platform_device *pdev)
{
struct input_dev *pwr;
int fall_irq, rise_irq;
int err;
pwr = devm_input_allocate_device(&pdev->dev);
if (!pwr) {
dev_err(&pdev->dev, "Can't allocate power button\n");
return -ENOMEM;
}
pwr->name = "rk805 pwrkey";
pwr->phys = "rk805_pwrkey/input0";
pwr->id.bustype = BUS_HOST;
input_set_capability(pwr, EV_KEY, KEY_POWER);
fall_irq = platform_get_irq(pdev, 0);
if (fall_irq < 0) {
dev_err(&pdev->dev, "Can't get fall irq: %d\n", fall_irq);
return fall_irq;
}
rise_irq = platform_get_irq(pdev, 1);
if (rise_irq < 0) {
dev_err(&pdev->dev, "Can't get rise irq: %d\n", rise_irq);
return rise_irq;
}
err = devm_request_any_context_irq(&pwr->dev, fall_irq,
pwrkey_fall_irq,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
"rk805_pwrkey_fall", pwr);
if (err < 0) {
dev_err(&pdev->dev, "Can't register fall irq: %d\n", err);
return err;
}
err = devm_request_any_context_irq(&pwr->dev, rise_irq,
pwrkey_rise_irq,
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"rk805_pwrkey_rise", pwr);
if (err < 0) {
dev_err(&pdev->dev, "Can't register rise irq: %d\n", err);
return err;
}
err = input_register_device(pwr);
if (err) {
dev_err(&pdev->dev, "Can't register power button: %d\n", err);
return err;
}
platform_set_drvdata(pdev, pwr);
device_init_wakeup(&pdev->dev, true);
return 0;
}
static struct platform_driver rk805_pwrkey_driver = {
.probe = rk805_pwrkey_probe,
.driver = {
.name = "rk805-pwrkey",
},
};
module_platform_driver(rk805_pwrkey_driver);
MODULE_AUTHOR("Joseph Chen <chenjh@rock-chips.com>");
MODULE_DESCRIPTION("RK805 PMIC Power Key driver");
MODULE_LICENSE("GPL");
+371
View File
@@ -0,0 +1,371 @@
/*
* 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/devices/rotary-encoder.rst 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/interrupt.h>
#include <linux/input.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/pm.h>
#include <linux/property.h>
#define DRV_NAME "rotary-encoder"
enum rotary_encoder_encoding {
ROTENC_GRAY,
ROTENC_BINARY,
};
struct rotary_encoder {
struct input_dev *input;
struct mutex access_mutex;
u32 steps;
u32 axis;
bool relative_axis;
bool rollover;
enum rotary_encoder_encoding encoding;
unsigned int pos;
struct gpio_descs *gpios;
unsigned int *irq;
bool armed;
signed char dir; /* 1 - clockwise, -1 - CCW */
unsigned int last_stable;
};
static unsigned int rotary_encoder_get_state(struct rotary_encoder *encoder)
{
int i;
unsigned int ret = 0;
for (i = 0; i < encoder->gpios->ndescs; ++i) {
int val = gpiod_get_value_cansleep(encoder->gpios->desc[i]);
/* convert from gray encoding to normal */
if (encoder->encoding == ROTENC_GRAY && ret & 1)
val = !val;
ret = ret << 1 | val;
}
return ret & 3;
}
static void rotary_encoder_report_event(struct rotary_encoder *encoder)
{
if (encoder->relative_axis) {
input_report_rel(encoder->input,
encoder->axis, encoder->dir);
} else {
unsigned int pos = encoder->pos;
if (encoder->dir < 0) {
/* turning counter-clockwise */
if (encoder->rollover)
pos += encoder->steps;
if (pos)
pos--;
} else {
/* turning clockwise */
if (encoder->rollover || pos < encoder->steps)
pos++;
}
if (encoder->rollover)
pos %= encoder->steps;
encoder->pos = pos;
input_report_abs(encoder->input, encoder->axis, encoder->pos);
}
input_sync(encoder->input);
}
static irqreturn_t rotary_encoder_irq(int irq, void *dev_id)
{
struct rotary_encoder *encoder = dev_id;
unsigned int state;
mutex_lock(&encoder->access_mutex);
state = rotary_encoder_get_state(encoder);
switch (state) {
case 0x0:
if (encoder->armed) {
rotary_encoder_report_event(encoder);
encoder->armed = false;
}
break;
case 0x1:
case 0x3:
if (encoder->armed)
encoder->dir = 2 - state;
break;
case 0x2:
encoder->armed = true;
break;
}
mutex_unlock(&encoder->access_mutex);
return IRQ_HANDLED;
}
static irqreturn_t rotary_encoder_half_period_irq(int irq, void *dev_id)
{
struct rotary_encoder *encoder = dev_id;
unsigned int state;
mutex_lock(&encoder->access_mutex);
state = rotary_encoder_get_state(encoder);
if (state & 1) {
encoder->dir = ((encoder->last_stable - state + 1) % 4) - 1;
} else {
if (state != encoder->last_stable) {
rotary_encoder_report_event(encoder);
encoder->last_stable = state;
}
}
mutex_unlock(&encoder->access_mutex);
return IRQ_HANDLED;
}
static irqreturn_t rotary_encoder_quarter_period_irq(int irq, void *dev_id)
{
struct rotary_encoder *encoder = dev_id;
unsigned int state;
mutex_lock(&encoder->access_mutex);
state = rotary_encoder_get_state(encoder);
if ((encoder->last_stable + 1) % 4 == state)
encoder->dir = 1;
else if (encoder->last_stable == (state + 1) % 4)
encoder->dir = -1;
else
goto out;
rotary_encoder_report_event(encoder);
out:
encoder->last_stable = state;
mutex_unlock(&encoder->access_mutex);
return IRQ_HANDLED;
}
static int rotary_encoder_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rotary_encoder *encoder;
struct input_dev *input;
irq_handler_t handler;
u32 steps_per_period;
unsigned int i;
int err;
encoder = devm_kzalloc(dev, sizeof(struct rotary_encoder), GFP_KERNEL);
if (!encoder)
return -ENOMEM;
mutex_init(&encoder->access_mutex);
device_property_read_u32(dev, "rotary-encoder,steps", &encoder->steps);
err = device_property_read_u32(dev, "rotary-encoder,steps-per-period",
&steps_per_period);
if (err) {
/*
* The 'half-period' property has been deprecated, you must
* use 'steps-per-period' and set an appropriate value, but
* we still need to parse it to maintain compatibility. If
* neither property is present we fall back to the one step
* per period behavior.
*/
steps_per_period = device_property_read_bool(dev,
"rotary-encoder,half-period") ? 2 : 1;
}
encoder->rollover =
device_property_read_bool(dev, "rotary-encoder,rollover");
if (!device_property_present(dev, "rotary-encoder,encoding") ||
!device_property_match_string(dev, "rotary-encoder,encoding",
"gray")) {
dev_info(dev, "gray");
encoder->encoding = ROTENC_GRAY;
} else if (!device_property_match_string(dev, "rotary-encoder,encoding",
"binary")) {
dev_info(dev, "binary");
encoder->encoding = ROTENC_BINARY;
} else {
dev_err(dev, "unknown encoding setting\n");
return -EINVAL;
}
device_property_read_u32(dev, "linux,axis", &encoder->axis);
encoder->relative_axis =
device_property_read_bool(dev, "rotary-encoder,relative-axis");
encoder->gpios = devm_gpiod_get_array(dev, NULL, GPIOD_IN);
if (IS_ERR(encoder->gpios)) {
dev_err(dev, "unable to get gpios\n");
return PTR_ERR(encoder->gpios);
}
if (encoder->gpios->ndescs < 2) {
dev_err(dev, "not enough gpios found\n");
return -EINVAL;
}
input = devm_input_allocate_device(dev);
if (!input)
return -ENOMEM;
encoder->input = input;
input->name = pdev->name;
input->id.bustype = BUS_HOST;
input->dev.parent = dev;
if (encoder->relative_axis)
input_set_capability(input, EV_REL, encoder->axis);
else
input_set_abs_params(input,
encoder->axis, 0, encoder->steps, 0, 1);
switch (steps_per_period >> (encoder->gpios->ndescs - 2)) {
case 4:
handler = &rotary_encoder_quarter_period_irq;
encoder->last_stable = rotary_encoder_get_state(encoder);
break;
case 2:
handler = &rotary_encoder_half_period_irq;
encoder->last_stable = rotary_encoder_get_state(encoder);
break;
case 1:
handler = &rotary_encoder_irq;
break;
default:
dev_err(dev, "'%d' is not a valid steps-per-period value\n",
steps_per_period);
return -EINVAL;
}
encoder->irq =
devm_kcalloc(dev,
encoder->gpios->ndescs, sizeof(*encoder->irq),
GFP_KERNEL);
if (!encoder->irq)
return -ENOMEM;
for (i = 0; i < encoder->gpios->ndescs; ++i) {
encoder->irq[i] = gpiod_to_irq(encoder->gpios->desc[i]);
err = devm_request_threaded_irq(dev, encoder->irq[i],
NULL, handler,
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
DRV_NAME, encoder);
if (err) {
dev_err(dev, "unable to request IRQ %d (gpio#%d)\n",
encoder->irq[i], i);
return err;
}
}
err = input_register_device(input);
if (err) {
dev_err(dev, "failed to register input device\n");
return err;
}
device_init_wakeup(dev,
device_property_read_bool(dev, "wakeup-source"));
platform_set_drvdata(pdev, encoder);
return 0;
}
static int __maybe_unused rotary_encoder_suspend(struct device *dev)
{
struct rotary_encoder *encoder = dev_get_drvdata(dev);
unsigned int i;
if (device_may_wakeup(dev)) {
for (i = 0; i < encoder->gpios->ndescs; ++i)
enable_irq_wake(encoder->irq[i]);
}
return 0;
}
static int __maybe_unused rotary_encoder_resume(struct device *dev)
{
struct rotary_encoder *encoder = dev_get_drvdata(dev);
unsigned int i;
if (device_may_wakeup(dev)) {
for (i = 0; i < encoder->gpios->ndescs; ++i)
disable_irq_wake(encoder->irq[i]);
}
return 0;
}
static SIMPLE_DEV_PM_OPS(rotary_encoder_pm_ops,
rotary_encoder_suspend, rotary_encoder_resume);
#ifdef CONFIG_OF
static const struct of_device_id rotary_encoder_of_match[] = {
{ .compatible = "rotary-encoder", },
{ },
};
MODULE_DEVICE_TABLE(of, rotary_encoder_of_match);
#endif
static struct platform_driver rotary_encoder_driver = {
.probe = rotary_encoder_probe,
.driver = {
.name = DRV_NAME,
.pm = &rotary_encoder_pm_ops,
.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");
+154
View File
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2018 Spreadtrum Communications Inc.
*/
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/input.h>
#include <linux/workqueue.h>
#define CUR_DRV_CAL_SEL GENMASK(13, 12)
#define SLP_LDOVIBR_PD_EN BIT(9)
#define LDO_VIBR_PD BIT(8)
struct vibra_info {
struct input_dev *input_dev;
struct work_struct play_work;
struct regmap *regmap;
u32 base;
u32 strength;
bool enabled;
};
static void sc27xx_vibra_set(struct vibra_info *info, bool on)
{
if (on) {
regmap_update_bits(info->regmap, info->base, LDO_VIBR_PD, 0);
regmap_update_bits(info->regmap, info->base,
SLP_LDOVIBR_PD_EN, 0);
info->enabled = true;
} else {
regmap_update_bits(info->regmap, info->base, LDO_VIBR_PD,
LDO_VIBR_PD);
regmap_update_bits(info->regmap, info->base,
SLP_LDOVIBR_PD_EN, SLP_LDOVIBR_PD_EN);
info->enabled = false;
}
}
static int sc27xx_vibra_hw_init(struct vibra_info *info)
{
return regmap_update_bits(info->regmap, info->base, CUR_DRV_CAL_SEL, 0);
}
static void sc27xx_vibra_play_work(struct work_struct *work)
{
struct vibra_info *info = container_of(work, struct vibra_info,
play_work);
if (info->strength && !info->enabled)
sc27xx_vibra_set(info, true);
else if (info->strength == 0 && info->enabled)
sc27xx_vibra_set(info, false);
}
static int sc27xx_vibra_play(struct input_dev *input, void *data,
struct ff_effect *effect)
{
struct vibra_info *info = input_get_drvdata(input);
info->strength = effect->u.rumble.weak_magnitude;
schedule_work(&info->play_work);
return 0;
}
static void sc27xx_vibra_close(struct input_dev *input)
{
struct vibra_info *info = input_get_drvdata(input);
cancel_work_sync(&info->play_work);
if (info->enabled)
sc27xx_vibra_set(info, false);
}
static int sc27xx_vibra_probe(struct platform_device *pdev)
{
struct vibra_info *info;
int error;
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
if (!info->regmap) {
dev_err(&pdev->dev, "failed to get vibrator regmap.\n");
return -ENODEV;
}
error = device_property_read_u32(&pdev->dev, "reg", &info->base);
if (error) {
dev_err(&pdev->dev, "failed to get vibrator base address.\n");
return error;
}
info->input_dev = devm_input_allocate_device(&pdev->dev);
if (!info->input_dev) {
dev_err(&pdev->dev, "failed to allocate input device.\n");
return -ENOMEM;
}
info->input_dev->name = "sc27xx:vibrator";
info->input_dev->id.version = 0;
info->input_dev->close = sc27xx_vibra_close;
input_set_drvdata(info->input_dev, info);
input_set_capability(info->input_dev, EV_FF, FF_RUMBLE);
INIT_WORK(&info->play_work, sc27xx_vibra_play_work);
info->enabled = false;
error = sc27xx_vibra_hw_init(info);
if (error) {
dev_err(&pdev->dev, "failed to initialize the vibrator.\n");
return error;
}
error = input_ff_create_memless(info->input_dev, NULL,
sc27xx_vibra_play);
if (error) {
dev_err(&pdev->dev, "failed to register vibrator to FF.\n");
return error;
}
error = input_register_device(info->input_dev);
if (error) {
dev_err(&pdev->dev, "failed to register input device.\n");
return error;
}
return 0;
}
static const struct of_device_id sc27xx_vibra_of_match[] = {
{ .compatible = "sprd,sc2731-vibrator", },
{}
};
MODULE_DEVICE_TABLE(of, sc27xx_vibra_of_match);
static struct platform_driver sc27xx_vibra_driver = {
.driver = {
.name = "sc27xx-vibrator",
.of_match_table = sc27xx_vibra_of_match,
},
.probe = sc27xx_vibra_probe,
};
module_platform_driver(sc27xx_vibra_driver);
MODULE_DESCRIPTION("Spreadtrum SC27xx Vibrator Driver");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>");
+164
View File
@@ -0,0 +1,164 @@
/*
* 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/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;
platform_set_drvdata(pdev, 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);
return error;
}
static int sgi_buttons_remove(struct platform_device *pdev)
{
struct buttons_dev *bdev = platform_get_drvdata(pdev);
input_unregister_polled_device(bdev->poll_dev);
input_free_polled_device(bdev->poll_dev);
kfree(bdev);
return 0;
}
static struct platform_driver sgi_buttons_driver = {
.probe = sgi_buttons_probe,
.remove = sgi_buttons_remove,
.driver = {
.name = "sgibtns",
},
};
module_platform_driver(sgi_buttons_driver);
MODULE_LICENSE("GPL");
+208
View File
@@ -0,0 +1,208 @@
/*
* Power key driver for SiRF PrimaII
*
* Copyright (c) 2013 - 2014 Cambridge Silicon Radio Limited, a CSR plc group
* company.
*
* Licensed under GPLv2 or later.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/rtc/sirfsoc_rtciobrg.h>
#include <linux/of.h>
#include <linux/workqueue.h>
struct sirfsoc_pwrc_drvdata {
u32 pwrc_base;
struct input_dev *input;
struct delayed_work work;
};
#define PWRC_ON_KEY_BIT (1 << 0)
#define PWRC_INT_STATUS 0xc
#define PWRC_INT_MASK 0x10
#define PWRC_PIN_STATUS 0x14
#define PWRC_KEY_DETECT_UP_TIME 20 /* ms*/
static int sirfsoc_pwrc_is_on_key_down(struct sirfsoc_pwrc_drvdata *pwrcdrv)
{
u32 state = sirfsoc_rtc_iobrg_readl(pwrcdrv->pwrc_base +
PWRC_PIN_STATUS);
return !(state & PWRC_ON_KEY_BIT); /* ON_KEY is active low */
}
static void sirfsoc_pwrc_report_event(struct work_struct *work)
{
struct sirfsoc_pwrc_drvdata *pwrcdrv =
container_of(work, struct sirfsoc_pwrc_drvdata, work.work);
if (sirfsoc_pwrc_is_on_key_down(pwrcdrv)) {
schedule_delayed_work(&pwrcdrv->work,
msecs_to_jiffies(PWRC_KEY_DETECT_UP_TIME));
} else {
input_event(pwrcdrv->input, EV_KEY, KEY_POWER, 0);
input_sync(pwrcdrv->input);
}
}
static irqreturn_t sirfsoc_pwrc_isr(int irq, void *dev_id)
{
struct sirfsoc_pwrc_drvdata *pwrcdrv = dev_id;
u32 int_status;
int_status = sirfsoc_rtc_iobrg_readl(pwrcdrv->pwrc_base +
PWRC_INT_STATUS);
sirfsoc_rtc_iobrg_writel(int_status & ~PWRC_ON_KEY_BIT,
pwrcdrv->pwrc_base + PWRC_INT_STATUS);
input_event(pwrcdrv->input, EV_KEY, KEY_POWER, 1);
input_sync(pwrcdrv->input);
schedule_delayed_work(&pwrcdrv->work,
msecs_to_jiffies(PWRC_KEY_DETECT_UP_TIME));
return IRQ_HANDLED;
}
static void sirfsoc_pwrc_toggle_interrupts(struct sirfsoc_pwrc_drvdata *pwrcdrv,
bool enable)
{
u32 int_mask;
int_mask = sirfsoc_rtc_iobrg_readl(pwrcdrv->pwrc_base + PWRC_INT_MASK);
if (enable)
int_mask |= PWRC_ON_KEY_BIT;
else
int_mask &= ~PWRC_ON_KEY_BIT;
sirfsoc_rtc_iobrg_writel(int_mask, pwrcdrv->pwrc_base + PWRC_INT_MASK);
}
static int sirfsoc_pwrc_open(struct input_dev *input)
{
struct sirfsoc_pwrc_drvdata *pwrcdrv = input_get_drvdata(input);
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, true);
return 0;
}
static void sirfsoc_pwrc_close(struct input_dev *input)
{
struct sirfsoc_pwrc_drvdata *pwrcdrv = input_get_drvdata(input);
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, false);
cancel_delayed_work_sync(&pwrcdrv->work);
}
static const struct of_device_id sirfsoc_pwrc_of_match[] = {
{ .compatible = "sirf,prima2-pwrc" },
{},
};
MODULE_DEVICE_TABLE(of, sirfsoc_pwrc_of_match);
static int sirfsoc_pwrc_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct sirfsoc_pwrc_drvdata *pwrcdrv;
int irq;
int error;
pwrcdrv = devm_kzalloc(&pdev->dev, sizeof(struct sirfsoc_pwrc_drvdata),
GFP_KERNEL);
if (!pwrcdrv) {
dev_info(&pdev->dev, "Not enough memory for the device data\n");
return -ENOMEM;
}
/*
* We can't use of_iomap because pwrc is not mapped in memory,
* the so-called base address is only offset in rtciobrg
*/
error = of_property_read_u32(np, "reg", &pwrcdrv->pwrc_base);
if (error) {
dev_err(&pdev->dev,
"unable to find base address of pwrc node in dtb\n");
return error;
}
pwrcdrv->input = devm_input_allocate_device(&pdev->dev);
if (!pwrcdrv->input)
return -ENOMEM;
pwrcdrv->input->name = "sirfsoc pwrckey";
pwrcdrv->input->phys = "pwrc/input0";
pwrcdrv->input->evbit[0] = BIT_MASK(EV_KEY);
input_set_capability(pwrcdrv->input, EV_KEY, KEY_POWER);
INIT_DELAYED_WORK(&pwrcdrv->work, sirfsoc_pwrc_report_event);
pwrcdrv->input->open = sirfsoc_pwrc_open;
pwrcdrv->input->close = sirfsoc_pwrc_close;
input_set_drvdata(pwrcdrv->input, pwrcdrv);
/* Make sure the device is quiesced */
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, false);
irq = platform_get_irq(pdev, 0);
error = devm_request_irq(&pdev->dev, irq,
sirfsoc_pwrc_isr, 0,
"sirfsoc_pwrc_int", pwrcdrv);
if (error) {
dev_err(&pdev->dev, "unable to claim irq %d, error: %d\n",
irq, error);
return error;
}
error = input_register_device(pwrcdrv->input);
if (error) {
dev_err(&pdev->dev,
"unable to register input device, error: %d\n",
error);
return error;
}
dev_set_drvdata(&pdev->dev, pwrcdrv);
device_init_wakeup(&pdev->dev, 1);
return 0;
}
static int __maybe_unused sirfsoc_pwrc_resume(struct device *dev)
{
struct sirfsoc_pwrc_drvdata *pwrcdrv = dev_get_drvdata(dev);
struct input_dev *input = pwrcdrv->input;
/*
* Do not mask pwrc interrupt as we want pwrc work as a wakeup source
* if users touch X_ONKEY_B, see arch/arm/mach-prima2/pm.c
*/
mutex_lock(&input->mutex);
if (input->users)
sirfsoc_pwrc_toggle_interrupts(pwrcdrv, true);
mutex_unlock(&input->mutex);
return 0;
}
static SIMPLE_DEV_PM_OPS(sirfsoc_pwrc_pm_ops, NULL, sirfsoc_pwrc_resume);
static struct platform_driver sirfsoc_pwrc_driver = {
.probe = sirfsoc_pwrc_probe,
.driver = {
.name = "sirfsoc-pwrc",
.pm = &sirfsoc_pwrc_pm_ops,
.of_match_table = sirfsoc_pwrc_of_match,
}
};
module_platform_driver(sirfsoc_pwrc_driver);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Binghua Duan <Binghua.Duan@csr.com>, Xianglong Du <Xianglong.Du@csr.com>");
MODULE_DESCRIPTION("CSR Prima2 PWRC Driver");
MODULE_ALIAS("platform:sirfsoc-pwrc");
+398
View File
@@ -0,0 +1,398 @@
/*
* Supports for the button array on SoC tablets originally running
* Windows 8.
*
* (C) Copyright 2014 Intel Corporation
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2
* of the License.
*/
#include <linux/module.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/acpi.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio_keys.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
struct soc_button_info {
const char *name;
int acpi_index;
unsigned int event_type;
unsigned int event_code;
bool autorepeat;
bool wakeup;
};
/*
* Some of the buttons like volume up/down are auto repeat, while others
* are not. To support both, we register two platform devices, and put
* buttons into them based on whether the key should be auto repeat.
*/
#define BUTTON_TYPES 2
struct soc_button_data {
struct platform_device *children[BUTTON_TYPES];
};
/*
* Get the Nth GPIO number from the ACPI object.
*/
static int soc_button_lookup_gpio(struct device *dev, int acpi_index)
{
struct gpio_desc *desc;
int gpio;
desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS);
if (IS_ERR(desc))
return PTR_ERR(desc);
gpio = desc_to_gpio(desc);
gpiod_put(desc);
return gpio;
}
static struct platform_device *
soc_button_device_create(struct platform_device *pdev,
const struct soc_button_info *button_info,
bool autorepeat)
{
const struct soc_button_info *info;
struct platform_device *pd;
struct gpio_keys_button *gpio_keys;
struct gpio_keys_platform_data *gpio_keys_pdata;
int n_buttons = 0;
int gpio;
int error;
for (info = button_info; info->name; info++)
if (info->autorepeat == autorepeat)
n_buttons++;
gpio_keys_pdata = devm_kzalloc(&pdev->dev,
sizeof(*gpio_keys_pdata) +
sizeof(*gpio_keys) * n_buttons,
GFP_KERNEL);
if (!gpio_keys_pdata)
return ERR_PTR(-ENOMEM);
gpio_keys = (void *)(gpio_keys_pdata + 1);
n_buttons = 0;
for (info = button_info; info->name; info++) {
if (info->autorepeat != autorepeat)
continue;
gpio = soc_button_lookup_gpio(&pdev->dev, info->acpi_index);
if (!gpio_is_valid(gpio))
continue;
gpio_keys[n_buttons].type = info->event_type;
gpio_keys[n_buttons].code = info->event_code;
gpio_keys[n_buttons].gpio = gpio;
gpio_keys[n_buttons].active_low = 1;
gpio_keys[n_buttons].desc = info->name;
gpio_keys[n_buttons].wakeup = info->wakeup;
/* These devices often use cheap buttons, use 50 ms debounce */
gpio_keys[n_buttons].debounce_interval = 50;
n_buttons++;
}
if (n_buttons == 0) {
error = -ENODEV;
goto err_free_mem;
}
gpio_keys_pdata->buttons = gpio_keys;
gpio_keys_pdata->nbuttons = n_buttons;
gpio_keys_pdata->rep = autorepeat;
pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO);
if (!pd) {
error = -ENOMEM;
goto err_free_mem;
}
error = platform_device_add_data(pd, gpio_keys_pdata,
sizeof(*gpio_keys_pdata));
if (error)
goto err_free_pdev;
error = platform_device_add(pd);
if (error)
goto err_free_pdev;
return pd;
err_free_pdev:
platform_device_put(pd);
err_free_mem:
devm_kfree(&pdev->dev, gpio_keys_pdata);
return ERR_PTR(error);
}
static int soc_button_get_acpi_object_int(const union acpi_object *obj)
{
if (obj->type != ACPI_TYPE_INTEGER)
return -1;
return obj->integer.value;
}
/* Parse a single ACPI0011 _DSD button descriptor */
static int soc_button_parse_btn_desc(struct device *dev,
const union acpi_object *desc,
int collection_uid,
struct soc_button_info *info)
{
int upage, usage;
if (desc->type != ACPI_TYPE_PACKAGE ||
desc->package.count != 5 ||
/* First byte should be 1 (control) */
soc_button_get_acpi_object_int(&desc->package.elements[0]) != 1 ||
/* Third byte should be collection uid */
soc_button_get_acpi_object_int(&desc->package.elements[2]) !=
collection_uid) {
dev_err(dev, "Invalid ACPI Button Descriptor\n");
return -ENODEV;
}
info->event_type = EV_KEY;
info->acpi_index =
soc_button_get_acpi_object_int(&desc->package.elements[1]);
upage = soc_button_get_acpi_object_int(&desc->package.elements[3]);
usage = soc_button_get_acpi_object_int(&desc->package.elements[4]);
/*
* The UUID: fa6bd625-9ce8-470d-a2c7-b3ca36c4282e descriptors use HID
* usage page and usage codes, but otherwise the device is not HID
* compliant: it uses one irq per button instead of generating HID
* input reports and some buttons should generate wakeups where as
* others should not, so we cannot use the HID subsystem.
*
* Luckily all devices only use a few usage page + usage combinations,
* so we can simply check for the known combinations here.
*/
if (upage == 0x01 && usage == 0x81) {
info->name = "power";
info->event_code = KEY_POWER;
info->wakeup = true;
} else if (upage == 0x07 && usage == 0xe3) {
info->name = "home";
info->event_code = KEY_LEFTMETA;
info->wakeup = true;
} else if (upage == 0x0c && usage == 0xe9) {
info->name = "volume_up";
info->event_code = KEY_VOLUMEUP;
info->autorepeat = true;
} else if (upage == 0x0c && usage == 0xea) {
info->name = "volume_down";
info->event_code = KEY_VOLUMEDOWN;
info->autorepeat = true;
} else {
dev_warn(dev, "Unknown button index %d upage %02x usage %02x, ignoring\n",
info->acpi_index, upage, usage);
info->name = "unknown";
info->event_code = KEY_RESERVED;
}
return 0;
}
/* ACPI0011 _DSD btns descriptors UUID: fa6bd625-9ce8-470d-a2c7-b3ca36c4282e */
static const u8 btns_desc_uuid[16] = {
0x25, 0xd6, 0x6b, 0xfa, 0xe8, 0x9c, 0x0d, 0x47,
0xa2, 0xc7, 0xb3, 0xca, 0x36, 0xc4, 0x28, 0x2e
};
/* Parse ACPI0011 _DSD button descriptors */
static struct soc_button_info *soc_button_get_button_info(struct device *dev)
{
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER };
const union acpi_object *desc, *el0, *uuid, *btns_desc = NULL;
struct soc_button_info *button_info;
acpi_status status;
int i, btn, collection_uid = -1;
status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL,
&buf, ACPI_TYPE_PACKAGE);
if (ACPI_FAILURE(status)) {
dev_err(dev, "ACPI _DSD object not found\n");
return ERR_PTR(-ENODEV);
}
/* Look for the Button Descriptors UUID */
desc = buf.pointer;
for (i = 0; (i + 1) < desc->package.count; i += 2) {
uuid = &desc->package.elements[i];
if (uuid->type != ACPI_TYPE_BUFFER ||
uuid->buffer.length != 16 ||
desc->package.elements[i + 1].type != ACPI_TYPE_PACKAGE) {
break;
}
if (memcmp(uuid->buffer.pointer, btns_desc_uuid, 16) == 0) {
btns_desc = &desc->package.elements[i + 1];
break;
}
}
if (!btns_desc) {
dev_err(dev, "ACPI Button Descriptors not found\n");
button_info = ERR_PTR(-ENODEV);
goto out;
}
/* The first package describes the collection */
el0 = &btns_desc->package.elements[0];
if (el0->type == ACPI_TYPE_PACKAGE &&
el0->package.count == 5 &&
/* First byte should be 0 (collection) */
soc_button_get_acpi_object_int(&el0->package.elements[0]) == 0 &&
/* Third byte should be 0 (top level collection) */
soc_button_get_acpi_object_int(&el0->package.elements[2]) == 0) {
collection_uid = soc_button_get_acpi_object_int(
&el0->package.elements[1]);
}
if (collection_uid == -1) {
dev_err(dev, "Invalid Button Collection Descriptor\n");
button_info = ERR_PTR(-ENODEV);
goto out;
}
/* There are package.count - 1 buttons + 1 terminating empty entry */
button_info = devm_kcalloc(dev, btns_desc->package.count,
sizeof(*button_info), GFP_KERNEL);
if (!button_info) {
button_info = ERR_PTR(-ENOMEM);
goto out;
}
/* Parse the button descriptors */
for (i = 1, btn = 0; i < btns_desc->package.count; i++, btn++) {
if (soc_button_parse_btn_desc(dev,
&btns_desc->package.elements[i],
collection_uid,
&button_info[btn])) {
button_info = ERR_PTR(-ENODEV);
goto out;
}
}
out:
kfree(buf.pointer);
return button_info;
}
static int soc_button_remove(struct platform_device *pdev)
{
struct soc_button_data *priv = platform_get_drvdata(pdev);
int i;
for (i = 0; i < BUTTON_TYPES; i++)
if (priv->children[i])
platform_device_unregister(priv->children[i]);
return 0;
}
static int soc_button_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
const struct acpi_device_id *id;
struct soc_button_info *button_info;
struct soc_button_data *priv;
struct platform_device *pd;
int i;
int error;
id = acpi_match_device(dev->driver->acpi_match_table, dev);
if (!id)
return -ENODEV;
if (!id->driver_data) {
button_info = soc_button_get_button_info(dev);
if (IS_ERR(button_info))
return PTR_ERR(button_info);
} else {
button_info = (struct soc_button_info *)id->driver_data;
}
error = gpiod_count(dev, NULL);
if (error < 0) {
dev_dbg(dev, "no GPIO attached, ignoring...\n");
return -ENODEV;
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
platform_set_drvdata(pdev, priv);
for (i = 0; i < BUTTON_TYPES; i++) {
pd = soc_button_device_create(pdev, button_info, i == 0);
if (IS_ERR(pd)) {
error = PTR_ERR(pd);
if (error != -ENODEV) {
soc_button_remove(pdev);
return error;
}
continue;
}
priv->children[i] = pd;
}
if (!priv->children[0] && !priv->children[1])
return -ENODEV;
if (!id->driver_data)
devm_kfree(dev, button_info);
return 0;
}
/*
* Definition of buttons on the tablet. The ACPI index of each button
* is defined in section 2.8.7.2 of "Windows ACPI Design Guide for SoC
* Platforms"
*/
static struct soc_button_info soc_button_PNP0C40[] = {
{ "power", 0, EV_KEY, KEY_POWER, false, true },
{ "home", 1, EV_KEY, KEY_LEFTMETA, false, true },
{ "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false },
{ "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false },
{ "rotation_lock", 4, EV_KEY, KEY_ROTATE_LOCK_TOGGLE, false, false },
{ }
};
static const struct acpi_device_id soc_button_acpi_match[] = {
{ "PNP0C40", (unsigned long)soc_button_PNP0C40 },
{ "ACPI0011", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, soc_button_acpi_match);
static struct platform_driver soc_button_driver = {
.probe = soc_button_probe,
.remove = soc_button_remove,
.driver = {
.name = KBUILD_MODNAME,
.acpi_match_table = ACPI_PTR(soc_button_acpi_match),
},
};
module_platform_driver(soc_button_driver);
MODULE_LICENSE("GPL");
+364
View File
@@ -0,0 +1,364 @@
/*
* 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) {
sbus_writeb(0x01, info->regs + 0);
sbus_writeb(0x00, info->regs + 2);
sbus_writeb((count >> 16) & 0xff, info->regs + 3);
sbus_writeb((count >> 8) & 0xff, info->regs + 4);
sbus_writeb(0x00, info->regs + 5);
} else {
sbus_writeb(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 */
sbus_writeb(sbus_readb(info->enable_reg) | 3, info->enable_reg);
/* set command for counter 2, 2 byte write */
sbus_writeb(0xB6, info->freq_regs + 1);
/* select desired HZ */
sbus_writeb(count & 0xff, info->freq_regs + 0);
sbus_writeb((count >> 8) & 0xff, info->freq_regs + 0);
} else {
/* disable counter 2 */
sbus_writeb(sbus_readb(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 = platform_get_drvdata(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;
platform_set_drvdata(op, state);
err = sparcspkr_probe(&op->dev);
if (err)
goto out_clear_drvdata;
return 0;
out_clear_drvdata:
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 = platform_get_drvdata(op);
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);
kfree(state);
return 0;
}
static const struct of_device_id bbc_beep_match[] = {
{
.name = "beep",
.compatible = "SUNW,bbc-beep",
},
{},
};
MODULE_DEVICE_TABLE(of, bbc_beep_match);
static struct platform_driver bbc_beep_driver = {
.driver = {
.name = "bbcbeep",
.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;
platform_set_drvdata(op, state);
err = sparcspkr_probe(&op->dev);
if (err)
goto out_clear_drvdata;
return 0;
out_clear_drvdata:
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 = platform_get_drvdata(op);
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);
kfree(state);
return 0;
}
static const struct of_device_id grover_beep_match[] = {
{
.name = "beep",
.compatible = "SUNW,smbus-beep",
},
{},
};
MODULE_DEVICE_TABLE(of, grover_beep_match);
static struct platform_driver grover_beep_driver = {
.driver = {
.name = "groverbeep",
.of_match_table = grover_beep_match,
},
.probe = grover_beep_probe,
.remove = grover_remove,
.shutdown = sparcspkr_shutdown,
};
static struct platform_driver * const drivers[] = {
&bbc_beep_driver,
&grover_beep_driver,
};
static int __init sparcspkr_init(void)
{
return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
}
static void __exit sparcspkr_exit(void)
{
platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
}
module_init(sparcspkr_init);
module_exit(sparcspkr_exit);
+170
View File
@@ -0,0 +1,170 @@
/*
* Texas Instruments' TPS65217 and TPS65218 Power Button Input Driver
*
* Copyright (C) 2014 Texas Instruments Incorporated - http://www.ti.com/
* Author: Felipe Balbi <balbi@ti.com>
* Author: Marcin Niestroj <m.niestroj@grinn-global.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 "as is" WITHOUT ANY WARRANTY of any
* kind, whether express or implied; without even the implied warranty
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/tps65217.h>
#include <linux/mfd/tps65218.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/slab.h>
struct tps6521x_data {
unsigned int reg_status;
unsigned int pb_mask;
const char *name;
};
static const struct tps6521x_data tps65217_data = {
.reg_status = TPS65217_REG_STATUS,
.pb_mask = TPS65217_STATUS_PB,
.name = "tps65217_pwrbutton",
};
static const struct tps6521x_data tps65218_data = {
.reg_status = TPS65218_REG_STATUS,
.pb_mask = TPS65218_STATUS_PB_STATE,
.name = "tps65218_pwrbutton",
};
struct tps6521x_pwrbutton {
struct device *dev;
struct regmap *regmap;
struct input_dev *idev;
const struct tps6521x_data *data;
char phys[32];
};
static const struct of_device_id of_tps6521x_pb_match[] = {
{ .compatible = "ti,tps65217-pwrbutton", .data = &tps65217_data },
{ .compatible = "ti,tps65218-pwrbutton", .data = &tps65218_data },
{ },
};
MODULE_DEVICE_TABLE(of, of_tps6521x_pb_match);
static irqreturn_t tps6521x_pb_irq(int irq, void *_pwr)
{
struct tps6521x_pwrbutton *pwr = _pwr;
const struct tps6521x_data *tps_data = pwr->data;
unsigned int reg;
int error;
error = regmap_read(pwr->regmap, tps_data->reg_status, &reg);
if (error) {
dev_err(pwr->dev, "can't read register: %d\n", error);
goto out;
}
if (reg & tps_data->pb_mask) {
input_report_key(pwr->idev, KEY_POWER, 1);
pm_wakeup_event(pwr->dev, 0);
} else {
input_report_key(pwr->idev, KEY_POWER, 0);
}
input_sync(pwr->idev);
out:
return IRQ_HANDLED;
}
static int tps6521x_pb_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct tps6521x_pwrbutton *pwr;
struct input_dev *idev;
const struct of_device_id *match;
int error;
int irq;
match = of_match_node(of_tps6521x_pb_match, dev->of_node);
if (!match)
return -ENXIO;
pwr = devm_kzalloc(dev, sizeof(*pwr), GFP_KERNEL);
if (!pwr)
return -ENOMEM;
pwr->data = match->data;
idev = devm_input_allocate_device(dev);
if (!idev)
return -ENOMEM;
idev->name = pwr->data->name;
snprintf(pwr->phys, sizeof(pwr->phys), "%s/input0",
pwr->data->name);
idev->phys = pwr->phys;
idev->dev.parent = dev;
idev->id.bustype = BUS_I2C;
input_set_capability(idev, EV_KEY, KEY_POWER);
pwr->regmap = dev_get_regmap(dev->parent, NULL);
pwr->dev = dev;
pwr->idev = idev;
device_init_wakeup(dev, true);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(dev, "No IRQ resource!\n");
return -EINVAL;
}
error = devm_request_threaded_irq(dev, irq, NULL, tps6521x_pb_irq,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING |
IRQF_ONESHOT,
pwr->data->name, pwr);
if (error) {
dev_err(dev, "failed to request IRQ #%d: %d\n", irq, error);
return error;
}
error= input_register_device(idev);
if (error) {
dev_err(dev, "Can't register power button: %d\n", error);
return error;
}
return 0;
}
static const struct platform_device_id tps6521x_pwrbtn_id_table[] = {
{ "tps65218-pwrbutton", },
{ "tps65217-pwrbutton", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, tps6521x_pwrbtn_id_table);
static struct platform_driver tps6521x_pb_driver = {
.probe = tps6521x_pb_probe,
.driver = {
.name = "tps6521x_pwrbutton",
.of_match_table = of_tps6521x_pb_match,
},
.id_table = tps6521x_pwrbtn_id_table,
};
module_platform_driver(tps6521x_pb_driver);
MODULE_DESCRIPTION("TPS6521X Power Button");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
+114
View File
@@ -0,0 +1,114 @@
/**
* 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/mfd/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 twl4030_pwrbutton_probe(struct platform_device *pdev)
{
struct input_dev *pwr;
int irq = platform_get_irq(pdev, 0);
int err;
pwr = devm_input_allocate_device(&pdev->dev);
if (!pwr) {
dev_err(&pdev->dev, "Can't allocate power button\n");
return -ENOMEM;
}
input_set_capability(pwr, EV_KEY, KEY_POWER);
pwr->name = "twl4030_pwrbutton";
pwr->phys = "twl4030_pwrbutton/input0";
pwr->dev.parent = &pdev->dev;
err = devm_request_threaded_irq(&pdev->dev, irq, NULL, powerbutton_irq,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING |
IRQF_ONESHOT,
"twl4030_pwrbutton", pwr);
if (err < 0) {
dev_err(&pdev->dev, "Can't get IRQ for pwrbutton: %d\n", err);
return err;
}
err = input_register_device(pwr);
if (err) {
dev_err(&pdev->dev, "Can't register power button: %d\n", err);
return err;
}
device_init_wakeup(&pdev->dev, true);
return 0;
}
#ifdef CONFIG_OF
static const struct of_device_id twl4030_pwrbutton_dt_match_table[] = {
{ .compatible = "ti,twl4030-pwrbutton" },
{},
};
MODULE_DEVICE_TABLE(of, twl4030_pwrbutton_dt_match_table);
#endif
static struct platform_driver twl4030_pwrbutton_driver = {
.probe = twl4030_pwrbutton_probe,
.driver = {
.name = "twl4030_pwrbutton",
.of_match_table = of_match_ptr(twl4030_pwrbutton_dt_match_table),
},
};
module_platform_driver(twl4030_pwrbutton_driver);
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>");
+264
View File
@@ -0,0 +1,264 @@
/*
* 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/mfd/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, &reg, 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,
&reg, 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,
&reg, 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,
&reg, 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,
&reg, 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 ***/
static int __maybe_unused 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 __maybe_unused twl4030_vibra_resume(struct device *dev)
{
vibra_disable_leds();
return 0;
}
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 *parent)
{
struct device_node *node;
if (pdata && pdata->coexist)
return true;
node = of_get_child_by_name(parent, "codec");
if (node) {
of_node_put(node);
return true;
}
return false;
}
static int twl4030_vibra_probe(struct platform_device *pdev)
{
struct twl4030_vibra_data *pdata = dev_get_platdata(&pdev->dev);
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->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",
.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");
+382
View File
@@ -0,0 +1,382 @@
/*
* 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 work_struct play_work;
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);
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(info->dev, "Vibra is configured for audio\n");
return;
}
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);
}
static int vibra_play(struct input_dev *input, void *data,
struct ff_effect *effect)
{
struct vibra_info *info = input_get_drvdata(input);
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;
schedule_work(&info->play_work);
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);
if (info->enabled)
twl6040_vibra_disable(info);
}
static int __maybe_unused twl6040_vibra_suspend(struct device *dev)
{
struct platform_device *pdev = to_platform_device(dev);
struct vibra_info *info = platform_get_drvdata(pdev);
cancel_work_sync(&info->play_work);
if (info->enabled)
twl6040_vibra_disable(info);
return 0;
}
static SIMPLE_DEV_PM_OPS(twl6040_vibra_pm_ops, twl6040_vibra_suspend, NULL);
static int twl6040_vibra_probe(struct platform_device *pdev)
{
struct device *twl6040_core_dev = pdev->dev.parent;
struct device_node *twl6040_core_node;
struct vibra_info *info;
int vddvibl_uV = 0;
int vddvibr_uV = 0;
int error;
twl6040_core_node = of_get_child_by_name(twl6040_core_dev->of_node,
"vibra");
if (!twl6040_core_node) {
dev_err(&pdev->dev, "parent of node is missing?\n");
return -EINVAL;
}
info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
if (!info) {
of_node_put(twl6040_core_node);
dev_err(&pdev->dev, "couldn't allocate memory\n");
return -ENOMEM;
}
info->dev = &pdev->dev;
info->twl6040 = dev_get_drvdata(pdev->dev.parent);
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);
of_node_put(twl6040_core_node);
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;
}
error = devm_request_threaded_irq(&pdev->dev, info->irq, NULL,
twl6040_vib_irq_handler,
IRQF_ONESHOT,
"twl6040_irq_vib", info);
if (error) {
dev_err(info->dev, "VIB IRQ request failed: %d\n", error);
return error;
}
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)
*/
error = devm_regulator_bulk_get(twl6040_core_dev,
ARRAY_SIZE(info->supplies),
info->supplies);
if (error) {
dev_err(info->dev, "couldn't get regulators %d\n", error);
return error;
}
if (vddvibl_uV) {
error = regulator_set_voltage(info->supplies[0].consumer,
vddvibl_uV, vddvibl_uV);
if (error) {
dev_err(info->dev, "failed to set VDDVIBL volt %d\n",
error);
return error;
}
}
if (vddvibr_uV) {
error = regulator_set_voltage(info->supplies[1].consumer,
vddvibr_uV, vddvibr_uV);
if (error) {
dev_err(info->dev, "failed to set VDDVIBR volt %d\n",
error);
return error;
}
}
INIT_WORK(&info->play_work, vibra_play_work);
info->input_dev = devm_input_allocate_device(&pdev->dev);
if (!info->input_dev) {
dev_err(info->dev, "couldn't allocate input device\n");
return -ENOMEM;
}
input_set_drvdata(info->input_dev, info);
info->input_dev->name = "twl6040:vibrator";
info->input_dev->id.version = 1;
info->input_dev->close = twl6040_vibra_close;
__set_bit(FF_RUMBLE, info->input_dev->ffbit);
error = input_ff_create_memless(info->input_dev, NULL, vibra_play);
if (error) {
dev_err(info->dev, "couldn't register vibrator to FF\n");
return error;
}
error = input_register_device(info->input_dev);
if (error) {
dev_err(info->dev, "couldn't register input device\n");
return error;
}
platform_set_drvdata(pdev, info);
return 0;
}
static struct platform_driver twl6040_vibra_driver = {
.probe = twl6040_vibra_probe,
.driver = {
.name = "twl6040-vibra",
.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>");
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+150
View File
@@ -0,0 +1,150 @@
/**
* 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/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 | IRQF_ONESHOT,
"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",
},
};
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>");
+572
View File
@@ -0,0 +1,572 @@
/*
* 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/input/mt.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>
#include <xen/platform_pci.h>
struct xenkbd_info {
struct input_dev *kbd;
struct input_dev *ptr;
struct input_dev *mtouch;
struct xenkbd_page *page;
int gref;
int irq;
struct xenbus_device *xbdev;
char phys[32];
/* current MT slot/contact ID we are injecting events in */
int mtouch_cur_contact_id;
};
enum { KPARAM_X, KPARAM_Y, KPARAM_CNT };
static int ptr_size[KPARAM_CNT] = { XENFB_WIDTH, XENFB_HEIGHT };
module_param_array(ptr_size, int, NULL, 0444);
MODULE_PARM_DESC(ptr_size,
"Pointing device width, height in pixels (default 800,600)");
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 void xenkbd_handle_motion_event(struct xenkbd_info *info,
struct xenkbd_motion *motion)
{
if (unlikely(!info->ptr))
return;
input_report_rel(info->ptr, REL_X, motion->rel_x);
input_report_rel(info->ptr, REL_Y, motion->rel_y);
if (motion->rel_z)
input_report_rel(info->ptr, REL_WHEEL, -motion->rel_z);
input_sync(info->ptr);
}
static void xenkbd_handle_position_event(struct xenkbd_info *info,
struct xenkbd_position *pos)
{
if (unlikely(!info->ptr))
return;
input_report_abs(info->ptr, ABS_X, pos->abs_x);
input_report_abs(info->ptr, ABS_Y, pos->abs_y);
if (pos->rel_z)
input_report_rel(info->ptr, REL_WHEEL, -pos->rel_z);
input_sync(info->ptr);
}
static void xenkbd_handle_key_event(struct xenkbd_info *info,
struct xenkbd_key *key)
{
struct input_dev *dev;
int value = key->pressed;
if (test_bit(key->keycode, info->ptr->keybit)) {
dev = info->ptr;
} else if (test_bit(key->keycode, info->kbd->keybit)) {
dev = info->kbd;
if (key->pressed && test_bit(key->keycode, info->kbd->key))
value = 2; /* Mark as autorepeat */
} else {
pr_warn("unhandled keycode 0x%x\n", key->keycode);
return;
}
if (unlikely(!dev))
return;
input_event(dev, EV_KEY, key->keycode, value);
input_sync(dev);
}
static void xenkbd_handle_mt_event(struct xenkbd_info *info,
struct xenkbd_mtouch *mtouch)
{
if (unlikely(!info->mtouch))
return;
if (mtouch->contact_id != info->mtouch_cur_contact_id) {
info->mtouch_cur_contact_id = mtouch->contact_id;
input_mt_slot(info->mtouch, mtouch->contact_id);
}
switch (mtouch->event_type) {
case XENKBD_MT_EV_DOWN:
input_mt_report_slot_state(info->mtouch, MT_TOOL_FINGER, true);
/* fall through */
case XENKBD_MT_EV_MOTION:
input_report_abs(info->mtouch, ABS_MT_POSITION_X,
mtouch->u.pos.abs_x);
input_report_abs(info->mtouch, ABS_MT_POSITION_Y,
mtouch->u.pos.abs_y);
break;
case XENKBD_MT_EV_SHAPE:
input_report_abs(info->mtouch, ABS_MT_TOUCH_MAJOR,
mtouch->u.shape.major);
input_report_abs(info->mtouch, ABS_MT_TOUCH_MINOR,
mtouch->u.shape.minor);
break;
case XENKBD_MT_EV_ORIENT:
input_report_abs(info->mtouch, ABS_MT_ORIENTATION,
mtouch->u.orientation);
break;
case XENKBD_MT_EV_UP:
input_mt_report_slot_state(info->mtouch, MT_TOOL_FINGER, false);
break;
case XENKBD_MT_EV_SYN:
input_mt_sync_frame(info->mtouch);
input_sync(info->mtouch);
break;
}
}
static void xenkbd_handle_event(struct xenkbd_info *info,
union xenkbd_in_event *event)
{
switch (event->type) {
case XENKBD_TYPE_MOTION:
xenkbd_handle_motion_event(info, &event->motion);
break;
case XENKBD_TYPE_KEY:
xenkbd_handle_key_event(info, &event->key);
break;
case XENKBD_TYPE_POS:
xenkbd_handle_position_event(info, &event->pos);
break;
case XENKBD_TYPE_MTOUCH:
xenkbd_handle_mt_event(info, &event->mtouch);
break;
}
}
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++)
xenkbd_handle_event(info, &XENKBD_IN_RING_REF(page, cons));
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;
bool with_mtouch, with_kbd, with_ptr;
struct xenkbd_info *info;
struct input_dev *kbd, *ptr, *mtouch;
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;
/*
* The below are reverse logic, e.g. if the feature is set, then
* do not expose the corresponding virtual device.
*/
with_kbd = !xenbus_read_unsigned(dev->otherend,
XENKBD_FIELD_FEAT_DSBL_KEYBRD, 0);
with_ptr = !xenbus_read_unsigned(dev->otherend,
XENKBD_FIELD_FEAT_DSBL_POINTER, 0);
/* Direct logic: if set, then create multi-touch device. */
with_mtouch = xenbus_read_unsigned(dev->otherend,
XENKBD_FIELD_FEAT_MTOUCH, 0);
if (with_mtouch) {
ret = xenbus_write(XBT_NIL, dev->nodename,
XENKBD_FIELD_REQ_MTOUCH, "1");
if (ret) {
pr_warn("xenkbd: can't request multi-touch");
with_mtouch = 0;
}
}
/* keyboard */
if (with_kbd) {
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 */
if (with_ptr) {
unsigned int abs;
/* Set input abs params to match backend screen res */
abs = xenbus_read_unsigned(dev->otherend,
XENKBD_FIELD_FEAT_ABS_POINTER, 0);
ptr_size[KPARAM_X] = xenbus_read_unsigned(dev->otherend,
XENKBD_FIELD_WIDTH,
ptr_size[KPARAM_X]);
ptr_size[KPARAM_Y] = xenbus_read_unsigned(dev->otherend,
XENKBD_FIELD_HEIGHT,
ptr_size[KPARAM_Y]);
if (abs) {
ret = xenbus_write(XBT_NIL, dev->nodename,
XENKBD_FIELD_REQ_ABS_POINTER, "1");
if (ret) {
pr_warn("xenkbd: can't request abs-pointer\n");
abs = 0;
}
}
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,
ptr_size[KPARAM_X], 0, 0);
input_set_abs_params(ptr, ABS_Y, 0,
ptr_size[KPARAM_Y], 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;
}
/* multi-touch device */
if (with_mtouch) {
int num_cont, width, height;
mtouch = input_allocate_device();
if (!mtouch)
goto error_nomem;
num_cont = xenbus_read_unsigned(info->xbdev->otherend,
XENKBD_FIELD_MT_NUM_CONTACTS,
1);
width = xenbus_read_unsigned(info->xbdev->otherend,
XENKBD_FIELD_MT_WIDTH,
XENFB_WIDTH);
height = xenbus_read_unsigned(info->xbdev->otherend,
XENKBD_FIELD_MT_HEIGHT,
XENFB_HEIGHT);
mtouch->name = "Xen Virtual Multi-touch";
mtouch->phys = info->phys;
mtouch->id.bustype = BUS_PCI;
mtouch->id.vendor = 0x5853;
mtouch->id.product = 0xfffd;
input_set_abs_params(mtouch, ABS_MT_TOUCH_MAJOR,
0, 255, 0, 0);
input_set_abs_params(mtouch, ABS_MT_POSITION_X,
0, width, 0, 0);
input_set_abs_params(mtouch, ABS_MT_POSITION_Y,
0, height, 0, 0);
ret = input_mt_init_slots(mtouch, num_cont, INPUT_MT_DIRECT);
if (ret) {
input_free_device(mtouch);
xenbus_dev_fatal(info->xbdev, ret,
"input_mt_init_slots");
goto error;
}
ret = input_register_device(mtouch);
if (ret) {
input_free_device(mtouch);
xenbus_dev_fatal(info->xbdev, ret,
"input_register_device(mtouch)");
goto error;
}
info->mtouch_cur_contact_id = -1;
info->mtouch = mtouch;
}
if (!(with_kbd || with_ptr || with_mtouch)) {
ret = -ENXIO;
goto error;
}
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);
if (info->mtouch)
input_unregister_device(info->mtouch);
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_gfn(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, XENKBD_FIELD_RING_REF, "%lu",
virt_to_gfn(info->page));
if (ret)
goto error_xenbus;
ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_RING_GREF,
"%u", info->gref);
if (ret)
goto error_xenbus;
ret = xenbus_printf(xbt, dev->nodename, XENKBD_FIELD_EVT_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(info->gref, 0, 0UL);
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(info->gref, 0, 0UL);
info->gref = -1;
}
static void xenkbd_backend_changed(struct xenbus_device *dev,
enum xenbus_state backend_state)
{
switch (backend_state) {
case XenbusStateInitialising:
case XenbusStateInitialised:
case XenbusStateReconfiguring:
case XenbusStateReconfigured:
case XenbusStateUnknown:
break;
case XenbusStateInitWait:
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)
xenbus_switch_state(dev, XenbusStateConnected);
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[] = {
{ XENKBD_DRIVER_NAME },
{ "" }
};
static struct xenbus_driver xenkbd_driver = {
.ids = xenkbd_ids,
.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;
if (!xen_has_pv_devices())
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:" XENKBD_DRIVER_NAME);
File diff suppressed because it is too large. Load diff
+220
View File
@@ -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. */
__be16 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 */