/*	$NetBSD: pxa2x0_rtc.c,v 1.7 2020/01/02 22:27:15 thorpej Exp $	*/

/*
 * Copyright (c) 2007 NONAKA Kimihiro <nonaka@netbsd.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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: pxa2x0_rtc.c,v 1.7 2020/01/02 22:27:15 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/kernel.h>

#include <dev/clock_subr.h>

#include <sys/bus.h>

#include <arm/xscale/pxa2x0cpu.h>
#include <arm/xscale/pxa2x0reg.h>
#include <arm/xscale/pxa2x0var.h>

#ifdef PXARTC_DEBUG
#define	DPRINTF(s)	printf s
#else
#define	DPRINTF(s)
#endif

struct pxartc_softc {
	device_t		sc_dev;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;

	struct todr_chip_handle	sc_todr;

	int			sc_flags;
#define	FLAG_WRISTWATCH	(1 << 0)
};

static int  pxartc_match(device_t, cfdata_t, void *);
static void pxartc_attach(device_t, device_t, void *);

CFATTACH_DECL_NEW(pxartc, sizeof(struct pxartc_softc),
    pxartc_match, pxartc_attach, NULL, NULL);

/* todr(9) interface */
static int pxartc_todr_gettime(todr_chip_handle_t, struct timeval *);
static int pxartc_todr_settime(todr_chip_handle_t, struct timeval *);
static int pxartc_wristwatch_gettime(todr_chip_handle_t, struct clock_ymdhms *);
static int pxartc_wristwatch_settime(todr_chip_handle_t, struct clock_ymdhms *);

static int
pxartc_match(device_t parent, cfdata_t cf, void *aux)
{
	struct pxaip_attach_args *pxa = aux;

	if (strcmp(pxa->pxa_name, cf->cf_name) != 0)
		return 0;

	if (pxa->pxa_size == 0) {
		pxa->pxa_size =
		    CPU_IS_PXA270 ? PXA270_RTC_SIZE : PXA250_RTC_SIZE;
	}
	return 1;
}

static void
pxartc_attach(device_t parent, device_t self, void *aux)
{
	struct pxartc_softc *sc = device_private(self);
	struct pxaip_attach_args *pxa = aux;

	sc->sc_dev = self;
	sc->sc_iot = pxa->pxa_iot;

	aprint_normal(": Real-time Clock\n");

	if (bus_space_map(sc->sc_iot, pxa->pxa_addr, pxa->pxa_size, 0,
	    &sc->sc_ioh)) {
		aprint_error("%s: couldn't map registers\n",
		    device_xname(sc->sc_dev));
		return;
	}

	memset(&sc->sc_todr, 0, sizeof(sc->sc_todr));
	sc->sc_todr.cookie = sc;
	if (pxa->pxa_size == PXA270_RTC_SIZE) {
		aprint_normal("%s: using wristwatch register\n",
		    device_xname(sc->sc_dev));
		sc->sc_flags |= FLAG_WRISTWATCH;
		sc->sc_todr.todr_gettime_ymdhms = pxartc_wristwatch_gettime;
		sc->sc_todr.todr_settime_ymdhms = pxartc_wristwatch_settime;
	} else {
		sc->sc_todr.todr_gettime = pxartc_todr_gettime;
		sc->sc_todr.todr_settime = pxartc_todr_settime;
	}

	todr_attach(&sc->sc_todr);
}

static int
pxartc_todr_gettime(todr_chip_handle_t ch, struct timeval *tv)
{
	struct pxartc_softc *sc = ch->cookie;

	tv->tv_sec = bus_space_read_4(sc->sc_iot, sc->sc_ioh, RTC_RCNR);
	tv->tv_usec = 0;
#ifdef PXARTC_DEBUG
	DPRINTF(("%s: RCNR = %08llx\n", device_xname(sc->sc_dev),
	    tv->tv_sec));
	clock_secs_to_ymdhms(tv->tv_sec, &dt);
	DPRINTF(("%s: %02d/%02d/%02d %02d:%02d:%02d\n",
	    device_xname(sc->sc_dev),
	    dt.dt_year, dt.dt_mon, dt.dt_day,
	    dt.dt_hour, dt.dt_min, dt.dt_sec));
#endif
	return 0;
}

static int
pxartc_todr_settime(todr_chip_handle_t ch, struct timeval *tv)
{
	struct pxartc_softc *sc = ch->cookie;

#ifdef PXARTC_DEBUG
	DPRINTF(("%s: RCNR = %08llx\n", device_xname(sc->sc_dev),
	    tv->tv_sec));
	clock_secs_to_ymdhms(tv->tv_sec, &dt);
	DPRINTF(("%s: %02d/%02d/%02d %02d:%02d:%02d\n",
	    device_xname(sc->sc_dev),
	    dt.dt_year, dt.dt_mon, dt.dt_day,
	    dt.dt_hour, dt.dt_min, dt.dt_sec));
#endif
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, RTC_RCNR, tv->tv_sec);
#ifdef PXARTC_DEBUG
	{
		uint32_t cntr;
		delay(1);
		cntr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, RTC_RCNR);
		DPRINTF(("%s: new RCNR = %08x\n", device_xname(sc->sc_dev),
		    cntr));
		clock_secs_to_ymdhms(cntr, &dt);
		DPRINTF(("%s: %02d/%02d/%02d %02d:%02d:%02d\n",
		    device_xname(sc->sc_dev),
		    dt.dt_year, dt.dt_mon, dt.dt_day,
		    dt.dt_hour, dt.dt_min, dt.dt_sec));
	}
#endif
	return 0;
}

static int
pxartc_wristwatch_gettime(todr_chip_handle_t ch, struct clock_ymdhms *dt)
{
	struct pxartc_softc *sc = ch->cookie;
	uint32_t dayr, yearr;
	int s;

	DPRINTF(("%s: pxartc_wristwatch_gettime()\n",
		 device_xname(sc->sc_dev)));

	s = splhigh();
	dayr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, RTC_RDCR);
	yearr = bus_space_read_4(sc->sc_iot, sc->sc_ioh, RTC_RYCR);
	splx(s);

	DPRINTF(("%s: RDCR = %08x, RYCR = %08x\n", device_xname(sc->sc_dev),
	    dayr, yearr));

	dt->dt_sec = (dayr >> RDCR_SECOND_SHIFT) & RDCR_SECOND_MASK;
	dt->dt_min = (dayr >> RDCR_MINUTE_SHIFT) & RDCR_MINUTE_MASK;
	dt->dt_hour = (dayr >> RDCR_HOUR_SHIFT) & RDCR_HOUR_MASK;
	dt->dt_day = (yearr >> RYCR_DOM_SHIFT) & RYCR_DOM_MASK;
	dt->dt_mon = (yearr >> RYCR_MONTH_SHIFT) & RYCR_MONTH_MASK;
	dt->dt_year = (yearr >> RYCR_YEAR_SHIFT) & RYCR_YEAR_MASK;

	DPRINTF(("%s: %02d/%02d/%02d %02d:%02d:%02d\n",
	    device_xname(sc->sc_dev),
	    dt->dt_year, dt->dt_mon, dt->dt_day,
	    dt->dt_hour, dt->dt_min, dt->dt_sec));

	return 0;
}

static int
pxartc_wristwatch_settime(todr_chip_handle_t ch, struct clock_ymdhms *dt)
{
	struct pxartc_softc *sc = ch->cookie;
	uint32_t dayr, yearr;
	uint32_t wom;	/* week of month: 1=first week of month */
	int s;

	DPRINTF(("%s: pxartc_wristwatch_settime()\n",
		 device_xname(sc->sc_dev)));

	DPRINTF(("%s: %02d/%02d/%02d %02d:%02d:%02d\n",
	    device_xname(sc->sc_dev),
	    dt->dt_year, dt->dt_mon, dt->dt_day,
	    dt->dt_hour, dt->dt_min, dt->dt_sec));

	dayr = (dt->dt_sec & RDCR_SECOND_MASK) << RDCR_SECOND_SHIFT;
	dayr |= (dt->dt_min & RDCR_MINUTE_MASK) << RDCR_MINUTE_SHIFT;
	dayr |= (dt->dt_hour & RDCR_HOUR_MASK) << RDCR_HOUR_SHIFT;
	dayr |= ((dt->dt_wday + 1) & RDCR_DOW_MASK) << RDCR_DOW_SHIFT;
	wom = ((dt->dt_day - 1 + 6 - dt->dt_wday) / 7) + 1;
	dayr |= (wom & RDCR_WOM_MASK) << RDCR_WOM_SHIFT;
	yearr = (dt->dt_day & RYCR_DOM_MASK) << RYCR_DOM_SHIFT;
	yearr |= (dt->dt_mon & RYCR_MONTH_MASK) << RYCR_MONTH_SHIFT;
	yearr |= (dt->dt_year & RYCR_YEAR_MASK) << RYCR_YEAR_SHIFT;

	DPRINTF(("%s: RDCR = %08x, RYCR = %08x\n", device_xname(sc->sc_dev),
	    dayr, yearr));

	/*
	 * We must write RYCR register before write RDCR register.
	 *
	 * See PXA270 Processor Family Developer's Manual p.946
	 *   21.4.2.3.1 Writing RDCR and RYCR Counter Registers with Valid Data.
	 */
	s = splhigh();
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, RTC_RYCR, yearr);
	bus_space_write_4(sc->sc_iot, sc->sc_ioh, RTC_RDCR, dayr);
	splx(s);

#ifdef PXARTC_DEBUG
	{
		struct clock_ymdhms dummy;
		pxartc_wristwatch_gettime(sc, &dummy);
	}
#endif

	return 0;
}