aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/bridge/ti-sn65dsi86.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/bridge/ti-sn65dsi86.c')
-rw-r--r--drivers/gpu/drm/bridge/ti-sn65dsi86.c760
1 files changed, 414 insertions, 346 deletions
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index 45a2969afb2b..41d48a393e7f 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -23,6 +23,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
+#include <drm/drm_dp_aux_bus.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
@@ -116,6 +117,7 @@
* struct ti_sn65dsi86 - Platform data for ti-sn65dsi86 driver.
* @bridge_aux: AUX-bus sub device for MIPI-to-eDP bridge functionality.
* @gpio_aux: AUX-bus sub device for GPIO controller functionality.
+ * @aux_aux: AUX-bus sub device for eDP AUX channel functionality.
*
* @dev: Pointer to the top level (i2c) device.
* @regmap: Regmap for accessing i2c.
@@ -124,9 +126,8 @@
* @connector: Our connector.
* @host_node: Remote DSI node.
* @dsi: Our MIPI DSI source.
- * @edid: Detected EDID of eDP panel.
* @refclk: Our reference clock.
- * @panel: Our panel.
+ * @next_bridge: The bridge on the eDP side.
* @enable_gpio: The GPIO we toggle to enable the bridge.
* @supplies: Data for bulk enabling/disabling our regulators.
* @dp_lanes: Count of dp_lanes we're using.
@@ -148,17 +149,17 @@
struct ti_sn65dsi86 {
struct auxiliary_device bridge_aux;
struct auxiliary_device gpio_aux;
+ struct auxiliary_device aux_aux;
struct device *dev;
struct regmap *regmap;
struct drm_dp_aux aux;
struct drm_bridge bridge;
struct drm_connector connector;
- struct edid *edid;
struct device_node *host_node;
struct mipi_dsi_device *dsi;
struct clk *refclk;
- struct drm_panel *panel;
+ struct drm_bridge *next_bridge;
struct gpio_desc *enable_gpio;
struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM];
int dp_lanes;
@@ -306,6 +307,9 @@ static int __maybe_unused ti_sn65dsi86_resume(struct device *dev)
return ret;
}
+ /* td2: min 100 us after regulators before enabling the GPIO */
+ usleep_range(100, 110);
+
gpiod_set_value(pdata->enable_gpio, 1);
/*
@@ -393,35 +397,222 @@ static void ti_sn65dsi86_debugfs_init(struct ti_sn65dsi86 *pdata)
debugfs_create_file("status", 0600, debugfs, pdata, &status_fops);
}
-/* Connector funcs */
-static struct ti_sn65dsi86 *
-connector_to_ti_sn65dsi86(struct drm_connector *connector)
+/* -----------------------------------------------------------------------------
+ * Auxiliary Devices (*not* AUX)
+ */
+
+static void ti_sn65dsi86_uninit_aux(void *data)
{
- return container_of(connector, struct ti_sn65dsi86, connector);
+ auxiliary_device_uninit(data);
}
-static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
+static void ti_sn65dsi86_delete_aux(void *data)
{
- struct ti_sn65dsi86 *pdata = connector_to_ti_sn65dsi86(connector);
- struct edid *edid = pdata->edid;
- int num, ret;
+ auxiliary_device_delete(data);
+}
- if (!edid) {
- pm_runtime_get_sync(pdata->dev);
- edid = pdata->edid = drm_get_edid(connector, &pdata->aux.ddc);
- pm_runtime_put_autosuspend(pdata->dev);
+/*
+ * AUX bus docs say that a non-NULL release is mandatory, but it makes no
+ * sense for the model used here where all of the aux devices are allocated
+ * in the single shared structure. We'll use this noop as a workaround.
+ */
+static void ti_sn65dsi86_noop(struct device *dev) {}
+
+static int ti_sn65dsi86_add_aux_device(struct ti_sn65dsi86 *pdata,
+ struct auxiliary_device *aux,
+ const char *name)
+{
+ struct device *dev = pdata->dev;
+ int ret;
+
+ aux->name = name;
+ aux->dev.parent = dev;
+ aux->dev.release = ti_sn65dsi86_noop;
+ device_set_of_node_from_dev(&aux->dev, dev);
+ ret = auxiliary_device_init(aux);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, ti_sn65dsi86_uninit_aux, aux);
+ if (ret)
+ return ret;
+
+ ret = auxiliary_device_add(aux);
+ if (ret)
+ return ret;
+ ret = devm_add_action_or_reset(dev, ti_sn65dsi86_delete_aux, aux);
+
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * AUX Adapter
+ */
+
+static struct ti_sn65dsi86 *aux_to_ti_sn65dsi86(struct drm_dp_aux *aux)
+{
+ return container_of(aux, struct ti_sn65dsi86, aux);
+}
+
+static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux,
+ struct drm_dp_aux_msg *msg)
+{
+ struct ti_sn65dsi86 *pdata = aux_to_ti_sn65dsi86(aux);
+ u32 request = msg->request & ~(DP_AUX_I2C_MOT | DP_AUX_I2C_WRITE_STATUS_UPDATE);
+ u32 request_val = AUX_CMD_REQ(msg->request);
+ u8 *buf = msg->buffer;
+ unsigned int len = msg->size;
+ unsigned int val;
+ int ret;
+ u8 addr_len[SN_AUX_LENGTH_REG + 1 - SN_AUX_ADDR_19_16_REG];
+
+ if (len > SN_AUX_MAX_PAYLOAD_BYTES)
+ return -EINVAL;
+
+ pm_runtime_get_sync(pdata->dev);
+ mutex_lock(&pdata->comms_mutex);
+
+ /*
+ * If someone tries to do a DDC over AUX transaction before pre_enable()
+ * on a device without a dedicated reference clock then we just can't
+ * do it. Fail right away. This prevents non-refclk users from reading
+ * the EDID before enabling the panel but such is life.
+ */
+ if (!pdata->comms_enabled) {
+ ret = -EIO;
+ goto exit;
+ }
+
+ switch (request) {
+ case DP_AUX_NATIVE_WRITE:
+ case DP_AUX_I2C_WRITE:
+ 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:
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ 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)
+ 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,
+ AUX_IRQ_STATUS_NAT_I2C_FAIL |
+ AUX_IRQ_STATUS_AUX_RPLY_TOUT |
+ AUX_IRQ_STATUS_AUX_SHORT);
+
+ 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), 0, 50 * 1000);
+ if (ret)
+ goto exit;
+
+ ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val);
+ if (ret)
+ goto exit;
+
+ 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.
+ */
+ ret = -ETIMEDOUT;
+ goto exit;
}
- 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;
+ if (val & AUX_IRQ_STATUS_AUX_SHORT) {
+ ret = regmap_read(pdata->regmap, SN_AUX_LENGTH_REG, &len);
+ if (ret)
+ goto exit;
+ } 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;
}
+ len = 0;
+ goto exit;
}
- return drm_panel_get_modes(pdata->panel, connector);
+ if (request != DP_AUX_NATIVE_WRITE && request != DP_AUX_I2C_WRITE && len != 0)
+ ret = regmap_bulk_read(pdata->regmap, SN_AUX_RDATA_REG(0), buf, len);
+
+exit:
+ mutex_unlock(&pdata->comms_mutex);
+ pm_runtime_mark_last_busy(pdata->dev);
+ pm_runtime_put_autosuspend(pdata->dev);
+
+ if (ret)
+ return ret;
+ return len;
+}
+
+static int ti_sn_aux_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+ int ret;
+
+ pdata->aux.name = "ti-sn65dsi86-aux";
+ pdata->aux.dev = &adev->dev;
+ pdata->aux.transfer = ti_sn_aux_transfer;
+ drm_dp_aux_init(&pdata->aux);
+
+ ret = devm_of_dp_aux_populate_ep_devices(&pdata->aux);
+ if (ret)
+ return ret;
+
+ /*
+ * The eDP to MIPI bridge parts don't work until the AUX channel is
+ * setup so we don't add it in the main driver probe, we add it now.
+ */
+ return ti_sn65dsi86_add_aux_device(pdata, &pdata->bridge_aux, "bridge");
+}
+
+static const struct auxiliary_device_id ti_sn_aux_id_table[] = {
+ { .name = "ti_sn65dsi86.aux", },
+ {},
+};
+
+static struct auxiliary_driver ti_sn_aux_driver = {
+ .name = "aux",
+ .probe = ti_sn_aux_probe,
+ .id_table = ti_sn_aux_id_table,
+};
+
+/* -----------------------------------------------------------------------------
+ * DRM Connector Operations
+ */
+
+static struct ti_sn65dsi86 *
+connector_to_ti_sn65dsi86(struct drm_connector *connector)
+{
+ return container_of(connector, struct ti_sn65dsi86, connector);
+}
+
+static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
+{
+ struct ti_sn65dsi86 *pdata = connector_to_ti_sn65dsi86(connector);
+
+ return drm_bridge_get_modes(pdata->next_bridge, connector);
}
static enum drm_mode_status
@@ -448,23 +639,32 @@ static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
-static struct ti_sn65dsi86 *bridge_to_ti_sn65dsi86(struct drm_bridge *bridge)
+static int ti_sn_bridge_connector_init(struct ti_sn65dsi86 *pdata)
{
- return container_of(bridge, struct ti_sn65dsi86, bridge);
-}
+ int ret;
-static int ti_sn65dsi86_parse_regulators(struct ti_sn65dsi86 *pdata)
-{
- unsigned int i;
- const char * const ti_sn_bridge_supply_names[] = {
- "vcca", "vcc", "vccio", "vpll",
- };
+ ret = drm_connector_init(pdata->bridge.dev, &pdata->connector,
+ &ti_sn_bridge_connector_funcs,
+ DRM_MODE_CONNECTOR_eDP);
+ if (ret) {
+ DRM_ERROR("Failed to initialize connector with drm\n");
+ return ret;
+ }
- for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++)
- pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
+ drm_connector_helper_add(&pdata->connector,
+ &ti_sn_bridge_connector_helper_funcs);
+ drm_connector_attach_encoder(&pdata->connector, pdata->bridge.encoder);
- return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM,
- pdata->supplies);
+ return 0;
+}
+
+/*------------------------------------------------------------------------------
+ * DRM Bridge
+ */
+
+static struct ti_sn65dsi86 *bridge_to_ti_sn65dsi86(struct drm_bridge *bridge)
+{
+ return container_of(bridge, struct ti_sn65dsi86, bridge);
}
static int ti_sn_bridge_attach(struct drm_bridge *bridge,
@@ -491,17 +691,9 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
return ret;
}
- ret = drm_connector_init(bridge->dev, &pdata->connector,
- &ti_sn_bridge_connector_funcs,
- DRM_MODE_CONNECTOR_eDP);
- if (ret) {
- DRM_ERROR("Failed to initialize connector with drm\n");
+ ret = ti_sn_bridge_connector_init(pdata);
+ if (ret < 0)
goto err_conn_init;
- }
-
- drm_connector_helper_add(&pdata->connector,
- &ti_sn_bridge_connector_helper_funcs);
- drm_connector_attach_encoder(&pdata->connector, bridge->encoder);
/*
* TODO: ideally finding host resource and dsi dev registration needs
@@ -547,8 +739,19 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
}
pdata->dsi = dsi;
+ /* We never want the next bridge to *also* create a connector: */
+ flags |= DRM_BRIDGE_ATTACH_NO_CONNECTOR;
+
+ /* Attach the next bridge */
+ ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
+ &pdata->bridge, flags);
+ if (ret < 0)
+ goto err_dsi_detach;
+
return 0;
+err_dsi_detach:
+ mipi_dsi_detach(dsi);
err_dsi_attach:
mipi_dsi_device_unregister(dsi);
err_dsi_host:
@@ -567,14 +770,8 @@ static void ti_sn_bridge_disable(struct drm_bridge *bridge)
{
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
- drm_panel_disable(pdata->panel);
-
/* disable video stream */
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, 0);
- /* semi auto link training mode OFF */
- regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0);
- /* disable DP PLL */
- regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0);
}
static void ti_sn_bridge_set_dsi_rate(struct ti_sn65dsi86 *pdata)
@@ -633,9 +830,9 @@ static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn65dsi86 *pdata)
return i;
}
-static void ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata,
- bool rate_valid[])
+static unsigned int ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata)
{
+ unsigned int valid_rates = 0;
unsigned int rate_per_200khz;
unsigned int rate_mhz;
u8 dpcd_val;
@@ -675,13 +872,13 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata,
j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
j++) {
if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
- rate_valid[j] = true;
+ valid_rates |= BIT(j);
}
}
for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
- if (rate_valid[i])
- return;
+ if (valid_rates & BIT(i))
+ return valid_rates;
}
DRM_DEV_ERROR(pdata->dev,
"No matching eDP rates in table; falling back\n");
@@ -703,15 +900,17 @@ static void ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata,
(int)dpcd_val);
fallthrough;
case DP_LINK_BW_5_4:
- rate_valid[7] = 1;
+ valid_rates |= BIT(7);
fallthrough;
case DP_LINK_BW_2_7:
- rate_valid[4] = 1;
+ valid_rates |= BIT(4);
fallthrough;
case DP_LINK_BW_1_62:
- rate_valid[1] = 1;
+ valid_rates |= BIT(1);
break;
}
+
+ return valid_rates;
}
static void ti_sn_bridge_set_video_timings(struct ti_sn65dsi86 *pdata)
@@ -829,8 +1028,8 @@ exit:
static void ti_sn_bridge_enable(struct drm_bridge *bridge)
{
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
- bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { };
const char *last_err_str = "No supported DP rate";
+ unsigned int valid_rates;
int dp_rate_idx;
unsigned int val;
int ret = -EINVAL;
@@ -869,13 +1068,13 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
val);
- ti_sn_bridge_read_valid_rates(pdata, rate_valid);
+ valid_rates = ti_sn_bridge_read_valid_rates(pdata);
/* Train until we run out of rates */
for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata);
dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
dp_rate_idx++) {
- if (!rate_valid[dp_rate_idx])
+ if (!(valid_rates & BIT(dp_rate_idx)))
continue;
ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
@@ -893,8 +1092,6 @@ static void ti_sn_bridge_enable(struct drm_bridge *bridge)
/* enable video stream */
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE,
VSTREAM_ENABLE);
-
- drm_panel_enable(pdata->panel);
}
static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge)
@@ -906,14 +1103,20 @@ static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge)
if (!pdata->refclk)
ti_sn65dsi86_enable_comms(pdata);
- drm_panel_prepare(pdata->panel);
+ /* td7: min 100 us after enable before DSI data */
+ usleep_range(100, 110);
}
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge)
{
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
- drm_panel_unprepare(pdata->panel);
+ /* semi auto link training mode OFF */
+ regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0);
+ /* Num lanes to 0 as per power sequencing in data sheet */
+ regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK, 0);
+ /* disable DP PLL */
+ regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0);
if (!pdata->refclk)
ti_sn65dsi86_disable_comms(pdata);
@@ -930,137 +1133,135 @@ static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
.post_disable = ti_sn_bridge_post_disable,
};
-static struct ti_sn65dsi86 *aux_to_ti_sn65dsi86(struct drm_dp_aux *aux)
-{
- return container_of(aux, struct ti_sn65dsi86, aux);
-}
-
-static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux,
- struct drm_dp_aux_msg *msg)
+static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata,
+ struct device_node *np)
{
- struct ti_sn65dsi86 *pdata = aux_to_ti_sn65dsi86(aux);
- u32 request = msg->request & ~(DP_AUX_I2C_MOT | DP_AUX_I2C_WRITE_STATUS_UPDATE);
- u32 request_val = AUX_CMD_REQ(msg->request);
- u8 *buf = msg->buffer;
- unsigned int len = msg->size;
- unsigned int val;
- int ret;
- u8 addr_len[SN_AUX_LENGTH_REG + 1 - SN_AUX_ADDR_19_16_REG];
-
- if (len > SN_AUX_MAX_PAYLOAD_BYTES)
- return -EINVAL;
-
- pm_runtime_get_sync(pdata->dev);
- mutex_lock(&pdata->comms_mutex);
+ u32 lane_assignments[SN_MAX_DP_LANES] = { 0, 1, 2, 3 };
+ u32 lane_polarities[SN_MAX_DP_LANES] = { };
+ struct device_node *endpoint;
+ u8 ln_assign = 0;
+ u8 ln_polrs = 0;
+ int dp_lanes;
+ int i;
/*
- * If someone tries to do a DDC over AUX transaction before pre_enable()
- * on a device without a dedicated reference clock then we just can't
- * do it. Fail right away. This prevents non-refclk users from reading
- * the EDID before enabling the panel but such is life.
+ * Read config from the device tree about lane remapping and lane
+ * polarities. These are optional and we assume identity map and
+ * normal polarity if nothing is specified. It's OK to specify just
+ * data-lanes but not lane-polarities but not vice versa.
+ *
+ * Error checking is light (we just make sure we don't crash or
+ * buffer overrun) and we assume dts is well formed and specifying
+ * mappings that the hardware supports.
*/
- if (!pdata->comms_enabled) {
- ret = -EIO;
- goto exit;
+ endpoint = of_graph_get_endpoint_by_regs(np, 1, -1);
+ dp_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
+ if (dp_lanes > 0 && dp_lanes <= SN_MAX_DP_LANES) {
+ of_property_read_u32_array(endpoint, "data-lanes",
+ lane_assignments, dp_lanes);
+ of_property_read_u32_array(endpoint, "lane-polarities",
+ lane_polarities, dp_lanes);
+ } else {
+ dp_lanes = SN_MAX_DP_LANES;
}
+ of_node_put(endpoint);
- switch (request) {
- case DP_AUX_NATIVE_WRITE:
- case DP_AUX_I2C_WRITE:
- 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:
- ret = -EINVAL;
- goto exit;
+ /*
+ * Convert into register format. Loop over all lanes even if
+ * data-lanes had fewer elements so that we nicely initialize
+ * the LN_ASSIGN register.
+ */
+ for (i = SN_MAX_DP_LANES - 1; i >= 0; i--) {
+ ln_assign = ln_assign << LN_ASSIGN_WIDTH | lane_assignments[i];
+ ln_polrs = ln_polrs << 1 | lane_polarities[i];
}
- 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));
+ /* Stash in our struct for when we power on */
+ pdata->dp_lanes = dp_lanes;
+ pdata->ln_assign = ln_assign;
+ pdata->ln_polrs = ln_polrs;
+}
- if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE)
- regmap_bulk_write(pdata->regmap, SN_AUX_WDATA_REG(0), buf, len);
+static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)
+{
+ struct device_node *np = pdata->dev->of_node;
- /* Clear old status bits before start so we don't get confused */
- regmap_write(pdata->regmap, SN_AUX_CMD_STATUS_REG,
- AUX_IRQ_STATUS_NAT_I2C_FAIL |
- AUX_IRQ_STATUS_AUX_RPLY_TOUT |
- AUX_IRQ_STATUS_AUX_SHORT);
+ pdata->host_node = of_graph_get_remote_node(np, 0, 0);
- regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val | AUX_CMD_SEND);
+ if (!pdata->host_node) {
+ DRM_ERROR("remote dsi host node not found\n");
+ return -ENODEV;
+ }
- /* 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), 0, 50 * 1000);
- if (ret)
- goto exit;
+ return 0;
+}
- ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val);
- if (ret)
- goto exit;
+static int ti_sn_bridge_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+ struct device_node *np = pdata->dev->of_node;
+ struct drm_panel *panel;
+ int ret;
- 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.
- */
- ret = -ETIMEDOUT;
- goto exit;
- }
+ ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
+ if (ret)
+ return dev_err_probe(&adev->dev, ret,
+ "could not find any panel node\n");
- if (val & AUX_IRQ_STATUS_AUX_SHORT) {
- ret = regmap_read(pdata->regmap, SN_AUX_LENGTH_REG, &len);
- if (ret)
- goto exit;
- } 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;
- }
- len = 0;
- goto exit;
+ pdata->next_bridge = devm_drm_panel_bridge_add(pdata->dev, panel);
+ if (IS_ERR(pdata->next_bridge)) {
+ DRM_ERROR("failed to create panel bridge\n");
+ return PTR_ERR(pdata->next_bridge);
}
- if (request != DP_AUX_NATIVE_WRITE && request != DP_AUX_I2C_WRITE && len != 0)
- ret = regmap_bulk_read(pdata->regmap, SN_AUX_RDATA_REG(0), buf, len);
-
-exit:
- mutex_unlock(&pdata->comms_mutex);
- pm_runtime_mark_last_busy(pdata->dev);
- pm_runtime_put_autosuspend(pdata->dev);
+ ti_sn_bridge_parse_lanes(pdata, np);
+ ret = ti_sn_bridge_parse_dsi_host(pdata);
if (ret)
return ret;
- return len;
+
+ pdata->bridge.funcs = &ti_sn_bridge_funcs;
+ pdata->bridge.of_node = np;
+
+ drm_bridge_add(&pdata->bridge);
+
+ return 0;
}
-static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)
+static void ti_sn_bridge_remove(struct auxiliary_device *adev)
{
- struct device_node *np = pdata->dev->of_node;
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
- pdata->host_node = of_graph_get_remote_node(np, 0, 0);
+ if (!pdata)
+ return;
- if (!pdata->host_node) {
- DRM_ERROR("remote dsi host node not found\n");
- return -ENODEV;
+ if (pdata->dsi) {
+ mipi_dsi_detach(pdata->dsi);
+ mipi_dsi_device_unregister(pdata->dsi);
}
- return 0;
+ drm_bridge_remove(&pdata->bridge);
+
+ of_node_put(pdata->host_node);
}
+static const struct auxiliary_device_id ti_sn_bridge_id_table[] = {
+ { .name = "ti_sn65dsi86.bridge", },
+ {},
+};
+
+static struct auxiliary_driver ti_sn_bridge_driver = {
+ .name = "bridge",
+ .probe = ti_sn_bridge_probe,
+ .remove = ti_sn_bridge_remove,
+ .id_table = ti_sn_bridge_id_table,
+};
+
+/* -----------------------------------------------------------------------------
+ * GPIO Controller
+ */
+
#if defined(CONFIG_OF_GPIO)
static int tn_sn_bridge_of_xlate(struct gpio_chip *chip,
@@ -1265,171 +1466,27 @@ static inline void ti_sn_gpio_unregister(void) {}
#endif
-static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata,
- struct device_node *np)
-{
- u32 lane_assignments[SN_MAX_DP_LANES] = { 0, 1, 2, 3 };
- u32 lane_polarities[SN_MAX_DP_LANES] = { };
- struct device_node *endpoint;
- u8 ln_assign = 0;
- u8 ln_polrs = 0;
- int dp_lanes;
- int i;
-
- /*
- * Read config from the device tree about lane remapping and lane
- * polarities. These are optional and we assume identity map and
- * normal polarity if nothing is specified. It's OK to specify just
- * data-lanes but not lane-polarities but not vice versa.
- *
- * Error checking is light (we just make sure we don't crash or
- * buffer overrun) and we assume dts is well formed and specifying
- * mappings that the hardware supports.
- */
- endpoint = of_graph_get_endpoint_by_regs(np, 1, -1);
- dp_lanes = of_property_count_u32_elems(endpoint, "data-lanes");
- if (dp_lanes > 0 && dp_lanes <= SN_MAX_DP_LANES) {
- of_property_read_u32_array(endpoint, "data-lanes",
- lane_assignments, dp_lanes);
- of_property_read_u32_array(endpoint, "lane-polarities",
- lane_polarities, dp_lanes);
- } else {
- dp_lanes = SN_MAX_DP_LANES;
- }
- of_node_put(endpoint);
-
- /*
- * Convert into register format. Loop over all lanes even if
- * data-lanes had fewer elements so that we nicely initialize
- * the LN_ASSIGN register.
- */
- for (i = SN_MAX_DP_LANES - 1; i >= 0; i--) {
- ln_assign = ln_assign << LN_ASSIGN_WIDTH | lane_assignments[i];
- ln_polrs = ln_polrs << 1 | lane_polarities[i];
- }
-
- /* Stash in our struct for when we power on */
- pdata->dp_lanes = dp_lanes;
- pdata->ln_assign = ln_assign;
- pdata->ln_polrs = ln_polrs;
-}
-
-static int ti_sn_bridge_probe(struct auxiliary_device *adev,
- const struct auxiliary_device_id *id)
-{
- struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
- struct device_node *np = pdata->dev->of_node;
- int ret;
-
- ret = drm_of_find_panel_or_bridge(np, 1, 0, &pdata->panel, NULL);
- if (ret) {
- DRM_ERROR("could not find any panel node\n");
- return ret;
- }
-
- ti_sn_bridge_parse_lanes(pdata, np);
-
- ret = ti_sn_bridge_parse_dsi_host(pdata);
- if (ret)
- return ret;
-
- pdata->aux.name = "ti-sn65dsi86-aux";
- pdata->aux.dev = pdata->dev;
- pdata->aux.transfer = ti_sn_aux_transfer;
- drm_dp_aux_init(&pdata->aux);
-
- pdata->bridge.funcs = &ti_sn_bridge_funcs;
- pdata->bridge.of_node = np;
-
- drm_bridge_add(&pdata->bridge);
-
- return 0;
-}
-
-static void ti_sn_bridge_remove(struct auxiliary_device *adev)
-{
- struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
-
- if (!pdata)
- return;
-
- if (pdata->dsi) {
- mipi_dsi_detach(pdata->dsi);
- mipi_dsi_device_unregister(pdata->dsi);
- }
-
- kfree(pdata->edid);
-
- drm_bridge_remove(&pdata->bridge);
-
- of_node_put(pdata->host_node);
-}
-
-static const struct auxiliary_device_id ti_sn_bridge_id_table[] = {
- { .name = "ti_sn65dsi86.bridge", },
- {},
-};
-
-static struct auxiliary_driver ti_sn_bridge_driver = {
- .name = "bridge",
- .probe = ti_sn_bridge_probe,
- .remove = ti_sn_bridge_remove,
- .id_table = ti_sn_bridge_id_table,
-};
+/* -----------------------------------------------------------------------------
+ * Probe & Remove
+ */
static void ti_sn65dsi86_runtime_disable(void *data)
{
pm_runtime_disable(data);
}
-static void ti_sn65dsi86_uninit_aux(void *data)
-{
- auxiliary_device_uninit(data);
-}
-
-static void ti_sn65dsi86_delete_aux(void *data)
-{
- auxiliary_device_delete(data);
-}
-
-/*
- * AUX bus docs say that a non-NULL release is mandatory, but it makes no
- * sense for the model used here where all of the aux devices are allocated
- * in the single shared structure. We'll use this noop as a workaround.
- */
-static void ti_sn65dsi86_noop(struct device *dev) {}
-
-static int ti_sn65dsi86_add_aux_device(struct ti_sn65dsi86 *pdata,
- struct auxiliary_device *aux,
- const char *name)
+static int ti_sn65dsi86_parse_regulators(struct ti_sn65dsi86 *pdata)
{
- struct device *dev = pdata->dev;
- int ret;
-
- /*
- * NOTE: It would be nice to set the "of_node" of our children to be
- * the same "of_node"" that the top-level component has. That doesn't
- * work, though, since pinctrl will try (and fail) to reserve the
- * pins again. Until that gets sorted out the children will just need
- * to look at the of_node of the main device.
- */
-
- aux->name = name;
- aux->dev.parent = dev;
- aux->dev.release = ti_sn65dsi86_noop;
- ret = auxiliary_device_init(aux);
- if (ret)
- return ret;
- ret = devm_add_action_or_reset(dev, ti_sn65dsi86_uninit_aux, aux);
- if (ret)
- return ret;
+ unsigned int i;
+ const char * const ti_sn_bridge_supply_names[] = {
+ "vcca", "vcc", "vccio", "vpll",
+ };
- ret = auxiliary_device_add(aux);
- if (ret)
- return ret;
- ret = devm_add_action_or_reset(dev, ti_sn65dsi86_delete_aux, aux);
+ for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++)
+ pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
- return ret;
+ return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM,
+ pdata->supplies);
}
static int ti_sn65dsi86_probe(struct i2c_client *client,
@@ -1454,27 +1511,24 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
pdata->regmap = devm_regmap_init_i2c(client,
&ti_sn65dsi86_regmap_config);
- if (IS_ERR(pdata->regmap)) {
- DRM_ERROR("regmap i2c init failed\n");
- return PTR_ERR(pdata->regmap);
- }
+ if (IS_ERR(pdata->regmap))
+ return dev_err_probe(dev, PTR_ERR(pdata->regmap),
+ "regmap i2c init failed\n");
- pdata->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
- if (IS_ERR(pdata->enable_gpio)) {
- DRM_ERROR("failed to get enable gpio from DT\n");
- ret = PTR_ERR(pdata->enable_gpio);
- return ret;
- }
+ pdata->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(pdata->enable_gpio))
+ return dev_err_probe(dev, PTR_ERR(pdata->enable_gpio),
+ "failed to get enable gpio from DT\n");
ret = ti_sn65dsi86_parse_regulators(pdata);
- if (ret) {
- DRM_ERROR("failed to parse regulators\n");
- return ret;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to parse regulators\n");
pdata->refclk = devm_clk_get_optional(dev, "refclk");
if (IS_ERR(pdata->refclk))
- return PTR_ERR(pdata->refclk);
+ return dev_err_probe(dev, PTR_ERR(pdata->refclk),
+ "failed to get reference clock\n");
pm_runtime_enable(dev);
ret = devm_add_action_or_reset(dev, ti_sn65dsi86_runtime_disable, dev);
@@ -1490,10 +1544,11 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
* motiviation here is to solve the chicken-and-egg problem of probe
* ordering. The bridge wants the panel to be there when it probes.
* The panel wants its HPD GPIO (provided by sn65dsi86 on some boards)
- * when it probes. There will soon be other devices (DDC I2C bus, PWM)
- * that have the same problem. Having sub-devices allows the some sub
- * devices to finish probing even if others return -EPROBE_DEFER and
- * gets us around the problems.
+ * when it probes. The panel and maybe backlight might want the DDC
+ * bus. Soon the PWM provided by the bridge chip will have the same
+ * problem. Having sub-devices allows the some sub devices to finish
+ * probing even if others return -EPROBE_DEFER and gets us around the
+ * problems.
*/
if (IS_ENABLED(CONFIG_OF_GPIO)) {
@@ -1502,7 +1557,13 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
return ret;
}
- return ti_sn65dsi86_add_aux_device(pdata, &pdata->bridge_aux, "bridge");
+ /*
+ * NOTE: At the end of the AUX channel probe we'll add the aux device
+ * for the bridge. This is because the bridge can't be used until the
+ * AUX channel is there and this is a very simple solution to the
+ * dependency problem.
+ */
+ return ti_sn65dsi86_add_aux_device(pdata, &pdata->aux_aux, "aux");
}
static struct i2c_device_id ti_sn65dsi86_id[] = {
@@ -1539,12 +1600,18 @@ static int __init ti_sn65dsi86_init(void)
if (ret)
goto err_main_was_registered;
- ret = auxiliary_driver_register(&ti_sn_bridge_driver);
+ ret = auxiliary_driver_register(&ti_sn_aux_driver);
if (ret)
goto err_gpio_was_registered;
+ ret = auxiliary_driver_register(&ti_sn_bridge_driver);
+ if (ret)
+ goto err_aux_was_registered;
+
return 0;
+err_aux_was_registered:
+ auxiliary_driver_unregister(&ti_sn_aux_driver);
err_gpio_was_registered:
ti_sn_gpio_unregister();
err_main_was_registered:
@@ -1557,6 +1624,7 @@ module_init(ti_sn65dsi86_init);
static void __exit ti_sn65dsi86_exit(void)
{
auxiliary_driver_unregister(&ti_sn_bridge_driver);
+ auxiliary_driver_unregister(&ti_sn_aux_driver);
ti_sn_gpio_unregister();
i2c_del_driver(&ti_sn65dsi86_driver);
}