root/sys/arch/arm/amlogic/meson_i2c.c
/* $NetBSD: meson_i2c.c,v 1.5 2025/09/16 11:55:16 thorpej Exp $ */

/*-
 * Copyright (c) 2025 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Written by Vincent Defert for The NetBSD Foundation, Inc.
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(1, "$NetBSD: meson_i2c.c,v 1.5 2025/09/16 11:55:16 thorpej Exp $");

#include <sys/param.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/intr.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/kmem.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <dev/i2c/i2cvar.h>
#include <dev/fdt/fdtvar.h>

#define MESONI2C_CTRL_REG               0x00
#define MESONI2C_CTRL_START             __BIT(0)
#define MESONI2C_CTRL_ACK_IGNORE        __BIT(1)
#define MESONI2C_CTRL_STATUS            __BIT(2)
#define MESONI2C_CTRL_ERROR             __BIT(3)
#define MESONI2C_CTRL_READ_DATA_SHIFT   8
#define MESONI2C_CTRL_READ_DATA_MASK    __BITS(11, MESONI2C_CTRL_READ_DATA_SHIFT)
#define MESONI2C_CTRL_CLKDIV_SHIFT      12
#define MESONI2C_CTRL_CLKDIV_MASK       __BITS(21, MESONI2C_CTRL_CLKDIV_SHIFT)
#define MESONI2C_CTRL_CLKDIVEXT_SHIFT   28
#define MESONI2C_CTRL_CLKDIVEXT_MASK    __BITS(29, MESONI2C_CTRL_CLKDIVEXT_SHIFT)

#define MESONI2C_SLAVE_ADDR_REG         0x04
#define MESONI2C_SLAVE_ADDR_MASK        __BITS(7, 0)
#define MESONI2C_SLAVE_SDA_FILTER_MASK  __BITS(10, 8)
#define MESONI2C_SLAVE_SCL_FILTER_MASK  __BITS(13, 11)
#define MESONI2C_SLAVE_SCL_LOW_SHIFT    16
#define MESONI2C_SLAVE_SCL_LOW_MASK     __BITS(27, MESONI2C_SLAVE_SCL_LOW_SHIFT)
#define MESONI2C_SLAVE_SCL_LOW_EN       __BIT(28)

#define MESONI2C_TOKEN_LIST_REG0        0x08
#define MESONI2C_TOKEN_LIST_REG1        0x0c
#define MESONI2C_TOKEN_WDATA_REG0       0x10
#define MESONI2C_TOKEN_WDATA_REG1       0x14
#define MESONI2C_TOKEN_RDATA_REG0       0x18
#define MESONI2C_TOKEN_RDATA_REG1       0x1c

#define MESONI2C_TOKEN_BITS             4
#define MESONI2C_TOKEN_MASK             ((1 << MESONI2C_TOKEN_BITS) - 1)
#define MESONI2C_TOKEN_REG_HALF         (32 / MESONI2C_TOKEN_BITS)
#define MESONI2C_TOKEN_REG_FULL         (64 / MESONI2C_TOKEN_BITS)

#define MESONI2C_DATA_BITS              8
#define MESONI2C_DATA_MASK              ((1 << MESONI2C_DATA_BITS) - 1)
#define MESONI2C_DATA_REG_HALF          (32 / MESONI2C_DATA_BITS)
#define MESONI2C_DATA_REG_FULL          (64 / MESONI2C_DATA_BITS)

#define I2C_TIMEOUT_MS                  1000
#define FILTER_DELAY                    15

enum mesoni2c_token {
        TOKEN_END = 0,
        TOKEN_START,
        TOKEN_SLAVE_ADDR_WRITE,
        TOKEN_SLAVE_ADDR_READ,
        TOKEN_DATA,
        TOKEN_DATA_LAST,
        TOKEN_STOP,
};

enum mesoni2c_type {
        TYPE_MESON6,
        TYPE_GXBB,
        TYPE_AXG,
};

struct mesoni2c_softc {
        device_t                sc_dev;
        bus_space_tag_t         sc_bst;
        bus_space_handle_t      sc_bsh;
        struct clk              *sc_clk;
        u_int                   sc_clkfreq;
        struct i2c_controller   sc_ic;
        kcondvar_t              sc_cv;
        kmutex_t                sc_mtx;
        void                    *sc_ih;

        u_int                   sc_token_index;
        u_int                   sc_wdata_index;
        u_int                   sc_rdata_index;

        const uint8_t           *sc_cmdbuf;
        size_t                  sc_cmdlen;
        uint8_t                 *sc_databuf;
        size_t                  sc_datalen;
        i2c_op_t                sc_op;

        size_t                  sc_curlen;
        i2c_op_t                sc_curop;
        int                     sc_sendingCmd;
        int                     sc_error;
};

static void mesoni2c_set_mask(struct mesoni2c_softc *sc, bus_size_t reg, uint32_t mask, uint32_t value);
static void mesoni2c_reset_state(struct mesoni2c_softc *sc);
static int mesoni2c_push_token(struct mesoni2c_softc *sc, enum mesoni2c_token token);
static void mesoni2c_write_byte(struct mesoni2c_softc *sc, uint8_t data);
static uint8_t mesoni2c_get_byte(struct mesoni2c_softc *sc);
static void mesoni2c_prepare_xfer(struct mesoni2c_softc *sc, i2c_op_t op);
static void mesoni2c_partial_xfer(struct mesoni2c_softc *sc);
static int mesoni2c_exec(void *priv, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf, size_t cmdlen, void *buf, size_t buflen, int flags);
static int mesoni2c_intr(void *arg);
static void mesoni2c_set_clock_div_meson6(struct mesoni2c_softc *sc);
static void mesoni2c_set_clock_div_gxbb_axg(struct mesoni2c_softc *sc);
static int mesoni2c_match(device_t parent, cfdata_t cf, void *aux);
static void mesoni2c_attach(device_t parent, device_t self, void *aux);

CFATTACH_DECL_NEW(meson_i2c, sizeof(struct mesoni2c_softc),
    mesoni2c_match, mesoni2c_attach, NULL, NULL);

static const struct device_compatible_entry compat_data[] = {
        { .compat = "amlogic,meson6-i2c",       .value = TYPE_MESON6 },
        { .compat = "amlogic,meson-gxbb-i2c",   .value = TYPE_GXBB },
        { .compat = "amlogic,meson-axg-i2c",    .value = TYPE_AXG },
        DEVICE_COMPAT_EOL
};

#define RD4(sc, reg)            \
        bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))

#define WR4(sc, reg, val)       \
        bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))

static void
mesoni2c_set_mask(struct mesoni2c_softc *sc, bus_size_t reg, uint32_t mask,
        uint32_t value)
{
        uint32_t data = RD4(sc, reg);
        data &= ~mask;
        data |= value & mask;
        WR4(sc, reg, data);
}

static void
mesoni2c_reset_state(struct mesoni2c_softc *sc)
{
        sc->sc_token_index = 0;
        sc->sc_wdata_index = 0;
        sc->sc_rdata_index = 0;
}

static int
mesoni2c_push_token(struct mesoni2c_softc *sc, enum mesoni2c_token token)
{
        bus_size_t reg;
        u_int pos;

        if (sc->sc_token_index >= MESONI2C_TOKEN_REG_HALF) {
                reg = MESONI2C_TOKEN_LIST_REG1;
                pos = sc->sc_token_index - MESONI2C_TOKEN_REG_HALF;
        } else {
                reg = MESONI2C_TOKEN_LIST_REG0;
                pos = sc->sc_token_index;
        }

        sc->sc_token_index++;
        pos *= MESONI2C_TOKEN_BITS;
        mesoni2c_set_mask(sc, reg, MESONI2C_TOKEN_MASK << pos, token << pos);

        return sc->sc_token_index == MESONI2C_TOKEN_REG_FULL;
}

static void
mesoni2c_write_byte(struct mesoni2c_softc *sc, uint8_t data)
{
        bus_size_t reg;
        u_int pos;

        if (sc->sc_wdata_index >= MESONI2C_DATA_REG_HALF) {
                reg = MESONI2C_TOKEN_WDATA_REG1;
                pos = sc->sc_wdata_index - MESONI2C_DATA_REG_HALF;
        } else {
                reg = MESONI2C_TOKEN_WDATA_REG0;
                pos = sc->sc_wdata_index;
        }

        sc->sc_wdata_index++;
        pos *= MESONI2C_DATA_BITS;
        mesoni2c_set_mask(sc, reg, MESONI2C_DATA_MASK << pos, ((uint32_t) data) << pos);
        mesoni2c_push_token(sc, TOKEN_DATA);
}

static uint8_t
mesoni2c_get_byte(struct mesoni2c_softc *sc)
{
        bus_size_t reg;
        u_int pos;

        if (sc->sc_rdata_index >= MESONI2C_DATA_REG_HALF) {
                reg = MESONI2C_TOKEN_RDATA_REG1;
                pos = sc->sc_rdata_index - MESONI2C_DATA_REG_HALF;
        } else {
                reg = MESONI2C_TOKEN_RDATA_REG0;
                pos = sc->sc_rdata_index;
        }

        sc->sc_rdata_index++;
        pos *= MESONI2C_DATA_BITS;

        return (RD4(sc, reg) >> pos) & MESONI2C_DATA_MASK;
}

static void
mesoni2c_prepare_xfer(struct mesoni2c_softc *sc, i2c_op_t op)
{
        mesoni2c_reset_state(sc);
        mesoni2c_push_token(sc, TOKEN_START);
        mesoni2c_push_token(sc, I2C_OP_WRITE_P(op) ? TOKEN_SLAVE_ADDR_WRITE : TOKEN_SLAVE_ADDR_READ);
}

static void
mesoni2c_partial_xfer(struct mesoni2c_softc *sc)
{
        int dataBufferFree = MESONI2C_DATA_REG_FULL;

        while (sc->sc_curlen && dataBufferFree) {
                sc->sc_curlen--;
                dataBufferFree--;

                if (I2C_OP_WRITE_P(sc->sc_curop)) {
                        if (sc->sc_sendingCmd) {
                                uint8_t c = *(sc->sc_cmdbuf++);
                                mesoni2c_write_byte(sc, c);
                        } else {
                                mesoni2c_write_byte(sc, *(sc->sc_databuf++));
                        }
                } else {
                        mesoni2c_push_token(sc, sc->sc_curlen ? TOKEN_DATA : TOKEN_DATA_LAST);
                }
        }

        if (sc->sc_curlen == 0 && I2C_OP_STOP_P(sc->sc_curop)) {
                mesoni2c_push_token(sc, TOKEN_STOP);
        }

        mesoni2c_push_token(sc, TOKEN_END);

        mesoni2c_set_mask(sc, MESONI2C_CTRL_REG, MESONI2C_CTRL_START, 0);
        mesoni2c_set_mask(sc, MESONI2C_CTRL_REG, MESONI2C_CTRL_START, 1);
}

static int
mesoni2c_exec(void *priv, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf,
        size_t cmdlen, void *databuf, size_t datalen, int flags)
{
        struct mesoni2c_softc * const sc = priv;

        mutex_enter(&sc->sc_mtx);

        sc->sc_cmdbuf = cmdbuf;
        sc->sc_cmdlen = cmdlen;
        sc->sc_databuf = databuf;
        sc->sc_datalen = datalen;
        sc->sc_op = op;
        sc->sc_error = 0;

        mesoni2c_set_mask(sc, MESONI2C_SLAVE_ADDR_REG, MESONI2C_SLAVE_ADDR_MASK, addr << 1);

        if (cmdlen) {
                sc->sc_curlen = cmdlen;
                sc->sc_curop = datalen ? I2C_OP_WRITE : op;
                sc->sc_sendingCmd = 1;
        } else {
                sc->sc_curlen = datalen;
                sc->sc_curop = op;
                sc->sc_sendingCmd = 0;
        }

        mesoni2c_prepare_xfer(sc, sc->sc_curop);
        mesoni2c_partial_xfer(sc);

        if (cv_timedwait(&sc->sc_cv, &sc->sc_mtx, mstohz(I2C_TIMEOUT_MS)) == EWOULDBLOCK) {
                sc->sc_error = EIO;
        }

        mutex_exit(&sc->sc_mtx);

        return sc->sc_error;
}

static int
mesoni2c_intr(void *arg)
{
        struct mesoni2c_softc *sc = arg;
        int done = 0;

        mutex_enter(&sc->sc_mtx);

        if (RD4(sc, MESONI2C_CTRL_REG) & MESONI2C_CTRL_ERROR) {
                /*
                 * The ERROR bit is set when the ACK_IGNORE bit is cleared
                 * in MESONI2C_CTRL_REG and the device didn't respond with
                 * an ACK. In this case, the I2C controller automatically
                 * generates a STOP condition.
                 */
                sc->sc_error = EIO;
                done = 1;
        } else {
                if (I2C_OP_READ_P(sc->sc_curop)) {
                        /* Read data bytes */
                        u_int count = (RD4(sc, MESONI2C_CTRL_REG) & MESONI2C_CTRL_READ_DATA_MASK)
                                >> MESONI2C_CTRL_READ_DATA_SHIFT;

                        while (count--) {
                                *(sc->sc_databuf++) = mesoni2c_get_byte(sc);
                        }
                }

                if (sc->sc_curlen) {
                        /* Continue transfer */
                        mesoni2c_reset_state(sc);
                        mesoni2c_partial_xfer(sc);
                } else {
                        if (sc->sc_sendingCmd && sc->sc_datalen) {
                                /*
                                 * We've just finished transfering the command
                                 * bytes, we must now transfer the data.
                                 */
                                sc->sc_curlen = sc->sc_datalen;
                                sc->sc_curop = sc->sc_op;
                                sc->sc_sendingCmd = 0;

                                if (I2C_OP_READ_P(sc->sc_curop)) {
                                        mesoni2c_prepare_xfer(sc, sc->sc_curop);
                                } else {
                                        mesoni2c_reset_state(sc);
                                }

                                mesoni2c_partial_xfer(sc);
                        } else {
                                done = 1;
                        }
                }
        }

        if (done) {
                /* Tell mesoni2c_exec() we're done. */
                cv_broadcast(&sc->sc_cv);
        }

        mutex_exit(&sc->sc_mtx);

        return 1;
}

static void
mesoni2c_set_clock_div_meson6(struct mesoni2c_softc *sc)
{
        u_int rate = clk_get_rate(sc->sc_clk);
        u_int div = howmany(rate, sc->sc_clkfreq) - FILTER_DELAY;
        div = howmany(div, 4);

        /* Set prescaler */
        mesoni2c_set_mask(sc, MESONI2C_CTRL_REG, MESONI2C_CTRL_CLKDIV_MASK, (div & __BITS(9, 0)) << MESONI2C_CTRL_CLKDIV_SHIFT);
        mesoni2c_set_mask(sc, MESONI2C_CTRL_REG, MESONI2C_CTRL_CLKDIVEXT_MASK, (div >> 10) << MESONI2C_CTRL_CLKDIVEXT_SHIFT);

        /* Disable HIGH/LOW mode */
        mesoni2c_set_mask(sc, MESONI2C_SLAVE_ADDR_REG, MESONI2C_SLAVE_SCL_LOW_EN, 0);
}

static void
mesoni2c_set_clock_div_gxbb_axg(struct mesoni2c_softc *sc)
{
        u_int rate = clk_get_rate(sc->sc_clk);
        u_int divh, divl;

        /*
         * According to I2C-BUS Spec 2.1, in FAST-MODE, the minimum
         * LOW period is 1.3uS, and minimum HIGH is least 0.6us.
         * For
         * 400000 freq, the period is 2.5us. To keep within the specs,
         * give 40% of period to HIGH and 60% to LOW. This means HIGH
         * at 1.0us and LOW 1.5us.
         * The same applies for Fast-mode plus, where LOW is 0.5us and
         * HIGH is 0.26us.
         * Duty = H/(H + L) = 2/5
         */
        if (sc->sc_clkfreq <= 100000) {
                divh = howmany(rate, sc->sc_clkfreq);
                divl = howmany(divh, 4);
                divh = howmany(divh, 2) - FILTER_DELAY;
        } else {
                divh = howmany(rate * 2, sc->sc_clkfreq * 5) - FILTER_DELAY;
                divl = howmany(rate * 3, sc->sc_clkfreq * 5 * 2);
        }

        /* Set prescaler */
        mesoni2c_set_mask(sc, MESONI2C_CTRL_REG, MESONI2C_CTRL_CLKDIV_MASK, (divh & __BITS(9, 0)) << MESONI2C_CTRL_CLKDIV_SHIFT);
        mesoni2c_set_mask(sc, MESONI2C_CTRL_REG, MESONI2C_CTRL_CLKDIVEXT_MASK, (divh >> 10) << MESONI2C_CTRL_CLKDIVEXT_SHIFT);

        /* Set SCL low delay */
        mesoni2c_set_mask(sc, MESONI2C_SLAVE_ADDR_REG, MESONI2C_SLAVE_SCL_LOW_MASK, divl << MESONI2C_SLAVE_SCL_LOW_SHIFT);

        /* Enable HIGH/LOW mode */
        mesoni2c_set_mask(sc, MESONI2C_SLAVE_ADDR_REG, MESONI2C_SLAVE_SCL_LOW_EN, MESONI2C_SLAVE_SCL_LOW_EN);
}

static int
mesoni2c_match(device_t parent, cfdata_t cf, void *aux)
{
        struct fdt_attach_args * const faa = aux;

        return of_compatible_match(faa->faa_phandle, compat_data);
}

static void
mesoni2c_attach(device_t parent, device_t self, void *aux)
{
        struct mesoni2c_softc * const sc = device_private(self);
        struct fdt_attach_args * const faa = aux;
        const int phandle = faa->faa_phandle;
        bus_addr_t addr;
        bus_size_t size;
        char intrstr[128];

        if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
                aprint_error_dev(self, "couldn't get registers\n");
                return;
        }

        sc->sc_clk = fdtbus_clock_get_index(phandle, 0);

        if (sc->sc_clk == NULL || clk_enable(sc->sc_clk) != 0) {
                aprint_error_dev(self, "couldn't enable clock\n");
                return;
        }

        sc->sc_dev = self;
        sc->sc_bst = faa->faa_bst;

        if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
                aprint_error_dev(self, "couldn't map registers\n");
                return;
        }

        fdtbus_clock_assign(phandle);

        if (of_getprop_uint32(phandle, "clock-frequency", &sc->sc_clkfreq)) {
                sc->sc_clkfreq = 100000;
        } else {
                if (sc->sc_clkfreq < 100000) {
                        sc->sc_clkfreq = 100000;
                } else if (sc->sc_clkfreq > 400000) {
                        sc->sc_clkfreq = 400000;
                }
        }

        aprint_naive("\n");
        aprint_normal(": Meson I2C (%u Hz)\n", sc->sc_clkfreq);

        enum mesoni2c_type type = of_compatible_lookup(phandle, compat_data)->value;

        switch (type) {
        case TYPE_MESON6:
                mesoni2c_set_clock_div_meson6(sc);
                break;

        case TYPE_GXBB:
        case TYPE_AXG:
                mesoni2c_set_clock_div_gxbb_axg(sc);
                break;
        }

        mutex_init(&sc->sc_mtx, MUTEX_DEFAULT, IPL_VM);
        cv_init(&sc->sc_cv, "mesoniic");

        if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) {
                aprint_error_dev(self, "failed to decode interrupt\n");
                return;
        }

        sc->sc_ih = fdtbus_intr_establish_xname(phandle, 0, IPL_VM, 0,
                mesoni2c_intr, sc, device_xname(self));

        if (sc->sc_ih == NULL) {
                aprint_error_dev(self, "couldn't establish interrupt\n");
                return;
        }

        aprint_normal_dev(self, "interrupting on %s\n", intrstr);

        iic_tag_init(&sc->sc_ic);
        sc->sc_ic.ic_cookie = sc;
        sc->sc_ic.ic_exec = mesoni2c_exec;

        iicbus_attach(self, &sc->sc_ic);
}