corgikbd.c 12.1 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14
/*
 *  Keyboard driver for Sharp Corgi models (SL-C7xx)
 *
 *  Copyright (c) 2004-2005 Richard Purdie
 *
 *  Based on xtkbd.c/locomkbd.c
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 */

#include <linux/delay.h>
15
#include <linux/platform_device.h>
L
Linus Torvalds 已提交
16 17 18
#include <linux/init.h>
#include <linux/input.h>
#include <linux/interrupt.h>
19
#include <linux/jiffies.h>
L
Linus Torvalds 已提交
20 21 22 23 24 25 26 27 28 29 30 31
#include <linux/module.h>
#include <linux/slab.h>

#include <asm/arch/corgi.h>
#include <asm/arch/hardware.h>
#include <asm/arch/pxa-regs.h>
#include <asm/hardware/scoop.h>

#define KB_ROWS				8
#define KB_COLS				12
#define KB_ROWMASK(r)		(1 << (r))
#define SCANCODE(r,c)		( ((r)<<4) + (c) + 1 )
32 33
/* zero code, 124 scancodes */
#define	NR_SCANCODES		( SCANCODE(KB_ROWS-1,KB_COLS-1) +1 +1 )
L
Linus Torvalds 已提交
34

35 36
#define SCAN_INTERVAL		(50) /* ms */
#define HINGE_SCAN_INTERVAL	(250) /* ms */
L
Linus Torvalds 已提交
37 38 39 40

#define CORGI_KEY_CALENDER	KEY_F1
#define CORGI_KEY_ADDRESS	KEY_F2
#define CORGI_KEY_FN		KEY_F3
41
#define CORGI_KEY_CANCEL	KEY_F4
L
Linus Torvalds 已提交
42 43 44 45 46 47 48
#define CORGI_KEY_OFF		KEY_SUSPEND
#define CORGI_KEY_EXOK		KEY_F5
#define CORGI_KEY_EXCANCEL	KEY_F6
#define CORGI_KEY_EXJOGDOWN	KEY_F7
#define CORGI_KEY_EXJOGUP	KEY_F8
#define CORGI_KEY_JAP1		KEY_LEFTCTRL
#define CORGI_KEY_JAP2		KEY_LEFTALT
49
#define CORGI_KEY_MAIL		KEY_F10
L
Linus Torvalds 已提交
50 51 52 53 54 55 56 57 58 59
#define CORGI_KEY_OK		KEY_F11
#define CORGI_KEY_MENU		KEY_F12

static unsigned char corgikbd_keycode[NR_SCANCODES] = {
	0,                                                                                                                /* 0 */
	0, KEY_1, KEY_3, KEY_5, KEY_6, KEY_7, KEY_9, KEY_0, KEY_BACKSPACE, 0, 0, 0, 0, 0, 0, 0, 	                      /* 1-16 */
	0, KEY_2, KEY_4, KEY_R, KEY_Y, KEY_8, KEY_I, KEY_O, KEY_P, 0, 0, 0, 0, 0, 0, 0,                                   /* 17-32 */
	KEY_TAB, KEY_Q, KEY_E, KEY_T, KEY_G, KEY_U, KEY_J, KEY_K, 0, 0, 0, 0, 0, 0, 0, 0,                                 /* 33-48 */
	CORGI_KEY_CALENDER, KEY_W, KEY_S, KEY_F, KEY_V, KEY_H, KEY_M, KEY_L, 0, KEY_RIGHTSHIFT, 0, 0, 0, 0, 0, 0,         /* 49-64 */
	CORGI_KEY_ADDRESS, KEY_A, KEY_D, KEY_C, KEY_B, KEY_N, KEY_DOT, 0, KEY_ENTER, 0, KEY_LEFTSHIFT, 0, 0, 0, 0, 0, 	  /* 65-80 */
60 61
	CORGI_KEY_MAIL, KEY_Z, KEY_X, KEY_MINUS, KEY_SPACE, KEY_COMMA, 0, KEY_UP, 0, 0, 0, CORGI_KEY_FN, 0, 0, 0, 0,            /* 81-96 */
	KEY_SYSRQ, CORGI_KEY_JAP1, CORGI_KEY_JAP2, CORGI_KEY_CANCEL, CORGI_KEY_OK, CORGI_KEY_MENU, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0, 0, 0, 0,  /* 97-112 */
L
Linus Torvalds 已提交
62 63 64 65 66 67
	CORGI_KEY_OFF, CORGI_KEY_EXOK, CORGI_KEY_EXCANCEL, CORGI_KEY_EXJOGDOWN, CORGI_KEY_EXJOGUP, 0, 0, 0, 0, 0, 0, 0,   /* 113-124 */
};


struct corgikbd {
	unsigned char keycode[ARRAY_SIZE(corgikbd_keycode)];
68
	struct input_dev *input;
L
Linus Torvalds 已提交
69 70 71 72

	spinlock_t lock;
	struct timer_list timer;
	struct timer_list htimer;
73 74 75

	unsigned int suspended;
	unsigned long suspend_jiffies;
L
Linus Torvalds 已提交
76 77 78 79 80 81 82 83 84 85 86 87 88 89
};

#define KB_DISCHARGE_DELAY	10
#define KB_ACTIVATE_DELAY	10

/* Helper functions for reading the keyboard matrix
 * Note: We should really be using pxa_gpio_mode to alter GPDR but it
 *       requires a function call per GPIO bit which is excessive
 *       when we need to access 12 bits at once multiple times.
 * These functions must be called within local_irq_save()/local_irq_restore()
 * or similar.
 */
static inline void corgikbd_discharge_all(void)
{
90
	/* STROBE All HiZ */
L
Linus Torvalds 已提交
91 92 93 94 95 96
	GPCR2  = CORGI_GPIO_ALL_STROBE_BIT;
	GPDR2 &= ~CORGI_GPIO_ALL_STROBE_BIT;
}

static inline void corgikbd_activate_all(void)
{
97
	/* STROBE ALL -> High */
L
Linus Torvalds 已提交
98 99 100 101 102
	GPSR2  = CORGI_GPIO_ALL_STROBE_BIT;
	GPDR2 |= CORGI_GPIO_ALL_STROBE_BIT;

	udelay(KB_DISCHARGE_DELAY);

103
	/* Clear any interrupts we may have triggered when altering the GPIO lines */
L
Linus Torvalds 已提交
104 105 106 107 108 109
	GEDR1 = CORGI_GPIO_HIGH_SENSE_BIT;
	GEDR2 = CORGI_GPIO_LOW_SENSE_BIT;
}

static inline void corgikbd_activate_col(int col)
{
110
	/* STROBE col -> High, not col -> HiZ */
L
Linus Torvalds 已提交
111 112 113 114 115 116
	GPSR2 = CORGI_GPIO_STROBE_BIT(col);
	GPDR2 = (GPDR2 & ~CORGI_GPIO_ALL_STROBE_BIT) | CORGI_GPIO_STROBE_BIT(col);
}

static inline void corgikbd_reset_col(int col)
{
117
	/* STROBE col -> Low */
L
Linus Torvalds 已提交
118
	GPCR2 = CORGI_GPIO_STROBE_BIT(col);
119
	/* STROBE col -> out, not col -> HiZ */
L
Linus Torvalds 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132 133
	GPDR2 = (GPDR2 & ~CORGI_GPIO_ALL_STROBE_BIT) | CORGI_GPIO_STROBE_BIT(col);
}

#define GET_ROWS_STATUS(c)	(((GPLR1 & CORGI_GPIO_HIGH_SENSE_BIT) >> CORGI_GPIO_HIGH_SENSE_RSHIFT) | ((GPLR2 & CORGI_GPIO_LOW_SENSE_BIT) << CORGI_GPIO_LOW_SENSE_LSHIFT))

/*
 * The corgi keyboard only generates interrupts when a key is pressed.
 * When a key is pressed, we enable a timer which then scans the
 * keyboard to detect when the key is released.
 */

/* Scan the hardware keyboard and push any changes up through the input layer */
static void corgikbd_scankeyboard(struct corgikbd *corgikbd_data, struct pt_regs *regs)
{
134
	unsigned int row, col, rowd;
L
Linus Torvalds 已提交
135 136 137
	unsigned long flags;
	unsigned int num_pressed;

138 139 140
	if (corgikbd_data->suspended)
		return;

L
Linus Torvalds 已提交
141 142 143
	spin_lock_irqsave(&corgikbd_data->lock, flags);

	if (regs)
144
		input_regs(corgikbd_data->input, regs);
L
Linus Torvalds 已提交
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160

	num_pressed = 0;
	for (col = 0; col < KB_COLS; col++) {
		/*
		 * Discharge the output driver capacitatance
		 * in the keyboard matrix. (Yes it is significant..)
		 */

		corgikbd_discharge_all();
		udelay(KB_DISCHARGE_DELAY);

		corgikbd_activate_col(col);
		udelay(KB_ACTIVATE_DELAY);

		rowd = GET_ROWS_STATUS(col);
		for (row = 0; row < KB_ROWS; row++) {
161 162
			unsigned int scancode, pressed;

L
Linus Torvalds 已提交
163
			scancode = SCANCODE(row, col);
164 165
			pressed = rowd & KB_ROWMASK(row);

166
			input_report_key(corgikbd_data->input, corgikbd_data->keycode[scancode], pressed);
167 168

			if (pressed)
L
Linus Torvalds 已提交
169
				num_pressed++;
170 171 172

			if (pressed && (corgikbd_data->keycode[scancode] == CORGI_KEY_OFF)
					&& time_after(jiffies, corgikbd_data->suspend_jiffies + HZ)) {
173
				input_event(corgikbd_data->input, EV_PWR, CORGI_KEY_OFF, 1);
174 175
				corgikbd_data->suspend_jiffies=jiffies;
			}
L
Linus Torvalds 已提交
176 177 178 179 180 181
		}
		corgikbd_reset_col(col);
	}

	corgikbd_activate_all();

182
	input_sync(corgikbd_data->input);
L
Linus Torvalds 已提交
183 184 185

	/* if any keys are pressed, enable the timer */
	if (num_pressed)
186
		mod_timer(&corgikbd_data->timer, jiffies + msecs_to_jiffies(SCAN_INTERVAL));
L
Linus Torvalds 已提交
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219

	spin_unlock_irqrestore(&corgikbd_data->lock, flags);
}

/*
 * corgi keyboard interrupt handler.
 */
static irqreturn_t corgikbd_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
	struct corgikbd *corgikbd_data = dev_id;

	if (!timer_pending(&corgikbd_data->timer)) {
		/** wait chattering delay **/
		udelay(20);
		corgikbd_scankeyboard(corgikbd_data, regs);
	}

	return IRQ_HANDLED;
}

/*
 * corgi timer checking for released keys
 */
static void corgikbd_timer_callback(unsigned long data)
{
	struct corgikbd *corgikbd_data = (struct corgikbd *) data;
	corgikbd_scankeyboard(corgikbd_data, NULL);
}

/*
 * The hinge switches generate no interrupt so they need to be
 * monitored by a timer.
 *
220 221 222 223 224
 * We debounce the switches and pass them to the input system.
 *
 *  gprr == 0x00 - Keyboard with Landscape Screen
 *          0x08 - No Keyboard with Portrait Screen
 *          0x0c - Keyboard and Screen Closed
L
Linus Torvalds 已提交
225 226
 */

227
#define READ_GPIO_BIT(x)    (GPLR(x) & GPIO_bit(x))
L
Linus Torvalds 已提交
228 229 230 231 232 233 234 235 236 237
#define HINGE_STABLE_COUNT 2
static int sharpsl_hinge_state;
static int hinge_count;

static void corgikbd_hinge_timer(unsigned long data)
{
	struct corgikbd *corgikbd_data = (struct corgikbd *) data;
	unsigned long gprr;
	unsigned long flags;

238
	gprr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_GPRR) & (CORGI_SCP_SWA | CORGI_SCP_SWB);
239
	gprr |= (READ_GPIO_BIT(CORGI_GPIO_AK_INT) != 0);
L
Linus Torvalds 已提交
240 241 242 243 244 245 246 247
	if (gprr != sharpsl_hinge_state) {
		hinge_count = 0;
		sharpsl_hinge_state = gprr;
	} else if (hinge_count < HINGE_STABLE_COUNT) {
		hinge_count++;
		if (hinge_count >= HINGE_STABLE_COUNT) {
			spin_lock_irqsave(&corgikbd_data->lock, flags);

248 249
			input_report_switch(corgikbd_data->input, SW_0, ((sharpsl_hinge_state & CORGI_SCP_SWA) != 0));
			input_report_switch(corgikbd_data->input, SW_1, ((sharpsl_hinge_state & CORGI_SCP_SWB) != 0));
250
			input_report_switch(corgikbd_data->input, SW_2, (READ_GPIO_BIT(CORGI_GPIO_AK_INT) != 0));
251
			input_sync(corgikbd_data->input);
L
Linus Torvalds 已提交
252 253 254 255

			spin_unlock_irqrestore(&corgikbd_data->lock, flags);
		}
	}
256
	mod_timer(&corgikbd_data->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL));
L
Linus Torvalds 已提交
257 258
}

259
#ifdef CONFIG_PM
260
static int corgikbd_suspend(struct platform_device *dev, pm_message_t state)
261
{
262
	int i;
263
	struct corgikbd *corgikbd = platform_get_drvdata(dev);
264

265
	corgikbd->suspended = 1;
266 267 268 269
	/* strobe 0 is the power key so this can't be made an input for
	   powersaving therefore i = 1 */
	for (i = 1; i < CORGI_KEY_STROBE_NUM; i++)
		pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_IN);
270

271 272 273
	return 0;
}

274
static int corgikbd_resume(struct platform_device *dev)
275
{
276
	int i;
277
	struct corgikbd *corgikbd = platform_get_drvdata(dev);
278

279 280 281
	for (i = 1; i < CORGI_KEY_STROBE_NUM; i++)
		pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_OUT | GPIO_DFLT_HIGH);

282 283 284
	/* Upon resume, ignore the suspend key for a short while */
	corgikbd->suspend_jiffies=jiffies;
	corgikbd->suspended = 0;
285 286 287 288 289 290 291 292

	return 0;
}
#else
#define corgikbd_suspend	NULL
#define corgikbd_resume		NULL
#endif

293
static int __init corgikbd_probe(struct platform_device *pdev)
L
Linus Torvalds 已提交
294 295
{
	struct corgikbd *corgikbd;
296 297
	struct input_dev *input_dev;
	int i;
L
Linus Torvalds 已提交
298

299
	corgikbd = kzalloc(sizeof(struct corgikbd), GFP_KERNEL);
300 301 302 303
	input_dev = input_allocate_device();
	if (!corgikbd || !input_dev) {
		kfree(corgikbd);
		input_free_device(input_dev);
L
Linus Torvalds 已提交
304
		return -ENOMEM;
305
	}
L
Linus Torvalds 已提交
306

307
	platform_set_drvdata(pdev, corgikbd);
L
Linus Torvalds 已提交
308

309
	corgikbd->input = input_dev;
310
	spin_lock_init(&corgikbd->lock);
L
Linus Torvalds 已提交
311 312 313 314 315 316 317 318 319 320 321

	/* Init Keyboard rescan timer */
	init_timer(&corgikbd->timer);
	corgikbd->timer.function = corgikbd_timer_callback;
	corgikbd->timer.data = (unsigned long) corgikbd;

	/* Init Hinge Timer */
	init_timer(&corgikbd->htimer);
	corgikbd->htimer.function = corgikbd_hinge_timer;
	corgikbd->htimer.data = (unsigned long) corgikbd;

322 323
	corgikbd->suspend_jiffies=jiffies;

L
Linus Torvalds 已提交
324
	memcpy(corgikbd->keycode, corgikbd_keycode, sizeof(corgikbd->keycode));
325 326 327 328 329 330 331

	input_dev->name = "Corgi Keyboard";
	input_dev->phys = "corgikbd/input0";
	input_dev->id.bustype = BUS_HOST;
	input_dev->id.vendor = 0x0001;
	input_dev->id.product = 0x0001;
	input_dev->id.version = 0x0100;
332
	input_dev->cdev.dev = &pdev->dev;
333 334 335 336 337 338 339
	input_dev->private = corgikbd;

	input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_PWR) | BIT(EV_SW);
	input_dev->keycode = corgikbd->keycode;
	input_dev->keycodesize = sizeof(unsigned char);
	input_dev->keycodemax = ARRAY_SIZE(corgikbd_keycode);

L
Linus Torvalds 已提交
340
	for (i = 0; i < ARRAY_SIZE(corgikbd_keycode); i++)
341 342 343 344
		set_bit(corgikbd->keycode[i], input_dev->keybit);
	clear_bit(0, input_dev->keybit);
	set_bit(SW_0, input_dev->swbit);
	set_bit(SW_1, input_dev->swbit);
345
	set_bit(SW_2, input_dev->swbit);
346 347

	input_register_device(corgikbd->input);
L
Linus Torvalds 已提交
348

349
	mod_timer(&corgikbd->htimer, jiffies + msecs_to_jiffies(HINGE_SCAN_INTERVAL));
L
Linus Torvalds 已提交
350 351 352 353 354

	/* Setup sense interrupts - RisingEdge Detect, sense lines as inputs */
	for (i = 0; i < CORGI_KEY_SENSE_NUM; i++) {
		pxa_gpio_mode(CORGI_GPIO_KEY_SENSE(i) | GPIO_IN);
		if (request_irq(CORGI_IRQ_GPIO_KEY_SENSE(i), corgikbd_interrupt,
R
Russell King 已提交
355 356
				SA_INTERRUPT | SA_TRIGGER_RISING,
				"corgikbd", corgikbd))
L
Linus Torvalds 已提交
357 358 359 360 361 362 363
			printk(KERN_WARNING "corgikbd: Can't get IRQ: %d!\n", i);
	}

	/* Set Strobe lines as outputs - set high */
	for (i = 0; i < CORGI_KEY_STROBE_NUM; i++)
		pxa_gpio_mode(CORGI_GPIO_KEY_STROBE(i) | GPIO_OUT | GPIO_DFLT_HIGH);

364 365 366
	/* Setup the headphone jack as an input */
	pxa_gpio_mode(CORGI_GPIO_AK_INT | GPIO_IN);

L
Linus Torvalds 已提交
367 368 369
	return 0;
}

370
static int corgikbd_remove(struct platform_device *pdev)
L
Linus Torvalds 已提交
371 372
{
	int i;
373
	struct corgikbd *corgikbd = platform_get_drvdata(pdev);
L
Linus Torvalds 已提交
374 375 376 377 378 379 380

	for (i = 0; i < CORGI_KEY_SENSE_NUM; i++)
		free_irq(CORGI_IRQ_GPIO_KEY_SENSE(i), corgikbd);

	del_timer_sync(&corgikbd->htimer);
	del_timer_sync(&corgikbd->timer);

381
	input_unregister_device(corgikbd->input);
L
Linus Torvalds 已提交
382 383 384 385 386 387

	kfree(corgikbd);

	return 0;
}

388
static struct platform_driver corgikbd_driver = {
L
Linus Torvalds 已提交
389 390
	.probe		= corgikbd_probe,
	.remove		= corgikbd_remove,
391 392
	.suspend	= corgikbd_suspend,
	.resume		= corgikbd_resume,
393 394 395
	.driver		= {
		.name	= "corgi-keyboard",
	},
L
Linus Torvalds 已提交
396 397 398 399
};

static int __devinit corgikbd_init(void)
{
400
	return platform_driver_register(&corgikbd_driver);
L
Linus Torvalds 已提交
401 402 403 404
}

static void __exit corgikbd_exit(void)
{
405
	platform_driver_unregister(&corgikbd_driver);
L
Linus Torvalds 已提交
406 407 408 409 410 411 412 413
}

module_init(corgikbd_init);
module_exit(corgikbd_exit);

MODULE_AUTHOR("Richard Purdie <rpurdie@rpsys.net>");
MODULE_DESCRIPTION("Corgi Keyboard Driver");
MODULE_LICENSE("GPLv2");