Module Name: src Committed By: bouyer Date: Sun Oct 16 14:56:04 UTC 2022
Modified Files: src/sys/arch/arm/sunxi [bouyer-sunxi-drm]: sunxi_hdmi.c sunxi_hdmireg.h Log Message: Work in progress: convert sunxi_hdmi to drm To generate a diff of this commit: cvs rdiff -u -r1.14.16.1 -r1.14.16.2 src/sys/arch/arm/sunxi/sunxi_hdmi.c cvs rdiff -u -r1.1 -r1.1.32.1 src/sys/arch/arm/sunxi/sunxi_hdmireg.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/arch/arm/sunxi/sunxi_hdmi.c diff -u src/sys/arch/arm/sunxi/sunxi_hdmi.c:1.14.16.1 src/sys/arch/arm/sunxi/sunxi_hdmi.c:1.14.16.2 --- src/sys/arch/arm/sunxi/sunxi_hdmi.c:1.14.16.1 Sun Oct 2 10:37:12 2022 +++ src/sys/arch/arm/sunxi/sunxi_hdmi.c Sun Oct 16 14:56:04 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_hdmi.c,v 1.14.16.1 2022/10/02 10:37:12 bouyer Exp $ */ +/* $NetBSD: sunxi_hdmi.c,v 1.14.16.2 2022/10/16 14:56:04 bouyer Exp $ */ /*- * Copyright (c) 2014 Jared D. McNeill <jmcne...@invisible.ca> @@ -29,7 +29,7 @@ #include "opt_ddb.h" #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c,v 1.14.16.1 2022/10/02 10:37:12 bouyer Exp $"); +__KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c,v 1.14.16.2 2022/10/16 14:56:04 bouyer Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -52,6 +52,8 @@ __KERNEL_RCSID(0, "$NetBSD: sunxi_hdmi.c #include <arm/sunxi/sunxi_hdmireg.h> #include <arm/sunxi/sunxi_display.h> +#include <arm/sunxi/sunxi_drm.h> + #include <drm/drm_bridge.h> #include <drm/drm_connector.h> #include <drm/drm_drv.h> @@ -62,6 +64,14 @@ enum sunxi_hdmi_type { HDMI_A31, }; +struct sunxi_hdmi_softc; + +struct sunxi_hdmi_encoder { + struct drm_encoder base; + struct sunxi_hdmi_softc *sc; + struct drm_display_mode curmode; +}; + struct sunxi_hdmi_softc { device_t sc_dev; int sc_phandle; @@ -75,16 +85,22 @@ struct sunxi_hdmi_softc { void *sc_ih; lwp_t *sc_thread; - struct i2c_controller sc_ic; - kmutex_t sc_exec_lock; + struct sunxi_hdmi_encoder sc_encoder; + struct drm_connector sc_connector; + struct i2c_adapter sc_i2c_adapt; + + enum sc_display_mode { + DISPLAY_MODE_NONE = 0, + DISPLAY_MODE_DVI, + DISPLAY_MODE_HDMI + } sc_display_mode; - bool sc_display_connected; + kmutex_t sc_exec_lock; kmutex_t sc_pwr_lock; int sc_pwr_refcount; /* reference who needs HDMI */ uint32_t sc_ver; - unsigned int sc_i2c_blklen; struct fdt_device_ports sc_ports; struct fdt_endpoint *sc_in_ep; @@ -93,6 +109,56 @@ struct sunxi_hdmi_softc { struct drm_display_mode sc_curmode; }; +#define to_sunxi_hdmi_encoder(x) container_of(x, struct sunxi_hdmi_encoder, base) + +#define connector_to_hdmi(x) container_of(x, struct sunxi_hdmi_softc, sc_connector) + +static void +sunxi_hdmi_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs sunxi_hdmi_enc_funcs = { + .destroy = sunxi_hdmi_destroy, +}; + +static int sunxi_hdmi_atomic_check(struct drm_encoder *, + struct drm_crtc_state *, struct drm_connector_state *); +static void sunxi_hdmi_disable(struct drm_encoder *); +static void sunxi_hdmi_enable(struct drm_encoder *); +static void sunxi_hdmi_mode_set(struct drm_encoder *, + struct drm_display_mode *, struct drm_display_mode *); +static void sunxi_hdmi_mode_valid(struct drm_encoder *, + struct drm_display_mode *); + +static const struct drm_encoder_helper_funcs sunxi_hdmi_enc_helper_funcs = { + .atomic_check = sunxi_hdmi_atomic_check, + .disable = sunxi_hdmi_disable, + .enable = sunxi_hdmi_enable, + .mode_set = sunxi_hdmi_mode_set, + .mode_valid = sunxi_hdmi_mode_valid, +}; + +static enum drm_connector_status sunxi_hdmi_connector_detect( + struct drm_connector *, bool); + +static const struct drm_connector_funcs sunxi_hdmi_connector_funcs = { + .detect = sunxi_hdmi_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int sunxi_hdmi_get_modes(struct drm_connector *); + +static const struct drm_connector_helper_funcs sunxi_hdmi_connector_helper_funcs + = { + .get_modes = sunxi_hdmi_get_modes, +}; + #define HDMI_READ(sc, reg) \ bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg)) #define HDMI_WRITE(sc, reg, val) \ @@ -110,11 +176,11 @@ static const struct device_compatible_en static int sunxi_hdmi_match(device_t, cfdata_t, void *); static void sunxi_hdmi_attach(device_t, device_t, void *); static void sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *); -static int sunxi_hdmi_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, - size_t, void *, size_t, int); -static int sunxi_hdmi_i2c_xfer(void *, i2c_addr_t, uint8_t, uint8_t, - size_t, int, int); -static int sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *, int); +static int sunxi_hdmi_i2c_drm_xfer(struct i2c_adapter *, + struct i2c_msg *, int); +static int sunxi_hdmi_i2c_xfer(struct sunxi_hdmi_softc *, + struct i2c_msg *); +static int sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *); static int sunxi_hdmi_ep_activate(device_t, struct fdt_endpoint *, bool); static int sunxi_hdmi_ep_enable(device_t, struct fdt_endpoint *, bool); @@ -127,11 +193,10 @@ static u_int sunxi_hdmi_get_display_mode const struct edid_info *); #endif static void sunxi_hdmi_video_enable(struct sunxi_hdmi_softc *, bool); +static void sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *, int); #if 0 static void sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *, const struct videomode *, u_int); -static void sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *, - const struct videomode *, u_int); static void sunxi_hdmi_hpd(struct sunxi_hdmi_softc *); static void sunxi_hdmi_thread(void *); #endif @@ -168,6 +233,7 @@ sunxi_hdmi_attach(device_t parent, devic sc->sc_dev = self; sc->sc_phandle = phandle; sc->sc_bst = faa->faa_bst; + sc->sc_encoder.sc = sc; sc->sc_type = of_compatible_lookup(faa->faa_phandle, compat_data)->value; @@ -209,7 +275,6 @@ sunxi_hdmi_attach(device_t parent, devic aprint_normal(": HDMI %d.%d\n", vmaj, vmin); sc->sc_ver = ver; - sc->sc_i2c_blklen = 16; sc->sc_ports.dp_ep_activate = sunxi_hdmi_ep_activate; sc->sc_ports.dp_ep_enable = sunxi_hdmi_ep_enable; @@ -257,71 +322,83 @@ sunxi_hdmi_doreset(void) } } +static int +sun4i_hdmi_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state * crtc_state, + struct drm_connector_state *conn_state) +{ + if (crtc_state->mode.flags & DRM_MODE_FLAG_DBLCLK) + return -EINVAL; + return 0; +} + static void -sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *sc) +sunxi_hdmi_disable(struct drm_encoder *enc) +{ + struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc); + struct sunxi_hdmi_softc *sc = hdmi_encoder->sc; + sunxi_hdmi_video_enable(sc, false); +} + +static void +sunxi_hdmi_enable(struct drm_encoder *enc) +{ + struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc); + struct sunxi_hdmi_softc *sc = hdmi_encoder->sc; + struct drm_display_mode *mode = &enc->crtc->state->adjusted_mode; + sunxi_hdmi_video_enable(sc, true); +} + +static uint32_t sunxi_hdmi_i2c_func(struct i2c_adapter *adap) { - struct i2c_controller *ic = &sc->sc_ic; + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static const struct i2c_algorithm sunxi_hdmi_i2c_algorithm = { + .master_xfer = sunxi_hdmi_i2c_drm_xfer, + .functionality = sunxi_hdmi_i2c_func, +}; + +static void +sunxi_hdmi_i2c_init(struct sunxi_hdmi_softc *sc) +{ mutex_init(&sc->sc_exec_lock, MUTEX_DEFAULT, IPL_NONE); - iic_tag_init(ic); - ic->ic_cookie = sc; - ic->ic_exec = sunxi_hdmi_i2c_exec; + sc->sc_i2c_adapt.owner = THIS_MODULE; + sc->sc_i2c_adapt.class = I2C_CLASS_DDC; + sc->sc_i2c_adapt.algo = &sunxi_hdmi_i2c_algorithm; + strlcpy(sc->sc_i2c_adapt.name, "sunxi_hdmi_i2c", + sizeof(sc->sc_i2c_adapt.name)); } static int -sunxi_hdmi_i2c_exec(void *priv, i2c_op_t op, i2c_addr_t addr, - const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags) -{ - struct sunxi_hdmi_softc *sc = priv; - uint8_t *pbuf; - uint8_t block; - int resid; - off_t off; - int err; +sunxi_hdmi_i2c_drm_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num){ + struct sunxi_hdmi_softc *sc = i2c_get_adapdata(adap); + int i, err; mutex_enter(&sc->sc_exec_lock); - KASSERT(op == I2C_OP_READ_WITH_STOP); - KASSERT(addr == DDC_ADDR); - KASSERT(cmdlen > 0); - KASSERT(buf != NULL); + for (i = 0; i < num; i++) { + if (msg[i].len == 0 || + msg[i].len > SUNXI_HDMI_DDC_BYTE_COUNTER) + return -EINVAL; + } - err = sunxi_hdmi_i2c_reset(sc, flags); + err = sunxi_hdmi_i2c_reset(sc); if (err) goto done; - block = *(const uint8_t *)cmdbuf; - off = (block & 1) ? 128 : 0; - - pbuf = buf; - resid = len; - while (resid > 0) { - size_t blklen = uimin(resid, sc->sc_i2c_blklen); - - err = sunxi_hdmi_i2c_xfer(sc, addr, block >> 1, off, blklen, - SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_EOREAD, flags); + for (i = 0; i < num; i++) { + err = sunxi_hdmi_i2c_xfer(sc, &msg[i]); if (err) - goto done; - - if (HDMI_1_3_P(sc)) { - bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, - SUNXI_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen); - } else { - bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, - SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, pbuf, blklen); - } - + break; #ifdef SUNXI_HDMI_DEBUG - printf("off=%d:", (int)off); - for (int i = 0; i < blklen; i++) - printf(" %02x", pbuf[i]); + printf("msg %d:", i); + for (int j = 0; j < msg[i].len; j++) + printf(" %02x", msg[i].buf[j]); printf("\n"); #endif - - pbuf += blklen; - off += blklen; - resid -= blklen; } done: @@ -329,117 +406,212 @@ done: return err; } +#define SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK ( \ + SUNXI_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OP | \ + SUNXI_HDMI_DDC_INT_STATUS_RX_UNDERFLOW | \ + SUNXI_HDMI_DDC_INT_STATUS_TX_OVERFLOW | \ + SUNXI_HDMI_DDC_INT_STATUS_ARB_ERR | \ + SUNXI_HDMI_DDC_INT_STATUS_ACK_ERR | \ + SUNXI_HDMI_DDC_INT_STATUS_BUS_ERR) + static int -sunxi_hdmi_i2c_xfer_1_3(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg, - size_t len, int type, int flags) +sunxi_hdmi_i2c_xfer_1_3(struct sunxi_hdmi_softc *sc, struct i2c_msg *msg) { struct sunxi_hdmi_softc *sc = priv; uint32_t val; + uint8_t *buf; + int len; int retry; val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG); val &= ~SUNXI_HDMI_DDC_CTRL_FIFO_DIR; + if (msg->flags & I2C_M_RD) + val |= SUNXI_HDMI_DDC_CTRL_FIFO_DIR_READ; + else + val |= SUNXI_HDMI_DDC_CTRL_FIFO_DIR_WRITE; HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val); - val |= __SHIFTIN(block, SUNXI_HDMI_DDC_SLAVE_ADDR_0); - val |= __SHIFTIN(0x60, SUNXI_HDMI_DDC_SLAVE_ADDR_1); - val |= __SHIFTIN(reg, SUNXI_HDMI_DDC_SLAVE_ADDR_2); - val |= __SHIFTIN(addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3); + val = __SHIFTIN(msg->addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3); HDMI_WRITE(sc, SUNXI_HDMI_DDC_SLAVE_ADDR_REG, val); - val = HDMI_READ(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG); + val = __SHIFTIN(0, SUNXI_HDMI_DDC_FIFO_CTRL_TX_TRIGGER_THRESH); + val |= __SHIFTIN(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX, + SUNXI_HDMI_DDC_FIFO_CTRL_RX_TRIGGER_THRESH); val |= SUNXI_HDMI_DDC_FIFO_CTRL_ADDR_CLEAR; HDMI_WRITE(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG, val); - HDMI_WRITE(sc, SUNXI_HDMI_DDC_BYTE_COUNTER_REG, len); - - HDMI_WRITE(sc, SUNXI_HDMI_DDC_COMMAND_REG, type); + retry = 20; + while (HDMI_READ(sc, SUNXI_HDMI_DDC_FIFO_CTRL_REG) & SUNXI_HDMI_DDC_FIFO_CTRL_ADDR_CLEAR) { + if (--retry == 0) + return -EIO; + delay(100); + } + + HDMI_WRITE(sc, SUNXI_HDMI_DDC_BYTE_COUNTER_REG, msg->len); + + HDMI_WRITE(sc, SUNXI_HDMI_DDC_COMMAND_REG, + msg->flags & I2C_M_RD ? + SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOREAD : + SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOWRITE; + + val = SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ | + SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK | + SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; + HDMI_WRITE(sc, SUNXI_HDMI_DDC_INT_STATUS_REG, val); val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG); val |= SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START; HDMI_WRITE(sc, SUNXI_HDMI_DDC_CTRL_REG, val); - retry = 1000; - while (--retry > 0) { - val = HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG); - if ((val & SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0) - break; + /* read data when some is available */ + len = msg->len; + buf = msg->buf; + while (len > 0) { + int blklen = imin(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX, len); + retry = 0; + do { + val = HDMI_READ(sc, SUNXI_HDMI_DDC_INT_STATUS_REG); + if (++retry > 1000) + return -ETIMEOUT; + } while (val & (SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ | + SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE | + SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) == 0); + if (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) { + device_printf(sc->sc_dev, + "xfer failed, status=%08x\n", val); + return -EIO; + } + if (msg->flags & I2C_M_RD) + bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, + SUNXI_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen); + else + bus_space_write_multi_1(sc->sc_bst, sc->sc_bsh, + SUNXI_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen); + buf += blklen; + len -= blklen; + HDMI_WRITE(sc, SUNXI_HDMI_DDC_INT_STATUS_REG, + SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ); + } + + retry = 0; + while (HDMI_READ(sc, SUNXI_HDMI_DDC_CTRL_REG) & SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START) { + if (++retry == 1000) + return EIO; delay(1000); } - if (retry == 0) - return ETIMEDOUT; val = HDMI_READ(sc, SUNXI_HDMI_DDC_INT_STATUS_REG); - if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0) { + if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0 || + (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK)) { device_printf(sc->sc_dev, "xfer failed, status=%08x\n", val); - return EIO; + return -EIO; } - return 0; } static int -sunxi_hdmi_i2c_xfer_1_4(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg, - size_t len, int type, int flags) +sunxi_hdmi_i2c_xfer_1_4(struct sunxi_hdmi_softc *sc, struct i2c_msg *msg) { - struct sunxi_hdmi_softc *sc = priv; uint32_t val; + uint8_t *buf; + int len; int retry; - val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG); + val = __SHIFTIN(1, SUNXI_HDMI_DDC_FIFO_CTRL_TX_TRIGGER_THRESH); + val |= __SHIFTIN(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX, + SUNXI_HDMI_DDC_FIFO_CTRL_RX_TRIGGER_THRESH); val |= SUNXI_A31_HDMI_DDC_FIFO_CTRL_RST; HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG, val); + retry = 0; + while (HDMI_READ(sc, SUNXI_A31_HDMI_DDC_FIFO_CTRL_REG) & + SUNXI_A31_HDMI_DDC_FIFO_CTRL_RST) { + if (++rety > 2000) + return -EIO; + delay(100); + } - val = __SHIFTIN(block, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_SEG_PTR); - val |= __SHIFTIN(0x60, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DDC_CMD); - val |= __SHIFTIN(reg, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_OFF_ADR); - val |= __SHIFTIN(addr, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_DEV_ADR); + val = __SHIFTIN(msg->addr, SUNXI_HDMI_DDC_SLAVE_ADDR_3); HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_SLAVE_ADDR_REG, val); - HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_COMMAND_REG, - __SHIFTIN(len, SUNXI_A31_HDMI_DDC_COMMAND_DTC) | - __SHIFTIN(type, SUNXI_A31_HDMI_DDC_COMMAND_CMD)); + val = __SHIFTIN(msg->len, SUNXI_A31_HDMI_DDC_COMMAND_DTC); + val |= __SHIFTIN(msg->flags & I2C_M_RD ? + SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOREAD : + SUNXI_HDMI_DDC_COMMAND_ACCESS_CMD_IOWRITE); + HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_COMMAND_REG, val, + SUNXI_A31_HDMI_DDC_COMMAND_CMD)); + + val = SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ | + SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK | + SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE; + HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG, val); val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG); val |= SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START; HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_CTRL_REG, val); + /* read data when some is available */ + len = msg->len; + buf = msg->buf; + while (len > 0) { + int blklen; + retry = 0; + do { + val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG); + if (++retry > 1000) + return -ETIMEOUT; + } while (val & (SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ | + SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE | + SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) == 0); + if (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK) { + device_printf(sc->sc_dev, + "xfer failed, status=%08x\n", val); + return -EIO; + } + if (msg->flags & I2C_M_RD) { + blklen = + imin(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX - 1, len); + bus_space_read_multi_1(sc->sc_bst, sc->sc_bsh, + SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen); + } else { + blklen = + imin(SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX, len); + bus_space_write_multi_1(sc->sc_bst, sc->sc_bsh, + SUNXI_A31_HDMI_DDC_FIFO_ACCESS_REG, buf, blklen); + } + buf += blklen; + len -= blklen; + HDMI_WRITE(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG, + SUNXI_HDMI_DDC_INT_STATUS_FIFO_REQ); + } + retry = 1000; - while (--retry > 0) { - val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG); - if ((val & SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START) == 0) - break; - if (flags & I2C_F_POLL) - delay(1000); - else - kpause("hdmiddc", false, mstohz(10), &sc->sc_exec_lock); + while (HDMI_READ(sc, SUNXI_A31_HDMI_DDC_CTRL_REG) & SUNXI_A31_HDMI_DDC_CTRL_ACCESS_CMD_START) { + if (--retry == 0) + return -ETIMEDOUT; + delay(1000); + } + val = HDMI_READ(sc, SUNXI_A31_HDMI_DDC_INT_STATUS_REG); + if ((val & SUNXI_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE) == 0 || + (val & SUNXI_HDMI_DDC_INT_STATUS_ERR_MASK)) { + device_printf(sc->sc_dev, "xfer failed, status=%08x\n", val); + return -EIO; } - if (retry == 0) - return ETIMEDOUT; return 0; } static int -sunxi_hdmi_i2c_xfer(void *priv, i2c_addr_t addr, uint8_t block, uint8_t reg, - size_t len, int type, int flags) +sunxi_hdmi_i2c_xfer(struct sunxi_hdmi_softc *sc, struct i2c_msg *msg) { - struct sunxi_hdmi_softc *sc = priv; - int rv; - if (HDMI_1_3_P(sc)) { - rv = sunxi_hdmi_i2c_xfer_1_3(priv, addr, block, reg, len, - type, flags); + return sunxi_hdmi_i2c_xfer_1_3(sc, msg); } else { - rv = sunxi_hdmi_i2c_xfer_1_4(priv, addr, block, reg, len, - type, flags); + return sunxi_hdmi_i2c_xfer_1_4(sc, msg); } - - return rv; } static int -sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *sc, int flags) +sunxi_hdmi_i2c_reset(struct sunxi_hdmi_softc *sc) { uint32_t hpd, ctrl; @@ -491,6 +663,7 @@ sunxi_hdmi_ep_activate(device_t dev, str { struct sunxi_hdmi_softc *sc = device_private(dev); struct fdt_endpoint *in_ep, *out_ep; + struct drm_crtc *crtc; int error; /* our input is activated by tcon, we activate our output */ @@ -528,10 +701,36 @@ sunxi_hdmi_ep_activate(device_t dev, str aprint_error_dev(dev, "no output endpoint\n"); return ENODEV; } + sc->sc_in_rep = fdt_endpoint_remote(ep); + KASSERT(sc->sc_in_rep != NULL); + + if (fdt_endpoint_type(sc->sc_in_rep) != EP_DRM_ENCODER) { + device_printf(sc->sc_dev, ": in endpoint %d wrong type\n", + fdt_endpoint_type(sc->sc_in_rep)); + panic("hdmi fdt_endpoint_type"); + } + crtc = sunxi_tcon_get_crtc(fdt_endpoint_device(sc->sc_in_rep)); + + sc->sc_encoder.sc = sc; + sc->sc_encoder.base.possible_crtcs = 1 << drm_crtc_index(crtc); + drm_encoder_helper_add(&sc->sc_encoder.base, + &sunxi_hdmi_enc_helper_funcs); + error = drm_encoder_init(crtc->dev, &sc->sc_encoder.base, + sunxi_hdmi_enc_funcs, DRM_MODE_ENCODER_TMDS, NULL); + if (error) + return -error; + + drm_connector_helper_add(&sc->sc_connector, + &sunxi_hdmi_connector_helper_funcs); + error = drm_connector_init_with_ddc(crtc->dev, &sc->sc_connector, + DRM_MODE_CONNECTOR_HDMIA, &sc->sc_i2c_adapt); + if (error) + return -error; + drm_connector_attach_encoder(&sc->sc_connector, &sc->sc_encoder.base); + error = fdt_endpoint_activate(out_ep, activate); if (error == 0) { sc->sc_in_ep = ep; - sc->sc_in_rep = fdt_endpoint_remote(ep); sc->sc_out_ep = out_ep; sunxi_hdmi_do_enable(sc); return 0; @@ -819,39 +1018,12 @@ sunxi_hdmi_video_enable(struct sunxi_hdm #endif } -#if 0 -static void -sunxi_hdmi_set_videomode(struct sunxi_hdmi_softc *sc, - const struct videomode *mode, u_int display_mode) +static int +hdmi_clk_find(struct sunxi_hdmi_softc *sc, int rate, int *div, int *dbl) { - uint32_t val; - const u_int dblscan_p = !!(mode->flags & VID_DBLSCAN); - const u_int interlace_p = !!(mode->flags & VID_INTERLACE); - const u_int phsync_p = !!(mode->flags & VID_PHSYNC); - const u_int pvsync_p = !!(mode->flags & VID_PVSYNC); - const u_int hfp = mode->hsync_start - mode->hdisplay; - const u_int hspw = mode->hsync_end - mode->hsync_start; - const u_int hbp = mode->htotal - mode->hsync_start; - const u_int vfp = mode->vsync_start - mode->vdisplay; - const u_int vspw = mode->vsync_end - mode->vsync_start; - const u_int vbp = mode->vtotal - mode->vsync_start; - struct clk *clk_pll; + int best_diff; int parent_rate; - int best_div, best_dbl, best_diff; - int target_rate = mode->dot_clock * 1000; - -#ifdef SUNXI_HDMI_DEBUG - device_printf(sc->sc_dev, - "dblscan %d, interlace %d, phsync %d, pvsync %d\n", - dblscan_p, interlace_p, phsync_p, pvsync_p); - device_printf(sc->sc_dev, "h: %u %u %u %u\n", - mode->hdisplay, hbp, hfp, hspw); - device_printf(sc->sc_dev, "v: %u %u %u %u\n", - mode->vdisplay, vbp, vfp, vspw); -#endif - - HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, 0xffffffff); - + struct clk *clk_pll; /* assume tcon0 uses pll3, tcon1 uses pll7 */ switch(fdt_endpoint_index(sc->sc_in_ep)) { case 0: @@ -865,7 +1037,7 @@ sunxi_hdmi_set_videomode(struct sunxi_hd } parent_rate = clk_get_rate(clk_pll); KASSERT(parent_rate > 0); - best_div = best_dbl = 0; + *div = *dbl = 0; best_diff = INT_MAX; for (int d = 2; d > 0 && best_diff != 0; d--) { for (int m = 1; m <= 16 && best_diff != 0; m++) { @@ -873,20 +1045,56 @@ sunxi_hdmi_set_videomode(struct sunxi_hd int diff = abs(target_rate - cur_rate); if (diff >= 0 && diff < best_diff) { best_diff = diff; - best_div = m; - best_dbl = d; + *div = m; + *dbl = d; } } } #ifdef SUNXI_HDMI_DEBUG device_printf(sc->sc_dev, "parent rate: %d\n", parent_rate); - device_printf(sc->sc_dev, "dot_clock: %d\n", mode->dot_clock); + device_printf(sc->sc_dev, "crtc_clock: %d\n", mode->crtc_clock); device_printf(sc->sc_dev, "clkdiv: %d\n", best_div); device_printf(sc->sc_dev, "clkdbl: %c\n", (best_dbl == 1) ? 'Y' : 'N'); #endif + return best_diff; +} + +static void +sunxi_hdmi_mode_set(struct drm_encoder *enc, + struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) +{ + struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc); + struct sunxi_hdmi_softc *sc = hdmi_encoder->sc; + uint32_t val; + const u_int dblscan_p = !!(mode->flags & DRM_MODE_FLAG_DBLSCAN); + const u_int interlace_p = !!(mode->flags & DRM_MODE_FLAG_INTERLACE); + const u_int phsync_p = !!(mode->flags & DRM_MODE_FLAG_PHSYNC); + const u_int pvsync_p = !!(mode->flags & DRM_MODE_FLAG_PVSYNC); + const u_int hfp = mode->hsync_start - mode->hdisplay; + const u_int hspw = mode->hsync_end - mode->hsync_start; + const u_int hbp = mode->htotal - mode->hsync_start; + const u_int vfp = mode->vsync_start - mode->vdisplay; + const u_int vspw = mode->vsync_end - mode->vsync_start; + const u_int vbp = mode->vtotal - mode->vsync_start; + int div, dbl, diff; + int target_rate = mode->crtc_clock * 1000; + +#ifdef SUNXI_HDMI_DEBUG + device_printf(sc->sc_dev, + "dblscan %d, interlace %d, phsync %d, pvsync %d\n", + dblscan_p, interlace_p, phsync_p, pvsync_p); + device_printf(sc->sc_dev, "h: %u %u %u %u\n", + mode->hdisplay, hbp, hfp, hspw); + device_printf(sc->sc_dev, "v: %u %u %u %u\n", + mode->vdisplay, vbp, vfp, vspw); +#endif + + HDMI_WRITE(sc, SUNXI_HDMI_INT_STATUS_REG, 0xffffffff); - if (best_div == 0) { + diff = hdmi_clk_find(target_rate, &div, &dbl); + + if (div == 0) { device_printf(sc->sc_dev, "ERROR: TCON clk not configured\n"); return; } @@ -896,14 +1104,14 @@ sunxi_hdmi_set_videomode(struct sunxi_hd pad_ctrl0 = 0x7e8000ff; pad_ctrl1 = 0x01ded030; pll_ctrl = 0xba48a308; - pll_ctrl |= __SHIFTIN(best_div - 1, SUNXI_HDMI_PLL_CTRL_PREDIV); + pll_ctrl |= __SHIFTIN(div - 1, SUNXI_HDMI_PLL_CTRL_PREDIV); } else { pad_ctrl0 = 0xfe800000; pad_ctrl1 = 0x00d8c830; pll_ctrl = 0xfa4ef708; - pll_ctrl |= __SHIFTIN(best_div, SUNXI_HDMI_PLL_CTRL_PREDIV); + pll_ctrl |= __SHIFTIN(div, SUNXI_HDMI_PLL_CTRL_PREDIV); } - if (best_dbl == 2) + if (dbl == 2) pad_ctrl1 |= 0x40; HDMI_WRITE(sc, SUNXI_HDMI_PAD_CTRL0_REG, pad_ctrl0); @@ -923,7 +1131,7 @@ sunxi_hdmi_set_videomode(struct sunxi_hd val = HDMI_READ(sc, SUNXI_HDMI_VID_CTRL_REG); val &= ~SUNXI_HDMI_VID_CTRL_HDMI_MODE; - if (display_mode == DISPLAY_MODE_DVI) { + if (sc->sc_display_mode == DISPLAY_MODE_DVI) { val |= __SHIFTIN(SUNXI_HDMI_VID_CTRL_HDMI_MODE_DVI, SUNXI_HDMI_VID_CTRL_HDMI_MODE); } else { @@ -982,11 +1190,28 @@ sunxi_hdmi_set_videomode(struct sunxi_hd HDMI_WRITE(sc, SUNXI_HDMI_GP_PKT1_REG, 0); HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL0_REG, 0x00005321); HDMI_WRITE(sc, SUNXI_HDMI_PKT_CTRL1_REG, 0x0000000f); + sunxi_hdmi_set_audiomode(sc, mode->crtc_clock); +} + +static void +sunxi_hdmi_mode_valid(struct drm_encoder *enc, + struct drm_display_mode *mode) +{ + struct sunxi_hdmi_encoder *hdmi_encoder = to_sunxi_hdmi_encoder(enc); + struct sunxi_hdmi_softc *sc = hdmi_encoder->sc; + int diff, div, dbl; + int target_rate = mode->clock * 10000; + + if (target_rate > 165000000) /* max pixelclock for HDMI <= 1.2 */ + return MODE_CLOCK_HIGH; + diff = hdmi_clk_find(target_rate, &div, &dbl); + if (diff >= target_rate / 200) /* HDMI allows max 0.5% */ + return MODE_NOCLOCK; + return MODE_OK; } static void -sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *sc, - const struct videomode *mode, u_int display_mode) +sunxi_hdmi_set_audiomode(struct sunxi_hdmi_softc *sc, int dot_clock) { uint32_t cts, n, val; @@ -1004,7 +1229,7 @@ sunxi_hdmi_set_audiomode(struct sunxi_hd } while (val & SUNXI_HDMI_AUD_CTRL_RST); /* No audio support in DVI mode */ - if (display_mode != DISPLAY_MODE_HDMI) { + if (sc->sc_display_mode != DISPLAY_MODE_HDMI) { return; } @@ -1036,7 +1261,7 @@ sunxi_hdmi_set_audiomode(struct sunxi_hd /* Clock setup */ n = 6144; /* 48 kHz */ - cts = ((mode->dot_clock * 10) * (n / 128)) / 480; + cts = ((dot_clock * 10) * (n / 128)) / 480; HDMI_WRITE(sc, SUNXI_HDMI_AUD_CTS_REG, cts); HDMI_WRITE(sc, SUNXI_HDMI_AUD_N_REG, n); @@ -1063,48 +1288,42 @@ sunxi_hdmi_set_audiomode(struct sunxi_hd sunxi_hdmi_dump_regs(); #endif } -#endif -#if 0 -static void -sunxi_hdmi_hpd(struct sunxi_hdmi_softc *sc) +static enum drm_connector_status +sunxi_hdmi_connector_detect(struct drm_connector *connector, bool force) { - uint32_t hpd = HDMI_READ(sc, SUNXI_HDMI_HPD_REG); - bool con = !!(hpd & SUNXI_HDMI_HPD_HOTPLUG_DET); - - KASSERT(mutex_owned(&sc->sc_pwr_lock)); - if (sc->sc_display_connected == con) - return; + struct sunxi_hdmi_softc *sc = connector_to_hdmi(connector); - if (con) { - device_printf(sc->sc_dev, "display connected\n"); - sc->sc_pwr_refcount = 1; - sunxi_hdmi_read_edid(sc); - } else { - device_printf(sc->sc_dev, "display disconnected\n"); - sc->sc_pwr_refcount = 0; - sunxi_hdmi_video_enable(sc, false); - fdt_endpoint_enable(sc->sc_in_ep, false); - sunxi_tcon1_set_videomode( - fdt_endpoint_device(sc->sc_in_rep), NULL); + if (HDMI_READ(sc, SUNXI_HDMI_HPD_REG) & SUNXI_HDMI_HPD_HOTPLUG_DET) { + return connector_status_connected; } - - sc->sc_display_connected = con; + sc->sc_display_mode = DISPLAY_MODE_NONE; + return connector_status_disconnected; } -static void -sunxi_hdmi_thread(void *priv) +static int +sunxi_hdmi_get_modes(struct drm_connector * connector) { - struct sunxi_hdmi_softc *sc = priv; + struct sunxi_hdmi_softc *sc = connector_to_hdmi(connector); + struct edid *edid; + int ret; + + edid = drm_get_edid(connector, &sc->sc_i2c_adapt); + if (!edid) { + sc->sc_display_mode = DISPLAY_MODE_NONE; + return 0; + } - for (;;) { - mutex_enter(&sc->sc_pwr_lock); - sunxi_hdmi_hpd(sc); - mutex_exit(&sc->sc_pwr_lock); - kpause("hdmihotplug", false, mstohz(1000), NULL); + if (drm_detect_hdmi_monitor(edid)) { + sc->sc_display_mode = DISPLAY_MODE_HDMI; + } else { + sc->sc_display_mode = DISPLAY_MODE_DVI; } + drm_connector_update_edid_property(connector, edid); + ret = drm_add_edid_modes(connector, edid); + kfree(edid); + return ret; } -#endif static int sunxi_hdmi_poweron(struct sunxi_hdmi_softc *sc, bool enable) Index: src/sys/arch/arm/sunxi/sunxi_hdmireg.h diff -u src/sys/arch/arm/sunxi/sunxi_hdmireg.h:1.1 src/sys/arch/arm/sunxi/sunxi_hdmireg.h:1.1.32.1 --- src/sys/arch/arm/sunxi/sunxi_hdmireg.h:1.1 Tue Apr 3 12:52:16 2018 +++ src/sys/arch/arm/sunxi/sunxi_hdmireg.h Sun Oct 16 14:56:04 2022 @@ -1,4 +1,4 @@ -/* $NetBSD: sunxi_hdmireg.h,v 1.1 2018/04/03 12:52:16 bouyer Exp $ */ +/* $NetBSD: sunxi_hdmireg.h,v 1.1.32.1 2022/10/16 14:56:04 bouyer Exp $ */ /*- * Copyright (c) 2013 The NetBSD Foundation, Inc. @@ -214,7 +214,7 @@ #define SUNXI_HDMI_DDC_CTRL_ACCESS_CMD_START __BIT(30) #define SUNXI_HDMI_DDC_CTRL_FIFO_DIR __BIT(8) #define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_READ 0 -#define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_WRITE 1 +#define SUNXI_HDMI_DDC_CTRL_FIFO_DIR_WRITE __BIT(8) #define SUNXI_HDMI_DDC_CTRL_SW_RST __BIT(0) #define SUNXI_HDMI_DDC_SLAVE_ADDR_0 __BITS(31,24) @@ -236,6 +236,9 @@ #define SUNXI_HDMI_DDC_FIFO_CTRL_REQUEST_EN __BIT(8) #define SUNXI_HDMI_DDC_FIFO_CTRL_RX_TRIGGER_THRESH __BITS(7,4) #define SUNXI_HDMI_DDC_FIFO_CTRL_TX_TRIGGER_THRESH __BITS(3,0) +#define SUNXI_HDMI_DDC_FIFO_CTRL_TRIGGER_MAX __BITS(3,0) + +#define SUNXI_HDMI_DDC_BYTE_COUNTER __BITS(9, 0) #define SUNXI_HDMI_DDC_FIFO_STATUS_REQ_READY __BIT(7) #define SUNXI_HDMI_DDC_FIFO_STATUS_FULL __BIT(6)