Patchwork [U-Boot,V3,4/9] drivers/misc: add stmpe2401 port extender and keypad controller

login
register
mail settings
Submitter Alessandro Rubini
Date Dec. 5, 2009, 12:39 p.m.
Message ID <d20fb94714b04ffbb9c2153953bfe0fe8e39384a.1260016056.git.rubini@unipv.it>
Download mbox | patch
Permalink /patch/71688/
State Superseded
Delegated to: Albert ARIBAUD
Headers show

Comments

Alessandro Rubini - Dec. 5, 2009, 12:39 p.m.
From: Alessandro Rubini <rubini@unipv.it>

This driver is an i2c device acting as a port extender. Since
the keypad can be configured to act on specific row and column lines,
the specific setup is passed by the board file.  This is used by
the Nomadik nhk8815, through a later patch in this series.

Signed-off-by: Alessandro Rubini <rubini@unipv.it>
Acked-by: Andrea Gallo <andrea.gallo@stericsson.com>
---
 drivers/misc/Makefile    |    1 +
 drivers/misc/stmpe2401.c |  191 ++++++++++++++++++++++++++++++++++++++++++++++
 include/stmpe2401.h      |   66 ++++++++++++++++
 3 files changed, 258 insertions(+), 0 deletions(-)
 create mode 100644 drivers/misc/stmpe2401.c
 create mode 100644 include/stmpe2401.h

Patch

diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index f6df60f..76c009a 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -30,6 +30,7 @@  COBJS-$(CONFIG_DS4510)  += ds4510.o
 COBJS-$(CONFIG_FSL_LAW) += fsl_law.o
 COBJS-$(CONFIG_NS87308) += ns87308.o
 COBJS-$(CONFIG_STATUS_LED) += status_led.o
+COBJS-$(CONFIG_STMPE2401) += stmpe2401.o
 COBJS-$(CONFIG_TWL4030_LED) += twl4030_led.o
 
 COBJS	:= $(COBJS-y)
diff --git a/drivers/misc/stmpe2401.c b/drivers/misc/stmpe2401.c
new file mode 100644
index 0000000..f347d07
--- /dev/null
+++ b/drivers/misc/stmpe2401.c
@@ -0,0 +1,191 @@ 
+/*
+ * board/st/nhk8815/egpio.c: extended gpio as found on nhk8815 board
+ *
+ * Copyright 2009 Alessandro Rubini <rubini@unipv.it>
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+#include <common.h>
+#include <i2c.h>
+#include <stmpe2401.h>
+
+/*
+ * First, an interface to set and read registers, used in this file as well
+ */
+int pe_getreg(int addr, int reg)
+{
+	unsigned char val8 = reg;
+	int ret;
+
+	ret = i2c_read(addr, reg, 1 /* len */, &val8, 1);
+	if (ret < 0)
+		return ret;
+	return val8;
+}
+
+int pe_setreg(int addr, int reg, int val)
+{
+	unsigned char val8 = val;
+
+	return i2c_write(addr, reg, 1, &val8, 1);
+}
+
+/*
+ * Generic higher-level GPIO interface
+ */
+int pe_gpio_af(int addr, int pin, int af)
+{
+	int regval;
+
+	regval = pe_getreg(addr, PE_GPIO_AFR(pin));
+	if (regval < 0)
+		return regval;
+	regval &= ~PE_GPIO_AF_MASK(pin);
+	regval |= af << PE_GPIO_AF_SHIFT(pin);
+	return pe_setreg(addr, PE_GPIO_AFR(pin), regval);
+}
+
+int pe_gpio_dir(int addr, int pin, int dir)
+{
+	int regval;
+
+	/* 0 == input, 1 == output */
+	regval = pe_getreg(addr, PE_GPIO_GPDR(pin));
+	if (regval < 0)
+		return regval;
+	regval &= ~PE_GPIO_MASK(pin);
+	if (dir)
+		regval |= PE_GPIO_MASK(pin);
+	return pe_setreg(addr, PE_GPIO_GPDR(pin), regval);
+}
+
+int pe_gpio_pud(int addr, int pin, int pu, int pd)
+{
+	int regval, mask = PE_GPIO_MASK(pin);
+
+	/* change pullup */
+	regval = pe_getreg(addr, PE_GPIO_GPPUR(pin));
+	if (regval < 0)
+		return regval;
+	if (pu)
+		regval |= mask;
+	else
+		regval &= ~mask;
+	regval = pe_setreg(addr, PE_GPIO_GPPUR(pin), regval);
+	if (regval < 0)
+		return regval;
+
+	/* change pulldown */
+	regval = pe_getreg(addr, PE_GPIO_GPPDR(pin));
+	if (regval < 0)
+		return regval;
+	if (pu)
+		regval |= mask;
+	else
+		regval &= ~mask;
+	regval = pe_setreg(addr, PE_GPIO_GPPDR(pin), regval);
+	if (regval < 0)
+		return regval;
+
+	return 0;
+}
+
+int pe_gpio_set(int addr, int pin, int val)
+{
+	int reg;
+
+	if (val)
+		reg = PE_GPIO_GPSR(pin);
+	else
+		reg = PE_GPIO_GPCR(pin);
+
+	return pe_setreg(addr, reg, PE_GPIO_MASK(pin));
+}
+
+int pe_gpio_get(int addr, int pin)
+{
+	int regval;
+
+	regval = pe_getreg(addr, PE_GPIO_GPMR(pin));
+	if (regval < 0)
+		return regval;
+	return (regval & PE_GPIO_MASK(pin)) ? 1 : 0;
+}
+
+/*
+ * Generic higher-level keypad interface: we have 12 rows out, 8 columns in
+ */
+int pe_kpc_init(int addr, int rowmask, int colmask, int debounce_ms)
+{
+	int i;
+	/* note that gpio15 is missing in the rows, so use tables */
+	static unsigned char row_to_gpio[12] = {
+		8, 9, 10, 11,   12, 13, 14, 16,   17, 18, 19, 20};
+	static unsigned char col_to_gpio[8] = {
+		0, 1, 2, 3,   4, 5, 6, 7};
+
+	/* First, configure pins for alternate functions (and pullup) */
+	for (i = 0; i < ARRAY_SIZE(row_to_gpio); i++) {
+		if (rowmask & (1 << i)) {
+			pe_gpio_dir(addr, row_to_gpio[i], 1 /* out */);
+			pe_gpio_af(addr, row_to_gpio[i], PE_GPIO_AF_AF1);
+			pe_gpio_pud(addr, row_to_gpio[i], 0, 0);
+		}
+	}
+	for (i = 0; i < ARRAY_SIZE(col_to_gpio); i++) {
+		if (colmask & (1 << i)) {
+			pe_gpio_dir(addr, col_to_gpio[i], 0 /* in */);
+			pe_gpio_af(addr, col_to_gpio[i], PE_GPIO_AF_AF1);
+			pe_gpio_pud(addr, col_to_gpio[i], 1, 0);
+		}
+	}
+
+	/* Set configuration for kpc: no support for dedicated keys */
+	pe_setreg(addr, PE_KPC_COL, colmask);
+	pe_setreg(addr, PE_KPC_ROW_MSB, 0xc0 | (rowmask >> 8));
+	pe_setreg(addr, PE_KPC_ROW_LSB, rowmask & 0xff);
+	pe_setreg(addr, PE_KPC_CTRL_MSB, 0x30 /* scan count is 3 */);
+	pe_setreg(addr, PE_KPC_CTRL_LSB, debounce_ms << 1);
+
+	/* Configure interrupt controller */
+	pe_setreg(addr, PE_ICR_LSB, 0x1); /* level, active low */
+	pe_setreg(addr, PE_IER_LSB, 0x2); /* bit1: keypad */
+
+	/* Start scanning */
+	pe_setreg(addr, PE_KPC_CTRL_LSB, (debounce_ms << 1) | 1);
+	return 0;
+}
+
+int pe_kpc_getkey(int addr, int *row, int *col)
+{
+	int key0, key1;
+
+	/* ack irq: bit 1 is keypad */
+	pe_setreg(addr, PE_ISR_LSB, 0x2);
+	/* get data -- one key only at a time: ignore key1*/
+	key0 = pe_getreg(addr, PE_KPC_DATA0);
+	key1 = pe_getreg(addr, PE_KPC_DATA1);
+	if (key0 & 0x80) /* release: return error */
+		return -1;
+	if ((key0 & 0x78) == 0x78) /* no key reported */
+		return -1;
+	*row = ((key0 & 0x78) >> 3);
+	*col = key0 & 0x07;
+	return 0;
+}
diff --git a/include/stmpe2401.h b/include/stmpe2401.h
new file mode 100644
index 0000000..3f513df
--- /dev/null
+++ b/include/stmpe2401.h
@@ -0,0 +1,66 @@ 
+/*
+ * Defines and rototypes for port extender STMPE2401. Use "PE_" as short prefix.
+ */
+
+#ifndef __STMPE2401_H
+#define __STMPE2401_H
+
+/*
+ * registers for the EGPIO blocks: we have groups of three registers,
+ * starting from MSB, so use negative offsets from LSB.
+ */
+#define PE_GPIO_OFFSET(gpio)  (- (gpio) / 8)
+#define PE_GPIO_MASK(gpio) (1 << ((gpio) & 7))
+
+#define PE_GPIO_GPMR(gpio)	(0xa4 + PE_GPIO_OFFSET(gpio)) /* monitor */
+#define PE_GPIO_GPCR(gpio)	(0x88 + PE_GPIO_OFFSET(gpio)) /* clear */
+#define PE_GPIO_GPSR(gpio)	(0x85 + PE_GPIO_OFFSET(gpio)) /* set */
+#define PE_GPIO_GPDR(gpio)	(0x8b + PE_GPIO_OFFSET(gpio)) /* direction */
+#define PE_GPIO_GPPUR(gpio)	(0x97 + PE_GPIO_OFFSET(gpio)) /* pull-up */
+#define PE_GPIO_GPPDR(gpio)	(0x9a + PE_GPIO_OFFSET(gpio)) /* pull-down */
+
+/* for alternate function, we have two bits per gpio, so 6 registers */
+#define PE_GPIO_AF_OFFSET(gpio)	(- (gpio) / 4)
+#define PE_GPIO_AF_SHIFT(gpio)	(2 * ((gpio) & 3))
+#define PE_GPIO_AF_MASK(gpio)	(3 << PE_GPIO_AF_SHIFT(gpio))
+#define PE_GPIO_AFR(gpio)	(0xa0 + PE_GPIO_AF_OFFSET(gpio))
+
+enum egpio_af {
+	PE_GPIO_AF_GPIO = 0,
+	PE_GPIO_AF_AF1,
+	PE_GPIO_AF_AF2,
+	PE_GPIO_AF_AF3
+};
+
+/* keypad controller registers */
+#define PE_KPC_COL		0x60
+#define PE_KPC_ROW_MSB		0x61
+#define PE_KPC_ROW_LSB		0x62
+#define PE_KPC_CTRL_MSB		0x63
+#define PE_KPC_CTRL_LSB		0x64
+#define PE_KPC_DATA0		0x68
+#define PE_KPC_DATA1		0x69
+#define PE_KPC_DATA2		0x6a
+
+/* interrupt controller registers (not all of them: we only need the LSB) */
+#define PE_ICR_LSB		0x11 /* control reg */
+#define PE_IER_LSB		0x13 /* enable reg */
+#define PE_ISR_LSB		0x15 /* status reg */
+
+/*
+ * prototypes of public functions
+ */
+extern int pe_getreg(int addr, int reg);
+extern int pe_setreg(int addr, int reg, int val);
+
+extern int pe_gpio_af(int addr, int gpio, int af);
+extern int pe_gpio_dir(int addr, int gpio, int dir);
+extern int pe_gpio_pud(int addr, int gpio, int pu, int pd);
+extern int pe_gpio_set(int addr, int gpio, int val);
+extern int pe_gpio_get(int addr, int gpio);
+
+/* here, rowmask is bits 0..11 for outputs, colmask is bits 0..7, for inputs */
+extern int pe_kpc_init(int addr, int rowmask, int colmask, int debounce_ms);
+extern int pe_kpc_getkey(int addr, int *row, int *col);
+
+#endif /* __STMPE2401_H */