diff options
author | Ruslan Bukin <br@FreeBSD.org> | 2014-03-30 15:22:36 +0000 |
---|---|---|
committer | Ruslan Bukin <br@FreeBSD.org> | 2014-03-30 15:22:36 +0000 |
commit | 657fae63fc5eec23524ad29a37eea39db3fe5ad2 (patch) | |
tree | 93425d01f4fc2590333a1eec24e3bbee0adbe0a3 | |
parent | a3fcf1f8d914a76b677e2c8656af9e31bb8f671f (diff) | |
download | src-657fae63fc5eec23524ad29a37eea39db3fe5ad2.tar.gz src-657fae63fc5eec23524ad29a37eea39db3fe5ad2.zip |
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
Notes
Notes:
svn path=/head/; revision=263936
-rw-r--r-- | sys/arm/conf/CHROMEBOOK | 5 | ||||
-rw-r--r-- | sys/arm/conf/CHROMEBOOK.hints | 5 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/chrome_ec.c | 253 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/chrome_ec.h | 36 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/chrome_kb.c | 792 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/chrome_kb.h | 122 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/exynos5_combiner.c | 304 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/exynos5_combiner.h | 29 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/exynos5_ehci.c | 57 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/exynos5_i2c.c | 476 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/exynos5_pad.c | 716 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/exynos5_pad.h | 29 | ||||
-rw-r--r-- | sys/arm/samsung/exynos/files.exynos5 | 7 | ||||
-rw-r--r-- | sys/boot/fdt/dts/arm/exynos5250-arndale.dts | 8 | ||||
-rw-r--r-- | sys/boot/fdt/dts/arm/exynos5250-chromebook.dts | 13 | ||||
-rw-r--r-- | sys/boot/fdt/dts/arm/exynos5250.dtsi | 88 |
16 files changed, 2909 insertions, 31 deletions
diff --git a/sys/arm/conf/CHROMEBOOK b/sys/arm/conf/CHROMEBOOK index be6d75891caa..ea69035f332e 100644 --- a/sys/arm/conf/CHROMEBOOK +++ b/sys/arm/conf/CHROMEBOOK @@ -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 diff --git a/sys/arm/conf/CHROMEBOOK.hints b/sys/arm/conf/CHROMEBOOK.hints new file mode 100644 index 000000000000..908eb9eec491 --- /dev/null +++ b/sys/arm/conf/CHROMEBOOK.hints @@ -0,0 +1,5 @@ +# $FreeBSD$ + +# Chrome Embedded Controller +hint.chrome_ec.0.at="iicbus0" +hint.chrome_ec.0.addr=0x1e diff --git a/sys/arm/samsung/exynos/chrome_ec.c b/sys/arm/samsung/exynos/chrome_ec.c new file mode 100644 index 000000000000..87e5b4123bf1 --- /dev/null +++ b/sys/arm/samsung/exynos/chrome_ec.c @@ -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); diff --git a/sys/arm/samsung/exynos/chrome_ec.h b/sys/arm/samsung/exynos/chrome_ec.h new file mode 100644 index 000000000000..9d3f521e962f --- /dev/null +++ b/sys/arm/samsung/exynos/chrome_ec.h @@ -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); diff --git a/sys/arm/samsung/exynos/chrome_kb.c b/sys/arm/samsung/exynos/chrome_kb.c new file mode 100644 index 000000000000..9129131b5ebd --- /dev/null +++ b/sys/arm/samsung/exynos/chrome_kb.c @@ -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); diff --git a/sys/arm/samsung/exynos/chrome_kb.h b/sys/arm/samsung/exynos/chrome_kb.h new file mode 100644 index 000000000000..3903a9be830c --- /dev/null +++ b/sys/arm/samsung/exynos/chrome_kb.h @@ -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 */ +}; diff --git a/sys/arm/samsung/exynos/exynos5_combiner.c b/sys/arm/samsung/exynos/exynos5_combiner.c new file mode 100644 index 000000000000..e7bb8961d76c --- /dev/null +++ b/sys/arm/samsung/exynos/exynos5_combiner.c @@ -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); diff --git a/sys/arm/samsung/exynos/exynos5_combiner.h b/sys/arm/samsung/exynos/exynos5_combiner.h new file mode 100644 index 000000000000..7be8dd0ca563 --- /dev/null +++ b/sys/arm/samsung/exynos/exynos5_combiner.h @@ -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); diff --git a/sys/arm/samsung/exynos/exynos5_ehci.c b/sys/arm/samsung/exynos/exynos5_ehci.c index dc438d5c5bdc..7dacafa72a04 100644 --- a/sys/arm/samsung/exynos/exynos5_ehci.c +++ b/sys/arm/samsung/exynos/exynos5_ehci.c @@ -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; + + /* 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); + } - /* 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); + if (power) + GPIO_PIN_SET(gpio_dev, PIN_USB, GPIO_PIN_HIGH); + else + GPIO_PIN_SET(gpio_dev, PIN_USB, GPIO_PIN_LOW); - /* 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 (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," diff --git a/sys/arm/samsung/exynos/exynos5_i2c.c b/sys/arm/samsung/exynos/exynos5_i2c.c new file mode 100644 index 000000000000..bf7548fad83a --- /dev/null +++ b/sys/arm/samsung/exynos/exynos5_i2c.c @@ -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); diff --git a/sys/arm/samsung/exynos/exynos5_pad.c b/sys/arm/samsung/exynos/exynos5_pad.c new file mode 100644 index 000000000000..f9f38d019cd1 --- /dev/null +++ b/sys/arm/samsung/exynos/exynos5_pad.c @@ -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); diff --git a/sys/arm/samsung/exynos/exynos5_pad.h b/sys/arm/samsung/exynos/exynos5_pad.h new file mode 100644 index 000000000000..4b83a025e2b8 --- /dev/null +++ b/sys/arm/samsung/exynos/exynos5_pad.h @@ -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); diff --git a/sys/arm/samsung/exynos/files.exynos5 b/sys/arm/samsung/exynos/files.exynos5 index fc68cd3b2eb1..2bd3a16be89c 100644 --- a/sys/arm/samsung/exynos/files.exynos5 +++ b/sys/arm/samsung/exynos/files.exynos5 @@ -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 diff --git a/sys/boot/fdt/dts/arm/exynos5250-arndale.dts b/sys/boot/fdt/dts/arm/exynos5250-arndale.dts index 67cadcbdf64b..c693a3097525 100644 --- a/sys/boot/fdt/dts/arm/exynos5250-arndale.dts +++ b/sys/boot/fdt/dts/arm/exynos5250-arndale.dts @@ -38,6 +38,14 @@ reg = < 0x40000000 0x80000000 >; /* 2G */ }; + SOC: Exynos5@0 { + + pad0: pad@11400000 { + status = "okay"; + }; + + }; + chosen { stdin = &serial2; stdout = &serial2; diff --git a/sys/boot/fdt/dts/arm/exynos5250-chromebook.dts b/sys/boot/fdt/dts/arm/exynos5250-chromebook.dts index 0b9c97884e9b..051b9d471c98 100644 --- a/sys/boot/fdt/dts/arm/exynos5250-chromebook.dts +++ b/sys/boot/fdt/dts/arm/exynos5250-chromebook.dts @@ -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 { diff --git a/sys/boot/fdt/dts/arm/exynos5250.dtsi b/sys/boot/fdt/dts/arm/exynos5250.dtsi index f679a13a4319..0ac91485e01d 100644 --- a/sys/boot/fdt/dts/arm/exynos5250.dtsi +++ b/sys/boot/fdt/dts/arm/exynos5250.dtsi @@ -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"; |