corgikbd.c 11.7 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
/*
 *  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>
#include <linux/device.h>
#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 32 33 34 35 36 37 38 39 40 41
#include <linux/module.h>
#include <linux/slab.h>
#include <asm/irq.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 )
/* zero code, 124 scancodes + 3 hinge combinations */
#define	NR_SCANCODES		( SCANCODE(KB_ROWS-1,KB_COLS-1) +1 +1 +3 )
#define SCAN_INTERVAL		(HZ/10)

#define HINGE_SCAN_INTERVAL		(HZ/4)

#define CORGI_KEY_CALENDER	KEY_F1
#define CORGI_KEY_ADDRESS	KEY_F2
#define CORGI_KEY_FN		KEY_F3
42
#define CORGI_KEY_CANCEL	KEY_F4
L
Linus Torvalds 已提交
43 44 45 46 47 48 49
#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
50
#define CORGI_KEY_MAIL		KEY_F10
L
Linus Torvalds 已提交
51 52 53 54 55 56 57 58 59 60 61 62 63
#define CORGI_KEY_OK		KEY_F11
#define CORGI_KEY_MENU		KEY_F12
#define CORGI_HINGE_0		KEY_KP0
#define CORGI_HINGE_1		KEY_KP1
#define CORGI_HINGE_2		KEY_KP2

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 */
64 65
	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 已提交
66 67 68 69 70 71 72 73 74 75 76 77 78
	CORGI_KEY_OFF, CORGI_KEY_EXOK, CORGI_KEY_EXCANCEL, CORGI_KEY_EXJOGDOWN, CORGI_KEY_EXJOGUP, 0, 0, 0, 0, 0, 0, 0,   /* 113-124 */
	CORGI_HINGE_0, CORGI_HINGE_1, CORGI_HINGE_2	  /* 125-127 */
};


struct corgikbd {
	unsigned char keycode[ARRAY_SIZE(corgikbd_keycode)];
	struct input_dev input;
	char phys[32];

	spinlock_t lock;
	struct timer_list timer;
	struct timer_list htimer;
79 80 81

	unsigned int suspended;
	unsigned long suspend_jiffies;
L
Linus Torvalds 已提交
82 83 84 85 86 87 88 89 90 91 92 93 94 95
};

#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)
{
96
	/* STROBE All HiZ */
L
Linus Torvalds 已提交
97 98 99 100 101 102
	GPCR2  = CORGI_GPIO_ALL_STROBE_BIT;
	GPDR2 &= ~CORGI_GPIO_ALL_STROBE_BIT;
}

static inline void corgikbd_activate_all(void)
{
103
	/* STROBE ALL -> High */
L
Linus Torvalds 已提交
104 105 106 107 108
	GPSR2  = CORGI_GPIO_ALL_STROBE_BIT;
	GPDR2 |= CORGI_GPIO_ALL_STROBE_BIT;

	udelay(KB_DISCHARGE_DELAY);

109
	/* Clear any interrupts we may have triggered when altering the GPIO lines */
L
Linus Torvalds 已提交
110 111 112 113 114 115
	GEDR1 = CORGI_GPIO_HIGH_SENSE_BIT;
	GEDR2 = CORGI_GPIO_LOW_SENSE_BIT;
}

static inline void corgikbd_activate_col(int col)
{
116
	/* STROBE col -> High, not col -> HiZ */
L
Linus Torvalds 已提交
117 118 119 120 121 122
	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)
{
123
	/* STROBE col -> Low */
L
Linus Torvalds 已提交
124
	GPCR2 = CORGI_GPIO_STROBE_BIT(col);
125
	/* STROBE col -> out, not col -> HiZ */
L
Linus Torvalds 已提交
126 127 128 129 130 131 132 133 134 135 136 137 138 139
	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)
{
140
	unsigned int row, col, rowd;
L
Linus Torvalds 已提交
141 142 143
	unsigned long flags;
	unsigned int num_pressed;

144 145 146
	if (corgikbd_data->suspended)
		return;

L
Linus Torvalds 已提交
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
	spin_lock_irqsave(&corgikbd_data->lock, flags);

	if (regs)
		input_regs(&corgikbd_data->input, regs);

	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++) {
167 168
			unsigned int scancode, pressed;

L
Linus Torvalds 已提交
169
			scancode = SCANCODE(row, col);
170 171 172 173 174
			pressed = rowd & KB_ROWMASK(row);

			input_report_key(&corgikbd_data->input, corgikbd_data->keycode[scancode], pressed);

			if (pressed)
L
Linus Torvalds 已提交
175
				num_pressed++;
176 177 178 179 180 181

			if (pressed && (corgikbd_data->keycode[scancode] == CORGI_KEY_OFF)
					&& time_after(jiffies, corgikbd_data->suspend_jiffies + HZ)) {
				input_event(&corgikbd_data->input, EV_PWR, CORGI_KEY_OFF, 1);
				corgikbd_data->suspend_jiffies=jiffies;
			}
L
Linus Torvalds 已提交
182 183 184 185 186 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 220 221 222 223 224 225
		}
		corgikbd_reset_col(col);
	}

	corgikbd_activate_all();

	input_sync(&corgikbd_data->input);

	/* if any keys are pressed, enable the timer */
	if (num_pressed)
		mod_timer(&corgikbd_data->timer, jiffies + SCAN_INTERVAL);

	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.
 *
226 227 228 229 230
 * 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 已提交
231 232 233 234 235 236 237 238 239 240 241 242
 */

#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;

243
	gprr = read_scoop_reg(&corgiscoop_device.dev, SCOOP_GPRR) & (CORGI_SCP_SWA | CORGI_SCP_SWB);
L
Linus Torvalds 已提交
244 245 246 247 248 249 250 251
	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);

252 253
			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));
L
Linus Torvalds 已提交
254 255 256 257 258 259 260 261
			input_sync(&corgikbd_data->input);

			spin_unlock_irqrestore(&corgikbd_data->lock, flags);
		}
	}
	mod_timer(&corgikbd_data->htimer, jiffies + HINGE_SCAN_INTERVAL);
}

262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
#ifdef CONFIG_PM
static int corgikbd_suspend(struct device *dev, pm_message_t state, uint32_t level)
{
	if (level == SUSPEND_POWER_DOWN) {
		struct corgikbd *corgikbd = dev_get_drvdata(dev);
		corgikbd->suspended = 1;
	}
	return 0;
}

static int corgikbd_resume(struct device *dev, uint32_t level)
{
	if (level == RESUME_POWER_ON) {
		struct corgikbd *corgikbd = dev_get_drvdata(dev);

		/* Upon resume, ignore the suspend key for a short while */
		corgikbd->suspend_jiffies=jiffies;
		corgikbd->suspended = 0;
	}
	return 0;
}
#else
#define corgikbd_suspend	NULL
#define corgikbd_resume		NULL
#endif

L
Linus Torvalds 已提交
288 289 290 291 292
static int __init corgikbd_probe(struct device *dev)
{
	int i;
	struct corgikbd *corgikbd;

293
	corgikbd = kzalloc(sizeof(struct corgikbd), GFP_KERNEL);
L
Linus Torvalds 已提交
294 295 296 297 298 299
	if (!corgikbd)
		return -ENOMEM;

	dev_set_drvdata(dev,corgikbd);
	strcpy(corgikbd->phys, "corgikbd/input0");

300
	spin_lock_init(&corgikbd->lock);
L
Linus Torvalds 已提交
301 302 303 304 305 306 307 308 309 310 311

	/* 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;

312 313
	corgikbd->suspend_jiffies=jiffies;

L
Linus Torvalds 已提交
314 315 316 317 318 319 320 321 322
	init_input_dev(&corgikbd->input);
	corgikbd->input.private = corgikbd;
	corgikbd->input.name = "Corgi Keyboard";
	corgikbd->input.dev = dev;
	corgikbd->input.phys = corgikbd->phys;
	corgikbd->input.id.bustype = BUS_HOST;
	corgikbd->input.id.vendor = 0x0001;
	corgikbd->input.id.product = 0x0001;
	corgikbd->input.id.version = 0x0100;
323
	corgikbd->input.evbit[0] = BIT(EV_KEY) | BIT(EV_REP) | BIT(EV_PWR) | BIT(EV_SW);
L
Linus Torvalds 已提交
324 325 326 327 328 329 330 331
	corgikbd->input.keycode = corgikbd->keycode;
	corgikbd->input.keycodesize = sizeof(unsigned char);
	corgikbd->input.keycodemax = ARRAY_SIZE(corgikbd_keycode);

	memcpy(corgikbd->keycode, corgikbd_keycode, sizeof(corgikbd->keycode));
	for (i = 0; i < ARRAY_SIZE(corgikbd_keycode); i++)
		set_bit(corgikbd->keycode[i], corgikbd->input.keybit);
	clear_bit(0, corgikbd->input.keybit);
332 333
	set_bit(SW_0, corgikbd->input.swbit);
	set_bit(SW_1, corgikbd->input.swbit);
L
Linus Torvalds 已提交
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379

	input_register_device(&corgikbd->input);
	mod_timer(&corgikbd->htimer, jiffies + HINGE_SCAN_INTERVAL);

	/* 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,
						SA_INTERRUPT, "corgikbd", corgikbd))
			printk(KERN_WARNING "corgikbd: Can't get IRQ: %d!\n", i);
		else
			set_irq_type(CORGI_IRQ_GPIO_KEY_SENSE(i),IRQT_RISING);
	}

	/* 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);

	printk(KERN_INFO "input: Corgi Keyboard Registered\n");

	return 0;
}

static int corgikbd_remove(struct device *dev)
{
	int i;
	struct corgikbd *corgikbd = dev_get_drvdata(dev);

	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);

	input_unregister_device(&corgikbd->input);

	kfree(corgikbd);

	return 0;
}

static struct device_driver corgikbd_driver = {
	.name		= "corgi-keyboard",
	.bus		= &platform_bus_type,
	.probe		= corgikbd_probe,
	.remove		= corgikbd_remove,
380 381
	.suspend	= corgikbd_suspend,
	.resume		= corgikbd_resume,
L
Linus Torvalds 已提交
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399
};

static int __devinit corgikbd_init(void)
{
	return driver_register(&corgikbd_driver);
}

static void __exit corgikbd_exit(void)
{
	driver_unregister(&corgikbd_driver);
}

module_init(corgikbd_init);
module_exit(corgikbd_exit);

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