git: 6b77d34f4491 - main - HYM8563: Add support for clock output.

From: Michal Meloun <mmel_at_FreeBSD.org>
Date: Sun, 22 Feb 2026 17:53:48 UTC
The branch main has been updated by mmel:

URL: https://cgit.FreeBSD.org/src/commit/?id=6b77d34f449128e6591131ec3d1822e2df3d8d8a

commit 6b77d34f449128e6591131ec3d1822e2df3d8d8a
Author:     Michal Meloun <mmel@FreeBSD.org>
AuthorDate: 2025-11-06 19:19:37 +0000
Commit:     Michal Meloun <mmel@FreeBSD.org>
CommitDate: 2026-02-22 17:53:27 +0000

    HYM8563: Add support for clock output.
    
    The RTC contains a configurable clock output.
    
    MFC after:      3 weeks
---
 sys/dev/iicbus/rtc/hym8563.c | 238 ++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 213 insertions(+), 25 deletions(-)

diff --git a/sys/dev/iicbus/rtc/hym8563.c b/sys/dev/iicbus/rtc/hym8563.c
index f2db23d47e67..56c5e88edc83 100644
--- a/sys/dev/iicbus/rtc/hym8563.c
+++ b/sys/dev/iicbus/rtc/hym8563.c
@@ -40,7 +40,8 @@
 #include <sys/lock.h>
 #include <sys/module.h>
 
-#ifdef FDT
+#if defined(FDT) && !defined(__powerpc64__) 
+#include <dev/clk/clk.h>
 #include <dev/ofw/ofw_bus.h>
 #include <dev/ofw/ofw_bus_subr.h>
 #endif
@@ -73,14 +74,28 @@
 #define	HYM8563_WEEKDAY		0x06
 #define	HYM8563_MONTH		0x07	/* plus 1 bit for century */
 #define	 HYM8563_MONTH_CENTURY		(1 << 7)
-#define HYM8563_YEAR		0x08
+#define	HYM8563_YEAR		0x08
+
+#define	HYM8563_CLKOUT		0x0D
+#define	 HYM8563_CLKOUT_ENABLE	(1 << 7)
+#define	 HYM8563_CLKOUT_32768	0
+#define	 HYM8563_CLKOUT_1024	1
+#define	 HYM8563_CLKOUT_32	2
+#define	 HYM8563_CLKOUT_1	3
+#define	 HYM8563_CLKOUT_MASK	3
 
 struct hym8563_softc {
 	device_t			dev;
 	struct intr_config_hook		init_hook;
 };
 
-#ifdef FDT
+#if defined(FDT) && !defined(__powerpc64__) 
+/* Clock class and method */
+struct hym8563_clk_sc {
+	device_t		base_dev;
+};
+
+
 static struct ofw_compat_data compat_data[] = {
 	{"haoyu,hym8563", 1},
 	{NULL,           0},
@@ -89,35 +104,200 @@ static struct ofw_compat_data compat_data[] = {
 
 
 static inline int
-hym8563_read_buf(struct hym8563_softc *sc, uint8_t reg, uint8_t *buf,
-    uint16_t buflen) 
+hym8563_read_buf(device_t dev, uint8_t reg, uint8_t *buf, uint16_t buflen)
 {
 
-	return (iicdev_readfrom(sc->dev, reg, buf, buflen, IIC_WAIT));
+	return (iicdev_readfrom(dev, reg, buf, buflen, IIC_WAIT));
 }
 
 static inline int
-hym8563_write_buf(struct hym8563_softc *sc, uint8_t reg, uint8_t *buf,
-    uint16_t buflen) 
+hym8563_write_buf(device_t dev, uint8_t reg, uint8_t *buf,  uint16_t buflen)
 {
 
-	return (iicdev_writeto(sc->dev, reg, buf, buflen, IIC_WAIT));
+	return (iicdev_writeto(dev, reg, buf, buflen, IIC_WAIT));
 }
 
 static inline int
-hym8563_read_1(struct hym8563_softc *sc, uint8_t reg, uint8_t *data) 
+hym8563_read_1(device_t dev, uint8_t reg, uint8_t *data)
 {
 
-	return (iicdev_readfrom(sc->dev, reg, data, 1, IIC_WAIT));
+	return (iicdev_readfrom(dev, reg, data, 1, IIC_WAIT));
 }
 
 static inline int
-hym8563_write_1(struct hym8563_softc *sc, uint8_t reg, uint8_t val) 
+hym8563_write_1(device_t dev, uint8_t reg, uint8_t val)
+{
+
+	return (iicdev_writeto(dev, reg, &val, 1, IIC_WAIT));
+}
+
+#if defined(FDT) && !defined(__powerpc64__) 
+static int
+hym8563_clk_set_gate(struct clknode *clk, bool enable)
 {
+	struct hym8563_clk_sc *sc;
+	uint8_t val;
+	int rv;
+
+	sc = clknode_get_softc(clk);
 
-	return (iicdev_writeto(sc->dev, reg, &val, 1, IIC_WAIT));
+	rv = hym8563_read_1(sc->base_dev, HYM8563_CLKOUT, &val);
+	if (rv != 0) {
+		device_printf(sc->base_dev,
+		    "Cannot read CLKOUT registers: %d\n", rv);
+		return (rv);
+	}
+	if (enable)
+		val |= HYM8563_CLKOUT_ENABLE;
+	else
+		val &= ~HYM8563_CLKOUT_ENABLE;
+	hym8563_write_1(sc->base_dev, HYM8563_CLKOUT, val);
+	if (rv != 0) {
+		device_printf(sc->base_dev,
+		    "Cannot write CLKOUT registers: %d\n", rv);
+		return (rv);
+	}
+	return (0);
 }
 
+static int
+hym8563_clk_recalc(struct clknode *clk, uint64_t *freq)
+{
+	struct hym8563_clk_sc *sc;
+	uint8_t val;
+	int rv;
+
+	sc = clknode_get_softc(clk);
+
+	rv = hym8563_read_1(sc->base_dev, HYM8563_CLKOUT, &val);
+	if (rv != 0) {
+		device_printf(sc->base_dev,
+		    "Cannot read CLKOUT registers: %d\n", rv);
+		return (rv);
+	}
+
+	switch (val & HYM8563_CLKOUT_MASK) {
+	case HYM8563_CLKOUT_32768:
+		*freq = 32768;
+		break;
+	case HYM8563_CLKOUT_1024:
+		*freq = 1024;
+		break;
+	case HYM8563_CLKOUT_32:
+		*freq = 32;
+		break;
+	case HYM8563_CLKOUT_1:
+		*freq = 1;
+		break;
+	default:
+		return (EINVAL);
+	}
+	return (0);
+}
+static int
+hym8563_clk_set(struct clknode *clk, uint64_t fparent, uint64_t *fout,
+    int flags, int *stop)
+{
+	struct hym8563_clk_sc *sc;
+	uint8_t val, tmp;
+	int rv;
+
+	sc = clknode_get_softc(clk);
+
+	switch (*fout) {
+	case 32768:
+		tmp = HYM8563_CLKOUT_32768;
+		break;
+	case 1024:
+		tmp = HYM8563_CLKOUT_1024;
+		break;
+	case 32:
+		tmp = HYM8563_CLKOUT_32;
+		break;
+	case 1:
+		tmp = HYM8563_CLKOUT_1;
+		break;
+	default:
+		*stop = 1;
+		return (EINVAL);
+	}
+
+	rv = hym8563_read_1(sc->base_dev, HYM8563_CLKOUT, &val);
+	if (rv != 0) {
+		device_printf(sc->base_dev,
+		    "Cannot read CLKOUT registers: %d\n", rv);
+		return (rv);
+	}
+
+	val &= ~HYM8563_CLKOUT_MASK;
+	val |= tmp;
+	rv = hym8563_write_1(sc->base_dev, HYM8563_CLKOUT, val);
+	if (rv != 0) {
+		device_printf(sc->base_dev,
+		    "Cannot write CLKOUT registers: %d\n", rv);
+		return (rv);
+	}
+
+	return (0);
+}
+
+static clknode_method_t hym8563_clk_clknode_methods[] = {
+	CLKNODEMETHOD(clknode_recalc_freq,	hym8563_clk_recalc),
+	CLKNODEMETHOD(clknode_set_freq,		hym8563_clk_set),
+	CLKNODEMETHOD(clknode_set_gate,		hym8563_clk_set_gate),
+	CLKNODEMETHOD_END
+};
+
+DEFINE_CLASS_1(hym8563_clk_clknode, hym8563_clk_clknode_class,
+    hym8563_clk_clknode_methods, sizeof(struct hym8563_clk_sc),
+    clknode_class);
+
+
+static int
+hym8563_attach_clocks(struct hym8563_softc *sc)
+{
+	struct clkdom *clkdom;
+	struct clknode_init_def clkidef;
+	struct clknode *clk;
+	struct hym8563_clk_sc *clksc;
+	const char **clknames;
+	phandle_t node;
+	int nclks, rv;
+
+	node = ofw_bus_get_node(sc->dev);
+
+	/* clock-output-names are optional. Could use them for clkidef.name. */
+	nclks = ofw_bus_string_list_to_array(node, "clock-output-names",
+	    &clknames);
+
+	clkdom = clkdom_create(sc->dev);
+
+	memset(&clkidef, 0, sizeof(clkidef));
+	clkidef.id = 1;
+	clkidef.name = (nclks == 1) ? clknames[0] : "hym8563-clkout";
+	clk = clknode_create(clkdom, &hym8563_clk_clknode_class, &clkidef);
+	if (clk == NULL) {
+		device_printf(sc->dev, "Cannot create '%s'.\n", clkidef.name);
+		return (ENXIO);
+	}
+	clksc = clknode_get_softc(clk);
+	clksc->base_dev = sc->dev;
+	clknode_register(clkdom, clk);
+
+	rv = clkdom_finit(clkdom);
+	if (rv != 0) {
+		device_printf(sc->dev, "Cannot finalize clkdom initialization: "
+		    "%d\n", rv);
+		return (ENXIO);
+	}
+
+	if (bootverbose)
+		clkdom_dump(clkdom);
+
+	return (0);
+}
+#endif
+
 static int
 hym8563_gettime(device_t dev, struct timespec *ts)
 {
@@ -129,7 +309,7 @@ hym8563_gettime(device_t dev, struct timespec *ts)
 	sc = device_get_softc(dev);
 
 	/* Read all RTC data */
-	rv = hym8563_read_buf(sc, HYM8563_SEC, buf, sizeof(buf));
+	rv = hym8563_read_buf(sc->dev, HYM8563_SEC, buf, sizeof(buf));
 	if (rv != 0) {
 		device_printf(sc->dev, "Cannot read time registers: %d\n", rv);
 		return (rv);
@@ -154,7 +334,7 @@ hym8563_gettime(device_t dev, struct timespec *ts)
 	if (buf[5] & HYM8563_MONTH_CENTURY)
 		bct.year += 0x100;
 
-	clock_dbgprint_bcd(sc->dev, CLOCK_DBG_READ, &bct); 
+	clock_dbgprint_bcd(sc->dev, CLOCK_DBG_READ, &bct);
 	return (clock_bcd_to_ts(&bct, ts, false));
 }
 
@@ -182,14 +362,14 @@ hym8563_settime(device_t dev, struct timespec *ts)
 		buf[5] |= HYM8563_MONTH_CENTURY;
 
 	/* Stop RTC */
-	rv = hym8563_write_1(sc, HYM8563_CTRL1, HYM8563_CTRL1_STOP);
+	rv = hym8563_write_1(sc->dev, HYM8563_CTRL1, HYM8563_CTRL1_STOP);
 	if (rv != 0) {
 		device_printf(sc->dev, "Cannot write CTRL1 register: %d\n", rv);
 		return (rv);
 	}
 
 	/* Write all RTC data */
-	rv = hym8563_write_buf(sc, HYM8563_SEC, buf, sizeof(buf));
+	rv = hym8563_write_buf(sc->dev, HYM8563_SEC, buf, sizeof(buf));
 	if (rv != 0) {
 		device_printf(sc->dev, "Cannot write time registers: %d\n", rv);
 		return (rv);
@@ -197,7 +377,7 @@ hym8563_settime(device_t dev, struct timespec *ts)
 	return (rv);
 
 	/* Start RTC again */
-	rv = hym8563_write_1(sc, HYM8563_CTRL1, 0);
+	rv = hym8563_write_1(sc->dev, HYM8563_CTRL1, 0);
 	if (rv != 0) {
 		device_printf(sc->dev, "Cannot write CTRL1 register: %d\n", rv);
 		return (rv);
@@ -217,14 +397,14 @@ hym8563_init(void *arg)
 	config_intrhook_disestablish(&sc->init_hook);
 
 	/* Clear CTL1 register (stop and test bits) */
-	rv = hym8563_write_1(sc, HYM8563_CTRL1, 0);
+	rv = hym8563_write_1(sc->dev, HYM8563_CTRL1, 0);
 	if (rv != 0) {
 		device_printf(sc->dev, "Cannot init CTRL1 register: %d\n", rv);
 		return;
 	}
-	
+
 	/* Disable interrupts and alarms */
-	rv = hym8563_read_1(sc, HYM8563_CTRL2, &reg);
+	rv = hym8563_read_1(sc->dev, HYM8563_CTRL2, &reg);
 	if (rv != 0) {
 		device_printf(sc->dev, "Cannot read CTRL2 register: %d\n", rv);
 		return;
@@ -232,7 +412,7 @@ hym8563_init(void *arg)
 	rv &= ~HYM8563_CTRL2_TI_TP;
 	rv &= ~HYM8563_CTRL2_AF;
 	rv &= ~HYM8563_CTRL2_TF;
-	rv = hym8563_write_1(sc, HYM8563_CTRL2, 0);
+	rv = hym8563_write_1(sc->dev, HYM8563_CTRL2, 0);
 	if (rv != 0) {
 		device_printf(sc->dev, "Cannot write CTRL2 register: %d\n", rv);
 		return;
@@ -250,7 +430,7 @@ static int
 hym8563_probe(device_t dev)
 {
 
-#ifdef FDT
+#if defined(FDT) && !defined(__powerpc64__) 
 	if (!ofw_bus_status_okay(dev))
 		return (ENXIO);
 
@@ -266,10 +446,15 @@ static int
 hym8563_attach(device_t dev)
 {
 	struct hym8563_softc *sc;
-	
+
 	sc = device_get_softc(dev);
 	sc->dev = dev;
 
+#if defined(FDT) && !defined(__powerpc64__) 
+	if (hym8563_attach_clocks(sc) != 0)
+		return(ENXIO);
+#endif
+
 	/*
 	 * Chip init must wait until interrupts are enabled.  Often i2c access
 	 * works only when the interrupts are available.
@@ -305,7 +490,10 @@ static device_method_t hym8563_methods[] = {
 
 static DEFINE_CLASS_0(hym8563_rtc, hym8563_driver, hym8563_methods,
     sizeof(struct hym8563_softc));
-DRIVER_MODULE(hym8563, iicbus, hym8563_driver, NULL, NULL);
+EARLY_DRIVER_MODULE(hym8563, iicbus, hym8563_driver, NULL, NULL,
+    BUS_PASS_SUPPORTDEV + BUS_PASS_ORDER_FIRST);
 MODULE_VERSION(hym8563, 1);
 MODULE_DEPEND(hym8563, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+#if defined(FDT) && !defined(__powerpc64__) 
 IICBUS_FDT_PNP_INFO(compat_data);
+#endif
\ No newline at end of file