/* $OpenBSD: acrtc.c,v 1.6 2022/10/17 19:09:46 kettenis Exp $ */ /* * Copyright (c) 2017 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * 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 #include #include #include #include #include #include #include #include #define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) #define CK32K_OUT_CTRL1 0xc1 #define CK32K_OUT_CTRL_PRE_DIV_MASK (0x7 << 5) #define CK32K_OUT_CTRL_PRE_DIV_32K (0x7 << 5) #define CK32K_OUT_CTRL_MUX_SEL_MASK (1 << 4) #define CK32K_OUT_CTRL_MUX_SEL_32K (0 << 4) #define CK32K_OUT_CTRL_POST_DIV_MASK (0x7 << 1) #define CK32K_OUT_CTRL_POST_DIV_32K (0x0 << 1) #define CK32K_OUT_CTRL_ENA (1 << 0) #define RTC_CTRL 0xc7 #define RTC_CTRL_12H_24H_MODE (1 << 0) #define RTC_SEC 0xc8 #define RTC_SEC_MASK (0x7f << 0) #define RTC_MIN 0xc9 #define RTC_MIN_MASK (0x7f << 0) #define RTC_HOU 0xca #define RTC_HOU_MASK (0x3f << 0) #define RTC_WEE 0xcb #define RTC_WEE_MASK (0x07 << 0) #define RTC_DAY 0xcc #define RTC_DAY_MASK (0x3f << 0) #define RTC_MON 0xcd #define RTC_MON_MASK (0x1f << 0) #define RTC_YEA 0xce #define RTC_YEA_LEAP_YEAR (1 << 15) #define RTC_YEA_MASK (0xff << 0) #define RTC_UPD_TRIG 0xcf #define RTC_UPD_TRIG_UPDATE (1 << 15) struct acrtc_softc { struct device sc_dev; void *sc_cookie; uint16_t sc_rta; struct todr_chip_handle sc_todr; struct clock_device sc_cd; }; int acrtc_match(struct device *, void *, void *); void acrtc_attach(struct device *, struct device *, void *); const struct cfattach acrtc_ca = { sizeof(struct acrtc_softc), acrtc_match, acrtc_attach }; struct cfdriver acrtc_cd = { NULL, "acrtc", DV_DULL }; int acrtc_clock_read(struct acrtc_softc *, struct clock_ymdhms *); int acrtc_clock_write(struct acrtc_softc *, struct clock_ymdhms *); int acrtc_gettime(struct todr_chip_handle *, struct timeval *); int acrtc_settime(struct todr_chip_handle *, struct timeval *); void acrtc_ck32k_enable(void *, uint32_t *, int); int acrtc_match(struct device *parent, void *match, void *aux) { struct rsb_attach_args *ra = aux; if (strcmp(ra->ra_name, "x-powers,ac100") == 0) return 1; return 0; } void acrtc_attach(struct device *parent, struct device *self, void *aux) { struct acrtc_softc *sc = (struct acrtc_softc *)self; struct rsb_attach_args *ra = aux; int node; sc->sc_cookie = ra->ra_cookie; sc->sc_rta = ra->ra_rta; printf("\n"); sc->sc_todr.cookie = sc; sc->sc_todr.todr_gettime = acrtc_gettime; sc->sc_todr.todr_settime = acrtc_settime; sc->sc_todr.todr_quality = 1000; todr_attach(&sc->sc_todr); node = OF_getnodebyname(ra->ra_node, "rtc"); if (node == 0) return; sc->sc_cd.cd_node = node; sc->sc_cd.cd_cookie = sc; sc->sc_cd.cd_enable = acrtc_ck32k_enable; clock_register(&sc->sc_cd); } static inline uint16_t acrtc_read_reg(struct acrtc_softc *sc, uint8_t reg) { return rsb_read_2(sc->sc_cookie, sc->sc_rta, reg); } static inline void acrtc_write_reg(struct acrtc_softc *sc, uint8_t reg, uint16_t value) { rsb_write_2(sc->sc_cookie, sc->sc_rta, reg, value); } int acrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv) { struct acrtc_softc *sc = handle->cookie; struct clock_ymdhms dt; int error; error = acrtc_clock_read(sc, &dt); if (error) return error; if (dt.dt_sec > 59 || dt.dt_min > 59 || dt.dt_hour > 23 || dt.dt_day > 31 || dt.dt_day == 0 || dt.dt_mon > 12 || dt.dt_mon == 0 || dt.dt_year < POSIX_BASE_YEAR) return EINVAL; tv->tv_sec = clock_ymdhms_to_secs(&dt); tv->tv_usec = 0; return 0; } int acrtc_settime(struct todr_chip_handle *handle, struct timeval *tv) { struct acrtc_softc *sc = handle->cookie; struct clock_ymdhms dt; clock_secs_to_ymdhms(tv->tv_sec, &dt); return acrtc_clock_write(sc, &dt); } int acrtc_clock_read(struct acrtc_softc *sc, struct clock_ymdhms *dt) { uint16_t ctrl; dt->dt_sec = FROMBCD(acrtc_read_reg(sc, RTC_SEC) & RTC_SEC_MASK); dt->dt_min = FROMBCD(acrtc_read_reg(sc, RTC_MIN) & RTC_MIN_MASK); dt->dt_hour = FROMBCD(acrtc_read_reg(sc, RTC_HOU) & RTC_HOU_MASK); dt->dt_day = FROMBCD(acrtc_read_reg(sc, RTC_DAY) & RTC_DAY_MASK); dt->dt_mon = FROMBCD(acrtc_read_reg(sc, RTC_MON) & RTC_MON_MASK); dt->dt_year = FROMBCD(acrtc_read_reg(sc, RTC_YEA) & RTC_YEA_MASK); dt->dt_year += 2000; #ifdef DEBUG printf("%02d/%02d/%04d %02d:%02d:%0d\n", dt->dt_day, dt->dt_mon, dt->dt_year, dt->dt_hour, dt->dt_min, dt->dt_sec); #endif /* Consider the time to be invalid if the clock is in 12H mode. */ ctrl = acrtc_read_reg(sc, RTC_CTRL); if ((ctrl & RTC_CTRL_12H_24H_MODE) == 0) return EINVAL; return 0; } int acrtc_clock_write(struct acrtc_softc *sc, struct clock_ymdhms *dt) { uint16_t leap = isleap(dt->dt_year) ? RTC_YEA_LEAP_YEAR : 0; acrtc_write_reg(sc, RTC_SEC, TOBCD(dt->dt_sec)); acrtc_write_reg(sc, RTC_MIN, TOBCD(dt->dt_min)); acrtc_write_reg(sc, RTC_HOU, TOBCD(dt->dt_hour)); acrtc_write_reg(sc, RTC_WEE, TOBCD(dt->dt_wday)); acrtc_write_reg(sc, RTC_DAY, TOBCD(dt->dt_day)); acrtc_write_reg(sc, RTC_MON, TOBCD(dt->dt_mon)); acrtc_write_reg(sc, RTC_YEA, TOBCD(dt->dt_year - 2000) | leap); acrtc_write_reg(sc, RTC_UPD_TRIG, RTC_UPD_TRIG_UPDATE); /* Switch to 24H mode to indicate the time is now valid. */ acrtc_write_reg(sc, RTC_CTRL, RTC_CTRL_12H_24H_MODE); return 0; } void acrtc_ck32k_enable(void *cookie, uint32_t *cells, int on) { struct acrtc_softc *sc = cookie; uint32_t idx = cells[0]; uint16_t reg; reg = acrtc_read_reg(sc, CK32K_OUT_CTRL1 + idx); reg &= ~CK32K_OUT_CTRL_PRE_DIV_MASK; reg &= ~CK32K_OUT_CTRL_MUX_SEL_MASK; reg &= ~CK32K_OUT_CTRL_POST_DIV_MASK; reg |= CK32K_OUT_CTRL_PRE_DIV_32K; reg |= CK32K_OUT_CTRL_MUX_SEL_32K; reg |= CK32K_OUT_CTRL_POST_DIV_32K; if (on) reg |= CK32K_OUT_CTRL_ENA; else reg &= ~CK32K_OUT_CTRL_ENA; acrtc_write_reg(sc, CK32K_OUT_CTRL1 + idx, reg); }