Date: Sun, 14 Feb 2016 23:51:13 +0000 (UTC) From: Andrew Turner <andrew@FreeBSD.org> To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r295622 - in head/sys: arm/mv conf dev/iicbus/twsi Message-ID: <201602142351.u1ENpDap044812@repo.freebsd.org>
next in thread | raw e-mail | index | archive | help
Author: andrew Date: Sun Feb 14 23:51:13 2016 New Revision: 295622 URL: https://svnweb.freebsd.org/changeset/base/295622 Log: Move the twsi driver source to be under iicbus. It is in a separate directory as it is expected multiple attachments will be added for the SoC families that use this hardware. Sponsored by: ABT Systems Ltd Added: head/sys/dev/iicbus/twsi/ head/sys/dev/iicbus/twsi/twsi.c - copied unchanged from r295621, head/sys/arm/mv/twsi.c Deleted: head/sys/arm/mv/twsi.c Modified: head/sys/arm/mv/files.mv head/sys/conf/files.arm Modified: head/sys/arm/mv/files.mv ============================================================================== --- head/sys/arm/mv/files.mv Sun Feb 14 23:42:07 2016 (r295621) +++ head/sys/arm/mv/files.mv Sun Feb 14 23:51:13 2016 (r295622) @@ -19,7 +19,6 @@ arm/mv/mv_machdep.c standard arm/mv/mv_pci.c optional pci arm/mv/mv_ts.c standard arm/mv/timer.c standard -arm/mv/twsi.c optional twsi dev/cesa/cesa.c optional cesa dev/mge/if_mge.c optional mge Modified: head/sys/conf/files.arm ============================================================================== --- head/sys/conf/files.arm Sun Feb 14 23:42:07 2016 (r295621) +++ head/sys/conf/files.arm Sun Feb 14 23:51:13 2016 (r295622) @@ -101,6 +101,7 @@ dev/fb/fb.c optional sc dev/fdt/fdt_arm_platform.c optional platform fdt dev/hwpmc/hwpmc_arm.c optional hwpmc dev/hwpmc/hwpmc_armv7.c optional hwpmc armv6 +dev/iicbus/twsi/twsi.c optional twsi dev/psci/psci.c optional psci dev/psci/psci_arm.S optional psci dev/syscons/scgfbrndr.c optional sc Copied: head/sys/dev/iicbus/twsi/twsi.c (from r295621, head/sys/arm/mv/twsi.c) ============================================================================== --- /dev/null 00:00:00 1970 (empty, because file is newly added) +++ head/sys/dev/iicbus/twsi/twsi.c Sun Feb 14 23:51:13 2016 (r295622, copy of r295621, head/sys/arm/mv/twsi.c) @@ -0,0 +1,644 @@ +/*- + * Copyright (C) 2008 MARVELL INTERNATIONAL LTD. + * All rights reserved. + * + * Developed by Semihalf. + * + * 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. + * 3. Neither the name of MARVELL nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY 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 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. + */ + +/* + * Driver for the TWSI (aka I2C, aka IIC) bus controller found on Marvell + * SoCs. Supports master operation only, and works in polling mode. + * + * Calls to DELAY() are needed per Application Note AN-179 "TWSI Software + * Guidelines for Discovery(TM), Horizon (TM) and Feroceon(TM) Devices". + */ + +#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/resource.h> + +#include <machine/_inttypes.h> +#include <machine/bus.h> +#include <machine/resource.h> + +#include <sys/rman.h> + +#include <sys/lock.h> +#include <sys/mutex.h> + +#include <dev/iicbus/iiconf.h> +#include <dev/iicbus/iicbus.h> +#include <dev/fdt/fdt_common.h> +#include <dev/ofw/ofw_bus.h> +#include <dev/ofw/ofw_bus_subr.h> + +#include <arm/mv/mvvar.h> + +#include "iicbus_if.h" + +#define MV_TWSI_NAME "twsi" +#define IICBUS_DEVNAME "iicbus" + +#define TWSI_SLAVE_ADDR 0x00 +#define TWSI_EXT_SLAVE_ADDR 0x10 +#define TWSI_DATA 0x04 + +#define TWSI_CONTROL 0x08 +#define TWSI_CONTROL_ACK (1 << 2) +#define TWSI_CONTROL_IFLG (1 << 3) +#define TWSI_CONTROL_STOP (1 << 4) +#define TWSI_CONTROL_START (1 << 5) +#define TWSI_CONTROL_TWSIEN (1 << 6) +#define TWSI_CONTROL_INTEN (1 << 7) + +#define TWSI_STATUS 0x0c +#define TWSI_STATUS_START 0x08 +#define TWSI_STATUS_RPTD_START 0x10 +#define TWSI_STATUS_ADDR_W_ACK 0x18 +#define TWSI_STATUS_DATA_WR_ACK 0x28 +#define TWSI_STATUS_ADDR_R_ACK 0x40 +#define TWSI_STATUS_DATA_RD_ACK 0x50 +#define TWSI_STATUS_DATA_RD_NOACK 0x58 + +#define TWSI_BAUD_RATE 0x0c +#define TWSI_BAUD_RATE_PARAM(M,N) ((((M) << 3) | ((N) & 0x7)) & 0x7f) +#define TWSI_BAUD_RATE_RAW(C,M,N) ((C)/((10*(M+1))<<(N+1))) +#define TWSI_BAUD_RATE_SLOW 50000 /* 50kHz */ +#define TWSI_BAUD_RATE_FAST 100000 /* 100kHz */ + +#define TWSI_SOFT_RESET 0x1c + +#define TWSI_DEBUG +#undef TWSI_DEBUG + +#ifdef TWSI_DEBUG +#define debugf(fmt, args...) do { printf("%s(): ", __func__); printf(fmt,##args); } while (0) +#else +#define debugf(fmt, args...) +#endif + +struct mv_twsi_softc { + device_t dev; + struct resource *res[1]; /* SYS_RES_MEMORY */ + struct mtx mutex; + device_t iicbus; +}; + +static struct mv_twsi_baud_rate { + uint32_t raw; + int param; + int m; + int n; +} baud_rate[IIC_FASTEST + 1]; + +static int mv_twsi_probe(device_t); +static int mv_twsi_attach(device_t); +static int mv_twsi_detach(device_t); + +static int mv_twsi_reset(device_t dev, u_char speed, u_char addr, + u_char *oldaddr); +static int mv_twsi_repeated_start(device_t dev, u_char slave, int timeout); +static int mv_twsi_start(device_t dev, u_char slave, int timeout); +static int mv_twsi_stop(device_t dev); +static int mv_twsi_read(device_t dev, char *buf, int len, int *read, int last, + int delay); +static int mv_twsi_write(device_t dev, const char *buf, int len, int *sent, + int timeout); + +static struct resource_spec res_spec[] = { + { SYS_RES_MEMORY, 0, RF_ACTIVE }, + { -1, 0 } +}; + +static struct ofw_compat_data compat_data[] = { + { "mrvl,twsi", true }, + { "marvell,mv64xxx-i2c", true }, + { NULL, false } +}; + +static device_method_t mv_twsi_methods[] = { + /* device interface */ + DEVMETHOD(device_probe, mv_twsi_probe), + DEVMETHOD(device_attach, mv_twsi_attach), + DEVMETHOD(device_detach, mv_twsi_detach), + + /* iicbus interface */ + DEVMETHOD(iicbus_callback, iicbus_null_callback), + DEVMETHOD(iicbus_repeated_start, mv_twsi_repeated_start), + DEVMETHOD(iicbus_start, mv_twsi_start), + DEVMETHOD(iicbus_stop, mv_twsi_stop), + DEVMETHOD(iicbus_write, mv_twsi_write), + DEVMETHOD(iicbus_read, mv_twsi_read), + DEVMETHOD(iicbus_reset, mv_twsi_reset), + DEVMETHOD(iicbus_transfer, iicbus_transfer_gen), + { 0, 0 } +}; + +static devclass_t mv_twsi_devclass; + +static driver_t mv_twsi_driver = { + MV_TWSI_NAME, + mv_twsi_methods, + sizeof(struct mv_twsi_softc), +}; + +DRIVER_MODULE(twsi, simplebus, mv_twsi_driver, mv_twsi_devclass, 0, 0); +DRIVER_MODULE(iicbus, twsi, iicbus_driver, iicbus_devclass, 0, 0); +MODULE_DEPEND(twsi, iicbus, 1, 1, 1); + +static __inline uint32_t +TWSI_READ(struct mv_twsi_softc *sc, bus_size_t off) +{ + + return (bus_read_4(sc->res[0], off)); +} + +static __inline void +TWSI_WRITE(struct mv_twsi_softc *sc, bus_size_t off, uint32_t val) +{ + + bus_write_4(sc->res[0], off, val); +} + +static __inline void +twsi_control_clear(struct mv_twsi_softc *sc, uint32_t mask) +{ + uint32_t val; + + val = TWSI_READ(sc, TWSI_CONTROL); + val &= ~mask; + TWSI_WRITE(sc, TWSI_CONTROL, val); +} + +static __inline void +twsi_control_set(struct mv_twsi_softc *sc, uint32_t mask) +{ + uint32_t val; + + val = TWSI_READ(sc, TWSI_CONTROL); + val |= mask; + TWSI_WRITE(sc, TWSI_CONTROL, val); +} + +static __inline void +twsi_clear_iflg(struct mv_twsi_softc *sc) +{ + + DELAY(1000); + twsi_control_clear(sc, TWSI_CONTROL_IFLG); + DELAY(1000); +} + + +/* + * timeout given in us + * returns + * 0 on sucessfull mask change + * non-zero on timeout + */ +static int +twsi_poll_ctrl(struct mv_twsi_softc *sc, int timeout, uint32_t mask) +{ + + timeout /= 10; + while (!(TWSI_READ(sc, TWSI_CONTROL) & mask)) { + DELAY(10); + if (--timeout < 0) + return (timeout); + } + return (0); +} + + +/* + * 'timeout' is given in us. Note also that timeout handling is not exact -- + * twsi_locked_start() total wait can be more than 2 x timeout + * (twsi_poll_ctrl() is called twice). 'mask' can be either TWSI_STATUS_START + * or TWSI_STATUS_RPTD_START + */ +static int +twsi_locked_start(device_t dev, struct mv_twsi_softc *sc, int32_t mask, + u_char slave, int timeout) +{ + int read_access, iflg_set = 0; + uint32_t status; + + mtx_assert(&sc->mutex, MA_OWNED); + + if (mask == TWSI_STATUS_RPTD_START) + /* read IFLG to know if it should be cleared later; from NBSD */ + iflg_set = TWSI_READ(sc, TWSI_CONTROL) & TWSI_CONTROL_IFLG; + + twsi_control_set(sc, TWSI_CONTROL_START); + + if (mask == TWSI_STATUS_RPTD_START && iflg_set) { + debugf("IFLG set, clearing\n"); + twsi_clear_iflg(sc); + } + + /* + * Without this delay we timeout checking IFLG if the timeout is 0. + * NBSD driver always waits here too. + */ + DELAY(1000); + + if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { + debugf("timeout sending %sSTART condition\n", + mask == TWSI_STATUS_START ? "" : "repeated "); + return (IIC_ETIMEOUT); + } + + status = TWSI_READ(sc, TWSI_STATUS); + if (status != mask) { + debugf("wrong status (%02x) after sending %sSTART condition\n", + status, mask == TWSI_STATUS_START ? "" : "repeated "); + return (IIC_ESTATUS); + } + + TWSI_WRITE(sc, TWSI_DATA, slave); + DELAY(1000); + twsi_clear_iflg(sc); + + if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { + debugf("timeout sending slave address\n"); + return (IIC_ETIMEOUT); + } + + read_access = (slave & 0x1) ? 1 : 0; + status = TWSI_READ(sc, TWSI_STATUS); + if (status != (read_access ? + TWSI_STATUS_ADDR_R_ACK : TWSI_STATUS_ADDR_W_ACK)) { + debugf("no ACK (status: %02x) after sending slave address\n", + status); + return (IIC_ENOACK); + } + + return (IIC_NOERR); +} + +static int +mv_twsi_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (!ofw_bus_search_compatible(dev, compat_data)->ocd_data) + return (ENXIO); + + device_set_desc(dev, "Marvell Integrated I2C Bus Controller"); + return (BUS_PROBE_DEFAULT); +} + +#define ABSSUB(a,b) (((a) > (b)) ? (a) - (b) : (b) - (a)) +static void +mv_twsi_cal_baud_rate(const uint32_t target, struct mv_twsi_baud_rate *rate) +{ + uint32_t clk, cur, diff, diff0; + int m, n, m0, n0; + + /* Calculate baud rate. */ + m0 = n0 = 4; /* Default values on reset */ + diff0 = 0xffffffff; + clk = get_tclk(); + + for (n = 0; n < 8; n++) { + for (m = 0; m < 16; m++) { + cur = TWSI_BAUD_RATE_RAW(clk,m,n); + diff = ABSSUB(target, cur); + if (diff < diff0) { + m0 = m; + n0 = n; + diff0 = diff; + } + } + } + rate->raw = TWSI_BAUD_RATE_RAW(clk, m0, n0); + rate->param = TWSI_BAUD_RATE_PARAM(m0, n0); + rate->m = m0; + rate->n = n0; +} + +static int +mv_twsi_attach(device_t dev) +{ + struct mv_twsi_softc *sc; + phandle_t child, iicbusnode; + device_t childdev; + struct iicbus_ivar *devi; + char dname[32]; /* 32 is taken from struct u_device */ + uint32_t paddr; + int len, error; + + sc = device_get_softc(dev); + sc->dev = dev; + bzero(baud_rate, sizeof(baud_rate)); + + mtx_init(&sc->mutex, device_get_nameunit(dev), MV_TWSI_NAME, MTX_DEF); + + /* Allocate IO resources */ + if (bus_alloc_resources(dev, res_spec, sc->res)) { + device_printf(dev, "could not allocate resources\n"); + mv_twsi_detach(dev); + return (ENXIO); + } + + mv_twsi_cal_baud_rate(TWSI_BAUD_RATE_SLOW, &baud_rate[IIC_SLOW]); + mv_twsi_cal_baud_rate(TWSI_BAUD_RATE_FAST, &baud_rate[IIC_FAST]); + if (bootverbose) + device_printf(dev, "calculated baud rates are:\n" + " %" PRIu32 " kHz (M=%d, N=%d) for slow,\n" + " %" PRIu32 " kHz (M=%d, N=%d) for fast.\n", + baud_rate[IIC_SLOW].raw / 1000, + baud_rate[IIC_SLOW].m, + baud_rate[IIC_SLOW].n, + baud_rate[IIC_FAST].raw / 1000, + baud_rate[IIC_FAST].m, + baud_rate[IIC_FAST].n); + + sc->iicbus = device_add_child(dev, IICBUS_DEVNAME, -1); + if (sc->iicbus == NULL) { + device_printf(dev, "could not add iicbus child\n"); + mv_twsi_detach(dev); + return (ENXIO); + } + /* Attach iicbus. */ + bus_generic_attach(dev); + + iicbusnode = 0; + /* Find iicbus as the child devices in the device tree. */ + for (child = OF_child(ofw_bus_get_node(dev)); child != 0; + child = OF_peer(child)) { + len = OF_getproplen(child, "model"); + if (len <= 0 || len > sizeof(dname)) + continue; + error = OF_getprop(child, "model", &dname, len); + if (error == -1) + continue; + len = strlen(dname); + if (len == strlen(IICBUS_DEVNAME) && + strncasecmp(dname, IICBUS_DEVNAME, len) == 0) { + iicbusnode = child; + break; + } + } + if (iicbusnode == 0) + goto attach_end; + + /* Attach child devices onto iicbus. */ + for (child = OF_child(iicbusnode); child != 0; child = OF_peer(child)) { + /* Get slave address. */ + error = OF_getprop(child, "i2c-address", &paddr, sizeof(paddr)); + if (error == -1) + error = OF_getprop(child, "reg", &paddr, sizeof(paddr)); + if (error == -1) + continue; + + /* Get device driver name. */ + len = OF_getproplen(child, "model"); + if (len <= 0 || len > sizeof(dname)) + continue; + OF_getprop(child, "model", &dname, len); + + if (bootverbose) + device_printf(dev, "adding a device %s at %d.\n", + dname, fdt32_to_cpu(paddr)); + childdev = BUS_ADD_CHILD(sc->iicbus, 0, dname, -1); + devi = IICBUS_IVAR(childdev); + devi->addr = fdt32_to_cpu(paddr); + } + +attach_end: + bus_generic_attach(sc->iicbus); + + return (0); +} + +static int +mv_twsi_detach(device_t dev) +{ + struct mv_twsi_softc *sc; + int rv; + + sc = device_get_softc(dev); + + if ((rv = bus_generic_detach(dev)) != 0) + return (rv); + + if (sc->iicbus != NULL) + if ((rv = device_delete_child(dev, sc->iicbus)) != 0) + return (rv); + + bus_release_resources(dev, res_spec, sc->res); + + mtx_destroy(&sc->mutex); + return (0); +} + +/* + * Only slave mode supported, disregard [old]addr + */ +static int +mv_twsi_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) +{ + struct mv_twsi_softc *sc; + uint32_t param; + + sc = device_get_softc(dev); + + switch (speed) { + case IIC_SLOW: + case IIC_FAST: + param = baud_rate[speed].param; + break; + case IIC_FASTEST: + case IIC_UNKNOWN: + default: + param = baud_rate[IIC_FAST].param; + break; + } + + mtx_lock(&sc->mutex); + TWSI_WRITE(sc, TWSI_SOFT_RESET, 0x0); + DELAY(2000); + TWSI_WRITE(sc, TWSI_BAUD_RATE, param); + TWSI_WRITE(sc, TWSI_CONTROL, TWSI_CONTROL_TWSIEN | TWSI_CONTROL_ACK); + DELAY(1000); + mtx_unlock(&sc->mutex); + + return (0); +} + +/* + * timeout is given in us + */ +static int +mv_twsi_repeated_start(device_t dev, u_char slave, int timeout) +{ + struct mv_twsi_softc *sc; + int rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + rv = twsi_locked_start(dev, sc, TWSI_STATUS_RPTD_START, slave, + timeout); + mtx_unlock(&sc->mutex); + + if (rv) { + mv_twsi_stop(dev); + return (rv); + } else + return (IIC_NOERR); +} + +/* + * timeout is given in us + */ +static int +mv_twsi_start(device_t dev, u_char slave, int timeout) +{ + struct mv_twsi_softc *sc; + int rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + rv = twsi_locked_start(dev, sc, TWSI_STATUS_START, slave, timeout); + mtx_unlock(&sc->mutex); + + if (rv) { + mv_twsi_stop(dev); + return (rv); + } else + return (IIC_NOERR); +} + +static int +mv_twsi_stop(device_t dev) +{ + struct mv_twsi_softc *sc; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + twsi_control_set(sc, TWSI_CONTROL_STOP); + DELAY(1000); + twsi_clear_iflg(sc); + mtx_unlock(&sc->mutex); + + return (IIC_NOERR); +} + +static int +mv_twsi_read(device_t dev, char *buf, int len, int *read, int last, int delay) +{ + struct mv_twsi_softc *sc; + uint32_t status; + int last_byte, rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + *read = 0; + while (*read < len) { + /* + * Check if we are reading last byte of the last buffer, + * do not send ACK then, per I2C specs + */ + last_byte = ((*read == len - 1) && last) ? 1 : 0; + if (last_byte) + twsi_control_clear(sc, TWSI_CONTROL_ACK); + else + twsi_control_set(sc, TWSI_CONTROL_ACK); + + DELAY (1000); + twsi_clear_iflg(sc); + + if (twsi_poll_ctrl(sc, delay, TWSI_CONTROL_IFLG)) { + debugf("timeout reading data\n"); + rv = IIC_ETIMEOUT; + goto out; + } + + status = TWSI_READ(sc, TWSI_STATUS); + if (status != (last_byte ? + TWSI_STATUS_DATA_RD_NOACK : TWSI_STATUS_DATA_RD_ACK)) { + debugf("wrong status (%02x) while reading\n", status); + rv = IIC_ESTATUS; + goto out; + } + + *buf++ = TWSI_READ(sc, TWSI_DATA); + (*read)++; + } + rv = IIC_NOERR; +out: + mtx_unlock(&sc->mutex); + return (rv); +} + +static int +mv_twsi_write(device_t dev, const char *buf, int len, int *sent, int timeout) +{ + struct mv_twsi_softc *sc; + uint32_t status; + int rv; + + sc = device_get_softc(dev); + + mtx_lock(&sc->mutex); + *sent = 0; + while (*sent < len) { + TWSI_WRITE(sc, TWSI_DATA, *buf++); + + twsi_clear_iflg(sc); + if (twsi_poll_ctrl(sc, timeout, TWSI_CONTROL_IFLG)) { + debugf("timeout writing data\n"); + rv = IIC_ETIMEOUT; + goto out; + } + + status = TWSI_READ(sc, TWSI_STATUS); + if (status != TWSI_STATUS_DATA_WR_ACK) { + debugf("wrong status (%02x) while writing\n", status); + rv = IIC_ESTATUS; + goto out; + } + (*sent)++; + } + rv = IIC_NOERR; +out: + mtx_unlock(&sc->mutex); + return (rv); +}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201602142351.u1ENpDap044812>