1/* $OpenBSD: qcrtc.c,v 1.4 2024/05/13 01:15:50 jsg Exp $ */ 2/* 3 * Copyright (c) 2022 Patrick Wildt <patrick@blueri.se> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18#include <sys/param.h> 19#include <sys/systm.h> 20#include <sys/device.h> 21 22#include <machine/bus.h> 23#include <machine/fdt.h> 24 25#include <dev/fdt/spmivar.h> 26 27#include <dev/ofw/openfirm.h> 28#include <dev/ofw/ofw_misc.h> 29#include <dev/ofw/fdt.h> 30 31#include <dev/clock_subr.h> 32 33/* Registers. */ 34#define RTC_WRITE 0x40 35#define RTC_CTRL 0x46 36#define RTC_CTRL_EN (1U << 7) 37#define RTC_READ 0x48 38 39struct qcrtc_softc { 40 struct device sc_dev; 41 int sc_node; 42 spmi_tag_t sc_tag; 43 int8_t sc_sid; 44 uint16_t sc_addr; 45 46 struct todr_chip_handle sc_todr; 47}; 48 49int qcrtc_match(struct device *, void *, void *); 50void qcrtc_attach(struct device *, struct device *, void *); 51 52const struct cfattach qcrtc_ca = { 53 sizeof (struct qcrtc_softc), qcrtc_match, qcrtc_attach 54}; 55 56struct cfdriver qcrtc_cd = { 57 NULL, "qcrtc", DV_DULL 58}; 59 60int qcrtc_gettime(struct todr_chip_handle *, struct timeval *); 61int qcrtc_settime(struct todr_chip_handle *, struct timeval *); 62 63extern int qcscm_uefi_rtc_get(uint32_t *); 64extern int qcscm_uefi_rtc_set(uint32_t); 65 66int 67qcrtc_match(struct device *parent, void *match, void *aux) 68{ 69 struct spmi_attach_args *saa = aux; 70 71 return OF_is_compatible(saa->sa_node, "qcom,pmk8350-rtc"); 72} 73 74void 75qcrtc_attach(struct device *parent, struct device *self, void *aux) 76{ 77 struct qcrtc_softc *sc = (struct qcrtc_softc *)self; 78 struct spmi_attach_args *saa = aux; 79 uint32_t reg[2]; 80 81 if (OF_getpropintarray(saa->sa_node, "reg", 82 reg, sizeof(reg)) != sizeof(reg)) { 83 printf(": can't find registers\n"); 84 return; 85 } 86 87 sc->sc_node = saa->sa_node; 88 sc->sc_tag = saa->sa_tag; 89 sc->sc_sid = saa->sa_sid; 90 sc->sc_addr = reg[0]; 91 92 printf("\n"); 93 94 sc->sc_todr.cookie = sc; 95 sc->sc_todr.todr_gettime = qcrtc_gettime; 96 sc->sc_todr.todr_settime = qcrtc_settime; 97 sc->sc_todr.todr_quality = 0; 98 todr_attach(&sc->sc_todr); 99} 100 101int 102qcrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv) 103{ 104 struct qcrtc_softc *sc = handle->cookie; 105 uint32_t reg, off; 106 int error; 107 108 /* Read current counting RTC value. */ 109 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 110 sc->sc_addr + RTC_READ, ®, sizeof(reg)); 111 if (error) { 112 printf("%s: error reading RTC\n", sc->sc_dev.dv_xname); 113 return error; 114 } 115 116 /* Retrieve RTC offset from either NVRAM or UEFI. */ 117 error = nvmem_read_cell(sc->sc_node, "offset", &off, sizeof(off)); 118 if (error == ENXIO || (!error && off == 0)) 119 error = qcscm_uefi_rtc_get(&off); 120 if (error) 121 return error; 122 123 tv->tv_sec = off + reg; 124 tv->tv_usec = 0; 125 return 0; 126} 127 128int 129qcrtc_settime(struct todr_chip_handle *handle, struct timeval *tv) 130{ 131 struct qcrtc_softc *sc = handle->cookie; 132 uint32_t reg, off; 133 int error; 134 135 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 136 sc->sc_addr + RTC_READ, ®, sizeof(reg)); 137 if (error) { 138 printf("%s: error reading RTC\n", sc->sc_dev.dv_xname); 139 return error; 140 } 141 142 /* Store RTC offset in either NVRAM or UEFI. */ 143 off = tv->tv_sec - reg; 144 error = nvmem_write_cell(sc->sc_node, "offset", &off, sizeof(off)); 145 if (error == ENXIO) 146 error = qcscm_uefi_rtc_set(off); 147 148 return error; 149} 150