Add support for keyboard used in Samsung Chromebook (ARM machine)

Support covers device drivers for:
- Interrupt Combiner
- gpio/pad, External Interrupts Controller (pad)
- I2C Interface
- Chrome Embedded Controller
- Chrome Keyboard

Also:
- Use new gpio dev class in EHCI driver
- Expand device tree information
This commit is contained in:
Ruslan Bukin 2014-03-30 15:22:36 +00:00
parent a3fcf1f8d9
commit 657fae63fc
16 changed files with 2909 additions and 31 deletions

View File

@ -20,6 +20,11 @@
include "EXYNOS5250.common"
ident CHROMEBOOK
hints "CHROMEBOOK.hints"
device chrome_ec # Chrome Embedded Controller
device chrome_kb # Chrome Keyboard
# Framebuffer
device vt
device kbdmux

View File

@ -0,0 +1,5 @@
# $FreeBSD$
# Chrome Embedded Controller
hint.chrome_ec.0.at="iicbus0"
hint.chrome_ec.0.addr=0x1e

View File

@ -0,0 +1,253 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPREC OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNEC FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINEC INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Samsung Chromebook Embedded Controller
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <sys/gpio.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#include <dev/iicbus/iiconf.h>
#include "iicbus_if.h"
#include "gpio_if.h"
#include <arm/samsung/exynos/chrome_ec.h>
/* TODO: export to DTS */
#define OUR_GPIO 177
#define EC_GPIO 168
struct ec_softc {
device_t dev;
};
struct ec_softc *ec_sc;
/*
* bus_claim, bus_release
* both functions used for bus arbitration
* in multi-master mode
*/
static int
bus_claim(struct ec_softc *sc)
{
device_t gpio_dev;
int status;
gpio_dev = devclass_get_device(devclass_find("gpio"), 0);
if (gpio_dev == NULL) {
device_printf(sc->dev, "cant find gpio_dev\n");
return (1);
}
/* Say we want the bus */
GPIO_PIN_SET(gpio_dev, OUR_GPIO, GPIO_PIN_LOW);
/* Check EC decision */
GPIO_PIN_GET(gpio_dev, EC_GPIO, &status);
if (status == 1) {
/* Okay. We have bus */
return (0);
}
/* EC is master */
return (-1);
}
static int
bus_release(struct ec_softc *sc)
{
device_t gpio_dev;
gpio_dev = devclass_get_device(devclass_find("gpio"), 0);
if (gpio_dev == NULL) {
device_printf(sc->dev, "cant find gpio_dev\n");
return (1);
}
GPIO_PIN_SET(gpio_dev, OUR_GPIO, GPIO_PIN_HIGH);
return (0);
}
static int
ec_probe(device_t dev)
{
device_set_desc(dev, "Chromebook Embedded Controller");
return (BUS_PROBE_DEFAULT);
}
static int
fill_checksum(uint8_t *data_out, int len)
{
int res;
int i;
res = 0;
for (i = 0; i < len; i++) {
res += data_out[i];
}
data_out[len] = (res & 0xff);
return (0);
}
int
ec_command(uint8_t cmd, uint8_t *dout, uint8_t dout_len,
uint8_t *dinp, uint8_t dinp_len)
{
struct ec_softc *sc;
uint8_t *msg_dout;
uint8_t *msg_dinp;
int ret;
int i;
msg_dout = malloc(dout_len + 4, M_DEVBUF, M_NOWAIT);
msg_dinp = malloc(dinp_len + 4, M_DEVBUF, M_NOWAIT);
if (ec_sc == NULL)
return (-1);
sc = ec_sc;
msg_dout[0] = EC_CMD_VERSION0;
msg_dout[1] = cmd;
msg_dout[2] = dout_len;
for (i = 0; i < dout_len; i++) {
msg_dout[i + 3] = dout[i];
};
fill_checksum(msg_dout, dout_len + 3);
struct iic_msg msgs[] = {
{ 0x1e, IIC_M_WR, dout_len + 4, msg_dout, },
{ 0x1e, IIC_M_RD, dinp_len + 4, msg_dinp, },
};
ret = iicbus_transfer(sc->dev, msgs, 2);
if (ret != 0) {
device_printf(sc->dev, "i2c transfer returned %d\n", ret);
free(msg_dout, M_DEVBUF);
free(msg_dinp, M_DEVBUF);
return (-1);
}
for (i = 0; i < dinp_len; i++) {
dinp[i] = msg_dinp[i + 3];
};
free(msg_dout, M_DEVBUF);
free(msg_dinp, M_DEVBUF);
return (0);
}
int ec_hello(void)
{
uint8_t data_in[4];
uint8_t data_out[4];
data_in[0] = 0x40;
data_in[1] = 0x30;
data_in[2] = 0x20;
data_in[3] = 0x10;
ec_command(EC_CMD_MKBP_STATE, data_in, 4,
data_out, 4);
return (0);
}
static int
ec_attach(device_t dev)
{
struct ec_softc *sc;
sc = device_get_softc(dev);
sc->dev = dev;
ec_sc = sc;
/*
* Claim the bus.
*
* We don't know cases when EC is master,
* so hold the bus forever for us.
*
*/
if (bus_claim(sc) != 0) {
return (ENXIO);
}
return (0);
}
static device_method_t ec_methods[] = {
DEVMETHOD(device_probe, ec_probe),
DEVMETHOD(device_attach, ec_attach),
{ 0, 0 }
};
static driver_t ec_driver = {
"chrome_ec",
ec_methods,
sizeof(struct ec_softc),
};
static devclass_t ec_devclass;
DRIVER_MODULE(chrome_ec, iicbus, ec_driver, ec_devclass, 0, 0);
MODULE_VERSION(chrome_ec, 1);
MODULE_DEPEND(chrome_ec, iicbus, 1, 1, 1);

View File

@ -0,0 +1,36 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#define EC_CMD_HELLO 0x01
#define EC_CMD_GET_VERSION 0x02
#define EC_CMD_MKBP_STATE 0x60
#define EC_CMD_VERSION0 0xdc
int ec_command(uint8_t cmd, uint8_t *dout, uint8_t dout_len,
uint8_t *dinp, uint8_t dinp_len);
int ec_hello(void);

View File

@ -0,0 +1,792 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Samsung Chromebook Keyboard
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <sys/kdb.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <sys/mutex.h>
#include <sys/gpio.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <sys/ioccom.h>
#include <sys/filio.h>
#include <sys/tty.h>
#include <sys/kbio.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#include "gpio_if.h"
#include <arm/samsung/exynos/chrome_ec.h>
#include <arm/samsung/exynos/chrome_kb.h>
#include <arm/samsung/exynos/exynos5_combiner.h>
#include <arm/samsung/exynos/exynos5_pad.h>
#define CKB_LOCK() mtx_lock(&Giant)
#define CKB_UNLOCK() mtx_unlock(&Giant)
#ifdef INVARIANTS
/*
* Assert that the lock is held in all contexts
* where the code can be executed.
*/
#define CKB_LOCK_ASSERT() mtx_assert(&Giant, MA_OWNED)
/*
* Assert that the lock is held in the contexts
* where it really has to be so.
*/
#define CKB_CTX_LOCK_ASSERT() \
do { \
if (!kdb_active && panicstr == NULL) \
mtx_assert(&Giant, MA_OWNED); \
} while (0)
#else
#define CKB_LOCK_ASSERT() (void)0
#define CKB_CTX_LOCK_ASSERT() (void)0
#endif
/*
* Define a stub keyboard driver in case one hasn't been
* compiled into the kernel
*/
#include <sys/kbio.h>
#include <dev/kbd/kbdreg.h>
#include <dev/kbd/kbdtables.h>
#define CKB_NFKEY 12
#define CKB_FLAG_COMPOSE 0x1
#define CKB_FLAG_POLLING 0x2
#define KBD_DRIVER_NAME "ckbd"
/* TODO: take interrupt from DTS */
#define KB_GPIO_INT 146
struct ckb_softc {
keyboard_t sc_kbd;
keymap_t sc_keymap;
accentmap_t sc_accmap;
fkeytab_t sc_fkeymap[CKB_NFKEY];
struct resource* sc_mem_res;
struct resource* sc_irq_res;
void* sc_intr_hl;
int sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */
int sc_state; /* shift/lock key state */
int sc_accents; /* accent key index (> 0) */
int sc_flags; /* flags */
struct callout sc_repeat_callout;
int sc_repeat_key;
int sc_repeating;
int flag;
int rows;
int cols;
device_t dev;
struct thread *sc_poll_thread;
uint8_t *scan_local;
uint8_t *scan;
};
/* prototypes */
static void ckb_set_leds(struct ckb_softc *, uint8_t);
static int ckb_set_typematic(keyboard_t *, int);
static uint32_t ckb_read_char(keyboard_t *, int);
static void ckb_clear_state(keyboard_t *);
static int ckb_ioctl(keyboard_t *, u_long, caddr_t);
static int ckb_enable(keyboard_t *);
static int ckb_disable(keyboard_t *);
static void
ckb_repeat(void *arg)
{
struct ckb_softc *sc;
sc = arg;
if (KBD_IS_ACTIVE(&sc->sc_kbd) && KBD_IS_BUSY(&sc->sc_kbd)) {
if (sc->sc_repeat_key != -1) {
sc->sc_repeating = 1;
sc->sc_kbd.kb_callback.kc_func(&sc->sc_kbd,
KBDIO_KEYINPUT, sc->sc_kbd.kb_callback.kc_arg);
}
}
}
/* detect a keyboard, not used */
static int
ckb__probe(int unit, void *arg, int flags)
{
return (ENXIO);
}
/* reset and initialize the device, not used */
static int
ckb_init(int unit, keyboard_t **kbdp, void *arg, int flags)
{
return (ENXIO);
}
/* test the interface to the device, not used */
static int
ckb_test_if(keyboard_t *kbd)
{
return (0);
}
/* finish using this keyboard, not used */
static int
ckb_term(keyboard_t *kbd)
{
return (ENXIO);
}
/* keyboard interrupt routine, not used */
static int
ckb_intr(keyboard_t *kbd, void *arg)
{
return (0);
}
/* lock the access to the keyboard, not used */
static int
ckb_lock(keyboard_t *kbd, int lock)
{
return (1);
}
/* clear the internal state of the keyboard */
static void
ckb_clear_state(keyboard_t *kbd)
{
struct ckb_softc *sc;
sc = kbd->kb_data;
CKB_CTX_LOCK_ASSERT();
sc->sc_flags &= ~(CKB_FLAG_COMPOSE | CKB_FLAG_POLLING);
sc->sc_state &= LOCK_MASK; /* preserve locking key state */
sc->sc_accents = 0;
}
/* save the internal state, not used */
static int
ckb_get_state(keyboard_t *kbd, void *buf, size_t len)
{
return (len == 0) ? 1 : -1;
}
/* set the internal state, not used */
static int
ckb_set_state(keyboard_t *kbd, void *buf, size_t len)
{
return (EINVAL);
}
/* check if data is waiting */
static int
ckb_check(keyboard_t *kbd)
{
struct ckb_softc *sc;
int i;
sc = kbd->kb_data;
CKB_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (0);
if (sc->sc_flags & CKB_FLAG_POLLING) {
return (1);
};
for (i = 0; i < sc->cols; i++)
if (sc->scan_local[i] != sc->scan[i]) {
return (1);
};
if (sc->sc_repeating)
return (1);
return (0);
}
/* check if char is waiting */
static int
ckb_check_char_locked(keyboard_t *kbd)
{
CKB_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (0);
return (ckb_check(kbd));
}
static int
ckb_check_char(keyboard_t *kbd)
{
int result;
CKB_LOCK();
result = ckb_check_char_locked(kbd);
CKB_UNLOCK();
return (result);
}
/* read one byte from the keyboard if it's allowed */
/* Currently unused. */
static int
ckb_read(keyboard_t *kbd, int wait)
{
CKB_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (-1);
printf("Implement ME: %s\n", __func__);
return (0);
}
int scantokey(int i, int j);
int
scantokey(int i, int j)
{
int k;
for (k = 0; k < KEYMAP_LEN; k++)
if ((keymap[k].col == i) && (keymap[k].row == j))
return (keymap[k].key);
return (0);
}
/* read char from the keyboard */
static uint32_t
ckb_read_char_locked(keyboard_t *kbd, int wait)
{
struct ckb_softc *sc;
int i,j;
uint16_t key;
int oldbit;
int newbit;
sc = kbd->kb_data;
CKB_CTX_LOCK_ASSERT();
if (!KBD_IS_ACTIVE(kbd))
return (NOKEY);
if (sc->sc_repeating) {
sc->sc_repeating = 0;
callout_reset(&sc->sc_repeat_callout, hz / 10,
ckb_repeat, sc);
return (sc->sc_repeat_key);
};
if (sc->sc_flags & CKB_FLAG_POLLING) {
/* TODO */
};
for (i = 0; i < sc->cols; i++) {
for (j = 0; j < sc->rows; j++) {
oldbit = (sc->scan_local[i] & (1 << j));
newbit = (sc->scan[i] & (1 << j));
if (oldbit == newbit)
continue;
key = scantokey(i,j);
if (key == 0) {
continue;
};
if (newbit > 0) {
/* key pressed */
sc->scan_local[i] |= (1 << j);
/* setup repeating */
sc->sc_repeat_key = key;
callout_reset(&sc->sc_repeat_callout,
hz / 2, ckb_repeat, sc);
} else {
/* key released */
sc->scan_local[i] &= ~(1 << j);
/* release flag */
key |= 0x80;
/* unsetup repeating */
sc->sc_repeat_key = -1;
callout_stop(&sc->sc_repeat_callout);
}
return (key);
}
}
return (NOKEY);
}
/* Currently wait is always false. */
static uint32_t
ckb_read_char(keyboard_t *kbd, int wait)
{
uint32_t keycode;
CKB_LOCK();
keycode = ckb_read_char_locked(kbd, wait);
CKB_UNLOCK();
return (keycode);
}
/* some useful control functions */
static int
ckb_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg)
{
struct ckb_softc *sc;
int i;
sc = kbd->kb_data;
CKB_LOCK_ASSERT();
switch (cmd) {
case KDGKBMODE: /* get keyboard mode */
*(int *)arg = sc->sc_mode;
break;
case KDSKBMODE: /* set keyboard mode */
switch (*(int *)arg) {
case K_XLATE:
if (sc->sc_mode != K_XLATE) {
/* make lock key state and LED state match */
sc->sc_state &= ~LOCK_MASK;
sc->sc_state |= KBD_LED_VAL(kbd);
}
/* FALLTHROUGH */
case K_RAW:
case K_CODE:
if (sc->sc_mode != *(int *)arg) {
if ((sc->sc_flags & CKB_FLAG_POLLING) == 0)
ckb_clear_state(kbd);
sc->sc_mode = *(int *)arg;
}
break;
default:
return (EINVAL);
}
break;
case KDGETLED: /* get keyboard LED */
*(int *)arg = KBD_LED_VAL(kbd);
break;
case KDSETLED: /* set keyboard LED */
/* NOTE: lock key state in "sc_state" won't be changed */
if (*(int *)arg & ~LOCK_MASK)
return (EINVAL);
i = *(int *)arg;
/* replace CAPS LED with ALTGR LED for ALTGR keyboards */
if (sc->sc_mode == K_XLATE &&
kbd->kb_keymap->n_keys > ALTGR_OFFSET) {
if (i & ALKED)
i |= CLKED;
else
i &= ~CLKED;
}
if (KBD_HAS_DEVICE(kbd)) {
/* Configure LED */
}
KBD_LED_VAL(kbd) = *(int *)arg;
break;
case KDGKBSTATE: /* get lock key state */
*(int *)arg = sc->sc_state & LOCK_MASK;
break;
case KDSKBSTATE: /* set lock key state */
if (*(int *)arg & ~LOCK_MASK) {
return (EINVAL);
}
sc->sc_state &= ~LOCK_MASK;
sc->sc_state |= *(int *)arg;
/* set LEDs and quit */
return (ckb_ioctl(kbd, KDSETLED, arg));
case KDSETREPEAT: /* set keyboard repeat rate (new
* interface) */
if (!KBD_HAS_DEVICE(kbd)) {
return (0);
}
if (((int *)arg)[1] < 0) {
return (EINVAL);
}
if (((int *)arg)[0] < 0) {
return (EINVAL);
}
if (((int *)arg)[0] < 200) /* fastest possible value */
kbd->kb_delay1 = 200;
else
kbd->kb_delay1 = ((int *)arg)[0];
kbd->kb_delay2 = ((int *)arg)[1];
return (0);
case KDSETRAD: /* set keyboard repeat rate (old
* interface) */
return (ckb_set_typematic(kbd, *(int *)arg));
case PIO_KEYMAP: /* set keyboard translation table */
case OPIO_KEYMAP: /* set keyboard translation table
* (compat) */
case PIO_KEYMAPENT: /* set keyboard translation table
* entry */
case PIO_DEADKEYMAP: /* set accent key translation table */
sc->sc_accents = 0;
/* FALLTHROUGH */
default:
return (genkbd_commonioctl(kbd, cmd, arg));
}
return (0);
}
static int
ckb_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg)
{
int result;
/*
* XXX KDGKBSTATE, KDSKBSTATE and KDSETLED can be called from any
* context where printf(9) can be called, which among other things
* includes interrupt filters and threads with any kinds of locks
* already held. For this reason it would be dangerous to acquire
* the Giant here unconditionally. On the other hand we have to
* have it to handle the ioctl.
* So we make our best effort to auto-detect whether we can grab
* the Giant or not. Blame syscons(4) for this.
*/
switch (cmd) {
case KDGKBSTATE:
case KDSKBSTATE:
case KDSETLED:
if (!mtx_owned(&Giant) && !SCHEDULER_STOPPED())
return (EDEADLK); /* best I could come up with */
/* FALLTHROUGH */
default:
CKB_LOCK();
result = ckb_ioctl_locked(kbd, cmd, arg);
CKB_UNLOCK();
return (result);
}
}
/*
* Enable the access to the device; until this function is called,
* the client cannot read from the keyboard.
*/
static int
ckb_enable(keyboard_t *kbd)
{
CKB_LOCK();
KBD_ACTIVATE(kbd);
CKB_UNLOCK();
return (0);
}
/* disallow the access to the device */
static int
ckb_disable(keyboard_t *kbd)
{
CKB_LOCK();
KBD_DEACTIVATE(kbd);
CKB_UNLOCK();
return (0);
}
/* local functions */
static int
ckb_set_typematic(keyboard_t *kbd, int code)
{
static const int delays[] = {250, 500, 750, 1000};
static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63,
68, 76, 84, 92, 100, 110, 118, 126,
136, 152, 168, 184, 200, 220, 236, 252,
272, 304, 336, 368, 400, 440, 472, 504};
if (code & ~0x7f) {
return (EINVAL);
}
kbd->kb_delay1 = delays[(code >> 5) & 3];
kbd->kb_delay2 = rates[code & 0x1f];
return (0);
}
static int
ckb_poll(keyboard_t *kbd, int on)
{
struct ckb_softc *sc;
sc = kbd->kb_data;
CKB_LOCK();
if (on) {
sc->sc_flags |= CKB_FLAG_POLLING;
sc->sc_poll_thread = curthread;
} else {
sc->sc_flags &= ~CKB_FLAG_POLLING;
}
CKB_UNLOCK();
return (0);
}
/* local functions */
static int dummy_kbd_configure(int flags);
keyboard_switch_t ckbdsw = {
.probe = &ckb__probe,
.init = &ckb_init,
.term = &ckb_term,
.intr = &ckb_intr,
.test_if = &ckb_test_if,
.enable = &ckb_enable,
.disable = &ckb_disable,
.read = &ckb_read,
.check = &ckb_check,
.read_char = &ckb_read_char,
.check_char = &ckb_check_char,
.ioctl = &ckb_ioctl,
.lock = &ckb_lock,
.clear_state = &ckb_clear_state,
.get_state = &ckb_get_state,
.set_state = &ckb_set_state,
.get_fkeystr = &genkbd_get_fkeystr,
.poll = &ckb_poll,
.diag = &genkbd_diag,
};
static int
dummy_kbd_configure(int flags)
{
return (0);
}
KEYBOARD_DRIVER(ckbd, ckbdsw, dummy_kbd_configure);
static int
parse_dts(struct ckb_softc *sc)
{
phandle_t node;
pcell_t dts_value;
int len;
if ((node = ofw_bus_get_node(sc->dev)) == -1)
return (ENXIO);
if ((len = OF_getproplen(node, "keypad,num-rows")) <= 0)
return (ENXIO);
OF_getprop(node, "keypad,num-rows", &dts_value, len);
sc->rows = fdt32_to_cpu(dts_value);
if ((len = OF_getproplen(node, "keypad,num-columns")) <= 0)
return (ENXIO);
OF_getprop(node, "keypad,num-columns", &dts_value, len);
sc->cols = fdt32_to_cpu(dts_value);
if ((sc->rows == 0) || (sc->cols == 0))
return (ENXIO);
return (0);
}
void
ckb_ec_intr(void *arg)
{
struct ckb_softc *sc;
sc = arg;
if (sc->sc_flags & CKB_FLAG_POLLING)
return;
ec_command(EC_CMD_MKBP_STATE, sc->scan, sc->cols,
sc->scan, sc->cols);
(sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT,
sc->sc_kbd.kb_callback.kc_arg);
};
static int
chrome_kb_attach(device_t dev)
{
struct ckb_softc *sc;
keyboard_t *kbd;
int error;
int rid;
int i;
sc = device_get_softc(dev);
sc->dev = dev;
if ((error = parse_dts(sc)) != 0)
return error;
#if 0
device_printf(sc->dev, "Keyboard matrix [%dx%d]\n",
sc->cols, sc->rows);
#endif
/* TODO: take interrupt from DTS */
pad_setup_intr(KB_GPIO_INT, ckb_ec_intr, sc);
kbd = &sc->sc_kbd;
rid = 0;
sc->scan_local = malloc(sc->cols, M_DEVBUF, M_NOWAIT);
sc->scan = malloc(sc->cols, M_DEVBUF, M_NOWAIT);
for (i = 0; i < sc->cols; i++) {
sc->scan_local[i] = 0;
sc->scan[i] = 0;
};
kbd_init_struct(kbd, KBD_DRIVER_NAME, KB_OTHER,
device_get_unit(dev), 0, 0, 0);
kbd->kb_data = (void *)sc;
sc->sc_keymap = key_map;
sc->sc_accmap = accent_map;
for (i = 0; i < CKB_NFKEY; i++) {
sc->sc_fkeymap[i] = fkey_tab[i];
}
kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap,
sc->sc_fkeymap, CKB_NFKEY);
KBD_FOUND_DEVICE(kbd);
ckb_clear_state(kbd);
KBD_PROBE_DONE(kbd);
callout_init(&sc->sc_repeat_callout, 0);
KBD_INIT_DONE(kbd);
if (kbd_register(kbd) < 0) {
return (ENXIO);
};
KBD_CONFIG_DONE(kbd);
return (0);
}
static int
chrome_kb_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (ofw_bus_is_compatible(dev, "google,cros-ec-keyb")) {
device_set_desc(dev, "Chrome EC Keyboard");
return (BUS_PROBE_DEFAULT);
}
return (ENXIO);
}
static device_method_t chrome_kb_methods[] = {
DEVMETHOD(device_probe, chrome_kb_probe),
DEVMETHOD(device_attach, chrome_kb_attach),
{ 0, 0 }
};
static driver_t chrome_kb_driver = {
"chrome_kb",
chrome_kb_methods,
sizeof(struct ckb_softc),
};
static devclass_t chrome_kb_devclass;
DRIVER_MODULE(chrome_kb, simplebus, chrome_kb_driver,
chrome_kb_devclass, 0, 0);

View File

@ -0,0 +1,122 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
void ckb_ec_intr(void *);
struct key {
uint8_t row;
uint8_t col;
uint8_t key;
};
#define KEYMAP_LEN 75
struct key keymap[KEYMAP_LEN] = {
{ 0x00, 0x01, 0x7d }, /* lmeta */
{ 0x00, 0x02, 0x3b }, /* F1 */
{ 0x00, 0x03, 0x30 }, /* B */
{ 0x00, 0x04, 0x44 }, /* F10 */
{ 0x00, 0x06, 0x31 }, /* N */
{ 0x00, 0x08, 0x0d }, /* = */
{ 0x00, 0x0a, 0x64 }, /* ralt */
{ 0x01, 0x01, 0x01 }, /* escape */
{ 0x01, 0x02, 0x3e }, /* F4 */
{ 0x01, 0x03, 0x22 }, /* G */
{ 0x01, 0x04, 0x41 }, /* F7 */
{ 0x01, 0x06, 0x23 }, /* H */
{ 0x01, 0x08, 0x28 }, /* ' */
{ 0x01, 0x09, 0x43 }, /* F9 */
{ 0x01, 0x0b, 0x0e }, /* backspace */
{ 0x02, 0x00, 0x1d }, /* lctrl */
{ 0x02, 0x01, 0x0f }, /* tab */
{ 0x02, 0x02, 0x3d }, /* F3 */
{ 0x02, 0x03, 0x14 }, /* t */
{ 0x02, 0x04, 0x40 }, /* F6 */
{ 0x02, 0x05, 0x1b }, /* ] */
{ 0x02, 0x06, 0x15 }, /* y */
{ 0x02, 0x07, 0x56 }, /* 102nd */
{ 0x02, 0x08, 0x1a }, /* [ */
{ 0x02, 0x09, 0x42 }, /* F8 */
{ 0x03, 0x01, 0x29 }, /* grave */
{ 0x03, 0x02, 0x3c }, /* F2 */
{ 0x03, 0x03, 0x06 }, /* 5 */
{ 0x03, 0x04, 0x3f }, /* F5 */
{ 0x03, 0x06, 0x07 }, /* 6 */
{ 0x03, 0x08, 0x0c }, /* - */
{ 0x03, 0x0b, 0x2b }, /* \ */
{ 0x04, 0x00, 0x61 }, /* rctrl */
{ 0x04, 0x01, 0x1e }, /* a */
{ 0x04, 0x02, 0x20 }, /* d */
{ 0x04, 0x03, 0x21 }, /* f */
{ 0x04, 0x04, 0x1f }, /* s */
{ 0x04, 0x05, 0x25 }, /* k */
{ 0x04, 0x06, 0x24 }, /* j */
{ 0x04, 0x08, 0x27 }, /* ; */
{ 0x04, 0x09, 0x26 }, /* l */
{ 0x04, 0x0a, 0x2b }, /* \ */
{ 0x04, 0x0b, 0x1c }, /* enter */
{ 0x05, 0x01, 0x2c }, /* z */
{ 0x05, 0x02, 0x2e }, /* c */
{ 0x05, 0x03, 0x2f }, /* v */
{ 0x05, 0x04, 0x2d }, /* x */
{ 0x05, 0x05, 0x33 }, /* , */
{ 0x05, 0x06, 0x32 }, /* m */
{ 0x05, 0x07, 0x2a }, /* lsh */
{ 0x05, 0x08, 0x35 }, /* / */
{ 0x05, 0x09, 0x34 }, /* . */
{ 0x05, 0x0B, 0x39 }, /* space */
{ 0x06, 0x01, 0x02 }, /* 1 */
{ 0x06, 0x02, 0x04 }, /* 3 */
{ 0x06, 0x03, 0x05 }, /* 4 */
{ 0x06, 0x04, 0x03 }, /* 2 */
{ 0x06, 0x05, 0x09 }, /* 8 */
{ 0x06, 0x06, 0x08 }, /* 7 */
{ 0x06, 0x08, 0x0b }, /* 0 */
{ 0x06, 0x09, 0x0a }, /* 9 */
{ 0x06, 0x0a, 0x38 }, /* lalt */
{ 0x06, 0x0b, 0x64 }, /* down */
{ 0x06, 0x0c, 0x62 }, /* right */
{ 0x07, 0x01, 0x10 }, /* q */
{ 0x07, 0x02, 0x12 }, /* e */
{ 0x07, 0x03, 0x13 }, /* r */
{ 0x07, 0x04, 0x11 }, /* w */
{ 0x07, 0x05, 0x17 }, /* i */
{ 0x07, 0x06, 0x16 }, /* u */
{ 0x07, 0x07, 0x36 }, /* rsh */
{ 0x07, 0x08, 0x19 }, /* p */
{ 0x07, 0x09, 0x18 }, /* o */
{ 0x07, 0x0b, 0x5F }, /* up */
{ 0x07, 0x0c, 0x61 }, /* left */
};

View File

@ -0,0 +1,304 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Samsung Exynos 5 Interrupt Combiner
* Chapter 7, Exynos 5 Dual User's Manual Public Rev 1.00
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#include <arm/samsung/exynos/exynos5_common.h>
#include <arm/samsung/exynos/exynos5_combiner.h>
#define NGRP 32
#define ITABLE_LEN 24
#define IESR(n) (0x10 * n + 0x0) /* Interrupt enable set */
#define IECR(n) (0x10 * n + 0x4) /* Interrupt enable clear */
#define ISTR(n) (0x10 * n + 0x8) /* Interrupt status */
#define IMSR(n) (0x10 * n + 0xC) /* Interrupt masked status */
#define CIPSR 0x100 /* Combined interrupt pending */
struct combiner_softc {
struct resource *res[1 + NGRP];
bus_space_tag_t bst;
bus_space_handle_t bsh;
void *ih[NGRP];
device_t dev;
};
struct combiner_softc *combiner_sc;
static struct resource_spec combiner_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 1, RF_ACTIVE },
{ SYS_RES_IRQ, 2, RF_ACTIVE },
{ SYS_RES_IRQ, 3, RF_ACTIVE },
{ SYS_RES_IRQ, 4, RF_ACTIVE },
{ SYS_RES_IRQ, 5, RF_ACTIVE },
{ SYS_RES_IRQ, 6, RF_ACTIVE },
{ SYS_RES_IRQ, 7, RF_ACTIVE },
{ SYS_RES_IRQ, 8, RF_ACTIVE },
{ SYS_RES_IRQ, 9, RF_ACTIVE },
{ SYS_RES_IRQ, 10, RF_ACTIVE },
{ SYS_RES_IRQ, 11, RF_ACTIVE },
{ SYS_RES_IRQ, 12, RF_ACTIVE },
{ SYS_RES_IRQ, 13, RF_ACTIVE },
{ SYS_RES_IRQ, 14, RF_ACTIVE },
{ SYS_RES_IRQ, 15, RF_ACTIVE },
{ SYS_RES_IRQ, 16, RF_ACTIVE },
{ SYS_RES_IRQ, 17, RF_ACTIVE },
{ SYS_RES_IRQ, 18, RF_ACTIVE },
{ SYS_RES_IRQ, 19, RF_ACTIVE },
{ SYS_RES_IRQ, 20, RF_ACTIVE },
{ SYS_RES_IRQ, 21, RF_ACTIVE },
{ SYS_RES_IRQ, 22, RF_ACTIVE },
{ SYS_RES_IRQ, 23, RF_ACTIVE },
{ SYS_RES_IRQ, 24, RF_ACTIVE },
{ SYS_RES_IRQ, 25, RF_ACTIVE },
{ SYS_RES_IRQ, 26, RF_ACTIVE },
{ SYS_RES_IRQ, 27, RF_ACTIVE },
{ SYS_RES_IRQ, 28, RF_ACTIVE },
{ SYS_RES_IRQ, 29, RF_ACTIVE },
{ SYS_RES_IRQ, 30, RF_ACTIVE },
{ SYS_RES_IRQ, 31, RF_ACTIVE },
{ -1, 0 }
};
struct combiner_entry {
int combiner_id;
int bit;
char *source_name;
};
static struct combiner_entry interrupt_table[ITABLE_LEN] = {
{ 63, 1, "EINT[15]" },
{ 63, 0, "EINT[14]" },
{ 62, 1, "EINT[13]" },
{ 62, 0, "EINT[12]" },
{ 61, 1, "EINT[11]" },
{ 61, 0, "EINT[10]" },
{ 60, 1, "EINT[9]" },
{ 60, 0, "EINT[8]" },
{ 59, 1, "EINT[7]" },
{ 59, 0, "EINT[6]" },
{ 58, 1, "EINT[5]" },
{ 58, 0, "EINT[4]" },
{ 57, 3, "MCT_G3" },
{ 57, 2, "MCT_G2" },
{ 57, 1, "EINT[3]" },
{ 57, 0, "EINT[2]" },
{ 56, 6, "SYSMMU_G2D[1]" },
{ 56, 5, "SYSMMU_G2D[0]" },
{ 56, 2, "SYSMMU_FIMC_LITE1[1]" },
{ 56, 1, "SYSMMU_FIMC_LITE1[0]" },
{ 56, 0, "EINT[1]" },
{ 55, 4, "MCT_G1" },
{ 55, 3, "MCT_G0" },
{ 55, 0, "EINT[0]" },
/* TODO: add groups 54-32 */
};
struct combined_intr {
uint32_t enabled;
void (*ih) (void *);
void *ih_user;
};
static struct combined_intr intr_map[32][8];
static void
combiner_intr(void *arg)
{
struct combiner_softc *sc;
void (*ih) (void *);
void *ih_user;
int enabled;
int intrs;
int shift;
int cirq;
int grp;
int i,n;
sc = arg;
intrs = READ4(sc, CIPSR);
for (grp = 0; grp < 32; grp++) {
if (intrs & (1 << grp)) {
n = (grp / 4);
shift = (grp % 4) * 8;
cirq = READ4(sc, ISTR(n));
for (i = 0; i < 8; i++) {
if (cirq & (1 << (i + shift))) {
ih = intr_map[grp][i].ih;
ih_user = intr_map[grp][i].ih_user;
enabled = intr_map[grp][i].enabled;
if (enabled && (ih != NULL)) {
ih(ih_user);
}
}
}
}
}
}
void
combiner_setup_intr(char *source_name, void (*ih)(void *), void *ih_user)
{
struct combiner_entry *entry;
struct combined_intr *cirq;
struct combiner_softc *sc;
int shift;
int reg;
int grp;
int n;
int i;
sc = combiner_sc;
if (sc == NULL) {
device_printf(sc->dev, "Error: combiner is not attached\n");
return;
}
entry = NULL;
for (i = 0; i < ITABLE_LEN; i++) {
if (strcmp(interrupt_table[i].source_name, source_name) == 0) {
entry = &interrupt_table[i];
}
}
if (entry == NULL) {
device_printf(sc->dev, "Can't find interrupt name %s\n",
source_name);
return;
}
#if 0
device_printf(sc->dev, "Setting up interrupt %s\n", source_name);
#endif
grp = entry->combiner_id - 32;
cirq = &intr_map[grp][entry->bit];
cirq->enabled = 1;
cirq->ih = ih;
cirq->ih_user = ih_user;
n = grp / 4;
shift = (grp % 4) * 8 + entry->bit;
reg = (1 << shift);
WRITE4(sc, IESR(n), reg);
}
static int
combiner_probe(device_t dev)
{
if (!ofw_bus_is_compatible(dev, "exynos,combiner"))
return (ENXIO);
device_set_desc(dev, "Samsung Exynos 5 Interrupt Combiner");
return (BUS_PROBE_DEFAULT);
}
static int
combiner_attach(device_t dev)
{
struct combiner_softc *sc;
int err;
int i;
sc = device_get_softc(dev);
sc->dev = dev;
if (bus_alloc_resources(dev, combiner_spec, sc->res)) {
device_printf(dev, "could not allocate resources\n");
return (ENXIO);
}
/* Memory interface */
sc->bst = rman_get_bustag(sc->res[0]);
sc->bsh = rman_get_bushandle(sc->res[0]);
combiner_sc = sc;
/* Setup interrupt handler */
for (i = 0; i < NGRP; i++) {
err = bus_setup_intr(dev, sc->res[1+i], INTR_TYPE_BIO | \
INTR_MPSAFE, NULL, combiner_intr, sc, &sc->ih[i]);
if (err) {
device_printf(dev, "Unable to alloc int resource.\n");
return (ENXIO);
}
}
return (0);
}
static device_method_t combiner_methods[] = {
DEVMETHOD(device_probe, combiner_probe),
DEVMETHOD(device_attach, combiner_attach),
{ 0, 0 }
};
static driver_t combiner_driver = {
"combiner",
combiner_methods,
sizeof(struct combiner_softc),
};
static devclass_t combiner_devclass;
DRIVER_MODULE(combiner, simplebus, combiner_driver, combiner_devclass, 0, 0);

View File

@ -0,0 +1,29 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
void combiner_setup_intr(char *source_name, void (*ih)(void *), void *ih_user);

View File

@ -36,6 +36,7 @@ __FBSDID("$FreeBSD$");
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/rman.h>
#include <sys/gpio.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
@ -54,16 +55,14 @@ __FBSDID("$FreeBSD$");
#include <machine/bus.h>
#include <machine/resource.h>
#include "gpio_if.h"
#include "opt_platform.h"
/* GPIO control */
#define GPIO_CON(x, v) ((v) << ((x) * 4))
#define GPIO_MASK 0xf
#define GPIO_OUTPUT 1
#define GPIO_INPUT 0
#define GPX3CON 0x0
#define GPX3DAT 0x4
#define PIN_USB 5
#define PIN_USB 161
/* PWR control */
#define EXYNOS5_PWR_USBHOST_PHY 0x708
@ -90,16 +89,15 @@ static int exynos_ehci_detach(device_t dev);
static int exynos_ehci_probe(device_t dev);
struct exynos_ehci_softc {
device_t dev;
ehci_softc_t base;
struct resource *res[6];
struct resource *res[5];
bus_space_tag_t host_bst;
bus_space_tag_t pwr_bst;
bus_space_tag_t sysreg_bst;
bus_space_tag_t gpio_bst;
bus_space_handle_t host_bsh;
bus_space_handle_t pwr_bsh;
bus_space_handle_t sysreg_bsh;
bus_space_handle_t gpio_bsh;
};
@ -108,7 +106,6 @@ static struct resource_spec exynos_ehci_spec[] = {
{ SYS_RES_MEMORY, 1, RF_ACTIVE },
{ SYS_RES_MEMORY, 2, RF_ACTIVE },
{ SYS_RES_MEMORY, 3, RF_ACTIVE },
{ SYS_RES_MEMORY, 4, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE },
{ -1, 0 }
};
@ -160,19 +157,24 @@ exynos_ehci_probe(device_t dev)
static int
gpio_ctrl(struct exynos_ehci_softc *esc, int dir, int power)
{
int reg;
device_t gpio_dev;
/* Power control */
reg = bus_space_read_4(esc->gpio_bst, esc->gpio_bsh, GPX3DAT);
reg &= ~(1 << PIN_USB);
reg |= (power << PIN_USB);
bus_space_write_4(esc->gpio_bst, esc->gpio_bsh, GPX3DAT, reg);
/* Get the GPIO device, we need this to give power to USB */
gpio_dev = devclass_get_device(devclass_find("gpio"), 0);
if (gpio_dev == NULL) {
device_printf(esc->dev, "cant find gpio_dev\n");
return (1);
}
/* Input/Output control */
reg = bus_space_read_4(esc->gpio_bst, esc->gpio_bsh, GPX3CON);
reg &= ~GPIO_CON(PIN_USB, GPIO_MASK);
reg |= GPIO_CON(PIN_USB, dir);
bus_space_write_4(esc->gpio_bst, esc->gpio_bsh, GPX3CON, reg);
if (power)
GPIO_PIN_SET(gpio_dev, PIN_USB, GPIO_PIN_HIGH);
else
GPIO_PIN_SET(gpio_dev, PIN_USB, GPIO_PIN_LOW);
if (dir)
GPIO_PIN_SETFLAGS(gpio_dev, PIN_USB, GPIO_PIN_OUTPUT);
else
GPIO_PIN_SETFLAGS(gpio_dev, PIN_USB, GPIO_PIN_INPUT);
return (0);
}
@ -224,6 +226,7 @@ exynos_ehci_attach(device_t dev)
int err;
esc = device_get_softc(dev);
esc->dev = dev;
sc = &esc->base;
sc->sc_bus.parent = dev;
sc->sc_bus.devices = sc->sc_devices;
@ -251,10 +254,6 @@ exynos_ehci_attach(device_t dev)
esc->sysreg_bst = rman_get_bustag(esc->res[3]);
esc->sysreg_bsh = rman_get_bushandle(esc->res[3]);
/* GPIO */
esc->gpio_bst = rman_get_bustag(esc->res[4]);
esc->gpio_bsh = rman_get_bushandle(esc->res[4]);
/* get all DMA memory */
if (usb_bus_mem_alloc_all(&sc->sc_bus, USB_GET_DMA_TAG(dev),
&ehci_iterate_hw_softc))
@ -272,7 +271,7 @@ exynos_ehci_attach(device_t dev)
phy_init(esc);
/* Setup interrupt handler */
err = bus_setup_intr(dev, esc->res[5], INTR_TYPE_BIO | INTR_MPSAFE,
err = bus_setup_intr(dev, esc->res[4], INTR_TYPE_BIO | INTR_MPSAFE,
NULL, (driver_intr_t *)ehci_interrupt, sc,
&sc->sc_intr_hdl);
if (err) {
@ -285,7 +284,7 @@ exynos_ehci_attach(device_t dev)
sc->sc_bus.bdev = device_add_child(dev, "usbus", -1);
if (!sc->sc_bus.bdev) {
device_printf(dev, "Could not add USB device\n");
err = bus_teardown_intr(dev, esc->res[5],
err = bus_teardown_intr(dev, esc->res[4],
sc->sc_intr_hdl);
if (err)
device_printf(dev, "Could not tear down irq,"
@ -306,7 +305,7 @@ exynos_ehci_attach(device_t dev)
device_delete_child(dev, sc->sc_bus.bdev);
sc->sc_bus.bdev = NULL;
err = bus_teardown_intr(dev, esc->res[5],
err = bus_teardown_intr(dev, esc->res[4],
sc->sc_intr_hdl);
if (err)
device_printf(dev, "Could not tear down irq,"
@ -345,8 +344,8 @@ exynos_ehci_detach(device_t dev)
bus_space_write_4(sc->sc_io_tag, sc->sc_io_hdl,
EHCI_USBINTR, 0);
if (esc->res[5] && sc->sc_intr_hdl) {
err = bus_teardown_intr(dev, esc->res[5],
if (esc->res[4] && sc->sc_intr_hdl) {
err = bus_teardown_intr(dev, esc->res[4],
sc->sc_intr_hdl);
if (err) {
device_printf(dev, "Could not tear down irq,"

View File

@ -0,0 +1,476 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Samsung Exynos 5 Inter-Integrated Circuit (I2C)
* Chapter 13, Exynos 5 Dual User's Manual Public Rev 1.00
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <dev/iicbus/iiconf.h>
#include <dev/iicbus/iicbus.h>
#include "iicbus_if.h"
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#include <arm/samsung/exynos/exynos5_common.h>
#define I2CCON 0x00 /* Control register */
#define ACKGEN (1 << 7) /* Acknowledge Enable */
/*
* Source Clock of I2C-bus Transmit Clock Prescaler
*
* 0 = I2CCLK = fPCLK/16
* 1 = I2CCLK = fPCLK/512
*/
#define I2CCLK (1 << 6)
#define IRQ_EN (1 << 5) /* Tx/Rx Interrupt Enable/Disable */
#define IPEND (1 << 4) /* Tx/Rx Interrupt Pending Flag */
#define CLKVAL_M 0xf /* Transmit Clock Prescaler Mask */
#define CLKVAL_S 0
#define I2CSTAT 0x04 /* Control/status register */
#define I2CMODE_M 0x3 /* Master/Slave Tx/Rx Mode Select */
#define I2CMODE_S 6
#define I2CMODE_SR 0x0 /* Slave Receive Mode */
#define I2CMODE_ST 0x1 /* Slave Transmit Mode */
#define I2CMODE_MR 0x2 /* Master Receive Mode */
#define I2CMODE_MT 0x3 /* Master Transmit Mode */
#define I2CSTAT_BSY (1 << 5) /* Busy Signal Status bit */
#define I2C_START_STOP (1 << 5) /* Busy Signal Status bit */
#define RXTX_EN (1 << 4) /* Data Output Enable/Disable */
#define ARBST (1 << 3) /* Arbitration status flag */
#define ADDAS (1 << 2) /* Address-as-slave Status Flag */
#define ADDZERO (1 << 1) /* Address Zero Status Flag */
#define ACKRECVD (1 << 0) /* Last-received Bit Status Flag */
#define I2CADD 0x08 /* Address register */
#define I2CDS 0x0C /* Transmit/receive data shift */
#define I2CLC 0x10 /* Multi-master line control */
#define FILTER_EN (1 << 2) /* Filter Enable bit */
#define SDAOUT_DELAY_M 0x3 /* SDA Line Delay Length */
#define SDAOUT_DELAY_S 0
#ifdef DEBUG
#define DPRINTF(fmt, args...) \
printf(fmt, ##args)
#else
#define DPRINTF(fmt, args...)
#endif
static int i2c_start(device_t, u_char, int);
static int i2c_stop(device_t);
static int i2c_reset(device_t, u_char, u_char, u_char *);
static int i2c_read(device_t, char *, int, int *, int, int);
static int i2c_write(device_t, const char *, int, int *, int);
struct i2c_softc {
struct resource *res[2];
bus_space_tag_t bst;
bus_space_handle_t bsh;
device_t dev;
device_t iicbus;
struct mtx mutex;
void *ih;
int intr;
};
static struct resource_spec i2c_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE },
{ -1, 0 }
};
static int
i2c_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "exynos,i2c"))
return (ENXIO);
device_set_desc(dev, "Samsung Exynos 5 I2C controller");
return (BUS_PROBE_DEFAULT);
}
static int
clear_ipend(struct i2c_softc *sc)
{
int reg;
reg = READ1(sc, I2CCON);
reg &= ~(IPEND);
WRITE1(sc, I2CCON, reg);
return (0);
}
static int
i2c_attach(device_t dev)
{
struct i2c_softc *sc;
int reg;
sc = device_get_softc(dev);
sc->dev = dev;
mtx_init(&sc->mutex, device_get_nameunit(dev), "I2C", MTX_DEF);
if (bus_alloc_resources(dev, i2c_spec, sc->res)) {
device_printf(dev, "could not allocate resources\n");
return (ENXIO);
}
/* Memory interface */
sc->bst = rman_get_bustag(sc->res[0]);
sc->bsh = rman_get_bushandle(sc->res[0]);
sc->iicbus = device_add_child(dev, "iicbus", -1);
if (sc->iicbus == NULL) {
device_printf(dev, "could not add iicbus child");
mtx_destroy(&sc->mutex);
return (ENXIO);
}
WRITE1(sc, I2CSTAT, 0);
WRITE1(sc, I2CADD, 0x00);
/* Mode */
reg = (RXTX_EN);
reg |= (I2CMODE_MT << I2CMODE_S);
WRITE1(sc, I2CSTAT, reg);
bus_generic_attach(dev);
return (0);
}
static int
wait_for_iif(struct i2c_softc *sc)
{
int retry;
int reg;
retry = 1000;
while (retry --) {
reg = READ1(sc, I2CCON);
if (reg & IPEND) {
return (IIC_NOERR);
}
DELAY(50);
}
return (IIC_ETIMEOUT);
}
static int
wait_for_nibb(struct i2c_softc *sc)
{
int retry;
retry = 1000;
while (retry --) {
if ((READ1(sc, I2CSTAT) & I2CSTAT_BSY) == 0)
return (IIC_NOERR);
DELAY(10);
}
return (IIC_ETIMEOUT);
}
static int
is_ack(struct i2c_softc *sc)
{
int stat;
stat = READ1(sc, I2CSTAT);
if (!(stat & 1)) {
/* ACK received */
return (1);
}
return (0);
}
static int
i2c_start(device_t dev, u_char slave, int timeout)
{
struct i2c_softc *sc;
int error;
int reg;
sc = device_get_softc(dev);
DPRINTF("i2c start\n");
mtx_lock(&sc->mutex);
#if 0
DPRINTF("I2CCON == 0x%08x\n", READ1(sc, I2CCON));
DPRINTF("I2CSTAT == 0x%08x\n", READ1(sc, I2CSTAT));
#endif
if (slave & 1) {
slave &= ~(1);
slave <<= 1;
slave |= 1;
} else {
slave <<= 1;
}
error = wait_for_nibb(sc);
if (error) {
mtx_unlock(&sc->mutex);
DPRINTF("cant i2c start: IIC_EBUSBSY\n");
return (IIC_EBUSBSY);
}
reg = READ1(sc, I2CCON);
reg |= (IRQ_EN | ACKGEN);
WRITE1(sc, I2CCON, reg);
WRITE1(sc, I2CDS, slave);
DELAY(50);
reg = (RXTX_EN);
reg |= I2C_START_STOP;
reg |= (I2CMODE_MT << I2CMODE_S);
WRITE1(sc, I2CSTAT, reg);
error = wait_for_iif(sc);
if (error) {
DPRINTF("cant i2c start: iif error\n");
mtx_unlock(&sc->mutex);
return (error);
}
if (!is_ack(sc)) {
DPRINTF("cant i2c start: no ack\n");
mtx_unlock(&sc->mutex);
return (IIC_ENOACK);
};
mtx_unlock(&sc->mutex);
return (IIC_NOERR);
}
static int
i2c_stop(device_t dev)
{
struct i2c_softc *sc;
int reg;
int error;
sc = device_get_softc(dev);
DPRINTF("i2c stop\n");
mtx_lock(&sc->mutex);
reg = READ1(sc, I2CSTAT);
int mode = (reg >> I2CMODE_S) & I2CMODE_M;
reg = (RXTX_EN);
reg |= (mode << I2CMODE_S);
WRITE1(sc, I2CSTAT, reg);
clear_ipend(sc);
error = wait_for_nibb(sc);
if (error) {
DPRINTF("cant i2c stop: nibb error\n");
return (error);
}
mtx_unlock(&sc->mutex);
return (IIC_NOERR);
}
static int
i2c_reset(device_t dev, u_char speed, u_char addr, u_char *oldadr)
{
struct i2c_softc *sc;
sc = device_get_softc(dev);
DPRINTF("i2c reset\n");
mtx_lock(&sc->mutex);
/* TODO */
mtx_unlock(&sc->mutex);
return (IIC_NOERR);
}
static int
i2c_read(device_t dev, char *buf, int len,
int *read, int last, int delay)
{
struct i2c_softc *sc;
int error;
int reg;
uint8_t d;
sc = device_get_softc(dev);
DPRINTF("i2c read\n");
reg = (RXTX_EN);
reg |= (I2CMODE_MR << I2CMODE_S);
reg |= I2C_START_STOP;
WRITE1(sc, I2CSTAT, reg);
*read = 0;
mtx_lock(&sc->mutex);
/* dummy read */
READ1(sc, I2CDS);
DPRINTF("Read ");
while (*read < len) {
/* Do not ack last read */
if (*read == (len - 1)) {
reg = READ1(sc, I2CCON);
reg &= ~(ACKGEN);
WRITE1(sc, I2CCON, reg);
};
clear_ipend(sc);
error = wait_for_iif(sc);
if (error) {
DPRINTF("cant i2c read: iif error\n");
mtx_unlock(&sc->mutex);
return (error);
}
d = READ1(sc, I2CDS);
DPRINTF("0x%02x ", d);
*buf++ = d;
(*read)++;
}
DPRINTF("\n");
mtx_unlock(&sc->mutex);
return (IIC_NOERR);
}
static int
i2c_write(device_t dev, const char *buf, int len, int *sent, int timeout)
{
struct i2c_softc *sc;
int error;
sc = device_get_softc(dev);
DPRINTF("i2c write\n");
*sent = 0;
mtx_lock(&sc->mutex);
DPRINTF("writing ");
while (*sent < len) {
uint8_t d = *buf++;
DPRINTF("0x%02x ", d);
WRITE1(sc, I2CDS, d);
DELAY(50);
clear_ipend(sc);
error = wait_for_iif(sc);
if (error) {
DPRINTF("cant i2c write: iif error\n");
mtx_unlock(&sc->mutex);
return (error);
}
if (!is_ack(sc)) {
DPRINTF("cant i2c write: no ack\n");
mtx_unlock(&sc->mutex);
return (IIC_ENOACK);
};
(*sent)++;
}
DPRINTF("\n");
mtx_unlock(&sc->mutex);
return (IIC_NOERR);
}
static device_method_t i2c_methods[] = {
DEVMETHOD(device_probe, i2c_probe),
DEVMETHOD(device_attach, i2c_attach),
DEVMETHOD(iicbus_callback, iicbus_null_callback),
DEVMETHOD(iicbus_start, i2c_start),
DEVMETHOD(iicbus_stop, i2c_stop),
DEVMETHOD(iicbus_reset, i2c_reset),
DEVMETHOD(iicbus_read, i2c_read),
DEVMETHOD(iicbus_write, i2c_write),
DEVMETHOD(iicbus_transfer, iicbus_transfer_gen),
{ 0, 0 }
};
static driver_t i2c_driver = {
"i2c",
i2c_methods,
sizeof(struct i2c_softc),
};
static devclass_t i2c_devclass;
DRIVER_MODULE(i2c, simplebus, i2c_driver, i2c_devclass, 0, 0);
DRIVER_MODULE(iicbus, i2c, iicbus_driver, iicbus_devclass, 0, 0);

View File

@ -0,0 +1,716 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* Samsung Exynos 5 Pad Control
* Chapter 4, Exynos 5 Dual User's Manual Public Rev 1.00
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/malloc.h>
#include <sys/rman.h>
#include <sys/timeet.h>
#include <sys/timetc.h>
#include <sys/watchdog.h>
#include <sys/mutex.h>
#include <sys/gpio.h>
#include <dev/fdt/fdt_common.h>
#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/cpu.h>
#include <machine/intr.h>
#include "gpio_if.h"
#include <arm/samsung/exynos/exynos5_combiner.h>
#include <arm/samsung/exynos/exynos5_pad.h>
#define GPIO_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx)
#define GPIO_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx)
#define DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)
#define NPORTS 4
#define NGRP 40
#define NGPIO 253
#define NINTS 16
#define PIN_IN 0
#define PIN_OUT 1
#define READ4(_sc, _port, _reg) \
bus_space_read_4(_sc->bst[_port], _sc->bsh[_port], _reg)
#define WRITE4(_sc, _port, _reg, _val) \
bus_space_write_4(_sc->bst[_port], _sc->bsh[_port], _reg, _val)
/*
* GPIO interface
*/
static int pad_pin_max(device_t, int *);
static int pad_pin_getcaps(device_t, uint32_t, uint32_t *);
static int pad_pin_getname(device_t, uint32_t, char *);
static int pad_pin_getflags(device_t, uint32_t, uint32_t *);
static int pad_pin_setflags(device_t, uint32_t, uint32_t);
static int pad_pin_set(device_t, uint32_t, unsigned int);
static int pad_pin_get(device_t, uint32_t, unsigned int *);
static int pad_pin_toggle(device_t, uint32_t pin);
struct pad_softc {
struct resource *res[NPORTS+4];
bus_space_tag_t bst[NPORTS];
bus_space_handle_t bsh[NPORTS];
struct mtx sc_mtx;
int gpio_npins;
struct gpio_pin gpio_pins[NGPIO];
void *gpio_ih[NPORTS+4];
device_t dev;
};
struct pad_softc *gpio_sc;
static struct resource_spec pad_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ SYS_RES_MEMORY, 1, RF_ACTIVE },
{ SYS_RES_MEMORY, 2, RF_ACTIVE },
{ SYS_RES_MEMORY, 3, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 1, RF_ACTIVE },
{ SYS_RES_IRQ, 2, RF_ACTIVE },
{ SYS_RES_IRQ, 3, RF_ACTIVE },
{ -1, 0 }
};
struct pad_intr {
uint32_t enabled;
void (*ih) (void *);
void *ih_user;
};
static struct pad_intr intr_map[NGPIO];
struct interrupt_entry {
int gpio_number;
char *combiner_source_name;
};
struct interrupt_entry interrupt_table[NINTS] = {
{ 147, "EINT[15]" },
{ 146, "EINT[14]" },
{ 145, "EINT[13]" },
{ 144, "EINT[12]" },
{ 143, "EINT[11]" },
{ 142, "EINT[10]" },
{ 141, "EINT[9]" },
{ 140, "EINT[8]" },
{ 139, "EINT[7]" },
{ 138, "EINT[6]" },
{ 137, "EINT[5]" },
{ 136, "EINT[4]" },
{ 135, "EINT[3]" },
{ 134, "EINT[2]" },
{ 133, "EINT[1]" },
{ 132, "EINT[0]" },
};
struct gpio_bank {
char *name;
uint32_t port;
uint32_t con;
uint32_t ngpio;
uint32_t ext_int_grp;
uint32_t ext_con;
uint32_t ext_flt_con;
uint32_t mask;
uint32_t pend;
};
/*
* 253 multi-functional input/output ports
*/
static struct gpio_bank gpio_map[] = {
/* first 132 gpio */
{ "gpa0", 0, 0x000, 8, 1, 0x700, 0x800, 0x900, 0xA00 },
{ "gpa1", 0, 0x020, 6, 2, 0x704, 0x808, 0x904, 0xA04 },
{ "gpa2", 0, 0x040, 8, 3, 0x708, 0x810, 0x908, 0xA08 },
{ "gpb0", 0, 0x060, 5, 4, 0x70C, 0x818, 0x90C, 0xA0C },
{ "gpb1", 0, 0x080, 5, 5, 0x710, 0x820, 0x910, 0xA10 },
{ "gpb2", 0, 0x0A0, 4, 6, 0x714, 0x828, 0x914, 0xA14 },
{ "gpb3", 0, 0x0C0, 4, 7, 0x718, 0x830, 0x918, 0xA18 },
{ "gpc0", 0, 0x0E0, 7, 8, 0x71C, 0x838, 0x91C, 0xA1C },
{ "gpc1", 0, 0x100, 4, 9, 0x720, 0x840, 0x920, 0xA20 },
{ "gpc2", 0, 0x120, 7, 10, 0x724, 0x848, 0x924, 0xA24 },
{ "gpc3", 0, 0x140, 7, 11, 0x728, 0x850, 0x928, 0xA28 },
{ "gpd0", 0, 0x160, 4, 12, 0x72C, 0x858, 0x92C, 0xA2C },
{ "gpd1", 0, 0x180, 8, 13, 0x730, 0x860, 0x930, 0xA30 },
{ "gpy0", 0, 0x1A0, 6, 0, 0, 0, 0, 0 },
{ "gpy1", 0, 0x1C0, 4, 0, 0, 0, 0, 0 },
{ "gpy2", 0, 0x1E0, 6, 0, 0, 0, 0, 0 },
{ "gpy3", 0, 0x200, 8, 0, 0, 0, 0, 0 },
{ "gpy4", 0, 0x220, 8, 0, 0, 0, 0, 0 },
{ "gpy5", 0, 0x240, 8, 0, 0, 0, 0, 0 },
{ "gpy6", 0, 0x260, 8, 0, 0, 0, 0, 0 },
{ "gpc4", 0, 0x2E0, 7, 30, 0x734, 0x868, 0x934, 0xA34 },
/* next 32 */
{ "gpx0", 0, 0xC00, 8, 40, 0xE00, 0xE80, 0xF00, 0xF40 },
{ "gpx1", 0, 0xC20, 8, 41, 0xE04, 0xE88, 0xF04, 0xF44 },
{ "gpx2", 0, 0xC40, 8, 42, 0xE08, 0xE90, 0xF08, 0xF48 },
{ "gpx3", 0, 0xC60, 8, 43, 0xE0C, 0xE98, 0xF0C, 0xF4C },
{ "gpe0", 1, 0x000, 8, 14, 0x700, 0x800, 0x900, 0xA00 },
{ "gpe1", 1, 0x020, 2, 15, 0x704, 0x808, 0x904, 0xA04 },
{ "gpf0", 1, 0x040, 4, 16, 0x708, 0x810, 0x908, 0xA08 },
{ "gpf1", 1, 0x060, 4, 17, 0x70C, 0x818, 0x90C, 0xA0C },
{ "gpg0", 1, 0x080, 8, 18, 0x710, 0x820, 0x910, 0xA10 },
{ "gpg1", 1, 0x0A0, 8, 19, 0x714, 0x828, 0x914, 0xA14 },
{ "gpg2", 1, 0x0C0, 2, 20, 0x718, 0x830, 0x918, 0xA18 },
{ "gph0", 1, 0x0E0, 4, 21, 0x71C, 0x838, 0x91C, 0xA1C },
{ "gph1", 1, 0x100, 8, 22, 0x720, 0x840, 0x920, 0xA20 },
{ "gpv0", 2, 0x000, 8, 60, 0x700, 0x800, 0x900, 0xA00 },
{ "gpv1", 2, 0x020, 8, 61, 0x704, 0x808, 0x904, 0xA04 },
{ "gpv2", 2, 0x060, 8, 62, 0x708, 0x810, 0x908, 0xA08 },
{ "gpv3", 2, 0x080, 8, 63, 0x70C, 0x818, 0x90C, 0xA0C },
{ "gpv4", 2, 0x0C0, 2, 64, 0x710, 0x820, 0x910, 0xA10 },
{ "gpz", 3, 0x000, 7, 50, 0x700, 0x800, 0x900, 0xA00 },
};
static int
get_bank(int gpio_number, struct gpio_bank *bank, int *pin_shift)
{
int ngpio;
int i;
int n;
n = 0;
for (i = 0; i < NGRP; i++) {
ngpio = gpio_map[i].ngpio;
if ((n + ngpio) >= gpio_number) {
*bank = gpio_map[i];
*pin_shift = (gpio_number - n);
return (0);
};
n += ngpio;
};
return (-1);
}
static int
port_intr(void *arg)
{
struct port_softc *sc;
sc = arg;
return (FILTER_HANDLED);
}
static void
ext_intr(void *arg)
{
struct pad_softc *sc;
void (*ih) (void *);
void *ih_user;
int ngpio;
int found;
int reg;
int i,j;
int n,k;
sc = arg;
n = 0;
for (i = 0; i < NGRP; i++) {
found = 0;
ngpio = gpio_map[i].ngpio;
if (gpio_map[i].pend == 0) {
n += ngpio;
continue;
}
reg = READ4(sc, gpio_map[i].port, gpio_map[i].pend);
for (j = 0; j < ngpio; j++) {
if (reg & (1 << j)) {
found = 1;
k = (n + j);
if (intr_map[k].enabled == 1) {
ih = intr_map[k].ih;
ih_user = intr_map[k].ih_user;
ih(ih_user);
}
}
}
if (found) {
/* ACK */
WRITE4(sc, gpio_map[i].port, gpio_map[i].pend, reg);
}
n += ngpio;
}
}
int
pad_setup_intr(int gpio_number, void (*ih)(void *), void *ih_user)
{
struct interrupt_entry *entry;
struct pad_intr *pad_irq;
struct gpio_bank bank;
struct pad_softc *sc;
int pin_shift;
int reg;
int i;
sc = gpio_sc;
if (sc == NULL) {
device_printf(sc->dev, "Error: pad is not attached\n");
return (-1);
}
if (get_bank(gpio_number, &bank, &pin_shift) != 0)
return (-1);
entry = NULL;
for (i = 0; i < NINTS; i++)
if (interrupt_table[i].gpio_number == gpio_number)
entry = &interrupt_table[i];
if (entry == NULL) {
device_printf(sc->dev, "Cant find interrupt source for %d\n",
gpio_number);
return (-1);
}
#if 0
printf("Request interrupt name %s\n", entry->combiner_source_name);
#endif
pad_irq = &intr_map[gpio_number];
pad_irq->enabled = 1;
pad_irq->ih = ih;
pad_irq->ih_user = ih_user;
/* Setup port as external interrupt source */
reg = READ4(sc, bank.port, bank.con);
reg |= (0xf << (pin_shift * 4));
#if 0
printf("writing 0x%08x to 0x%08x\n", reg, bank.con);
#endif
WRITE4(sc, bank.port, bank.con, reg);
/*
* Configure interrupt pin
*
* 0x0 = Sets Low level
* 0x1 = Sets High level
* 0x2 = Triggers Falling edge
* 0x3 = Triggers Rising edge
* 0x4 = Triggers Both edge
*
* TODO: add parameter. For now configure as 0x0
*/
reg = READ4(sc, bank.port, bank.ext_con);
reg &= ~(0x7 << (pin_shift * 4));
WRITE4(sc, bank.port, bank.ext_con, reg);
/* Unmask */
reg = READ4(sc, bank.port, bank.mask);
reg &= ~(1 << pin_shift);
WRITE4(sc, bank.port, bank.mask, reg);
combiner_setup_intr(entry->combiner_source_name, ext_intr, sc);
return (0);
}
static int
pad_probe(device_t dev)
{
if (!ofw_bus_status_okay(dev))
return (ENXIO);
if (!ofw_bus_is_compatible(dev, "exynos,pad"))
return (ENXIO);
device_set_desc(dev, "Exynos Pad Control");
return (BUS_PROBE_DEFAULT);
}
static int
pad_attach(device_t dev)
{
struct gpio_bank bank;
struct pad_softc *sc;
int pin_shift;
int reg;
int i;
sc = device_get_softc(dev);
mtx_init(&sc->sc_mtx, device_get_nameunit(dev), NULL, MTX_DEF);
if (bus_alloc_resources(dev, pad_spec, sc->res)) {
device_printf(dev, "could not allocate resources\n");
return (ENXIO);
}
/* Memory interface */
for (i = 0; i < NPORTS; i++) {
sc->bst[i] = rman_get_bustag(sc->res[i]);
sc->bsh[i] = rman_get_bushandle(sc->res[i]);
};
sc->dev = dev;
sc->gpio_npins = NGPIO;
gpio_sc = sc;
for (i = 0; i < NPORTS; i++) {
if ((bus_setup_intr(dev, sc->res[NPORTS + i],
INTR_TYPE_BIO | INTR_MPSAFE, port_intr,
NULL, sc, &sc->gpio_ih[i]))) {
device_printf(dev,
"ERROR: Unable to register interrupt handler\n");
return (ENXIO);
}
};
for (i = 0; i < sc->gpio_npins; i++) {
sc->gpio_pins[i].gp_pin = i;
sc->gpio_pins[i].gp_caps = DEFAULT_CAPS;
if (get_bank(i, &bank, &pin_shift) != 0)
continue;
pin_shift *= 4;
reg = READ4(sc, bank.port, bank.con);
if (reg & (PIN_OUT << pin_shift))
sc->gpio_pins[i].gp_flags = GPIO_PIN_OUTPUT;
else
sc->gpio_pins[i].gp_flags = GPIO_PIN_INPUT;
/* TODO: add other pin statuses */
snprintf(sc->gpio_pins[i].gp_name, GPIOMAXNAME,
"pad%d.%d", device_get_unit(dev), i);
}
device_add_child(dev, "gpioc", device_get_unit(dev));
device_add_child(dev, "gpiobus", device_get_unit(dev));
return (bus_generic_attach(dev));
}
static int
pad_pin_max(device_t dev, int *maxpin)
{
*maxpin = NGPIO - 1;
return (0);
}
static int
pad_pin_getname(device_t dev, uint32_t pin, char *name)
{
struct pad_softc *sc;
int i;
sc = device_get_softc(dev);
for (i = 0; i < sc->gpio_npins; i++) {
if (sc->gpio_pins[i].gp_pin == pin)
break;
}
if (i >= sc->gpio_npins)
return (EINVAL);
GPIO_LOCK(sc);
memcpy(name, sc->gpio_pins[i].gp_name, GPIOMAXNAME);
GPIO_UNLOCK(sc);
return (0);
}
static int
pad_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
{
struct pad_softc *sc;
int i;
sc = device_get_softc(dev);
for (i = 0; i < sc->gpio_npins; i++) {
if (sc->gpio_pins[i].gp_pin == pin)
break;
}
if (i >= sc->gpio_npins)
return (EINVAL);
GPIO_LOCK(sc);
*caps = sc->gpio_pins[i].gp_caps;
GPIO_UNLOCK(sc);
return (0);
}
static int
pad_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
{
struct pad_softc *sc;
int i;
sc = device_get_softc(dev);
for (i = 0; i < sc->gpio_npins; i++) {
if (sc->gpio_pins[i].gp_pin == pin)
break;
}
if (i >= sc->gpio_npins)
return (EINVAL);
GPIO_LOCK(sc);
*flags = sc->gpio_pins[i].gp_flags;
GPIO_UNLOCK(sc);
return (0);
}
static int
pad_pin_get(device_t dev, uint32_t pin, unsigned int *val)
{
struct gpio_bank bank;
struct pad_softc *sc;
int pin_shift;
int i;
sc = device_get_softc(dev);
for (i = 0; i < sc->gpio_npins; i++) {
if (sc->gpio_pins[i].gp_pin == pin)
break;
}
if (i >= sc->gpio_npins)
return (EINVAL);
if (get_bank(pin, &bank, &pin_shift) != 0)
return (EINVAL);
GPIO_LOCK(sc);
if (READ4(sc, bank.port, bank.con + 0x4) & (1 << pin_shift))
*val = 1;
else
*val = 0;
GPIO_UNLOCK(sc);
return (0);
}
static int
pad_pin_toggle(device_t dev, uint32_t pin)
{
struct gpio_bank bank;
struct pad_softc *sc;
int pin_shift;
int reg;
int i;
sc = device_get_softc(dev);
for (i = 0; i < sc->gpio_npins; i++) {
if (sc->gpio_pins[i].gp_pin == pin)
break;
}
if (i >= sc->gpio_npins)
return (EINVAL);
if (get_bank(pin, &bank, &pin_shift) != 0)
return (EINVAL);
GPIO_LOCK(sc);
reg = READ4(sc, bank.port, bank.con + 0x4);
if (reg & (1 << pin_shift))
reg &= ~(1 << pin_shift);
else
reg |= (1 << pin_shift);
WRITE4(sc, bank.port, bank.con + 0x4, reg);
GPIO_UNLOCK(sc);
return (0);
}
static void
pad_pin_configure(struct pad_softc *sc, struct gpio_pin *pin,
unsigned int flags)
{
struct gpio_bank bank;
int pin_shift;
int reg;
GPIO_LOCK(sc);
/*
* Manage input/output
*/
if (flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) {
pin->gp_flags &= ~(GPIO_PIN_INPUT|GPIO_PIN_OUTPUT);
if (get_bank(pin->gp_pin, &bank, &pin_shift) != 0)
return;
pin_shift *= 4;
#if 0
printf("bank is 0x%08x pin_shift %d\n", bank.con, pin_shift);
#endif
if (flags & GPIO_PIN_OUTPUT) {
pin->gp_flags |= GPIO_PIN_OUTPUT;
reg = READ4(sc, bank.port, bank.con);
reg &= ~(0xf << pin_shift);
reg |= (PIN_OUT << pin_shift);
WRITE4(sc, bank.port, bank.con, reg);
} else {
pin->gp_flags |= GPIO_PIN_INPUT;
reg = READ4(sc, bank.port, bank.con);
reg &= ~(0xf << pin_shift);
WRITE4(sc, bank.port, bank.con, reg);
}
}
GPIO_UNLOCK(sc);
}
static int
pad_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
{
struct pad_softc *sc;
int i;
sc = device_get_softc(dev);
for (i = 0; i < sc->gpio_npins; i++) {
if (sc->gpio_pins[i].gp_pin == pin)
break;
}
if (i >= sc->gpio_npins)
return (EINVAL);
/* Check for unwanted flags. */
if ((flags & sc->gpio_pins[i].gp_caps) != flags)
return (EINVAL);
/* Can't mix input/output together */
if ((flags & (GPIO_PIN_INPUT|GPIO_PIN_OUTPUT)) ==
(GPIO_PIN_INPUT|GPIO_PIN_OUTPUT))
return (EINVAL);
pad_pin_configure(sc, &sc->gpio_pins[i], flags);
return (0);
}
static int
pad_pin_set(device_t dev, uint32_t pin, unsigned int value)
{
struct pad_softc *sc;
struct gpio_bank bank;
int pin_shift;
int reg;
int i;
sc = device_get_softc(dev);
for (i = 0; i < sc->gpio_npins; i++) {
if (sc->gpio_pins[i].gp_pin == pin)
break;
}
if (i >= sc->gpio_npins)
return (EINVAL);
if (get_bank(pin, &bank, &pin_shift) != 0)
return (EINVAL);
GPIO_LOCK(sc);
reg = READ4(sc, bank.port, bank.con + 0x4);
reg &= ~(PIN_OUT << pin_shift);
if (value)
reg |= (PIN_OUT << pin_shift);
WRITE4(sc, bank.port, bank.con + 0x4, reg);
GPIO_UNLOCK(sc);
return (0);
}
static device_method_t pad_methods[] = {
DEVMETHOD(device_probe, pad_probe),
DEVMETHOD(device_attach, pad_attach),
/* GPIO protocol */
DEVMETHOD(gpio_pin_max, pad_pin_max),
DEVMETHOD(gpio_pin_getname, pad_pin_getname),
DEVMETHOD(gpio_pin_getcaps, pad_pin_getcaps),
DEVMETHOD(gpio_pin_getflags, pad_pin_getflags),
DEVMETHOD(gpio_pin_get, pad_pin_get),
DEVMETHOD(gpio_pin_toggle, pad_pin_toggle),
DEVMETHOD(gpio_pin_setflags, pad_pin_setflags),
DEVMETHOD(gpio_pin_set, pad_pin_set),
{ 0, 0 }
};
static driver_t pad_driver = {
"gpio",
pad_methods,
sizeof(struct pad_softc),
};
static devclass_t pad_devclass;
DRIVER_MODULE(pad, simplebus, pad_driver, pad_devclass, 0, 0);

View File

@ -0,0 +1,29 @@
/*-
* Copyright (c) 2014 Ruslan Bukin <br@bsdpad.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
int pad_setup_intr(int gpio_number, void (*ih)(void *), void *ih_user);

View File

@ -17,8 +17,15 @@ arm/samsung/exynos/exynos5_mct.c standard
arm/samsung/exynos/exynos5_mp.c optional smp
arm/samsung/exynos/exynos5_common.c standard
arm/samsung/exynos/exynos5_machdep.c standard
arm/samsung/exynos/exynos5_combiner.c standard
arm/samsung/exynos/exynos5_pad.c optional gpio
arm/samsung/exynos/uart.c optional uart
arm/samsung/exynos/exynos5_ehci.c optional ehci
arm/samsung/exynos/exynos5_fimd.c optional vt
arm/samsung/exynos/exynos5_i2c.c optional iicbus
# chromebook drivers
arm/samsung/exynos/chrome_ec.c optional chrome_ec
arm/samsung/exynos/chrome_kb.c optional chrome_kb
#dev/sdhci/sdhci_fdt.c optional sdhci

View File

@ -38,6 +38,14 @@
reg = < 0x40000000 0x80000000 >; /* 2G */
};
SOC: Exynos5@0 {
pad0: pad@11400000 {
status = "okay";
};
};
chosen {
stdin = &serial2;
stdout = &serial2;

View File

@ -40,6 +40,10 @@
SOC: Exynos5@0 {
pad0: pad@11400000 {
status = "okay";
};
fimd0: fimd@14400000 {
status = "okay";
@ -50,6 +54,15 @@
panel-backlight-pin = < 25 >;
};
i2c4: i2c@12CA0000 {
status = "okay";
};
keyboard-controller {
compatible = "google,cros-ec-keyb";
keypad,num-rows = <8>;
keypad,num-columns = <13>;
};
};
chosen {

View File

@ -57,6 +57,16 @@
#interrupt-cells = <1>;
};
combiner: interrupt-controller@10440000 {
compatible = "exynos,combiner";
reg = <0x10440000 0x1000>;
interrupts = < 32 33 34 35 36 37 38 39
40 41 42 43 44 45 46 47
48 49 50 51 52 53 54 55
56 57 58 59 60 61 62 63 >;
interrupt-parent = <&GIC>;
};
clk0: clk@10010000 {
compatible = "exynos,clk";
reg = < 0x10020000 0x20000 >;
@ -81,13 +91,23 @@
clock-frequency = <24000000>;
};
pad0: pad@11400000 {
compatible = "exynos,pad";
status = "disabled";
reg = <0x11400000 0x1000>, /* gpio left */
<0x13400000 0x1000>, /* gpio right */
<0x10D10000 0x1000>, /* gpio c2c */
<0x03860000 0x1000>;
interrupts = < 78 77 82 79 >;
interrupt-parent = <&GIC>;
};
usb@12110000 {
compatible = "exynos,usb-ehci", "usb-ehci";
reg = <0x12110000 0x1000>, /* EHCI */
<0x12130000 0x1000>, /* EHCI host ctrl */
<0x10040000 0x1000>, /* Power */
<0x10050230 0x10>, /* Sysreg */
<0x11400C60 0x10>; /* GPIO left */
<0x10050230 0x10>; /* Sysreg */
interrupts = < 103 >;
interrupt-parent = <&GIC>;
};
@ -167,6 +187,70 @@
current-speed = <115200>;
};
i2c0: i2c@12C60000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12C60000 0x10000>;
interrupts = < 88 >;
interrupt-parent = <&GIC>;
};
i2c1: i2c@12C70000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12C70000 0x10000>;
interrupts = < 89 >;
interrupt-parent = <&GIC>;
};
i2c2: i2c@12C80000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12C80000 0x10000>;
interrupts = < 90 >;
interrupt-parent = <&GIC>;
};
i2c3: i2c@12C90000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12C90000 0x10000>;
interrupts = < 91 >;
interrupt-parent = <&GIC>;
};
i2c4: i2c@12CA0000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12CA0000 0x10000>;
interrupts = < 92 >;
interrupt-parent = <&GIC>;
};
i2c5: i2c@12CB0000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12CB0000 0x10000>;
interrupts = < 93 >;
interrupt-parent = <&GIC>;
};
i2c6: i2c@12CC0000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12CC0000 0x10000>;
interrupts = < 94 >;
interrupt-parent = <&GIC>;
};
i2c7: i2c@12CD0000 {
compatible = "exynos,i2c";
status = "disabled";
reg = <0x12CD0000 0x10000>;
interrupts = < 95 >;
interrupt-parent = <&GIC>;
};
fimd0: fimd@14400000 {
compatible = "exynos,fimd";
status = "disabled";