/*-
 * Copyright 2013-2015 John Wehle <john@feith.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.
 */

/*
 * Amlogic aml8726 RTC driver.
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#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/lock.h>
#include <sys/mutex.h>
#include <sys/resource.h>
#include <sys/rman.h>

#include <sys/time.h>

#include <machine/bus.h>
#include <machine/cpu.h>

#include <dev/fdt/fdt_common.h>
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>

#include <arm/amlogic/aml8726/aml8726_soc.h>

#include "clock_if.h"

/*
 * The RTC initialization various slightly between the different chips.
 *
 *                 aml8726-m1     aml8726-m3     aml8726-m6 (and later)
 *  init-always    true           true           false
 *  xo-init        0x0004         0x3c0a         0x180a
 *  gpo-init       0x100000       0x100000       0x500000
 */

struct aml8726_rtc_init {
	boolean_t	always;
	uint16_t	xo;
	uint32_t	gpo;
};

struct aml8726_rtc_softc {
	device_t		dev;
	struct aml8726_rtc_init	init;
	struct resource	*	res[2];
	struct mtx		mtx;
};

static struct resource_spec aml8726_rtc_spec[] = {
	{ SYS_RES_MEMORY,	0,	RF_ACTIVE },
	{ SYS_RES_IRQ,		0,	RF_ACTIVE },
	{ -1, 0 }
};

#define	AML_RTC_LOCK(sc)		mtx_lock_spin(&(sc)->mtx)
#define	AML_RTC_UNLOCK(sc)		mtx_unlock_spin(&(sc)->mtx)
#define	AML_RTC_LOCK_INIT(sc)		\
    mtx_init(&(sc)->mtx, device_get_nameunit((sc)->dev),	\
    "rtc", MTX_SPIN)
#define	AML_RTC_LOCK_DESTROY(sc)	mtx_destroy(&(sc)->mtx);

#define	AML_RTC_0_REG			0
#define	AML_RTC_SCLK			(1 << 0)
#define	AML_RTC_SDI			(1 << 2)
#define	AML_RTC_SEN			(1 << 1)
#define	AML_RTC_AS			(1 << 17)
#define	AML_RTC_ABSY			(1 << 22)
#define	AML_RTC_IRQ_DIS			(1 << 12)
#define	AML_RTC_1_REG			4
#define	AML_RTC_SDO			(1 << 0)
#define	AML_RTC_SRDY			(1 << 1)
#define	AML_RTC_2_REG			8
#define	AML_RTC_3_REG			12
#define	AML_RTC_MSR_BUSY		(1 << 20)
#define	AML_RTC_MSR_CA			(1 << 17)
#define	AML_RTC_MSR_DURATION_EN		(1 << 16)
#define	AML_RTC_MSR_DURATION_MASK	0xffff
#define	AML_RTC_MSR_DURATION_SHIFT	0
#define	AML_RTC_4_REG			16

#define	AML_RTC_TIME_SREG		0
#define	AML_RTC_GPO_SREG		1
#define	AML_RTC_GPO_LEVEL		(1 << 24)
#define	AML_RTC_GPO_BUSY		(1 << 23)
#define	AML_RTC_GPO_ACTIVE_HIGH		(1 << 22)
#define	AML_RTC_GPO_CMD_MASK		(3 << 20)
#define	AML_RTC_GPO_CMD_SHIFT		20
#define	AML_RTC_GPO_CMD_NOW		(1 << 20)
#define	AML_RTC_GPO_CMD_COUNT		(2 << 20)
#define	AML_RTC_GPO_CMD_PULSE		(3 << 20)
#define	AML_RTC_GPO_CNT_MASK		0xfffff
#define	AML_RTC_GPO_CNT_SHIFT		0

#define	CSR_WRITE_4(sc, reg, val)	bus_write_4((sc)->res[0], reg, (val))
#define	CSR_READ_4(sc, reg)		bus_read_4((sc)->res[0], reg)
#define	CSR_BARRIER(sc, reg)		bus_barrier((sc)->res[0], reg, 4, \
    (BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE))

static int
aml8726_rtc_start_transfer(struct aml8726_rtc_softc *sc)
{
	unsigned i;

	/* idle the serial interface */
	CSR_WRITE_4(sc, AML_RTC_0_REG, (CSR_READ_4(sc, AML_RTC_0_REG) &
	    ~(AML_RTC_SCLK | AML_RTC_SEN | AML_RTC_SDI)));

	CSR_BARRIER(sc, AML_RTC_0_REG);

	/* see if it is ready for a new cycle */
	for (i = 40; i; i--) {
		DELAY(5);
		if ( (CSR_READ_4(sc, AML_RTC_1_REG) & AML_RTC_SRDY) )
			break;
	}

	if (i == 0)
		return (EIO);

	/* start the cycle */
	CSR_WRITE_4(sc, AML_RTC_0_REG, (CSR_READ_4(sc, AML_RTC_0_REG) |
	    AML_RTC_SEN));

	return (0);
}

static inline void
aml8726_rtc_sclk_pulse(struct aml8726_rtc_softc *sc)
{

	DELAY(5);

	CSR_WRITE_4(sc, AML_RTC_0_REG, (CSR_READ_4(sc, AML_RTC_0_REG) |
	    AML_RTC_SCLK));

	CSR_BARRIER(sc, AML_RTC_0_REG);

	DELAY(5);

	CSR_WRITE_4(sc, AML_RTC_0_REG, (CSR_READ_4(sc, AML_RTC_0_REG) &
	    ~AML_RTC_SCLK));

	CSR_BARRIER(sc, AML_RTC_0_REG);
}

static inline void
aml8726_rtc_send_bit(struct aml8726_rtc_softc *sc, unsigned bit)
{

	if (bit) {
		CSR_WRITE_4(sc, AML_RTC_0_REG, (CSR_READ_4(sc, AML_RTC_0_REG) |
		    AML_RTC_SDI));
	} else {
		CSR_WRITE_4(sc, AML_RTC_0_REG, (CSR_READ_4(sc, AML_RTC_0_REG) &
		    ~AML_RTC_SDI));
	}

	aml8726_rtc_sclk_pulse(sc);
}

static inline void
aml8726_rtc_send_addr(struct aml8726_rtc_softc *sc, u_char addr)
{
	unsigned mask;

	for (mask = 1 << 3; mask; mask >>= 1) {
		if (mask == 1) {
			/* final bit indicates read / write mode */
			CSR_WRITE_4(sc, AML_RTC_0_REG,
			    (CSR_READ_4(sc, AML_RTC_0_REG) & ~AML_RTC_SEN));
		}
		aml8726_rtc_send_bit(sc, (addr & mask));
	}
}

static inline void
aml8726_rtc_send_data(struct aml8726_rtc_softc *sc, uint32_t data)
{
	unsigned mask;

	for (mask = 1U << 31; mask; mask >>= 1)
		aml8726_rtc_send_bit(sc, (data & mask));
}

static inline void
aml8726_rtc_recv_data(struct aml8726_rtc_softc *sc, uint32_t *dp)
{
	uint32_t data;
	unsigned i;

	data = 0;

	for (i = 0; i < 32; i++) {
		aml8726_rtc_sclk_pulse(sc);
		data <<= 1;
		data |= (CSR_READ_4(sc, AML_RTC_1_REG) & AML_RTC_SDO) ? 1 : 0;
	}

	*dp = data;
}

static int
aml8726_rtc_sreg_read(struct aml8726_rtc_softc *sc, u_char sreg, uint32_t *val)
{
	u_char addr;
	int error;

	/* read is indicated by lsb = 0 */
	addr = (sreg << 1) | 0;

	error = aml8726_rtc_start_transfer(sc);

	if (error)
		return (error);

	aml8726_rtc_send_addr(sc, addr);
	aml8726_rtc_recv_data(sc, val);

	return (0);
}

static int
aml8726_rtc_sreg_write(struct aml8726_rtc_softc *sc, u_char sreg, uint32_t val)
{
	u_char addr;
	int error;

	/* write is indicated by lsb = 1 */
	addr = (sreg << 1) | 1;

	error = aml8726_rtc_start_transfer(sc);

	if (error)
		return (error);

	aml8726_rtc_send_data(sc, val);
	aml8726_rtc_send_addr(sc, addr);

	return (0);
}

static int
aml8726_rtc_initialize(struct aml8726_rtc_softc *sc)
{
	int error;
	unsigned i;

	/* idle the serial interface */
	CSR_WRITE_4(sc, AML_RTC_0_REG, (CSR_READ_4(sc, AML_RTC_0_REG) &
	    ~(AML_RTC_SCLK | AML_RTC_SEN | AML_RTC_SDI)));

	CSR_BARRIER(sc, AML_RTC_0_REG);

	/* see if it is ready for a new cycle */
	for (i = 40; i; i--) {
		DELAY(5);
		if ( (CSR_READ_4(sc, AML_RTC_1_REG) & AML_RTC_SRDY) )
			break;
	}

	if (sc->init.always == TRUE || (CSR_READ_4(sc, AML_RTC_1_REG) &
	    AML_RTC_SRDY) == 0) {

		/*
		 * The RTC has a 16 bit initialization register.  The upper
		 * bits can be written directly.  The lower bits are written
		 * through a shift register.
		 */

		CSR_WRITE_4(sc, AML_RTC_4_REG, ((sc->init.xo >> 8) & 0xff));

		CSR_WRITE_4(sc, AML_RTC_0_REG,
		    ((CSR_READ_4(sc, AML_RTC_0_REG) & 0xffffff) |
		    ((uint32_t)(sc->init.xo & 0xff) << 24) | AML_RTC_AS |
		    AML_RTC_IRQ_DIS));

		while ((CSR_READ_4(sc, AML_RTC_0_REG) & AML_RTC_ABSY) != 0)
			cpu_spinwait();

		DELAY(2);

		error = aml8726_rtc_sreg_write(sc, AML_RTC_GPO_SREG,
		    sc->init.gpo);

		if (error)
			return (error);
	}

	return (0);
}

static int
aml8726_rtc_check_xo(struct aml8726_rtc_softc *sc)
{
	uint32_t now, previous;
	int i;

	/*
	 * The RTC is driven by a 32.768khz clock meaning it's period
	 * is roughly 30.5 us.  Check that it's working (implying the
	 * RTC could contain a valid value) by enabling count always
	 * and seeing if the value changes after 200 us (per RTC User
	 * Guide ... presumably the extra time is to cover XO startup).
	 */

	CSR_WRITE_4(sc, AML_RTC_3_REG, (CSR_READ_4(sc, AML_RTC_3_REG) |
	    AML_RTC_MSR_CA));

	previous = CSR_READ_4(sc, AML_RTC_2_REG);

	for (i = 0; i < 4; i++) {
		DELAY(50);
		now = CSR_READ_4(sc, AML_RTC_2_REG);
		if (now != previous)
			break;
	}

	CSR_WRITE_4(sc, AML_RTC_3_REG, (CSR_READ_4(sc, AML_RTC_3_REG) &
	    ~AML_RTC_MSR_CA));

	if (now == previous)
		return (EINVAL);

	return (0);
}

static int
aml8726_rtc_probe(device_t dev)
{

	if (!ofw_bus_status_okay(dev))
		return (ENXIO);

	if (!ofw_bus_is_compatible(dev, "amlogic,aml8726-rtc"))
		return (ENXIO);

	device_set_desc(dev, "Amlogic aml8726 RTC");

	return (BUS_PROBE_DEFAULT);
}

static int
aml8726_rtc_attach(device_t dev)
{
	struct aml8726_rtc_softc *sc = device_get_softc(dev);

	sc->dev = dev;

	switch (aml8726_soc_hw_rev) {
	case AML_SOC_HW_REV_M3:
		sc->init.always = true;
		sc->init.xo = 0x3c0a;
		sc->init.gpo = 0x100000;
		break;
	case AML_SOC_HW_REV_M6:
	case AML_SOC_HW_REV_M8:
	case AML_SOC_HW_REV_M8B:
		sc->init.always = false;
		sc->init.xo = 0x180a;
		sc->init.gpo = 0x500000;
		break;
	default:
		device_printf(dev, "unsupported SoC\n");
		return (ENXIO);
		/* NOTREACHED */
	}

	if (bus_alloc_resources(dev, aml8726_rtc_spec, sc->res)) {
		device_printf(dev, "can not allocate resources for device\n");
		return (ENXIO);
	}

	aml8726_rtc_initialize(sc);

	if (aml8726_rtc_check_xo(sc) != 0) {
		device_printf(dev, "crystal oscillator check failed\n");

		bus_release_resources(dev, aml8726_rtc_spec, sc->res);

		return (ENXIO);
	}

	AML_RTC_LOCK_INIT(sc);

	clock_register(dev, 1000000);

	return (0);
}

static int
aml8726_rtc_detach(device_t dev)
{

	return (EBUSY);
}

static int
aml8726_rtc_gettime(device_t dev, struct timespec *ts)
{
	struct aml8726_rtc_softc *sc = device_get_softc(dev);
	uint32_t sec;
	int error;

	AML_RTC_LOCK(sc);

	error = aml8726_rtc_sreg_read(sc, AML_RTC_TIME_SREG, &sec);

	AML_RTC_UNLOCK(sc);

	ts->tv_sec = sec;
	ts->tv_nsec = 0;

	return (error);
}

static int
aml8726_rtc_settime(device_t dev, struct timespec *ts)
{
	struct aml8726_rtc_softc *sc = device_get_softc(dev);
	uint32_t sec;
	int error;

	sec = ts->tv_sec;

	/* Accuracy is only one second. */
	if (ts->tv_nsec >= 500000000)
		sec++;

	AML_RTC_LOCK(sc);

	error = aml8726_rtc_sreg_write(sc, AML_RTC_TIME_SREG, sec);

	AML_RTC_UNLOCK(sc);

	return (error);	
}

static device_method_t aml8726_rtc_methods[] = {
	/* Device interface */
	DEVMETHOD(device_probe,		aml8726_rtc_probe),
	DEVMETHOD(device_attach,	aml8726_rtc_attach),
	DEVMETHOD(device_detach,	aml8726_rtc_detach),

	/* Clock interface */
	DEVMETHOD(clock_gettime,	aml8726_rtc_gettime),
	DEVMETHOD(clock_settime,	aml8726_rtc_settime),

	DEVMETHOD_END
};

static driver_t aml8726_rtc_driver = {
	"rtc",
	aml8726_rtc_methods,
	sizeof(struct aml8726_rtc_softc),
};

static devclass_t aml8726_rtc_devclass;

DRIVER_MODULE(rtc, simplebus, aml8726_rtc_driver, aml8726_rtc_devclass, 0, 0);