1 /* $OpenBSD: qcrtc.c,v 1.3 2023/07/22 22:48:35 patrick 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 39 struct 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 49 int qcrtc_match(struct device *, void *, void *); 50 void qcrtc_attach(struct device *, struct device *, void *); 51 52 const struct cfattach qcrtc_ca = { 53 sizeof (struct qcrtc_softc), qcrtc_match, qcrtc_attach 54 }; 55 56 struct cfdriver qcrtc_cd = { 57 NULL, "qcrtc", DV_DULL 58 }; 59 60 int qcrtc_gettime(struct todr_chip_handle *, struct timeval *); 61 int qcrtc_settime(struct todr_chip_handle *, struct timeval *); 62 63 void qcrtc_tick(void *); 64 65 extern int qcscm_uefi_rtc_get(uint32_t *); 66 extern int qcscm_uefi_rtc_set(uint32_t); 67 68 int 69 qcrtc_match(struct device *parent, void *match, void *aux) 70 { 71 struct spmi_attach_args *saa = aux; 72 73 return OF_is_compatible(saa->sa_node, "qcom,pmk8350-rtc"); 74 } 75 76 void 77 qcrtc_attach(struct device *parent, struct device *self, void *aux) 78 { 79 struct qcrtc_softc *sc = (struct qcrtc_softc *)self; 80 struct spmi_attach_args *saa = aux; 81 uint32_t reg[2]; 82 83 if (OF_getpropintarray(saa->sa_node, "reg", 84 reg, sizeof(reg)) != sizeof(reg)) { 85 printf(": can't find registers\n"); 86 return; 87 } 88 89 sc->sc_node = saa->sa_node; 90 sc->sc_tag = saa->sa_tag; 91 sc->sc_sid = saa->sa_sid; 92 sc->sc_addr = reg[0]; 93 94 printf("\n"); 95 96 sc->sc_todr.cookie = sc; 97 sc->sc_todr.todr_gettime = qcrtc_gettime; 98 sc->sc_todr.todr_settime = qcrtc_settime; 99 sc->sc_todr.todr_quality = 0; 100 todr_attach(&sc->sc_todr); 101 } 102 103 int 104 qcrtc_gettime(struct todr_chip_handle *handle, struct timeval *tv) 105 { 106 struct qcrtc_softc *sc = handle->cookie; 107 uint32_t reg, off; 108 int error; 109 110 /* Read current counting RTC value. */ 111 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 112 sc->sc_addr + RTC_READ, ®, sizeof(reg)); 113 if (error) { 114 printf("%s: error reading RTC\n", sc->sc_dev.dv_xname); 115 return error; 116 } 117 118 /* Retrieve RTC offset from either NVRAM or UEFI. */ 119 error = nvmem_read_cell(sc->sc_node, "offset", &off, sizeof(off)); 120 if (error == ENXIO || (!error && off == 0)) 121 error = qcscm_uefi_rtc_get(&off); 122 if (error) 123 return error; 124 125 tv->tv_sec = off + reg; 126 tv->tv_usec = 0; 127 return 0; 128 } 129 130 int 131 qcrtc_settime(struct todr_chip_handle *handle, struct timeval *tv) 132 { 133 struct qcrtc_softc *sc = handle->cookie; 134 uint32_t reg, off; 135 int error; 136 137 error = spmi_cmd_read(sc->sc_tag, sc->sc_sid, SPMI_CMD_EXT_READL, 138 sc->sc_addr + RTC_READ, ®, sizeof(reg)); 139 if (error) { 140 printf("%s: error reading RTC\n", sc->sc_dev.dv_xname); 141 return error; 142 } 143 144 /* Store RTC offset in either NVRAM or UEFI. */ 145 off = tv->tv_sec - reg; 146 error = nvmem_write_cell(sc->sc_node, "offset", &off, sizeof(off)); 147 if (error == ENXIO) 148 error = qcscm_uefi_rtc_set(off); 149 150 return error; 151 } 152