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.c475
1 files changed, 408 insertions, 67 deletions
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi86.c b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
index 6154bed0af5b..dab8f76618f3 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi86.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi86.c
@@ -4,7 +4,9 @@
* datasheet: https://www.ti.com/lit/ds/symlink/sn65dsi86.pdf
*/
+#include <linux/atomic.h>
#include <linux/auxiliary_bus.h>
+#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/clk.h>
#include <linux/debugfs.h>
@@ -15,6 +17,7 @@
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/pm_runtime.h>
+#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -91,6 +94,13 @@
#define SN_ML_TX_MODE_REG 0x96
#define ML_TX_MAIN_LINK_OFF 0
#define ML_TX_NORMAL_MODE BIT(0)
+#define SN_PWM_PRE_DIV_REG 0xA0
+#define SN_BACKLIGHT_SCALE_REG 0xA1
+#define BACKLIGHT_SCALE_MAX 0xFFFF
+#define SN_BACKLIGHT_REG 0xA3
+#define SN_PWM_EN_INV_REG 0xA5
+#define SN_PWM_INV_MASK BIT(0)
+#define SN_PWM_EN_MASK BIT(1)
#define SN_AUX_CMD_STATUS_REG 0xF4
#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
#define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
@@ -113,11 +123,14 @@
#define SN_LINK_TRAINING_TRIES 10
+#define SN_PWM_GPIO_IDX 3 /* 4th GPIO */
+
/**
* 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.
+ * @pwm_aux: AUX-bus sub device for PWM controller functionality.
*
* @dev: Pointer to the top level (i2c) device.
* @regmap: Regmap for accessing i2c.
@@ -145,11 +158,17 @@
* bitmap so we can do atomic ops on it without an extra
* lock so concurrent users of our 4 GPIOs don't stomp on
* each other's read-modify-write.
+ *
+ * @pchip: pwm_chip if the PWM is exposed.
+ * @pwm_enabled: Used to track if the PWM signal is currently enabled.
+ * @pwm_pin_busy: Track if GPIO4 is currently requested for GPIO or PWM.
+ * @pwm_refclk_freq: Cache for the reference clock input to the PWM.
*/
struct ti_sn65dsi86 {
struct auxiliary_device bridge_aux;
struct auxiliary_device gpio_aux;
struct auxiliary_device aux_aux;
+ struct auxiliary_device pwm_aux;
struct device *dev;
struct regmap *regmap;
@@ -172,6 +191,12 @@ struct ti_sn65dsi86 {
struct gpio_chip gchip;
DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS);
#endif
+#if defined(CONFIG_PWM)
+ struct pwm_chip pchip;
+ bool pwm_enabled;
+ atomic_t pwm_pin_busy;
+#endif
+ unsigned int pwm_refclk_freq;
};
static const struct regmap_range ti_sn65dsi86_volatile_ranges[] = {
@@ -188,13 +213,30 @@ static const struct regmap_config ti_sn65dsi86_regmap_config = {
.val_bits = 8,
.volatile_table = &ti_sn_bridge_volatile_table,
.cache_type = REGCACHE_NONE,
+ .max_register = 0xFF,
};
+static int __maybe_unused ti_sn65dsi86_read_u16(struct ti_sn65dsi86 *pdata,
+ unsigned int reg, u16 *val)
+{
+ u8 buf[2];
+ int ret;
+
+ ret = regmap_bulk_read(pdata->regmap, reg, buf, ARRAY_SIZE(buf));
+ if (ret)
+ return ret;
+
+ *val = buf[0] | (buf[1] << 8);
+
+ return 0;
+}
+
static void ti_sn65dsi86_write_u16(struct ti_sn65dsi86 *pdata,
unsigned int reg, u16 val)
{
- regmap_write(pdata->regmap, reg, val & 0xFF);
- regmap_write(pdata->regmap, reg + 1, val >> 8);
+ u8 buf[2] = { val & 0xff, val >> 8 };
+
+ regmap_bulk_write(pdata->regmap, reg, buf, ARRAY_SIZE(buf));
}
static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn65dsi86 *pdata)
@@ -253,6 +295,12 @@ static void ti_sn_bridge_set_refclk_freq(struct ti_sn65dsi86 *pdata)
regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK,
REFCLK_FREQ(i));
+
+ /*
+ * The PWM refclk is based on the value written to SN_DPPLL_SRC_REG,
+ * regardless of its actual sourcing.
+ */
+ pdata->pwm_refclk_freq = ti_sn_bridge_refclk_lut[i];
}
static void ti_sn65dsi86_enable_comms(struct ti_sn65dsi86 *pdata)
@@ -655,58 +703,24 @@ 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,
- enum drm_bridge_attach_flags flags)
+static int ti_sn_attach_host(struct ti_sn65dsi86 *pdata)
{
- int ret, val;
- struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
+ int val;
struct mipi_dsi_host *host;
struct mipi_dsi_device *dsi;
+ struct device *dev = pdata->dev;
const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
.channel = 0,
.node = NULL,
- };
-
- if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
- DRM_ERROR("Fix bridge driver to make connector optional!");
- return -EINVAL;
- }
-
- pdata->aux.drm_dev = bridge->dev;
- ret = drm_dp_aux_register(&pdata->aux);
- if (ret < 0) {
- drm_err(bridge->dev, "Failed to register DP AUX channel: %d\n", ret);
- return ret;
- }
-
- ret = ti_sn_bridge_connector_init(pdata);
- if (ret < 0)
- goto err_conn_init;
+ };
- /*
- * TODO: ideally finding host resource and dsi dev registration needs
- * to be done in bridge probe. But some existing DSI host drivers will
- * wait for any of the drm_bridge/drm_panel to get added to the global
- * bridge/panel list, before completing their probe. So if we do the
- * dsi dev registration part in bridge probe, before populating in
- * the global bridge list, then it will cause deadlock as dsi host probe
- * will never complete, neither our bridge probe. So keeping it here
- * will satisfy most of the existing host drivers. Once the host driver
- * is fixed we can move the below code to bridge probe safely.
- */
host = of_find_mipi_dsi_host_by_node(pdata->host_node);
- if (!host) {
- DRM_ERROR("failed to find dsi host\n");
- ret = -ENODEV;
- goto err_dsi_host;
- }
+ if (!host)
+ return -EPROBE_DEFER;
- dsi = mipi_dsi_device_register_full(host, &info);
- if (IS_ERR(dsi)) {
- DRM_ERROR("failed to create dsi device\n");
- ret = PTR_ERR(dsi);
- goto err_dsi_host;
- }
+ dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
+ if (IS_ERR(dsi))
+ return PTR_ERR(dsi);
/* TODO: setting to 4 MIPI lanes always for now */
dsi->lanes = 4;
@@ -714,18 +728,38 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
/* check if continuous dsi clock is required or not */
- pm_runtime_get_sync(pdata->dev);
+ pm_runtime_get_sync(dev);
regmap_read(pdata->regmap, SN_DPPLL_SRC_REG, &val);
- pm_runtime_put_autosuspend(pdata->dev);
+ pm_runtime_put_autosuspend(dev);
if (!(val & DPPLL_CLK_SRC_DSICLK))
dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
- ret = mipi_dsi_attach(dsi);
+ pdata->dsi = dsi;
+
+ return devm_mipi_dsi_attach(dev, dsi);
+}
+
+static int ti_sn_bridge_attach(struct drm_bridge *bridge,
+ enum drm_bridge_attach_flags flags)
+{
+ struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
+ int ret;
+
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
+ pdata->aux.drm_dev = bridge->dev;
+ ret = drm_dp_aux_register(&pdata->aux);
if (ret < 0) {
- DRM_ERROR("failed to attach dsi to host\n");
- goto err_dsi_attach;
+ drm_err(bridge->dev, "Failed to register DP AUX channel: %d\n", ret);
+ return ret;
}
- pdata->dsi = dsi;
+
+ ret = ti_sn_bridge_connector_init(pdata);
+ if (ret < 0)
+ goto err_conn_init;
/* We never want the next bridge to *also* create a connector: */
flags |= DRM_BRIDGE_ATTACH_NO_CONNECTOR;
@@ -734,14 +768,10 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
&pdata->bridge, flags);
if (ret < 0)
- goto err_dsi_detach;
+ goto err_dsi_host;
return 0;
-err_dsi_detach:
- mipi_dsi_detach(dsi);
-err_dsi_attach:
- mipi_dsi_device_unregister(dsi);
err_dsi_host:
drm_connector_cleanup(&pdata->connector);
err_conn_init:
@@ -1227,7 +1257,17 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
drm_bridge_add(&pdata->bridge);
+ ret = ti_sn_attach_host(pdata);
+ if (ret) {
+ dev_err_probe(pdata->dev, ret, "failed to attach dsi host\n");
+ goto err_remove_bridge;
+ }
+
return 0;
+
+err_remove_bridge:
+ drm_bridge_remove(&pdata->bridge);
+ return ret;
}
static void ti_sn_bridge_remove(struct auxiliary_device *adev)
@@ -1237,11 +1277,6 @@ static void ti_sn_bridge_remove(struct auxiliary_device *adev)
if (!pdata)
return;
- if (pdata->dsi) {
- mipi_dsi_detach(pdata->dsi);
- mipi_dsi_device_unregister(pdata->dsi);
- }
-
drm_bridge_remove(&pdata->bridge);
of_node_put(pdata->host_node);
@@ -1260,9 +1295,287 @@ static struct auxiliary_driver ti_sn_bridge_driver = {
};
/* -----------------------------------------------------------------------------
- * GPIO Controller
+ * PWM Controller
*/
+#if defined(CONFIG_PWM)
+static int ti_sn_pwm_pin_request(struct ti_sn65dsi86 *pdata)
+{
+ return atomic_xchg(&pdata->pwm_pin_busy, 1) ? -EBUSY : 0;
+}
+
+static void ti_sn_pwm_pin_release(struct ti_sn65dsi86 *pdata)
+{
+ atomic_set(&pdata->pwm_pin_busy, 0);
+}
+
+static struct ti_sn65dsi86 *pwm_chip_to_ti_sn_bridge(struct pwm_chip *chip)
+{
+ return container_of(chip, struct ti_sn65dsi86, pchip);
+}
+
+static int ti_sn_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+
+ return ti_sn_pwm_pin_request(pdata);
+}
+
+static void ti_sn_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+
+ ti_sn_pwm_pin_release(pdata);
+}
+
+/*
+ * Limitations:
+ * - The PWM signal is not driven when the chip is powered down, or in its
+ * reset state and the driver does not implement the "suspend state"
+ * described in the documentation. In order to save power, state->enabled is
+ * interpreted as denoting if the signal is expected to be valid, and is used
+ * to determine if the chip needs to be kept powered.
+ * - Changing both period and duty_cycle is not done atomically, neither is the
+ * multi-byte register updates, so the output might briefly be undefined
+ * during update.
+ */
+static int ti_sn_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+ unsigned int pwm_en_inv;
+ unsigned int backlight;
+ unsigned int pre_div;
+ unsigned int scale;
+ u64 period_max;
+ u64 period;
+ int ret;
+
+ if (!pdata->pwm_enabled) {
+ ret = pm_runtime_get_sync(pdata->dev);
+ if (ret < 0) {
+ pm_runtime_put_sync(pdata->dev);
+ return ret;
+ }
+ }
+
+ if (state->enabled) {
+ if (!pdata->pwm_enabled) {
+ /*
+ * The chip might have been powered down while we
+ * didn't hold a PM runtime reference, so mux in the
+ * PWM function on the GPIO pin again.
+ */
+ ret = regmap_update_bits(pdata->regmap, SN_GPIO_CTRL_REG,
+ SN_GPIO_MUX_MASK << (2 * SN_PWM_GPIO_IDX),
+ SN_GPIO_MUX_SPECIAL << (2 * SN_PWM_GPIO_IDX));
+ if (ret) {
+ dev_err(pdata->dev, "failed to mux in PWM function\n");
+ goto out;
+ }
+ }
+
+ /*
+ * Per the datasheet the PWM frequency is given by:
+ *
+ * REFCLK_FREQ
+ * PWM_FREQ = -----------------------------------
+ * PWM_PRE_DIV * BACKLIGHT_SCALE + 1
+ *
+ * However, after careful review the author is convinced that
+ * the documentation has lost some parenthesis around
+ * "BACKLIGHT_SCALE + 1".
+ *
+ * With the period T_pwm = 1/PWM_FREQ this can be written:
+ *
+ * T_pwm * REFCLK_FREQ = PWM_PRE_DIV * (BACKLIGHT_SCALE + 1)
+ *
+ * In order to keep BACKLIGHT_SCALE within its 16 bits,
+ * PWM_PRE_DIV must be:
+ *
+ * T_pwm * REFCLK_FREQ
+ * PWM_PRE_DIV >= -------------------------
+ * BACKLIGHT_SCALE_MAX + 1
+ *
+ * To simplify the search and to favour higher resolution of
+ * the duty cycle over accuracy of the period, the lowest
+ * possible PWM_PRE_DIV is used. Finally the scale is
+ * calculated as:
+ *
+ * T_pwm * REFCLK_FREQ
+ * BACKLIGHT_SCALE = ---------------------- - 1
+ * PWM_PRE_DIV
+ *
+ * Here T_pwm is represented in seconds, so appropriate scaling
+ * to nanoseconds is necessary.
+ */
+
+ /* Minimum T_pwm is 1 / REFCLK_FREQ */
+ if (state->period <= NSEC_PER_SEC / pdata->pwm_refclk_freq) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Maximum T_pwm is 255 * (65535 + 1) / REFCLK_FREQ
+ * Limit period to this to avoid overflows
+ */
+ period_max = div_u64((u64)NSEC_PER_SEC * 255 * (65535 + 1),
+ pdata->pwm_refclk_freq);
+ period = min(state->period, period_max);
+
+ pre_div = DIV64_U64_ROUND_UP(period * pdata->pwm_refclk_freq,
+ (u64)NSEC_PER_SEC * (BACKLIGHT_SCALE_MAX + 1));
+ scale = div64_u64(period * pdata->pwm_refclk_freq, (u64)NSEC_PER_SEC * pre_div) - 1;
+
+ /*
+ * The documentation has the duty ratio given as:
+ *
+ * duty BACKLIGHT
+ * ------- = ---------------------
+ * period BACKLIGHT_SCALE + 1
+ *
+ * Solve for BACKLIGHT, substituting BACKLIGHT_SCALE according
+ * to definition above and adjusting for nanosecond
+ * representation of duty cycle gives us:
+ */
+ backlight = div64_u64(state->duty_cycle * pdata->pwm_refclk_freq,
+ (u64)NSEC_PER_SEC * pre_div);
+ if (backlight > scale)
+ backlight = scale;
+
+ ret = regmap_write(pdata->regmap, SN_PWM_PRE_DIV_REG, pre_div);
+ if (ret) {
+ dev_err(pdata->dev, "failed to update PWM_PRE_DIV\n");
+ goto out;
+ }
+
+ ti_sn65dsi86_write_u16(pdata, SN_BACKLIGHT_SCALE_REG, scale);
+ ti_sn65dsi86_write_u16(pdata, SN_BACKLIGHT_REG, backlight);
+ }
+
+ pwm_en_inv = FIELD_PREP(SN_PWM_EN_MASK, state->enabled) |
+ FIELD_PREP(SN_PWM_INV_MASK, state->polarity == PWM_POLARITY_INVERSED);
+ ret = regmap_write(pdata->regmap, SN_PWM_EN_INV_REG, pwm_en_inv);
+ if (ret) {
+ dev_err(pdata->dev, "failed to update PWM_EN/PWM_INV\n");
+ goto out;
+ }
+
+ pdata->pwm_enabled = state->enabled;
+out:
+
+ if (!pdata->pwm_enabled)
+ pm_runtime_put_sync(pdata->dev);
+
+ return ret;
+}
+
+static void ti_sn_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
+ unsigned int pwm_en_inv;
+ unsigned int pre_div;
+ u16 backlight;
+ u16 scale;
+ int ret;
+
+ ret = regmap_read(pdata->regmap, SN_PWM_EN_INV_REG, &pwm_en_inv);
+ if (ret)
+ return;
+
+ ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_SCALE_REG, &scale);
+ if (ret)
+ return;
+
+ ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_REG, &backlight);
+ if (ret)
+ return;
+
+ ret = regmap_read(pdata->regmap, SN_PWM_PRE_DIV_REG, &pre_div);
+ if (ret)
+ return;
+
+ state->enabled = FIELD_GET(SN_PWM_EN_MASK, pwm_en_inv);
+ if (FIELD_GET(SN_PWM_INV_MASK, pwm_en_inv))
+ state->polarity = PWM_POLARITY_INVERSED;
+ else
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pre_div * (scale + 1),
+ pdata->pwm_refclk_freq);
+ state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pre_div * backlight,
+ pdata->pwm_refclk_freq);
+
+ if (state->duty_cycle > state->period)
+ state->duty_cycle = state->period;
+}
+static const struct pwm_ops ti_sn_pwm_ops = {
+ .request = ti_sn_pwm_request,
+ .free = ti_sn_pwm_free,
+ .apply = ti_sn_pwm_apply,
+ .get_state = ti_sn_pwm_get_state,
+ .owner = THIS_MODULE,
+};
+
+static int ti_sn_pwm_probe(struct auxiliary_device *adev,
+ const struct auxiliary_device_id *id)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+
+ pdata->pchip.dev = pdata->dev;
+ pdata->pchip.ops = &ti_sn_pwm_ops;
+ pdata->pchip.npwm = 1;
+ pdata->pchip.of_xlate = of_pwm_single_xlate;
+ pdata->pchip.of_pwm_n_cells = 1;
+
+ return pwmchip_add(&pdata->pchip);
+}
+
+static void ti_sn_pwm_remove(struct auxiliary_device *adev)
+{
+ struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
+
+ pwmchip_remove(&pdata->pchip);
+
+ if (pdata->pwm_enabled)
+ pm_runtime_put_sync(pdata->dev);
+}
+
+static const struct auxiliary_device_id ti_sn_pwm_id_table[] = {
+ { .name = "ti_sn65dsi86.pwm", },
+ {},
+};
+
+static struct auxiliary_driver ti_sn_pwm_driver = {
+ .name = "pwm",
+ .probe = ti_sn_pwm_probe,
+ .remove = ti_sn_pwm_remove,
+ .id_table = ti_sn_pwm_id_table,
+};
+
+static int __init ti_sn_pwm_register(void)
+{
+ return auxiliary_driver_register(&ti_sn_pwm_driver);
+}
+
+static void ti_sn_pwm_unregister(void)
+{
+ auxiliary_driver_unregister(&ti_sn_pwm_driver);
+}
+
+#else
+static inline int ti_sn_pwm_pin_request(struct ti_sn65dsi86 *pdata) { return 0; }
+static inline void ti_sn_pwm_pin_release(struct ti_sn65dsi86 *pdata) {}
+
+static inline int ti_sn_pwm_register(void) { return 0; }
+static inline void ti_sn_pwm_unregister(void) {}
+#endif
+
+/* -----------------------------------------------------------------------------
+ * GPIO Controller
+ */
#if defined(CONFIG_OF_GPIO)
static int tn_sn_bridge_of_xlate(struct gpio_chip *chip,
@@ -1395,10 +1708,25 @@ static int ti_sn_bridge_gpio_direction_output(struct gpio_chip *chip,
return ret;
}
+static int ti_sn_bridge_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
+
+ if (offset == SN_PWM_GPIO_IDX)
+ return ti_sn_pwm_pin_request(pdata);
+
+ return 0;
+}
+
static void ti_sn_bridge_gpio_free(struct gpio_chip *chip, unsigned int offset)
{
+ struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
+
/* We won't keep pm_runtime if we're input, so switch there on free */
ti_sn_bridge_gpio_direction_input(chip, offset);
+
+ if (offset == SN_PWM_GPIO_IDX)
+ ti_sn_pwm_pin_release(pdata);
}
static const char * const ti_sn_bridge_gpio_names[SN_NUM_GPIOS] = {
@@ -1420,6 +1748,7 @@ static int ti_sn_gpio_probe(struct auxiliary_device *adev,
pdata->gchip.owner = THIS_MODULE;
pdata->gchip.of_xlate = tn_sn_bridge_of_xlate;
pdata->gchip.of_gpio_n_cells = 2;
+ pdata->gchip.request = ti_sn_bridge_gpio_request;
pdata->gchip.free = ti_sn_bridge_gpio_free;
pdata->gchip.get_direction = ti_sn_bridge_gpio_get_direction;
pdata->gchip.direction_input = ti_sn_bridge_gpio_direction_input;
@@ -1546,10 +1875,9 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
* 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. 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.
+ * bus or the pwm_chip. 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)) {
@@ -1558,6 +1886,12 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
return ret;
}
+ if (IS_ENABLED(CONFIG_PWM)) {
+ ret = ti_sn65dsi86_add_aux_device(pdata, &pdata->pwm_aux, "pwm");
+ if (ret)
+ return ret;
+ }
+
/*
* 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
@@ -1601,10 +1935,14 @@ static int __init ti_sn65dsi86_init(void)
if (ret)
goto err_main_was_registered;
- ret = auxiliary_driver_register(&ti_sn_aux_driver);
+ ret = ti_sn_pwm_register();
if (ret)
goto err_gpio_was_registered;
+ ret = auxiliary_driver_register(&ti_sn_aux_driver);
+ if (ret)
+ goto err_pwm_was_registered;
+
ret = auxiliary_driver_register(&ti_sn_bridge_driver);
if (ret)
goto err_aux_was_registered;
@@ -1613,6 +1951,8 @@ static int __init ti_sn65dsi86_init(void)
err_aux_was_registered:
auxiliary_driver_unregister(&ti_sn_aux_driver);
+err_pwm_was_registered:
+ ti_sn_pwm_unregister();
err_gpio_was_registered:
ti_sn_gpio_unregister();
err_main_was_registered:
@@ -1626,6 +1966,7 @@ static void __exit ti_sn65dsi86_exit(void)
{
auxiliary_driver_unregister(&ti_sn_bridge_driver);
auxiliary_driver_unregister(&ti_sn_aux_driver);
+ ti_sn_pwm_unregister();
ti_sn_gpio_unregister();
i2c_del_driver(&ti_sn65dsi86_driver);
}