2011-02-01 13:48:00

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 0/7] Qualcomm PMIC8058 sub-device drivers

This patch series contains the V2 RFC version of sub-device drivers
for Qualcomm PMIC8058. It is submitted here as RFC, due the dependent
drivers like PMIC8058 core driver and SSBI bus driver are not yet
merged into the mainline. We are sending these patches early to get
the feedback from the community.

In this V2 version we have added one new driver- PM8058 based vibrator.

If you want to refer the PMIC8058 core and SSBI driver they are available
here:

PMIC8058 core driver:
https://www.codeaurora.org/gitweb/quic/la/?p=kernel/msm.git;a=blob;f=drivers/mfd/pmic8058.c;h=dc561988c474721d76178ab1a2f82a3a4d1b03a1;hb=refs/heads/msm-2.6.35

SSBI driver:
https://www.codeaurora.org/gitweb/quic/la/?p=kernel/msm.git;a=blob;f=drivers/i2c/busses/i2c-ssbi.c;h=5bfcf3cb96f0c2e0f1a6b7f789458d2faf67f260;hb=refs/heads/msm-2.6.35

Amy Maloche (1):
input: misc: Add support for PM8058 based vibrator driver

Anirudh Ghayal (2):
input: pmic8058-othc: Add support for PM8058 based OTHC
drivers: rtc: Add support for Qualcomm PMIC8058 RTC

Trilok Soni (4):
matrix_keypad: Increase the max limit of rows and columns
input: pm8058_keypad: Qualcomm PMIC8058 keypad controller driver
led: pmic8058: Add PMIC8058 leds driver
input: pmic8058_pwrkey: Add support for power key

drivers/input/keyboard/Kconfig | 11 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/pmic8058-keypad.c | 754 ++++++++++++++++++++++++++++++
drivers/input/misc/Kconfig | 32 ++
drivers/input/misc/Makefile | 3 +
drivers/input/misc/pmic8058-othc.c | 535 +++++++++++++++++++++
drivers/input/misc/pmic8058-pwrkey.c | 318 +++++++++++++
drivers/input/misc/pmic8058-vibrator.c | 306 ++++++++++++
drivers/leds/Kconfig | 11 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-pmic8058.c | 360 ++++++++++++++
drivers/rtc/Kconfig | 9 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-pm8058.c | 488 +++++++++++++++++++
include/linux/input/matrix_keypad.h | 4 +-
include/linux/input/pmic8058-keypad.h | 53 +++
include/linux/input/pmic8058-othc.h | 117 +++++
include/linux/input/pmic8058-pwrkey.h | 37 ++
include/linux/leds-pmic8058.h | 61 +++
include/linux/rtc/rtc-pm8058.h | 29 ++
20 files changed, 3129 insertions(+), 2 deletions(-)
create mode 100644 drivers/input/keyboard/pmic8058-keypad.c
create mode 100644 drivers/input/misc/pmic8058-othc.c
create mode 100644 drivers/input/misc/pmic8058-pwrkey.c
create mode 100644 drivers/input/misc/pmic8058-vibrator.c
create mode 100644 drivers/leds/leds-pmic8058.c
create mode 100644 drivers/rtc/rtc-pm8058.c
create mode 100644 include/linux/input/pmic8058-keypad.h
create mode 100644 include/linux/input/pmic8058-othc.h
create mode 100644 include/linux/input/pmic8058-pwrkey.h
create mode 100644 include/linux/leds-pmic8058.h
create mode 100644 include/linux/rtc/rtc-pm8058.h

--
1.7.3.5


2011-02-01 13:48:20

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 1/7] matrix_keypad: Increase the max limit of rows and columns

From: Trilok Soni <[email protected]>

Some keyboard controller have support for more than
16 columns and rows. Moving this value to 32.

Cc: Eric Miao <[email protected]>
Cc: Dmitry Torokhov <[email protected]>
Signed-off-by: Trilok Soni <[email protected]>
---
Changes from v1:
Moved the max columns and rows to 32

include/linux/input/matrix_keypad.h | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/include/linux/input/matrix_keypad.h b/include/linux/input/matrix_keypad.h
index 6974746..fe7c4b9 100644
--- a/include/linux/input/matrix_keypad.h
+++ b/include/linux/input/matrix_keypad.h
@@ -4,8 +4,8 @@
#include <linux/types.h>
#include <linux/input.h>

-#define MATRIX_MAX_ROWS 16
-#define MATRIX_MAX_COLS 16
+#define MATRIX_MAX_ROWS 32
+#define MATRIX_MAX_COLS 32

#define KEY(row, col, val) ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\
(((col) & (MATRIX_MAX_COLS - 1)) << 16) |\
--
1.7.3.5

2011-02-01 13:48:25

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 2/7] input: pm8058_keypad: Qualcomm PMIC8058 keypad controller driver

From: Trilok Soni <[email protected]>

Add Qualcomm PMIC8058 based keypad controller driver
supporting upto 18x8 matrix configuration.

Cc: Dmitry Torokhov <[email protected]>
Signed-off-by: Trilok Soni <[email protected]>
---
Changes from v1:
Addressed review comments from Dmitry.
Race condition handling in suspend/resume
Statically allocate keycode array.

drivers/input/keyboard/Kconfig | 11 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/pmic8058-keypad.c | 754 ++++++++++++++++++++++++++++++
include/linux/input/pmic8058-keypad.h | 53 +++
4 files changed, 819 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/keyboard/pmic8058-keypad.c
create mode 100644 include/linux/input/pmic8058-keypad.h

diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig
index 4175073..e85fe36 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -370,6 +370,17 @@ config KEYBOARD_PXA930_ROTARY
To compile this driver as a module, choose M here: the
module will be called pxa930_rotary.

+config KEYBOARD_PMIC8058
+ tristate "Qualcomm PMIC8058 keypad support"
+ depends on PMIC8058
+ help
+ Say Y here if you want to enable the driver for the PMIC8058
+ keypad provided as a reference design from Qualcomm. This is intended
+ to support upto 18x8 matrix based keypad design.
+
+ To compile this driver as a module, choose M here: the module will
+ be called pmic8058-keypad.
+
config KEYBOARD_SAMSUNG
tristate "Samsung keypad support"
depends on SAMSUNG_DEV_KEYPAD
diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile
index 4e5571b..23e82cf 100644
--- a/drivers/input/keyboard/Makefile
+++ b/drivers/input/keyboard/Makefile
@@ -34,6 +34,7 @@ obj-$(CONFIG_KEYBOARD_OMAP4) += omap4-keypad.o
obj-$(CONFIG_KEYBOARD_OPENCORES) += opencores-kbd.o
obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keypad.o
obj-$(CONFIG_KEYBOARD_PXA930_ROTARY) += pxa930_rotary.o
+obj-$(CONFIG_KEYBOARD_PMIC8058) += pmic8058-keypad.o
obj-$(CONFIG_KEYBOARD_QT2160) += qt2160.o
obj-$(CONFIG_KEYBOARD_SAMSUNG) += samsung-keypad.o
obj-$(CONFIG_KEYBOARD_SH_KEYSC) += sh_keysc.o
diff --git a/drivers/input/keyboard/pmic8058-keypad.c b/drivers/input/keyboard/pmic8058-keypad.c
new file mode 100644
index 0000000..9f641a1
--- /dev/null
+++ b/drivers/input/keyboard/pmic8058-keypad.c
@@ -0,0 +1,754 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/bitops.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+
+#include <linux/input/pmic8058-keypad.h>
+
+#define PM8058_MAX_ROWS 18
+#define PM8058_MAX_COLS 8
+#define PM8058_ROW_SHIFT 3
+#define PM8058_MATRIX_MAX_SIZE (PM8058_MAX_ROWS * PM8058_MAX_COLS)
+
+#define PM8058_MIN_ROWS 5
+#define PM8058_MIN_COLS 5
+
+#define MAX_SCAN_DELAY 128
+#define MIN_SCAN_DELAY 1
+
+/* in nanoseconds */
+#define MAX_ROW_HOLD_DELAY 122000
+#define MIN_ROW_HOLD_DELAY 30500
+
+#define MAX_DEBOUNCE_TIME 20
+#define MIN_DEBOUNCE_TIME 5
+
+#define KEYP_CTRL 0x148
+
+#define KEYP_CTRL_EVNTS BIT(0)
+#define KEYP_CTRL_EVNTS_MASK 0x3
+
+#define KEYP_CTRL_SCAN_COLS_SHIFT 5
+#define KEYP_CTRL_SCAN_COLS_MIN 5
+#define KEYP_CTRL_SCAN_COLS_BITS 0x3
+
+#define KEYP_CTRL_SCAN_ROWS_SHIFT 2
+#define KEYP_CTRL_SCAN_ROWS_MIN 5
+#define KEYP_CTRL_SCAN_ROWS_BITS 0x7
+
+#define KEYP_CTRL_KEYP_EN BIT(7)
+
+#define KEYP_SCAN 0x149
+
+#define KEYP_SCAN_READ_STATE BIT(0)
+#define KEYP_SCAN_DBOUNCE_SHIFT 1
+#define KEYP_SCAN_PAUSE_SHIFT 3
+#define KEYP_SCAN_ROW_HOLD_SHIFT 6
+
+#define KEYP_TEST 0x14A
+
+#define KEYP_TEST_CLEAR_RECENT_SCAN BIT(6)
+#define KEYP_TEST_CLEAR_OLD_SCAN BIT(5)
+#define KEYP_TEST_READ_RESET BIT(4)
+#define KEYP_TEST_DTEST_EN BIT(3)
+#define KEYP_TEST_ABORT_READ BIT(0)
+
+#define KEYP_TEST_DBG_SELECT_SHIFT 1
+
+/* bits of these registers represent
+ * '0' for key press
+ * '1' for key release
+ */
+#define KEYP_RECENT_DATA 0x14B
+#define KEYP_OLD_DATA 0x14C
+
+#define KEYP_CLOCK_FREQ 32768
+
+/**
+ * struct pmic8058_kp - internal keypad data structure
+ * @pdata - keypad platform data pointer
+ * @input - input device pointer for keypad
+ * @key_sense_irq - key press/release irq number
+ * @key_stuck_irq - key stuck notification irq number
+ * @keycodes - array to hold the key codes
+ * @dev - parent device pointer
+ * @keystate - present key press/release state
+ * @stuckstate - present state when key stuck irq
+ * @pm_chip - parent MFD core device
+ * @ctrl_reg - control register value
+ */
+struct pmic8058_kp {
+ const struct pmic8058_keypad_data *pdata;
+ struct input_dev *input;
+ int key_sense_irq;
+ int key_stuck_irq;
+
+ unsigned short keycodes[PM8058_MATRIX_MAX_SIZE];
+
+ struct device *dev;
+ u16 keystate[PM8058_MAX_ROWS];
+ u16 stuckstate[PM8058_MAX_ROWS];
+
+ struct pm8058_chip *pm_chip;
+
+ u8 ctrl_reg;
+};
+
+static int pmic8058_kp_write_u8(struct pmic8058_kp *kp,
+ u8 data, u16 reg)
+{
+ int rc;
+
+ rc = pm8058_write(kp->pm_chip, reg, &data, 1);
+ if (rc < 0)
+ dev_warn(kp->dev, "Error writing pmic8058: %X - ret %X\n",
+ reg, rc);
+ return rc;
+}
+
+static int pmic8058_kp_read(struct pmic8058_kp *kp,
+ u8 *data, u16 reg, unsigned num_bytes)
+{
+ int rc;
+
+ rc = pm8058_read(kp->pm_chip, reg, data, num_bytes);
+ if (rc < 0)
+ dev_warn(kp->dev, "Error reading pmic8058: %X - ret %X\n",
+ reg, rc);
+
+ return rc;
+}
+
+static int pmic8058_kp_read_u8(struct pmic8058_kp *kp,
+ u8 *data, u16 reg)
+{
+ int rc;
+
+ rc = pmic8058_kp_read(kp, data, reg, 1);
+ if (rc < 0)
+ dev_warn(kp->dev, "Error reading pmic8058: %X - ret %X\n",
+ reg, rc);
+ return rc;
+}
+
+static u8 pmic8058_col_state(struct pmic8058_kp *kp, u8 col)
+{
+ /* all keys pressed on that particular row? */
+ if (col == 0x00)
+ return 1 << kp->pdata->num_cols;
+ else
+ return col & ((1 << kp->pdata->num_cols) - 1);
+}
+
+/*
+ * Synchronous read protocol for RevB0 onwards:
+ *
+ * 1. Write '1' to ReadState bit in KEYP_SCAN register
+ * 2. Wait 2*32KHz clocks, so that HW can successfully enter read mode
+ * synchronously
+ * 3. Read rows in old array first if events are more than one
+ * 4. Read rows in recent array
+ * 5. Wait 4*32KHz clocks
+ * 6. Write '0' to ReadState bit of KEYP_SCAN register so that hw can
+ * synchronously exit read mode.
+ */
+static int pmic8058_chk_sync_read(struct pmic8058_kp *kp)
+{
+ int rc;
+ u8 scan_val;
+
+ rc = pmic8058_kp_read_u8(kp, &scan_val, KEYP_SCAN);
+ if (rc < 0)
+ return rc;
+
+ scan_val |= 0x1;
+
+ rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN);
+ if (rc < 0)
+ return rc;
+
+ /* 2 * 32KHz clocks */
+ udelay((2 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1);
+
+ return rc;
+}
+
+static int pmic8058_kp_read_data(struct pmic8058_kp *kp, u16 *state,
+ u16 data_reg, int read_rows)
+{
+ int rc, row;
+ u8 new_data[PM8058_MAX_ROWS];
+
+ rc = pmic8058_kp_read(kp, new_data, data_reg, read_rows);
+
+ if (!rc) {
+ for (row = 0; row < kp->pdata->num_rows; row++) {
+ dev_dbg(kp->dev, "new_data[%d] = %d\n", row,
+ new_data[row]);
+ state[row] = pmic8058_col_state(kp, new_data[row]);
+ }
+ }
+
+ return rc;
+}
+
+static int pmic8058_kp_read_matrix(struct pmic8058_kp *kp, u16 *new_state,
+ u16 *old_state)
+{
+ int rc, read_rows;
+ u8 scan_val;
+
+ read_rows = kp->pdata->num_rows;
+
+ pmic8058_chk_sync_read(kp);
+
+ if (old_state) {
+ rc = pmic8058_kp_read_data(kp, old_state, KEYP_OLD_DATA,
+ read_rows);
+ if (rc < 0)
+ return rc;
+ }
+
+ rc = pmic8058_kp_read_data(kp, new_state, KEYP_RECENT_DATA,
+ read_rows);
+ if (rc < 0)
+ return rc;
+
+ /* 4 * 32KHz clocks */
+ udelay((4 * DIV_ROUND_UP(USEC_PER_SEC, KEYP_CLOCK_FREQ)) + 1);
+
+ rc = pmic8058_kp_read_u8(kp, &scan_val, KEYP_SCAN);
+ if (rc < 0)
+ return rc;
+ scan_val &= 0xFE;
+ rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN);
+ if (rc < 0)
+ return rc;
+
+ return rc;
+}
+
+static void __pmic8058_kp_scan_matrix(struct pmic8058_kp *kp, u16 *new_state,
+ u16 *old_state)
+{
+ int row, col, code;
+
+ for (row = 0; row < kp->pdata->num_rows; row++) {
+ int bits_changed = new_state[row] ^ old_state[row];
+
+ if (!bits_changed)
+ continue;
+
+ for (col = 0; col < kp->pdata->num_cols; col++) {
+ if (!(bits_changed & (1 << col)))
+ continue;
+
+ dev_dbg(kp->dev, "key [%d:%d] %s\n", row, col,
+ !(new_state[row] & (1 << col)) ?
+ "pressed" : "released");
+
+ code = MATRIX_SCAN_CODE(row, col, PM8058_ROW_SHIFT);
+ input_event(kp->input, EV_MSC, MSC_SCAN, code);
+ input_report_key(kp->input,
+ kp->keycodes[code],
+ !(new_state[row] & (1 << col)));
+
+ input_sync(kp->input);
+ }
+ }
+}
+
+static bool pmic8058_detect_ghost_keys(struct pmic8058_kp *kp, u16 *new_state)
+{
+ int row, found_first = -1;
+ u16 check, row_state;
+
+ check = 0;
+ for (row = 0; row < kp->pdata->num_rows; row++) {
+ row_state = (~new_state[row]) &
+ ((1 << kp->pdata->num_cols) - 1);
+
+ if (hweight16(row_state) > 1) {
+ if (found_first == -1)
+ found_first = row;
+ if (check & row_state) {
+ dev_dbg(kp->dev, "detected ghost key on row[%d]"
+ " and row[%d]\n", found_first, row);
+ return true;
+ }
+ }
+ check |= row_state;
+ }
+ return false;
+}
+
+static int pmic8058_kp_scan_matrix(struct pmic8058_kp *kp, unsigned int events)
+{
+ u16 new_state[PM8058_MAX_ROWS];
+ u16 old_state[PM8058_MAX_ROWS];
+ int rc;
+
+ switch (events) {
+ case 0x1:
+ rc = pmic8058_kp_read_matrix(kp, new_state, NULL);
+ if (rc < 0)
+ return rc;
+
+ /* detecting ghost key is not an error */
+ if (pmic8058_detect_ghost_keys(kp, new_state))
+ return 0;
+ __pmic8058_kp_scan_matrix(kp, new_state, kp->keystate);
+ memcpy(kp->keystate, new_state, sizeof(new_state));
+ break;
+ case 0x3: /* two events - eventcounter is gray-coded */
+ rc = pmic8058_kp_read_matrix(kp, new_state, old_state);
+ if (rc < 0)
+ return rc;
+
+ __pmic8058_kp_scan_matrix(kp, old_state, kp->keystate);
+ __pmic8058_kp_scan_matrix(kp, new_state, old_state);
+ memcpy(kp->keystate, new_state, sizeof(new_state));
+ break;
+ case 0x2:
+ dev_dbg(kp->dev, "Some key events were lost\n");
+ rc = pmic8058_kp_read_matrix(kp, new_state, old_state);
+ if (rc < 0)
+ return rc;
+ __pmic8058_kp_scan_matrix(kp, old_state, kp->keystate);
+ __pmic8058_kp_scan_matrix(kp, new_state, old_state);
+ memcpy(kp->keystate, new_state, sizeof(new_state));
+ break;
+ default:
+ rc = -EINVAL;
+ }
+ return rc;
+}
+
+/*
+ * NOTE: We are reading recent and old data registers blindly
+ * whenever key-stuck interrupt happens, because events counter doesn't
+ * get updated when this interrupt happens due to key stuck doesn't get
+ * considered as key state change.
+ *
+ * We are not using old data register contents after they are being read
+ * because it might report the key which was pressed before the key being stuck
+ * as stuck key because it's pressed status is stored in the old data
+ * register.
+ */
+static irqreturn_t pmic8058_kp_stuck_irq(int irq, void *data)
+{
+ u16 new_state[PM8058_MAX_ROWS];
+ u16 old_state[PM8058_MAX_ROWS];
+ int rc;
+ struct pmic8058_kp *kp = data;
+
+ rc = pmic8058_kp_read_matrix(kp, new_state, old_state);
+ if (rc < 0) {
+ dev_err(kp->dev, "failed to read keypad matrix\n");
+ return IRQ_HANDLED;
+ }
+
+ __pmic8058_kp_scan_matrix(kp, new_state, kp->stuckstate);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pmic8058_kp_irq(int irq, void *data)
+{
+ struct pmic8058_kp *kp = data;
+ u8 ctrl_val, events;
+ int rc;
+
+ rc = pmic8058_kp_read(kp, &ctrl_val, KEYP_CTRL, 1);
+ if (rc < 0) {
+ dev_err(kp->dev, "failed to read keyp_ctrl register\n");
+ return IRQ_HANDLED;
+ }
+
+ events = ctrl_val & KEYP_CTRL_EVNTS_MASK;
+
+ rc = pmic8058_kp_scan_matrix(kp, events);
+ if (rc < 0)
+ dev_err(kp->dev, "failed to scan matrix\n");
+
+ return IRQ_HANDLED;
+}
+
+static int pmic8058_kpd_init(struct pmic8058_kp *kp)
+{
+ int bits, rc, cycles;
+ u8 scan_val = 0, ctrl_val = 0;
+ static const u8 row_bits[] = {
+ 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7,
+ };
+
+ /* Find column bits */
+ if (kp->pdata->num_cols < KEYP_CTRL_SCAN_COLS_MIN)
+ bits = 0;
+ else
+ bits = kp->pdata->num_cols - KEYP_CTRL_SCAN_COLS_MIN;
+ ctrl_val = (bits & KEYP_CTRL_SCAN_COLS_BITS) <<
+ KEYP_CTRL_SCAN_COLS_SHIFT;
+
+ /* Find row bits */
+ if (kp->pdata->num_rows < KEYP_CTRL_SCAN_ROWS_MIN)
+ bits = 0;
+ else
+ bits = row_bits[kp->pdata->num_rows - KEYP_CTRL_SCAN_ROWS_MIN];
+
+ ctrl_val |= (bits << KEYP_CTRL_SCAN_ROWS_SHIFT);
+
+ rc = pmic8058_kp_write_u8(kp, ctrl_val, KEYP_CTRL);
+ if (rc < 0)
+ return rc;
+
+ bits = (kp->pdata->debounce_ms / 5) - 1;
+
+ scan_val |= (bits << KEYP_SCAN_DBOUNCE_SHIFT);
+
+ bits = fls(kp->pdata->scan_delay_ms) - 1;
+ scan_val |= (bits << KEYP_SCAN_PAUSE_SHIFT);
+
+ /* Row hold time is a multiple of 32KHz cycles. */
+ cycles = (kp->pdata->row_hold_ns * KEYP_CLOCK_FREQ) / NSEC_PER_SEC;
+
+ scan_val |= (cycles << KEYP_SCAN_ROW_HOLD_SHIFT);
+
+ rc = pmic8058_kp_write_u8(kp, scan_val, KEYP_SCAN);
+
+ return rc;
+}
+
+static int pmic8058_kp_enable(struct pmic8058_kp *kp)
+{
+ int rc;
+
+ kp->ctrl_reg |= KEYP_CTRL_KEYP_EN;
+
+ rc = pmic8058_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL);
+ if (rc < 0)
+ return rc;
+
+ return rc;
+}
+
+static int pmic8058_kp_disable(struct pmic8058_kp *kp)
+{
+ int rc;
+
+ kp->ctrl_reg &= ~KEYP_CTRL_KEYP_EN;
+
+ rc = pmic8058_kp_write_u8(kp, kp->ctrl_reg, KEYP_CTRL);
+ if (rc < 0)
+ return rc;
+
+ return rc;
+}
+
+static int pmic8058_kp_open(struct input_dev *dev)
+{
+ struct pmic8058_kp *kp = input_get_drvdata(dev);
+
+ return pmic8058_kp_enable(kp);
+}
+
+static void pmic8058_kp_close(struct input_dev *dev)
+{
+ struct pmic8058_kp *kp = input_get_drvdata(dev);
+
+ pmic8058_kp_disable(kp);
+}
+
+/*
+ * keypad controller should be initialized in the following sequence
+ * only, otherwise it might get into FSM stuck state.
+ *
+ * - Initialize keypad control parameters, like no. of rows, columns,
+ * timing values etc.,
+ * - configure rows and column gpios pull up/down.
+ * - set irq edge type.
+ * - enable the keypad controller.
+ */
+static int __devinit pmic8058_kp_probe(struct platform_device *pdev)
+{
+ struct pmic8058_keypad_data *pdata = pdev->dev.platform_data;
+ const struct matrix_keymap_data *keymap_data;
+ struct pmic8058_kp *kp;
+ int rc;
+ u8 ctrl_val;
+ struct pm8058_chip *pm_chip;
+
+ pm_chip = platform_get_drvdata(pdev);
+ if (pm_chip == NULL) {
+ dev_err(&pdev->dev, "no parent data passed in\n");
+ return -EINVAL;
+ }
+
+ /* Check PMIC8058 version. 1.0 version is not supported */
+ if (pm8058_rev(pm_chip) == PM_8058_REV_1p0) {
+ dev_err(&pdev->dev, "PMIC8058 1.0 version is not supported\n");
+ return -ENODEV;
+ }
+
+ if (!pdata || !pdata->num_cols || !pdata->num_rows ||
+ pdata->num_cols > PM8058_MAX_COLS ||
+ pdata->num_rows > PM8058_MAX_ROWS ||
+ pdata->num_cols < PM8058_MIN_COLS ||
+ pdata->num_rows < PM8058_MIN_ROWS) {
+ dev_err(&pdev->dev, "invalid platform data\n");
+ return -EINVAL;
+ }
+
+ if (!pdata->scan_delay_ms || pdata->scan_delay_ms > MAX_SCAN_DELAY
+ || pdata->scan_delay_ms < MIN_SCAN_DELAY ||
+ !is_power_of_2(pdata->scan_delay_ms)) {
+ dev_err(&pdev->dev, "invalid keypad scan time supplied\n");
+ return -EINVAL;
+ }
+
+ if (!pdata->row_hold_ns || pdata->row_hold_ns > MAX_ROW_HOLD_DELAY ||
+ pdata->row_hold_ns < MIN_ROW_HOLD_DELAY ||
+ ((pdata->row_hold_ns % MIN_ROW_HOLD_DELAY) != 0)) {
+ dev_err(&pdev->dev, "invalid keypad row hold time supplied\n");
+ return -EINVAL;
+ }
+
+ if (!pdata->debounce_ms ||
+ ((pdata->debounce_ms % 5) != 0) ||
+ pdata->debounce_ms > MAX_DEBOUNCE_TIME ||
+ pdata->debounce_ms < MIN_DEBOUNCE_TIME) {
+ dev_err(&pdev->dev, "invalid debounce time supplied\n");
+ return -EINVAL;
+ }
+
+ keymap_data = pdata->keymap_data;
+ if (!keymap_data) {
+ dev_err(&pdev->dev, "no keymap data supplied\n");
+ return -EINVAL;
+ }
+
+ kp = kzalloc(sizeof(*kp), GFP_KERNEL);
+ if (!kp)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, kp);
+
+ kp->pdata = pdata;
+ kp->dev = &pdev->dev;
+ kp->pm_chip = pm_chip;
+
+ kp->input = input_allocate_device();
+ if (!kp->input) {
+ dev_err(&pdev->dev, "unable to allocate input device\n");
+ rc = -ENOMEM;
+ goto err_alloc_device;
+ }
+
+ kp->key_sense_irq = platform_get_irq(pdev, 0);
+ if (kp->key_sense_irq < 0) {
+ dev_err(&pdev->dev, "unable to get keypad sense irq\n");
+ rc = -ENXIO;
+ goto err_get_irq;
+ }
+
+ kp->key_stuck_irq = platform_get_irq(pdev, 1);
+ if (kp->key_stuck_irq < 0) {
+ dev_err(&pdev->dev, "unable to get keypad stuck irq\n");
+ rc = -ENXIO;
+ goto err_get_irq;
+ }
+
+ kp->input->name = pdata->input_name ? : "PMIC8058 keypad";
+ kp->input->phys = pdata->input_phys_device ? : "pmic8058_keypad/input0";
+
+ kp->input->dev.parent = &pdev->dev;
+
+ kp->input->id.bustype = BUS_I2C;
+ kp->input->id.version = 0x0001;
+ kp->input->id.product = 0x0001;
+ kp->input->id.vendor = 0x0001;
+
+ kp->input->evbit[0] = BIT_MASK(EV_KEY);
+
+ if (pdata->rep)
+ __set_bit(EV_REP, kp->input->evbit);
+
+ kp->input->keycode = kp->keycodes;
+ kp->input->keycodemax = PM8058_MATRIX_MAX_SIZE;
+ kp->input->keycodesize = sizeof(kp->keycodes);
+ kp->input->open = pmic8058_kp_open;
+ kp->input->close = pmic8058_kp_close;
+
+ matrix_keypad_build_keymap(keymap_data, PM8058_ROW_SHIFT,
+ kp->input->keycode, kp->input->keybit);
+
+ input_set_capability(kp->input, EV_MSC, MSC_SCAN);
+ input_set_drvdata(kp->input, kp);
+
+ /* initialize keypad state */
+ memset(kp->keystate, 0xff, sizeof(kp->keystate));
+ memset(kp->stuckstate, 0xff, sizeof(kp->stuckstate));
+
+ rc = pmic8058_kpd_init(kp);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "unable to initialize keypad controller\n");
+ goto err_kpd_init;
+ }
+
+ rc = request_any_context_irq(kp->key_sense_irq, pmic8058_kp_irq,
+ IRQF_TRIGGER_RISING, "pmic-keypad", kp);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "failed to request keypad sense irq\n");
+ goto err_req_sense_irq;
+ }
+
+ rc = request_any_context_irq(kp->key_stuck_irq, pmic8058_kp_stuck_irq,
+ IRQF_TRIGGER_RISING, "pmic-keypad-stuck", kp);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "failed to request keypad stuck irq\n");
+ goto err_req_stuck_irq;
+ }
+
+ rc = pmic8058_kp_read_u8(kp, &ctrl_val, KEYP_CTRL);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "failed to read KEYP_CTRL register\n");
+ goto err_pmic_reg_read;
+ }
+
+ kp->ctrl_reg = ctrl_val;
+
+ rc = input_register_device(kp->input);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "unable to register keypad input device\n");
+ goto err_reg_input_dev;
+ }
+
+ device_init_wakeup(&pdev->dev, pdata->wakeup);
+
+ return 0;
+
+err_reg_input_dev:
+err_pmic_reg_read:
+ free_irq(kp->key_stuck_irq, NULL);
+err_req_stuck_irq:
+ free_irq(kp->key_sense_irq, NULL);
+err_req_sense_irq:
+err_kpd_init:
+err_get_irq:
+ input_free_device(kp->input);
+err_alloc_device:
+ kfree(kp);
+ return rc;
+}
+
+static int __devexit pmic8058_kp_remove(struct platform_device *pdev)
+{
+ struct pmic8058_kp *kp = platform_get_drvdata(pdev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ free_irq(kp->key_stuck_irq, NULL);
+ free_irq(kp->key_sense_irq, NULL);
+ input_unregister_device(kp->input);
+ kfree(kp);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pmic8058_kp_suspend(struct device *dev)
+{
+ struct pmic8058_kp *kp = dev_get_drvdata(dev);
+ struct input_dev *input_dev = kp->input;
+
+ if (device_may_wakeup(dev)) {
+ enable_irq_wake(kp->key_sense_irq);
+ } else {
+ mutex_lock(&input_dev->mutex);
+
+ if (input_dev->users)
+ pmic8058_kp_disable(kp);
+
+ mutex_unlock(&input_dev->mutex);
+ }
+
+ return 0;
+}
+
+static int pmic8058_kp_resume(struct device *dev)
+{
+ struct pmic8058_kp *kp = dev_get_drvdata(dev);
+ struct input_dev *input_dev = kp->input;
+
+ if (device_may_wakeup(dev)) {
+ disable_irq_wake(kp->key_sense_irq);
+ } else {
+ mutex_lock(&input_dev->mutex);
+
+ if (input_dev->users)
+ pmic8058_kp_enable(kp);
+
+ mutex_unlock(&input_dev->mutex);
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops pm8058_kp_pm_ops = {
+ .suspend = pmic8058_kp_suspend,
+ .resume = pmic8058_kp_resume,
+};
+#endif
+
+static struct platform_driver pmic8058_kp_driver = {
+ .probe = pmic8058_kp_probe,
+ .remove = __devexit_p(pmic8058_kp_remove),
+ .driver = {
+ .name = "pm8058-keypad",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &pm8058_kp_pm_ops,
+#endif
+ },
+};
+
+static int __init pmic8058_kp_init(void)
+{
+ return platform_driver_register(&pmic8058_kp_driver);
+}
+module_init(pmic8058_kp_init);
+
+static void __exit pmic8058_kp_exit(void)
+{
+ platform_driver_unregister(&pmic8058_kp_driver);
+}
+module_exit(pmic8058_kp_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PMIC8058 keypad driver");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:pmic8058_keypad");
+MODULE_AUTHOR("Trilok Soni <[email protected]>");
diff --git a/include/linux/input/pmic8058-keypad.h b/include/linux/input/pmic8058-keypad.h
new file mode 100644
index 0000000..f5acfb9
--- /dev/null
+++ b/include/linux/input/pmic8058-keypad.h
@@ -0,0 +1,53 @@
+/* Copyright (c) 2009-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __PMIC8058_KEYPAD_H__
+#define __PMIC8058_KEYPAD_H__
+
+#include <linux/input/matrix_keypad.h>
+
+/**
+ * struct pmic8058_keypad_data - platform data for keypad
+ * @keymap_data - matrix keymap data
+ * @input_name - input device name
+ * @input_phys_device - input device name
+ * @num_cols - number of columns of keypad
+ * @num_rows - number of row of keypad
+ * @debounce_ms - debounce period in milliseconds
+ * @scan_delay_ms - scan delay in milliseconds
+ * @row_hold_ns - row hold period in nanoseconds
+ * @wakeup - configure keypad as wakeup
+ * @rep - enable or disable key repeat bit
+ */
+struct pmic8058_keypad_data {
+ const struct matrix_keymap_data *keymap_data;
+
+ const char *input_name;
+ const char *input_phys_device;
+
+ unsigned int num_cols;
+ unsigned int num_rows;
+
+ unsigned int debounce_ms;
+ unsigned int scan_delay_ms;
+ unsigned int row_hold_ns;
+
+ bool wakeup;
+ bool rep;
+};
+
+#endif /*__PMIC8058_KEYPAD_H__ */
--
1.7.3.5

2011-02-01 13:48:33

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 3/7] led: pmic8058: Add PMIC8058 leds driver

From: Trilok Soni <[email protected]>

Add support for Qualcomm PMIC8058 keyboard
backlight, flash and low current leds.

Cc: Dmitry Torokhov <[email protected]>
Cc: Lars-Peter Clausen <[email protected]>
Signed-off-by: Trilok Soni <[email protected]>
---
Changes from v1:
Addressed review comments from Peter and Dmitry.

drivers/leds/Kconfig | 11 ++
drivers/leds/Makefile | 1 +
drivers/leds/leds-pmic8058.c | 360 +++++++++++++++++++++++++++++++++++++++++
include/linux/leds-pmic8058.h | 61 +++++++
4 files changed, 433 insertions(+), 0 deletions(-)
create mode 100644 drivers/leds/leds-pmic8058.c
create mode 100644 include/linux/leds-pmic8058.h

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 6f190f4..235a1e5 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -250,6 +250,17 @@ config LEDS_PCA955X
LED driver chips accessed via the I2C bus. Supported
devices include PCA9550, PCA9551, PCA9552, and PCA9553.

+config LEDS_PMIC8058
+ tristate "LED Support for Qualcomm PMIC8058"
+ depends on PMIC8058
+ help
+ This option enables support for LEDs connected over PMIC8058
+ (Power Management IC) chip on Qualcomm reference boards,
+ for example SURF and FFAs.
+
+ To compile this driver as a module, choose M here: the module will
+ be called leds-pmic8058.
+
config LEDS_WM831X_STATUS
tristate "LED support for status LEDs on WM831x PMICs"
depends on LEDS_CLASS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index aae6989..9706739 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -21,6 +21,7 @@ obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o
obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
+obj-$(CONFIG_LEDS_PMIC8058) += leds-pmic8058.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
diff --git a/drivers/leds/leds-pmic8058.c b/drivers/leds/leds-pmic8058.c
new file mode 100644
index 0000000..fb3d7be
--- /dev/null
+++ b/drivers/leds/leds-pmic8058.c
@@ -0,0 +1,360 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/leds-pmic8058.h>
+
+#define SSBI_REG_ADDR_DRV_KEYPAD 0x48
+#define PM8058_DRV_KEYPAD_BL_MASK 0xf0
+#define PM8058_DRV_KEYPAD_BL_SHIFT 0x04
+
+#define SSBI_REG_ADDR_FLASH_DRV0 0x49
+#define PM8058_DRV_FLASH_MASK 0xf0
+#define PM8058_DRV_FLASH_SHIFT 0x04
+
+#define SSBI_REG_ADDR_FLASH_DRV1 0xFB
+
+#define SSBI_REG_ADDR_LED_CTRL_BASE 0x131
+#define SSBI_REG_ADDR_LED_CTRL(n) (SSBI_REG_ADDR_LED_CTRL_BASE + (n))
+#define PM8058_DRV_LED_CTRL_MASK 0xf8
+#define PM8058_DRV_LED_CTRL_SHIFT 0x03
+
+#define MAX_FLASH_LED_CURRENT 300
+#define MAX_LC_LED_CURRENT 40
+#define MAX_KP_BL_LED_CURRENT 300
+
+#define MAX_KEYPAD_BL_LEVEL (1 << 4)
+#define MAX_LED_DRV_LEVEL 20 /* 2 * 20 mA */
+
+#define PMIC8058_LED_OFFSET(id) ((id) - PMIC8058_ID_LED_0)
+
+#define MAX_KB_LED_BRIGHTNESS 15
+#define MAX_LC_LED_BRIGHTNESS 20
+#define MAX_FLASH_LED_BRIGHTNESS 15
+
+#define PMIC8058_MAX_LEDS 7
+
+/**
+ * struct pmic8058_led_data - internal led data structure
+ * @led_classdev - led class device
+ * @id - led index
+ * @led_brightness - led brightness levels
+ * @pm_chip - parent MFD core device
+ * @work - workqueue for led
+ * @lock - to protect the transactions
+ * @reg - cached value of led register
+ */
+struct pmic8058_led_data {
+ struct led_classdev cdev;
+ int id;
+ u8 reg;
+ enum led_brightness brightness;
+ struct pm8058_chip *pm_chip;
+ struct work_struct work;
+ struct mutex lock;
+};
+
+#define PM8058_MAX_LEDS 7
+
+static void led_kp_set(struct pmic8058_led_data *led, enum led_brightness value)
+{
+ int rc;
+ u8 level;
+
+ level = (value << PM8058_DRV_KEYPAD_BL_SHIFT) &
+ PM8058_DRV_KEYPAD_BL_MASK;
+
+ led->reg &= ~PM8058_DRV_KEYPAD_BL_MASK;
+ led->reg |= level;
+
+ rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_DRV_KEYPAD,
+ &led->reg, 1);
+ if (rc < 0)
+ dev_err(led->cdev.dev, "can't set keypad backlight level\n");
+}
+
+static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value)
+{
+ int rc, offset;
+ u8 level;
+
+ level = (value << PM8058_DRV_LED_CTRL_SHIFT) &
+ PM8058_DRV_LED_CTRL_MASK;
+
+ offset = PMIC8058_LED_OFFSET(led->id);
+
+ led->reg &= ~PM8058_DRV_LED_CTRL_MASK;
+ led->reg |= level;
+
+ rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_LED_CTRL(offset),
+ &led->reg, 1);
+ if (rc)
+ dev_err(led->cdev.dev, "can't set (%d) led value\n",
+ led->id);
+}
+
+static void
+led_flash_set(struct pmic8058_led_data *led, enum led_brightness value)
+{
+ int rc;
+ u8 level;
+ u16 reg_addr;
+
+ level = (value << PM8058_DRV_FLASH_SHIFT) &
+ PM8058_DRV_FLASH_MASK;
+
+ led->reg &= ~PM8058_DRV_FLASH_MASK;
+ led->reg |= level;
+
+ if (led->id == PMIC8058_ID_FLASH_LED_0)
+ reg_addr = SSBI_REG_ADDR_FLASH_DRV0;
+ else
+ reg_addr = SSBI_REG_ADDR_FLASH_DRV1;
+
+ rc = pm8058_write(led->pm_chip, reg_addr, &led->reg, 1);
+ if (rc < 0)
+ dev_err(led->cdev.dev, "can't set flash led%d level\n",
+ led->id);
+}
+
+static void pmic8058_led_work(struct work_struct *work)
+{
+ struct pmic8058_led_data *led = container_of(work,
+ struct pmic8058_led_data, work);
+
+ mutex_lock(&led->lock);
+
+ switch (led->id) {
+ case PMIC8058_ID_LED_KB_LIGHT:
+ led_kp_set(led, led->brightness);
+ break;
+ case PMIC8058_ID_LED_0:
+ case PMIC8058_ID_LED_1:
+ case PMIC8058_ID_LED_2:
+ led_lc_set(led, led->brightness);
+ break;
+ case PMIC8058_ID_FLASH_LED_0:
+ case PMIC8058_ID_FLASH_LED_1:
+ led_flash_set(led, led->brightness);
+ break;
+ }
+
+ mutex_unlock(&led->lock);
+}
+
+static void pmic8058_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct pmic8058_led_data *led;
+
+ led = container_of(led_cdev, struct pmic8058_led_data, cdev);
+
+ led->brightness = value;
+ schedule_work(&led->work);
+}
+
+static enum led_brightness pmic8058_led_get(struct led_classdev *led_cdev)
+{
+ struct pmic8058_led_data *led;
+
+ led = container_of(led_cdev, struct pmic8058_led_data, cdev);
+
+ return led->brightness;
+}
+
+static int get_max_brightness(enum pmic8058_leds id)
+{
+ switch (id) {
+ case PMIC8058_ID_LED_KB_LIGHT:
+ return MAX_KB_LED_BRIGHTNESS;
+ case PMIC8058_ID_LED_0:
+ case PMIC8058_ID_LED_1:
+ case PMIC8058_ID_LED_2:
+ return MAX_LC_LED_BRIGHTNESS;
+ case PMIC8058_ID_FLASH_LED_0:
+ case PMIC8058_ID_FLASH_LED_1:
+ return MAX_FLASH_LED_CURRENT;
+ default:
+ return 0;
+ }
+}
+
+static int get_init_value(struct pmic8058_led_data *led, u8 *val)
+{
+ int rc, offset;
+ u16 addr;
+
+ switch (led->id) {
+ case PMIC8058_ID_LED_KB_LIGHT:
+ addr = SSBI_REG_ADDR_DRV_KEYPAD;
+ break;
+ case PMIC8058_ID_LED_0:
+ case PMIC8058_ID_LED_1:
+ case PMIC8058_ID_LED_2:
+ offset = PMIC8058_LED_OFFSET(led->id);
+ addr = SSBI_REG_ADDR_LED_CTRL(offset);
+ break;
+ case PMIC8058_ID_FLASH_LED_0:
+ addr = SSBI_REG_ADDR_FLASH_DRV0;
+ break;
+ case PMIC8058_ID_FLASH_LED_1:
+ addr = SSBI_REG_ADDR_FLASH_DRV1;
+ break;
+ }
+
+ rc = pm8058_read(led->pm_chip, addr, val, 1);
+ if (rc)
+ dev_err(led->cdev.dev, "can't get led(%d) level\n", led->id);
+
+ return rc;
+}
+
+static int __devinit pmic8058_led_probe(struct platform_device *pdev)
+{
+ const struct pmic8058_leds_platform_data *pdata =
+ pdev->dev.platform_data;
+ struct pmic8058_led_data *led_dat;
+ struct pmic8058_led *curr_led;
+ int rc, i;
+ struct pm8058_chip *pm_chip;
+ struct pmic8058_led_data *led;
+
+ pm_chip = platform_get_drvdata(pdev);
+ if (pm_chip == NULL) {
+ dev_err(&pdev->dev, "no parent data passed in\n");
+ return -EINVAL;
+ }
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "platform data not supplied\n");
+ return -EINVAL;
+ }
+
+ if (pdata->num_leds > PMIC8058_MAX_LEDS) {
+ dev_err(&pdev->dev, "can't handle more than %d LEDs\n",
+ PMIC8058_MAX_LEDS);
+ return -EFAULT;
+ }
+
+ led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL);
+ if (led == NULL) {
+ dev_err(&pdev->dev, "failed to alloc memory\n");
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < pdata->num_leds; i++) {
+ curr_led = &pdata->leds[i];
+ led_dat = &led[curr_led->id];
+
+ led_dat->id = curr_led->id;
+
+ if (!((led_dat->id >= PMIC8058_ID_LED_KB_LIGHT) &&
+ (led_dat->id <= PMIC8058_ID_FLASH_LED_1))) {
+ dev_err(&pdev->dev, "invalid LED ID (%d) specified\n",
+ led_dat->id);
+ rc = -EINVAL;
+ goto fail_id_check;
+ }
+
+ led_dat->cdev.name = curr_led->name;
+ led_dat->cdev.default_trigger = curr_led->default_trigger;
+ led_dat->cdev.brightness_set = pmic8058_led_set;
+ led_dat->cdev.brightness_get = pmic8058_led_get;
+ led_dat->cdev.brightness = LED_OFF;
+ led_dat->cdev.flags = LED_CORE_SUSPENDRESUME;
+
+ led_dat->cdev.max_brightness = get_max_brightness(led_dat->id);
+ led_dat->pm_chip = pm_chip;
+
+ rc = get_init_value(led_dat, &led_dat->reg);
+ if (rc < 0)
+ goto fail_id_check;
+
+ mutex_init(&led_dat->lock);
+ INIT_WORK(&led_dat->work, pmic8058_led_work);
+
+ rc = led_classdev_register(&pdev->dev, &led_dat->cdev);
+ if (rc) {
+ dev_err(&pdev->dev, "unable to register led %d\n",
+ led_dat->id);
+ goto fail_id_check;
+ }
+ }
+
+ platform_set_drvdata(pdev, led);
+
+ return 0;
+
+fail_id_check:
+ if (i > 0) {
+ for (i = i - 1; i >= 0; i--)
+ led_classdev_unregister(&led[i].cdev);
+ }
+ kfree(led);
+ return rc;
+}
+
+static int __devexit pmic8058_led_remove(struct platform_device *pdev)
+{
+ int i;
+ const struct pmic8058_leds_platform_data *pdata =
+ pdev->dev.platform_data;
+ struct pmic8058_led_data *led = platform_get_drvdata(pdev);
+
+ for (i = 0; i < pdata->num_leds; i++) {
+ mutex_destroy(&led[led->id].lock);
+ led_classdev_unregister(&led[led->id].cdev);
+ cancel_work_sync(&led[led->id].work);
+ }
+
+ kfree(led);
+
+ return 0;
+}
+
+static struct platform_driver pmic8058_led_driver = {
+ .probe = pmic8058_led_probe,
+ .remove = __devexit_p(pmic8058_led_remove),
+ .driver = {
+ .name = "pm8058-led",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pmic8058_led_init(void)
+{
+ return platform_driver_register(&pmic8058_led_driver);
+}
+module_init(pmic8058_led_init);
+
+static void __exit pmic8058_led_exit(void)
+{
+ platform_driver_unregister(&pmic8058_led_driver);
+}
+module_exit(pmic8058_led_exit);
+
+MODULE_DESCRIPTION("PMIC8058 LEDs driver");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("platform:pmic8058-led");
+MODULE_AUTHOR("Trilok Soni <[email protected]>");
diff --git a/include/linux/leds-pmic8058.h b/include/linux/leds-pmic8058.h
new file mode 100644
index 0000000..42a3ae7
--- /dev/null
+++ b/include/linux/leds-pmic8058.h
@@ -0,0 +1,61 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __LEDS_PMIC8058_H__
+#define __LEDS_PMIC8058_H__
+
+/**
+ * enum pmic8058_leds - PMIC8058 supported led ids
+ * @PMIC8058_ID_LED_KB_LIGHT - keyboard backlight led
+ * @PMIC8058_ID_LED_0 - First low current led
+ * @PMIC8058_ID_LED_1 - Second low current led
+ * @PMIC8058_ID_LED_2 - Third low current led
+ * @PMIC8058_ID_FLASH_LED_0 - First flash led
+ * @PMIC8058_ID_FLASH_LED_0 - Second flash led
+ */
+enum pmic8058_leds {
+ PMIC8058_ID_LED_KB_LIGHT = 1,
+ PMIC8058_ID_LED_0,
+ PMIC8058_ID_LED_1,
+ PMIC8058_ID_LED_2,
+ PMIC8058_ID_FLASH_LED_0,
+ PMIC8058_ID_FLASH_LED_1,
+};
+
+/**
+ * struct pmic8058_led - per led data
+ * @name - name of the led
+ * @default_trigger - default trigger which needs to e attached
+ * @id - supported led id
+ */
+struct pmic8058_led {
+ const char *name;
+ const char *default_trigger;
+ enum pmic8058_leds id;
+};
+
+/**
+ * struct pmic8058_leds_platform_data - platform data for leds
+ * @num_leds - number of leds
+ * @leds - array of struct pmic8058_led
+ */
+struct pmic8058_leds_platform_data {
+ int num_leds;
+ struct pmic8058_led *leds;
+};
+
+#endif /* __LEDS_PMIC8058_H__ */
--
1.7.3.5

2011-02-01 13:48:40

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 4/7] input: pmic8058_pwrkey: Add support for power key

From: Trilok Soni <[email protected]>

Add support for PMIC8058 power key driven over dedicated
KYPD_PWR_N pin. It allows the user to specify the amount
of time by which the power key reporting can be delayed.

Cc: Dmitry Torokhov <[email protected]>
Signed-off-by: Trilok Soni <[email protected]>
---
Changes from v1:
Addressed review comments from Dmitry.
Added KEY_SCREENLOCK instead of KEY_END.
Moved to timer list instead of hires timers.

drivers/input/misc/Kconfig | 11 ++
drivers/input/misc/Makefile | 1 +
drivers/input/misc/pmic8058-pwrkey.c | 318 +++++++++++++++++++++++++++++++++
include/linux/input/pmic8058-pwrkey.h | 37 ++++
4 files changed, 367 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/misc/pmic8058-pwrkey.c
create mode 100644 include/linux/input/pmic8058-pwrkey.h

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index b0c6772..d70a7be 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -330,6 +330,17 @@ config INPUT_PWM_BEEPER
To compile this driver as a module, choose M here: the module will be
called pwm-beeper.

+config INPUT_PMIC8058_PWRKEY
+ tristate "PMIC8058 power key support"
+ depends on PMIC8058
+ help
+ Say Y here if you want support for the PMIC8058 power key.
+
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pmic8058-pwrkey.
+
config INPUT_GPIO_ROTARY_ENCODER
tristate "Rotary encoders connected to GPIO pins"
depends on GPIOLIB && GENERIC_GPIO
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 9b47971..0348704 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o
obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
+obj-$(CONFIG_INPUT_PMIC8058_PWRKEY) += pmic8058-pwrkey.o
obj-$(CONFIG_INPUT_RB532_BUTTON) += rb532_button.o
obj-$(CONFIG_INPUT_GPIO_ROTARY_ENCODER) += rotary_encoder.o
obj-$(CONFIG_INPUT_SGI_BTNS) += sgi_btns.o
diff --git a/drivers/input/misc/pmic8058-pwrkey.c b/drivers/input/misc/pmic8058-pwrkey.c
new file mode 100644
index 0000000..029483e
--- /dev/null
+++ b/drivers/input/misc/pmic8058-pwrkey.c
@@ -0,0 +1,318 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/input.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/log2.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+
+#include <linux/input/pmic8058-pwrkey.h>
+
+#define PON_CNTL_1 0x1C
+#define PON_CNTL_PULL_UP BIT(7)
+#define PON_CNTL_TRIG_DELAY_MASK (0x7)
+
+/**
+ * struct pmic8058_pwrkey - pmic8058 pwrkey information
+ * @key_press_irq: key press irq number
+ * @pm_chip: pmic8058 parent
+ * @timer: timer for end key simulation
+ * @key_pressed: flag to keep track for power key reporting
+ * @pdata: platform data
+ * @lock: protect key press update and end key simulation
+ */
+struct pmic8058_pwrkey {
+ struct input_dev *pwr;
+ int key_press_irq;
+ struct pm8058_chip *pm_chip;
+ struct timer_list timer;
+ bool key_pressed;
+ const struct pmic8058_pwrkey_pdata *pdata;
+ spinlock_t lock;
+};
+
+static void pmic8058_pwrkey_timer(unsigned long handle)
+{
+ unsigned long flags;
+ struct pmic8058_pwrkey *pwrkey = (struct pmic8058_pwrkey *)handle;
+
+ spin_lock_irqsave(&pwrkey->lock, flags);
+ pwrkey->key_pressed = true;
+
+ input_report_key(pwrkey->pwr, KEY_POWER, 1);
+ input_sync(pwrkey->pwr);
+ spin_unlock_irqrestore(&pwrkey->lock, flags);
+}
+
+static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey)
+{
+ struct pmic8058_pwrkey *pwrkey = _pwrkey;
+ const struct pmic8058_pwrkey_pdata *pdata = pwrkey->pdata;
+ unsigned long flags;
+
+ /* no pwrkey time duration, means no end key simulation */
+ if (!pwrkey->pdata->pwrkey_time_ms) {
+ input_report_key(pwrkey->pwr, KEY_POWER, 1);
+ input_sync(pwrkey->pwr);
+ return IRQ_HANDLED;
+ }
+
+ spin_lock_irqsave(&pwrkey->lock, flags);
+
+ input_report_key(pwrkey->pwr, KEY_SCROLLLOCK, 1);
+ input_sync(pwrkey->pwr);
+
+ mod_timer(&pwrkey->timer, jiffies +
+ msecs_to_jiffies(pdata->pwrkey_time_ms));
+ spin_unlock_irqrestore(&pwrkey->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pwrkey_release_irq(int irq, void *_pwrkey)
+{
+ struct pmic8058_pwrkey *pwrkey = _pwrkey;
+ unsigned long flags;
+
+ /* no pwrkey time, means no delay in pwr key reporting */
+ if (!pwrkey->pdata->pwrkey_time_ms) {
+ input_report_key(pwrkey->pwr, KEY_POWER, 0);
+ input_sync(pwrkey->pwr);
+ return IRQ_HANDLED;
+ }
+
+ spin_lock_irqsave(&pwrkey->lock, flags);
+ del_timer_sync(&pwrkey->timer);
+
+ if (pwrkey->key_pressed) {
+ pwrkey->key_pressed = false;
+ input_report_key(pwrkey->pwr, KEY_POWER, 0);
+ input_sync(pwrkey->pwr);
+ }
+
+ input_report_key(pwrkey->pwr, KEY_SCREENLOCK, 0);
+ input_sync(pwrkey->pwr);
+
+ spin_unlock_irqrestore(&pwrkey->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int pmic8058_pwrkey_suspend(struct device *dev)
+{
+ struct pmic8058_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(pwrkey->key_press_irq);
+
+ return 0;
+}
+
+static int pmic8058_pwrkey_resume(struct device *dev)
+{
+ struct pmic8058_pwrkey *pwrkey = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(pwrkey->key_press_irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pm8058_pwr_key_pm_ops = {
+ .suspend = pmic8058_pwrkey_suspend,
+ .resume = pmic8058_pwrkey_resume,
+};
+#endif
+
+static int __devinit pmic8058_pwrkey_probe(struct platform_device *pdev)
+{
+ struct input_dev *pwr;
+ int key_release_irq = platform_get_irq(pdev, 0);
+ int key_press_irq = platform_get_irq(pdev, 1);
+ int err;
+ unsigned int delay;
+ u8 pon_cntl;
+ struct pmic8058_pwrkey *pwrkey;
+ struct pmic8058_pwrkey_pdata *pdata = pdev->dev.platform_data;
+ struct pm8058_chip *pm_chip;
+
+ pm_chip = platform_get_drvdata(pdev);
+ if (pm_chip == NULL) {
+ dev_err(&pdev->dev, "no parent data passed in\n");
+ return -EINVAL;
+ }
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "power key platform data not supplied\n");
+ return -EINVAL;
+ }
+
+ if (pdata->kpd_trigger_delay_us > 62500) {
+ dev_err(&pdev->dev, "invalid pwr key trigger delay\n");
+ return -EINVAL;
+ }
+
+ if (pdata->pwrkey_time_ms &&
+ (pdata->pwrkey_time_ms < 500 || pdata->pwrkey_time_ms > 1000)) {
+ dev_err(&pdev->dev, "invalid pwr key time supplied\n");
+ return -EINVAL;
+ }
+
+ pwrkey = kzalloc(sizeof(*pwrkey), GFP_KERNEL);
+ if (!pwrkey)
+ return -ENOMEM;
+
+ pwrkey->pm_chip = pm_chip;
+ pwrkey->pdata = pdata;
+
+ pwr = input_allocate_device();
+ if (!pwr) {
+ dev_dbg(&pdev->dev, "Can't allocate power button\n");
+ err = -ENOMEM;
+ goto free_pwrkey;
+ }
+
+ input_set_capability(pwr, EV_KEY, KEY_POWER);
+ input_set_capability(pwr, EV_KEY, KEY_SCROLLLOCK);
+
+ pwr->name = "pmic8058_pwrkey";
+ pwr->phys = "pmic8058_pwrkey/input0";
+ pwr->dev.parent = &pdev->dev;
+
+ delay = (pdata->kpd_trigger_delay_us << 10) / USEC_PER_SEC;
+ delay = 1 + ilog2(delay);
+
+ err = pm8058_read(pwrkey->pm_chip, PON_CNTL_1, &pon_cntl, 1);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed reading PON_CNTL_1 err=%d\n", err);
+ goto free_input_dev;
+ }
+
+
+ pon_cntl &= ~PON_CNTL_TRIG_DELAY_MASK;
+ pon_cntl |= (delay & PON_CNTL_TRIG_DELAY_MASK);
+ pon_cntl |= (pdata->pull_up ? PON_CNTL_PULL_UP : ~PON_CNTL_PULL_UP);
+ err = pm8058_write(pwrkey->pm_chip, PON_CNTL_1, &pon_cntl, 1);
+ if (err < 0) {
+ dev_err(&pdev->dev, "failed writing PON_CNTL_1 err=%d\n", err);
+ goto free_input_dev;
+ }
+
+ setup_timer(&pwrkey->timer, pmic8058_pwrkey_timer,
+ (unsigned long) pwrkey);
+
+ spin_lock_init(&pwrkey->lock);
+
+ err = input_register_device(pwr);
+ if (err) {
+ dev_dbg(&pdev->dev, "Can't register power key: %d\n", err);
+ goto free_input_dev;
+ }
+
+ pwrkey->key_press_irq = key_press_irq;
+ pwrkey->pwr = pwr;
+
+ platform_set_drvdata(pdev, pwrkey);
+
+ err = request_any_context_irq(key_press_irq, pwrkey_press_irq,
+ IRQF_TRIGGER_RISING, "pmic8058_pwrkey_press", pwrkey);
+ if (err < 0) {
+ dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
+ key_press_irq, err);
+ goto unreg_input_dev;
+ }
+
+ err = request_any_context_irq(key_release_irq, pwrkey_release_irq,
+ IRQF_TRIGGER_RISING, "pmic8058_pwrkey_release",
+ pwrkey);
+ if (err < 0) {
+ dev_dbg(&pdev->dev, "Can't get %d IRQ for pwrkey: %d\n",
+ key_release_irq, err);
+
+ goto free_press_irq;
+ }
+
+ device_init_wakeup(&pdev->dev, pdata->wakeup);
+
+ return 0;
+
+free_press_irq:
+ free_irq(key_press_irq, NULL);
+unreg_input_dev:
+ input_unregister_device(pwr);
+ pwr = NULL;
+free_input_dev:
+ input_free_device(pwr);
+free_pwrkey:
+ kfree(pwrkey);
+ return err;
+}
+
+static int __devexit pmic8058_pwrkey_remove(struct platform_device *pdev)
+{
+ struct pmic8058_pwrkey *pwrkey = platform_get_drvdata(pdev);
+ int key_release_irq = platform_get_irq(pdev, 0);
+ int key_press_irq = platform_get_irq(pdev, 1);
+
+ device_init_wakeup(&pdev->dev, 0);
+
+ free_irq(key_press_irq, pwrkey);
+ free_irq(key_release_irq, pwrkey);
+ del_timer_sync(&pwrkey->timer);
+ input_unregister_device(pwrkey->pwr);
+ kfree(pwrkey);
+
+ return 0;
+}
+
+static struct platform_driver pmic8058_pwrkey_driver = {
+ .probe = pmic8058_pwrkey_probe,
+ .remove = __devexit_p(pmic8058_pwrkey_remove),
+ .driver = {
+ .name = "pm8058-pwrkey",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &pm8058_pwr_key_pm_ops,
+#endif
+ },
+};
+
+static int __init pmic8058_pwrkey_init(void)
+{
+ return platform_driver_register(&pmic8058_pwrkey_driver);
+}
+module_init(pmic8058_pwrkey_init);
+
+static void __exit pmic8058_pwrkey_exit(void)
+{
+ platform_driver_unregister(&pmic8058_pwrkey_driver);
+}
+module_exit(pmic8058_pwrkey_exit);
+
+MODULE_ALIAS("platform:pmic8058_pwrkey");
+MODULE_DESCRIPTION("PMIC8058 Power Key driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Trilok Soni <[email protected]>");
diff --git a/include/linux/input/pmic8058-pwrkey.h b/include/linux/input/pmic8058-pwrkey.h
new file mode 100644
index 0000000..e41c616
--- /dev/null
+++ b/include/linux/input/pmic8058-pwrkey.h
@@ -0,0 +1,37 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __PMIC8058_PWRKEY_H__
+#define __PMIC8058_PWRKEY_H__
+/**
+ * struct pmic8058_pwrkey_pdata - platform data for pwrkey driver
+ * @pull up: power on register control for pull up/down configuration
+ * @pwrkey_time_ms: time after which power key event should be generated, if
+ * key is released before then end key is reported.
+ * Supply zero for only power key reporting.
+ * @kpd_trigger_delay_us: time delay for power key state change interrupt
+ * trigger.
+ * @wakeup: configure power key as wakeup source
+ */
+struct pmic8058_pwrkey_pdata {
+ bool pull_up;
+ u16 pwrkey_time_ms;
+ u32 kpd_trigger_delay_us;
+ u32 wakeup;
+};
+
+#endif /* __PMIC8058_PWRKEY_H__ */
--
1.7.3.5

2011-02-01 13:48:46

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 5/7] input: pmic8058-othc: Add support for PM8058 based OTHC

One-touch headset controller is a hardware module in Qualcomm's PMIC8058.
It supports headset insert/remove and switch press/release detection events
over 3 MIC BIAS lines. The MIC BIAS lines can be configured to support
headset detection or act as regular BIAS lines.

Cc: Shubhrajyoti Datta <[email protected]>
Cc: Dmitry Torokhov <[email protected]>
Signed-off-by: Anirudh Ghayal <[email protected]>
---
Addressed review comments from Dimitry.
removed IRQF_ONESHOT, const pdata.

drivers/input/misc/Kconfig | 9 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/pmic8058-othc.c | 535 +++++++++++++++++++++++++++++++++++
include/linux/input/pmic8058-othc.h | 117 ++++++++
4 files changed, 662 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/misc/pmic8058-othc.c
create mode 100644 include/linux/input/pmic8058-othc.h

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index d70a7be..3802f54 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -465,4 +465,13 @@ config INPUT_CMA3000_I2C
To compile this driver as a module, choose M here: the
module will be called cma3000_d0x_i2c.

+config INPUT_PMIC8058_OTHC
+ tristate "Qualcomm PMIC8058 OTHC support"
+ depends on PMIC8058
+ help
+ Say Y here if you want support PMIC8058 One-touch Headset Controller
+ (OTHC)
+
+ To compile this driver as a module, choose M here: the
+ module will be called pmic8058-othc.
endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 0348704..df9a679 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -31,6 +31,7 @@ obj-$(CONFIG_INPUT_PCAP) += pcap_keys.o
obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o
obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o
obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o
+obj-$(CONFIG_INPUT_PMIC8058_OTHC) += pmic8058-othc.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
obj-$(CONFIG_INPUT_PMIC8058_PWRKEY) += pmic8058-pwrkey.o
diff --git a/drivers/input/misc/pmic8058-othc.c b/drivers/input/misc/pmic8058-othc.c
new file mode 100644
index 0000000..83fd8a1
--- /dev/null
+++ b/drivers/input/misc/pmic8058-othc.c
@@ -0,0 +1,535 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#define pr_fmt(fmt) "%s:" fmt, __func__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/input.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/spinlock.h>
+
+#include <linux/mfd/pmic8058.h>
+#include <linux/input/pmic8058-othc.h>
+
+#define PM8058_OTHC_LOW_CURR_MASK 0xF0
+#define PM8058_OTHC_HIGH_CURR_MASK 0x0F
+#define PM8058_OTHC_EN_SIG_MASK 0x3F
+#define PM8058_OTHC_HYST_PREDIV_MASK 0xC7
+#define PM8058_OTHC_CLK_PREDIV_MASK 0xF8
+#define PM8058_OTHC_HYST_CLK_MASK 0x0F
+#define PM8058_OTHC_PERIOD_CLK_MASK 0xF0
+
+#define PM8058_OTHC_LOW_CURR_SHIFT 0x4
+#define PM8058_OTHC_EN_SIG_SHIFT 0x6
+#define PM8058_OTHC_HYST_PREDIV_SHIFT 0x3
+#define PM8058_OTHC_HYST_CLK_SHIFT 0x4
+
+#define PM8058_OTHC_LOW_CURR_MIRROR 10
+#define PM8058_OTHC_HIGH_CURR_MIRROR 100
+#define PM8058_OTHC_CLK_SRC_SHIFT 10
+
+/**
+ * struct pm8058_othc - othc driver data structure
+ * @othc_ipd: input device for othc
+ * @othc_pdata: a pointer to the platform data
+ * @othc_base: base address of the OTHC controller
+ * @othc_irq_sw: switch detect irq number
+ * @othc_irq_ir: headset jack detect irq number
+ * @othc_sw_state: current state of the switch
+ * @othc_ir_state: current state of the headset
+ * @switch_reject: indicates if valid switch press
+ * @switch_debounce_ms: the debounce time for the switch
+ * @lock: spin lock variable
+ * @timer: timer for switch debounce time
+ * @pm_chip: pointer to the pm8058 parent chip
+ */
+struct pm8058_othc {
+ struct input_dev *othc_ipd;
+ const struct pmic8058_othc_config_pdata *othc_pdata;
+ int othc_base;
+ int othc_irq_sw;
+ int othc_irq_ir;
+ bool othc_sw_state;
+ bool othc_ir_state;
+ bool switch_reject;
+ unsigned long switch_debounce_ms;
+ spinlock_t lock;
+ struct timer_list timer;
+ struct pm8058_chip *pm_chip;
+};
+
+static struct pm8058_othc *config[OTHC_MICBIAS_MAX];
+
+/**
+ * pm8058_micbias_enable() - Enables/Disables the MIC_BIAS
+ * @micbias: MIC BIAS line which needs to be enabled
+ * @enable: operational state for the MIC BIAS line
+ *
+ * The API pm8058_micbias_enable() configures the MIC_BIAS. Only the lines
+ * which are not used for headset detection can be configured using this API.
+ * The API returns an error code if it fails to configure, else it returns 0.
+ */
+int pm8058_micbias_enable(enum othc_micbias micbias,
+ enum othc_micbias_enable enable)
+{
+ int rc;
+ u8 reg;
+ struct pm8058_othc *dd = config[micbias];
+
+ if (dd == NULL) {
+ pr_err("MIC_BIAS not registered, cannot enable\n");
+ return -ENODEV;
+ }
+
+ if (dd->othc_pdata->micbias_capability != OTHC_MICBIAS) {
+ pr_err("MIC_BIAS enable capability not supported\n");
+ return -EINVAL;
+ }
+
+ rc = pm8058_read(dd->pm_chip, dd->othc_base + 1, &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ reg &= PM8058_OTHC_EN_SIG_MASK;
+ reg |= (enable << PM8058_OTHC_EN_SIG_SHIFT);
+
+ rc = pm8058_write(dd->pm_chip, dd->othc_base + 1, &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 write failed\n");
+ return rc;
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(pm8058_micbias_enable);
+
+#ifdef CONFIG_PM
+static int pm8058_othc_suspend(struct device *dev)
+{
+ struct pm8058_othc *dd = dev_get_drvdata(dev);
+
+ if (dd->othc_pdata->micbias_capability == OTHC_MICBIAS_HSED) {
+ if (device_may_wakeup(dev)) {
+ enable_irq_wake(dd->othc_irq_sw);
+ enable_irq_wake(dd->othc_irq_ir);
+ }
+ }
+
+ return 0;
+}
+
+static int pm8058_othc_resume(struct device *dev)
+{
+ struct pm8058_othc *dd = dev_get_drvdata(dev);
+
+ if (dd->othc_pdata->micbias_capability == OTHC_MICBIAS_HSED) {
+ if (device_may_wakeup(dev)) {
+ disable_irq_wake(dd->othc_irq_sw);
+ disable_irq_wake(dd->othc_irq_ir);
+ }
+ }
+
+ return 0;
+}
+
+static const struct dev_pm_ops pm8058_othc_pm_ops = {
+ .suspend = pm8058_othc_suspend,
+ .resume = pm8058_othc_resume,
+};
+#endif
+
+static int __devexit pm8058_othc_remove(struct platform_device *pd)
+{
+ struct pm8058_othc *dd = platform_get_drvdata(pd);
+
+ if (dd->othc_pdata->micbias_capability == OTHC_MICBIAS_HSED) {
+ device_init_wakeup(&pd->dev, 0);
+ free_irq(dd->othc_irq_sw, dd);
+ free_irq(dd->othc_irq_ir, dd);
+ del_timer_sync(&dd->timer);
+ input_unregister_device(dd->othc_ipd);
+ }
+
+ kfree(dd);
+
+ return 0;
+}
+
+static void pm8058_othc_timer(unsigned long handle)
+{
+ unsigned long flags;
+ struct pm8058_othc *dd = (struct pm8058_othc *)handle;
+
+ spin_lock_irqsave(&dd->lock, flags);
+ dd->switch_reject = false;
+ spin_unlock_irqrestore(&dd->lock, flags);
+}
+
+static irqreturn_t pm8058_no_sw(int irq, void *dev_id)
+{
+ struct pm8058_othc *dd = dev_id;
+ unsigned long flags;
+
+ /*
+ * Due to a hardware bug, spurious switch interrutps are seen while
+ * inserting the headset slowly. A timer based logic rejects these
+ * switch interrutps.
+ */
+ spin_lock_irqsave(&dd->lock, flags);
+ if (dd->switch_reject == true) {
+ spin_unlock_irqrestore(&dd->lock, flags);
+ return IRQ_HANDLED;
+ }
+ spin_unlock_irqrestore(&dd->lock, flags);
+
+ dd->othc_sw_state = !dd->othc_sw_state;
+ input_report_key(dd->othc_ipd, KEY_MEDIA, dd->othc_sw_state);
+ input_sync(dd->othc_ipd);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm8058_nc_ir(int irq, void *dev_id)
+{
+ unsigned long flags;
+ struct pm8058_othc *dd = dev_id;
+
+ spin_lock_irqsave(&dd->lock, flags);
+ dd->switch_reject = true;
+ spin_unlock_irqrestore(&dd->lock, flags);
+
+ mod_timer(&dd->timer, jiffies +
+ msecs_to_jiffies(dd->switch_debounce_ms));
+
+ dd->othc_ir_state = !dd->othc_ir_state;
+ input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, dd->othc_ir_state);
+ input_sync(dd->othc_ipd);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit
+pm8058_configure_othc(struct pm8058_othc *dd)
+{
+ int rc;
+ u8 reg, value;
+ u32 value1;
+ u16 base_addr = dd->othc_base;
+ const struct othc_hsed_config *hsed_config =
+ dd->othc_pdata->hsed_config;
+
+ /* Control Register 1*/
+ rc = pm8058_read(dd->pm_chip, base_addr, &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ if (hsed_config->othc_headset == OTHC_HEADSET_NO) {
+ /* set iDAC high current threshold */
+ value = (hsed_config->othc_highcurr_thresh_uA /
+ PM8058_OTHC_HIGH_CURR_MIRROR) - 2;
+ reg = (reg & PM8058_OTHC_HIGH_CURR_MASK) | value;
+ } else {
+ /* set iDAC low current threshold */
+ value = (hsed_config->othc_lowcurr_thresh_uA /
+ PM8058_OTHC_LOW_CURR_MIRROR) - 1;
+ reg &= PM8058_OTHC_LOW_CURR_MASK;
+ reg |= (value << PM8058_OTHC_LOW_CURR_SHIFT);
+ }
+
+ rc = pm8058_write(dd->pm_chip, base_addr, &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ /* Control register 2*/
+ rc = pm8058_read(dd->pm_chip, base_addr + 1, &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ value = dd->othc_pdata->micbias_enable;
+ reg &= PM8058_OTHC_EN_SIG_MASK;
+ reg |= (value << PM8058_OTHC_EN_SIG_SHIFT);
+
+ value = 0;
+ value1 = (hsed_config->othc_hyst_prediv_us <<
+ PM8058_OTHC_CLK_SRC_SHIFT) / USEC_PER_SEC;
+ while (value1 != 0) {
+ value1 = value1 >> 1;
+ value++;
+ }
+ if (value > 7) {
+ pr_err("Invalid input argument - othc_hyst_prediv_us\n");
+ return -EINVAL;
+ }
+ reg &= PM8058_OTHC_HYST_PREDIV_MASK;
+ reg |= (value << PM8058_OTHC_HYST_PREDIV_SHIFT);
+
+ value = 0;
+ value1 = (hsed_config->othc_period_clkdiv_us <<
+ PM8058_OTHC_CLK_SRC_SHIFT) / USEC_PER_SEC;
+ while (value1 != 1) {
+ value1 = value1 >> 1;
+ value++;
+ }
+ if (value > 8) {
+ pr_err("Invalid input argument - othc_period_clkdiv_us\n");
+ return -EINVAL;
+ }
+ reg = (reg & PM8058_OTHC_CLK_PREDIV_MASK) | (value - 1);
+
+ rc = pm8058_write(dd->pm_chip, base_addr + 1, &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ /* Control register 3 */
+ rc = pm8058_read(dd->pm_chip, base_addr + 2 , &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ value = hsed_config->othc_hyst_clk_us /
+ hsed_config->othc_hyst_prediv_us;
+ if (value > 15) {
+ pr_err("Invalid input argument - othc_hyst_prediv_us\n");
+ return -EINVAL;
+ }
+ reg &= PM8058_OTHC_HYST_CLK_MASK;
+ reg |= value << PM8058_OTHC_HYST_CLK_SHIFT;
+
+ value = hsed_config->othc_period_clk_us /
+ hsed_config->othc_period_clkdiv_us;
+ if (value > 15) {
+ pr_err("Invalid input argument - othc_hyst_prediv_us\n");
+ return -EINVAL;
+ }
+ reg = (reg & PM8058_OTHC_PERIOD_CLK_MASK) | value;
+
+ rc = pm8058_write(dd->pm_chip, base_addr + 2, &reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int __devinit
+pm8058_othc_configure_hsed(struct pm8058_othc *dd, struct platform_device *pd)
+{
+ int rc;
+ struct input_dev *ipd;
+ const struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data;
+ const struct othc_hsed_config *hsed_config = pdata->hsed_config;
+
+ ipd = input_allocate_device();
+ if (ipd == NULL) {
+ dev_err(&pd->dev, "Memory allocate to input device failed!\n");
+ rc = -ENOMEM;
+ goto fail_input_alloc;
+ }
+
+ dd->othc_irq_sw = platform_get_irq(pd, 0);
+ dd->othc_irq_ir = platform_get_irq(pd, 1);
+ if (dd->othc_irq_ir < 0 || dd->othc_irq_sw < 0) {
+ dev_err(&pd->dev, "othc resource:IRQ_IR absent!\n");
+ rc = -ENXIO;
+ goto fail_othc_config;
+ }
+
+ ipd->name = "pmic8058_othc";
+ ipd->phys = "pmic8058_othc/input0";
+ ipd->dev.parent = &pd->dev;
+
+ input_set_capability(ipd, EV_SW, SW_HEADPHONE_INSERT);
+ input_set_capability(ipd, EV_KEY, KEY_MEDIA);
+
+ input_set_drvdata(ipd, dd);
+
+ dd->othc_ipd = ipd;
+ dd->othc_sw_state = false;
+ dd->othc_ir_state = false;
+ spin_lock_init(&dd->lock);
+ dd->switch_debounce_ms = hsed_config->switch_debounce_ms;
+ setup_timer(&dd->timer, pm8058_othc_timer, (unsigned long) dd);
+
+ rc = pm8058_configure_othc(dd);
+ if (rc < 0)
+ goto fail_othc_config;
+
+ rc = input_register_device(ipd);
+ if (rc) {
+ dev_err(&pd->dev, "Register OTHC device failed!\n");
+ goto fail_othc_config;
+ }
+
+ /* Check if the headset is already inserted during boot up */
+ rc = pm8058_irq_get_rt_status(dd->pm_chip, dd->othc_irq_ir);
+ if (rc < 0) {
+ dev_err(&pd->dev, "Unable to get headset status at boot!\n");
+ goto fail_ir_irq;
+ }
+ if (rc) {
+ dev_dbg(&pd->dev, "Headset inserted during boot up!\n");
+ dd->othc_ir_state = true;
+ input_report_switch(dd->othc_ipd, SW_HEADPHONE_INSERT, 1);
+ input_sync(dd->othc_ipd);
+ }
+
+ rc = request_any_context_irq(dd->othc_irq_ir, pm8058_nc_ir,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "pm8058_othc_ir", dd);
+ if (rc < 0) {
+ dev_err(&pd->dev, "Request pm8058_othc_ir IRQ failed!\n");
+ goto fail_ir_irq;
+ }
+
+ rc = request_any_context_irq(dd->othc_irq_sw, pm8058_no_sw,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "pm8058_othc_sw", dd);
+ if (rc < 0) {
+ dev_err(&pd->dev, "Request pm8058_othc_sw IRQ failed!\n");
+ goto fail_sw_irq;
+ }
+
+ device_init_wakeup(&pd->dev, hsed_config->othc_wakeup);
+
+ return 0;
+
+fail_sw_irq:
+ free_irq(dd->othc_irq_ir, dd);
+fail_ir_irq:
+ input_unregister_device(ipd);
+ dd->othc_ipd = NULL;
+fail_othc_config:
+ input_free_device(ipd);
+fail_input_alloc:
+ return rc;
+}
+
+static int __devinit pm8058_othc_probe(struct platform_device *pd)
+{
+ int rc;
+ struct pm8058_othc *dd;
+ struct pm8058_chip *chip;
+ struct resource *res;
+ struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data;
+
+ chip = platform_get_drvdata(pd);
+ if (chip == NULL) {
+ dev_err(&pd->dev, "Invalid driver information!\n");
+ return -EINVAL;
+ }
+
+ /* PMIC8058 version 1.0 not supported */
+ if (pm8058_rev(chip) == PM_8058_REV_1p0) {
+ dev_err(&pd->dev, "PMIC8058 version not supported!\n");
+ return -ENODEV;
+ }
+
+ if (pdata == NULL) {
+ dev_err(&pd->dev, "Platform data not present!\n");
+ return -EINVAL;
+ }
+
+ dd = kzalloc(sizeof(*dd), GFP_KERNEL);
+ if (dd == NULL) {
+ dev_err(&pd->dev, "Unable to allocate memory!\n");
+ return -ENOMEM;
+ }
+
+ res = platform_get_resource_byname(pd, IORESOURCE_IO, "othc_base");
+ if (res == NULL) {
+ dev_err(&pd->dev, "OTHC Base address, resource absent!\n");
+ rc = -ENXIO;
+ goto fail_get_res;
+ }
+
+ dd->othc_pdata = pdata;
+ dd->pm_chip = chip;
+ dd->othc_base = res->start;
+
+ platform_set_drvdata(pd, dd);
+
+ if (pdata->micbias_capability == OTHC_MICBIAS_HSED) {
+ /* HSED to be supported on this MICBIAS line */
+ if (pdata->hsed_config != NULL) {
+ rc = pm8058_othc_configure_hsed(dd, pd);
+ if (rc < 0)
+ goto fail_get_res;
+ } else {
+ dev_err(&pd->dev, "HSED config data absent!\n");
+ rc = -EINVAL;
+ goto fail_get_res;
+ }
+ }
+
+ /* Store the local driver data structure */
+ if (dd->othc_pdata->micbias_select < OTHC_MICBIAS_MAX)
+ config[dd->othc_pdata->micbias_select] = dd;
+
+ dev_dbg(&pd->dev, "Device %s:%d successfully registered\n",
+ pd->name, pd->id);
+ return 0;
+
+fail_get_res:
+ kfree(dd);
+ return rc;
+}
+
+static struct platform_driver pm8058_othc_driver = {
+ .driver = {
+ .name = "pm8058-othc",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &pm8058_othc_pm_ops,
+#endif
+ },
+ .probe = pm8058_othc_probe,
+ .remove = __devexit_p(pm8058_othc_remove),
+};
+
+static int __init pm8058_othc_init(void)
+{
+ return platform_driver_register(&pm8058_othc_driver);
+}
+module_init(pm8058_othc_init);
+
+static void __exit pm8058_othc_exit(void)
+{
+ platform_driver_unregister(&pm8058_othc_driver);
+}
+module_exit(pm8058_othc_exit);
+
+MODULE_ALIAS("platform:pmic8058_othc");
+MODULE_DESCRIPTION("PMIC8058 OTHC");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Anirudh Ghayal <[email protected]>");
diff --git a/include/linux/input/pmic8058-othc.h b/include/linux/input/pmic8058-othc.h
new file mode 100644
index 0000000..2d7ff79
--- /dev/null
+++ b/include/linux/input/pmic8058-othc.h
@@ -0,0 +1,117 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __PMIC8058_OTHC_H__
+#define __PMIC8058_OTHC_H__
+
+/**
+ * enum othc_micbias_enable - MIC BIAS operational states
+ *
+ * This enum describes the different configurations of the BIAS line.
+ */
+enum othc_micbias_enable {
+ /* Turn off BIAS */
+ OTHC_SIGNAL_OFF,
+ /* Turn on BIAS if TCXO_EN is high */
+ OTHC_SIGNAL_TCXO,
+ /* Turn on BIAS if TCXO_EN or PWN is high */
+ OTHC_SIGNAL_PWM_TCXO,
+ /* Turn on BIAS always */
+ OTHC_SIGNAL_ALWAYS_ON,
+};
+
+/**
+ * enum othc_headset_type - Different type of supported headset
+ *
+ * This enum describes the different types of supported headsets.
+ */
+enum othc_headset_type {
+ OTHC_HEADSET_NO,
+ OTHC_HEADSET_NC,
+};
+
+/**
+ * enum othc_micbias - Lists the number of MIC BIAS lines.
+ *
+ * This enum lists all the total number of BIAS lines.
+ */
+enum othc_micbias {
+ OTHC_MICBIAS_0,
+ OTHC_MICBIAS_1,
+ OTHC_MICBIAS_2,
+ OTHC_MICBIAS_MAX,
+};
+
+/**
+ * enum othc_micbias_capability - Capability of the MIC BIAS line
+ *
+ * This enum describes the capability of the MIC BIAS line, it can either be
+ * used for headset or a regular speaker MIC BIAS.
+ */
+enum othc_micbias_capability {
+ OTHC_MICBIAS,
+ OTHC_MICBIAS_HSED,
+};
+
+/**
+ * struct othc_hsed_config - headset specific configuration structure
+ * @othc_headset: type of headset
+ * @othc_lowcurr_thresh_uA: low current threshold for the headset
+ * @othc_highcurr_thresh_uA: high current threshold for the headset
+ * @othc_hyst_prediv_us: hysterisis time pre-divider
+ * @othc_period_clkdiv_us: pwm period pre-divider
+ * @othc_hyst_clk_us: hysterisis clock period
+ * @othc_hyst_clk_us: hysterisis clock period
+ * @othc_period_clk_us: pwm clock period
+ * @othc_wakeup: wakeup capability
+ * @switch_debounce_ms: specifies the switch debounce time
+ *
+ * This structure provides the configurable parameters for headset. This is a
+ * part of the platform data.
+ */
+struct othc_hsed_config {
+ enum othc_headset_type othc_headset;
+ u16 othc_lowcurr_thresh_uA;
+ u16 othc_highcurr_thresh_uA;
+ u32 othc_hyst_prediv_us;
+ u32 othc_period_clkdiv_us;
+ u32 othc_hyst_clk_us;
+ u32 othc_period_clk_us;
+ int othc_wakeup;
+ unsigned long switch_debounce_ms;
+};
+
+/**
+ * struct pmic8058_othc_config_pdata - platform data for OTHC
+ * @micbias_select: selects the MIC BIAS
+ * @micbias_enable: default operational configuration of the MIC BIAS
+ * @micbias_capability: capability supported by the MIC BIAS
+ * @hsed_config: pointer to headset configuration
+ *
+ * This structure is the platform data provided to the OTHC driver
+ */
+struct pmic8058_othc_config_pdata {
+ enum othc_micbias micbias_select;
+ enum othc_micbias_enable micbias_enable;
+ enum othc_micbias_capability micbias_capability;
+ struct othc_hsed_config *hsed_config;
+};
+
+int pm8058_micbias_enable(enum othc_micbias micbias,
+ enum othc_micbias_enable enable);
+
+#endif /* __PMIC8058_OTHC_H__ */
--
1.7.3.5

2011-02-01 13:48:57

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 7/7] input: misc: Add support for PM8058 based vibrator driver

From: Amy Maloche <[email protected]>

Add support for pmic8058 based vibrator to facilitate haptics.

Cc: Dmitry Torokhov <[email protected]>
Signed-off-by: Mohan Pallaka <[email protected]>
---
drivers/input/misc/Kconfig | 12 ++
drivers/input/misc/Makefile | 1 +
drivers/input/misc/pmic8058-vibrator.c | 306 ++++++++++++++++++++++++++++++++
3 files changed, 319 insertions(+), 0 deletions(-)
create mode 100644 drivers/input/misc/pmic8058-vibrator.c

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 3802f54..e391f9e 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -74,6 +74,18 @@ config INPUT_PCSPKR
To compile this driver as a module, choose M here: the
module will be called pcspkr.

+config INPUT_PMIC8058_VIBRATOR
+ tristate "Qualcomm PM8058 vibrator support"
+ depends on PMIC8058
+ select INPUT_FF_MEMLESS
+ help
+ This option enables device driver support for the vibrator
+ on Qualcomm PM8058 chip. This driver supports ff-memless interface
+ from input framework.
+
+ To compile this driver as module, choose M here: the
+ module will be called pmic8058-vibrator.
+
config INPUT_SPARCSPKR
tristate "SPARC Speaker support"
depends on PCI && SPARC64
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index df9a679..b5fa9f6 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_INPUT_PCF50633_PMU) += pcf50633-input.o
obj-$(CONFIG_INPUT_PCF8574) += pcf8574_keypad.o
obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o
obj-$(CONFIG_INPUT_PMIC8058_OTHC) += pmic8058-othc.o
+obj-$(CONFIG_INPUT_PMIC8058_VIBRATOR) += pmic8058-vibrator.o
obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
obj-$(CONFIG_INPUT_PWM_BEEPER) += pwm-beeper.o
obj-$(CONFIG_INPUT_PMIC8058_PWRKEY) += pmic8058-pwrkey.o
diff --git a/drivers/input/misc/pmic8058-vibrator.c b/drivers/input/misc/pmic8058-vibrator.c
new file mode 100644
index 0000000..cb1a797
--- /dev/null
+++ b/drivers/input/misc/pmic8058-vibrator.c
@@ -0,0 +1,306 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/pmic8058.h>
+#include <linux/input/pmic8058-vibrator.h>
+
+#define VIB_DRV 0x4A
+
+#define VIB_DRV_SEL_MASK 0xf8
+#define VIB_DRV_SEL_SHIFT 0x03
+#define VIB_DRV_EN_MANUAL_MASK 0xfc
+
+#define VIB_MAX_LEVEL_mV (3100)
+#define VIB_MIN_LEVEL_mV (1200)
+#define VIB_MAX_LEVELS (VIB_MAX_LEVEL_mV - VIB_MIN_LEVEL_mV)
+
+#define MAX_FF_SPEED 0xff
+
+/*
+ * struct pmic8058_vib - structure to hold vibrator data
+ * vib_input_dev: input device supporting force feedback
+ * work: work structure to set the vibration parameters
+ * dev: device supporting force feedback
+ * pdata: platform data
+ * pm_chip: reference to pmic chip to carry out read/write operations
+ * speed: speed of vibration set from userland
+ * state: state of vibrator
+ * level: level of vibration to set in the chip
+ * reg_vib_drv: VIB_DRV register value
+ *
+ */
+struct pmic8058_vib {
+ struct input_dev *vib_input_dev;
+ struct work_struct work;
+ struct device *dev;
+ const struct pmic8058_vibrator_pdata *pdata;
+ struct pm8058_chip *pm_chip;
+ int speed;
+ int state;
+ int level;
+ u8 reg_vib_drv;
+};
+
+/*
+ * pmic8058_vib_read_u8 - helper to read a byte from pmic chip
+ * vib: pointer to vibrator structure
+ * data: placeholder for data to be read
+ * reg: register address
+ *
+ */
+static int pmic8058_vib_read_u8(struct pmic8058_vib *vib,
+ u8 *data, u16 reg)
+{
+ int rc;
+
+ rc = pm8058_read(vib->pm_chip, reg, data, 1);
+ if (rc < 0)
+ dev_warn(vib->dev, "Error reading pmic8058 reg 0x%x(0x%x)\n",
+ reg, rc);
+ return rc;
+}
+
+/*
+ * pmic8058_vib_write_u8 - helper to write a byte to pmic chip
+ * vib: pointer to vibrator structure
+ * data: data to write
+ * reg: register address
+ *
+ */
+static int pmic8058_vib_write_u8(struct pmic8058_vib *vib,
+ u8 data, u16 reg)
+{
+ int rc;
+
+ rc = pm8058_write(vib->pm_chip, reg, &data, 1);
+ if (rc < 0)
+ dev_warn(vib->dev, "Error writing pmic8058 reg 0x%x(0x%x)\n",
+ reg, rc);
+ return rc;
+}
+
+/*
+ * pmic8058_vib_set - handler to start/stop vibration
+ * vib: pointer to vibrator structure
+ * on: state to set
+ *
+ */
+static int pmic8058_vib_set(struct pmic8058_vib *vib, int on)
+{
+ int rc;
+ u8 val;
+
+ val = vib->reg_vib_drv;
+
+ if (on)
+ val |= ((vib->level << VIB_DRV_SEL_SHIFT) & VIB_DRV_SEL_MASK);
+ else
+ val &= ~VIB_DRV_SEL_MASK;
+
+ rc = pmic8058_vib_write_u8(vib, val, VIB_DRV);
+ if (rc < 0)
+ return rc;
+
+ vib->reg_vib_drv = val;
+ return rc;
+}
+
+/*
+ * pmic8058_work_handler - worker to set vibration level
+ * work: pointer to work_struct
+ *
+ */
+static void pmic8058_work_handler(struct work_struct *work)
+{
+ struct pmic8058_vib *vib;
+ int rc;
+ u8 val;
+
+ vib = container_of(work, struct pmic8058_vib, work);
+
+ rc = pmic8058_vib_read_u8(vib, &val, VIB_DRV);
+ if (rc < 0)
+ return;
+
+ /*
+ * pmic vibrator supports voltage ranges from 1.2 to 3.1V, so
+ * scale the level to fit into these ranges.
+ */
+ if (vib->speed) {
+ vib->state = 1;
+ vib->level = ((VIB_MAX_LEVELS * vib->speed) / MAX_FF_SPEED) +
+ VIB_MIN_LEVEL_mV;
+ vib->level /= 100;
+ } else {
+ vib->state = 0;
+ vib->level = VIB_MIN_LEVEL_mV / 100;
+ }
+ pmic8058_vib_set(vib, vib->state);
+}
+
+/*
+ * pmic8058_vib_play_effect - function to handle vib effects.
+ * dev: input device pointer
+ * data: data of effect
+ * effect: effect to play
+ *
+ * Currently this driver supports only rumble effects.
+ *
+ */
+static int pmic8058_vib_play_effect(struct input_dev *dev, void *data,
+ struct ff_effect *effect)
+{
+ struct pmic8058_vib *vib = input_get_drvdata(dev);
+
+ vib->speed = effect->u.rumble.strong_magnitude >> 8;
+ if (!vib->speed)
+ vib->speed = effect->u.rumble.weak_magnitude >> 9;
+ schedule_work(&vib->work);
+ return 0;
+}
+
+static int __devinit pmic8058_vib_probe(struct platform_device *pdev)
+
+{
+ struct pm8058_chip *pm_chip;
+ struct pmic8058_vib *vib;
+ const struct pmic8058_vibrator_pdata *pdata = pdev->dev.platform_data;
+ int rc;
+ u8 val;
+
+ pm_chip = platform_get_drvdata(pdev);
+ if (pm_chip == NULL) {
+ dev_err(&pdev->dev, "no parent data passed in\n");
+ return -EINVAL;
+ }
+
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->level_mV < VIB_MIN_LEVEL_mV ||
+ pdata->level_mV > VIB_MAX_LEVEL_mV)
+ return -EINVAL;
+
+ vib = kzalloc(sizeof(*vib), GFP_KERNEL);
+ if (!vib)
+ return -ENOMEM;
+
+ vib->pm_chip = pm_chip;
+ vib->pdata = pdata;
+ vib->dev = &pdev->dev;
+
+ INIT_WORK(&vib->work, pmic8058_work_handler);
+
+ vib->vib_input_dev = input_allocate_device();
+
+ if (vib->vib_input_dev == NULL) {
+ dev_err(&pdev->dev, "couldn't allocate input device\n");
+ rc = -ENOMEM;
+ goto err_alloc_dev;
+ }
+
+ input_set_drvdata(vib->vib_input_dev, vib);
+
+ vib->vib_input_dev->name = "pmic8058_vibrator";
+ vib->vib_input_dev->id.version = 1;
+ vib->vib_input_dev->dev.parent = &pdev->dev;
+
+ /* operate in manual mode */
+ rc = pmic8058_vib_read_u8(vib, &val, VIB_DRV);
+ if (rc < 0)
+ goto err_read_vib;
+ val &= ~VIB_DRV_EN_MANUAL_MASK;
+ rc = pmic8058_vib_write_u8(vib, val, VIB_DRV);
+ if (rc < 0)
+ goto err_read_vib;
+
+ vib->reg_vib_drv = val;
+
+ input_set_capability(vib->vib_input_dev, EV_FF, FF_RUMBLE);
+
+ rc = input_ff_create_memless(vib->vib_input_dev, NULL,
+ pmic8058_vib_play_effect);
+ if (rc < 0) {
+ dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n");
+ goto err_create_memless;
+ }
+
+ platform_set_drvdata(pdev, vib);
+
+ rc = input_register_device(vib->vib_input_dev);
+ if (rc < 0) {
+ dev_dbg(&pdev->dev, "couldn't register input device\n");
+ goto err_reg_input_dev;
+ }
+ return 0;
+
+err_reg_input_dev:
+ input_ff_destroy(vib->vib_input_dev);
+err_create_memless:
+err_read_vib:
+ input_free_device(vib->vib_input_dev);
+err_alloc_dev:
+ kfree(vib);
+ return rc;
+}
+
+static int __devexit pmic8058_vib_remove(struct platform_device *pdev)
+{
+ struct pmic8058_vib *vib = platform_get_drvdata(pdev);
+
+ cancel_work_sync(&vib->work);
+ if (vib->state)
+ pmic8058_vib_set(vib, 0);
+
+ input_unregister_device(vib->vib_input_dev);
+ kfree(vib);
+ return 0;
+}
+
+static struct platform_driver pmic8058_vib_driver = {
+ .probe = pmic8058_vib_probe,
+ .remove = __devexit_p(pmic8058_vib_remove),
+ .driver = {
+ .name = "pm8058-vib",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init pmic8058_vib_init(void)
+{
+ return platform_driver_register(&pmic8058_vib_driver);
+}
+module_init(pmic8058_vib_init);
+
+static void __exit pmic8058_vib_exit(void)
+{
+ platform_driver_unregister(&pmic8058_vib_driver);
+}
+module_exit(pmic8058_vib_exit);
+
+MODULE_ALIAS("platform:pmic8058_vib");
+MODULE_DESCRIPTION("PMIC8058 vibrator driver based on ff-memless framework");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Amy Maloche <[email protected]>");
--
1.7.3.5

2011-02-01 13:48:50

by Anirudh Ghayal

[permalink] [raw]
Subject: [RFC v2 PATCH 6/7] drivers: rtc: Add support for Qualcomm PMIC8058 RTC

PMIC8058 is Qualcomm's power management IC. A
32-bit RTC is housed inside this PMIC. The RTC driver
uses SSBI to communicate with the RTC module.

Cc: Alessandro Zummo <[email protected]>
Cc: Lars-Peter Clausen <[email protected]>
Signed-off-by: Anirudh Ghayal <[email protected]>
---
Changes from v1:
Addressed review comments from Peter.
removed rtc"0", local variable to store ctrl register state,
removed rtc_irq, added alarm_irq_enable

drivers/rtc/Kconfig | 9 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-pm8058.c | 488 ++++++++++++++++++++++++++++++++++++++++
include/linux/rtc/rtc-pm8058.h | 29 +++
4 files changed, 527 insertions(+), 0 deletions(-)
create mode 100644 drivers/rtc/rtc-pm8058.c
create mode 100644 include/linux/rtc/rtc-pm8058.h

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 4941cad..1458c26 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -985,4 +985,13 @@ config RTC_DRV_LPC32XX
This driver can also be buillt as a module. If so, the module
will be called rtc-lpc32xx.

+config RTC_DRV_PM8058
+ tristate "Qualcomm PMIC8058 RTC"
+ depends on PMIC8058
+ help
+ Say Y here if you want to support the Qualcomm PMIC8058 RTC.
+
+ To compile this driver as a module, choose M here: the
+ module will be called pmic8058-rtc.
+
endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 2afdaf3..4bee015 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -74,6 +74,7 @@ obj-$(CONFIG_RTC_DRV_PCF8563) += rtc-pcf8563.o
obj-$(CONFIG_RTC_DRV_PCF8583) += rtc-pcf8583.o
obj-$(CONFIG_RTC_DRV_PCF2123) += rtc-pcf2123.o
obj-$(CONFIG_RTC_DRV_PCF50633) += rtc-pcf50633.o
+obj-$(CONFIG_RTC_DRV_PM8058) += rtc-pm8058.o
obj-$(CONFIG_RTC_DRV_PL030) += rtc-pl030.o
obj-$(CONFIG_RTC_DRV_PL031) += rtc-pl031.o
obj-$(CONFIG_RTC_DRV_PS3) += rtc-ps3.o
diff --git a/drivers/rtc/rtc-pm8058.c b/drivers/rtc/rtc-pm8058.c
new file mode 100644
index 0000000..3588273
--- /dev/null
+++ b/drivers/rtc/rtc-pm8058.c
@@ -0,0 +1,488 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/rtc.h>
+#include <linux/mfd/pmic8058.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/rtc/rtc-pm8058.h>
+
+/* RTC control registers */
+#define PM8058_RTC_CTRL 0x1E8
+#define PM8058_RTC_ALARM_CTRL 0x1E9
+#define PM8058_RTC_TEST 0x1F6
+
+/* RTC register bases */
+#define PM8058_RTC_READ_BASE 0x1EE
+#define PM8058_RTC_WRITE_BASE 0x1EA
+#define PM8058_RTC_ALARM_BASE 0x1F2
+
+/* RTC_CTRL register bit fields */
+#define PM8058_RTC_ENABLE BIT(7)
+#define PM8058_RTC_ALARM_ENABLE BIT(1)
+
+#define NUM_8_BIT_RTC_REGS 0x4
+
+/**
+ * struct pm8058_rtc - rtc driver internal structure
+ * @rtc: rtc device for this driver
+ * @rtc_alarm_irq: rtc alarm irq number
+ * @pm_chip: pointer to pm8058 parent structure
+ */
+struct pm8058_rtc {
+ struct rtc_device *rtc;
+ int rtc_alarm_irq;
+ u8 rtc_ctrl_reg;
+ struct pm8058_chip *pm_chip;
+};
+
+/*
+ * The RTC registers need to be read/written one byte at a time. This is a
+ * hardware limitation.
+ */
+static int
+pm8058_rtc_read_bytes(struct pm8058_rtc *rtc_dd, u8 *rtc_val, int base)
+{
+ int i, rc;
+
+ /* Read the 32-bit register value, 8 bits at a time. */
+ for (i = 0; i < NUM_8_BIT_RTC_REGS; i++) {
+ rc = pm8058_read(rtc_dd->pm_chip, base + i, &rtc_val[i], 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int
+pm8058_rtc_write_bytes(struct pm8058_rtc *rtc_dd, u8 *rtc_val, int base)
+{
+ int i, rc;
+
+ /* Write the 32-bit register value, 8 bits at a time. */
+ for (i = 0; i < NUM_8_BIT_RTC_REGS; i++) {
+ rc = pm8058_write(rtc_dd->pm_chip, base + i, &rtc_val[i], 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Steps to write the RTC registers.
+ * 1. Disable alarm if enabled.
+ * 2. Write 0x00 to LSB.
+ * 3. Write Byte[1], Byte[2], Byte[3] then Byte[0].
+ * 4. Enable alarm if disabled in step 1.
+ */
+static int
+pm8058_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ unsigned long secs;
+ u8 value[4], reg = 0, alarm_enabled = 0, i;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rtc_tm_to_time(tm, &secs);
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ dev_dbg(dev, "Seconds value to be written to RTC = %lu\n", secs);
+
+ if (rtc_dd->rtc_ctrl_reg & PM8058_RTC_ALARM_ENABLE) {
+ alarm_enabled = 1;
+ rtc_dd->rtc_ctrl_reg &= ~PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
+ &rtc_dd->rtc_ctrl_reg, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8058 write failed\n");
+ rtc_dd->rtc_ctrl_reg |= PM8058_RTC_ALARM_ENABLE;
+ return rc;
+ }
+ }
+
+ /* Write Byte[1], Byte[2], Byte[3], Byte[0] */
+ reg = 0;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE, &reg, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8058 write failed\n");
+ return rc;
+ }
+
+ for (i = 1; i < NUM_8_BIT_RTC_REGS; i++) {
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE + i,
+ &value[i], 1);
+ if (rc < 0) {
+ dev_err(dev, "Write to RTC registers failed\n");
+ return rc;
+ }
+ }
+
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE,
+ &value[0], 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8058 write failed\n");
+ return rc;
+ }
+
+ if (alarm_enabled) {
+ rtc_dd->rtc_ctrl_reg |= PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
+ &rtc_dd->rtc_ctrl_reg, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8058 write failed\n");
+ rtc_dd->rtc_ctrl_reg &= ~PM8058_RTC_ALARM_ENABLE;
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int
+pm8058_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = pm8058_rtc_read_bytes(rtc_dd, value, PM8058_RTC_READ_BASE);
+ if (rc < 0) {
+ dev_err(dev, "RTC time read failed\n");
+ return rc;
+ }
+
+ /*
+ * Read the LSB again and check if there has been a carry over.
+ * If there is, redo the read operation.
+ */
+ rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_READ_BASE, &reg, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8058 read failed\n");
+ return rc;
+ }
+
+ if (unlikely(reg < value[0])) {
+ rc = pm8058_rtc_read_bytes(rtc_dd, value,
+ PM8058_RTC_READ_BASE);
+ if (rc < 0) {
+ dev_err(dev, "RTC time read failed\n");
+ return rc;
+ }
+ }
+
+ secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24);
+
+ rtc_time_to_tm(secs, tm);
+
+ rc = rtc_valid_tm(tm);
+ if (rc < 0) {
+ dev_err(dev, "Invalid time read from PMIC8058\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "secs = %lu, h:m:s == %d:%d:%d, d/m/y = %d/%d/%d\n",
+ secs, tm->tm_hour, tm->tm_min, tm->tm_sec,
+ tm->tm_mday, tm->tm_mon, tm->tm_year);
+
+ return 0;
+}
+
+static int
+pm8058_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rtc_tm_to_time(&alarm->time, &secs);
+
+ value[0] = secs & 0xFF;
+ value[1] = (secs >> 8) & 0xFF;
+ value[2] = (secs >> 16) & 0xFF;
+ value[3] = (secs >> 24) & 0xFF;
+
+ rc = pm8058_rtc_write_bytes(rtc_dd, value, PM8058_RTC_ALARM_BASE);
+ if (rc < 0) {
+ dev_err(dev, "Alarm could not be set\n");
+ return rc;
+ }
+
+ reg = rtc_dd->rtc_ctrl_reg;
+
+ reg = (alarm->enabled) ? (reg | PM8058_RTC_ALARM_ENABLE) :
+ (reg & ~PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, &reg, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8058 write failed\n");
+ return rc;
+ }
+ rtc_dd->rtc_ctrl_reg = reg;
+
+ dev_dbg(dev, "Alarm Set for h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+
+ return 0;
+}
+
+static int
+pm8058_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4];
+ unsigned long secs;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ /* Check if the alarm is enabled */
+ alarm->enabled = !!(rtc_dd->rtc_ctrl_reg & PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_rtc_read_bytes(rtc_dd, value,
+ PM8058_RTC_ALARM_BASE);
+ if (rc < 0) {
+ dev_err(dev, "RTC alarm time read failed\n");
+ return rc;
+ }
+
+ secs = value[0] | (value[1] << 8) | (value[2] << 16) | (value[3] << 24);
+
+ rtc_time_to_tm(secs, &alarm->time);
+
+ rc = rtc_valid_tm(&alarm->time);
+ if (rc < 0) {
+ dev_err(dev, "Invalid time read from PMIC8058\n");
+ return rc;
+ }
+
+ dev_dbg(dev, "Alarm set for - h:r:s=%d:%d:%d, d/m/y=%d/%d/%d\n",
+ alarm->time.tm_hour, alarm->time.tm_min,
+ alarm->time.tm_sec, alarm->time.tm_mday,
+ alarm->time.tm_mon, alarm->time.tm_year);
+
+ return 0;
+}
+
+
+static int
+pm8058_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
+{
+ int rc;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+ u8 reg = rtc_dd->rtc_ctrl_reg;
+
+ reg = (enabled) ? (reg | PM8058_RTC_ALARM_ENABLE) :
+ (reg & ~PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, &reg, 1);
+ if (rc < 0) {
+ dev_err(dev, "PM8058 write failed\n");
+ return rc;
+ }
+ rtc_dd->rtc_ctrl_reg = reg;
+
+ return 0;
+}
+
+static struct rtc_class_ops pm8058_rtc_ops = {
+ .read_time = pm8058_rtc_read_time,
+ .set_alarm = pm8058_rtc_set_alarm,
+ .read_alarm = pm8058_rtc_read_alarm,
+ .alarm_irq_enable = pm8058_rtc_alarm_irq_enable,
+};
+
+static irqreturn_t pm8058_alarm_trigger(int irq, void *dev_id)
+{
+ struct pm8058_rtc *rtc_dd = dev_id;
+
+ rtc_update_irq(rtc_dd->rtc, 1, RTC_IRQF | RTC_AF);
+
+ pr_debug("Alarm Triggered !!\n");
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit pm8058_rtc_probe(struct platform_device *pdev)
+{
+ int rc;
+ u8 reg;
+ bool rtc_write_enable = false;
+ struct pm8058_rtc *rtc_dd;
+ struct pm8058_chip *pm_chip;
+ const struct pm8058_rtc_pdata *pdata = pdev->dev.platform_data;
+
+ if (pdata != NULL)
+ rtc_write_enable = pdata->rtc_write_enable;
+
+ pm_chip = platform_get_drvdata(pdev);
+ if (pm_chip == NULL) {
+ dev_err(&pdev->dev, "Invalid driver information!\n");
+ return -ENXIO;
+ }
+
+ rtc_dd = kzalloc(sizeof(*rtc_dd), GFP_KERNEL);
+ if (rtc_dd == NULL) {
+ dev_err(&pdev->dev, "Unable to allocate memory!\n");
+ return -ENOMEM;
+ }
+
+ rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 0);
+ if (!rtc_dd->rtc_alarm_irq) {
+ dev_err(&pdev->dev, "RTC / Alarm IRQ resource absent!\n");
+ rc = -ENXIO;
+ goto fail_rtc_enable;
+ }
+
+ rtc_dd->pm_chip = pm_chip;
+
+ /* Check if the RTC is on, else turn it on */
+ rc = pm8058_read(pm_chip, PM8058_RTC_CTRL, &reg, 1);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "PM8058 read failed!\n");
+ goto fail_rtc_enable;
+ }
+
+ if (!(reg & PM8058_RTC_ENABLE)) {
+ reg |= PM8058_RTC_ENABLE;
+ rc = pm8058_write(pm_chip, PM8058_RTC_CTRL, &reg, 1);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "PM8058 write failed!\n");
+ goto fail_rtc_enable;
+ }
+ }
+ rtc_dd->rtc_ctrl_reg = reg;
+
+ if (rtc_write_enable == true)
+ pm8058_rtc_ops.set_time = pm8058_rtc_set_time,
+
+ /* Register the RTC device */
+ rtc_dd->rtc = rtc_device_register("pm8058_rtc", &pdev->dev,
+ &pm8058_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dd->rtc)) {
+ dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
+ __func__, PTR_ERR(rtc_dd->rtc));
+ rc = PTR_ERR(rtc_dd->rtc);
+ goto fail_rtc_enable;
+ }
+
+ platform_set_drvdata(pdev, rtc_dd);
+
+ /* Request the alarm IRQ */
+ rc = request_any_context_irq(rtc_dd->rtc_alarm_irq,
+ pm8058_alarm_trigger, IRQF_TRIGGER_RISING,
+ "pm8058_rtc_alarm", rtc_dd);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Request IRQ failed (%d)\n", rc);
+ goto fail_req_irq;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ dev_dbg(&pdev->dev, "Probe success !!\n");
+
+ return 0;
+
+fail_req_irq:
+ rtc_device_unregister(rtc_dd->rtc);
+fail_rtc_enable:
+ kfree(rtc_dd);
+ return rc;
+}
+
+#ifdef CONFIG_PM
+static int pm8058_rtc_resume(struct device *dev)
+{
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ return 0;
+}
+
+static int pm8058_rtc_suspend(struct device *dev)
+{
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(rtc_dd->rtc_alarm_irq);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pm8058_rtc_pm_ops = {
+ .suspend = pm8058_rtc_suspend,
+ .resume = pm8058_rtc_resume,
+};
+#endif
+
+static int __devexit pm8058_rtc_remove(struct platform_device *pdev)
+{
+ struct pm8058_rtc *rtc_dd = platform_get_drvdata(pdev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ free_irq(rtc_dd->rtc_alarm_irq, rtc_dd);
+ rtc_device_unregister(rtc_dd->rtc);
+ kfree(rtc_dd);
+
+ return 0;
+}
+
+static struct platform_driver pm8058_rtc_driver = {
+ .probe = pm8058_rtc_probe,
+ .remove = __devexit_p(pm8058_rtc_remove),
+ .driver = {
+ .name = "pm8058-rtc",
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &pm8058_rtc_pm_ops,
+#endif
+ },
+};
+
+static int __init pm8058_rtc_init(void)
+{
+ return platform_driver_register(&pm8058_rtc_driver);
+}
+module_init(pm8058_rtc_init);
+
+static void __exit pm8058_rtc_exit(void)
+{
+ platform_driver_unregister(&pm8058_rtc_driver);
+}
+module_exit(pm8058_rtc_exit);
+
+MODULE_ALIAS("platform:pm8058-rtc");
+MODULE_DESCRIPTION("PMIC8058 RTC driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Anirudh Ghayal <[email protected]>");
diff --git a/include/linux/rtc/rtc-pm8058.h b/include/linux/rtc/rtc-pm8058.h
new file mode 100644
index 0000000..651c5b4
--- /dev/null
+++ b/include/linux/rtc/rtc-pm8058.h
@@ -0,0 +1,29 @@
+/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+#ifndef __RTC_PM8058_H__
+#define __RTC_PM8058_H__
+
+/**
+ * struct pm8058_rtc_pdata - RTC driver platform data
+ * @rtc_write_enable: variable stating RTC write capability
+ */
+struct pm8058_rtc_pdata {
+ bool rtc_write_enable;
+};
+
+#endif /* __RTC_PM8058_H__ */
--
1.7.3.5

2011-02-01 14:34:01

by Mark Brown

[permalink] [raw]
Subject: Re: [rtc-linux] [RFC v2 PATCH 5/7] input: pmic8058-othc: Add support for PM8058 based OTHC

On Tue, Feb 01, 2011 at 07:17:41PM +0530, Anirudh Ghayal wrote:
> One-touch headset controller is a hardware module in Qualcomm's PMIC8058.
> It supports headset insert/remove and switch press/release detection events
> over 3 MIC BIAS lines. The MIC BIAS lines can be configured to support
> headset detection or act as regular BIAS lines.

This should probably be integrated with ASoC for management of the
biases if nothing else, though it'd also allow other stuff. ALSA has
support for creating input devices from jacks already.

> + input_set_capability(ipd, EV_SW, SW_HEADPHONE_INSERT);
> + input_set_capability(ipd, EV_KEY, KEY_MEDIA);

The ALSA (well, ASoC) stuff would also allow the input device to be
merged with that for other detection methods so if you've got a headset
jack capable of detecting other things (eg, mechanical insertion or
separate mic and headphone detection).

2011-02-01 17:49:39

by Dmitry Torokhov

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 4/7] input: pmic8058_pwrkey: Add support for power key

On Tue, Feb 01, 2011 at 07:17:40PM +0530, Anirudh Ghayal wrote:
> From: Trilok Soni <[email protected]>
>
> Add support for PMIC8058 power key driven over dedicated
> KYPD_PWR_N pin. It allows the user to specify the amount
> of time by which the power key reporting can be delayed.
>
> Cc: Dmitry Torokhov <[email protected]>
> Signed-off-by: Trilok Soni <[email protected]>
> ---
> Changes from v1:
> Addressed review comments from Dmitry.
> Added KEY_SCREENLOCK instead of KEY_END.

...
> + input_report_key(pwrkey->pwr, KEY_SCROLLLOCK, 1);
...
> + input_report_key(pwrkey->pwr, KEY_SCREENLOCK, 0);
...
> + input_set_capability(pwr, EV_KEY, KEY_SCROLLLOCK);


You are confused about the event you are sending.

Thanks.

--
Dmitry

2011-02-02 07:43:32

by Trilok Soni

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 4/7] input: pmic8058_pwrkey: Add support for power key

Hi Dmitry,

On 2/1/2011 11:19 PM, Dmitry Torokhov wrote:
> On Tue, Feb 01, 2011 at 07:17:40PM +0530, Anirudh Ghayal wrote:
>> From: Trilok Soni <[email protected]>
>>
>> Add support for PMIC8058 power key driven over dedicated
>> KYPD_PWR_N pin. It allows the user to specify the amount
>> of time by which the power key reporting can be delayed.
>>
>> Cc: Dmitry Torokhov <[email protected]>
>> Signed-off-by: Trilok Soni <[email protected]>
>> ---
>> Changes from v1:
>> Addressed review comments from Dmitry.
>> Added KEY_SCREENLOCK instead of KEY_END.
>
> ...
>> + input_report_key(pwrkey->pwr, KEY_SCROLLLOCK, 1);
> ...
>> + input_report_key(pwrkey->pwr, KEY_SCREENLOCK, 0);
> ...
>> + input_set_capability(pwr, EV_KEY, KEY_SCROLLLOCK);
>
>
> You are confused about the event you are sending.

My mistake. I will fix this to KEY_SCREENLOCK in v3.

---Trilok Soni

--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

2011-02-02 07:51:58

by Trilok Soni

[permalink] [raw]
Subject: Re: [rtc-linux] [RFC v2 PATCH 5/7] input: pmic8058-othc: Add support for PM8058 based OTHC

Hi Mark,

On 2/1/2011 8:03 PM, Mark Brown wrote:
> On Tue, Feb 01, 2011 at 07:17:41PM +0530, Anirudh Ghayal wrote:
>> One-touch headset controller is a hardware module in Qualcomm's PMIC8058.
>> It supports headset insert/remove and switch press/release detection events
>> over 3 MIC BIAS lines. The MIC BIAS lines can be configured to support
>> headset detection or act as regular BIAS lines.
>
> This should probably be integrated with ASoC for management of the
> biases if nothing else, though it'd also allow other stuff. ALSA has
> support for creating input devices from jacks already.
>
>> + input_set_capability(ipd, EV_SW, SW_HEADPHONE_INSERT);
>> + input_set_capability(ipd, EV_KEY, KEY_MEDIA);
>
> The ALSA (well, ASoC) stuff would also allow the input device to be
> merged with that for other detection methods so if you've got a headset
> jack capable of detecting other things (eg, mechanical insertion or
> separate mic and headphone detection).

Thanks for the review. We will analyze on how much we should be able to move
from this OTHC driver to ALSA jack detection.

---Trilok Soni

--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

2011-02-02 08:33:24

by Dmitry Torokhov

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 7/7] input: misc: Add support for PM8058 based vibrator driver

Hi Anirudh,

On Tue, Feb 01, 2011 at 07:17:43PM +0530, Anirudh Ghayal wrote:
> +
> +#include <linux/mfd/pmic8058.h>
> +#include <linux/input/pmic8058-vibrator.h>

Where is this header file? Shouldn't it be part of this patch?

> +
> +#define VIB_DRV 0x4A
> +
> +#define VIB_DRV_SEL_MASK 0xf8
> +#define VIB_DRV_SEL_SHIFT 0x03
> +#define VIB_DRV_EN_MANUAL_MASK 0xfc
> +
> +#define VIB_MAX_LEVEL_mV (3100)
> +#define VIB_MIN_LEVEL_mV (1200)
> +#define VIB_MAX_LEVELS (VIB_MAX_LEVEL_mV - VIB_MIN_LEVEL_mV)
> +
> +#define MAX_FF_SPEED 0xff
> +
> +/*
> + * struct pmic8058_vib - structure to hold vibrator data
> + * vib_input_dev: input device supporting force feedback
> + * work: work structure to set the vibration parameters
> + * dev: device supporting force feedback
> + * pdata: platform data
> + * pm_chip: reference to pmic chip to carry out read/write operations
> + * speed: speed of vibration set from userland
> + * state: state of vibrator
> + * level: level of vibration to set in the chip
> + * reg_vib_drv: VIB_DRV register value

Hmm, this is kind of kerneldoc but not quite (kerneldoc's arguments
start with '@').

> + *
> + */
> +struct pmic8058_vib {
> + struct input_dev *vib_input_dev;
> + struct work_struct work;
> + struct device *dev;
> + const struct pmic8058_vibrator_pdata *pdata;
> + struct pm8058_chip *pm_chip;
> + int speed;
> + int state;
> + int level;
> + u8 reg_vib_drv;
> +};
> +
> +/*
> + * pmic8058_vib_read_u8 - helper to read a byte from pmic chip
> + * vib: pointer to vibrator structure
> + * data: placeholder for data to be read
> + * reg: register address
> + *
> + */
> +static int pmic8058_vib_read_u8(struct pmic8058_vib *vib,
> + u8 *data, u16 reg)
> +{
> + int rc;
> +
> + rc = pm8058_read(vib->pm_chip, reg, data, 1);
> + if (rc < 0)
> + dev_warn(vib->dev, "Error reading pmic8058 reg 0x%x(0x%x)\n",
> + reg, rc);
> + return rc;
> +}
> +
> +/*
> + * pmic8058_vib_write_u8 - helper to write a byte to pmic chip
> + * vib: pointer to vibrator structure
> + * data: data to write
> + * reg: register address
> + *
> + */
> +static int pmic8058_vib_write_u8(struct pmic8058_vib *vib,
> + u8 data, u16 reg)
> +{
> + int rc;
> +
> + rc = pm8058_write(vib->pm_chip, reg, &data, 1);
> + if (rc < 0)
> + dev_warn(vib->dev, "Error writing pmic8058 reg 0x%x(0x%x)\n",
> + reg, rc);
> + return rc;
> +}
> +
> +/*
> + * pmic8058_vib_set - handler to start/stop vibration
> + * vib: pointer to vibrator structure
> + * on: state to set
> + *
> + */
> +static int pmic8058_vib_set(struct pmic8058_vib *vib, int on)
> +{
> + int rc;
> + u8 val;
> +
> + val = vib->reg_vib_drv;
> +
> + if (on)
> + val |= ((vib->level << VIB_DRV_SEL_SHIFT) & VIB_DRV_SEL_MASK);
> + else
> + val &= ~VIB_DRV_SEL_MASK;
> +
> + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV);
> + if (rc < 0)
> + return rc;
> +
> + vib->reg_vib_drv = val;
> + return rc;
> +}
> +
> +/*
> + * pmic8058_work_handler - worker to set vibration level
> + * work: pointer to work_struct
> + *
> + */
> +static void pmic8058_work_handler(struct work_struct *work)
> +{
> + struct pmic8058_vib *vib;
> + int rc;
> + u8 val;
> +
> + vib = container_of(work, struct pmic8058_vib, work);
> +
> + rc = pmic8058_vib_read_u8(vib, &val, VIB_DRV);
> + if (rc < 0)
> + return;
> +
> + /*
> + * pmic vibrator supports voltage ranges from 1.2 to 3.1V, so
> + * scale the level to fit into these ranges.
> + */
> + if (vib->speed) {
> + vib->state = 1;
> + vib->level = ((VIB_MAX_LEVELS * vib->speed) / MAX_FF_SPEED) +
> + VIB_MIN_LEVEL_mV;
> + vib->level /= 100;
> + } else {
> + vib->state = 0;
> + vib->level = VIB_MIN_LEVEL_mV / 100;
> + }
> + pmic8058_vib_set(vib, vib->state);
> +}
> +
> +/*
> + * pmic8058_vib_play_effect - function to handle vib effects.
> + * dev: input device pointer
> + * data: data of effect
> + * effect: effect to play
> + *
> + * Currently this driver supports only rumble effects.
> + *
> + */
> +static int pmic8058_vib_play_effect(struct input_dev *dev, void *data,
> + struct ff_effect *effect)
> +{
> + struct pmic8058_vib *vib = input_get_drvdata(dev);
> +
> + vib->speed = effect->u.rumble.strong_magnitude >> 8;
> + if (!vib->speed)
> + vib->speed = effect->u.rumble.weak_magnitude >> 9;
> + schedule_work(&vib->work);
> + return 0;
> +}
> +
> +static int __devinit pmic8058_vib_probe(struct platform_device *pdev)
> +
> +{
> + struct pm8058_chip *pm_chip;
> + struct pmic8058_vib *vib;
> + const struct pmic8058_vibrator_pdata *pdata = pdev->dev.platform_data;
> + int rc;
> + u8 val;
> +
> + pm_chip = platform_get_drvdata(pdev);

The parent should not abuse platform data of _this_ device, it belongs
to this driver. In fact you overwrite it with pmic8058_vib below, which
means that you can't unbind the driver and bind it again.

Please find other way to pass pm_chip.

> + if (pm_chip == NULL) {
> + dev_err(&pdev->dev, "no parent data passed in\n");
> + return -EINVAL;
> + }
> +
> + if (!pdata)
> + return -EINVAL;
> +
> + if (pdata->level_mV < VIB_MIN_LEVEL_mV ||
> + pdata->level_mV > VIB_MAX_LEVEL_mV)
> + return -EINVAL;
> +
> + vib = kzalloc(sizeof(*vib), GFP_KERNEL);
> + if (!vib)
> + return -ENOMEM;
> +
> + vib->pm_chip = pm_chip;
> + vib->pdata = pdata;
> + vib->dev = &pdev->dev;
> +
> + INIT_WORK(&vib->work, pmic8058_work_handler);
> +
> + vib->vib_input_dev = input_allocate_device();
> +
> + if (vib->vib_input_dev == NULL) {
> + dev_err(&pdev->dev, "couldn't allocate input device\n");
> + rc = -ENOMEM;
> + goto err_alloc_dev;
> + }
> +
> + input_set_drvdata(vib->vib_input_dev, vib);
> +
> + vib->vib_input_dev->name = "pmic8058_vibrator";
> + vib->vib_input_dev->id.version = 1;
> + vib->vib_input_dev->dev.parent = &pdev->dev;
> +
> + /* operate in manual mode */
> + rc = pmic8058_vib_read_u8(vib, &val, VIB_DRV);
> + if (rc < 0)
> + goto err_read_vib;
> + val &= ~VIB_DRV_EN_MANUAL_MASK;
> + rc = pmic8058_vib_write_u8(vib, val, VIB_DRV);
> + if (rc < 0)
> + goto err_read_vib;
> +
> + vib->reg_vib_drv = val;
> +
> + input_set_capability(vib->vib_input_dev, EV_FF, FF_RUMBLE);
> +
> + rc = input_ff_create_memless(vib->vib_input_dev, NULL,
> + pmic8058_vib_play_effect);
> + if (rc < 0) {
> + dev_dbg(&pdev->dev, "couldn't register vibrator to FF\n");
> + goto err_create_memless;
> + }
> +
> + platform_set_drvdata(pdev, vib);
> +
> + rc = input_register_device(vib->vib_input_dev);
> + if (rc < 0) {
> + dev_dbg(&pdev->dev, "couldn't register input device\n");
> + goto err_reg_input_dev;
> + }

platform_set_drvdata(pdev, vib) should go here so you do not need to
clean it if input_register_device() fails.

> + return 0;
> +
> +err_reg_input_dev:
> + input_ff_destroy(vib->vib_input_dev);
> +err_create_memless:
> +err_read_vib:
> + input_free_device(vib->vib_input_dev);
> +err_alloc_dev:
> + kfree(vib);
> + return rc;
> +}
> +
> +static int __devexit pmic8058_vib_remove(struct platform_device *pdev)
> +{
> + struct pmic8058_vib *vib = platform_get_drvdata(pdev);
> +
> + cancel_work_sync(&vib->work);
> + if (vib->state)
> + pmic8058_vib_set(vib, 0);

This should probably be part of pmic8058_vib_close() method.

> +
> + input_unregister_device(vib->vib_input_dev);
> + kfree(vib);

Need to call platform_set_drvdata(pdev, NULL);

> + return 0;
> +}
> +
> +static struct platform_driver pmic8058_vib_driver = {
> + .probe = pmic8058_vib_probe,
> + .remove = __devexit_p(pmic8058_vib_remove),
> + .driver = {
> + .name = "pm8058-vib",
> + .owner = THIS_MODULE,
> + },
> +};

What about PM? Do you need to shut off vibration if device goes to sleep?

Thanks.

--
Dmitry

2011-02-02 08:54:18

by Trilok Soni

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 7/7] input: misc: Add support for PM8058 based vibrator driver

Hi Dmitry,

On 2/2/2011 2:03 PM, Dmitry Torokhov wrote:
> Hi Anirudh,
>
> On Tue, Feb 01, 2011 at 07:17:43PM +0530, Anirudh Ghayal wrote:
>> +
>> +#include <linux/mfd/pmic8058.h>
>> +#include <linux/input/pmic8058-vibrator.h>
>
> Where is this header file? Shouldn't it be part of this patch?

Looks like someone forgot "git add" on it. We will fix this in v3.

>
>> +
>> +#define VIB_DRV 0x4A
>> +
>> +#define VIB_DRV_SEL_MASK 0xf8
>> +#define VIB_DRV_SEL_SHIFT 0x03
>> +#define VIB_DRV_EN_MANUAL_MASK 0xfc
>> +
>> +#define VIB_MAX_LEVEL_mV (3100)
>> +#define VIB_MIN_LEVEL_mV (1200)
>> +#define VIB_MAX_LEVELS (VIB_MAX_LEVEL_mV - VIB_MIN_LEVEL_mV)
>> +
>> +#define MAX_FF_SPEED 0xff
>> +
>> +/*
>> + * struct pmic8058_vib - structure to hold vibrator data
>> + * vib_input_dev: input device supporting force feedback
>> + * work: work structure to set the vibration parameters
>> + * dev: device supporting force feedback
>> + * pdata: platform data
>> + * pm_chip: reference to pmic chip to carry out read/write operations
>> + * speed: speed of vibration set from userland
>> + * state: state of vibrator
>> + * level: level of vibration to set in the chip
>> + * reg_vib_drv: VIB_DRV register value
>
> Hmm, this is kind of kerneldoc but not quite (kerneldoc's arguments
> start with '@').

Ack. It will be fixed.

>> +
>> +static int __devinit pmic8058_vib_probe(struct platform_device *pdev)
>> +
>> +{
>> + struct pm8058_chip *pm_chip;
>> + struct pmic8058_vib *vib;
>> + const struct pmic8058_vibrator_pdata *pdata = pdev->dev.platform_data;
>> + int rc;
>> + u8 val;
>> +
>> + pm_chip = platform_get_drvdata(pdev);
>
> The parent should not abuse platform data of _this_ device, it belongs
> to this driver. In fact you overwrite it with pmic8058_vib below, which
> means that you can't unbind the driver and bind it again.
>
> Please find other way to pass pm_chip.

This will be fixed through pmic8058 core driver, when we submit. We are aware of it,
and the all the sub-device driver will be changed to do it properly once we release
them again with pmic core driver submission.

>> +
>> + platform_set_drvdata(pdev, vib);
>> +
>> + rc = input_register_device(vib->vib_input_dev);
>> + if (rc < 0) {
>> + dev_dbg(&pdev->dev, "couldn't register input device\n");
>> + goto err_reg_input_dev;
>> + }
>
> platform_set_drvdata(pdev, vib) should go here so you do not need to
> clean it if input_register_device() fails.

Ack

>> +
>> +static int __devexit pmic8058_vib_remove(struct platform_device *pdev)
>> +{
>> + struct pmic8058_vib *vib = platform_get_drvdata(pdev);
>> +
>> + cancel_work_sync(&vib->work);
>> + if (vib->state)
>> + pmic8058_vib_set(vib, 0);
>
> This should probably be part of pmic8058_vib_close() method.
>

Ok. We will check and fix.

>> +
>> + input_unregister_device(vib->vib_input_dev);
>> + kfree(vib);
>
> Need to call platform_set_drvdata(pdev, NULL);

Ack.

>
> What about PM? Do you need to shut off vibration if device goes to sleep?

Yes. Let me check and add it to v3 patch series.

I will drop the PMIC8058 OTHC from v3 series, as Mark suggested to explore ASoC way of
doing it.

---Trilok Soni

--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

2011-02-06 01:45:49

by Lars-Peter Clausen

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 3/7] led: pmic8058: Add PMIC8058 leds driver

On 02/01/2011 02:47 PM, Anirudh Ghayal wrote:
> From: Trilok Soni <[email protected]>
>
> Add support for Qualcomm PMIC8058 keyboard
> backlight, flash and low current leds.
>
> Cc: Dmitry Torokhov <[email protected]>
> Cc: Lars-Peter Clausen <[email protected]>
> Signed-off-by: Trilok Soni <[email protected]>
> ---
> Changes from v1:
> Addressed review comments from Peter and Dmitry.
>
> drivers/leds/Kconfig | 11 ++
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-pmic8058.c | 360 +++++++++++++++++++++++++++++++++++++++++
> include/linux/leds-pmic8058.h | 61 +++++++
> 4 files changed, 433 insertions(+), 0 deletions(-)
> create mode 100644 drivers/leds/leds-pmic8058.c
> create mode 100644 include/linux/leds-pmic8058.h
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index 6f190f4..235a1e5 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -250,6 +250,17 @@ config LEDS_PCA955X
> LED driver chips accessed via the I2C bus. Supported
> devices include PCA9550, PCA9551, PCA9552, and PCA9553.
>
> +config LEDS_PMIC8058
> + tristate "LED Support for Qualcomm PMIC8058"
> + depends on PMIC8058
> + help
> + This option enables support for LEDs connected over PMIC8058
> + (Power Management IC) chip on Qualcomm reference boards,
> + for example SURF and FFAs.
> +
> + To compile this driver as a module, choose M here: the module will
> + be called leds-pmic8058.
> +
> config LEDS_WM831X_STATUS
> tristate "LED support for status LEDs on WM831x PMICs"
> depends on LEDS_CLASS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index aae6989..9706739 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -21,6 +21,7 @@ obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o
> obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o
> obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
> obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
> +obj-$(CONFIG_LEDS_PMIC8058) += leds-pmic8058.o
> obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
> obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
> obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
> diff --git a/drivers/leds/leds-pmic8058.c b/drivers/leds/leds-pmic8058.c
> new file mode 100644
> index 0000000..fb3d7be
> --- /dev/null
> +++ b/drivers/leds/leds-pmic8058.c
> @@ -0,0 +1,360 @@
> +/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> + * 02110-1301, USA.
> + */
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/platform_device.h>
> +#include <linux/leds.h>
> +#include <linux/workqueue.h>
> +#include <linux/spinlock.h>
> +#include <linux/mfd/pmic8058.h>
> +#include <linux/leds-pmic8058.h>
> +
> +#define SSBI_REG_ADDR_DRV_KEYPAD 0x48
> +#define PM8058_DRV_KEYPAD_BL_MASK 0xf0
> +#define PM8058_DRV_KEYPAD_BL_SHIFT 0x04
> +
> +#define SSBI_REG_ADDR_FLASH_DRV0 0x49
> +#define PM8058_DRV_FLASH_MASK 0xf0
> +#define PM8058_DRV_FLASH_SHIFT 0x04
> +
> +#define SSBI_REG_ADDR_FLASH_DRV1 0xFB
> +
> +#define SSBI_REG_ADDR_LED_CTRL_BASE 0x131
> +#define SSBI_REG_ADDR_LED_CTRL(n) (SSBI_REG_ADDR_LED_CTRL_BASE + (n))
> +#define PM8058_DRV_LED_CTRL_MASK 0xf8
> +#define PM8058_DRV_LED_CTRL_SHIFT 0x03
> +
> +#define MAX_FLASH_LED_CURRENT 300
> +#define MAX_LC_LED_CURRENT 40
> +#define MAX_KP_BL_LED_CURRENT 300
> +
> +#define MAX_KEYPAD_BL_LEVEL (1 << 4)
> +#define MAX_LED_DRV_LEVEL 20 /* 2 * 20 mA */
> +
> +#define PMIC8058_LED_OFFSET(id) ((id) - PMIC8058_ID_LED_0)
> +
> +#define MAX_KB_LED_BRIGHTNESS 15
> +#define MAX_LC_LED_BRIGHTNESS 20
> +#define MAX_FLASH_LED_BRIGHTNESS 15
> +
> +#define PMIC8058_MAX_LEDS 7
> +
> +/**
> + * struct pmic8058_led_data - internal led data structure
> + * @led_classdev - led class device
> + * @id - led index
> + * @led_brightness - led brightness levels
> + * @pm_chip - parent MFD core device
> + * @work - workqueue for led
> + * @lock - to protect the transactions
> + * @reg - cached value of led register
> + */
> +struct pmic8058_led_data {
> + struct led_classdev cdev;
> + int id;
> + u8 reg;
> + enum led_brightness brightness;
> + struct pm8058_chip *pm_chip;
> + struct work_struct work;
> + struct mutex lock;
> +};
> +
> +#define PM8058_MAX_LEDS 7
This is unused

> +
> +static void led_kp_set(struct pmic8058_led_data *led, enum led_brightness value)
> +{
> + int rc;
> + u8 level;
> +
> + level = (value << PM8058_DRV_KEYPAD_BL_SHIFT) &
> + PM8058_DRV_KEYPAD_BL_MASK;
> +
> + led->reg &= ~PM8058_DRV_KEYPAD_BL_MASK;
> + led->reg |= level;
> +
> + rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_DRV_KEYPAD,
> + &led->reg, 1);
> + if (rc < 0)
> + dev_err(led->cdev.dev, "can't set keypad backlight level\n");
> +}
> +
> +static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value)
> +{
> + int rc, offset;
> + u8 level;
> +
> + level = (value << PM8058_DRV_LED_CTRL_SHIFT) &
> + PM8058_DRV_LED_CTRL_MASK;
> +
> + offset = PMIC8058_LED_OFFSET(led->id);
> +
> + led->reg &= ~PM8058_DRV_LED_CTRL_MASK;
> + led->reg |= level;
> +
> + rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_LED_CTRL(offset),
> + &led->reg, 1);
> + if (rc)
> + dev_err(led->cdev.dev, "can't set (%d) led value\n",
> + led->id);
> +}
> +
> +static void
> +led_flash_set(struct pmic8058_led_data *led, enum led_brightness value)
> +{
> + int rc;
> + u8 level;
> + u16 reg_addr;
> +
> + level = (value << PM8058_DRV_FLASH_SHIFT) &
> + PM8058_DRV_FLASH_MASK;
> +
> + led->reg &= ~PM8058_DRV_FLASH_MASK;
> + led->reg |= level;
> +
> + if (led->id == PMIC8058_ID_FLASH_LED_0)
> + reg_addr = SSBI_REG_ADDR_FLASH_DRV0;
> + else
> + reg_addr = SSBI_REG_ADDR_FLASH_DRV1;
> +
> + rc = pm8058_write(led->pm_chip, reg_addr, &led->reg, 1);
> + if (rc < 0)
> + dev_err(led->cdev.dev, "can't set flash led%d level\n",
> + led->id);
> +}
> +
> +static void pmic8058_led_work(struct work_struct *work)
> +{
> + struct pmic8058_led_data *led = container_of(work,
> + struct pmic8058_led_data, work);
> +
> + mutex_lock(&led->lock);
> +
> + switch (led->id) {
> + case PMIC8058_ID_LED_KB_LIGHT:
> + led_kp_set(led, led->brightness);
> + break;
> + case PMIC8058_ID_LED_0:
> + case PMIC8058_ID_LED_1:
> + case PMIC8058_ID_LED_2:
> + led_lc_set(led, led->brightness);
> + break;
> + case PMIC8058_ID_FLASH_LED_0:
> + case PMIC8058_ID_FLASH_LED_1:
> + led_flash_set(led, led->brightness);
> + break;
> + }
> +
> + mutex_unlock(&led->lock);
> +}
> +
> +static void pmic8058_led_set(struct led_classdev *led_cdev,
> + enum led_brightness value)
> +{
> + struct pmic8058_led_data *led;
> +
> + led = container_of(led_cdev, struct pmic8058_led_data, cdev);
> +
> + led->brightness = value;
> + schedule_work(&led->work);
As discussed last time it might be a good idea to schedule the work on the
system_nrt_wq work queue and remove the mutex in the work function.

> +}
> +
> +static enum led_brightness pmic8058_led_get(struct led_classdev *led_cdev)
> +{
> + struct pmic8058_led_data *led;
> +
> + led = container_of(led_cdev, struct pmic8058_led_data, cdev);
> +
> + return led->brightness;
> +}
> +
> +static int get_max_brightness(enum pmic8058_leds id)
> +{
> + switch (id) {
> + case PMIC8058_ID_LED_KB_LIGHT:
> + return MAX_KB_LED_BRIGHTNESS;
> + case PMIC8058_ID_LED_0:
> + case PMIC8058_ID_LED_1:
> + case PMIC8058_ID_LED_2:
> + return MAX_LC_LED_BRIGHTNESS;
> + case PMIC8058_ID_FLASH_LED_0:
> + case PMIC8058_ID_FLASH_LED_1:
> + return MAX_FLASH_LED_CURRENT;
> + default:
> + return 0;
> + }
> +}
> +
> +static int get_init_value(struct pmic8058_led_data *led, u8 *val)
> +{
> + int rc, offset;
> + u16 addr;
> +
> + switch (led->id) {
> + case PMIC8058_ID_LED_KB_LIGHT:
> + addr = SSBI_REG_ADDR_DRV_KEYPAD;
> + break;
> + case PMIC8058_ID_LED_0:
> + case PMIC8058_ID_LED_1:
> + case PMIC8058_ID_LED_2:
> + offset = PMIC8058_LED_OFFSET(led->id);
> + addr = SSBI_REG_ADDR_LED_CTRL(offset);
> + break;
> + case PMIC8058_ID_FLASH_LED_0:
> + addr = SSBI_REG_ADDR_FLASH_DRV0;
> + break;
> + case PMIC8058_ID_FLASH_LED_1:
> + addr = SSBI_REG_ADDR_FLASH_DRV1;
> + break;
> + }
> +
> + rc = pm8058_read(led->pm_chip, addr, val, 1);
> + if (rc)
> + dev_err(led->cdev.dev, "can't get led(%d) level\n", led->id);
> +
> + return rc;
> +}
> +
> +static int __devinit pmic8058_led_probe(struct platform_device *pdev)
> +{
> + const struct pmic8058_leds_platform_data *pdata =
> + pdev->dev.platform_data;
> + struct pmic8058_led_data *led_dat;
> + struct pmic8058_led *curr_led;
> + int rc, i;
> + struct pm8058_chip *pm_chip;
> + struct pmic8058_led_data *led;
> +
> + pm_chip = platform_get_drvdata(pdev);
> + if (pm_chip == NULL) {
> + dev_err(&pdev->dev, "no parent data passed in\n");
> + return -EINVAL;
> + }
> +
> + if (pdata == NULL) {
> + dev_err(&pdev->dev, "platform data not supplied\n");
> + return -EINVAL;
> + }
> +
> + if (pdata->num_leds > PMIC8058_MAX_LEDS) {
> + dev_err(&pdev->dev, "can't handle more than %d LEDs\n",
> + PMIC8058_MAX_LEDS);
> + return -EFAULT;
> + }
> +
> + led = kcalloc(pdata->num_leds, sizeof(*led), GFP_KERNEL);
> + if (led == NULL) {
> + dev_err(&pdev->dev, "failed to alloc memory\n");
> + return -ENOMEM;
> + }
> +
> + for (i = 0; i < pdata->num_leds; i++) {
> + curr_led = &pdata->leds[i];
> + led_dat = &led[curr_led->id];
> +
> + led_dat->id = curr_led->id;
> +
> + if (!((led_dat->id >= PMIC8058_ID_LED_KB_LIGHT) &&
> + (led_dat->id <= PMIC8058_ID_FLASH_LED_1))) {
> + dev_err(&pdev->dev, "invalid LED ID (%d) specified\n",
> + led_dat->id);
> + rc = -EINVAL;
> + goto fail_id_check;
> + }
> +
> + led_dat->cdev.name = curr_led->name;
> + led_dat->cdev.default_trigger = curr_led->default_trigger;
> + led_dat->cdev.brightness_set = pmic8058_led_set;
> + led_dat->cdev.brightness_get = pmic8058_led_get;
> + led_dat->cdev.brightness = LED_OFF;
> + led_dat->cdev.flags = LED_CORE_SUSPENDRESUME;
> +
> + led_dat->cdev.max_brightness = get_max_brightness(led_dat->id);
> + led_dat->pm_chip = pm_chip;
> +
> + rc = get_init_value(led_dat, &led_dat->reg);
> + if (rc < 0)
> + goto fail_id_check;
> +
> + mutex_init(&led_dat->lock);
> + INIT_WORK(&led_dat->work, pmic8058_led_work);
> +
> + rc = led_classdev_register(&pdev->dev, &led_dat->cdev);
> + if (rc) {
> + dev_err(&pdev->dev, "unable to register led %d\n",
> + led_dat->id);
> + goto fail_id_check;
> + }
> + }
> +
> + platform_set_drvdata(pdev, led);
> +
> + return 0;
> +
> +fail_id_check:
> + if (i > 0) {
> + for (i = i - 1; i >= 0; i--)
> + led_classdev_unregister(&led[i].cdev);
> + }
> + kfree(led);
> + return rc;
> +}
> +
> +static int __devexit pmic8058_led_remove(struct platform_device *pdev)
> +{
> + int i;
> + const struct pmic8058_leds_platform_data *pdata =
> + pdev->dev.platform_data;
> + struct pmic8058_led_data *led = platform_get_drvdata(pdev);
> +
> + for (i = 0; i < pdata->num_leds; i++) {
> + mutex_destroy(&led[led->id].lock);
Since the mutex is used in the work function it should be destroyed after canceling
the work.
> + led_classdev_unregister(&led[led->id].cdev);
> + cancel_work_sync(&led[led->id].work);
This looks wrong. I guess it should be "&led[i]..."
> + }
> +
> + kfree(led);
> +
> + return 0;
> +}
> +
> +static struct platform_driver pmic8058_led_driver = {
> + .probe = pmic8058_led_probe,
> + .remove = __devexit_p(pmic8058_led_remove),
> + .driver = {
> + .name = "pm8058-led",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init pmic8058_led_init(void)
> +{
> + return platform_driver_register(&pmic8058_led_driver);
> +}
> +module_init(pmic8058_led_init);
> +
> +static void __exit pmic8058_led_exit(void)
> +{
> + platform_driver_unregister(&pmic8058_led_driver);
> +}
> +module_exit(pmic8058_led_exit);
> +
> +MODULE_DESCRIPTION("PMIC8058 LEDs driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_VERSION("1.0");
> +MODULE_ALIAS("platform:pmic8058-led");
> +MODULE_AUTHOR("Trilok Soni <[email protected]>");
> diff --git a/include/linux/leds-pmic8058.h b/include/linux/leds-pmic8058.h
> new file mode 100644
> index 0000000..42a3ae7
> --- /dev/null
> +++ b/include/linux/leds-pmic8058.h
> @@ -0,0 +1,61 @@
> +/* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
> + * 02110-1301, USA.
> + */
> +
> +#ifndef __LEDS_PMIC8058_H__
> +#define __LEDS_PMIC8058_H__
> +
> +/**
> + * enum pmic8058_leds - PMIC8058 supported led ids
> + * @PMIC8058_ID_LED_KB_LIGHT - keyboard backlight led
> + * @PMIC8058_ID_LED_0 - First low current led
> + * @PMIC8058_ID_LED_1 - Second low current led
> + * @PMIC8058_ID_LED_2 - Third low current led
> + * @PMIC8058_ID_FLASH_LED_0 - First flash led
> + * @PMIC8058_ID_FLASH_LED_0 - Second flash led
> + */
> +enum pmic8058_leds {
> + PMIC8058_ID_LED_KB_LIGHT = 1,
> + PMIC8058_ID_LED_0,
> + PMIC8058_ID_LED_1,
> + PMIC8058_ID_LED_2,
> + PMIC8058_ID_FLASH_LED_0,
> + PMIC8058_ID_FLASH_LED_1,
> +};
> +
> +/**
> + * struct pmic8058_led - per led data
> + * @name - name of the led
> + * @default_trigger - default trigger which needs to e attached
> + * @id - supported led id
> + */
> +struct pmic8058_led {
> + const char *name;
> + const char *default_trigger;
> + enum pmic8058_leds id;
> +};
> +
> +/**
> + * struct pmic8058_leds_platform_data - platform data for leds
> + * @num_leds - number of leds
> + * @leds - array of struct pmic8058_led
> + */
> +struct pmic8058_leds_platform_data {
> + int num_leds;
> + struct pmic8058_led *leds;
> +};
> +

Wasn't it the idea to reuse led_info and led_platform_data?

> +#endif /* __LEDS_PMIC8058_H__ */

Otherwise it looks good to me.

- Lars

2011-02-09 08:02:50

by Eric Miao

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 1/7] matrix_keypad: Increase the max limit of rows and columns

On Tue, Feb 1, 2011 at 9:47 PM, Anirudh Ghayal <[email protected]> wrote:
> From: Trilok Soni <[email protected]>
>
> Some keyboard controller have support for more than
> 16 columns and rows. Moving this value to 32.
>
> Cc: Eric Miao <[email protected]>
> Cc: Dmitry Torokhov <[email protected]>
> Signed-off-by: Trilok Soni <[email protected]>

This looks good to me, as long as the bits don't exceed 256, which
is used in the #KEY() macro. So far, 32 looks to be a sane number
to me, unless we have further requirement to get this larger.

Acked-by: Eric Miao <[email protected]>


> ---
> Changes from v1:
> Moved the max columns and rows to 32
>
>  include/linux/input/matrix_keypad.h |    4 ++--
>  1 files changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/include/linux/input/matrix_keypad.h b/include/linux/input/matrix_keypad.h
> index 6974746..fe7c4b9 100644
> --- a/include/linux/input/matrix_keypad.h
> +++ b/include/linux/input/matrix_keypad.h
> @@ -4,8 +4,8 @@
>  #include <linux/types.h>
>  #include <linux/input.h>
>
> -#define MATRIX_MAX_ROWS                16
> -#define MATRIX_MAX_COLS                16
> +#define MATRIX_MAX_ROWS                32
> +#define MATRIX_MAX_COLS                32
>
>  #define KEY(row, col, val)     ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\
>                                 (((col) & (MATRIX_MAX_COLS - 1)) << 16) |\
> --
> 1.7.3.5
>
>

2011-02-10 12:20:22

by Trilok Soni

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 1/7] matrix_keypad: Increase the max limit of rows and columns

Hi Dmitry,

On 2/9/2011 1:32 PM, Eric Miao wrote:
> On Tue, Feb 1, 2011 at 9:47 PM, Anirudh Ghayal <[email protected]> wrote:
>> From: Trilok Soni <[email protected]>
>>
>> Some keyboard controller have support for more than
>> 16 columns and rows. Moving this value to 32.
>>
>> Cc: Eric Miao <[email protected]>
>> Cc: Dmitry Torokhov <[email protected]>
>> Signed-off-by: Trilok Soni <[email protected]>
>
> This looks good to me, as long as the bits don't exceed 256, which
> is used in the #KEY() macro. So far, 32 looks to be a sane number
> to me, unless we have further requirement to get this larger.
>
> Acked-by: Eric Miao <[email protected]>
>

Are you going to pick up this patch only from the series you want us to get
it submitted it separately?

---Trilok Soni

--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.

2011-02-10 12:41:17

by Eric Miao

[permalink] [raw]
Subject: Re: [RFC v2 PATCH 1/7] matrix_keypad: Increase the max limit of rows and columns

On Thu, Feb 10, 2011 at 8:20 PM, Trilok Soni <[email protected]> wrote:
> Hi Dmitry,
>
> On 2/9/2011 1:32 PM, Eric Miao wrote:
>> On Tue, Feb 1, 2011 at 9:47 PM, Anirudh Ghayal <[email protected]> wrote:
>>> From: Trilok Soni <[email protected]>
>>>
>>> Some keyboard controller have support for more than
>>> 16 columns and rows. Moving this value to 32.
>>>
>>> Cc: Eric Miao <[email protected]>
>>> Cc: Dmitry Torokhov <[email protected]>
>>> Signed-off-by: Trilok Soni <[email protected]>
>>
>> This looks good to me, as long as the bits don't exceed 256, which
>> is used in the #KEY() macro. So far, 32 looks to be a sane number
>> to me, unless we have further requirement to get this larger.
>>
>> Acked-by: Eric Miao <[email protected]>
>>
>
> Are you going to pick up this patch only from the series you want us to get
> it submitted it separately?

I believe Dmitry will pick this series once all the patches are reviewed?