diff options
Diffstat (limited to 'drivers/gpu/drm/bridge/ti-sn65dsi86.c')
-rw-r--r-- | drivers/gpu/drm/bridge/ti-sn65dsi86.c | 103 |
1 files changed, 71 insertions, 32 deletions
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c index 6ca1debd0f88..f27306c51e4d 100644 --- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c +++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c @@ -17,6 +17,8 @@ #include <linux/regmap.h> #include <linux/regulator/consumer.h> +#include <asm/unaligned.h> + #include <drm/drm_atomic.h> #include <drm/drm_atomic_helper.h> #include <drm/drm_bridge.h> @@ -72,6 +74,7 @@ #define SN_AUX_ADDR_19_16_REG 0x74 #define SN_AUX_ADDR_15_8_REG 0x75 #define SN_AUX_ADDR_7_0_REG 0x76 +#define SN_AUX_ADDR_MASK GENMASK(19, 0) #define SN_AUX_LENGTH_REG 0x77 #define SN_AUX_CMD_REG 0x78 #define AUX_CMD_SEND BIT(0) @@ -118,6 +121,7 @@ * @debugfs: Used for managing our debugfs. * @host_node: Remote DSI node. * @dsi: Our MIPI DSI source. + * @edid: Detected EDID of eDP panel. * @refclk: Our reference clock. * @panel: Our panel. * @enable_gpio: The GPIO we toggle to enable the bridge. @@ -143,6 +147,7 @@ struct ti_sn_bridge { struct drm_bridge bridge; struct drm_connector connector; struct dentry *debugfs; + struct edid *edid; struct device_node *host_node; struct mipi_dsi_device *dsi; struct clk *refclk; @@ -264,6 +269,23 @@ connector_to_ti_sn_bridge(struct drm_connector *connector) static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector) { struct ti_sn_bridge *pdata = connector_to_ti_sn_bridge(connector); + struct edid *edid = pdata->edid; + int num, ret; + + if (!edid) { + pm_runtime_get_sync(pdata->dev); + edid = pdata->edid = drm_get_edid(connector, &pdata->aux.ddc); + pm_runtime_put(pdata->dev); + } + + if (edid && drm_edid_is_valid(edid)) { + ret = drm_connector_update_edid_property(connector, edid); + if (!ret) { + num = drm_add_edid_modes(connector, edid); + if (num) + return num; + } + } return drm_panel_get_modes(pdata->panel, connector); } @@ -856,13 +878,15 @@ static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux, struct drm_dp_aux_msg *msg) { struct ti_sn_bridge *pdata = aux_to_ti_sn_bridge(aux); - u32 request = msg->request & ~DP_AUX_I2C_MOT; + u32 request = msg->request & ~(DP_AUX_I2C_MOT | DP_AUX_I2C_WRITE_STATUS_UPDATE); u32 request_val = AUX_CMD_REQ(msg->request); - u8 *buf = (u8 *)msg->buffer; + u8 *buf = msg->buffer; + unsigned int len = msg->size; unsigned int val; - int ret, i; + int ret; + u8 addr_len[SN_AUX_LENGTH_REG + 1 - SN_AUX_ADDR_19_16_REG]; - if (msg->size > SN_AUX_MAX_PAYLOAD_BYTES) + if (len > SN_AUX_MAX_PAYLOAD_BYTES) return -EINVAL; switch (request) { @@ -871,24 +895,21 @@ static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux, case DP_AUX_NATIVE_READ: case DP_AUX_I2C_READ: regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val); + /* Assume it's good */ + msg->reply = 0; break; default: return -EINVAL; } - regmap_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, - (msg->address >> 16) & 0xF); - regmap_write(pdata->regmap, SN_AUX_ADDR_15_8_REG, - (msg->address >> 8) & 0xFF); - regmap_write(pdata->regmap, SN_AUX_ADDR_7_0_REG, msg->address & 0xFF); - - regmap_write(pdata->regmap, SN_AUX_LENGTH_REG, msg->size); + BUILD_BUG_ON(sizeof(addr_len) != sizeof(__be32)); + put_unaligned_be32((msg->address & SN_AUX_ADDR_MASK) << 8 | len, + addr_len); + regmap_bulk_write(pdata->regmap, SN_AUX_ADDR_19_16_REG, addr_len, + ARRAY_SIZE(addr_len)); - if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE) { - for (i = 0; i < msg->size; i++) - regmap_write(pdata->regmap, SN_AUX_WDATA_REG(i), - buf[i]); - } + if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE) + regmap_bulk_write(pdata->regmap, SN_AUX_WDATA_REG(0), buf, len); /* Clear old status bits before start so we don't get confused */ regmap_write(pdata->regmap, SN_AUX_CMD_STATUS_REG, @@ -898,35 +919,52 @@ static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux, regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val | AUX_CMD_SEND); + /* Zero delay loop because i2c transactions are slow already */ ret = regmap_read_poll_timeout(pdata->regmap, SN_AUX_CMD_REG, val, - !(val & AUX_CMD_SEND), 200, - 50 * 1000); + !(val & AUX_CMD_SEND), 0, 50 * 1000); if (ret) return ret; ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val); if (ret) return ret; - else if ((val & AUX_IRQ_STATUS_NAT_I2C_FAIL) - || (val & AUX_IRQ_STATUS_AUX_RPLY_TOUT) - || (val & AUX_IRQ_STATUS_AUX_SHORT)) - return -ENXIO; - if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE) - return msg->size; + if (val & AUX_IRQ_STATUS_AUX_RPLY_TOUT) { + /* + * The hardware tried the message seven times per the DP spec + * but it hit a timeout. We ignore defers here because they're + * handled in hardware. + */ + return -ETIMEDOUT; + } - for (i = 0; i < msg->size; i++) { - unsigned int val; - ret = regmap_read(pdata->regmap, SN_AUX_RDATA_REG(i), - &val); + if (val & AUX_IRQ_STATUS_AUX_SHORT) { + ret = regmap_read(pdata->regmap, SN_AUX_LENGTH_REG, &len); if (ret) return ret; - - WARN_ON(val & ~0xFF); - buf[i] = (u8)(val & 0xFF); + } else if (val & AUX_IRQ_STATUS_NAT_I2C_FAIL) { + switch (request) { + case DP_AUX_I2C_WRITE: + case DP_AUX_I2C_READ: + msg->reply |= DP_AUX_I2C_REPLY_NACK; + break; + case DP_AUX_NATIVE_READ: + case DP_AUX_NATIVE_WRITE: + msg->reply |= DP_AUX_NATIVE_REPLY_NACK; + break; + } + return 0; } - return msg->size; + if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE || + len == 0) + return len; + + ret = regmap_bulk_read(pdata->regmap, SN_AUX_RDATA_REG(0), buf, len); + if (ret) + return ret; + + return len; } static int ti_sn_bridge_parse_dsi_host(struct ti_sn_bridge *pdata) @@ -1268,6 +1306,7 @@ static int ti_sn_bridge_remove(struct i2c_client *client) if (!pdata) return -EINVAL; + kfree(pdata->edid); ti_sn_debugfs_remove(pdata); of_node_put(pdata->host_node); |