This patch series contains 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. So we
are sending these patches early to get the feedback from the community.
If you want to refer the PMIC8058 core and SSBI driver then they are
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
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 | 769 ++++++++++++++++++++++++++++++
drivers/input/misc/Kconfig | 21 +
drivers/input/misc/Makefile | 2 +
drivers/input/misc/pmic8058-othc.c | 544 +++++++++++++++++++++
drivers/input/misc/pmic8058-pwrkey.c | 322 +++++++++++++
drivers/leds/Kconfig | 11 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-pmic8058.c | 405 ++++++++++++++++
drivers/rtc/Kconfig | 9 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-pm8058.c | 487 +++++++++++++++++++
include/linux/input/matrix_keypad.h | 8 +-
include/linux/input/pmic8058-keypad.h | 58 +++
include/linux/input/pmic8058-othc.h | 117 +++++
include/linux/input/pmic8058-pwrkey.h | 37 ++
include/linux/leds-pmic8058.h | 63 +++
include/linux/rtc/rtc-pm8058.h | 29 ++
19 files changed, 2892 insertions(+), 4 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/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
Some keyboard controller have support for more than
16 columns and rows.
Cc: Dmitry Torokhov <[email protected]>
Cc: Eric Miao <[email protected]>
Signed-off-by: Trilok Soni <[email protected]>
---
include/linux/input/matrix_keypad.h | 8 ++++----
1 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/include/linux/input/matrix_keypad.h b/include/linux/input/matrix_keypad.h
index 80352ad..d80845e 100644
--- a/include/linux/input/matrix_keypad.h
+++ b/include/linux/input/matrix_keypad.h
@@ -4,11 +4,11 @@
#include <linux/types.h>
#include <linux/input.h>
-#define MATRIX_MAX_ROWS 16
-#define MATRIX_MAX_COLS 16
+#define MATRIX_MAX_ROWS 18
+#define MATRIX_MAX_COLS 18
-#define KEY(row, col, val) ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\
- (((col) & (MATRIX_MAX_COLS - 1)) << 16) |\
+#define KEY(row, col, val) ((((row) % (MATRIX_MAX_ROWS)) << 24) |\
+ (((col) % (MATRIX_MAX_COLS)) << 16) |\
(val & 0xffff))
#define KEY_ROW(k) (((k) >> 24) & 0xff)
--
1.7.0.2
Add Qualcomm PMIC8058 based keypad controller driver
supporting upto 18x8 matrix configuration.
Cc: Dmitry Torokhov <[email protected]>
Signed-off-by: Trilok Soni <[email protected]>
---
drivers/input/keyboard/Kconfig | 11 +
drivers/input/keyboard/Makefile | 1 +
drivers/input/keyboard/pmic8058-keypad.c | 769 ++++++++++++++++++++++++++++++
include/linux/input/pmic8058-keypad.h | 58 +++
4 files changed, 839 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 b8c51b9..d536acb 100644
--- a/drivers/input/keyboard/Kconfig
+++ b/drivers/input/keyboard/Kconfig
@@ -364,6 +364,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 a34452e..9ff8dbe 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..77ddda6
--- /dev/null
+++ b/drivers/input/keyboard/pmic8058-keypad.c
@@ -0,0 +1,769 @@
+/* Copyright (c) 2009-2010, 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/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;
+
+ 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 int 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 1;
+ }
+ }
+ check |= row_state;
+ }
+ return 0;
+}
+
+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 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;
+
+ enable_irq(kp->key_sense_irq);
+ enable_irq(kp->key_stuck_irq);
+
+ 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;
+
+ disable_irq(kp->key_sense_irq);
+ disable_irq(kp->key_stuck_irq);
+
+ 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;
+ unsigned short *keycodes;
+ 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 -EFAULT;
+ }
+
+ /* Check PMIC8058 version. A0 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->rows_gpio_start < 0 || pdata->cols_gpio_start < 0) {
+ dev_err(&pdev->dev, "invalid gpio_start 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;
+
+ keycodes = kzalloc(PM8058_MATRIX_MAX_SIZE * sizeof(*keycodes),
+ GFP_KERNEL);
+ if (!keycodes) {
+ rc = -ENOMEM;
+ goto err_alloc_mem;
+ }
+
+ platform_set_drvdata(pdev, kp);
+
+ kp->pdata = pdata;
+ kp->dev = &pdev->dev;
+ kp->keycodes = keycodes;
+ 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;
+ }
+
+ if (pdata->input_name)
+ kp->input->name = pdata->input_name;
+ else
+ kp->input->name = "PMIC8058 keypad";
+
+ if (pdata->input_phys_device)
+ kp->input->phys = pdata->input_phys_device;
+ else
+ kp->input->phys = "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 = keycodes;
+ kp->input->keycodemax = PM8058_MATRIX_MAX_SIZE;
+ kp->input->keycodesize = sizeof(*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;
+ }
+ /* kp->open will enable the irq */
+ disable_irq(kp->key_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;
+ }
+ /* kp->open will enable the irq */
+ disable_irq(kp->key_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(keycodes);
+err_alloc_mem:
+ 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->input->keycode);
+ kfree(kp);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int pmic8058_kp_suspend(struct device *dev)
+{
+ struct pmic8058_kp *kp = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ enable_irq_wake(kp->key_sense_irq);
+
+ return 0;
+}
+
+static int pmic8058_kp_resume(struct device *dev)
+{
+ struct pmic8058_kp *kp = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ disable_irq_wake(kp->key_sense_irq);
+
+ 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..901f6cc
--- /dev/null
+++ b/include/linux/input/pmic8058-keypad.h
@@ -0,0 +1,58 @@
+/* Copyright (c) 2009-2010, 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
+ * @rows_gpio_start - starting PMIC gpio number for rows
+ * @cols_gpio_start - starting PMIC gpio number for cols
+ * @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 rows_gpio_start;
+ unsigned int cols_gpio_start;
+
+ unsigned int debounce_ms;
+ unsigned int scan_delay_ms;
+ unsigned int row_hold_ns;
+
+ bool wakeup;
+ bool rep;
+};
+
+#endif /*__PMIC8058_KEYPAD_H__ */
--
1.7.0.2
From: Anirudh Ghayal <[email protected]>
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: Dmitry Torokhov <[email protected]>
Signed-off-by: Anirudh Ghayal <[email protected]>
---
drivers/input/misc/Kconfig | 10 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/pmic8058-othc.c | 544 +++++++++++++++++++++++++++++++++++
include/linux/input/pmic8058-othc.h | 117 ++++++++
4 files changed, 672 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 aeb9165..df6097c 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -359,6 +359,16 @@ config INPUT_PMIC8058_PWRKEY
To compile this driver as a module, choose M here: the
module will be called pmic8058-pwrkey.
+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.
+
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 c4357a0..a713370 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -29,6 +29,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..78f157a
--- /dev/null
+++ b/drivers/input/misc/pmic8058-othc.c
@@ -0,0 +1,544 @@
+/* Copyright (c) 2010, 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/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;
+ 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, ®, 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, ®, 1);
+ if (rc < 0) {
+ pr_err("PM8058 write failed\n");
+ return rc;
+ }
+
+ return rc;
+}
+EXPORT_SYMBOL(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);
+
+ if (dd->othc_sw_state == false) {
+ dd->othc_sw_state = true;
+ input_report_key(dd->othc_ipd, KEY_MEDIA, 1);
+ } else if (dd->othc_sw_state == true) {
+ dd->othc_sw_state = false;
+ input_report_key(dd->othc_ipd, KEY_MEDIA, 0);
+ }
+ 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));
+
+ if (dd->othc_ir_state == false) {
+ dd->othc_ir_state = true;
+ input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 1);
+ } else {
+ dd->othc_ir_state = false;
+ input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 0);
+ }
+
+ input_sync(dd->othc_ipd);
+
+ return IRQ_HANDLED;
+}
+
+static int pm8058_configure_othc(struct pm8058_othc *dd)
+{
+ int rc;
+ u8 reg, value;
+ u32 value1;
+ u16 base_addr = dd->othc_base;
+ struct othc_hsed_config *hsed_config = dd->othc_pdata->hsed_config;
+
+ /* Control Register 1*/
+ rc = pm8058_read(dd->pm_chip, base_addr, ®, 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, ®, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ /* Control register 2*/
+ rc = pm8058_read(dd->pm_chip, base_addr + 1, ®, 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, ®, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ /* Control register 3 */
+ rc = pm8058_read(dd->pm_chip, base_addr + 2 , ®, 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, ®, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ return 0;
+}
+
+static int
+pm8058_othc_configure_hsed(struct pm8058_othc *dd, struct platform_device *pd)
+{
+ int rc;
+ struct input_dev *ipd;
+ struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data;
+ 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 | IRQF_ONESHOT,
+ "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 | IRQF_ONESHOT,
+ "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 A0 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);
+}
+
+static void __exit pm8058_othc_exit(void)
+{
+ platform_driver_unregister(&pm8058_othc_driver);
+}
+
+module_init(pm8058_othc_init);
+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..341ac7c
--- /dev/null
+++ b/include/linux/input/pmic8058-othc.h
@@ -0,0 +1,117 @@
+/* Copyright (c) 2010, 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.0.2
From: Anirudh Ghayal <[email protected]>
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]>
Signed-off-by: Anirudh Ghayal <[email protected]>
---
drivers/rtc/Kconfig | 9 +
drivers/rtc/Makefile | 1 +
drivers/rtc/rtc-pm8058.c | 487 ++++++++++++++++++++++++++++++++++++++++
include/linux/rtc/rtc-pm8058.h | 29 +++
4 files changed, 526 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 2883428..9f4ea00 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -665,6 +665,15 @@ config RTC_DRV_NUC900
If you say yes here you get support for the RTC subsystem of the
NUC910/NUC920 used in embedded systems.
+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.
+
comment "on-CPU RTC drivers"
config RTC_DRV_DAVINCI
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 4c2832d..d7a4f7d 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -73,6 +73,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..9fef82d
--- /dev/null
+++ b/drivers/rtc/rtc-pm8058.c
@@ -0,0 +1,487 @@
+/* Copyright (c) 2010, 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
+ * @rtc0 - rtc device for this driver
+ * @rtc_irq - rtc irq number
+ * @rtc_alarm_irq - rtc alarm irq number
+ * @pm_chip - pointer to pm8058 parent structure
+ */
+struct pm8058_rtc {
+ struct rtc_device *rtc0;
+ int rtc_irq;
+ int rtc_alarm_irq;
+ struct pm8058_chip *pm_chip;
+};
+
+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_rtc0_set_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ unsigned long secs = 0;
+ u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg = 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;
+
+ pr_debug("Seconds value to be written to RTC = %lu\n", secs);
+
+ /* Disable alarm before updating RTC */
+ rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL, &ctrl_reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ if (ctrl_reg & PM8058_RTC_ALARM_ENABLE) {
+ alarm_enabled = 1;
+ ctrl_reg &= ~PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
+ &ctrl_reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 write failed\n");
+ return rc;
+ }
+ }
+
+ /* Write Byte[1], Byte[2], Byte[3], Byte[0] */
+ reg = 0;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE, ®, 1);
+ if (rc < 0) {
+ pr_err("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) {
+ pr_err("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) {
+ pr_err("PM8058 write failed\n");
+ return rc;
+ }
+
+ if (alarm_enabled) {
+ ctrl_reg |= PM8058_RTC_ALARM_ENABLE;
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
+ &ctrl_reg, 1);
+ if (rc < 0) {
+ pr_err("PM8058 write failed\n");
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int
+pm8058_rtc0_read_time(struct device *dev, struct rtc_time *tm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs = 0;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ rc = pm8058_rtc_read_bytes(rtc_dd, value, PM8058_RTC_READ_BASE);
+ if (rc < 0) {
+ pr_err("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, ®, 1);
+ if (rc < 0) {
+ pr_err("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) {
+ pr_err("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) {
+ pr_err("Invalid time read from PMIC8058\n");
+ return rc;
+ }
+
+ pr_debug("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_rtc0_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs = 0;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ /* Check if a alarm is valid */
+ rc = rtc_valid_tm(&alarm->time);
+ if (rc < 0) {
+ pr_err("Alarm time invalid\n");
+ return -EINVAL;
+ }
+
+ 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) {
+ pr_err("Alarm could not be set\n");
+ return rc;
+ }
+
+ rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+
+ reg = (alarm->enabled) ? (reg | PM8058_RTC_ALARM_ENABLE) :
+ (reg & ~PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("PM8058 write failed\n");
+ return rc;
+ }
+
+ pr_debug("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_rtc0_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
+{
+ int rc;
+ u8 value[4], reg;
+ unsigned long secs = 0;
+ struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
+
+ /* Check if the alarm is enabled */
+ rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
+ if (rc < 0) {
+ pr_err("PM8058 read failed\n");
+ return rc;
+ }
+ alarm->enabled = !!(reg & PM8058_RTC_ALARM_ENABLE);
+
+ rc = pm8058_rtc_read_bytes(rtc_dd, value,
+ PM8058_RTC_ALARM_BASE);
+ if (rc < 0) {
+ pr_err("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) {
+ pr_err("Invalid time read from PMIC8058\n");
+ return rc;
+ }
+
+ pr_debug("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 struct rtc_class_ops pm8058_rtc0_ops = {
+ .read_time = pm8058_rtc0_read_time,
+ .set_alarm = pm8058_rtc0_set_alarm,
+ .read_alarm = pm8058_rtc0_read_alarm,
+};
+
+static irqreturn_t pm8058_alarm_trigger(int irq, void *dev_id)
+{
+ unsigned long events = 0;
+ struct pm8058_rtc *rtc_dd = dev_id;
+
+ events = RTC_IRQF | RTC_AF;
+ rtc_update_irq(rtc_dd->rtc0, 1, events);
+
+ pr_debug("Alarm Triggered !!\n");
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit pm8058_rtc_probe(struct platform_device *pdev)
+{
+ int rc;
+ u8 reg;
+ struct pm8058_rtc *rtc_dd;
+ struct pm8058_chip *pm_chip;
+ struct pm8058_rtc_pdata *pdata = pdev->dev.platform_data;
+
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "Platform data absent!\n");
+ return -ENXIO;
+ }
+
+ 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_irq = platform_get_irq(pdev, 0);
+ rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 1);
+ if (!rtc_dd->rtc_alarm_irq || !rtc_dd->rtc_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, ®, 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, ®, 1);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "PM8058 write failed!\n");
+ goto fail_rtc_enable;
+ }
+ }
+
+ if (pdata->rtc_write_enable == true)
+ pm8058_rtc0_ops.set_time = pm8058_rtc0_set_time,
+
+ /* Register the RTC device */
+ rtc_dd->rtc0 = rtc_device_register("pm8058_rtc0", &pdev->dev,
+ &pm8058_rtc0_ops, THIS_MODULE);
+ if (IS_ERR(rtc_dd->rtc0)) {
+ dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
+ __func__, PTR_ERR(rtc_dd->rtc0));
+ rc = PTR_ERR(rtc_dd->rtc0);
+ 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->rtc0);
+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->rtc0);
+ 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);
+}
+
+static void __exit pm8058_rtc_exit(void)
+{
+ platform_driver_unregister(&pm8058_rtc_driver);
+}
+
+module_init(pm8058_rtc_init);
+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..51f7c0b
--- /dev/null
+++ b/include/linux/rtc/rtc-pm8058.h
@@ -0,0 +1,29 @@
+/* Copyright (c) 2010, 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.0.2
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]>
---
drivers/input/misc/Kconfig | 11 +
drivers/input/misc/Makefile | 1 +
drivers/input/misc/pmic8058-pwrkey.c | 322 +++++++++++++++++++++++++++++++++
include/linux/input/pmic8058-pwrkey.h | 37 ++++
4 files changed, 371 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 b99b8cb..aeb9165 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -348,6 +348,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 1fe1f6c..c4357a0 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -31,6 +31,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..3714b24
--- /dev/null
+++ b/drivers/input/misc/pmic8058-pwrkey.c
@@ -0,0 +1,322 @@
+/* Copyright (c) 2010, 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/hrtimer.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 hrtimer timer;
+ bool key_pressed;
+ struct pmic8058_pwrkey_pdata *pdata;
+ spinlock_t lock;
+};
+
+static enum hrtimer_restart pmic8058_pwrkey_timer(struct hrtimer *timer)
+{
+ unsigned long flags;
+ struct pmic8058_pwrkey *pwrkey = container_of(timer,
+ struct pmic8058_pwrkey, timer);
+
+ 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);
+
+ return HRTIMER_NORESTART;
+}
+
+static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey)
+{
+ struct pmic8058_pwrkey *pwrkey = _pwrkey;
+ 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_END, 1);
+ input_sync(pwrkey->pwr);
+
+ hrtimer_start(&pwrkey->timer,
+ ktime_set(pdata->pwrkey_time_ms / 1000,
+ (pdata->pwrkey_time_ms % 1000) * 1000000),
+ HRTIMER_MODE_REL);
+ 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);
+ hrtimer_cancel(&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_END, 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 -EFAULT;
+ }
+
+ 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_END);
+
+ 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;
+ }
+
+ hrtimer_init(&pwrkey->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ pwrkey->timer.function = pmic8058_pwrkey_timer;
+
+ 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);
+ 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..dd849fe
--- /dev/null
+++ b/include/linux/input/pmic8058-pwrkey.h
@@ -0,0 +1,37 @@
+/* Copyright (c) 2010, 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.0.2
Add support for Qualcomm PMIC8058 keyboard
backlight, flash and low current leds.
Cc: Richard Purdie <[email protected]>
Signed-off-by: Trilok Soni <[email protected]>
---
drivers/leds/Kconfig | 11 +
drivers/leds/Makefile | 1 +
drivers/leds/leds-pmic8058.c | 405 +++++++++++++++++++++++++++++++++++++++++
include/linux/leds-pmic8058.h | 63 +++++++
4 files changed, 480 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 cc2a88d..e1ebcad 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -214,6 +214,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 MFD_WM831X
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 9c96db4..6c51883 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_CLEVO_MAIL) += leds-clevo-mail.o
diff --git a/drivers/leds/leds-pmic8058.c b/drivers/leds/leds-pmic8058.c
new file mode 100644
index 0000000..2933eb0
--- /dev/null
+++ b/drivers/leds/leds-pmic8058.c
@@ -0,0 +1,405 @@
+/* Copyright (c) 2010, 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 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
+ * @value_lock - to protect the register value writes
+ * @reg_kp - cached value of keypad led backlight register
+ * @reg_led_ctrl - cached values of low-current led registers
+ * @reg_flash_led0 - cached value of first flash led control register
+ * @reg_flash_led1 - cached value of second flash led control register
+ */
+struct pmic8058_led_data {
+ struct led_classdev cdev;
+ int id;
+ enum led_brightness brightness;
+ struct pm8058_chip *pm_chip;
+ struct work_struct work;
+ struct mutex lock;
+ spinlock_t value_lock;
+ u8 reg_kp;
+ u8 reg_led_ctrl[3];
+ u8 reg_flash_led0;
+ u8 reg_flash_led1;
+};
+
+#define PM8058_MAX_LEDS 7
+
+static void led_kp_set(struct pmic8058_led_data *led, enum led_brightness value)
+{
+ int rc;
+ u8 level;
+ unsigned long flags;
+
+ spin_lock_irqsave(&led->value_lock, flags);
+ level = (value << PM8058_DRV_KEYPAD_BL_SHIFT) &
+ PM8058_DRV_KEYPAD_BL_MASK;
+
+ led->reg_kp &= ~PM8058_DRV_KEYPAD_BL_MASK;
+ led->reg_kp |= level;
+ spin_unlock_irqrestore(&led->value_lock, flags);
+
+ rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_DRV_KEYPAD,
+ &led->reg_kp, 1);
+ if (rc < 0)
+ dev_err(led->cdev.dev, "can't set keypad backlight level\n");
+}
+
+static enum led_brightness led_kp_get(struct pmic8058_led_data *led)
+{
+ if ((led->reg_kp & PM8058_DRV_KEYPAD_BL_MASK) >>
+ PM8058_DRV_KEYPAD_BL_SHIFT)
+ return LED_FULL;
+ else
+ return LED_OFF;
+}
+
+static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value)
+{
+ unsigned long flags;
+ int rc, offset;
+ u8 level, tmp;
+
+ spin_lock_irqsave(&led->value_lock, flags);
+
+ level = (led->brightness << PM8058_DRV_LED_CTRL_SHIFT) &
+ PM8058_DRV_LED_CTRL_MASK;
+
+ offset = PMIC8058_LED_OFFSET(led->id);
+ tmp = led->reg_led_ctrl[offset];
+
+ tmp &= ~PM8058_DRV_LED_CTRL_MASK;
+ tmp |= level;
+ spin_unlock_irqrestore(&led->value_lock, flags);
+
+ rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_LED_CTRL(offset),
+ &tmp, 1);
+ if (rc) {
+ dev_err(led->cdev.dev, "can't set (%d) led value\n",
+ led->id);
+ return;
+ }
+
+ spin_lock_irqsave(&led->value_lock, flags);
+ led->reg_led_ctrl[offset] = tmp;
+ spin_unlock_irqrestore(&led->value_lock, flags);
+}
+
+static enum led_brightness led_lc_get(struct pmic8058_led_data *led)
+{
+ int offset;
+ u8 value;
+
+ offset = PMIC8058_LED_OFFSET(led->id);
+ value = led->reg_led_ctrl[offset];
+
+ if ((value & PM8058_DRV_LED_CTRL_MASK) >>
+ PM8058_DRV_LED_CTRL_SHIFT)
+ return LED_FULL;
+ else
+ return LED_OFF;
+}
+
+static void
+led_flash_set(struct pmic8058_led_data *led, enum led_brightness value)
+{
+ int rc;
+ u8 level;
+ unsigned long flags;
+ u8 reg_flash_led;
+ u16 reg_addr;
+
+ spin_lock_irqsave(&led->value_lock, flags);
+ level = (value << PM8058_DRV_FLASH_SHIFT) &
+ PM8058_DRV_FLASH_MASK;
+
+ if (led->id == PMIC8058_ID_FLASH_LED_0) {
+ led->reg_flash_led0 &= ~PM8058_DRV_FLASH_MASK;
+ led->reg_flash_led0 |= level;
+ reg_flash_led = led->reg_flash_led0;
+ reg_addr = SSBI_REG_ADDR_FLASH_DRV0;
+ } else {
+ led->reg_flash_led1 &= ~PM8058_DRV_FLASH_MASK;
+ led->reg_flash_led1 |= level;
+ reg_flash_led = led->reg_flash_led1;
+ reg_addr = SSBI_REG_ADDR_FLASH_DRV1;
+ }
+ spin_unlock_irqrestore(&led->value_lock, flags);
+
+ rc = pm8058_write(led->pm_chip, reg_addr, ®_flash_led, 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;
+ unsigned long flags;
+
+ led = container_of(led_cdev, struct pmic8058_led_data, cdev);
+
+ spin_lock_irqsave(&led->value_lock, flags);
+ led->brightness = value;
+ schedule_work(&led->work);
+ spin_unlock_irqrestore(&led->value_lock, flags);
+}
+
+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);
+
+ switch (led->id) {
+ case PMIC8058_ID_LED_KB_LIGHT:
+ return led_kp_get(led);
+ case PMIC8058_ID_LED_0:
+ case PMIC8058_ID_LED_1:
+ case PMIC8058_ID_LED_2:
+ return led_lc_get(led);
+ }
+ return LED_OFF;
+}
+
+static int pmic8058_led_probe(struct platform_device *pdev)
+{
+ struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data;
+ struct pmic8058_led_data *led_dat;
+ struct pmic8058_led *curr_led;
+ int rc, i = 0;
+ struct pm8058_chip *pm_chip;
+ u8 reg_kp;
+ u8 reg_led_ctrl[3];
+ u8 reg_flash_led0;
+ u8 reg_flash_led1;
+ static 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 -EFAULT;
+ }
+
+ 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 = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL);
+ if (led == NULL) {
+ dev_err(&pdev->dev, "failed to alloc memory\n");
+ return -ENOMEM;
+ }
+
+ rc = pm8058_read(pm_chip, SSBI_REG_ADDR_DRV_KEYPAD, ®_kp,
+ 1);
+ if (rc) {
+ dev_err(&pdev->dev, "can't get keypad backlight level\n");
+ goto err_reg_read;
+ }
+
+ rc = pm8058_read(pm_chip, SSBI_REG_ADDR_LED_CTRL_BASE,
+ reg_led_ctrl, 3);
+ if (rc) {
+ dev_err(&pdev->dev, "can't get led levels\n");
+ goto err_reg_read;
+ }
+
+ rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV0,
+ ®_flash_led0, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "can't read flash led0\n");
+ goto err_reg_read;
+ }
+
+ rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV1,
+ ®_flash_led1, 1);
+ if (rc) {
+ dev_err(&pdev->dev, "can't get flash led1\n");
+ goto err_reg_read;
+ }
+
+ for (i = 0; i < pdata->num_leds; i++) {
+ curr_led = &pdata->leds[i];
+ led_dat = &led[curr_led->id];
+
+ 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.max_brightness = curr_led->max_brightness;
+ led_dat->cdev.flags = LED_CORE_SUSPENDRESUME;
+
+ led_dat->id = curr_led->id;
+ led_dat->reg_kp = reg_kp;
+ memcpy(led->reg_led_ctrl, reg_led_ctrl,
+ sizeof(reg_led_ctrl));
+ led_dat->reg_flash_led0 = reg_flash_led0;
+ led_dat->reg_flash_led1 = reg_flash_led1;
+
+ 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->pm_chip = pm_chip;
+
+ mutex_init(&led_dat->lock);
+ spin_lock_init(&led_dat->value_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;
+
+err_reg_read:
+ kfree(led);
+fail_id_check:
+ if (i > 0) {
+ for (i = i - 1; i >= 0; i--)
+ led_classdev_unregister(&led[i].cdev);
+ }
+ return rc;
+}
+
+static int __devexit pmic8058_led_remove(struct platform_device *pdev)
+{
+ int i;
+ 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);
+ }
+
+ 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..c54c7e6
--- /dev/null
+++ b/include/linux/leds-pmic8058.h
@@ -0,0 +1,63 @@
+/* Copyright (c) 2010, 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
+ * @max_brightness - maximum brightness level supported by the led
+ * @id - supported led id
+ */
+struct pmic8058_led {
+ const char *name;
+ const char *default_trigger;
+ unsigned max_brightness;
+ int 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.0.2
On Wed, Nov 10, 2010 at 8:47 PM, Trilok Soni <[email protected]> wrote:
> Some keyboard controller have support for more than
> 16 columns and rows.
>
> Cc: Dmitry Torokhov <[email protected]>
> Cc: Eric Miao <[email protected]>
> Signed-off-by: Trilok Soni <[email protected]>
> ---
> Â include/linux/input/matrix_keypad.h | Â Â 8 ++++----
> Â 1 files changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/include/linux/input/matrix_keypad.h b/include/linux/input/matrix_keypad.h
> index 80352ad..d80845e 100644
> --- a/include/linux/input/matrix_keypad.h
> +++ b/include/linux/input/matrix_keypad.h
> @@ -4,11 +4,11 @@
> Â #include <linux/types.h>
> Â #include <linux/input.h>
>
> -#define MATRIX_MAX_ROWS Â Â Â Â Â Â Â Â 16
> -#define MATRIX_MAX_COLS Â Â Â Â Â Â Â Â 16
> +#define MATRIX_MAX_ROWS Â Â Â Â Â Â Â Â 18
> +#define MATRIX_MAX_COLS Â Â Â Â Â Â Â Â 18
>
> -#define KEY(row, col, val) Â Â ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\
> - Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â (((col) & (MATRIX_MAX_COLS - 1)) << 16) |\
> +#define KEY(row, col, val) Â Â ((((row) % (MATRIX_MAX_ROWS)) << 24) |\
> + Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â (((col) % (MATRIX_MAX_COLS)) << 16) |\
> Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â Â (val & 0xffff))
>
Or maybe we can solve this completely by introducing something like:
struct matrix_keycode {
int row;
int col;
int value;
}
And make changes to KEY() macro and other places. You may also want to
eliminate the hardcoded MATRIX_MAX_* in matrix_keypad.c.
> Â #define KEY_ROW(k) Â Â Â Â Â Â (((k) >> 24) & 0xff)
> --
> 1.7.0.2
>
>
Trilok Soni wrote:
> Add support for Qualcomm PMIC8058 keyboard
> backlight, flash and low current leds.
>
> Cc: Richard Purdie <[email protected]>
> Signed-off-by: Trilok Soni <[email protected]>
> ---
> drivers/leds/Kconfig | 11 +
> drivers/leds/Makefile | 1 +
> drivers/leds/leds-pmic8058.c | 405 +++++++++++++++++++++++++++++++++++++++++
> include/linux/leds-pmic8058.h | 63 +++++++
> 4 files changed, 480 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 cc2a88d..e1ebcad 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -214,6 +214,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 MFD_WM831X
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 9c96db4..6c51883 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_CLEVO_MAIL) += leds-clevo-mail.o
> diff --git a/drivers/leds/leds-pmic8058.c b/drivers/leds/leds-pmic8058.c
> new file mode 100644
> index 0000000..2933eb0
> --- /dev/null
> +++ b/drivers/leds/leds-pmic8058.c
> @@ -0,0 +1,405 @@
> +/* Copyright (c) 2010, 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 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
> + * @value_lock - to protect the register value writes
> + * @reg_kp - cached value of keypad led backlight register
> + * @reg_led_ctrl - cached values of low-current led registers
> + * @reg_flash_led0 - cached value of first flash led control register
> + * @reg_flash_led1 - cached value of second flash led control register
> + */
> +struct pmic8058_led_data {
> + struct led_classdev cdev;
> + int id;
"enum pmic8058_leds" instead of int
> + enum led_brightness brightness;
> + struct pm8058_chip *pm_chip;
> + struct work_struct work;
> + struct mutex lock;
> + spinlock_t value_lock;
> + u8 reg_kp;
> + u8 reg_led_ctrl[3];
> + u8 reg_flash_led0;
> + u8 reg_flash_led1;
You allocate a separate pmic8058_led_data for each led, so one "u8 reg" should be
sufficient.
> +};
> +
> +#define PM8058_MAX_LEDS 7
> +
> +static void led_kp_set(struct pmic8058_led_data *led, enum led_brightness value)
> +{
> + int rc;
> + u8 level;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&led->value_lock, flags);
This function is only ever called from within the workqueue so there is no need for
locking.
> + level = (value << PM8058_DRV_KEYPAD_BL_SHIFT) &
> + PM8058_DRV_KEYPAD_BL_MASK;
> +
> + led->reg_kp &= ~PM8058_DRV_KEYPAD_BL_MASK;
> + led->reg_kp |= level;
> + spin_unlock_irqrestore(&led->value_lock, flags);
> +
> + rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_DRV_KEYPAD,
> + &led->reg_kp, 1);
> + if (rc < 0)
> + dev_err(led->cdev.dev, "can't set keypad backlight level\n");
> +}
> +
> +static enum led_brightness led_kp_get(struct pmic8058_led_data *led)
> +{
> + if ((led->reg_kp & PM8058_DRV_KEYPAD_BL_MASK) >>
> + PM8058_DRV_KEYPAD_BL_SHIFT)
> + return LED_FULL;
> + else
> + return LED_OFF;
> +}
> +
Shouldn't you be returning the actual brightness here instead of only either on or
off? The brightness is btw. stored in led->brightness, so you can use the same getter
for all three types of leds.
> +static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value)
> +{
> + unsigned long flags;
> + int rc, offset;
> + u8 level, tmp;
> +
> + spin_lock_irqsave(&led->value_lock, flags);
This function is only ever called from within the workqueue so there is no need for
locking.
> +
> + level = (led->brightness << PM8058_DRV_LED_CTRL_SHIFT) &
> + PM8058_DRV_LED_CTRL_MASK;
> +
> + offset = PMIC8058_LED_OFFSET(led->id);
> + tmp = led->reg_led_ctrl[offset];
> +
> + tmp &= ~PM8058_DRV_LED_CTRL_MASK;
> + tmp |= level;
> + spin_unlock_irqrestore(&led->value_lock, flags);
> +
> + rc = pm8058_write(led->pm_chip, SSBI_REG_ADDR_LED_CTRL(offset),
> + &tmp, 1);
> + if (rc) {
> + dev_err(led->cdev.dev, "can't set (%d) led value\n",
> + led->id);
> + return;
> + }
> +
> + spin_lock_irqsave(&led->value_lock, flags);
> + led->reg_led_ctrl[offset] = tmp;
> + spin_unlock_irqrestore(&led->value_lock, flags);
> +}
> +
> +static enum led_brightness led_lc_get(struct pmic8058_led_data *led)
> +{
> + int offset;
> + u8 value;
> +
> + offset = PMIC8058_LED_OFFSET(led->id);
> + value = led->reg_led_ctrl[offset];
> +
> + if ((value & PM8058_DRV_LED_CTRL_MASK) >>
> + PM8058_DRV_LED_CTRL_SHIFT)
> + return LED_FULL;
> + else
> + return LED_OFF;
> +}
See above.
> +
> +static void
> +led_flash_set(struct pmic8058_led_data *led, enum led_brightness value)
> +{
> + int rc;
> + u8 level;
> + unsigned long flags;
> + u8 reg_flash_led;
> + u16 reg_addr;
> +
> + spin_lock_irqsave(&led->value_lock, flags);
This function is only ever called from within the workqueue so there is no need for
locking.
> + level = (value << PM8058_DRV_FLASH_SHIFT) &
> + PM8058_DRV_FLASH_MASK;
> +
> + if (led->id == PMIC8058_ID_FLASH_LED_0) {
> + led->reg_flash_led0 &= ~PM8058_DRV_FLASH_MASK;
> + led->reg_flash_led0 |= level;
> + reg_flash_led = led->reg_flash_led0;
> + reg_addr = SSBI_REG_ADDR_FLASH_DRV0;
> + } else {
> + led->reg_flash_led1 &= ~PM8058_DRV_FLASH_MASK;
> + led->reg_flash_led1 |= level;
> + reg_flash_led = led->reg_flash_led1;
> + reg_addr = SSBI_REG_ADDR_FLASH_DRV1;
> + }
> + spin_unlock_irqrestore(&led->value_lock, flags);
> +
> + rc = pm8058_write(led->pm_chip, reg_addr, ®_flash_led, 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);
> +
Since this is a workqueue and there will only one running instance per led at a time
there is no need to take a lock here.
> + 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;
> + unsigned long flags;
> +
> + led = container_of(led_cdev, struct pmic8058_led_data, cdev);
> +
> + spin_lock_irqsave(&led->value_lock, flags);
Locking is not really required here since it is only a single assignment...
> + led->brightness = value;
> + schedule_work(&led->work);
and scheudule_work does not have to be inside of the lock.
> + spin_unlock_irqrestore(&led->value_lock, flags);
> +}
> +
> +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; (See above)
> +
> + switch (led->id) {
> + case PMIC8058_ID_LED_KB_LIGHT:
> + return led_kp_get(led);
> + case PMIC8058_ID_LED_0:
> + case PMIC8058_ID_LED_1:
> + case PMIC8058_ID_LED_2:
> + return led_lc_get(led);
> + }
> + return LED_OFF;
> +}
> +
> +static int pmic8058_led_probe(struct platform_device *pdev)
__devinit
> +{
> + struct pmic8058_leds_platform_data *pdata = pdev->dev.platform_data;
> + struct pmic8058_led_data *led_dat;
> + struct pmic8058_led *curr_led;
> + int rc, i = 0;
> + struct pm8058_chip *pm_chip;
> + u8 reg_kp;
> + u8 reg_led_ctrl[3];
> + u8 reg_flash_led0;
> + u8 reg_flash_led1;
> + static struct pmic8058_led_data *led;
> +
> + pm_chip = platform_get_drvdata(pdev);
This looks at least a bit bogus since you'll overwrite the drvdata later. Can't you
get the pm8058_chip through pdev->dev.parent somehow?
> + if (pm_chip == NULL) {
> + dev_err(&pdev->dev, "no parent data passed in\n");
> + return -EFAULT;
-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;
-EINVAL
> + }
> +
> + led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL);
Use kcalloc instead of kzalloc.
> + if (led == NULL) {
> + dev_err(&pdev->dev, "failed to alloc memory\n");
> + return -ENOMEM;
> + }
> +
> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_DRV_KEYPAD, ®_kp,
> + 1);
> + if (rc) {
> + dev_err(&pdev->dev, "can't get keypad backlight level\n");
> + goto err_reg_read;
> + }
> +
> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_LED_CTRL_BASE,
> + reg_led_ctrl, 3);
> + if (rc) {
> + dev_err(&pdev->dev, "can't get led levels\n");
> + goto err_reg_read;
> + }
> +
> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV0,
> + ®_flash_led0, 1);
> + if (rc) {
> + dev_err(&pdev->dev, "can't read flash led0\n");
> + goto err_reg_read;
> + }
> +
> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV1,
> + ®_flash_led1, 1);
> + if (rc) {
> + dev_err(&pdev->dev, "can't get flash led1\n");
> + goto err_reg_read;
> + }
How about adding a helper function which will initializes the leds 'reg' field
depending on its id? It will certainly to improve code readability.
> +
> + for (i = 0; i < pdata->num_leds; i++) {
> + curr_led = &pdata->leds[i];
> + led_dat = &led[curr_led->id];
> +
> + 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.max_brightness = curr_led->max_brightness;
> + led_dat->cdev.flags = LED_CORE_SUSPENDRESUME;
> +
> + led_dat->id = curr_led->id;
> + led_dat->reg_kp = reg_kp;
> + memcpy(led->reg_led_ctrl, reg_led_ctrl,
> + sizeof(reg_led_ctrl));
> + led_dat->reg_flash_led0 = reg_flash_led0;
> + led_dat->reg_flash_led1 = reg_flash_led1;
> +
> + 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->pm_chip = pm_chip;
> +
> + mutex_init(&led_dat->lock);
> + spin_lock_init(&led_dat->value_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;
> +
> +err_reg_read:
> + kfree(led);
> +fail_id_check:
> + if (i > 0) {
> + for (i = i - 1; i >= 0; i--)
> + led_classdev_unregister(&led[i].cdev);
> + }
> + return rc;
> +}
> +
> +static int __devexit pmic8058_led_remove(struct platform_device *pdev)
> +{
> + int i;
> + 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);
> + }
You need to free the 'led' array.
> +
> + 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..c54c7e6
> --- /dev/null
> +++ b/include/linux/leds-pmic8058.h
> @@ -0,0 +1,63 @@
> +/* Copyright (c) 2010, 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
> + * @max_brightness - maximum brightness level supported by the led
> + * @id - supported led id
> + */
> +struct pmic8058_led {
> + const char *name;
> + const char *default_trigger;
> + unsigned max_brightness;
Should max_brightness not rather be hardcoded in the driver? As far as I can tell it
depend on the hardware and is 4 bits wide for flash and bl leds and 5 bits for the
others.
> + int id;
enum pmic8058_leds instead of int
> +};
> +
> +/**
> + * 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;
size_t
> + struct pmic8058_led *leds;
> +};
If max_brightness is hardcoded in the driver you can reuse "struct led_info" and
"struct struct led_platform_data" instead of adding your own structs.
> +
> +#endif /* __LEDS_PMIC8058_H__ */
On Wed, Nov 10, 2010 at 09:45:05PM +0100, Lars-Peter Clausen wrote:
> Trilok Soni wrote:
> > +
> > +static void led_kp_set(struct pmic8058_led_data *led, enum led_brightness value)
> > +{
> > + int rc;
> > + u8 level;
> > + unsigned long flags;
> > +
> > + spin_lock_irqsave(&led->value_lock, flags);
> This function is only ever called from within the workqueue so there is no need for
> locking.
>
That is a common misconception, unfortunately. The same work may
be executing on several CPUs at the same time if it was scheduled on
multi-threaded work queue.
...
> > + schedule_work(&led->work);
And sure enough, keventd is such workqueue.
Now, whether having the same work run simultaneously is OK or not is a
different question altogether...
--
Dmitry
On Thu, Nov 11, 2010 at 02:33:45AM +0800, Eric Miao wrote:
> On Wed, Nov 10, 2010 at 8:47 PM, Trilok Soni <[email protected]> wrote:
> > Some keyboard controller have support for more than
> > 16 columns and rows.
> >
> > Cc: Dmitry Torokhov <[email protected]>
> > Cc: Eric Miao <[email protected]>
> > Signed-off-by: Trilok Soni <[email protected]>
> > ---
> > ?include/linux/input/matrix_keypad.h | ? ?8 ++++----
> > ?1 files changed, 4 insertions(+), 4 deletions(-)
> >
> > diff --git a/include/linux/input/matrix_keypad.h b/include/linux/input/matrix_keypad.h
> > index 80352ad..d80845e 100644
> > --- a/include/linux/input/matrix_keypad.h
> > +++ b/include/linux/input/matrix_keypad.h
> > @@ -4,11 +4,11 @@
> > ?#include <linux/types.h>
> > ?#include <linux/input.h>
> >
> > -#define MATRIX_MAX_ROWS ? ? ? ? ? ? ? ?16
> > -#define MATRIX_MAX_COLS ? ? ? ? ? ? ? ?16
> > +#define MATRIX_MAX_ROWS ? ? ? ? ? ? ? ?18
> > +#define MATRIX_MAX_COLS ? ? ? ? ? ? ? ?18
> >
> > -#define KEY(row, col, val) ? ? ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\
> > - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(((col) & (MATRIX_MAX_COLS - 1)) << 16) |\
> > +#define KEY(row, col, val) ? ? ((((row) % (MATRIX_MAX_ROWS)) << 24) |\
> > + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(((col) % (MATRIX_MAX_COLS)) << 16) |\
> > ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (val & 0xffff))
> >
>
> Or maybe we can solve this completely by introducing something like:
>
> struct matrix_keycode {
> int row;
> int col;
> int value;
Though that triples the space needed to store initial keymaps.
--
Dmitry
Dmitry Torokhov wrote:
> On Wed, Nov 10, 2010 at 09:45:05PM +0100, Lars-Peter Clausen wrote:
>> Trilok Soni wrote:
>>> +
>>> +static void led_kp_set(struct pmic8058_led_data *led, enum led_brightness value)
>>> +{
>>> + int rc;
>>> + u8 level;
>>> + unsigned long flags;
>>> +
>>> + spin_lock_irqsave(&led->value_lock, flags);
>> This function is only ever called from within the workqueue so there is no need for
>> locking.
>>
>
> That is a common misconception, unfortunately. The same work may
> be executing on several CPUs at the same time if it was scheduled on
> multi-threaded work queue.
>
Hm, right my fault.
Still the comment above is still valid, because the original workqueue handler was
locked by a mutex. But the comment regarding the mutex should have been:
You can remove the mutex if you queue the work on the system_nrt_wq workqueue
> ...
>
>>> + schedule_work(&led->work);
>
> And sure enough, keventd is such workqueue.
>
> Now, whether having the same work run simultaneously is OK or not is a
> different question altogether...
>
- Lars
Hi Trilkok,
On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
> 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.
>
Why do we need to delay KEY_POWER reporting? Do we need to use high
resolution timers or regular timers would do as well? KEY_END appears to
be abused (you don't want to move your cursor to the end of line, do
you?). Also I wonder if header file should reside in linux/mfd with the
rest of pmic8058 components.
Thanks.
> Cc: Dmitry Torokhov <[email protected]>
> Signed-off-by: Trilok Soni <[email protected]>
> ---
> drivers/input/misc/Kconfig | 11 +
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/pmic8058-pwrkey.c | 322 +++++++++++++++++++++++++++++++++
> include/linux/input/pmic8058-pwrkey.h | 37 ++++
> 4 files changed, 371 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 b99b8cb..aeb9165 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -348,6 +348,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 1fe1f6c..c4357a0 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -31,6 +31,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..3714b24
> --- /dev/null
> +++ b/drivers/input/misc/pmic8058-pwrkey.c
> @@ -0,0 +1,322 @@
> +/* Copyright (c) 2010, 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/hrtimer.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 hrtimer timer;
> + bool key_pressed;
> + struct pmic8058_pwrkey_pdata *pdata;
> + spinlock_t lock;
> +};
> +
> +static enum hrtimer_restart pmic8058_pwrkey_timer(struct hrtimer *timer)
> +{
> + unsigned long flags;
> + struct pmic8058_pwrkey *pwrkey = container_of(timer,
> + struct pmic8058_pwrkey, timer);
> +
> + 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);
> +
> + return HRTIMER_NORESTART;
> +}
> +
> +static irqreturn_t pwrkey_press_irq(int irq, void *_pwrkey)
> +{
> + struct pmic8058_pwrkey *pwrkey = _pwrkey;
> + 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_END, 1);
> + input_sync(pwrkey->pwr);
> +
> + hrtimer_start(&pwrkey->timer,
> + ktime_set(pdata->pwrkey_time_ms / 1000,
> + (pdata->pwrkey_time_ms % 1000) * 1000000),
> + HRTIMER_MODE_REL);
> + 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);
> + hrtimer_cancel(&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_END, 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 -EFAULT;
> + }
> +
> + 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_END);
> +
> + 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;
> + }
> +
> + hrtimer_init(&pwrkey->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> + pwrkey->timer.function = pmic8058_pwrkey_timer;
> +
> + 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);
> + 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..dd849fe
> --- /dev/null
> +++ b/include/linux/input/pmic8058-pwrkey.h
> @@ -0,0 +1,37 @@
> +/* Copyright (c) 2010, 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.0.2
>
--
Dmitry
Hi Dmitry,
On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
> Hi Trilkok,
>
> On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
>> 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.
>>
>
> Why do we need to delay KEY_POWER reporting? Do we need to use high
> resolution timers or regular timers would do as well? KEY_END appears to
> be abused (you don't want to move your cursor to the end of line, do
> you?). Also I wonder if header file should reside in linux/mfd with the
> rest of pmic8058 components.
Most of the time Mobile devices come with single physical key for POWER,
which if pressed for less than 500ms (configurable) then it will only
report KEY_END (which say locks the screen on mobile) and if it pressed
more than 500ms then it will also report KEY_POWER event too, which will
say display menu on your mobile for asking you to suspend/switch off/etc, operations.
For the timers I can move from hrtimers to regular timers.
For the header file, I can move them to include/linux/mfd too. No problem on that.
---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.
Hi Peter,
>> +struct pmic8058_led_data {
>> + struct led_classdev cdev;
>> + int id;
> "enum pmic8058_leds" instead of int
Ack.
>> + enum led_brightness brightness;
>> + struct pm8058_chip *pm_chip;
>> + struct work_struct work;
>> + struct mutex lock;
>> + spinlock_t value_lock;
>> + u8 reg_kp;
>> + u8 reg_led_ctrl[3];
>> + u8 reg_flash_led0;
>> + u8 reg_flash_led1;
>
> You allocate a separate pmic8058_led_data for each led, so one "u8 reg" should be
> sufficient.
Agree. I will check and update.
>
>> +};
>> +
>> +#define PM8058_MAX_LEDS 7
>> +
>> +static void led_kp_set(struct pmic8058_led_data *led, enum led_brightness value)
>> +{
>> + int rc;
>> + u8 level;
>> + unsigned long flags;
>> +
>> + spin_lock_irqsave(&led->value_lock, flags);
> This function is only ever called from within the workqueue so there is no need for
> locking.
There is a historical reason here, but not application to upstream. You can see that we
have flash_drv lines also for flash leds in this code, which is used to drive
the camera flash leds. The core problem here is that there is no "camera flash" framework
in the v4l2 which could drive these pins from the kernel space, so internally we had
to also export this _set functions so that v4l2 drivers can use them to control
the brightness of the camera flash.
Looking at another way, you can use all of these low level leds, flash leds and keyboard
leds drive strengths by combining them into the h/w and driver single camera flash requiring
around 990mA of current.
I could remove these spin locks from this code though, as we don't have generic solution
in upstream to handle in-kernel usage of LED apis.
>> +
>> +static enum led_brightness led_kp_get(struct pmic8058_led_data *led)
>> +{
>> + if ((led->reg_kp & PM8058_DRV_KEYPAD_BL_MASK) >>
>> + PM8058_DRV_KEYPAD_BL_SHIFT)
>> + return LED_FULL;
>> + else
>> + return LED_OFF;
>> +}
>> +
>
> Shouldn't you be returning the actual brightness here instead of only either on or
> off? The brightness is btw. stored in led->brightness, so you can use the same getter
> for all three types of leds.
Ack.
>
>
>> +static void led_lc_set(struct pmic8058_led_data *led, enum led_brightness value)
>> +{
>> + unsigned long flags;
>> + int rc, offset;
>> + u8 level, tmp;
>> +
>> + spin_lock_irqsave(&led->value_lock, flags);
> This function is only ever called from within the workqueue so there is no need for
> locking.
As pointed above. Ack.
>> +
>> +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);
>> +
> Since this is a workqueue and there will only one running instance per led at a time
> there is no need to take a lock here.
I will check this.
>> +
>> +static void pmic8058_led_set(struct led_classdev *led_cdev,
>> + enum led_brightness value)
>> +{
>> + struct pmic8058_led_data *led;
>> + unsigned long flags;
>> +
>> + led = container_of(led_cdev, struct pmic8058_led_data, cdev);
>> +
>> + spin_lock_irqsave(&led->value_lock, flags);
> Locking is not really required here since it is only a single assignment...
>> + led->brightness = value;
>> + schedule_work(&led->work);
> and scheudule_work does not have to be inside of the lock.
>> + spin_unlock_irqrestore(&led->value_lock, flags);
locks will go away.
>> +}
>> +
>> +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; (See above)
Ack.
>> +
>> +static int pmic8058_led_probe(struct platform_device *pdev)
> __devinit
Ack.
>> +
>> + pm_chip = platform_get_drvdata(pdev);
> This looks at least a bit bogus since you'll overwrite the drvdata later. Can't you
> get the pm8058_chip through pdev->dev.parent somehow?
I will update this once the PMIC8058 core driver gets updated.
>> + if (pm_chip == NULL) {
>> + dev_err(&pdev->dev, "no parent data passed in\n");
>> + return -EFAULT;
> -EINVAL
Sure.
>> +
>> + if (pdata->num_leds > PMIC8058_MAX_LEDS) {
>> + dev_err(&pdev->dev, "can't handle more than %d LEDS\n",
>> + PMIC8058_MAX_LEDS);
>> + return -EFAULT;
> -EINVAL
Ack.
>> + }
>> +
>> + led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL);
> Use kcalloc instead of kzalloc.
Ok.
>> +
>> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_DRV_KEYPAD, ®_kp,
>> + 1);
>> + if (rc) {
>> + dev_err(&pdev->dev, "can't get keypad backlight level\n");
>> + goto err_reg_read;
>> + }
>> +
>> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_LED_CTRL_BASE,
>> + reg_led_ctrl, 3);
>> + if (rc) {
>> + dev_err(&pdev->dev, "can't get led levels\n");
>> + goto err_reg_read;
>> + }
>> +
>> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV0,
>> + ®_flash_led0, 1);
>> + if (rc) {
>> + dev_err(&pdev->dev, "can't read flash led0\n");
>> + goto err_reg_read;
>> + }
>> +
>> + rc = pm8058_read(pm_chip, SSBI_REG_ADDR_FLASH_DRV1,
>> + ®_flash_led1, 1);
>> + if (rc) {
>> + dev_err(&pdev->dev, "can't get flash led1\n");
>> + goto err_reg_read;
>> + }
> How about adding a helper function which will initializes the leds 'reg' field
> depending on its id? It will certainly to improve code readability.
Sure. I will add it.
>> +
>> +static int __devexit pmic8058_led_remove(struct platform_device *pdev)
>> +{
>> + int i;
>> + 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);
>> + }
>
> You need to free the 'led' array.
Thanks. Missed it.
>> +
>> +/**
>> + * struct pmic8058_led - per led data
>> + * @name - name of the led
>> + * @default_trigger - default trigger which needs to e attached
>> + * @max_brightness - maximum brightness level supported by the led
>> + * @id - supported led id
>> + */
>> +struct pmic8058_led {
>> + const char *name;
>> + const char *default_trigger;
>> + unsigned max_brightness;
> Should max_brightness not rather be hardcoded in the driver? As far as I can tell it
> depend on the hardware and is 4 bits wide for flash and bl leds and 5 bits for the
> others.
>> + int id;
>
> enum pmic8058_leds instead of int
Ack.
>> +struct pmic8058_leds_platform_data {
>> + int num_leds;
> size_t
Ack.
>> + struct pmic8058_led *leds;
>> +};
>
>
> If max_brightness is hardcoded in the driver you can reuse "struct led_info" and
> "struct struct led_platform_data" instead of adding your own structs.
I will check this and see if I can add it in next version.
Thanks for the review.
---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.
On Thu, Nov 11, 2010 at 05:30:21PM +0530, Trilok Soni wrote:
> Hi Dmitry,
>
> On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
> > Hi Trilkok,
> >
> > On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
> >> 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.
> >>
> >
> > Why do we need to delay KEY_POWER reporting? Do we need to use high
> > resolution timers or regular timers would do as well? KEY_END
> > appears to be abused (you don't want to move your cursor to the end
> > of line, do you?). Also I wonder if header file should reside in
> > linux/mfd with the rest of pmic8058 components.
>
> Most of the time Mobile devices come with single physical key for
> POWER, which if pressed for less than 500ms (configurable) then it
> will only report KEY_END (which say locks the screen on mobile) and if
> it pressed more than 500ms then it will also report KEY_POWER event
> too, which will say display menu on your mobile for asking you to
> suspend/switch off/etc, operations.
>
I see,. If you would have used KEY_SCREENLOCK iinstead of KEY_END I
would likely not ask this question ;)
> For the timers I can move from hrtimers to regular timers.
>
> For the header file, I can move them to include/linux/mfd too. No
> problem on that.
>
I am not even sure we need to keep them in separate header files, but it
is up to you.
Thanks.
--
Dmitry
Hi Alessandro,
On 11/10/2010 6:18 PM, Trilok Soni wrote:
> From: Anirudh Ghayal <[email protected]>
>
> 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]>
> Signed-off-by: Anirudh Ghayal <[email protected]>
Please review this driver. Thanks.
---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.
Hi Dmitry,
On 11/12/2010 6:27 AM, Dmitry Torokhov wrote:
> On Thu, Nov 11, 2010 at 05:30:21PM +0530, Trilok Soni wrote:
>> Hi Dmitry,
>>
>> On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
>>> Hi Trilkok,
>>>
>>> On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
>>>> 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.
>>>>
>>>
>>> Why do we need to delay KEY_POWER reporting? Do we need to use high
>>> resolution timers or regular timers would do as well? KEY_END
>>> appears to be abused (you don't want to move your cursor to the end
>>> of line, do you?). Also I wonder if header file should reside in
>>> linux/mfd with the rest of pmic8058 components.
>>
>> Most of the time Mobile devices come with single physical key for
>> POWER, which if pressed for less than 500ms (configurable) then it
>> will only report KEY_END (which say locks the screen on mobile) and if
>> it pressed more than 500ms then it will also report KEY_POWER event
>> too, which will say display menu on your mobile for asking you to
>> suspend/switch off/etc, operations.
>>
>
> I see,. If you would have used KEY_SCREENLOCK iinstead of KEY_END I
> would likely not ask this question ;)
>
KEY_SCRENNLOCK looks good, let me analyze the impact on userspace framework
which I have. I will come back on this in a day.
>> For the timers I can move from hrtimers to regular timers.
>>
>> For the header file, I can move them to include/linux/mfd too. No
>> problem on that.
>>
>
> I am not even sure we need to keep them in separate header files, but it
> is up to you.
Do you suggest that all the MFD sub-devices's platform data structures should come from single
header file?
---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.
Trilok Soni wrote:
> From: Anirudh Ghayal <[email protected]>
>
> 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]>
> Signed-off-by: Anirudh Ghayal <[email protected]>
> ---
> drivers/rtc/Kconfig | 9 +
> drivers/rtc/Makefile | 1 +
> drivers/rtc/rtc-pm8058.c | 487 ++++++++++++++++++++++++++++++++++++++++
> include/linux/rtc/rtc-pm8058.h | 29 +++
> 4 files changed, 526 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 2883428..9f4ea00 100644
> --- a/drivers/rtc/Kconfig
> +++ b/drivers/rtc/Kconfig
> @@ -665,6 +665,15 @@ config RTC_DRV_NUC900
> If you say yes here you get support for the RTC subsystem of the
> NUC910/NUC920 used in embedded systems.
>
> +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.
> +
> comment "on-CPU RTC drivers"
>
> config RTC_DRV_DAVINCI
> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
> index 4c2832d..d7a4f7d 100644
> --- a/drivers/rtc/Makefile
> +++ b/drivers/rtc/Makefile
> @@ -73,6 +73,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..9fef82d
> --- /dev/null
> +++ b/drivers/rtc/rtc-pm8058.c
> @@ -0,0 +1,487 @@
> +/* Copyright (c) 2010, 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)
Are there other bits in the ctrl register which are not touch by this driver?
If not you could turn the read-modify-write operations changing the ctrl register
into simple writes.
> +
> +#define NUM_8_BIT_RTC_REGS 0x4
> +
> +/**
> + * struct pm8058_rtc - rtc driver internal structure
> + * @rtc0 - rtc device for this driver
> + * @rtc_irq - rtc irq number
> + * @rtc_alarm_irq - rtc alarm irq number
> + * @pm_chip - pointer to pm8058 parent structure
> + */
> +struct pm8058_rtc {
> + struct rtc_device *rtc0;
Is there a reason for the '0'?
> + int rtc_irq;
> + int rtc_alarm_irq;
> + struct pm8058_chip *pm_chip;
> +};
> +
> +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;
> + }
> + }
Hm, I don't know the hardware, but I would assume that
pm8058_read(rtc_dd->pm_chip, base, rtc_val, NUM_8_BIT_RTC_REGS) should work as well.
> +
> + 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;
> + }
> + }
> +
Same here
> + 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_rtc0_set_time(struct device *dev, struct rtc_time *tm)
> +{
> + int rc;
> + unsigned long secs = 0;
> + u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg = 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;
> +
> + pr_debug("Seconds value to be written to RTC = %lu\n", secs);
> +
> + /* Disable alarm before updating RTC */
> + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL, &ctrl_reg, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
dev_err instead of pr_err. The other pr_err should also be replaced.
> + return rc;
> + }
> +
> + if (ctrl_reg & PM8058_RTC_ALARM_ENABLE) {
> + alarm_enabled = 1;
> + ctrl_reg &= ~PM8058_RTC_ALARM_ENABLE;
> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
> + &ctrl_reg, 1);
> + if (rc < 0) {
> + pr_err("PM8058 write failed\n");
> + return rc;
> + }
> + }
> +
> + /* Write Byte[1], Byte[2], Byte[3], Byte[0] */
> + reg = 0;
> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE, ®, 1);
> + if (rc < 0) {
> + pr_err("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) {
> + pr_err("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) {
> + pr_err("PM8058 write failed\n");
> + return rc;
> + }
> +
> + if (alarm_enabled) {
> + ctrl_reg |= PM8058_RTC_ALARM_ENABLE;
> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
> + &ctrl_reg, 1);
> + if (rc < 0) {
> + pr_err("PM8058 write failed\n");
> + return rc;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int
> +pm8058_rtc0_read_time(struct device *dev, struct rtc_time *tm)
> +{
> + int rc;
> + u8 value[4], reg;
> + unsigned long secs = 0;
> + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
> +
> + rc = pm8058_rtc_read_bytes(rtc_dd, value, PM8058_RTC_READ_BASE);
> + if (rc < 0) {
> + pr_err("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, ®, 1);
> + if (rc < 0) {
> + pr_err("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) {
> + pr_err("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) {
> + pr_err("Invalid time read from PMIC8058\n");
> + return rc;
> + }
> +
> + pr_debug("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_rtc0_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> + int rc;
> + u8 value[4], reg;
> + unsigned long secs = 0;
No need to initialize secs here.
> + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
> +
> + /* Check if a alarm is valid */
> + rc = rtc_valid_tm(&alarm->time);
> + if (rc < 0) {
> + pr_err("Alarm time invalid\n");
> + return -EINVAL;
> + }
The upper layer will already check if alarm->time is valid, so there is no need to
check it here again.
> +
> + 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) {
> + pr_err("Alarm could not be set\n");
> + return rc;
> + }
> +
> + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> +
> + reg = (alarm->enabled) ? (reg | PM8058_RTC_ALARM_ENABLE) :
> + (reg & ~PM8058_RTC_ALARM_ENABLE);
> +
> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 write failed\n");
> + return rc;
> + }
> +
> + pr_debug("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_rtc0_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
> +{
> + int rc;
> + u8 value[4], reg;
> + unsigned long secs = 0;
No need to initialize secs here.
> + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
> +
> + /* Check if the alarm is enabled */
> + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> + alarm->enabled = !!(reg & PM8058_RTC_ALARM_ENABLE);
> +
> + rc = pm8058_rtc_read_bytes(rtc_dd, value,
> + PM8058_RTC_ALARM_BASE);
> + if (rc < 0) {
> + pr_err("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) {
> + pr_err("Invalid time read from PMIC8058\n");
> + return rc;
> + }
> +
> + pr_debug("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 struct rtc_class_ops pm8058_rtc0_ops = {
> + .read_time = pm8058_rtc0_read_time,
> + .set_alarm = pm8058_rtc0_set_alarm,
> + .read_alarm = pm8058_rtc0_read_alarm,
Implementig alarm_irq_enable might be a good idea
> +};
> +
> +static irqreturn_t pm8058_alarm_trigger(int irq, void *dev_id)
> +{
> + unsigned long events = 0;
> + struct pm8058_rtc *rtc_dd = dev_id;
> +
> + events = RTC_IRQF | RTC_AF;
> + rtc_update_irq(rtc_dd->rtc0, 1, events);
just rtc_update_irq(rtc_dd->rtc0, 1, RTC_IRQF | RTC_AF); should be fine
> +
> + pr_debug("Alarm Triggered !!\n");
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int __devinit pm8058_rtc_probe(struct platform_device *pdev)
> +{
> + int rc;
> + u8 reg;
> + struct pm8058_rtc *rtc_dd;
> + struct pm8058_chip *pm_chip;
> + struct pm8058_rtc_pdata *pdata = pdev->dev.platform_data;
> +
> + if (pdata == NULL) {
> + dev_err(&pdev->dev, "Platform data absent!\n");
> + return -ENXIO;
> + }
It might be a good idea to assume a default for rtc_write_enable if pdata is not set.
> +
> + 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_irq = platform_get_irq(pdev, 0);
You don't seem to use this irq.
> + rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 1);
> + if (!rtc_dd->rtc_alarm_irq || !rtc_dd->rtc_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, ®, 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, ®, 1);
> + if (rc < 0) {
> + dev_err(&pdev->dev, "PM8058 write failed!\n");
> + goto fail_rtc_enable;
> + }
> + }
> +
> + if (pdata->rtc_write_enable == true)
> + pm8058_rtc0_ops.set_time = pm8058_rtc0_set_time,
> +
> + /* Register the RTC device */
> + rtc_dd->rtc0 = rtc_device_register("pm8058_rtc0", &pdev->dev,
> + &pm8058_rtc0_ops, THIS_MODULE);
> + if (IS_ERR(rtc_dd->rtc0)) {
> + dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
> + __func__, PTR_ERR(rtc_dd->rtc0));
> + rc = PTR_ERR(rtc_dd->rtc0);
> + 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->rtc0);
> +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->rtc0);
> + 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);
> +}
> +
> +static void __exit pm8058_rtc_exit(void)
> +{
> + platform_driver_unregister(&pm8058_rtc_driver);
> +}
> +
> +module_init(pm8058_rtc_init);
> +module_exit(pm8058_rtc_exit);
module_{init,exit} should go right beneath the function they are referring to
> +
> +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..51f7c0b
> --- /dev/null
> +++ b/include/linux/rtc/rtc-pm8058.h
> @@ -0,0 +1,29 @@
> +/* Copyright (c) 2010, 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;
> +};
Is there a technical reason why changing the rtc clocks time should be disabled or is
this a policy based decision?
> +
> +#endif /* __RTC_PM8058_H__ */
Hi Peter,
> Trilok Soni wrote:
>> From: Anirudh Ghayal<[email protected]>
>>
>> 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]>
>> Signed-off-by: Anirudh Ghayal<[email protected]>
>> ---
>> drivers/rtc/Kconfig | 9 +
>> drivers/rtc/Makefile | 1 +
>> drivers/rtc/rtc-pm8058.c | 487 ++++++++++++++++++++++++++++++++++++++++
>> include/linux/rtc/rtc-pm8058.h | 29 +++
>> 4 files changed, 526 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 2883428..9f4ea00 100644
>> --- a/drivers/rtc/Kconfig
>> +++ b/drivers/rtc/Kconfig
>> @@ -665,6 +665,15 @@ config RTC_DRV_NUC900
>> If you say yes here you get support for the RTC subsystem of the
>> NUC910/NUC920 used in embedded systems.
>>
>> +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.
>> +
>> comment "on-CPU RTC drivers"
>>
>> config RTC_DRV_DAVINCI
>> diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
>> index 4c2832d..d7a4f7d 100644
>> --- a/drivers/rtc/Makefile
>> +++ b/drivers/rtc/Makefile
>> @@ -73,6 +73,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..9fef82d
>> --- /dev/null
>> +++ b/drivers/rtc/rtc-pm8058.c
>> @@ -0,0 +1,487 @@
>> +/* Copyright (c) 2010, 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)
>
> Are there other bits in the ctrl register which are not touch by this driver?
> If not you could turn the read-modify-write operations changing the ctrl register
> into simple writes.
>
I will check this. I think this should be possible.
>> +
>> +#define NUM_8_BIT_RTC_REGS 0x4
>> +
>> +/**
>> + * struct pm8058_rtc - rtc driver internal structure
>> + * @rtc0 - rtc device for this driver
>> + * @rtc_irq - rtc irq number
>> + * @rtc_alarm_irq - rtc alarm irq number
>> + * @pm_chip - pointer to pm8058 parent structure
>> + */
>> +struct pm8058_rtc {
>> + struct rtc_device *rtc0;
> Is there a reason for the '0'?
No specific reason for this. I will remove the '0'.
>> + int rtc_irq;
>> + int rtc_alarm_irq;
>> + struct pm8058_chip *pm_chip;
>> +};
>> +
>> +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;
>> + }
>> + }
> Hm, I don't know the hardware, but I would assume that
> pm8058_read(rtc_dd->pm_chip, base, rtc_val, NUM_8_BIT_RTC_REGS) should work as well.
I did try this earlier but the RTC register does not get updated/return
a correct 32-bit value. It needs indivisual 8-bit SBI operations.
>> +
>> + 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;
>> + }
>> + }
>> +
> Same here
>> + 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_rtc0_set_time(struct device *dev, struct rtc_time *tm)
>> +{
>> + int rc;
>> + unsigned long secs = 0;
>> + u8 value[4], reg = 0, alarm_enabled = 0, ctrl_reg = 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;
>> +
>> + pr_debug("Seconds value to be written to RTC = %lu\n", secs);
>> +
>> + /* Disable alarm before updating RTC */
>> + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL,&ctrl_reg, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 read failed\n");
> dev_err instead of pr_err. The other pr_err should also be replaced.
Ok
>> + return rc;
>> + }
>> +
>> + if (ctrl_reg& PM8058_RTC_ALARM_ENABLE) {
>> + alarm_enabled = 1;
>> + ctrl_reg&= ~PM8058_RTC_ALARM_ENABLE;
>> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
>> + &ctrl_reg, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 write failed\n");
>> + return rc;
>> + }
>> + }
>> +
>> + /* Write Byte[1], Byte[2], Byte[3], Byte[0] */
>> + reg = 0;
>> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_WRITE_BASE,®, 1);
>> + if (rc< 0) {
>> + pr_err("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) {
>> + pr_err("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) {
>> + pr_err("PM8058 write failed\n");
>> + return rc;
>> + }
>> +
>> + if (alarm_enabled) {
>> + ctrl_reg |= PM8058_RTC_ALARM_ENABLE;
>> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,
>> + &ctrl_reg, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 write failed\n");
>> + return rc;
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int
>> +pm8058_rtc0_read_time(struct device *dev, struct rtc_time *tm)
>> +{
>> + int rc;
>> + u8 value[4], reg;
>> + unsigned long secs = 0;
>> + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
>> +
>> + rc = pm8058_rtc_read_bytes(rtc_dd, value, PM8058_RTC_READ_BASE);
>> + if (rc< 0) {
>> + pr_err("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,®, 1);
>> + if (rc< 0) {
>> + pr_err("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) {
>> + pr_err("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) {
>> + pr_err("Invalid time read from PMIC8058\n");
>> + return rc;
>> + }
>> +
>> + pr_debug("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_rtc0_set_alarm(struct device *dev, struct rtc_wkalrm *alarm)
>> +{
>> + int rc;
>> + u8 value[4], reg;
>> + unsigned long secs = 0;
> No need to initialize secs here.
Ok
>> + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
>> +
>> + /* Check if a alarm is valid */
>> + rc = rtc_valid_tm(&alarm->time);
>> + if (rc< 0) {
>> + pr_err("Alarm time invalid\n");
>> + return -EINVAL;
>> + }
> The upper layer will already check if alarm->time is valid, so there is no need to
> check it here again.
I see. I will remove this.
>> +
>> + 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) {
>> + pr_err("Alarm could not be set\n");
>> + return rc;
>> + }
>> +
>> + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL,®, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 read failed\n");
>> + return rc;
>> + }
>> +
>> + reg = (alarm->enabled) ? (reg | PM8058_RTC_ALARM_ENABLE) :
>> + (reg& ~PM8058_RTC_ALARM_ENABLE);
>> +
>> + rc = pm8058_write(rtc_dd->pm_chip, PM8058_RTC_CTRL,®, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 write failed\n");
>> + return rc;
>> + }
>> +
>> + pr_debug("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_rtc0_read_alarm(struct device *dev, struct rtc_wkalrm *alarm)
>> +{
>> + int rc;
>> + u8 value[4], reg;
>> + unsigned long secs = 0;
> No need to initialize secs here.
Ok
>> + struct pm8058_rtc *rtc_dd = dev_get_drvdata(dev);
>> +
>> + /* Check if the alarm is enabled */
>> + rc = pm8058_read(rtc_dd->pm_chip, PM8058_RTC_CTRL,®, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 read failed\n");
>> + return rc;
>> + }
>> + alarm->enabled = !!(reg& PM8058_RTC_ALARM_ENABLE);
>> +
>> + rc = pm8058_rtc_read_bytes(rtc_dd, value,
>> + PM8058_RTC_ALARM_BASE);
>> + if (rc< 0) {
>> + pr_err("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) {
>> + pr_err("Invalid time read from PMIC8058\n");
>> + return rc;
>> + }
>> +
>> + pr_debug("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 struct rtc_class_ops pm8058_rtc0_ops = {
>> + .read_time = pm8058_rtc0_read_time,
>> + .set_alarm = pm8058_rtc0_set_alarm,
>> + .read_alarm = pm8058_rtc0_read_alarm,
>
> Implementig alarm_irq_enable might be a good idea
Ok, I'll try to add the other supported ops as well.
>
>> +};
>> +
>> +static irqreturn_t pm8058_alarm_trigger(int irq, void *dev_id)
>> +{
>> + unsigned long events = 0;
>> + struct pm8058_rtc *rtc_dd = dev_id;
>> +
>> + events = RTC_IRQF | RTC_AF;
>> + rtc_update_irq(rtc_dd->rtc0, 1, events);
>
> just rtc_update_irq(rtc_dd->rtc0, 1, RTC_IRQF | RTC_AF); should be fine
Ok
>
>> +
>> + pr_debug("Alarm Triggered !!\n");
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int __devinit pm8058_rtc_probe(struct platform_device *pdev)
>> +{
>> + int rc;
>> + u8 reg;
>> + struct pm8058_rtc *rtc_dd;
>> + struct pm8058_chip *pm_chip;
>> + struct pm8058_rtc_pdata *pdata = pdev->dev.platform_data;
>> +
>> + if (pdata == NULL) {
>> + dev_err(&pdev->dev, "Platform data absent!\n");
>> + return -ENXIO;
>> + }
> It might be a good idea to assume a default for rtc_write_enable if pdata is not set.
Yes, makes sense.
>> +
>> + 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_irq = platform_get_irq(pdev, 0);
> You don't seem to use this irq.
This was for the RTC tick interrupt. I will remove this code.
>
>> + rtc_dd->rtc_alarm_irq = platform_get_irq(pdev, 1);
>> + if (!rtc_dd->rtc_alarm_irq || !rtc_dd->rtc_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,®, 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,®, 1);
>> + if (rc< 0) {
>> + dev_err(&pdev->dev, "PM8058 write failed!\n");
>> + goto fail_rtc_enable;
>> + }
>> + }
>> +
>> + if (pdata->rtc_write_enable == true)
>> + pm8058_rtc0_ops.set_time = pm8058_rtc0_set_time,
>> +
>> + /* Register the RTC device */
>> + rtc_dd->rtc0 = rtc_device_register("pm8058_rtc0",&pdev->dev,
>> + &pm8058_rtc0_ops, THIS_MODULE);
>> + if (IS_ERR(rtc_dd->rtc0)) {
>> + dev_err(&pdev->dev, "%s: RTC registration failed (%ld)\n",
>> + __func__, PTR_ERR(rtc_dd->rtc0));
>> + rc = PTR_ERR(rtc_dd->rtc0);
>> + 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->rtc0);
>> +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->rtc0);
>> + 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);
>> +}
>> +
>> +static void __exit pm8058_rtc_exit(void)
>> +{
>> + platform_driver_unregister(&pm8058_rtc_driver);
>> +}
>> +
>> +module_init(pm8058_rtc_init);
>> +module_exit(pm8058_rtc_exit);
> module_{init,exit} should go right beneath the function they are referring to
Ok.
>
>> +
>> +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..51f7c0b
>> --- /dev/null
>> +++ b/include/linux/rtc/rtc-pm8058.h
>> @@ -0,0 +1,29 @@
>> +/* Copyright (c) 2010, 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;
>> +};
> Is there a technical reason why changing the rtc clocks time should be disabled or is
> this a policy based decision?
In some of our MSM/QSD designs, we have a single RTC shared by multiple
processors (other than the ones running Linux). Thus, the need to have a
non-writable RTC using this pdata.
>> +
>> +#endif /* __RTC_PM8058_H__ */
>
Thank you for the detailed review. I will make these changes in my next
patch.
--Anirudh
--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
On Fri, Nov 12, 2010 at 02:26:28PM +0530, Trilok Soni wrote:
> Hi Dmitry,
>
> On 11/12/2010 6:27 AM, Dmitry Torokhov wrote:
> > On Thu, Nov 11, 2010 at 05:30:21PM +0530, Trilok Soni wrote:
> >> Hi Dmitry,
> >>
> >> On 11/11/2010 12:51 PM, Dmitry Torokhov wrote:
> >>> Hi Trilkok,
> >>>
> >>> On Wed, Nov 10, 2010 at 06:17:59PM +0530, Trilok Soni wrote:
> >>>> 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.
> >>>>
> >>>
> >>> Why do we need to delay KEY_POWER reporting? Do we need to use high
> >>> resolution timers or regular timers would do as well? KEY_END
> >>> appears to be abused (you don't want to move your cursor to the end
> >>> of line, do you?). Also I wonder if header file should reside in
> >>> linux/mfd with the rest of pmic8058 components.
> >>
> >> Most of the time Mobile devices come with single physical key for
> >> POWER, which if pressed for less than 500ms (configurable) then it
> >> will only report KEY_END (which say locks the screen on mobile) and if
> >> it pressed more than 500ms then it will also report KEY_POWER event
> >> too, which will say display menu on your mobile for asking you to
> >> suspend/switch off/etc, operations.
> >>
> >
> > I see,. If you would have used KEY_SCREENLOCK iinstead of KEY_END I
> > would likely not ask this question ;)
> >
>
> KEY_SCRENNLOCK looks good, let me analyze the impact on userspace framework
> which I have. I will come back on this in a day.
>
> >> For the timers I can move from hrtimers to regular timers.
> >>
> >> For the header file, I can move them to include/linux/mfd too. No
> >> problem on that.
> >>
> >
> > I am not even sure we need to keep them in separate header files, but it
> > is up to you.
>
> Do you suggest that all the MFD sub-devices's platform data structures should come from single
> header file?
>
It is an option, depends on how many external headers are needed, etc.
When I looked at this particular file I got the feeling that it could be
folded together with the rest. I expect that the board code that will
specify platform resources will include every one of this sub-files
anyway. But maybe if I was presented with the combined header I'd say
"wow, thats too big"...
Like I said, it is up to you.
--
Dmitry
Hi Dmitry,
On 11/10/2010 6:18 PM, Trilok Soni wrote:
> From: Anirudh Ghayal <[email protected]>
>
> 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: Dmitry Torokhov <[email protected]>
> Signed-off-by: Anirudh Ghayal <[email protected]>
> ---
Any comments on this patch?
---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.
Hi Dmitry,
On 11/10/2010 6:17 PM, Trilok Soni wrote:
> Add Qualcomm PMIC8058 based keypad controller driver
> supporting upto 18x8 matrix configuration.
>
> Cc: Dmitry Torokhov <[email protected]>
> Signed-off-by: Trilok Soni <[email protected]>
> ---
> drivers/input/keyboard/Kconfig | 11 +
> drivers/input/keyboard/Makefile | 1 +
> drivers/input/keyboard/pmic8058-keypad.c | 769 ++++++++++++++++++++++++++++++
> include/linux/input/pmic8058-keypad.h | 58 +++
> 4 files changed, 839 insertions(+), 0 deletions(-)
> create mode 100644 drivers/input/keyboard/pmic8058-keypad.c
> create mode 100644 include/linux/input/pmic8058-keypad.h
Any comments on this patch?
---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.
Hi Anirudh,
> -----Original Message-----
> From: [email protected] [mailto:linux-input-
> [email protected]] On Behalf Of Trilok Soni
> Sent: Wednesday, November 10, 2010 6:18 PM
> To: [email protected]
> Cc: [email protected]; [email protected]; linux-arm-
> [email protected]; Anirudh Ghayal; Dmitry Torokhov
> Subject: [RFC v1 PATCH 5/6] input: pmic8058-othc: Add support for PM8058
> based OTHC
>
> From: Anirudh Ghayal <[email protected]>
>
> 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.
Could you help with the datasheet link if it is free.
>
> Cc: Dmitry Torokhov <[email protected]>
> Signed-off-by: Anirudh Ghayal <[email protected]>
> ---
> drivers/input/misc/Kconfig | 10 +
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/pmic8058-othc.c | 544
> +++++++++++++++++++++++++++++++++++
> include/linux/input/pmic8058-othc.h | 117 ++++++++
> 4 files changed, 672 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 aeb9165..df6097c 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -359,6 +359,16 @@ config INPUT_PMIC8058_PWRKEY
> To compile this driver as a module, choose M here: the
> module will be called pmic8058-pwrkey.
>
> +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.
> +
> 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 c4357a0..a713370 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -29,6 +29,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..78f157a
> --- /dev/null
> +++ b/drivers/input/misc/pmic8058-othc.c
> @@ -0,0 +1,544 @@
> +/* Copyright (c) 2010, 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/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;
> + 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, ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 write failed\n");
> + return rc;
> + }
> +
> + return rc;
> +}
> +EXPORT_SYMBOL(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);
> +
> + if (dd->othc_sw_state == false) {
> + dd->othc_sw_state = true;
> + input_report_key(dd->othc_ipd, KEY_MEDIA, 1);
> + } else if (dd->othc_sw_state == true) {
> + dd->othc_sw_state = false;
> + input_report_key(dd->othc_ipd, KEY_MEDIA, 0);
> + }
> + 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));
> +
> + if (dd->othc_ir_state == false) {
> + dd->othc_ir_state = true;
> + input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 1);
> + } else {
> + dd->othc_ir_state = false;
> + input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 0);
> + }
> +
> + input_sync(dd->othc_ipd);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int pm8058_configure_othc(struct pm8058_othc *dd)
> +{
> + int rc;
> + u8 reg, value;
> + u32 value1;
> + u16 base_addr = dd->othc_base;
> + struct othc_hsed_config *hsed_config = dd->othc_pdata->hsed_config;
> +
> + /* Control Register 1*/
> + rc = pm8058_read(dd->pm_chip, base_addr, ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> +
> + /* Control register 2*/
> + rc = pm8058_read(dd->pm_chip, base_addr + 1, ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> +
> + /* Control register 3 */
> + rc = pm8058_read(dd->pm_chip, base_addr + 2 , ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int
If this is called only at init it can also be a devinit ?
> +pm8058_othc_configure_hsed(struct pm8058_othc *dd, struct platform_device
> *pd)
> +{
> + int rc;
> + struct input_dev *ipd;
> + struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data;
> + 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);
> + }
Not a comment. However I did not understand why the status at boot is required here.
> +
> + rc = request_any_context_irq(dd->othc_irq_ir, pm8058_nc_ir,
> + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
> + "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 | IRQF_ONESHOT,
> + "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);
Unregister and then falling back to free may not be what you intended.
> + 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 A0 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);
> +}
> +
> +static void __exit pm8058_othc_exit(void)
> +{
> + platform_driver_unregister(&pm8058_othc_driver);
> +}
> +
> +module_init(pm8058_othc_init);
> +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..341ac7c
> --- /dev/null
> +++ b/include/linux/input/pmic8058-othc.h
> @@ -0,0 +1,117 @@
> +/* Copyright (c) 2010, 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.0.2
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-input" in
> the body of a message to [email protected]
> More majordomo info at http://vger.kernel.org/majordomo-info.html
Hi Shubhrajyoti,
On 11/16/2010 11:06 AM, Datta, Shubhrajyoti wrote:
> Hi Anirudh,
>
>> -----Original Message-----
>> From: [email protected] [mailto:linux-input-
>> [email protected]] On Behalf Of Trilok Soni
>> Sent: Wednesday, November 10, 2010 6:18 PM
>> To: [email protected]
>> Cc: [email protected]; [email protected]; linux-arm-
>> [email protected]; Anirudh Ghayal; Dmitry Torokhov
>> Subject: [RFC v1 PATCH 5/6] input: pmic8058-othc: Add support for PM8058
>> based OTHC
>>
>> From: Anirudh Ghayal <[email protected]>
>>
>> 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.
> Could you help with the datasheet link if it is free.
>
datasheets are not available for open access.
>> +
>> +static int
> If this is called only at init it can also be a devinit ?
Ok.
>> + 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);
>> + }
> Not a comment. However I did not understand why the status at boot is required here.
We need to report right status when the system boots up with headset inserted.
>> +
>> +fail_sw_irq:
>> + free_irq(dd->othc_irq_ir, dd);
>> +fail_ir_irq:
>> + input_unregister_device(ipd);
> Unregister and then falling back to free may not be what you intended.
>
Nope. Please see we are making othc_ipd = NULL, so everything should be fine.
Thanks for the review comments.
--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
On Wed, Nov 10, 2010 at 03:30:36PM -0800, Dmitry Torokhov wrote:
> On Thu, Nov 11, 2010 at 02:33:45AM +0800, Eric Miao wrote:
> > On Wed, Nov 10, 2010 at 8:47 PM, Trilok Soni <[email protected]> wrote:
> > > Some keyboard controller have support for more than
> > > 16 columns and rows.
> > >
> > > Cc: Dmitry Torokhov <[email protected]>
> > > Cc: Eric Miao <[email protected]>
> > > Signed-off-by: Trilok Soni <[email protected]>
> > > ---
> > > ?include/linux/input/matrix_keypad.h | ? ?8 ++++----
> > > ?1 files changed, 4 insertions(+), 4 deletions(-)
> > >
> > > diff --git a/include/linux/input/matrix_keypad.h b/include/linux/input/matrix_keypad.h
> > > index 80352ad..d80845e 100644
> > > --- a/include/linux/input/matrix_keypad.h
> > > +++ b/include/linux/input/matrix_keypad.h
> > > @@ -4,11 +4,11 @@
> > > ?#include <linux/types.h>
> > > ?#include <linux/input.h>
> > >
> > > -#define MATRIX_MAX_ROWS ? ? ? ? ? ? ? ?16
> > > -#define MATRIX_MAX_COLS ? ? ? ? ? ? ? ?16
> > > +#define MATRIX_MAX_ROWS ? ? ? ? ? ? ? ?18
> > > +#define MATRIX_MAX_COLS ? ? ? ? ? ? ? ?18
> > >
> > > -#define KEY(row, col, val) ? ? ((((row) & (MATRIX_MAX_ROWS - 1)) << 24) |\
> > > - ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(((col) & (MATRIX_MAX_COLS - 1)) << 16) |\
> > > +#define KEY(row, col, val) ? ? ((((row) % (MATRIX_MAX_ROWS)) << 24) |\
> > > + ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(((col) % (MATRIX_MAX_COLS)) << 16) |\
> > > ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (val & 0xffff))
> > >
> >
> > Or maybe we can solve this completely by introducing something like:
> >
> > struct matrix_keycode {
> > int row;
> > int col;
> > int value;
>
> Though that triples the space needed to store initial keymaps.
>
Looking at it I think we should simply bump up max cols/rows to 32 and
be done with it. It will grow the matrix keypad structure by a few bytes
but nothing drastic.
--
Dmitry
On Wed, Nov 10, 2010 at 06:17:57PM +0530, Trilok Soni wrote:
> Add Qualcomm PMIC8058 based keypad controller driver
> supporting upto 18x8 matrix configuration.
>
Looks good, just a couple of nitpicks:
> +struct pmic8058_kp {
> + const struct pmic8058_keypad_data *pdata;
> + struct input_dev *input;
> + int key_sense_irq;
> + int key_stuck_irq;
> +
> + unsigned short *keycodes;
Since the size of keycode table is constant there is no need to allocate
it separately (and even if it was variable you still could move it to
the end of the structure and allocate together).
> +
> +static int pmic8058_detect_ghost_keys(struct pmic8058_kp *kp, u16 *new_state)
bool
> +{
> + 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 1;
true
> + }
> + }
> + check |= row_state;
> + }
> + return 0;
false
> +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;
> + unsigned short *keycodes;
> + 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 -EFAULT;
EFAULT is really odd, maybe -EINVAL?
> + }
> +
> + /* Check PMIC8058 version. A0 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->rows_gpio_start < 0 || pdata->cols_gpio_start < 0) {
> + dev_err(&pdev->dev, "invalid gpio_start 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");
Mixing style (logical || either in front of expression or after), please use
same style (and I prefer after).
> + 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;
> +
> + keycodes = kzalloc(PM8058_MATRIX_MAX_SIZE * sizeof(*keycodes),
> + GFP_KERNEL);
> + if (!keycodes) {
> + rc = -ENOMEM;
> + goto err_alloc_mem;
> + }
> +
> + platform_set_drvdata(pdev, kp);
> +
> + kp->pdata = pdata;
> + kp->dev = &pdev->dev;
> + kp->keycodes = keycodes;
> + 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;
> + }
> +
> + if (pdata->input_name)
> + kp->input->name = pdata->input_name;
> + else
> + kp->input->name = "PMIC8058 keypad";
> +
> + if (pdata->input_phys_device)
> + kp->input->phys = pdata->input_phys_device;
> + else
> + kp->input->phys = "pmic8058_keypad/input0";
kp->input->phys = pdata->input_phys_device ?: "pmic8058_keypad/input0";
Is shorter.
Thanks.
--
Dmitry
Hi Dimitry,
Please me know your comments this patch.
Thank you,
Anirudh
On 11/16/2010 12:06 PM, Trilok Soni wrote:
>
> Hi Shubhrajyoti,
>
> On 11/16/2010 11:06 AM, Datta, Shubhrajyoti wrote:
>> Hi Anirudh,
>>
>>> -----Original Message-----
>>> From: [email protected] [mailto:linux-input-
>>> [email protected]] On Behalf Of Trilok Soni
>>> Sent: Wednesday, November 10, 2010 6:18 PM
>>> To: [email protected]
>>> Cc: [email protected]; [email protected]; linux-arm-
>>> [email protected]; Anirudh Ghayal; Dmitry Torokhov
>>> Subject: [RFC v1 PATCH 5/6] input: pmic8058-othc: Add support for PM8058
>>> based OTHC
>>>
>>> From: Anirudh Ghayal<[email protected]>
>>>
>>> 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.
>> Could you help with the datasheet link if it is free.
>>
>
> datasheets are not available for open access.
>
>>> +
>>> +static int
>> If this is called only at init it can also be a devinit ?
>
> Ok.
>
>>> + 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);
>>> + }
>> Not a comment. However I did not understand why the status at boot is required here.
>
> We need to report right status when the system boots up with headset inserted.
>
>>> +
>>> +fail_sw_irq:
>>> + free_irq(dd->othc_irq_ir, dd);
>>> +fail_ir_irq:
>>> + input_unregister_device(ipd);
>> Unregister and then falling back to free may not be what you intended.
>>
>
> Nope. Please see we are making othc_ipd = NULL, so everything should be fine.
>
> Thanks for the review comments.
>
--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
Hi Peter,
>
>>> +
>>> +/**
>>> + * struct pmic8058_led - per led data
>>> + * @name - name of the led
>>> + * @default_trigger - default trigger which needs to e attached
>>> + * @max_brightness - maximum brightness level supported by the led
>>> + * @id - supported led id
>>> + */
>>> +struct pmic8058_led {
>>> + const char *name;
>>> + const char *default_trigger;
>>> + unsigned max_brightness;
>> Should max_brightness not rather be hardcoded in the driver? As far as I can tell it
>> depend on the hardware and is 4 bits wide for flash and bl leds and 5 bits for the
>> others.
>>> + int id;
>>
>> enum pmic8058_leds instead of int
>
> Ack.
>
>>> +struct pmic8058_leds_platform_data {
>>> + int num_leds;
>> size_t
>
> Ack.
>
>>> + struct pmic8058_led *leds;
>>> +};
>>
>>
>> If max_brightness is hardcoded in the driver you can reuse "struct led_info" and
>> "struct struct led_platform_data" instead of adding your own structs.
>
I couldn't remove these pmic8058_led structure due to the "enum pmic8058_led id" member
info which I need from every led. This can be removed completely only if I abuse
the "flags" parameter in struct led_info to pass the led id. Let me know what you think.
---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.
Hi Trilok,
On Wed, Nov 10, 2010 at 06:17:57PM +0530, Trilok Soni wrote:
> Add Qualcomm PMIC8058 based keypad controller driver
> supporting upto 18x8 matrix configuration.
>
Looks very good, just a couple of small things:
> +
> +#include <linux/input/pmic8058-keypad.h>
Move to MFD directory with the rest of pmic8058 definitions?
> + */
> +struct pmic8058_kp {
> + const struct pmic8058_keypad_data *pdata;
> + struct input_dev *input;
> + int key_sense_irq;
> + int key_stuck_irq;
> +
> + unsigned short *keycodes;
I'd pull the keycodes into this structure (at the end) so it can be
allocated in one shot. Hmm it even appears to be constant-sized. So just
declare it right here and be done with it.
> +
> +static int pmic8058_detect_ghost_keys(struct pmic8058_kp *kp, u16 *new_state)
bool
> +{
> + 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 1;
true
> + }
> + }
> + check |= row_state;
> + }
> + return 0;
false
> +
> +static int pmic8058_kpd_init(struct pmic8058_kp *kp)
> +{
> + int bits, rc, cycles;
> + u8 scan_val = 0, ctrl_val = 0;
> + static u8 row_bits[] = {
const?
> + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7,
> + };
> +
> +}
> +
> +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;
> +
> + enable_irq(kp->key_sense_irq);
> + enable_irq(kp->key_stuck_irq);
> +
> + 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;
> +
> + disable_irq(kp->key_sense_irq);
> + disable_irq(kp->key_stuck_irq);
> +
> + 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);
> +}
> +
You need to protect suspend/resume from racing with open_close. Take
dev->mutex and act depending on whether there are users of the device.
> + if (pdata->rows_gpio_start < 0 || pdata->cols_gpio_start < 0) {
> + dev_err(&pdev->dev, "invalid gpio_start platform data\n");
> + return -EINVAL;
These are declared as unsigned. Hmm, doesn't sparse catch it?
Thanks.
--
Dmitry
Hi Dmitry
On 12/6/2010 11:44 PM, Dmitry Torokhov wrote:
> Hi Trilok,
>
> On Wed, Nov 10, 2010 at 06:17:57PM +0530, Trilok Soni wrote:
>> Add Qualcomm PMIC8058 based keypad controller driver
>> supporting upto 18x8 matrix configuration.
>>
>
> Looks very good, just a couple of small things:
Thanks for reviewing same revision of the patch again :)
>
>> +
>> +#include <linux/input/pmic8058-keypad.h>
>
> Move to MFD directory with the rest of pmic8058 definitions?
As discussed earlier we can either have one big header file having all of the
sub-device(s) or have separate file for each sub-device driver, I need to check
which one would be better but I will atleast move pmic8058-keypad.h header
to "mfd" directory, as these patch series is in RFC state, we can have final
changes like along with when core-driver gets submitted for review.
>
>> + */
>> +struct pmic8058_kp {
>> + const struct pmic8058_keypad_data *pdata;
>> + struct input_dev *input;
>> + int key_sense_irq;
>> + int key_stuck_irq;
>> +
>> + unsigned short *keycodes;
>
> I'd pull the keycodes into this structure (at the end) so it can be
> allocated in one shot. Hmm it even appears to be constant-sized. So just
> declare it right here and be done with it.
Done. You had already commented on it during the 1st review :)
>
>> +
>> +static int pmic8058_detect_ghost_keys(struct pmic8058_kp *kp, u16 *new_state)
>
> bool
Done.
>
>> +{
>> + 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 1;
>
> true
>
>> + }
>> + }
>> + check |= row_state;
>> + }
>> + return 0;
>
> false
Done.
>
>> +
>> +static int pmic8058_kpd_init(struct pmic8058_kp *kp)
>> +{
>> + int bits, rc, cycles;
>> + u8 scan_val = 0, ctrl_val = 0;
>> + static u8 row_bits[] = {
>
> const?
>
>> + 0, 1, 2, 3, 4, 4, 5, 5, 6, 6, 6, 7, 7, 7,
>> + };
Sure.
>> +
>> +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);
>> +}
>> +
>
> You need to protect suspend/resume from racing with open_close. Take
> dev->mutex and act depending on whether there are users of the device.
Sure.
>
>> + if (pdata->rows_gpio_start < 0 || pdata->cols_gpio_start < 0) {
>> + dev_err(&pdev->dev, "invalid gpio_start platform data\n");
>> + return -EINVAL;
>
> These are declared as unsigned. Hmm, doesn't sparse catch it?
I will fix 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.
On Tue, Dec 07, 2010 at 02:52:11PM +0530, Trilok Soni wrote:
> Hi Dmitry
>
> On 12/6/2010 11:44 PM, Dmitry Torokhov wrote:
> > Hi Trilok,
> >
> > On Wed, Nov 10, 2010 at 06:17:57PM +0530, Trilok Soni wrote:
> >> Add Qualcomm PMIC8058 based keypad controller driver
> >> supporting upto 18x8 matrix configuration.
> >>
> >
> > Looks very good, just a couple of small things:
>
> Thanks for reviewing same revision of the patch again :)
>
Oops ;) Did I need to review something else from you then? I have a
nagging feeling that I have a patch outstanding...
--
Dmitry
Hi Dmitry,
On 12/7/2010 3:00 PM, Dmitry Torokhov wrote:
>
> Oops ;) Did I need to review something else from you then? I have a
> nagging feeling that I have a patch outstanding...
>
You need to review PMIC8058 OTHC patch.
https://patchwork.kernel.org/patch/314092/
---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.
Hi Trilok,
On Wed, Nov 10, 2010 at 06:18:00PM +0530, Trilok Soni wrote:
> From: Anirudh Ghayal <[email protected]>
>
> 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: Dmitry Torokhov <[email protected]>
> Signed-off-by: Anirudh Ghayal <[email protected]>
> ---
> drivers/input/misc/Kconfig | 10 +
> drivers/input/misc/Makefile | 1 +
> drivers/input/misc/pmic8058-othc.c | 544 +++++++++++++++++++++++++++++++++++
> include/linux/input/pmic8058-othc.h | 117 ++++++++
> 4 files changed, 672 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 aeb9165..df6097c 100644
> --- a/drivers/input/misc/Kconfig
> +++ b/drivers/input/misc/Kconfig
> @@ -359,6 +359,16 @@ config INPUT_PMIC8058_PWRKEY
> To compile this driver as a module, choose M here: the
> module will be called pmic8058-pwrkey.
>
> +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.
> +
> 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 c4357a0..a713370 100644
> --- a/drivers/input/misc/Makefile
> +++ b/drivers/input/misc/Makefile
> @@ -29,6 +29,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..78f157a
> --- /dev/null
> +++ b/drivers/input/misc/pmic8058-othc.c
> @@ -0,0 +1,544 @@
> +/* Copyright (c) 2010, 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/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;
> + struct pmic8058_othc_config_pdata *othc_pdata;
const?
> + 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, ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 write failed\n");
> + return rc;
> + }
> +
> + return rc;
> +}
> +EXPORT_SYMBOL(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) {
Why do we even bind to devices that are not 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);
I am not quite sure whether this locking helps anything... You do
protect the check by condition can change once you leave the protected
section since both IRQ handles and timer can run simultaneously...
> +
> + if (dd->othc_sw_state == false) {
> + dd->othc_sw_state = true;
> + input_report_key(dd->othc_ipd, KEY_MEDIA, 1);
> + } else if (dd->othc_sw_state == true) {
> + dd->othc_sw_state = false;
> + input_report_key(dd->othc_ipd, KEY_MEDIA, 0);
> + }
> + 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));
> +
> + if (dd->othc_ir_state == false) {
> + dd->othc_ir_state = true;
> + input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 1);
> + } else {
> + dd->othc_ir_state = false;
> + input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 0);
> + }
> +
> + input_sync(dd->othc_ipd);
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 pm8058_configure_othc(struct pm8058_othc *dd)
> +{
> + int rc;
> + u8 reg, value;
> + u32 value1;
> + u16 base_addr = dd->othc_base;
> + struct othc_hsed_config *hsed_config = dd->othc_pdata->hsed_config;
> +
> + /* Control Register 1*/
> + rc = pm8058_read(dd->pm_chip, base_addr, ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> +
> + /* Control register 2*/
> + rc = pm8058_read(dd->pm_chip, base_addr + 1, ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> +
> + /* Control register 3 */
> + rc = pm8058_read(dd->pm_chip, base_addr + 2 , ®, 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, ®, 1);
> + if (rc < 0) {
> + pr_err("PM8058 read failed\n");
> + return rc;
> + }
> +
> + return 0;
> +}
> +
> +static int
> +pm8058_othc_configure_hsed(struct pm8058_othc *dd, struct platform_device *pd)
> +{
> + int rc;
> + struct input_dev *ipd;
> + struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data;
const?
> + struct othc_hsed_config *hsed_config = pdata->hsed_config;
And here as well?
> +
> + 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);
What exactly this button is supposed to do? I do not think KEY_MEDIA is
the one you need here.
> +
> + 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 | IRQF_ONESHOT,
> + "pm8058_othc_ir", dd);
Hmm, non-threaded IRQs do not support IRQF_ONESHOT, do they? BTW, is
oneshot really needed here?
> + 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 | IRQF_ONESHOT,
> + "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 A0 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;
> +}
> +
Thanks.
--
Dmitry
Hi Dimitry,
Thanks for your comments.
On 12/7/2010 3:34 PM, Dmitry Torokhov wrote:
> Hi Trilok,
>
> On Wed, Nov 10, 2010 at 06:18:00PM +0530, Trilok Soni wrote:
>> From: Anirudh Ghayal<[email protected]>
>>
>> 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: Dmitry Torokhov<[email protected]>
>> Signed-off-by: Anirudh Ghayal<[email protected]>
>> ---
>> drivers/input/misc/Kconfig | 10 +
>> drivers/input/misc/Makefile | 1 +
>> drivers/input/misc/pmic8058-othc.c | 544 +++++++++++++++++++++++++++++++++++
>> include/linux/input/pmic8058-othc.h | 117 ++++++++
>> 4 files changed, 672 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 aeb9165..df6097c 100644
>> --- a/drivers/input/misc/Kconfig
>> +++ b/drivers/input/misc/Kconfig
>> @@ -359,6 +359,16 @@ config INPUT_PMIC8058_PWRKEY
>> To compile this driver as a module, choose M here: the
>> module will be called pmic8058-pwrkey.
>>
>> +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.
>> +
>> 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 c4357a0..a713370 100644
>> --- a/drivers/input/misc/Makefile
>> +++ b/drivers/input/misc/Makefile
>> @@ -29,6 +29,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..78f157a
>> --- /dev/null
>> +++ b/drivers/input/misc/pmic8058-othc.c
>> @@ -0,0 +1,544 @@
>> +/* Copyright (c) 2010, 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/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;
>> + struct pmic8058_othc_config_pdata *othc_pdata;
>
> const?
Ok.
>
>> + 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,®, 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,®, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 write failed\n");
>> + return rc;
>> + }
>> +
>> + return rc;
>> +}
>> +EXPORT_SYMBOL(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) {
>
> Why do we even bind to devices that are not OTHC_MICBIAS_HSED?
On PMIC 8058 we have 3 MIC_BIAS lines and all 3 have the capability to
support HSED OTHC controller. So, I register all the 3 BIAS lines,
though only the ones which are enabled for HSED regitser as input
devices. Also, I export an API pm8058_micbias_enable (above) which
allows to configure the non-HSED lines as regualr MIC_BIAS lines.
>
>> + 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);
>
> I am not quite sure whether this locking helps anything... You do
> protect the check by condition can change once you leave the protected
> section since both IRQ handles and timer can run simultaneously...
Sorry, I didn't quite get your concern. Here, only the timer updates the
varible and the int. handler checks for the condition and returns. As
these both can occur in parallel we need a lock.
>
>> +
>> + if (dd->othc_sw_state == false) {
>> + dd->othc_sw_state = true;
>> + input_report_key(dd->othc_ipd, KEY_MEDIA, 1);
>> + } else if (dd->othc_sw_state == true) {
>> + dd->othc_sw_state = false;
>> + input_report_key(dd->othc_ipd, KEY_MEDIA, 0);
>> + }
>> + 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));
>> +
>> + if (dd->othc_ir_state == false) {
>> + dd->othc_ir_state = true;
>> + input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 1);
>> + } else {
>> + dd->othc_ir_state = false;
>> + input_report_key(dd->othc_ipd, SW_HEADPHONE_INSERT, 0);
>> + }
>> +
>> + input_sync(dd->othc_ipd);
>
> 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);
Simpler :).
>
>> +
>> + return IRQ_HANDLED;
>> +}
>> +
>> +static int pm8058_configure_othc(struct pm8058_othc *dd)
>> +{
>> + int rc;
>> + u8 reg, value;
>> + u32 value1;
>> + u16 base_addr = dd->othc_base;
>> + struct othc_hsed_config *hsed_config = dd->othc_pdata->hsed_config;
>> +
>> + /* Control Register 1*/
>> + rc = pm8058_read(dd->pm_chip, base_addr,®, 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,®, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 read failed\n");
>> + return rc;
>> + }
>> +
>> + /* Control register 2*/
>> + rc = pm8058_read(dd->pm_chip, base_addr + 1,®, 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,®, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 read failed\n");
>> + return rc;
>> + }
>> +
>> + /* Control register 3 */
>> + rc = pm8058_read(dd->pm_chip, base_addr + 2 ,®, 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,®, 1);
>> + if (rc< 0) {
>> + pr_err("PM8058 read failed\n");
>> + return rc;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int
>> +pm8058_othc_configure_hsed(struct pm8058_othc *dd, struct platform_device *pd)
>> +{
>> + int rc;
>> + struct input_dev *ipd;
>> + struct pmic8058_othc_config_pdata *pdata = pd->dev.platform_data;
>
> const?
>
>> + struct othc_hsed_config *hsed_config = pdata->hsed_config;
>
> And here as well?
>
Sure.
>> +
>> + 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);
>
> What exactly this button is supposed to do? I do not think KEY_MEDIA is
> the one you need here.
This key acts as send/end key while in call and play/pause in case of
audio playback. So we used KEY_MEDIA as a generic media key. Could you
please suggest what can be used here?
>
>
>> +
>> + 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 | IRQF_ONESHOT,
>> + "pm8058_othc_ir", dd);
>
> Hmm, non-threaded IRQs do not support IRQF_ONESHOT, do they? BTW, is
> oneshot really needed here?
Right, I donot need IRQF_ONESHOT here.
>
>> + 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 | IRQF_ONESHOT,
>> + "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 A0 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;
>> +}
>> +
>
> Thanks.
>
--
Sent by a consultant of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum.
On 12/06/2010 02:44 PM, Trilok Soni wrote:
> Hi Peter,
>
>>
>>>> +
>>>> +/**
>>>> + * struct pmic8058_led - per led data
>>>> + * @name - name of the led
>>>> + * @default_trigger - default trigger which needs to e attached
>>>> + * @max_brightness - maximum brightness level supported by the led
>>>> + * @id - supported led id
>>>> + */
>>>> +struct pmic8058_led {
>>>> + const char *name;
>>>> + const char *default_trigger;
>>>> + unsigned max_brightness;
>>> Should max_brightness not rather be hardcoded in the driver? As far as I can tell it
>>> depend on the hardware and is 4 bits wide for flash and bl leds and 5 bits for the
>>> others.
>>>> + int id;
>>>
>>> enum pmic8058_leds instead of int
>>
>> Ack.
>>
>>>> +struct pmic8058_leds_platform_data {
>>>> + int num_leds;
>>> size_t
>>
>> Ack.
>>
>>>> + struct pmic8058_led *leds;
>>>> +};
>>>
>>>
>>> If max_brightness is hardcoded in the driver you can reuse "struct led_info" and
>>> "struct struct led_platform_data" instead of adding your own structs.
>>
>
> I couldn't remove these pmic8058_led structure due to the "enum pmic8058_led id" member
> info which I need from every led. This can be removed completely only if I abuse
> the "flags" parameter in struct led_info to pass the led id. Let me know what you think.
>
> ---Trilok Soni
>
Hi
I think that would be ok, other drivers seem to do the same.
- Lars
Hi Peter,
>>>>
>>>> If max_brightness is hardcoded in the driver you can reuse "struct led_info" and
>>>> "struct struct led_platform_data" instead of adding your own structs.
>>>
>>
>> I couldn't remove these pmic8058_led structure due to the "enum pmic8058_led id" member
>> info which I need from every led. This can be removed completely only if I abuse
>> the "flags" parameter in struct led_info to pass the led id. Let me know what you think.
>
> Hi
>
> I think that would be ok, other drivers seem to do the same.
Thanks. I will use "flags" parameter for "id" then and submit the re-worked version as V2.
---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.