root/sys/arch/arm/xscale/pxa2x0_rtc.c
/*      $NetBSD: pxa2x0_rtc.c,v 1.9 2025/09/07 21:45:12 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.9 2025/09/07 21:45:12 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.todr_dev = self;
        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 = device_private(ch->todr_dev);

        tv->tv_sec = bus_space_read_4(sc->sc_iot, sc->sc_ioh, RTC_RCNR);
        tv->tv_usec = 0;
#ifdef PXARTC_DEBUG
        struct clock_ymdhms dt;
        DPRINTF(("%s: RCNR = %08llx\n", device_xname(sc->sc_dev),
            tv->tv_sec));
        clock_secs_to_ymdhms(tv->tv_sec, &dt);
        DPRINTF(("%s: %02lld/%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 = device_private(ch->todr_dev);

#ifdef PXARTC_DEBUG
        struct clock_ymdhms dt;
        DPRINTF(("%s: RCNR = %08llx\n", device_xname(sc->sc_dev),
            tv->tv_sec));
        clock_secs_to_ymdhms(tv->tv_sec, &dt);
        DPRINTF(("%s: %02lld/%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: %02lld/%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 = device_private(ch->todr_dev);
        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: %02lld/%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 = device_private(ch->todr_dev);
        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: %02lld/%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(ch, &dummy);
        }
#endif

        return 0;
}