mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2025-01-09 06:33:34 +00:00
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input
Pull input updates from Dmitry Torokhov: "A few new haptic/button drivers, a rudimentary support for laptops using FocalTech touchpads; xpad driver will bind to more devices, and a few other driver fixes." * 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input: Input: soc_button_array - convert to platform bus Input: palmas-pwrbutton - fix typo in the license string Input: palmas-pwrbutton - use IRQF_ONESHOT Input: psmouse - add support for detecting FocalTech PS/2 touchpads Input: psmouse - add psmouse_matches_pnp_id helper function Input: joystick - use ktime for measuring timing Input: add haptic driver on max77693 Input: introduce palmas-pwrbutton Input: add support for the DRV2667 haptic driver Input: xpad - sync device IDs with xboxdrv Input: xpad - add VID/PID for Razer Sabertooth Input: cros_ec_keyb - optimize ghosting algorithm Input: drv260x - fix binding document Input: drv260x - add check for ERM mode and LRA Libraries Input: drv260x - remove unused defines Input: drv260x - add TI drv260x haptics driver
This commit is contained in:
commit
1e345ac686
50
Documentation/devicetree/bindings/input/ti,drv260x.txt
Normal file
50
Documentation/devicetree/bindings/input/ti,drv260x.txt
Normal file
@ -0,0 +1,50 @@
|
||||
* Texas Instruments - drv260x Haptics driver family
|
||||
|
||||
Required properties:
|
||||
- compatible - One of:
|
||||
"ti,drv2604" - DRV2604
|
||||
"ti,drv2605" - DRV2605
|
||||
"ti,drv2605l" - DRV2605L
|
||||
- reg - I2C slave address
|
||||
- vbat-supply - Required supply regulator
|
||||
- mode - Power up mode of the chip (defined in include/dt-bindings/input/ti-drv260x.h)
|
||||
DRV260X_LRA_MODE - Linear Resonance Actuator mode (Piezoelectric)
|
||||
DRV260X_LRA_NO_CAL_MODE - This is a LRA Mode but there is no calibration
|
||||
sequence during init. And the device is configured for real
|
||||
time playback mode (RTP mode).
|
||||
DRV260X_ERM_MODE - Eccentric Rotating Mass mode (Rotary vibrator)
|
||||
- library-sel - These are ROM based waveforms pre-programmed into the IC.
|
||||
This should be set to set the library to use at power up.
|
||||
(defined in include/dt-bindings/input/ti-drv260x.h)
|
||||
DRV260X_LIB_EMPTY - Do not use a pre-programmed library
|
||||
DRV260X_ERM_LIB_A - Pre-programmed Library
|
||||
DRV260X_ERM_LIB_B - Pre-programmed Library
|
||||
DRV260X_ERM_LIB_C - Pre-programmed Library
|
||||
DRV260X_ERM_LIB_D - Pre-programmed Library
|
||||
DRV260X_ERM_LIB_E - Pre-programmed Library
|
||||
DRV260X_ERM_LIB_F - Pre-programmed Library
|
||||
DRV260X_LIB_LRA - Pre-programmed LRA Library
|
||||
|
||||
Optional properties:
|
||||
- enable-gpio - gpio pin to enable/disable the device.
|
||||
- vib-rated-mv - The rated voltage of the actuator in millivolts.
|
||||
If this is not set then the value will be defaulted to
|
||||
3.2 v.
|
||||
- vib-overdrive-mv - The overdrive voltage of the actuator in millivolts.
|
||||
If this is not set then the value will be defaulted to
|
||||
3.2 v.
|
||||
Example:
|
||||
|
||||
haptics: haptics@5a {
|
||||
compatible = "ti,drv2605l";
|
||||
reg = <0x5a>;
|
||||
vbat-supply = <&vbat>;
|
||||
enable-gpio = <&gpio1 28 GPIO_ACTIVE_HIGH>;
|
||||
mode = <DRV260X_LRA_MODE>;
|
||||
library-sel = <DRV260X_LIB_LRA>;
|
||||
vib-rated-mv = <3200>;
|
||||
vib-overdriver-mv = <3200>;
|
||||
}
|
||||
|
||||
For more product information please see the link below:
|
||||
http://www.ti.com/product/drv2605
|
17
Documentation/devicetree/bindings/input/ti,drv2667.txt
Normal file
17
Documentation/devicetree/bindings/input/ti,drv2667.txt
Normal file
@ -0,0 +1,17 @@
|
||||
* Texas Instruments - drv2667 Haptics driver
|
||||
|
||||
Required properties:
|
||||
- compatible - "ti,drv2667" - DRV2667
|
||||
- reg - I2C slave address
|
||||
- vbat-supply - Required supply regulator
|
||||
|
||||
Example:
|
||||
|
||||
haptics: haptics@59 {
|
||||
compatible = "ti,drv2667";
|
||||
reg = <0x59>;
|
||||
vbat-supply = <&vbat>;
|
||||
};
|
||||
|
||||
For more product information please see the link below:
|
||||
http://www.ti.com/product/drv2667
|
@ -0,0 +1,36 @@
|
||||
Texas Instruments Palmas family power button module
|
||||
|
||||
This module is part of the Palmas family of PMICs. For more details
|
||||
about the whole chip see:
|
||||
Documentation/devicetree/bindings/mfd/palmas.txt.
|
||||
|
||||
This module provides a simple power button event via an Interrupt.
|
||||
|
||||
Required properties:
|
||||
- compatible: should be one of the following
|
||||
- "ti,palmas-pwrbutton": For Palmas compatible power on button
|
||||
- interrupt-parent: Parent interrupt device, must be handle of palmas node.
|
||||
- interrupts: Interrupt number of power button submodule on device.
|
||||
|
||||
Optional Properties:
|
||||
|
||||
- ti,palmas-long-press-seconds: Duration in seconds which the power
|
||||
button should be kept pressed for Palmas to power off automatically.
|
||||
NOTE: This depends on OTP support and POWERHOLD signal configuration
|
||||
on platform. Valid values are 6, 8, 10 and 12.
|
||||
- ti,palmas-pwron-debounce-milli-seconds: Duration in milliseconds
|
||||
which the power button should be kept pressed for Palmas to register
|
||||
a press for debouncing purposes. NOTE: This depends on specific
|
||||
Palmas variation capability. Valid values are 15, 100, 500 and 1000.
|
||||
|
||||
Example:
|
||||
|
||||
&palmas {
|
||||
palmas_pwr_button: pwrbutton {
|
||||
compatible = "ti,palmas-pwrbutton";
|
||||
interrupt-parent = <&tps659038>;
|
||||
interrupts = <1 IRQ_TYPE_EDGE_FALLING>;
|
||||
ti,palmas-long-press-seconds = <12>;
|
||||
ti,palmas-pwron-debounce-milli-seconds = <15>;
|
||||
};
|
||||
};
|
@ -15,8 +15,6 @@
|
||||
#include <linux/ctype.h>
|
||||
|
||||
static const struct acpi_device_id acpi_pnp_device_ids[] = {
|
||||
/* soc_button_array */
|
||||
{"PNP0C40"},
|
||||
/* pata_isapnp */
|
||||
{"PNP0600"}, /* Generic ESDI/IDE/ATA compatible hard disk controller */
|
||||
/* floppy */
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/sched.h> /* HZ */
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/timekeeping.h>
|
||||
|
||||
/*#include <asm/io.h>*/
|
||||
|
||||
@ -30,6 +31,10 @@ MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
|
||||
MODULE_DESCRIPTION("Generic gameport layer");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static bool use_ktime = true;
|
||||
module_param(use_ktime, bool, 0400);
|
||||
MODULE_PARM_DESC(use_ktime, "Use ktime for measuring I/O speed");
|
||||
|
||||
/*
|
||||
* gameport_mutex protects entire gameport subsystem and is taken
|
||||
* every time gameport port or driver registrered or unregistered.
|
||||
@ -75,6 +80,38 @@ static unsigned int get_time_pit(void)
|
||||
*/
|
||||
|
||||
static int gameport_measure_speed(struct gameport *gameport)
|
||||
{
|
||||
unsigned int i, t, tx;
|
||||
u64 t1, t2, t3;
|
||||
unsigned long flags;
|
||||
|
||||
if (gameport_open(gameport, NULL, GAMEPORT_MODE_RAW))
|
||||
return 0;
|
||||
|
||||
tx = ~0;
|
||||
|
||||
for (i = 0; i < 50; i++) {
|
||||
local_irq_save(flags);
|
||||
t1 = ktime_get_ns();
|
||||
for (t = 0; t < 50; t++)
|
||||
gameport_read(gameport);
|
||||
t2 = ktime_get_ns();
|
||||
t3 = ktime_get_ns();
|
||||
local_irq_restore(flags);
|
||||
udelay(i * 10);
|
||||
t = (t2 - t1) - (t3 - t2);
|
||||
if (t < tx)
|
||||
tx = t;
|
||||
}
|
||||
|
||||
gameport_close(gameport);
|
||||
t = 1000000 * 50;
|
||||
if (tx)
|
||||
t /= tx;
|
||||
return t;
|
||||
}
|
||||
|
||||
static int old_gameport_measure_speed(struct gameport *gameport)
|
||||
{
|
||||
#if defined(__i386__)
|
||||
|
||||
@ -521,7 +558,9 @@ static void gameport_add_port(struct gameport *gameport)
|
||||
if (gameport->parent)
|
||||
gameport->parent->child = gameport;
|
||||
|
||||
gameport->speed = gameport_measure_speed(gameport);
|
||||
gameport->speed = use_ktime ?
|
||||
gameport_measure_speed(gameport) :
|
||||
old_gameport_measure_speed(gameport);
|
||||
|
||||
list_add_tail(&gameport->node, &gameport_list);
|
||||
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include <linux/gameport.h>
|
||||
#include <linux/jiffies.h>
|
||||
#include <linux/timex.h>
|
||||
#include <linux/timekeeping.h>
|
||||
|
||||
#define DRIVER_DESC "Analog joystick and gamepad driver"
|
||||
|
||||
@ -43,6 +44,10 @@ MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>");
|
||||
MODULE_DESCRIPTION(DRIVER_DESC);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static bool use_ktime = true;
|
||||
module_param(use_ktime, bool, 0400);
|
||||
MODULE_PARM_DESC(use_ktime, "Use ktime for measuring I/O speed");
|
||||
|
||||
/*
|
||||
* Option parsing.
|
||||
*/
|
||||
@ -171,6 +176,25 @@ static unsigned long analog_faketime = 0;
|
||||
#warning Precise timer not defined for this architecture.
|
||||
#endif
|
||||
|
||||
static inline u64 get_time(void)
|
||||
{
|
||||
if (use_ktime) {
|
||||
return ktime_get_ns();
|
||||
} else {
|
||||
unsigned int x;
|
||||
GET_TIME(x);
|
||||
return x;
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned int delta(u64 x, u64 y)
|
||||
{
|
||||
if (use_ktime)
|
||||
return y - x;
|
||||
else
|
||||
return DELTA((unsigned int)x, (unsigned int)y);
|
||||
}
|
||||
|
||||
/*
|
||||
* analog_decode() decodes analog joystick data and reports input events.
|
||||
*/
|
||||
@ -226,7 +250,8 @@ static void analog_decode(struct analog *analog, int *axes, int *initial, int bu
|
||||
static int analog_cooked_read(struct analog_port *port)
|
||||
{
|
||||
struct gameport *gameport = port->gameport;
|
||||
unsigned int time[4], start, loop, now, loopout, timeout;
|
||||
u64 time[4], start, loop, now;
|
||||
unsigned int loopout, timeout;
|
||||
unsigned char data[4], this, last;
|
||||
unsigned long flags;
|
||||
int i, j;
|
||||
@ -236,7 +261,7 @@ static int analog_cooked_read(struct analog_port *port)
|
||||
|
||||
local_irq_save(flags);
|
||||
gameport_trigger(gameport);
|
||||
GET_TIME(now);
|
||||
now = get_time();
|
||||
local_irq_restore(flags);
|
||||
|
||||
start = now;
|
||||
@ -249,16 +274,16 @@ static int analog_cooked_read(struct analog_port *port)
|
||||
|
||||
local_irq_disable();
|
||||
this = gameport_read(gameport) & port->mask;
|
||||
GET_TIME(now);
|
||||
now = get_time();
|
||||
local_irq_restore(flags);
|
||||
|
||||
if ((last ^ this) && (DELTA(loop, now) < loopout)) {
|
||||
if ((last ^ this) && (delta(loop, now) < loopout)) {
|
||||
data[i] = last ^ this;
|
||||
time[i] = now;
|
||||
i++;
|
||||
}
|
||||
|
||||
} while (this && (i < 4) && (DELTA(start, now) < timeout));
|
||||
} while (this && (i < 4) && (delta(start, now) < timeout));
|
||||
|
||||
this <<= 4;
|
||||
|
||||
@ -266,7 +291,7 @@ static int analog_cooked_read(struct analog_port *port)
|
||||
this |= data[i];
|
||||
for (j = 0; j < 4; j++)
|
||||
if (data[i] & (1 << j))
|
||||
port->axes[j] = (DELTA(start, time[i]) << ANALOG_FUZZ_BITS) / port->loop;
|
||||
port->axes[j] = (delta(start, time[i]) << ANALOG_FUZZ_BITS) / port->loop;
|
||||
}
|
||||
|
||||
return -(this != port->mask);
|
||||
@ -365,31 +390,39 @@ static void analog_close(struct input_dev *dev)
|
||||
static void analog_calibrate_timer(struct analog_port *port)
|
||||
{
|
||||
struct gameport *gameport = port->gameport;
|
||||
unsigned int i, t, tx, t1, t2, t3;
|
||||
unsigned int i, t, tx;
|
||||
u64 t1, t2, t3;
|
||||
unsigned long flags;
|
||||
|
||||
local_irq_save(flags);
|
||||
GET_TIME(t1);
|
||||
if (use_ktime) {
|
||||
port->speed = 1000000;
|
||||
} else {
|
||||
local_irq_save(flags);
|
||||
t1 = get_time();
|
||||
#ifdef FAKE_TIME
|
||||
analog_faketime += 830;
|
||||
analog_faketime += 830;
|
||||
#endif
|
||||
mdelay(1);
|
||||
GET_TIME(t2);
|
||||
GET_TIME(t3);
|
||||
local_irq_restore(flags);
|
||||
mdelay(1);
|
||||
t2 = get_time();
|
||||
t3 = get_time();
|
||||
local_irq_restore(flags);
|
||||
|
||||
port->speed = DELTA(t1, t2) - DELTA(t2, t3);
|
||||
port->speed = delta(t1, t2) - delta(t2, t3);
|
||||
}
|
||||
|
||||
tx = ~0;
|
||||
|
||||
for (i = 0; i < 50; i++) {
|
||||
local_irq_save(flags);
|
||||
GET_TIME(t1);
|
||||
for (t = 0; t < 50; t++) { gameport_read(gameport); GET_TIME(t2); }
|
||||
GET_TIME(t3);
|
||||
t1 = get_time();
|
||||
for (t = 0; t < 50; t++) {
|
||||
gameport_read(gameport);
|
||||
t2 = get_time();
|
||||
}
|
||||
t3 = get_time();
|
||||
local_irq_restore(flags);
|
||||
udelay(i);
|
||||
t = DELTA(t1, t2) - DELTA(t2, t3);
|
||||
t = delta(t1, t2) - delta(t2, t3);
|
||||
if (t < tx) tx = t;
|
||||
}
|
||||
|
||||
|
@ -126,7 +126,9 @@ static const struct xpad_device {
|
||||
{ 0x045e, 0x0291, "Xbox 360 Wireless Receiver (XBOX)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
|
||||
{ 0x045e, 0x0719, "Xbox 360 Wireless Receiver", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
|
||||
{ 0x044f, 0x0f07, "Thrustmaster, Inc. Controller", 0, XTYPE_XBOX },
|
||||
{ 0x044f, 0xb326, "Thrustmaster Gamepad GP XID", 0, XTYPE_XBOX360 },
|
||||
{ 0x046d, 0xc21d, "Logitech Gamepad F310", 0, XTYPE_XBOX360 },
|
||||
{ 0x046d, 0xc21e, "Logitech Gamepad F510", 0, XTYPE_XBOX360 },
|
||||
{ 0x046d, 0xc21f, "Logitech Gamepad F710", 0, XTYPE_XBOX360 },
|
||||
{ 0x046d, 0xc242, "Logitech Chillstream Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x046d, 0xca84, "Logitech Xbox Cordless Controller", 0, XTYPE_XBOX },
|
||||
@ -140,10 +142,17 @@ static const struct xpad_device {
|
||||
{ 0x0738, 0x4540, "Mad Catz Beat Pad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
|
||||
{ 0x0738, 0x4556, "Mad Catz Lynx Wireless Controller", 0, XTYPE_XBOX },
|
||||
{ 0x0738, 0x4716, "Mad Catz Wired Xbox 360 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0x4718, "Mad Catz Street Fighter IV FightStick SE", 0, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0x4726, "Mad Catz Xbox 360 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0x4728, "Mad Catz Street Fighter IV FightPad", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0x4738, "Mad Catz Wired Xbox 360 Controller (SFIV)", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0x4740, "Mad Catz Beat Pad", 0, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0x6040, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
|
||||
{ 0x0738, 0xb726, "Mad Catz Xbox controller - MW2", 0, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0xbeef, "Mad Catz JOYTECH NEO SE Advanced GamePad", XTYPE_XBOX360 },
|
||||
{ 0x0738, 0xcb02, "Saitek Cyborg Rumble Pad - PC/Xbox 360", 0, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0xcb03, "Saitek P3200 Rumble Pad - PC/Xbox 360", 0, XTYPE_XBOX360 },
|
||||
{ 0x0738, 0xf738, "Super SFIV FightStick TE S", 0, XTYPE_XBOX360 },
|
||||
{ 0x0c12, 0x8802, "Zeroplus Xbox Controller", 0, XTYPE_XBOX },
|
||||
{ 0x0c12, 0x8809, "RedOctane Xbox Dance Pad", DANCEPAD_MAP_CONFIG, XTYPE_XBOX },
|
||||
{ 0x0c12, 0x880a, "Pelican Eclipse PL-2023", 0, XTYPE_XBOX },
|
||||
@ -156,28 +165,50 @@ static const struct xpad_device {
|
||||
{ 0x0e6f, 0x0005, "Eclipse wireless Controller", 0, XTYPE_XBOX },
|
||||
{ 0x0e6f, 0x0006, "Edge wireless Controller", 0, XTYPE_XBOX },
|
||||
{ 0x0e6f, 0x0105, "HSM3 Xbox360 dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 },
|
||||
{ 0x0e6f, 0x0113, "Afterglow AX.1 Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
|
||||
{ 0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
|
||||
{ 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
|
||||
{ 0x0e6f, 0x0301, "Logic3 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x0e6f, 0x0401, "Logic3 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x0e8f, 0x0201, "SmartJoy Frag Xpad/PS2 adaptor", 0, XTYPE_XBOX },
|
||||
{ 0x0e8f, 0x3008, "Generic xbox control (dealextreme)", 0, XTYPE_XBOX },
|
||||
{ 0x0f0d, 0x000a, "Hori Co. DOA4 FightStick", 0, XTYPE_XBOX360 },
|
||||
{ 0x0f0d, 0x000d, "Hori Fighting Stick EX2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
|
||||
{ 0x0f0d, 0x0016, "Hori Real Arcade Pro.EX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
|
||||
{ 0x0f30, 0x0202, "Joytech Advanced Controller", 0, XTYPE_XBOX },
|
||||
{ 0x0f30, 0x8888, "BigBen XBMiniPad Controller", 0, XTYPE_XBOX },
|
||||
{ 0x102c, 0xff0c, "Joytech Wireless Advanced Controller", 0, XTYPE_XBOX },
|
||||
{ 0x12ab, 0x0004, "Honey Bee Xbox360 dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 },
|
||||
{ 0x12ab, 0x0301, "PDP AFTERGLOW AX.1", 0, XTYPE_XBOX360 },
|
||||
{ 0x12ab, 0x8809, "Xbox DDR dancepad", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
|
||||
{ 0x1430, 0x4748, "RedOctane Guitar Hero X-plorer", 0, XTYPE_XBOX360 },
|
||||
{ 0x1430, 0x8888, "TX6500+ Dance Pad (first generation)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
|
||||
{ 0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x1532, 0x0037, "Razer Sabertooth", 0, XTYPE_XBOX360 },
|
||||
{ 0x15e4, 0x3f00, "Power A Mini Pro Elite", 0, XTYPE_XBOX360 },
|
||||
{ 0x15e4, 0x3f0a, "Xbox Airflo wired controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x15e4, 0x3f10, "Batarang Xbox 360 controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x162e, 0xbeef, "Joytech Neo-Se Take2", 0, XTYPE_XBOX360 },
|
||||
{ 0x1689, 0xfd00, "Razer Onza Tournament Edition", 0, XTYPE_XBOX360 },
|
||||
{ 0x1689, 0xfd01, "Razer Onza Classic Edition", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5d04, "Razer Sabertooth", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0x0002, "Harmonix Rock Band Guitar", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0x0003, "Harmonix Rock Band Drumkit", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0xf016, "Mad Catz Xbox 360 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0xf023, "MLG Pro Circuit Controller (Xbox)", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0xf028, "Street Fighter IV FightPad", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0xf038, "Street Fighter IV FightStick TE", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0xf900, "Harmonix Xbox 360 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0xf901, "Gamestop Xbox 360 Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x1bad, 0xf903, "Tron Xbox 360 controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5000, "Razer Atrox Arcade Stick", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5300, "PowerA MINI PROEX Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5303, "Xbox Airflo wired controller", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5500, "Hori XBOX 360 EX 2 with Turbo", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5501, "Hori Real Arcade Pro VX-SA", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5506, "Hori SOULCALIBUR V Stick", 0, XTYPE_XBOX360 },
|
||||
{ 0x24c6, 0x5b02, "Thrustmaster, Inc. GPX Controller", 0, XTYPE_XBOX360 },
|
||||
{ 0xffff, 0xffff, "Chinese-made Xbox Controller", 0, XTYPE_XBOX },
|
||||
{ 0x0000, 0x0000, "Generic X-Box pad", 0, XTYPE_UNKNOWN }
|
||||
};
|
||||
@ -274,6 +305,9 @@ static struct usb_device_id xpad_table[] = {
|
||||
XPAD_XBOX360_VENDOR(0x0f0d), /* Hori Controllers */
|
||||
XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */
|
||||
XPAD_XBOX360_VENDOR(0x24c6), /* PowerA Controllers */
|
||||
XPAD_XBOX360_VENDOR(0x1532), /* Razer Sabertooth */
|
||||
XPAD_XBOX360_VENDOR(0x15e4), /* Numark X-Box 360 controllers */
|
||||
XPAD_XBOX360_VENDOR(0x162e), /* Joytech X-Box 360 controllers */
|
||||
{ }
|
||||
};
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/interrupt.h>
|
||||
@ -38,6 +39,7 @@
|
||||
* @row_shift: log2 or number of rows, rounded up
|
||||
* @keymap_data: Matrix keymap data used to convert to keyscan values
|
||||
* @ghost_filter: true to enable the matrix key-ghosting filter
|
||||
* @valid_keys: bitmap of existing keys for each matrix column
|
||||
* @old_kb_state: bitmap of keys pressed last scan
|
||||
* @dev: Device pointer
|
||||
* @idev: Input device
|
||||
@ -49,6 +51,7 @@ struct cros_ec_keyb {
|
||||
int row_shift;
|
||||
const struct matrix_keymap_data *keymap_data;
|
||||
bool ghost_filter;
|
||||
uint8_t *valid_keys;
|
||||
uint8_t *old_kb_state;
|
||||
|
||||
struct device *dev;
|
||||
@ -57,39 +60,15 @@ struct cros_ec_keyb {
|
||||
};
|
||||
|
||||
|
||||
static bool cros_ec_keyb_row_has_ghosting(struct cros_ec_keyb *ckdev,
|
||||
uint8_t *buf, int row)
|
||||
{
|
||||
int pressed_in_row = 0;
|
||||
int row_has_teeth = 0;
|
||||
int col, mask;
|
||||
|
||||
mask = 1 << row;
|
||||
for (col = 0; col < ckdev->cols; col++) {
|
||||
if (buf[col] & mask) {
|
||||
pressed_in_row++;
|
||||
row_has_teeth |= buf[col] & ~mask;
|
||||
if (pressed_in_row > 1 && row_has_teeth) {
|
||||
/* ghosting */
|
||||
dev_dbg(ckdev->dev,
|
||||
"ghost found at: r%d c%d, pressed %d, teeth 0x%x\n",
|
||||
row, col, pressed_in_row,
|
||||
row_has_teeth);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns true when there is at least one combination of pressed keys that
|
||||
* results in ghosting.
|
||||
*/
|
||||
static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, uint8_t *buf)
|
||||
{
|
||||
int row;
|
||||
int col1, col2, buf1, buf2;
|
||||
struct device *dev = ckdev->dev;
|
||||
uint8_t *valid_keys = ckdev->valid_keys;
|
||||
|
||||
/*
|
||||
* Ghosting happens if for any pressed key X there are other keys
|
||||
@ -103,27 +82,23 @@ static bool cros_ec_keyb_has_ghosting(struct cros_ec_keyb *ckdev, uint8_t *buf)
|
||||
*
|
||||
* In this case only X, Y, and Z are pressed, but g appears to be
|
||||
* pressed too (see Wikipedia).
|
||||
*
|
||||
* We can detect ghosting in a single pass (*) over the keyboard state
|
||||
* by maintaining two arrays. pressed_in_row counts how many pressed
|
||||
* keys we have found in a row. row_has_teeth is true if any of the
|
||||
* pressed keys for this row has other pressed keys in its column. If
|
||||
* at any point of the scan we find that a row has multiple pressed
|
||||
* keys, and at least one of them is at the intersection with a column
|
||||
* with multiple pressed keys, we're sure there is ghosting.
|
||||
* Conversely, if there is ghosting, we will detect such situation for
|
||||
* at least one key during the pass.
|
||||
*
|
||||
* (*) This looks linear in the number of keys, but it's not. We can
|
||||
* cheat because the number of rows is small.
|
||||
*/
|
||||
for (row = 0; row < ckdev->rows; row++)
|
||||
if (cros_ec_keyb_row_has_ghosting(ckdev, buf, row))
|
||||
return true;
|
||||
for (col1 = 0; col1 < ckdev->cols; col1++) {
|
||||
buf1 = buf[col1] & valid_keys[col1];
|
||||
for (col2 = col1 + 1; col2 < ckdev->cols; col2++) {
|
||||
buf2 = buf[col2] & valid_keys[col2];
|
||||
if (hweight8(buf1 & buf2) > 1) {
|
||||
dev_dbg(dev, "ghost found at: B[%02d]:0x%02x & B[%02d]:0x%02x",
|
||||
col1, buf1, col2, buf2);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Compares the new keyboard state to the old one and produces key
|
||||
* press/release events accordingly. The keyboard state is 13 bytes (one byte
|
||||
@ -222,6 +197,30 @@ static void cros_ec_keyb_close(struct input_dev *dev)
|
||||
free_irq(ec->irq, ckdev);
|
||||
}
|
||||
|
||||
/*
|
||||
* Walks keycodes flipping bit in buffer COLUMNS deep where bit is ROW. Used by
|
||||
* ghosting logic to ignore NULL or virtual keys.
|
||||
*/
|
||||
static void cros_ec_keyb_compute_valid_keys(struct cros_ec_keyb *ckdev)
|
||||
{
|
||||
int row, col;
|
||||
int row_shift = ckdev->row_shift;
|
||||
unsigned short *keymap = ckdev->idev->keycode;
|
||||
unsigned short code;
|
||||
|
||||
BUG_ON(ckdev->idev->keycodesize != sizeof(*keymap));
|
||||
|
||||
for (col = 0; col < ckdev->cols; col++) {
|
||||
for (row = 0; row < ckdev->rows; row++) {
|
||||
code = keymap[MATRIX_SCAN_CODE(row, col, row_shift)];
|
||||
if (code && (code != KEY_BATTERY))
|
||||
ckdev->valid_keys[col] |= 1 << row;
|
||||
}
|
||||
dev_dbg(ckdev->dev, "valid_keys[%02d] = 0x%02x\n",
|
||||
col, ckdev->valid_keys[col]);
|
||||
}
|
||||
}
|
||||
|
||||
static int cros_ec_keyb_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct cros_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
|
||||
@ -242,6 +241,11 @@ static int cros_ec_keyb_probe(struct platform_device *pdev)
|
||||
&ckdev->cols);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
ckdev->valid_keys = devm_kzalloc(&pdev->dev, ckdev->cols, GFP_KERNEL);
|
||||
if (!ckdev->valid_keys)
|
||||
return -ENOMEM;
|
||||
|
||||
ckdev->old_kb_state = devm_kzalloc(&pdev->dev, ckdev->cols, GFP_KERNEL);
|
||||
if (!ckdev->old_kb_state)
|
||||
return -ENOMEM;
|
||||
@ -285,6 +289,8 @@ static int cros_ec_keyb_probe(struct platform_device *pdev)
|
||||
input_set_capability(idev, EV_MSC, MSC_SCAN);
|
||||
input_set_drvdata(idev, ckdev);
|
||||
ckdev->idev = idev;
|
||||
cros_ec_keyb_compute_valid_keys(ckdev);
|
||||
|
||||
err = input_register_device(ckdev->idev);
|
||||
if (err) {
|
||||
dev_err(dev, "cannot register input device\n");
|
||||
|
@ -144,6 +144,17 @@ config INPUT_M68K_BEEP
|
||||
tristate "M68k Beeper support"
|
||||
depends on M68K
|
||||
|
||||
config INPUT_MAX77693_HAPTIC
|
||||
tristate "MAXIM MAX77693 haptic controller support"
|
||||
depends on MFD_MAX77693 && PWM
|
||||
select INPUT_FF_MEMLESS
|
||||
help
|
||||
This option enables support for the haptic controller on
|
||||
MAXIM MAX77693 chip.
|
||||
|
||||
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
|
||||
@ -451,6 +462,16 @@ config HP_SDC_RTC
|
||||
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
|
||||
@ -676,4 +697,26 @@ config INPUT_SOC_BUTTON_ARRAY
|
||||
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 && GPIOLIB
|
||||
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_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 drv260x-haptics.
|
||||
|
||||
endif
|
||||
|
@ -26,6 +26,8 @@ obj-$(CONFIG_INPUT_COBALT_BTNS) += cobalt_btns.o
|
||||
obj-$(CONFIG_INPUT_DA9052_ONKEY) += da9052_onkey.o
|
||||
obj-$(CONFIG_INPUT_DA9055_ONKEY) += da9055_onkey.o
|
||||
obj-$(CONFIG_INPUT_DM355EVM) += dm355evm_keys.o
|
||||
obj-$(CONFIG_INPUT_DRV260X_HAPTICS) += drv260x.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_TILT_POLLED) += gpio_tilt_polled.o
|
||||
@ -35,11 +37,13 @@ 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_MPU3050) += mpu3050.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
|
||||
|
741
drivers/input/misc/drv260x.c
Normal file
741
drivers/input/misc/drv260x.c
Normal file
@ -0,0 +1,741 @@
|
||||
/*
|
||||
* 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/of_gpio.h>
|
||||
#include <linux/platform_device.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>
|
||||
#include <linux/platform_data/drv260x-pdata.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 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_default 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_default 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_default 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,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int drv260x_parse_dt(struct device *dev,
|
||||
struct drv260x_data *haptics)
|
||||
{
|
||||
struct device_node *np = dev->of_node;
|
||||
unsigned int voltage;
|
||||
int error;
|
||||
|
||||
error = of_property_read_u32(np, "mode", &haptics->mode);
|
||||
if (error) {
|
||||
dev_err(dev, "%s: No entry for mode\n", __func__);
|
||||
return error;
|
||||
}
|
||||
|
||||
error = of_property_read_u32(np, "library-sel", &haptics->library);
|
||||
if (error) {
|
||||
dev_err(dev, "%s: No entry for library selection\n",
|
||||
__func__);
|
||||
return error;
|
||||
}
|
||||
|
||||
error = of_property_read_u32(np, "vib-rated-mv", &voltage);
|
||||
if (!error)
|
||||
haptics->rated_voltage = drv260x_calculate_voltage(voltage);
|
||||
|
||||
|
||||
error = of_property_read_u32(np, "vib-overdrive-mv", &voltage);
|
||||
if (!error)
|
||||
haptics->overdrive_voltage = drv260x_calculate_voltage(voltage);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
static inline int drv260x_parse_dt(struct device *dev,
|
||||
struct drv260x_data *haptics)
|
||||
{
|
||||
dev_err(dev, "no platform data defined\n");
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int drv260x_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
const struct drv260x_platform_data *pdata = dev_get_platdata(&client->dev);
|
||||
struct drv260x_data *haptics;
|
||||
int error;
|
||||
|
||||
haptics = devm_kzalloc(&client->dev, sizeof(*haptics), GFP_KERNEL);
|
||||
if (!haptics)
|
||||
return -ENOMEM;
|
||||
|
||||
haptics->rated_voltage = DRV260X_DEF_OD_CLAMP_VOLT;
|
||||
haptics->rated_voltage = DRV260X_DEF_RATED_VOLT;
|
||||
|
||||
if (pdata) {
|
||||
haptics->mode = pdata->mode;
|
||||
haptics->library = pdata->library_selection;
|
||||
if (pdata->vib_overdrive_voltage)
|
||||
haptics->overdrive_voltage = drv260x_calculate_voltage(pdata->vib_overdrive_voltage);
|
||||
if (pdata->vib_rated_voltage)
|
||||
haptics->rated_voltage = drv260x_calculate_voltage(pdata->vib_rated_voltage);
|
||||
} else if (client->dev.of_node) {
|
||||
error = drv260x_parse_dt(&client->dev, haptics);
|
||||
if (error)
|
||||
return error;
|
||||
} else {
|
||||
dev_err(&client->dev, "Platform data not set\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
|
||||
if (haptics->mode < DRV260X_LRA_MODE ||
|
||||
haptics->mode > DRV260X_ERM_MODE) {
|
||||
dev_err(&client->dev,
|
||||
"Vibrator mode is invalid: %i\n",
|
||||
haptics->mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (haptics->library < DRV260X_LIB_EMPTY ||
|
||||
haptics->library > DRV260X_ERM_LIB_F) {
|
||||
dev_err(&client->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(&client->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(&client->dev,
|
||||
"ERM Mode with LRA Library mismatch\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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->enable_gpio = devm_gpiod_get(&client->dev, "enable");
|
||||
if (IS_ERR(haptics->enable_gpio)) {
|
||||
error = PTR_ERR(haptics->enable_gpio);
|
||||
if (error != -ENOENT && error != -ENOSYS)
|
||||
return error;
|
||||
haptics->enable_gpio = NULL;
|
||||
} else {
|
||||
gpiod_direction_output(haptics->enable_gpio, 1);
|
||||
}
|
||||
|
||||
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 = "drv260x:haptics";
|
||||
haptics->input_dev->dev.parent = client->dev.parent;
|
||||
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(&client->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(&client->dev, "Failed to allocate register map: %d\n",
|
||||
error);
|
||||
return error;
|
||||
}
|
||||
|
||||
error = drv260x_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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int 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 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
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);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
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);
|
||||
#endif
|
||||
|
||||
static struct i2c_driver drv260x_driver = {
|
||||
.probe = drv260x_probe,
|
||||
.driver = {
|
||||
.name = "drv260x-haptics",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(drv260x_of_match),
|
||||
.pm = &drv260x_pm_ops,
|
||||
},
|
||||
.id_table = drv260x_id,
|
||||
};
|
||||
module_i2c_driver(drv260x_driver);
|
||||
|
||||
MODULE_ALIAS("platform:drv260x-haptics");
|
||||
MODULE_DESCRIPTION("TI DRV260x haptics driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
|
500
drivers/input/misc/drv2667.c
Normal file
500
drivers/input/misc/drv2667.c
Normal file
@ -0,0 +1,500 @@
|
||||
/*
|
||||
* 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 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, 1);
|
||||
if (error)
|
||||
dev_err(&haptics->client->dev,
|
||||
"Failed to enter standby mode: %d\n", error);
|
||||
}
|
||||
|
||||
static const struct reg_default 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_default 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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int 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, 1);
|
||||
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 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
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",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(drv2667_of_match),
|
||||
.pm = &drv2667_pm_ops,
|
||||
},
|
||||
.id_table = drv2667_id,
|
||||
};
|
||||
module_i2c_driver(drv2667_driver);
|
||||
|
||||
MODULE_ALIAS("platform:drv2667-haptics");
|
||||
MODULE_DESCRIPTION("TI DRV2667 haptics driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
|
357
drivers/input/misc/max77693-haptic.c
Normal file
357
drivers/input/misc/max77693-haptic.c
Normal file
@ -0,0 +1,357 @@
|
||||
/*
|
||||
* MAXIM MAX77693 Haptic device driver
|
||||
*
|
||||
* Copyright (C) 2014 Samsung Electronics
|
||||
* Jaewon Kim <jaewon02.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.
|
||||
*/
|
||||
|
||||
#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-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 {
|
||||
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;
|
||||
enum max77693_haptic_pwm_divisor pwm_divisor;
|
||||
|
||||
struct work_struct work;
|
||||
};
|
||||
|
||||
static int max77693_haptic_set_duty_cycle(struct max77693_haptic *haptic)
|
||||
{
|
||||
int delta = (haptic->pwm_dev->period + haptic->pwm_duty) / 2;
|
||||
int error;
|
||||
|
||||
error = pwm_config(haptic->pwm_dev, delta, haptic->pwm_dev->period);
|
||||
if (error) {
|
||||
dev_err(haptic->dev, "failed to configure pwm: %d\n", error);
|
||||
return error;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max77693_haptic_configure(struct max77693_haptic *haptic,
|
||||
bool enable)
|
||||
{
|
||||
unsigned int value;
|
||||
int error;
|
||||
|
||||
value = ((haptic->type << MAX77693_CONFIG2_MODE) |
|
||||
(enable << MAX77693_CONFIG2_MEN) |
|
||||
(haptic->mode << MAX77693_CONFIG2_HTYP) |
|
||||
(haptic->pwm_divisor));
|
||||
|
||||
error = regmap_write(haptic->regmap_haptic,
|
||||
MAX77693_HAPTIC_REG_CONFIG2, 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;
|
||||
|
||||
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);
|
||||
uint64_t 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)
|
||||
*/
|
||||
period_mag_multi = (int64_t)(haptic->pwm_dev->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 = 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);
|
||||
}
|
||||
|
||||
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->regmap_haptic = max77693->regmap_haptic;
|
||||
haptic->dev = &pdev->dev;
|
||||
haptic->type = MAX77693_HAPTIC_LRA;
|
||||
haptic->mode = MAX77693_HAPTIC_EXTERNAL_MODE;
|
||||
haptic->pwm_divisor = MAX77693_HAPTIC_PWM_DIVISOR_128;
|
||||
haptic->suspend_state = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int 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 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(max77693_haptic_pm_ops,
|
||||
max77693_haptic_suspend, max77693_haptic_resume);
|
||||
|
||||
static struct platform_driver max77693_haptic_driver = {
|
||||
.driver = {
|
||||
.name = "max77693-haptic",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &max77693_haptic_pm_ops,
|
||||
},
|
||||
.probe = max77693_haptic_probe,
|
||||
};
|
||||
module_platform_driver(max77693_haptic_driver);
|
||||
|
||||
MODULE_AUTHOR("Jaewon Kim <jaewon02.kim@samsung.com>");
|
||||
MODULE_DESCRIPTION("MAXIM MAX77693 Haptic driver");
|
||||
MODULE_ALIAS("platform:max77693-haptic");
|
||||
MODULE_LICENSE("GPL");
|
332
drivers/input/misc/palmas-pwrbutton.c
Normal file
332
drivers/input/misc/palmas-pwrbutton.c
Normal file
@ -0,0 +1,332 @@
|
||||
/*
|
||||
* 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, ®);
|
||||
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;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
/**
|
||||
* 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 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 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;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(palmas_pwron_pm,
|
||||
palmas_pwron_suspend, palmas_pwron_resume);
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static 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",
|
||||
.owner = THIS_MODULE,
|
||||
.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.");
|
@ -18,7 +18,7 @@
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/gpio_keys.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pnp.h>
|
||||
#include <linux/acpi.h>
|
||||
|
||||
/*
|
||||
* Definition of buttons on the tablet. The ACPI index of each button
|
||||
@ -67,7 +67,7 @@ static int soc_button_lookup_gpio(struct device *dev, int acpi_index)
|
||||
}
|
||||
|
||||
static struct platform_device *
|
||||
soc_button_device_create(struct pnp_dev *pdev,
|
||||
soc_button_device_create(struct platform_device *pdev,
|
||||
const struct soc_button_info *button_info,
|
||||
bool autorepeat)
|
||||
{
|
||||
@ -138,30 +138,40 @@ soc_button_device_create(struct pnp_dev *pdev,
|
||||
return ERR_PTR(error);
|
||||
}
|
||||
|
||||
static void soc_button_remove(struct pnp_dev *pdev)
|
||||
static int soc_button_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct soc_button_data *priv = pnp_get_drvdata(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_pnp_probe(struct pnp_dev *pdev,
|
||||
const struct pnp_device_id *id)
|
||||
static int soc_button_probe(struct platform_device *pdev)
|
||||
{
|
||||
const struct soc_button_info *button_info = (void *)id->driver_data;
|
||||
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;
|
||||
|
||||
button_info = (struct soc_button_info *)id->driver_data;
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
|
||||
pnp_set_drvdata(pdev, priv);
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
for (i = 0; i < BUTTON_TYPES; i++) {
|
||||
pd = soc_button_device_create(pdev, button_info, i == 0);
|
||||
@ -192,30 +202,22 @@ static struct soc_button_info soc_button_PNP0C40[] = {
|
||||
{ }
|
||||
};
|
||||
|
||||
static const struct pnp_device_id soc_button_pnp_match[] = {
|
||||
{ .id = "PNP0C40", .driver_data = (long)soc_button_PNP0C40 },
|
||||
{ .id = "" }
|
||||
static const struct acpi_device_id soc_button_acpi_match[] = {
|
||||
{ "PNP0C40", (unsigned long)soc_button_PNP0C40 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pnp, soc_button_pnp_match);
|
||||
|
||||
static struct pnp_driver soc_button_pnp_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.id_table = soc_button_pnp_match,
|
||||
.probe = soc_button_pnp_probe,
|
||||
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,
|
||||
.owner = THIS_MODULE,
|
||||
.acpi_match_table = ACPI_PTR(soc_button_acpi_match),
|
||||
},
|
||||
};
|
||||
|
||||
static int __init soc_button_init(void)
|
||||
{
|
||||
return pnp_register_driver(&soc_button_pnp_driver);
|
||||
}
|
||||
|
||||
static void __exit soc_button_exit(void)
|
||||
{
|
||||
pnp_unregister_driver(&soc_button_pnp_driver);
|
||||
}
|
||||
|
||||
module_init(soc_button_init);
|
||||
module_exit(soc_button_exit);
|
||||
module_platform_driver(soc_button_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
@ -23,7 +23,7 @@ obj-$(CONFIG_MOUSE_SYNAPTICS_I2C) += synaptics_i2c.o
|
||||
obj-$(CONFIG_MOUSE_SYNAPTICS_USB) += synaptics_usb.o
|
||||
obj-$(CONFIG_MOUSE_VSXXXAA) += vsxxxaa.o
|
||||
|
||||
psmouse-objs := psmouse-base.o synaptics.o
|
||||
psmouse-objs := psmouse-base.o synaptics.o focaltech.o
|
||||
|
||||
psmouse-$(CONFIG_MOUSE_PS2_ALPS) += alps.o
|
||||
psmouse-$(CONFIG_MOUSE_PS2_ELANTECH) += elantech.o
|
||||
|
52
drivers/input/mouse/focaltech.c
Normal file
52
drivers/input/mouse/focaltech.c
Normal file
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Focaltech TouchPad PS/2 mouse driver
|
||||
*
|
||||
* Copyright (c) 2014 Red Hat Inc.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Red Hat authors:
|
||||
*
|
||||
* Hans de Goede <hdegoede@redhat.com>
|
||||
*/
|
||||
|
||||
/*
|
||||
* The Focaltech PS/2 touchpad protocol is unknown. This drivers deals with
|
||||
* detection only, to avoid further detection attempts confusing the touchpad
|
||||
* this way it at least works in PS/2 mouse compatibility mode.
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/libps2.h>
|
||||
#include "psmouse.h"
|
||||
|
||||
static const char * const focaltech_pnp_ids[] = {
|
||||
"FLT0101",
|
||||
"FLT0102",
|
||||
"FLT0103",
|
||||
NULL
|
||||
};
|
||||
|
||||
int focaltech_detect(struct psmouse *psmouse, bool set_properties)
|
||||
{
|
||||
if (!psmouse_matches_pnp_id(psmouse, focaltech_pnp_ids))
|
||||
return -ENODEV;
|
||||
|
||||
if (set_properties) {
|
||||
psmouse->vendor = "FocalTech";
|
||||
psmouse->name = "FocalTech Touchpad in mouse emulation mode";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int focaltech_init(struct psmouse *psmouse)
|
||||
{
|
||||
ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS);
|
||||
psmouse_reset(psmouse);
|
||||
|
||||
return 0;
|
||||
}
|
22
drivers/input/mouse/focaltech.h
Normal file
22
drivers/input/mouse/focaltech.h
Normal file
@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Focaltech TouchPad PS/2 mouse driver
|
||||
*
|
||||
* Copyright (c) 2014 Red Hat Inc.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* Red Hat authors:
|
||||
*
|
||||
* Hans de Goede <hdegoede@redhat.com>
|
||||
*/
|
||||
|
||||
#ifndef _FOCALTECH_H
|
||||
#define _FOCALTECH_H
|
||||
|
||||
int focaltech_detect(struct psmouse *psmouse, bool set_properties);
|
||||
int focaltech_init(struct psmouse *psmouse);
|
||||
|
||||
#endif
|
@ -35,6 +35,7 @@
|
||||
#include "elantech.h"
|
||||
#include "sentelic.h"
|
||||
#include "cypress_ps2.h"
|
||||
#include "focaltech.h"
|
||||
|
||||
#define DRIVER_DESC "PS/2 mouse driver"
|
||||
|
||||
@ -462,6 +463,20 @@ static int psmouse_poll(struct psmouse *psmouse)
|
||||
PSMOUSE_CMD_POLL | (psmouse->pktsize << 8));
|
||||
}
|
||||
|
||||
/*
|
||||
* psmouse_matches_pnp_id - check if psmouse matches one of the passed in ids.
|
||||
*/
|
||||
bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[])
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!strncmp(psmouse->ps2dev.serio->firmware_id, "PNP:", 4))
|
||||
for (i = 0; ids[i]; i++)
|
||||
if (strstr(psmouse->ps2dev.serio->firmware_id, ids[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Genius NetMouse magic init.
|
||||
@ -708,6 +723,21 @@ static int psmouse_extensions(struct psmouse *psmouse,
|
||||
{
|
||||
bool synaptics_hardware = false;
|
||||
|
||||
/* Always check for focaltech, this is safe as it uses pnp-id matching */
|
||||
if (psmouse_do_detect(focaltech_detect, psmouse, set_properties) == 0) {
|
||||
if (!set_properties || focaltech_init(psmouse) == 0) {
|
||||
/*
|
||||
* Not supported yet, use bare protocol.
|
||||
* Note that we need to also restrict
|
||||
* psmouse_max_proto so that psmouse_initialize()
|
||||
* does not try to reset rate and resolution,
|
||||
* because even that upsets the device.
|
||||
*/
|
||||
psmouse_max_proto = PSMOUSE_PS2;
|
||||
return PSMOUSE_PS2;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We always check for lifebook because it does not disturb mouse
|
||||
* (it only checks DMI information).
|
||||
|
@ -108,6 +108,7 @@ void psmouse_set_resolution(struct psmouse *psmouse, unsigned int resolution);
|
||||
psmouse_ret_t psmouse_process_byte(struct psmouse *psmouse);
|
||||
int psmouse_activate(struct psmouse *psmouse);
|
||||
int psmouse_deactivate(struct psmouse *psmouse);
|
||||
bool psmouse_matches_pnp_id(struct psmouse *psmouse, const char * const ids[]);
|
||||
|
||||
struct psmouse_attribute {
|
||||
struct device_attribute dattr;
|
||||
|
@ -185,18 +185,6 @@ static const char * const topbuttonpad_pnp_ids[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static bool matches_pnp_id(struct psmouse *psmouse, const char * const ids[])
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!strncmp(psmouse->ps2dev.serio->firmware_id, "PNP:", 4))
|
||||
for (i = 0; ids[i]; i++)
|
||||
if (strstr(psmouse->ps2dev.serio->firmware_id, ids[i]))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*****************************************************************************
|
||||
* Synaptics communications functions
|
||||
****************************************************************************/
|
||||
@ -362,7 +350,8 @@ static int synaptics_resolution(struct psmouse *psmouse)
|
||||
}
|
||||
|
||||
for (i = 0; min_max_pnpid_table[i].pnp_ids; i++) {
|
||||
if (matches_pnp_id(psmouse, min_max_pnpid_table[i].pnp_ids)) {
|
||||
if (psmouse_matches_pnp_id(psmouse,
|
||||
min_max_pnpid_table[i].pnp_ids)) {
|
||||
priv->x_min = min_max_pnpid_table[i].x_min;
|
||||
priv->x_max = min_max_pnpid_table[i].x_max;
|
||||
priv->y_min = min_max_pnpid_table[i].y_min;
|
||||
@ -1492,7 +1481,7 @@ static void set_input_params(struct psmouse *psmouse,
|
||||
|
||||
if (SYN_CAP_CLICKPAD(priv->ext_cap_0c)) {
|
||||
__set_bit(INPUT_PROP_BUTTONPAD, dev->propbit);
|
||||
if (matches_pnp_id(psmouse, topbuttonpad_pnp_ids))
|
||||
if (psmouse_matches_pnp_id(psmouse, topbuttonpad_pnp_ids))
|
||||
__set_bit(INPUT_PROP_TOPBUTTONPAD, dev->propbit);
|
||||
/* Clickpads report only left button */
|
||||
__clear_bit(BTN_RIGHT, dev->keybit);
|
||||
|
36
include/dt-bindings/input/ti-drv260x.h
Normal file
36
include/dt-bindings/input/ti-drv260x.h
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _DT_BINDINGS_TI_DRV260X_H
|
||||
#define _DT_BINDINGS_TI_DRV260X_H
|
||||
|
||||
/* Calibration Types */
|
||||
#define DRV260X_LRA_MODE 0x00
|
||||
#define DRV260X_LRA_NO_CAL_MODE 0x01
|
||||
#define DRV260X_ERM_MODE 0x02
|
||||
|
||||
/* Library Selection */
|
||||
#define DRV260X_LIB_EMPTY 0x00
|
||||
#define DRV260X_ERM_LIB_A 0x01
|
||||
#define DRV260X_ERM_LIB_B 0x02
|
||||
#define DRV260X_ERM_LIB_C 0x03
|
||||
#define DRV260X_ERM_LIB_D 0x04
|
||||
#define DRV260X_ERM_LIB_E 0x05
|
||||
#define DRV260X_LIB_LRA 0x06
|
||||
#define DRV260X_ERM_LIB_F 0x07
|
||||
|
||||
#endif
|
@ -251,6 +251,15 @@ enum max77693_haptic_reg {
|
||||
MAX77693_HAPTIC_REG_END,
|
||||
};
|
||||
|
||||
/* max77693-pmic LSCNFG configuraton register */
|
||||
#define MAX77693_PMIC_LOW_SYS_MASK 0x80
|
||||
#define MAX77693_PMIC_LOW_SYS_SHIFT 7
|
||||
|
||||
/* max77693-haptic configuration register */
|
||||
#define MAX77693_CONFIG2_MODE 7
|
||||
#define MAX77693_CONFIG2_MEN 6
|
||||
#define MAX77693_CONFIG2_HTYP 5
|
||||
|
||||
enum max77693_irq_source {
|
||||
LED_INT = 0,
|
||||
TOPSYS_INT,
|
||||
|
28
include/linux/platform_data/drv260x-pdata.h
Normal file
28
include/linux/platform_data/drv260x-pdata.h
Normal file
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Platform data for 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.
|
||||
*/
|
||||
|
||||
#ifndef _LINUX_DRV260X_PDATA_H
|
||||
#define _LINUX_DRV260X_PDATA_H
|
||||
|
||||
struct drv260x_platform_data {
|
||||
u32 library_selection;
|
||||
u32 mode;
|
||||
u32 vib_rated_voltage;
|
||||
u32 vib_overdrive_voltage;
|
||||
};
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user