xref: /openbmc/linux/drivers/input/keyboard/imx_keypad.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
10eb70904SFabio Estevam // SPDX-License-Identifier: GPL-2.0
20eb70904SFabio Estevam //
30eb70904SFabio Estevam // Driver for the IMX keypad port.
40eb70904SFabio Estevam // Copyright (C) 2009 Alberto Panizzo <maramaopercheseimorto@gmail.com>
576cdc083SAlberto Panizzo 
676cdc083SAlberto Panizzo #include <linux/clk.h>
776cdc083SAlberto Panizzo #include <linux/delay.h>
876cdc083SAlberto Panizzo #include <linux/device.h>
976cdc083SAlberto Panizzo #include <linux/err.h>
10*4e9cded6SDmitry Torokhov #include <linux/input.h>
1176cdc083SAlberto Panizzo #include <linux/input/matrix_keypad.h>
1276cdc083SAlberto Panizzo #include <linux/interrupt.h>
1376cdc083SAlberto Panizzo #include <linux/io.h>
1476cdc083SAlberto Panizzo #include <linux/jiffies.h>
1576cdc083SAlberto Panizzo #include <linux/kernel.h>
1676cdc083SAlberto Panizzo #include <linux/module.h>
170e14235eSLiu Ying #include <linux/of.h>
1876cdc083SAlberto Panizzo #include <linux/platform_device.h>
195a0e3ad6STejun Heo #include <linux/slab.h>
2076cdc083SAlberto Panizzo #include <linux/timer.h>
2176cdc083SAlberto Panizzo 
2276cdc083SAlberto Panizzo /*
2376cdc083SAlberto Panizzo  * Keypad Controller registers (halfword)
2476cdc083SAlberto Panizzo  */
2576cdc083SAlberto Panizzo #define KPCR		0x00 /* Keypad Control Register */
2676cdc083SAlberto Panizzo 
2776cdc083SAlberto Panizzo #define KPSR		0x02 /* Keypad Status Register */
2876cdc083SAlberto Panizzo #define KBD_STAT_KPKD	(0x1 << 0) /* Key Press Interrupt Status bit (w1c) */
2976cdc083SAlberto Panizzo #define KBD_STAT_KPKR	(0x1 << 1) /* Key Release Interrupt Status bit (w1c) */
3076cdc083SAlberto Panizzo #define KBD_STAT_KDSC	(0x1 << 2) /* Key Depress Synch Chain Status bit (w1c)*/
3176cdc083SAlberto Panizzo #define KBD_STAT_KRSS	(0x1 << 3) /* Key Release Synch Status bit (w1c)*/
3276cdc083SAlberto Panizzo #define KBD_STAT_KDIE	(0x1 << 8) /* Key Depress Interrupt Enable Status bit */
3376cdc083SAlberto Panizzo #define KBD_STAT_KRIE	(0x1 << 9) /* Key Release Interrupt Enable */
3476cdc083SAlberto Panizzo #define KBD_STAT_KPPEN	(0x1 << 10) /* Keypad Clock Enable */
3576cdc083SAlberto Panizzo 
3676cdc083SAlberto Panizzo #define KDDR		0x04 /* Keypad Data Direction Register */
3776cdc083SAlberto Panizzo #define KPDR		0x06 /* Keypad Data Register */
3876cdc083SAlberto Panizzo 
3976cdc083SAlberto Panizzo #define MAX_MATRIX_KEY_ROWS	8
4076cdc083SAlberto Panizzo #define MAX_MATRIX_KEY_COLS	8
4176cdc083SAlberto Panizzo #define MATRIX_ROW_SHIFT	3
4276cdc083SAlberto Panizzo 
4376cdc083SAlberto Panizzo #define MAX_MATRIX_KEY_NUM	(MAX_MATRIX_KEY_ROWS * MAX_MATRIX_KEY_COLS)
4476cdc083SAlberto Panizzo 
4576cdc083SAlberto Panizzo struct imx_keypad {
4676cdc083SAlberto Panizzo 
4776cdc083SAlberto Panizzo 	struct clk *clk;
4876cdc083SAlberto Panizzo 	struct input_dev *input_dev;
4976cdc083SAlberto Panizzo 	void __iomem *mmio_base;
5076cdc083SAlberto Panizzo 
5176cdc083SAlberto Panizzo 	int			irq;
5276cdc083SAlberto Panizzo 	struct timer_list	check_matrix_timer;
5376cdc083SAlberto Panizzo 
5476cdc083SAlberto Panizzo 	/*
5576cdc083SAlberto Panizzo 	 * The matrix is stable only if no changes are detected after
5676cdc083SAlberto Panizzo 	 * IMX_KEYPAD_SCANS_FOR_STABILITY scans
5776cdc083SAlberto Panizzo 	 */
5876cdc083SAlberto Panizzo #define IMX_KEYPAD_SCANS_FOR_STABILITY 3
5976cdc083SAlberto Panizzo 	int			stable_count;
6076cdc083SAlberto Panizzo 
6176cdc083SAlberto Panizzo 	bool			enabled;
6276cdc083SAlberto Panizzo 
6376cdc083SAlberto Panizzo 	/* Masks for enabled rows/cols */
6476cdc083SAlberto Panizzo 	unsigned short		rows_en_mask;
6576cdc083SAlberto Panizzo 	unsigned short		cols_en_mask;
6676cdc083SAlberto Panizzo 
6776cdc083SAlberto Panizzo 	unsigned short		keycodes[MAX_MATRIX_KEY_NUM];
6876cdc083SAlberto Panizzo 
6976cdc083SAlberto Panizzo 	/*
7076cdc083SAlberto Panizzo 	 * Matrix states:
7176cdc083SAlberto Panizzo 	 * -stable: achieved after a complete debounce process.
7276cdc083SAlberto Panizzo 	 * -unstable: used in the debouncing process.
7376cdc083SAlberto Panizzo 	 */
7476cdc083SAlberto Panizzo 	unsigned short		matrix_stable_state[MAX_MATRIX_KEY_COLS];
7576cdc083SAlberto Panizzo 	unsigned short		matrix_unstable_state[MAX_MATRIX_KEY_COLS];
7676cdc083SAlberto Panizzo };
7776cdc083SAlberto Panizzo 
7876cdc083SAlberto Panizzo /* Scan the matrix and return the new state in *matrix_volatile_state. */
imx_keypad_scan_matrix(struct imx_keypad * keypad,unsigned short * matrix_volatile_state)7976cdc083SAlberto Panizzo static void imx_keypad_scan_matrix(struct imx_keypad *keypad,
8076cdc083SAlberto Panizzo 				  unsigned short *matrix_volatile_state)
8176cdc083SAlberto Panizzo {
8276cdc083SAlberto Panizzo 	int col;
8376cdc083SAlberto Panizzo 	unsigned short reg_val;
8476cdc083SAlberto Panizzo 
8576cdc083SAlberto Panizzo 	for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
8676cdc083SAlberto Panizzo 		if ((keypad->cols_en_mask & (1 << col)) == 0)
8776cdc083SAlberto Panizzo 			continue;
8876cdc083SAlberto Panizzo 		/*
8976cdc083SAlberto Panizzo 		 * Discharge keypad capacitance:
9076cdc083SAlberto Panizzo 		 * 2. write 1s on column data.
9176cdc083SAlberto Panizzo 		 * 3. configure columns as totem-pole to discharge capacitance.
9276cdc083SAlberto Panizzo 		 * 4. configure columns as open-drain.
9376cdc083SAlberto Panizzo 		 */
9476cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPDR);
9576cdc083SAlberto Panizzo 		reg_val |= 0xff00;
9676cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPDR);
9776cdc083SAlberto Panizzo 
9876cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPCR);
9976cdc083SAlberto Panizzo 		reg_val &= ~((keypad->cols_en_mask & 0xff) << 8);
10076cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPCR);
10176cdc083SAlberto Panizzo 
10276cdc083SAlberto Panizzo 		udelay(2);
10376cdc083SAlberto Panizzo 
10476cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPCR);
10576cdc083SAlberto Panizzo 		reg_val |= (keypad->cols_en_mask & 0xff) << 8;
10676cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPCR);
10776cdc083SAlberto Panizzo 
10876cdc083SAlberto Panizzo 		/*
10976cdc083SAlberto Panizzo 		 * 5. Write a single column to 0, others to 1.
11076cdc083SAlberto Panizzo 		 * 6. Sample row inputs and save data.
11176cdc083SAlberto Panizzo 		 * 7. Repeat steps 2 - 6 for remaining columns.
11276cdc083SAlberto Panizzo 		 */
11376cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPDR);
11476cdc083SAlberto Panizzo 		reg_val &= ~(1 << (8 + col));
11576cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPDR);
11676cdc083SAlberto Panizzo 
11776cdc083SAlberto Panizzo 		/*
11876cdc083SAlberto Panizzo 		 * Delay added to avoid propagating the 0 from column to row
11976cdc083SAlberto Panizzo 		 * when scanning.
12076cdc083SAlberto Panizzo 		 */
12176cdc083SAlberto Panizzo 		udelay(5);
12276cdc083SAlberto Panizzo 
12376cdc083SAlberto Panizzo 		/*
12476cdc083SAlberto Panizzo 		 * 1s in matrix_volatile_state[col] means key pressures
12576cdc083SAlberto Panizzo 		 * throw data from non enabled rows.
12676cdc083SAlberto Panizzo 		 */
12776cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPDR);
12876cdc083SAlberto Panizzo 		matrix_volatile_state[col] = (~reg_val) & keypad->rows_en_mask;
12976cdc083SAlberto Panizzo 	}
13076cdc083SAlberto Panizzo 
13176cdc083SAlberto Panizzo 	/*
13276cdc083SAlberto Panizzo 	 * Return in standby mode:
13376cdc083SAlberto Panizzo 	 * 9. write 0s to columns
13476cdc083SAlberto Panizzo 	 */
13576cdc083SAlberto Panizzo 	reg_val = readw(keypad->mmio_base + KPDR);
13676cdc083SAlberto Panizzo 	reg_val &= 0x00ff;
13776cdc083SAlberto Panizzo 	writew(reg_val, keypad->mmio_base + KPDR);
13876cdc083SAlberto Panizzo }
13976cdc083SAlberto Panizzo 
14076cdc083SAlberto Panizzo /*
14176cdc083SAlberto Panizzo  * Compare the new matrix state (volatile) with the stable one stored in
14276cdc083SAlberto Panizzo  * keypad->matrix_stable_state and fire events if changes are detected.
14376cdc083SAlberto Panizzo  */
imx_keypad_fire_events(struct imx_keypad * keypad,unsigned short * matrix_volatile_state)14476cdc083SAlberto Panizzo static void imx_keypad_fire_events(struct imx_keypad *keypad,
14576cdc083SAlberto Panizzo 				   unsigned short *matrix_volatile_state)
14676cdc083SAlberto Panizzo {
14776cdc083SAlberto Panizzo 	struct input_dev *input_dev = keypad->input_dev;
14876cdc083SAlberto Panizzo 	int row, col;
14976cdc083SAlberto Panizzo 
15076cdc083SAlberto Panizzo 	for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
15176cdc083SAlberto Panizzo 		unsigned short bits_changed;
15276cdc083SAlberto Panizzo 		int code;
15376cdc083SAlberto Panizzo 
15476cdc083SAlberto Panizzo 		if ((keypad->cols_en_mask & (1 << col)) == 0)
15576cdc083SAlberto Panizzo 			continue; /* Column is not enabled */
15676cdc083SAlberto Panizzo 
15776cdc083SAlberto Panizzo 		bits_changed = keypad->matrix_stable_state[col] ^
15876cdc083SAlberto Panizzo 						matrix_volatile_state[col];
15976cdc083SAlberto Panizzo 
16076cdc083SAlberto Panizzo 		if (bits_changed == 0)
16176cdc083SAlberto Panizzo 			continue; /* Column does not contain changes */
16276cdc083SAlberto Panizzo 
16376cdc083SAlberto Panizzo 		for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) {
16476cdc083SAlberto Panizzo 			if ((keypad->rows_en_mask & (1 << row)) == 0)
16576cdc083SAlberto Panizzo 				continue; /* Row is not enabled */
16676cdc083SAlberto Panizzo 			if ((bits_changed & (1 << row)) == 0)
16776cdc083SAlberto Panizzo 				continue; /* Row does not contain changes */
16876cdc083SAlberto Panizzo 
16976cdc083SAlberto Panizzo 			code = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
17076cdc083SAlberto Panizzo 			input_event(input_dev, EV_MSC, MSC_SCAN, code);
17176cdc083SAlberto Panizzo 			input_report_key(input_dev, keypad->keycodes[code],
17276cdc083SAlberto Panizzo 				matrix_volatile_state[col] & (1 << row));
17376cdc083SAlberto Panizzo 			dev_dbg(&input_dev->dev, "Event code: %d, val: %d",
17476cdc083SAlberto Panizzo 				keypad->keycodes[code],
17576cdc083SAlberto Panizzo 				matrix_volatile_state[col] & (1 << row));
17676cdc083SAlberto Panizzo 		}
17776cdc083SAlberto Panizzo 	}
17876cdc083SAlberto Panizzo 	input_sync(input_dev);
17976cdc083SAlberto Panizzo }
18076cdc083SAlberto Panizzo 
18176cdc083SAlberto Panizzo /*
18276cdc083SAlberto Panizzo  * imx_keypad_check_for_events is the timer handler.
18376cdc083SAlberto Panizzo  */
imx_keypad_check_for_events(struct timer_list * t)1844ea40278SKees Cook static void imx_keypad_check_for_events(struct timer_list *t)
18576cdc083SAlberto Panizzo {
1864ea40278SKees Cook 	struct imx_keypad *keypad = from_timer(keypad, t, check_matrix_timer);
18776cdc083SAlberto Panizzo 	unsigned short matrix_volatile_state[MAX_MATRIX_KEY_COLS];
18876cdc083SAlberto Panizzo 	unsigned short reg_val;
18976cdc083SAlberto Panizzo 	bool state_changed, is_zero_matrix;
19076cdc083SAlberto Panizzo 	int i;
19176cdc083SAlberto Panizzo 
19276cdc083SAlberto Panizzo 	memset(matrix_volatile_state, 0, sizeof(matrix_volatile_state));
19376cdc083SAlberto Panizzo 
19476cdc083SAlberto Panizzo 	imx_keypad_scan_matrix(keypad, matrix_volatile_state);
19576cdc083SAlberto Panizzo 
19676cdc083SAlberto Panizzo 	state_changed = false;
19776cdc083SAlberto Panizzo 	for (i = 0; i < MAX_MATRIX_KEY_COLS; i++) {
19876cdc083SAlberto Panizzo 		if ((keypad->cols_en_mask & (1 << i)) == 0)
19976cdc083SAlberto Panizzo 			continue;
20076cdc083SAlberto Panizzo 
20176cdc083SAlberto Panizzo 		if (keypad->matrix_unstable_state[i] ^ matrix_volatile_state[i]) {
20276cdc083SAlberto Panizzo 			state_changed = true;
20376cdc083SAlberto Panizzo 			break;
20476cdc083SAlberto Panizzo 		}
20576cdc083SAlberto Panizzo 	}
20676cdc083SAlberto Panizzo 
20776cdc083SAlberto Panizzo 	/*
20876cdc083SAlberto Panizzo 	 * If the matrix state is changed from the previous scan
20976cdc083SAlberto Panizzo 	 *   (Re)Begin the debouncing process, saving the new state in
21076cdc083SAlberto Panizzo 	 *    keypad->matrix_unstable_state.
21176cdc083SAlberto Panizzo 	 * else
21276cdc083SAlberto Panizzo 	 *   Increase the count of number of scans with a stable state.
21376cdc083SAlberto Panizzo 	 */
21476cdc083SAlberto Panizzo 	if (state_changed) {
21576cdc083SAlberto Panizzo 		memcpy(keypad->matrix_unstable_state, matrix_volatile_state,
21676cdc083SAlberto Panizzo 			sizeof(matrix_volatile_state));
21776cdc083SAlberto Panizzo 		keypad->stable_count = 0;
21876cdc083SAlberto Panizzo 	} else
21976cdc083SAlberto Panizzo 		keypad->stable_count++;
22076cdc083SAlberto Panizzo 
22176cdc083SAlberto Panizzo 	/*
22276cdc083SAlberto Panizzo 	 * If the matrix is not as stable as we want reschedule scan
22376cdc083SAlberto Panizzo 	 * in the near future.
22476cdc083SAlberto Panizzo 	 */
22576cdc083SAlberto Panizzo 	if (keypad->stable_count < IMX_KEYPAD_SCANS_FOR_STABILITY) {
22676cdc083SAlberto Panizzo 		mod_timer(&keypad->check_matrix_timer,
22776cdc083SAlberto Panizzo 			  jiffies + msecs_to_jiffies(10));
22876cdc083SAlberto Panizzo 		return;
22976cdc083SAlberto Panizzo 	}
23076cdc083SAlberto Panizzo 
23176cdc083SAlberto Panizzo 	/*
23276cdc083SAlberto Panizzo 	 * If the matrix state is stable, fire the events and save the new
23376cdc083SAlberto Panizzo 	 * stable state. Note, if the matrix is kept stable for longer
23476cdc083SAlberto Panizzo 	 * (keypad->stable_count > IMX_KEYPAD_SCANS_FOR_STABILITY) all
23576cdc083SAlberto Panizzo 	 * events have already been generated.
23676cdc083SAlberto Panizzo 	 */
23776cdc083SAlberto Panizzo 	if (keypad->stable_count == IMX_KEYPAD_SCANS_FOR_STABILITY) {
23876cdc083SAlberto Panizzo 		imx_keypad_fire_events(keypad, matrix_volatile_state);
23976cdc083SAlberto Panizzo 
24076cdc083SAlberto Panizzo 		memcpy(keypad->matrix_stable_state, matrix_volatile_state,
24176cdc083SAlberto Panizzo 			sizeof(matrix_volatile_state));
24276cdc083SAlberto Panizzo 	}
24376cdc083SAlberto Panizzo 
24476cdc083SAlberto Panizzo 	is_zero_matrix = true;
24576cdc083SAlberto Panizzo 	for (i = 0; i < MAX_MATRIX_KEY_COLS; i++) {
24676cdc083SAlberto Panizzo 		if (matrix_volatile_state[i] != 0) {
24776cdc083SAlberto Panizzo 			is_zero_matrix = false;
24876cdc083SAlberto Panizzo 			break;
24976cdc083SAlberto Panizzo 		}
25076cdc083SAlberto Panizzo 	}
25176cdc083SAlberto Panizzo 
25276cdc083SAlberto Panizzo 
25376cdc083SAlberto Panizzo 	if (is_zero_matrix) {
25476cdc083SAlberto Panizzo 		/*
25576cdc083SAlberto Panizzo 		 * All keys have been released. Enable only the KDI
25676cdc083SAlberto Panizzo 		 * interrupt for future key presses (clear the KDI
25776cdc083SAlberto Panizzo 		 * status bit and its sync chain before that).
25876cdc083SAlberto Panizzo 		 */
25976cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPSR);
26076cdc083SAlberto Panizzo 		reg_val |= KBD_STAT_KPKD | KBD_STAT_KDSC;
26176cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPSR);
26276cdc083SAlberto Panizzo 
26376cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPSR);
26476cdc083SAlberto Panizzo 		reg_val |= KBD_STAT_KDIE;
26576cdc083SAlberto Panizzo 		reg_val &= ~KBD_STAT_KRIE;
26676cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPSR);
26776cdc083SAlberto Panizzo 	} else {
26876cdc083SAlberto Panizzo 		/*
26976cdc083SAlberto Panizzo 		 * Some keys are still pressed. Schedule a rescan in
27076cdc083SAlberto Panizzo 		 * attempt to detect multiple key presses and enable
27176cdc083SAlberto Panizzo 		 * the KRI interrupt to react quickly to key release
27276cdc083SAlberto Panizzo 		 * event.
27376cdc083SAlberto Panizzo 		 */
27476cdc083SAlberto Panizzo 		mod_timer(&keypad->check_matrix_timer,
27576cdc083SAlberto Panizzo 			  jiffies + msecs_to_jiffies(60));
27676cdc083SAlberto Panizzo 
27776cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPSR);
27876cdc083SAlberto Panizzo 		reg_val |= KBD_STAT_KPKR | KBD_STAT_KRSS;
27976cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPSR);
28076cdc083SAlberto Panizzo 
28176cdc083SAlberto Panizzo 		reg_val = readw(keypad->mmio_base + KPSR);
28276cdc083SAlberto Panizzo 		reg_val |= KBD_STAT_KRIE;
28376cdc083SAlberto Panizzo 		reg_val &= ~KBD_STAT_KDIE;
28476cdc083SAlberto Panizzo 		writew(reg_val, keypad->mmio_base + KPSR);
28576cdc083SAlberto Panizzo 	}
28676cdc083SAlberto Panizzo }
28776cdc083SAlberto Panizzo 
imx_keypad_irq_handler(int irq,void * dev_id)28876cdc083SAlberto Panizzo static irqreturn_t imx_keypad_irq_handler(int irq, void *dev_id)
28976cdc083SAlberto Panizzo {
29076cdc083SAlberto Panizzo 	struct imx_keypad *keypad = dev_id;
29176cdc083SAlberto Panizzo 	unsigned short reg_val;
29276cdc083SAlberto Panizzo 
29376cdc083SAlberto Panizzo 	reg_val = readw(keypad->mmio_base + KPSR);
29476cdc083SAlberto Panizzo 
29576cdc083SAlberto Panizzo 	/* Disable both interrupt types */
29676cdc083SAlberto Panizzo 	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
29776cdc083SAlberto Panizzo 	/* Clear interrupts status bits */
29876cdc083SAlberto Panizzo 	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD;
29976cdc083SAlberto Panizzo 	writew(reg_val, keypad->mmio_base + KPSR);
30076cdc083SAlberto Panizzo 
30176cdc083SAlberto Panizzo 	if (keypad->enabled) {
30276cdc083SAlberto Panizzo 		/* The matrix is supposed to be changed */
30376cdc083SAlberto Panizzo 		keypad->stable_count = 0;
30476cdc083SAlberto Panizzo 
30576cdc083SAlberto Panizzo 		/* Schedule the scanning procedure near in the future */
30676cdc083SAlberto Panizzo 		mod_timer(&keypad->check_matrix_timer,
30776cdc083SAlberto Panizzo 			  jiffies + msecs_to_jiffies(2));
30876cdc083SAlberto Panizzo 	}
30976cdc083SAlberto Panizzo 
31076cdc083SAlberto Panizzo 	return IRQ_HANDLED;
31176cdc083SAlberto Panizzo }
31276cdc083SAlberto Panizzo 
imx_keypad_config(struct imx_keypad * keypad)31376cdc083SAlberto Panizzo static void imx_keypad_config(struct imx_keypad *keypad)
31476cdc083SAlberto Panizzo {
31576cdc083SAlberto Panizzo 	unsigned short reg_val;
31676cdc083SAlberto Panizzo 
31776cdc083SAlberto Panizzo 	/*
31876cdc083SAlberto Panizzo 	 * Include enabled rows in interrupt generation (KPCR[7:0])
31976cdc083SAlberto Panizzo 	 * Configure keypad columns as open-drain (KPCR[15:8])
32076cdc083SAlberto Panizzo 	 */
32176cdc083SAlberto Panizzo 	reg_val = readw(keypad->mmio_base + KPCR);
32276cdc083SAlberto Panizzo 	reg_val |= keypad->rows_en_mask & 0xff;		/* rows */
32376cdc083SAlberto Panizzo 	reg_val |= (keypad->cols_en_mask & 0xff) << 8;	/* cols */
32476cdc083SAlberto Panizzo 	writew(reg_val, keypad->mmio_base + KPCR);
32576cdc083SAlberto Panizzo 
32676cdc083SAlberto Panizzo 	/* Write 0's to KPDR[15:8] (Colums) */
32776cdc083SAlberto Panizzo 	reg_val = readw(keypad->mmio_base + KPDR);
32876cdc083SAlberto Panizzo 	reg_val &= 0x00ff;
32976cdc083SAlberto Panizzo 	writew(reg_val, keypad->mmio_base + KPDR);
33076cdc083SAlberto Panizzo 
33176cdc083SAlberto Panizzo 	/* Configure columns as output, rows as input (KDDR[15:0]) */
33276cdc083SAlberto Panizzo 	writew(0xff00, keypad->mmio_base + KDDR);
33376cdc083SAlberto Panizzo 
33476cdc083SAlberto Panizzo 	/*
33576cdc083SAlberto Panizzo 	 * Clear Key Depress and Key Release status bit.
33676cdc083SAlberto Panizzo 	 * Clear both synchronizer chain.
33776cdc083SAlberto Panizzo 	 */
33876cdc083SAlberto Panizzo 	reg_val = readw(keypad->mmio_base + KPSR);
33976cdc083SAlberto Panizzo 	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD |
34076cdc083SAlberto Panizzo 		   KBD_STAT_KDSC | KBD_STAT_KRSS;
34176cdc083SAlberto Panizzo 	writew(reg_val, keypad->mmio_base + KPSR);
34276cdc083SAlberto Panizzo 
34376cdc083SAlberto Panizzo 	/* Enable KDI and disable KRI (avoid false release events). */
34476cdc083SAlberto Panizzo 	reg_val |= KBD_STAT_KDIE;
34576cdc083SAlberto Panizzo 	reg_val &= ~KBD_STAT_KRIE;
34676cdc083SAlberto Panizzo 	writew(reg_val, keypad->mmio_base + KPSR);
34776cdc083SAlberto Panizzo }
34876cdc083SAlberto Panizzo 
imx_keypad_inhibit(struct imx_keypad * keypad)34976cdc083SAlberto Panizzo static void imx_keypad_inhibit(struct imx_keypad *keypad)
35076cdc083SAlberto Panizzo {
35176cdc083SAlberto Panizzo 	unsigned short reg_val;
35276cdc083SAlberto Panizzo 
35376cdc083SAlberto Panizzo 	/* Inhibit KDI and KRI interrupts. */
35476cdc083SAlberto Panizzo 	reg_val = readw(keypad->mmio_base + KPSR);
35576cdc083SAlberto Panizzo 	reg_val &= ~(KBD_STAT_KRIE | KBD_STAT_KDIE);
356f35dd69bSMichael Grzeschik 	reg_val |= KBD_STAT_KPKR | KBD_STAT_KPKD;
35776cdc083SAlberto Panizzo 	writew(reg_val, keypad->mmio_base + KPSR);
35876cdc083SAlberto Panizzo 
35976cdc083SAlberto Panizzo 	/* Colums as open drain and disable all rows */
36092aab960SAndreas Pretzsch 	reg_val = (keypad->cols_en_mask & 0xff) << 8;
36192aab960SAndreas Pretzsch 	writew(reg_val, keypad->mmio_base + KPCR);
36276cdc083SAlberto Panizzo }
36376cdc083SAlberto Panizzo 
imx_keypad_close(struct input_dev * dev)36476cdc083SAlberto Panizzo static void imx_keypad_close(struct input_dev *dev)
36576cdc083SAlberto Panizzo {
36676cdc083SAlberto Panizzo 	struct imx_keypad *keypad = input_get_drvdata(dev);
36776cdc083SAlberto Panizzo 
36876cdc083SAlberto Panizzo 	dev_dbg(&dev->dev, ">%s\n", __func__);
36976cdc083SAlberto Panizzo 
37076cdc083SAlberto Panizzo 	/* Mark keypad as being inactive */
37176cdc083SAlberto Panizzo 	keypad->enabled = false;
37276cdc083SAlberto Panizzo 	synchronize_irq(keypad->irq);
37376cdc083SAlberto Panizzo 	del_timer_sync(&keypad->check_matrix_timer);
37476cdc083SAlberto Panizzo 
37576cdc083SAlberto Panizzo 	imx_keypad_inhibit(keypad);
37676cdc083SAlberto Panizzo 
37776cdc083SAlberto Panizzo 	/* Disable clock unit */
378a1e636e6SFabio Estevam 	clk_disable_unprepare(keypad->clk);
37976cdc083SAlberto Panizzo }
38076cdc083SAlberto Panizzo 
imx_keypad_open(struct input_dev * dev)38176cdc083SAlberto Panizzo static int imx_keypad_open(struct input_dev *dev)
38276cdc083SAlberto Panizzo {
38376cdc083SAlberto Panizzo 	struct imx_keypad *keypad = input_get_drvdata(dev);
384333fbe84SFabio Estevam 	int error;
38576cdc083SAlberto Panizzo 
38676cdc083SAlberto Panizzo 	dev_dbg(&dev->dev, ">%s\n", __func__);
38776cdc083SAlberto Panizzo 
388333fbe84SFabio Estevam 	/* Enable the kpp clock */
389333fbe84SFabio Estevam 	error = clk_prepare_enable(keypad->clk);
390333fbe84SFabio Estevam 	if (error)
391333fbe84SFabio Estevam 		return error;
392333fbe84SFabio Estevam 
39376cdc083SAlberto Panizzo 	/* We became active from now */
39476cdc083SAlberto Panizzo 	keypad->enabled = true;
39576cdc083SAlberto Panizzo 
39676cdc083SAlberto Panizzo 	imx_keypad_config(keypad);
39776cdc083SAlberto Panizzo 
39876cdc083SAlberto Panizzo 	/* Sanity control, not all the rows must be actived now. */
39976cdc083SAlberto Panizzo 	if ((readw(keypad->mmio_base + KPDR) & keypad->rows_en_mask) == 0) {
40076cdc083SAlberto Panizzo 		dev_err(&dev->dev,
40176cdc083SAlberto Panizzo 			"too many keys pressed, control pins initialisation\n");
40276cdc083SAlberto Panizzo 		goto open_err;
40376cdc083SAlberto Panizzo 	}
40476cdc083SAlberto Panizzo 
40576cdc083SAlberto Panizzo 	return 0;
40676cdc083SAlberto Panizzo 
40776cdc083SAlberto Panizzo open_err:
40876cdc083SAlberto Panizzo 	imx_keypad_close(dev);
40976cdc083SAlberto Panizzo 	return -EIO;
41076cdc083SAlberto Panizzo }
41176cdc083SAlberto Panizzo 
41227ec39c0SJingoo Han static const struct of_device_id imx_keypad_of_match[] = {
4130e14235eSLiu Ying 	{ .compatible = "fsl,imx21-kpp", },
4140e14235eSLiu Ying 	{ /* sentinel */ }
4150e14235eSLiu Ying };
4160e14235eSLiu Ying MODULE_DEVICE_TABLE(of, imx_keypad_of_match);
4170e14235eSLiu Ying 
imx_keypad_probe(struct platform_device * pdev)4185298cc4cSBill Pemberton static int imx_keypad_probe(struct platform_device *pdev)
41976cdc083SAlberto Panizzo {
42076cdc083SAlberto Panizzo 	struct imx_keypad *keypad;
42176cdc083SAlberto Panizzo 	struct input_dev *input_dev;
4220e14235eSLiu Ying 	int irq, error, i, row, col;
42376cdc083SAlberto Panizzo 
42476cdc083SAlberto Panizzo 	irq = platform_get_irq(pdev, 0);
4250bec8b7eSStephen Boyd 	if (irq < 0)
4267fb45edbSFabio Estevam 		return irq;
42776cdc083SAlberto Panizzo 
428da5bce19SFabio Estevam 	input_dev = devm_input_allocate_device(&pdev->dev);
42976cdc083SAlberto Panizzo 	if (!input_dev) {
43076cdc083SAlberto Panizzo 		dev_err(&pdev->dev, "failed to allocate the input device\n");
431da5bce19SFabio Estevam 		return -ENOMEM;
43276cdc083SAlberto Panizzo 	}
43376cdc083SAlberto Panizzo 
434061a5ad7SFabio Estevam 	keypad = devm_kzalloc(&pdev->dev, sizeof(*keypad), GFP_KERNEL);
43576cdc083SAlberto Panizzo 	if (!keypad) {
43676cdc083SAlberto Panizzo 		dev_err(&pdev->dev, "not enough memory for driver data\n");
437da5bce19SFabio Estevam 		return -ENOMEM;
43876cdc083SAlberto Panizzo 	}
43976cdc083SAlberto Panizzo 
44076cdc083SAlberto Panizzo 	keypad->input_dev = input_dev;
44176cdc083SAlberto Panizzo 	keypad->irq = irq;
44276cdc083SAlberto Panizzo 	keypad->stable_count = 0;
44376cdc083SAlberto Panizzo 
4444ea40278SKees Cook 	timer_setup(&keypad->check_matrix_timer,
4454ea40278SKees Cook 		    imx_keypad_check_for_events, 0);
44676cdc083SAlberto Panizzo 
447d8ea61c5SAnson Huang 	keypad->mmio_base = devm_platform_ioremap_resource(pdev, 0);
448da5bce19SFabio Estevam 	if (IS_ERR(keypad->mmio_base))
449da5bce19SFabio Estevam 		return PTR_ERR(keypad->mmio_base);
45076cdc083SAlberto Panizzo 
451da5bce19SFabio Estevam 	keypad->clk = devm_clk_get(&pdev->dev, NULL);
45276cdc083SAlberto Panizzo 	if (IS_ERR(keypad->clk)) {
45376cdc083SAlberto Panizzo 		dev_err(&pdev->dev, "failed to get keypad clock\n");
454da5bce19SFabio Estevam 		return PTR_ERR(keypad->clk);
45576cdc083SAlberto Panizzo 	}
45676cdc083SAlberto Panizzo 
45776cdc083SAlberto Panizzo 	/* Init the Input device */
45876cdc083SAlberto Panizzo 	input_dev->name = pdev->name;
45976cdc083SAlberto Panizzo 	input_dev->id.bustype = BUS_HOST;
46076cdc083SAlberto Panizzo 	input_dev->dev.parent = &pdev->dev;
46176cdc083SAlberto Panizzo 	input_dev->open = imx_keypad_open;
46276cdc083SAlberto Panizzo 	input_dev->close = imx_keypad_close;
46376cdc083SAlberto Panizzo 
46473e7f173SFabio Estevam 	error = matrix_keypad_build_keymap(NULL, NULL,
4651932811fSDmitry Torokhov 					   MAX_MATRIX_KEY_ROWS,
4661932811fSDmitry Torokhov 					   MAX_MATRIX_KEY_COLS,
4671932811fSDmitry Torokhov 					   keypad->keycodes, input_dev);
4681932811fSDmitry Torokhov 	if (error) {
4691932811fSDmitry Torokhov 		dev_err(&pdev->dev, "failed to build keymap\n");
470da5bce19SFabio Estevam 		return error;
4711932811fSDmitry Torokhov 	}
47276cdc083SAlberto Panizzo 
4730e14235eSLiu Ying 	/* Search for rows and cols enabled */
4740e14235eSLiu Ying 	for (row = 0; row < MAX_MATRIX_KEY_ROWS; row++) {
4750e14235eSLiu Ying 		for (col = 0; col < MAX_MATRIX_KEY_COLS; col++) {
4760e14235eSLiu Ying 			i = MATRIX_SCAN_CODE(row, col, MATRIX_ROW_SHIFT);
4770e14235eSLiu Ying 			if (keypad->keycodes[i] != KEY_RESERVED) {
4780e14235eSLiu Ying 				keypad->rows_en_mask |= 1 << row;
4790e14235eSLiu Ying 				keypad->cols_en_mask |= 1 << col;
4800e14235eSLiu Ying 			}
4810e14235eSLiu Ying 		}
4820e14235eSLiu Ying 	}
4830e14235eSLiu Ying 	dev_dbg(&pdev->dev, "enabled rows mask: %x\n", keypad->rows_en_mask);
4840e14235eSLiu Ying 	dev_dbg(&pdev->dev, "enabled cols mask: %x\n", keypad->cols_en_mask);
4850e14235eSLiu Ying 
4861932811fSDmitry Torokhov 	__set_bit(EV_REP, input_dev->evbit);
48776cdc083SAlberto Panizzo 	input_set_capability(input_dev, EV_MSC, MSC_SCAN);
48876cdc083SAlberto Panizzo 	input_set_drvdata(input_dev, keypad);
48976cdc083SAlberto Panizzo 
49076cdc083SAlberto Panizzo 	/* Ensure that the keypad will stay dormant until opened */
491e998200cSFabio Estevam 	error = clk_prepare_enable(keypad->clk);
492e998200cSFabio Estevam 	if (error)
493e998200cSFabio Estevam 		return error;
49476cdc083SAlberto Panizzo 	imx_keypad_inhibit(keypad);
495609455f4SFabio Estevam 	clk_disable_unprepare(keypad->clk);
49676cdc083SAlberto Panizzo 
497da5bce19SFabio Estevam 	error = devm_request_irq(&pdev->dev, irq, imx_keypad_irq_handler, 0,
49876cdc083SAlberto Panizzo 			    pdev->name, keypad);
49976cdc083SAlberto Panizzo 	if (error) {
50076cdc083SAlberto Panizzo 		dev_err(&pdev->dev, "failed to request IRQ\n");
501da5bce19SFabio Estevam 		return error;
50276cdc083SAlberto Panizzo 	}
50376cdc083SAlberto Panizzo 
50476cdc083SAlberto Panizzo 	/* Register the input device */
50576cdc083SAlberto Panizzo 	error = input_register_device(input_dev);
50676cdc083SAlberto Panizzo 	if (error) {
50776cdc083SAlberto Panizzo 		dev_err(&pdev->dev, "failed to register input device\n");
508da5bce19SFabio Estevam 		return error;
50976cdc083SAlberto Panizzo 	}
51076cdc083SAlberto Panizzo 
51176cdc083SAlberto Panizzo 	platform_set_drvdata(pdev, keypad);
51276cdc083SAlberto Panizzo 	device_init_wakeup(&pdev->dev, 1);
51376cdc083SAlberto Panizzo 
51476cdc083SAlberto Panizzo 	return 0;
51576cdc083SAlberto Panizzo }
51676cdc083SAlberto Panizzo 
imx_kbd_noirq_suspend(struct device * dev)517ce9a53ebSAnson Huang static int __maybe_unused imx_kbd_noirq_suspend(struct device *dev)
51881e8f2bcSHui Wang {
51981e8f2bcSHui Wang 	struct platform_device *pdev = to_platform_device(dev);
52081e8f2bcSHui Wang 	struct imx_keypad *kbd = platform_get_drvdata(pdev);
52181e8f2bcSHui Wang 	struct input_dev *input_dev = kbd->input_dev;
522ce9a53ebSAnson Huang 	unsigned short reg_val = readw(kbd->mmio_base + KPSR);
52381e8f2bcSHui Wang 
52481e8f2bcSHui Wang 	/* imx kbd can wake up system even clock is disabled */
52581e8f2bcSHui Wang 	mutex_lock(&input_dev->mutex);
52681e8f2bcSHui Wang 
527d69f0a43SAndrzej Pietrasiewicz 	if (input_device_enabled(input_dev))
528a1e636e6SFabio Estevam 		clk_disable_unprepare(kbd->clk);
52981e8f2bcSHui Wang 
53081e8f2bcSHui Wang 	mutex_unlock(&input_dev->mutex);
53181e8f2bcSHui Wang 
532ce9a53ebSAnson Huang 	if (device_may_wakeup(&pdev->dev)) {
533ce9a53ebSAnson Huang 		if (reg_val & KBD_STAT_KPKD)
534ce9a53ebSAnson Huang 			reg_val |= KBD_STAT_KRIE;
535ce9a53ebSAnson Huang 		if (reg_val & KBD_STAT_KPKR)
536ce9a53ebSAnson Huang 			reg_val |= KBD_STAT_KDIE;
537ce9a53ebSAnson Huang 		writew(reg_val, kbd->mmio_base + KPSR);
538ce9a53ebSAnson Huang 
53981e8f2bcSHui Wang 		enable_irq_wake(kbd->irq);
540ce9a53ebSAnson Huang 	}
54181e8f2bcSHui Wang 
54281e8f2bcSHui Wang 	return 0;
54381e8f2bcSHui Wang }
54481e8f2bcSHui Wang 
imx_kbd_noirq_resume(struct device * dev)545ce9a53ebSAnson Huang static int __maybe_unused imx_kbd_noirq_resume(struct device *dev)
54681e8f2bcSHui Wang {
54781e8f2bcSHui Wang 	struct platform_device *pdev = to_platform_device(dev);
54881e8f2bcSHui Wang 	struct imx_keypad *kbd = platform_get_drvdata(pdev);
54981e8f2bcSHui Wang 	struct input_dev *input_dev = kbd->input_dev;
550333fbe84SFabio Estevam 	int ret = 0;
55181e8f2bcSHui Wang 
55281e8f2bcSHui Wang 	if (device_may_wakeup(&pdev->dev))
55381e8f2bcSHui Wang 		disable_irq_wake(kbd->irq);
55481e8f2bcSHui Wang 
55581e8f2bcSHui Wang 	mutex_lock(&input_dev->mutex);
55681e8f2bcSHui Wang 
557d69f0a43SAndrzej Pietrasiewicz 	if (input_device_enabled(input_dev)) {
558333fbe84SFabio Estevam 		ret = clk_prepare_enable(kbd->clk);
559333fbe84SFabio Estevam 		if (ret)
560333fbe84SFabio Estevam 			goto err_clk;
561333fbe84SFabio Estevam 	}
56281e8f2bcSHui Wang 
563333fbe84SFabio Estevam err_clk:
56481e8f2bcSHui Wang 	mutex_unlock(&input_dev->mutex);
56581e8f2bcSHui Wang 
566333fbe84SFabio Estevam 	return ret;
56781e8f2bcSHui Wang }
56881e8f2bcSHui Wang 
569ce9a53ebSAnson Huang static const struct dev_pm_ops imx_kbd_pm_ops = {
570ce9a53ebSAnson Huang 	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(imx_kbd_noirq_suspend, imx_kbd_noirq_resume)
571ce9a53ebSAnson Huang };
57281e8f2bcSHui Wang 
57376cdc083SAlberto Panizzo static struct platform_driver imx_keypad_driver = {
57476cdc083SAlberto Panizzo 	.driver		= {
57576cdc083SAlberto Panizzo 		.name	= "imx-keypad",
57681e8f2bcSHui Wang 		.pm	= &imx_kbd_pm_ops,
57773e7f173SFabio Estevam 		.of_match_table = imx_keypad_of_match,
57876cdc083SAlberto Panizzo 	},
57976cdc083SAlberto Panizzo 	.probe		= imx_keypad_probe,
58076cdc083SAlberto Panizzo };
5815146c84fSJJ Ding module_platform_driver(imx_keypad_driver);
58276cdc083SAlberto Panizzo 
58376cdc083SAlberto Panizzo MODULE_AUTHOR("Alberto Panizzo <maramaopercheseimorto@gmail.com>");
58476cdc083SAlberto Panizzo MODULE_DESCRIPTION("IMX Keypad Port Driver");
58576cdc083SAlberto Panizzo MODULE_LICENSE("GPL v2");
58676cdc083SAlberto Panizzo MODULE_ALIAS("platform:imx-keypad");
587