Commit 1d4e547b552213f91f81aadb3db5efc9ba800ef2
1 parent
64a88d5d
Add the LM8323-based keyboard of N810.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4400 c046a42c-6fe2-441c-8c8c-71466251a162
Showing
4 changed files
with
658 additions
and
9 deletions
Makefile
| ... | ... | @@ -52,7 +52,7 @@ OBJS+=block.o |
| 52 | 52 | OBJS+=irq.o |
| 53 | 53 | OBJS+=i2c.o smbus.o smbus_eeprom.o max7310.o max111x.o wm8750.o |
| 54 | 54 | OBJS+=ssd0303.o ssd0323.o ads7846.o stellaris_input.o twl92230.o |
| 55 | -OBJS+=tmp105.o | |
| 55 | +OBJS+=tmp105.o lm832x.o | |
| 56 | 56 | OBJS+=scsi-disk.o cdrom.o |
| 57 | 57 | OBJS+=scsi-generic.o |
| 58 | 58 | OBJS+=usb.o usb-hub.o usb-linux.o usb-hid.o usb-msd.o usb-wacom.o usb-serial.o | ... | ... |
hw/i2c.h
| ... | ... | @@ -84,4 +84,8 @@ struct i2c_slave *tmp105_init(i2c_bus *bus, qemu_irq alarm); |
| 84 | 84 | void tmp105_reset(i2c_slave *i2c); |
| 85 | 85 | void tmp105_set(i2c_slave *i2c, int temp); |
| 86 | 86 | |
| 87 | +/* lm832x.c */ | |
| 88 | +struct i2c_slave *lm8323_init(i2c_bus *bus, qemu_irq nirq); | |
| 89 | +void lm832x_key_event(struct i2c_slave *i2c, int key, int state); | |
| 90 | + | |
| 87 | 91 | #endif | ... | ... |
hw/lm832x.c
0 → 100644
| 1 | +/* | |
| 2 | + * National Semiconductor LM8322/8323 GPIO keyboard & PWM chips. | |
| 3 | + * | |
| 4 | + * Copyright (C) 2008 Nokia Corporation | |
| 5 | + * Written by Andrzej Zaborowski <andrew@openedhand.com> | |
| 6 | + * | |
| 7 | + * This program is free software; you can redistribute it and/or | |
| 8 | + * modify it under the terms of the GNU General Public License as | |
| 9 | + * published by the Free Software Foundation; either version 2 or | |
| 10 | + * (at your option) version 3 of the License. | |
| 11 | + * | |
| 12 | + * This program is distributed in the hope that it will be useful, | |
| 13 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 14 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 15 | + * GNU General Public License for more details. | |
| 16 | + * | |
| 17 | + * You should have received a copy of the GNU General Public License | |
| 18 | + * along with this program; if not, write to the Free Software | |
| 19 | + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, | |
| 20 | + * MA 02111-1307 USA | |
| 21 | + */ | |
| 22 | + | |
| 23 | +#include "hw.h" | |
| 24 | +#include "i2c.h" | |
| 25 | +#include "qemu-timer.h" | |
| 26 | +#include "console.h" | |
| 27 | + | |
| 28 | +struct lm_kbd_s { | |
| 29 | + i2c_slave i2c; | |
| 30 | + int i2c_dir; | |
| 31 | + int i2c_cycle; | |
| 32 | + int reg; | |
| 33 | + | |
| 34 | + qemu_irq nirq; | |
| 35 | + uint16_t model; | |
| 36 | + | |
| 37 | + struct { | |
| 38 | + qemu_irq out[2]; | |
| 39 | + int in[2][2]; | |
| 40 | + } mux; | |
| 41 | + | |
| 42 | + uint8_t config; | |
| 43 | + uint8_t status; | |
| 44 | + uint8_t acttime; | |
| 45 | + uint8_t error; | |
| 46 | + uint8_t clock; | |
| 47 | + | |
| 48 | + struct { | |
| 49 | + uint16_t pull; | |
| 50 | + uint16_t mask; | |
| 51 | + uint16_t dir; | |
| 52 | + uint16_t level; | |
| 53 | + qemu_irq out[16]; | |
| 54 | + } gpio; | |
| 55 | + | |
| 56 | + struct { | |
| 57 | + uint8_t dbnctime; | |
| 58 | + uint8_t size; | |
| 59 | + int start; | |
| 60 | + int len; | |
| 61 | + uint8_t fifo[16]; | |
| 62 | + } kbd; | |
| 63 | + | |
| 64 | + struct { | |
| 65 | + uint16_t file[256]; | |
| 66 | + uint8_t faddr; | |
| 67 | + uint8_t addr[3]; | |
| 68 | + QEMUTimer *tm[3]; | |
| 69 | + } pwm; | |
| 70 | +}; | |
| 71 | + | |
| 72 | +#define INT_KEYPAD (1 << 0) | |
| 73 | +#define INT_ERROR (1 << 3) | |
| 74 | +#define INT_NOINIT (1 << 4) | |
| 75 | +#define INT_PWMEND(n) (1 << (5 + n)) | |
| 76 | + | |
| 77 | +#define ERR_BADPAR (1 << 0) | |
| 78 | +#define ERR_CMDUNK (1 << 1) | |
| 79 | +#define ERR_KEYOVR (1 << 2) | |
| 80 | +#define ERR_FIFOOVR (1 << 6) | |
| 81 | + | |
| 82 | +static void lm_kbd_irq_update(struct lm_kbd_s *s) | |
| 83 | +{ | |
| 84 | + qemu_set_irq(s->nirq, !s->status); | |
| 85 | +} | |
| 86 | + | |
| 87 | +static void lm_kbd_gpio_update(struct lm_kbd_s *s) | |
| 88 | +{ | |
| 89 | +} | |
| 90 | + | |
| 91 | +static void lm_kbd_reset(struct lm_kbd_s *s) | |
| 92 | +{ | |
| 93 | + s->config = 0x80; | |
| 94 | + s->status = INT_NOINIT; | |
| 95 | + s->acttime = 125; | |
| 96 | + s->kbd.dbnctime = 3; | |
| 97 | + s->kbd.size = 0x33; | |
| 98 | + s->clock = 0x08; | |
| 99 | + | |
| 100 | + lm_kbd_irq_update(s); | |
| 101 | + lm_kbd_gpio_update(s); | |
| 102 | +} | |
| 103 | + | |
| 104 | +static void lm_kbd_error(struct lm_kbd_s *s, int err) | |
| 105 | +{ | |
| 106 | + s->error |= err; | |
| 107 | + s->status |= INT_ERROR; | |
| 108 | + lm_kbd_irq_update(s); | |
| 109 | +} | |
| 110 | + | |
| 111 | +static void lm_kbd_pwm_tick(struct lm_kbd_s *s, int line) | |
| 112 | +{ | |
| 113 | +} | |
| 114 | + | |
| 115 | +static void lm_kbd_pwm_start(struct lm_kbd_s *s, int line) | |
| 116 | +{ | |
| 117 | + lm_kbd_pwm_tick(s, line); | |
| 118 | +} | |
| 119 | + | |
| 120 | +static void lm_kbd_pwm0_tick(void *opaque) | |
| 121 | +{ | |
| 122 | + lm_kbd_pwm_tick(opaque, 0); | |
| 123 | +} | |
| 124 | +static void lm_kbd_pwm1_tick(void *opaque) | |
| 125 | +{ | |
| 126 | + lm_kbd_pwm_tick(opaque, 1); | |
| 127 | +} | |
| 128 | +static void lm_kbd_pwm2_tick(void *opaque) | |
| 129 | +{ | |
| 130 | + lm_kbd_pwm_tick(opaque, 2); | |
| 131 | +} | |
| 132 | + | |
| 133 | +enum { | |
| 134 | + LM832x_CMD_READ_ID = 0x80, /* Read chip ID. */ | |
| 135 | + LM832x_CMD_WRITE_CFG = 0x81, /* Set configuration item. */ | |
| 136 | + LM832x_CMD_READ_INT = 0x82, /* Get interrupt status. */ | |
| 137 | + LM832x_CMD_RESET = 0x83, /* Reset, same as external one */ | |
| 138 | + LM823x_CMD_WRITE_PULL_DOWN = 0x84, /* Select GPIO pull-up/down. */ | |
| 139 | + LM832x_CMD_WRITE_PORT_SEL = 0x85, /* Select GPIO in/out. */ | |
| 140 | + LM832x_CMD_WRITE_PORT_STATE = 0x86, /* Set GPIO pull-up/down. */ | |
| 141 | + LM832x_CMD_READ_PORT_SEL = 0x87, /* Get GPIO in/out. */ | |
| 142 | + LM832x_CMD_READ_PORT_STATE = 0x88, /* Get GPIO pull-up/down. */ | |
| 143 | + LM832x_CMD_READ_FIFO = 0x89, /* Read byte from FIFO. */ | |
| 144 | + LM832x_CMD_RPT_READ_FIFO = 0x8a, /* Read FIFO (no increment). */ | |
| 145 | + LM832x_CMD_SET_ACTIVE = 0x8b, /* Set active time. */ | |
| 146 | + LM832x_CMD_READ_ERROR = 0x8c, /* Get error status. */ | |
| 147 | + LM832x_CMD_READ_ROTATOR = 0x8e, /* Read rotator status. */ | |
| 148 | + LM832x_CMD_SET_DEBOUNCE = 0x8f, /* Set debouncing time. */ | |
| 149 | + LM832x_CMD_SET_KEY_SIZE = 0x90, /* Set keypad size. */ | |
| 150 | + LM832x_CMD_READ_KEY_SIZE = 0x91, /* Get keypad size. */ | |
| 151 | + LM832x_CMD_READ_CFG = 0x92, /* Get configuration item. */ | |
| 152 | + LM832x_CMD_WRITE_CLOCK = 0x93, /* Set clock config. */ | |
| 153 | + LM832x_CMD_READ_CLOCK = 0x94, /* Get clock config. */ | |
| 154 | + LM832x_CMD_PWM_WRITE = 0x95, /* Write PWM script. */ | |
| 155 | + LM832x_CMD_PWM_START = 0x96, /* Start PWM engine. */ | |
| 156 | + LM832x_CMD_PWM_STOP = 0x97, /* Stop PWM engine. */ | |
| 157 | +}; | |
| 158 | + | |
| 159 | +#define LM832x_MAX_KPX 8 | |
| 160 | +#define LM832x_MAX_KPY 12 | |
| 161 | + | |
| 162 | +static uint8_t lm_kbd_read(struct lm_kbd_s *s, int reg, int byte) | |
| 163 | +{ | |
| 164 | + int ret; | |
| 165 | + | |
| 166 | + switch (reg) { | |
| 167 | + case LM832x_CMD_READ_ID: | |
| 168 | + ret = 0x0400; | |
| 169 | + break; | |
| 170 | + | |
| 171 | + case LM832x_CMD_READ_INT: | |
| 172 | + ret = s->status; | |
| 173 | + if (!(s->status & INT_NOINIT)) { | |
| 174 | + s->status = 0; | |
| 175 | + lm_kbd_irq_update(s); | |
| 176 | + } | |
| 177 | + break; | |
| 178 | + | |
| 179 | + case LM832x_CMD_READ_PORT_SEL: | |
| 180 | + ret = s->gpio.dir; | |
| 181 | + break; | |
| 182 | + case LM832x_CMD_READ_PORT_STATE: | |
| 183 | + ret = s->gpio.mask; | |
| 184 | + break; | |
| 185 | + | |
| 186 | + case LM832x_CMD_READ_FIFO: | |
| 187 | + if (s->kbd.len <= 1) | |
| 188 | + return 0x00; | |
| 189 | + | |
| 190 | + /* Example response from the two commands after a INT_KEYPAD | |
| 191 | + * interrupt caused by the key 0x3c being pressed: | |
| 192 | + * RPT_READ_FIFO: 55 bc 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01 | |
| 193 | + * READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01 | |
| 194 | + * RPT_READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01 | |
| 195 | + * | |
| 196 | + * 55 is the code of the key release event serviced in the previous | |
| 197 | + * interrupt handling. | |
| 198 | + * | |
| 199 | + * TODO: find out whether the FIFO is advanced a single character | |
| 200 | + * before reading every byte or the whole size of the FIFO at the | |
| 201 | + * last LM832x_CMD_READ_FIFO. This affects LM832x_CMD_RPT_READ_FIFO | |
| 202 | + * output in cases where there are more than one event in the FIFO. | |
| 203 | + * Assume 0xbc and 0x3c events are in the FIFO: | |
| 204 | + * RPT_READ_FIFO: 55 bc 3c 00 4e ff 0a 50 08 00 29 d9 08 01 c9 | |
| 205 | + * READ_FIFO: bc 3c 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 | |
| 206 | + * Does RPT_READ_FIFO now return 0xbc and 0x3c or only 0x3c? | |
| 207 | + */ | |
| 208 | + s->kbd.start ++; | |
| 209 | + s->kbd.start &= sizeof(s->kbd.fifo) - 1; | |
| 210 | + s->kbd.len --; | |
| 211 | + | |
| 212 | + return s->kbd.fifo[s->kbd.start]; | |
| 213 | + case LM832x_CMD_RPT_READ_FIFO: | |
| 214 | + if (byte >= s->kbd.len) | |
| 215 | + return 0x00; | |
| 216 | + | |
| 217 | + return s->kbd.fifo[(s->kbd.start + byte) & (sizeof(s->kbd.fifo) - 1)]; | |
| 218 | + | |
| 219 | + case LM832x_CMD_READ_ERROR: | |
| 220 | + return s->error; | |
| 221 | + | |
| 222 | + case LM832x_CMD_READ_ROTATOR: | |
| 223 | + return 0; | |
| 224 | + | |
| 225 | + case LM832x_CMD_READ_KEY_SIZE: | |
| 226 | + return s->kbd.size; | |
| 227 | + | |
| 228 | + case LM832x_CMD_READ_CFG: | |
| 229 | + return s->config & 0xf; | |
| 230 | + | |
| 231 | + case LM832x_CMD_READ_CLOCK: | |
| 232 | + return (s->clock & 0xfc) | 2; | |
| 233 | + | |
| 234 | + default: | |
| 235 | + lm_kbd_error(s, ERR_CMDUNK); | |
| 236 | + fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, reg); | |
| 237 | + return 0x00; | |
| 238 | + } | |
| 239 | + | |
| 240 | + return ret >> (byte << 3); | |
| 241 | +} | |
| 242 | + | |
| 243 | +static void lm_kbd_write(struct lm_kbd_s *s, int reg, int byte, uint8_t value) | |
| 244 | +{ | |
| 245 | + switch (reg) { | |
| 246 | + case LM832x_CMD_WRITE_CFG: | |
| 247 | + s->config = value; | |
| 248 | + /* This must be done whenever s->mux.in is updated (never). */ | |
| 249 | + if ((s->config >> 1) & 1) /* MUX1EN */ | |
| 250 | + qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 0) & 1]); | |
| 251 | + if ((s->config >> 3) & 1) /* MUX2EN */ | |
| 252 | + qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 2) & 1]); | |
| 253 | + /* TODO: check that this is issued only following the chip reset | |
| 254 | + * and not in the middle of operation and that it is followed by | |
| 255 | + * the GPIO ports re-resablishing through WRITE_PORT_SEL and | |
| 256 | + * WRITE_PORT_STATE (using a timer perhaps) and otherwise output | |
| 257 | + * warnings. */ | |
| 258 | + s->status = 0; | |
| 259 | + lm_kbd_irq_update(s); | |
| 260 | + s->kbd.len = 0; | |
| 261 | + s->kbd.start = 0; | |
| 262 | + s->reg = -1; | |
| 263 | + break; | |
| 264 | + | |
| 265 | + case LM832x_CMD_RESET: | |
| 266 | + if (value == 0xaa) | |
| 267 | + lm_kbd_reset(s); | |
| 268 | + else | |
| 269 | + lm_kbd_error(s, ERR_BADPAR); | |
| 270 | + s->reg = -1; | |
| 271 | + break; | |
| 272 | + | |
| 273 | + case LM823x_CMD_WRITE_PULL_DOWN: | |
| 274 | + if (!byte) | |
| 275 | + s->gpio.pull = value; | |
| 276 | + else { | |
| 277 | + s->gpio.pull |= value << 8; | |
| 278 | + lm_kbd_gpio_update(s); | |
| 279 | + s->reg = -1; | |
| 280 | + } | |
| 281 | + break; | |
| 282 | + case LM832x_CMD_WRITE_PORT_SEL: | |
| 283 | + if (!byte) | |
| 284 | + s->gpio.dir = value; | |
| 285 | + else { | |
| 286 | + s->gpio.dir |= value << 8; | |
| 287 | + lm_kbd_gpio_update(s); | |
| 288 | + s->reg = -1; | |
| 289 | + } | |
| 290 | + break; | |
| 291 | + case LM832x_CMD_WRITE_PORT_STATE: | |
| 292 | + if (!byte) | |
| 293 | + s->gpio.mask = value; | |
| 294 | + else { | |
| 295 | + s->gpio.mask |= value << 8; | |
| 296 | + lm_kbd_gpio_update(s); | |
| 297 | + s->reg = -1; | |
| 298 | + } | |
| 299 | + break; | |
| 300 | + | |
| 301 | + case LM832x_CMD_SET_ACTIVE: | |
| 302 | + s->acttime = value; | |
| 303 | + s->reg = -1; | |
| 304 | + break; | |
| 305 | + | |
| 306 | + case LM832x_CMD_SET_DEBOUNCE: | |
| 307 | + s->kbd.dbnctime = value; | |
| 308 | + s->reg = -1; | |
| 309 | + if (!value) | |
| 310 | + lm_kbd_error(s, ERR_BADPAR); | |
| 311 | + break; | |
| 312 | + | |
| 313 | + case LM832x_CMD_SET_KEY_SIZE: | |
| 314 | + s->kbd.size = value; | |
| 315 | + s->reg = -1; | |
| 316 | + if ( | |
| 317 | + (value & 0xf) < 3 || (value & 0xf) > LM832x_MAX_KPY || | |
| 318 | + (value >> 4) < 3 || (value >> 4) > LM832x_MAX_KPX) | |
| 319 | + lm_kbd_error(s, ERR_BADPAR); | |
| 320 | + break; | |
| 321 | + | |
| 322 | + case LM832x_CMD_WRITE_CLOCK: | |
| 323 | + s->clock = value; | |
| 324 | + s->reg = -1; | |
| 325 | + if ((value & 3) && (value & 3) != 3) { | |
| 326 | + lm_kbd_error(s, ERR_BADPAR); | |
| 327 | + fprintf(stderr, "%s: invalid clock setting in RCPWM\n", | |
| 328 | + __FUNCTION__); | |
| 329 | + } | |
| 330 | + /* TODO: Validate that the command is only issued once */ | |
| 331 | + break; | |
| 332 | + | |
| 333 | + case LM832x_CMD_PWM_WRITE: | |
| 334 | + if (byte == 0) { | |
| 335 | + if (!(value & 3) || (value >> 2) > 59) { | |
| 336 | + lm_kbd_error(s, ERR_BADPAR); | |
| 337 | + s->reg = -1; | |
| 338 | + break; | |
| 339 | + } | |
| 340 | + | |
| 341 | + s->pwm.faddr = value; | |
| 342 | + s->pwm.file[s->pwm.faddr] = 0; | |
| 343 | + } else if (byte == 1) { | |
| 344 | + s->pwm.file[s->pwm.faddr] |= value << 8; | |
| 345 | + } else if (byte == 2) { | |
| 346 | + s->pwm.file[s->pwm.faddr] |= value << 0; | |
| 347 | + s->reg = -1; | |
| 348 | + } | |
| 349 | + break; | |
| 350 | + case LM832x_CMD_PWM_START: | |
| 351 | + s->reg = -1; | |
| 352 | + if (!(value & 3) || (value >> 2) > 59) { | |
| 353 | + lm_kbd_error(s, ERR_BADPAR); | |
| 354 | + break; | |
| 355 | + } | |
| 356 | + | |
| 357 | + s->pwm.addr[(value & 3) - 1] = value >> 2; | |
| 358 | + lm_kbd_pwm_start(s, (value & 3) - 1); | |
| 359 | + break; | |
| 360 | + case LM832x_CMD_PWM_STOP: | |
| 361 | + s->reg = -1; | |
| 362 | + if (!(value & 3)) { | |
| 363 | + lm_kbd_error(s, ERR_BADPAR); | |
| 364 | + break; | |
| 365 | + } | |
| 366 | + | |
| 367 | + qemu_del_timer(s->pwm.tm[(value & 3) - 1]); | |
| 368 | + break; | |
| 369 | + | |
| 370 | + case -1: | |
| 371 | + lm_kbd_error(s, ERR_BADPAR); | |
| 372 | + break; | |
| 373 | + default: | |
| 374 | + lm_kbd_error(s, ERR_CMDUNK); | |
| 375 | + fprintf(stderr, "%s: unknown command %02x\n", __FUNCTION__, reg); | |
| 376 | + break; | |
| 377 | + } | |
| 378 | +} | |
| 379 | + | |
| 380 | +static void lm_i2c_event(i2c_slave *i2c, enum i2c_event event) | |
| 381 | +{ | |
| 382 | + struct lm_kbd_s *s = (struct lm_kbd_s *) i2c; | |
| 383 | + | |
| 384 | + switch (event) { | |
| 385 | + case I2C_START_RECV: | |
| 386 | + case I2C_START_SEND: | |
| 387 | + s->i2c_cycle = 0; | |
| 388 | + s->i2c_dir = (event == I2C_START_SEND); | |
| 389 | + break; | |
| 390 | + | |
| 391 | + default: | |
| 392 | + break; | |
| 393 | + } | |
| 394 | +} | |
| 395 | + | |
| 396 | +static int lm_i2c_rx(i2c_slave *i2c) | |
| 397 | +{ | |
| 398 | + struct lm_kbd_s *s = (struct lm_kbd_s *) i2c; | |
| 399 | + | |
| 400 | + return lm_kbd_read(s, s->reg, s->i2c_cycle ++); | |
| 401 | +} | |
| 402 | + | |
| 403 | +static int lm_i2c_tx(i2c_slave *i2c, uint8_t data) | |
| 404 | +{ | |
| 405 | + struct lm_kbd_s *s = (struct lm_kbd_s *) i2c; | |
| 406 | + | |
| 407 | + if (!s->i2c_cycle) | |
| 408 | + s->reg = data; | |
| 409 | + else | |
| 410 | + lm_kbd_write(s, s->reg, s->i2c_cycle - 1, data); | |
| 411 | + s->i2c_cycle ++; | |
| 412 | + | |
| 413 | + return 0; | |
| 414 | +} | |
| 415 | + | |
| 416 | +static void lm_kbd_save(QEMUFile *f, void *opaque) | |
| 417 | +{ | |
| 418 | + struct lm_kbd_s *s = (struct lm_kbd_s *) opaque; | |
| 419 | + int i; | |
| 420 | + | |
| 421 | + i2c_slave_save(f, &s->i2c); | |
| 422 | + qemu_put_byte(f, s->i2c_dir); | |
| 423 | + qemu_put_byte(f, s->i2c_cycle); | |
| 424 | + qemu_put_byte(f, (uint8_t) s->reg); | |
| 425 | + | |
| 426 | + qemu_put_8s(f, &s->config); | |
| 427 | + qemu_put_8s(f, &s->status); | |
| 428 | + qemu_put_8s(f, &s->acttime); | |
| 429 | + qemu_put_8s(f, &s->error); | |
| 430 | + qemu_put_8s(f, &s->clock); | |
| 431 | + | |
| 432 | + qemu_put_be16s(f, &s->gpio.pull); | |
| 433 | + qemu_put_be16s(f, &s->gpio.mask); | |
| 434 | + qemu_put_be16s(f, &s->gpio.dir); | |
| 435 | + qemu_put_be16s(f, &s->gpio.level); | |
| 436 | + | |
| 437 | + qemu_put_byte(f, s->kbd.dbnctime); | |
| 438 | + qemu_put_byte(f, s->kbd.size); | |
| 439 | + qemu_put_byte(f, s->kbd.start); | |
| 440 | + qemu_put_byte(f, s->kbd.len); | |
| 441 | + qemu_put_buffer(f, s->kbd.fifo, sizeof(s->kbd.fifo)); | |
| 442 | + | |
| 443 | + for (i = 0; i < sizeof(s->pwm.file); i ++) | |
| 444 | + qemu_put_be16s(f, &s->pwm.file[i]); | |
| 445 | + qemu_put_8s(f, &s->pwm.faddr); | |
| 446 | + qemu_put_buffer(f, s->pwm.addr, sizeof(s->pwm.addr)); | |
| 447 | + qemu_put_timer(f, s->pwm.tm[0]); | |
| 448 | + qemu_put_timer(f, s->pwm.tm[1]); | |
| 449 | + qemu_put_timer(f, s->pwm.tm[2]); | |
| 450 | +} | |
| 451 | + | |
| 452 | +static int lm_kbd_load(QEMUFile *f, void *opaque, int version_id) | |
| 453 | +{ | |
| 454 | + struct lm_kbd_s *s = (struct lm_kbd_s *) opaque; | |
| 455 | + int i; | |
| 456 | + | |
| 457 | + i2c_slave_load(f, &s->i2c); | |
| 458 | + s->i2c_dir = qemu_get_byte(f); | |
| 459 | + s->i2c_cycle = qemu_get_byte(f); | |
| 460 | + s->reg = (int8_t) qemu_get_byte(f); | |
| 461 | + | |
| 462 | + qemu_get_8s(f, &s->config); | |
| 463 | + qemu_get_8s(f, &s->status); | |
| 464 | + qemu_get_8s(f, &s->acttime); | |
| 465 | + qemu_get_8s(f, &s->error); | |
| 466 | + qemu_get_8s(f, &s->clock); | |
| 467 | + | |
| 468 | + qemu_get_be16s(f, &s->gpio.pull); | |
| 469 | + qemu_get_be16s(f, &s->gpio.mask); | |
| 470 | + qemu_get_be16s(f, &s->gpio.dir); | |
| 471 | + qemu_get_be16s(f, &s->gpio.level); | |
| 472 | + | |
| 473 | + s->kbd.dbnctime = qemu_get_byte(f); | |
| 474 | + s->kbd.size = qemu_get_byte(f); | |
| 475 | + s->kbd.start = qemu_get_byte(f); | |
| 476 | + s->kbd.len = qemu_get_byte(f); | |
| 477 | + qemu_get_buffer(f, s->kbd.fifo, sizeof(s->kbd.fifo)); | |
| 478 | + | |
| 479 | + for (i = 0; i < sizeof(s->pwm.file); i ++) | |
| 480 | + qemu_get_be16s(f, &s->pwm.file[i]); | |
| 481 | + qemu_get_8s(f, &s->pwm.faddr); | |
| 482 | + qemu_get_buffer(f, s->pwm.addr, sizeof(s->pwm.addr)); | |
| 483 | + qemu_get_timer(f, s->pwm.tm[0]); | |
| 484 | + qemu_get_timer(f, s->pwm.tm[1]); | |
| 485 | + qemu_get_timer(f, s->pwm.tm[2]); | |
| 486 | + | |
| 487 | + lm_kbd_irq_update(s); | |
| 488 | + lm_kbd_gpio_update(s); | |
| 489 | + | |
| 490 | + return 0; | |
| 491 | +} | |
| 492 | + | |
| 493 | +static int lm_kbd_iid = 0; | |
| 494 | + | |
| 495 | +struct i2c_slave *lm8323_init(i2c_bus *bus, qemu_irq nirq) | |
| 496 | +{ | |
| 497 | + struct lm_kbd_s *s; | |
| 498 | + | |
| 499 | + s = (struct lm_kbd_s *) i2c_slave_init(bus, 0, sizeof(struct lm_kbd_s)); | |
| 500 | + s->model = 0x8323; | |
| 501 | + s->pwm.tm[0] = qemu_new_timer(vm_clock, lm_kbd_pwm0_tick, s); | |
| 502 | + s->pwm.tm[1] = qemu_new_timer(vm_clock, lm_kbd_pwm1_tick, s); | |
| 503 | + s->pwm.tm[2] = qemu_new_timer(vm_clock, lm_kbd_pwm2_tick, s); | |
| 504 | + s->nirq = nirq; | |
| 505 | + | |
| 506 | + s->i2c.event = lm_i2c_event; | |
| 507 | + s->i2c.recv = lm_i2c_rx; | |
| 508 | + s->i2c.send = lm_i2c_tx; | |
| 509 | + | |
| 510 | + lm_kbd_reset(s); | |
| 511 | + | |
| 512 | + qemu_register_reset((void *) lm_kbd_reset, s); | |
| 513 | + register_savevm("LM8323", lm_kbd_iid ++, 0, | |
| 514 | + lm_kbd_save, lm_kbd_load, s); | |
| 515 | + | |
| 516 | + return &s->i2c; | |
| 517 | +} | |
| 518 | + | |
| 519 | +void lm832x_key_event(struct i2c_slave *i2c, int key, int state) | |
| 520 | +{ | |
| 521 | + struct lm_kbd_s *s = (struct lm_kbd_s *) i2c; | |
| 522 | + | |
| 523 | + if ((s->status & INT_ERROR) && (s->error & ERR_FIFOOVR)) | |
| 524 | + return; | |
| 525 | + | |
| 526 | + if (s->kbd.len >= sizeof(s->kbd.fifo)) | |
| 527 | + return lm_kbd_error(s, ERR_FIFOOVR); | |
| 528 | + | |
| 529 | + s->kbd.fifo[(s->kbd.start + s->kbd.len ++) & (sizeof(s->kbd.fifo) - 1)] = | |
| 530 | + key | (state << 7); | |
| 531 | + | |
| 532 | + /* We never set ERR_KEYOVR because we support multiple keys fine. */ | |
| 533 | + s->status |= INT_KEYPAD; | |
| 534 | + lm_kbd_irq_update(s); | |
| 535 | +} | ... | ... |
hw/nseries.c
| ... | ... | @@ -45,6 +45,7 @@ struct n800_s { |
| 45 | 45 | i2c_bus *i2c; |
| 46 | 46 | |
| 47 | 47 | int keymap[0x80]; |
| 48 | + i2c_slave *kbd; | |
| 48 | 49 | |
| 49 | 50 | struct tusb_s *usb; |
| 50 | 51 | void *retu; |
| ... | ... | @@ -92,16 +93,22 @@ struct n800_s { |
| 92 | 93 | #define N8X0_TAHVO_GPIO 111 |
| 93 | 94 | #define N800_UNKNOWN_GPIO4 112 /* out */ |
| 94 | 95 | #define N810_SLEEPX_LED_GPIO 112 |
| 95 | -#define N810_TSC_UNKNOWN_GPIO 118 /* out */ | |
| 96 | -#define N800_TSC_RESET_GPIO 119 /* ? */ | |
| 96 | +#define N800_TSC_RESET_GPIO 118 /* ? */ | |
| 97 | +#define N800_TSC_UNKNOWN_GPIO 119 /* out */ | |
| 97 | 98 | #define N8X0_TMP105_GPIO 125 |
| 98 | 99 | |
| 99 | 100 | /* Config */ |
| 100 | 101 | #define XLDR_LL_UART 1 |
| 101 | 102 | |
| 102 | -/* Addresses on the I2C bus */ | |
| 103 | -#define N8X0_TMP105_ADDR 0x48 | |
| 104 | -#define N8X0_MENELAUS_ADDR 0x72 | |
| 103 | +/* Addresses on the I2C bus 0 */ | |
| 104 | +#define N810_TLV320AIC33_ADDR 0x18 /* Audio CODEC */ | |
| 105 | +#define N8X0_TCM825x_ADDR 0x29 /* Camera */ | |
| 106 | +#define N810_LP5521_ADDR 0x32 /* LEDs */ | |
| 107 | +#define N810_TSL2563_ADDR 0x3d /* Light sensor */ | |
| 108 | +#define N810_LM8323_ADDR 0x45 /* Keyboard */ | |
| 109 | +/* Addresses on the I2C bus 1 */ | |
| 110 | +#define N8X0_TMP105_ADDR 0x48 /* Temperature sensor */ | |
| 111 | +#define N8X0_MENELAUS_ADDR 0x72 /* Power management */ | |
| 105 | 112 | |
| 106 | 113 | /* Chipselects on GPMC NOR interface */ |
| 107 | 114 | #define N8X0_ONENAND_CS 0 |
| ... | ... | @@ -190,12 +197,12 @@ static const int n800_keys[16] = { |
| 190 | 197 | 28, /* Enter */ |
| 191 | 198 | 77, /* Right */ |
| 192 | 199 | -1, |
| 193 | - 1, /* Cycle (ESC) */ | |
| 200 | + 1, /* Cycle (ESC) */ | |
| 194 | 201 | 80, /* Down */ |
| 195 | 202 | 62, /* Menu (F4) */ |
| 196 | 203 | -1, |
| 197 | 204 | 66, /* Zoom- (F8) */ |
| 198 | - 64, /* FS (F6) */ | |
| 205 | + 64, /* FullScreen (F6) */ | |
| 199 | 206 | 65, /* Zoom+ (F7) */ |
| 200 | 207 | -1, |
| 201 | 208 | }; |
| ... | ... | @@ -235,6 +242,107 @@ static void n810_tsc_setup(struct n800_s *s) |
| 235 | 242 | tsc2005_set_transform(s->ts.opaque, &n810_pointercal); |
| 236 | 243 | } |
| 237 | 244 | |
| 245 | +/* N810 Keyboard controller */ | |
| 246 | +static void n810_key_event(void *opaque, int keycode) | |
| 247 | +{ | |
| 248 | + struct n800_s *s = (struct n800_s *) opaque; | |
| 249 | + int code = s->keymap[keycode & 0x7f]; | |
| 250 | + | |
| 251 | + if (code == -1) { | |
| 252 | + if ((keycode & 0x7f) == RETU_KEYCODE) | |
| 253 | + retu_key_event(s->retu, !(keycode & 0x80)); | |
| 254 | + return; | |
| 255 | + } | |
| 256 | + | |
| 257 | + lm832x_key_event(s->kbd, code, !(keycode & 0x80)); | |
| 258 | +} | |
| 259 | + | |
| 260 | +#define M 0 | |
| 261 | + | |
| 262 | +static int n810_keys[0x80] = { | |
| 263 | + [0x01] = 16, /* Q */ | |
| 264 | + [0x02] = 37, /* K */ | |
| 265 | + [0x03] = 24, /* O */ | |
| 266 | + [0x04] = 25, /* P */ | |
| 267 | + [0x05] = 14, /* Backspace */ | |
| 268 | + [0x06] = 30, /* A */ | |
| 269 | + [0x07] = 31, /* S */ | |
| 270 | + [0x08] = 32, /* D */ | |
| 271 | + [0x09] = 33, /* F */ | |
| 272 | + [0x0a] = 34, /* G */ | |
| 273 | + [0x0b] = 35, /* H */ | |
| 274 | + [0x0c] = 36, /* J */ | |
| 275 | + | |
| 276 | + [0x11] = 17, /* W */ | |
| 277 | + [0x12] = 62, /* Menu (F4) */ | |
| 278 | + [0x13] = 38, /* L */ | |
| 279 | + [0x14] = 40, /* ' (Apostrophe) */ | |
| 280 | + [0x16] = 44, /* Z */ | |
| 281 | + [0x17] = 45, /* X */ | |
| 282 | + [0x18] = 46, /* C */ | |
| 283 | + [0x19] = 47, /* V */ | |
| 284 | + [0x1a] = 48, /* B */ | |
| 285 | + [0x1b] = 49, /* N */ | |
| 286 | + [0x1c] = 42, /* Shift (Left shift) */ | |
| 287 | + [0x1f] = 65, /* Zoom+ (F7) */ | |
| 288 | + | |
| 289 | + [0x21] = 18, /* E */ | |
| 290 | + [0x22] = 39, /* ; (Semicolon) */ | |
| 291 | + [0x23] = 12, /* - (Minus) */ | |
| 292 | + [0x24] = 13, /* = (Equal) */ | |
| 293 | + [0x2b] = 56, /* Fn (Left Alt) */ | |
| 294 | + [0x2c] = 50, /* M */ | |
| 295 | + [0x2f] = 66, /* Zoom- (F8) */ | |
| 296 | + | |
| 297 | + [0x31] = 19, /* R */ | |
| 298 | + [0x32] = 29 | M, /* Right Ctrl */ | |
| 299 | + [0x34] = 57, /* Space */ | |
| 300 | + [0x35] = 51, /* , (Comma) */ | |
| 301 | + [0x37] = 72 | M, /* Up */ | |
| 302 | + [0x3c] = 82 | M, /* Compose (Insert) */ | |
| 303 | + [0x3f] = 64, /* FullScreen (F6) */ | |
| 304 | + | |
| 305 | + [0x41] = 20, /* T */ | |
| 306 | + [0x44] = 52, /* . (Dot) */ | |
| 307 | + [0x46] = 77 | M, /* Right */ | |
| 308 | + [0x4f] = 63, /* Home (F5) */ | |
| 309 | + [0x51] = 21, /* Y */ | |
| 310 | + [0x53] = 80 | M, /* Down */ | |
| 311 | + [0x55] = 28, /* Enter */ | |
| 312 | + [0x5f] = 1, /* Cycle (ESC) */ | |
| 313 | + | |
| 314 | + [0x61] = 22, /* U */ | |
| 315 | + [0x64] = 75 | M, /* Left */ | |
| 316 | + | |
| 317 | + [0x71] = 23, /* I */ | |
| 318 | +#if 0 | |
| 319 | + [0x75] = 28 | M, /* KP Enter (KP Enter) */ | |
| 320 | +#else | |
| 321 | + [0x75] = 15, /* KP Enter (Tab) */ | |
| 322 | +#endif | |
| 323 | +}; | |
| 324 | + | |
| 325 | +#undef M | |
| 326 | + | |
| 327 | +static void n810_kbd_setup(struct n800_s *s) | |
| 328 | +{ | |
| 329 | + qemu_irq kbd_irq = omap2_gpio_in_get(s->cpu->gpif, N810_KEYBOARD_GPIO)[0]; | |
| 330 | + int i; | |
| 331 | + | |
| 332 | + for (i = 0; i < 0x80; i ++) | |
| 333 | + s->keymap[i] = -1; | |
| 334 | + for (i = 0; i < 0x80; i ++) | |
| 335 | + if (n810_keys[i] > 0) | |
| 336 | + s->keymap[n810_keys[i]] = i; | |
| 337 | + | |
| 338 | + qemu_add_kbd_event_handler(n810_key_event, s); | |
| 339 | + | |
| 340 | + /* Attach the LM8322 keyboard to the I2C bus, | |
| 341 | + * should happen in n8x0_i2c_setup and s->kbd be initialised here. */ | |
| 342 | + s->kbd = lm8323_init(s->i2c, kbd_irq); | |
| 343 | + i2c_set_slave_address(s->kbd, N810_LM8323_ADDR); | |
| 344 | +} | |
| 345 | + | |
| 238 | 346 | /* LCD MIPI DBI-C controller (URAL) */ |
| 239 | 347 | struct mipid_s { |
| 240 | 348 | int resp[4]; |
| ... | ... | @@ -954,8 +1062,10 @@ static void n8x0_init(ram_addr_t ram_size, const char *boot_device, |
| 954 | 1062 | n8x0_i2c_setup(s); |
| 955 | 1063 | if (model == 800) |
| 956 | 1064 | n800_tsc_kbd_setup(s); |
| 957 | - else if (model == 810) | |
| 1065 | + else if (model == 810) { | |
| 958 | 1066 | n810_tsc_setup(s); |
| 1067 | + n810_kbd_setup(s); | |
| 1068 | + } | |
| 959 | 1069 | n8x0_spi_setup(s); |
| 960 | 1070 | n8x0_dss_setup(s, ds); |
| 961 | 1071 | n8x0_cbus_setup(s); | ... | ... |