root/sys/arm64/nvidia/tegra210/max77620_rtc.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright 2020 Michal Meloun <mmel@FreeBSD.org>
 *
 * 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.
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/clock.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/sx.h>

#include <dev/iicbus/iiconf.h>
#include <dev/iicbus/iicbus.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>

#include "clock_if.h"
#include "ofw_iicbus_if.h"
#include "max77620.h"

#define MAX77620_RTC_INT        0x00
#define MAX77620_RTC_INTM       0x01
#define MAX77620_RTC_CONTROLM   0x02
#define MAX77620_RTC_CONTROL    0x03
#define  RTC_CONTROL_MODE_24            (1 << 1)
#define  RTC_CONTROL_BCD_EN             (1 << 0)

#define MAX77620_RTC_UPDATE0    0x04
#define  RTC_UPDATE0_RTC_RBUDR          (1 << 4)
#define  RTC_UPDATE0_RTC_UDR            (1 << 0)

#define MAX77620_WTSR_SMPL_CNTL 0x06
#define MAX77620_RTC_SEC        0x07
#define MAX77620_RTC_MIN        0x08
#define MAX77620_RTC_HOUR       0x09
#define MAX77620_RTC_WEEKDAY    0x0A
#define MAX77620_RTC_MONTH      0x0B
#define MAX77620_RTC_YEAR       0x0C
#define MAX77620_RTC_DATE       0x0D
#define MAX77620_ALARM1_SEC     0x0E
#define MAX77620_ALARM1_MIN     0x0F
#define MAX77620_ALARM1_HOUR    0x10
#define MAX77620_ALARM1_WEEKDAY 0x11
#define MAX77620_ALARM1_MONTH   0x12
#define MAX77620_ALARM1_YEAR    0x13
#define MAX77620_ALARM1_DATE    0x14
#define MAX77620_ALARM2_SEC     0x15
#define MAX77620_ALARM2_MIN     0x16
#define MAX77620_ALARM2_HOUR    0x17
#define MAX77620_ALARM2_WEEKDAY 0x18
#define MAX77620_ALARM2_MONTH   0x19
#define MAX77620_ALARM2_YEAR    0x1A
#define MAX77620_ALARM2_DATE    0x1B

#define MAX77620_RTC_START_YEAR 2000
#define MAX77620_RTC_I2C_ADDR   0x68

#define LOCK(_sc)               sx_xlock(&(_sc)->lock)
#define UNLOCK(_sc)             sx_xunlock(&(_sc)->lock)
#define LOCK_INIT(_sc)          sx_init(&(_sc)->lock, "max77620_rtc")
#define LOCK_DESTROY(_sc)       sx_destroy(&(_sc)->lock);

struct max77620_rtc_softc {
        device_t                        dev;
        struct sx                       lock;
        int                             bus_addr;
};

char max77620_rtc_compat[] = "maxim,max77620_rtc";

/*
 * Raw register access function.
 */
static int
max77620_rtc_read(struct max77620_rtc_softc *sc, uint8_t reg, uint8_t *val)
{
        uint8_t addr;
        int rv;
        struct iic_msg msgs[2] = {
                {0, IIC_M_WR, 1, &addr},
                {0, IIC_M_RD, 1, val},
        };

        msgs[0].slave = sc->bus_addr;
        msgs[1].slave = sc->bus_addr;
        addr = reg;

        rv = iicbus_transfer(sc->dev, msgs, 2);
        if (rv != 0) {
                device_printf(sc->dev,
                    "Error when reading reg 0x%02X, rv: %d\n", reg,  rv);
                return (EIO);
        }

        return (0);
}

static int
max77620_rtc_read_buf(struct max77620_rtc_softc *sc, uint8_t reg,
    uint8_t *buf, size_t size)
{
        uint8_t addr;
        int rv;
        struct iic_msg msgs[2] = {
                {0, IIC_M_WR, 1, &addr},
                {0, IIC_M_RD, size, buf},
        };

        msgs[0].slave = sc->bus_addr;
        msgs[1].slave = sc->bus_addr;
        addr = reg;

        rv = iicbus_transfer(sc->dev, msgs, 2);
        if (rv != 0) {
                device_printf(sc->dev,
                    "Error when reading reg 0x%02X, rv: %d\n", reg,  rv);
                return (EIO);
        }

        return (0);
}

static int
max77620_rtc_write(struct max77620_rtc_softc *sc, uint8_t reg, uint8_t val)
{
        uint8_t data[2];
        int rv;

        struct iic_msg msgs[1] = {
                {0, IIC_M_WR, 2, data},
        };

        msgs[0].slave = sc->bus_addr;
        data[0] = reg;
        data[1] = val;

        rv = iicbus_transfer(sc->dev, msgs, 1);
        if (rv != 0) {
                device_printf(sc->dev,
                    "Error when writing reg 0x%02X, rv: %d\n", reg, rv);
                return (EIO);
        }
        return (0);
}

static int
max77620_rtc_write_buf(struct max77620_rtc_softc *sc, uint8_t reg, uint8_t *buf,
    size_t size)
{
        uint8_t data[1];
        int rv;
        struct iic_msg msgs[2] = {
                {0, IIC_M_WR, 1, data},
                {0, IIC_M_WR | IIC_M_NOSTART, size, buf},
        };

        msgs[0].slave = sc->bus_addr;
        msgs[1].slave = sc->bus_addr;
        data[0] = reg;

        rv = iicbus_transfer(sc->dev, msgs, 2);
        if (rv != 0) {
                device_printf(sc->dev,
                    "Error when writing reg 0x%02X, rv: %d\n", reg, rv);
                return (EIO);
        }
        return (0);
}

static int
max77620_rtc_modify(struct max77620_rtc_softc *sc, uint8_t reg, uint8_t clear,
    uint8_t set)
{
        uint8_t val;
        int rv;

        rv = max77620_rtc_read(sc, reg, &val);
        if (rv != 0)
                return (rv);

        val &= ~clear;
        val |= set;

        rv = max77620_rtc_write(sc, reg, val);
        if (rv != 0)
                return (rv);

        return (0);
}

static int
max77620_rtc_update(struct max77620_rtc_softc *sc, bool for_read)
{
        uint8_t reg;
        int rv;

        reg = for_read ? RTC_UPDATE0_RTC_RBUDR: RTC_UPDATE0_RTC_UDR;
        rv = max77620_rtc_modify(sc, MAX77620_RTC_UPDATE0, reg, reg);
        if (rv != 0)
                return (rv);

        DELAY(16000);
        return (rv);
}

static int
max77620_rtc_gettime(device_t dev, struct timespec *ts)
{
        struct max77620_rtc_softc *sc;
        struct clocktime ct;
        uint8_t buf[7];
        int rv;

        sc = device_get_softc(dev);

        LOCK(sc);
        rv = max77620_rtc_update(sc, true);
        if (rv != 0) {
                UNLOCK(sc);
                device_printf(sc->dev, "Failed to strobe RTC data\n");
                return (rv);
        }

        rv = max77620_rtc_read_buf(sc, MAX77620_RTC_SEC, buf, nitems(buf));
        UNLOCK(sc);
        if (rv != 0) {
                device_printf(sc->dev, "Failed to read RTC data\n");
                return (rv);
        }
        ct.nsec = 0;
        ct.sec  = bcd2bin(buf[0] & 0x7F);
        ct.min  = bcd2bin(buf[1] & 0x7F);
        ct.hour = bcd2bin(buf[2] & 0x3F);
        ct.dow  = ffs(buf[3] & 07);
        ct.mon  = bcd2bin(buf[4] & 0x1F);
        ct.year = bcd2bin(buf[5] & 0x7F) + MAX77620_RTC_START_YEAR;
        ct.day  = bcd2bin(buf[6] & 0x3F);

        return (clock_ct_to_ts(&ct, ts));
}

static int
max77620_rtc_settime(device_t dev, struct timespec *ts)
{
        struct max77620_rtc_softc *sc;
        struct clocktime ct;
        uint8_t buf[7];
        int rv;

        sc = device_get_softc(dev);
        clock_ts_to_ct(ts, &ct);

        if (ct.year < MAX77620_RTC_START_YEAR)
                return (EINVAL);

        buf[0] = bin2bcd(ct.sec);
        buf[1] = bin2bcd(ct.min);
        buf[2] = bin2bcd(ct.hour);
        buf[3] = 1 << ct.dow;
        buf[4] = bin2bcd(ct.mon);
        buf[5] = bin2bcd(ct.year - MAX77620_RTC_START_YEAR);
        buf[6] = bin2bcd(ct.day);

        LOCK(sc);
        rv = max77620_rtc_write_buf(sc, MAX77620_RTC_SEC, buf, nitems(buf));
        if (rv != 0) {
                UNLOCK(sc);
                device_printf(sc->dev, "Failed to write RTC data\n");
                return (rv);
        }
        rv = max77620_rtc_update(sc, false);
        UNLOCK(sc);
        if (rv != 0) {
                device_printf(sc->dev, "Failed to update RTC data\n");
                return (rv);
        }

        return (0);
}

static int
max77620_rtc_probe(device_t dev)
{
        const char *compat;

        /*
         * TODO:
         * ofw_bus_is_compatible() should use compat string from devinfo cache
         * maximum size of OFW property should be defined in public header
         */
        if ((compat = ofw_bus_get_compat(dev)) == NULL)
                return (ENXIO);
        if (strncasecmp(compat, max77620_rtc_compat, 255) != 0)
                return (ENXIO);

        device_set_desc(dev, "MAX77620 RTC");
        return (BUS_PROBE_DEFAULT);
}

static int
max77620_rtc_attach(device_t dev)
{
        struct max77620_rtc_softc *sc;
        uint8_t reg;
        int rv;

        sc = device_get_softc(dev);
        sc->dev = dev;
        sc->bus_addr = iicbus_get_addr(dev);

        LOCK_INIT(sc);

        reg = RTC_CONTROL_MODE_24 | RTC_CONTROL_BCD_EN;
        rv = max77620_rtc_modify(sc, MAX77620_RTC_CONTROLM, reg, reg);
        if (rv != 0) {
                device_printf(sc->dev, "Failed to configure RTC\n");
                goto fail;
        }

        rv = max77620_rtc_modify(sc, MAX77620_RTC_CONTROL, reg, reg);
        if (rv != 0) {
                device_printf(sc->dev, "Failed to configure RTC\n");
                goto fail;
        }
        rv = max77620_rtc_update(sc, false);
        if (rv != 0) {
                device_printf(sc->dev, "Failed to update RTC data\n");
                return (rv);
        }

        clock_register(sc->dev, 1000000);

        bus_attach_children(dev);
        return (0);

fail:
        LOCK_DESTROY(sc);
        return (rv);
}

static int
max77620_rtc_detach(device_t dev)
{
        struct max77620_softc *sc;
        int error;

        error = bus_generic_detach(dev);
        if (error != 0)
                return (error);

        sc = device_get_softc(dev);
        LOCK_DESTROY(sc);

        return (0);
}

/*
 * The secondary address of MAX77620 (RTC function) is not in DT,
 * add it manualy as subdevice
 */
int
max77620_rtc_create(struct max77620_softc *sc, phandle_t node)
{
        device_t parent, child;
        int rv;

        parent = device_get_parent(sc->dev);

        child = BUS_ADD_CHILD(parent, 0, NULL, DEVICE_UNIT_ANY);
        if (child == NULL)      {
                device_printf(sc->dev, "Cannot create MAX77620 RTC device.\n");
                return (ENXIO);
        }

        rv = OFW_IICBUS_SET_DEVINFO(parent, child, -1, "rtc@68",
             max77620_rtc_compat, MAX77620_RTC_I2C_ADDR << 1);
        if (rv != 0)    {
                device_printf(sc->dev, "Cannot setup MAX77620 RTC device.\n");
                return (ENXIO);
        }

        return (0);
}

static device_method_t max77620_rtc_methods[] = {
        /* Device interface */
        DEVMETHOD(device_probe,         max77620_rtc_probe),
        DEVMETHOD(device_attach,        max77620_rtc_attach),
        DEVMETHOD(device_detach,        max77620_rtc_detach),

        /* RTC interface */
        DEVMETHOD(clock_gettime,        max77620_rtc_gettime),
        DEVMETHOD(clock_settime,        max77620_rtc_settime),

        DEVMETHOD_END
};

static DEFINE_CLASS_0(rtc, max77620_rtc_driver, max77620_rtc_methods,
    sizeof(struct max77620_rtc_softc));
EARLY_DRIVER_MODULE(max77620rtc_, iicbus, max77620_rtc_driver, NULL, NULL, 74);