2018-07-20 14:54:02 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/*
|
|
|
|
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
2020-07-08 20:16:04 +08:00
|
|
|
* datasheet: https://www.ti.com/lit/ds/symlink/sn65dsi86.pdf
|
2018-07-20 14:54:02 +08:00
|
|
|
*/
|
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
#include <linux/auxiliary_bus.h>
|
2020-05-08 05:34:55 +08:00
|
|
|
#include <linux/bits.h>
|
2018-07-20 14:54:02 +08:00
|
|
|
#include <linux/clk.h>
|
2019-07-02 23:44:17 +08:00
|
|
|
#include <linux/debugfs.h>
|
2018-07-20 14:54:02 +08:00
|
|
|
#include <linux/gpio/consumer.h>
|
2020-05-08 05:34:55 +08:00
|
|
|
#include <linux/gpio/driver.h>
|
2018-07-20 14:54:02 +08:00
|
|
|
#include <linux/i2c.h>
|
2018-08-14 05:30:42 +08:00
|
|
|
#include <linux/iopoll.h>
|
2019-05-20 02:36:36 +08:00
|
|
|
#include <linux/module.h>
|
2018-07-20 14:54:02 +08:00
|
|
|
#include <linux/of_graph.h>
|
|
|
|
#include <linux/pm_runtime.h>
|
|
|
|
#include <linux/regmap.h>
|
|
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
|
2020-11-03 02:11:41 +08:00
|
|
|
#include <asm/unaligned.h>
|
|
|
|
|
2019-05-20 02:36:36 +08:00
|
|
|
#include <drm/drm_atomic.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
2019-08-26 23:26:29 +08:00
|
|
|
#include <drm/drm_bridge.h>
|
2019-05-20 02:36:36 +08:00
|
|
|
#include <drm/drm_dp_helper.h>
|
|
|
|
#include <drm/drm_mipi_dsi.h>
|
|
|
|
#include <drm/drm_of.h>
|
|
|
|
#include <drm/drm_panel.h>
|
|
|
|
#include <drm/drm_print.h>
|
|
|
|
#include <drm/drm_probe_helper.h>
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
#define SN_DEVICE_REV_REG 0x08
|
|
|
|
#define SN_DPPLL_SRC_REG 0x0A
|
2018-08-14 05:30:41 +08:00
|
|
|
#define DPPLL_CLK_SRC_DSICLK BIT(0)
|
|
|
|
#define REFCLK_FREQ_MASK GENMASK(3, 1)
|
|
|
|
#define REFCLK_FREQ(x) ((x) << 1)
|
|
|
|
#define DPPLL_SRC_DP_PLL_LOCK BIT(7)
|
|
|
|
#define SN_PLL_ENABLE_REG 0x0D
|
2018-07-20 14:54:02 +08:00
|
|
|
#define SN_DSI_LANES_REG 0x10
|
2018-08-14 05:30:41 +08:00
|
|
|
#define CHA_DSI_LANES_MASK GENMASK(4, 3)
|
|
|
|
#define CHA_DSI_LANES(x) ((x) << 3)
|
2018-07-20 14:54:02 +08:00
|
|
|
#define SN_DSIA_CLK_FREQ_REG 0x12
|
|
|
|
#define SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG 0x20
|
|
|
|
#define SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG 0x24
|
|
|
|
#define SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG 0x2C
|
|
|
|
#define SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG 0x2D
|
2018-08-14 05:30:41 +08:00
|
|
|
#define CHA_HSYNC_POLARITY BIT(7)
|
2018-07-20 14:54:02 +08:00
|
|
|
#define SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG 0x30
|
|
|
|
#define SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG 0x31
|
2018-08-14 05:30:41 +08:00
|
|
|
#define CHA_VSYNC_POLARITY BIT(7)
|
2018-07-20 14:54:02 +08:00
|
|
|
#define SN_CHA_HORIZONTAL_BACK_PORCH_REG 0x34
|
|
|
|
#define SN_CHA_VERTICAL_BACK_PORCH_REG 0x36
|
|
|
|
#define SN_CHA_HORIZONTAL_FRONT_PORCH_REG 0x38
|
|
|
|
#define SN_CHA_VERTICAL_FRONT_PORCH_REG 0x3A
|
2020-05-19 02:47:17 +08:00
|
|
|
#define SN_LN_ASSIGN_REG 0x59
|
|
|
|
#define LN_ASSIGN_WIDTH 2
|
2018-08-14 05:30:41 +08:00
|
|
|
#define SN_ENH_FRAME_REG 0x5A
|
|
|
|
#define VSTREAM_ENABLE BIT(3)
|
2020-05-19 02:47:17 +08:00
|
|
|
#define LN_POLRS_OFFSET 4
|
|
|
|
#define LN_POLRS_MASK 0xf0
|
2018-07-20 14:54:02 +08:00
|
|
|
#define SN_DATA_FORMAT_REG 0x5B
|
2019-12-19 06:35:27 +08:00
|
|
|
#define BPP_18_RGB BIT(0)
|
2018-08-14 05:30:41 +08:00
|
|
|
#define SN_HPD_DISABLE_REG 0x5C
|
|
|
|
#define HPD_DISABLE BIT(0)
|
2020-05-08 05:34:55 +08:00
|
|
|
#define SN_GPIO_IO_REG 0x5E
|
|
|
|
#define SN_GPIO_INPUT_SHIFT 4
|
|
|
|
#define SN_GPIO_OUTPUT_SHIFT 0
|
|
|
|
#define SN_GPIO_CTRL_REG 0x5F
|
|
|
|
#define SN_GPIO_MUX_INPUT 0
|
|
|
|
#define SN_GPIO_MUX_OUTPUT 1
|
|
|
|
#define SN_GPIO_MUX_SPECIAL 2
|
|
|
|
#define SN_GPIO_MUX_MASK 0x3
|
2018-08-14 05:30:42 +08:00
|
|
|
#define SN_AUX_WDATA_REG(x) (0x64 + (x))
|
2018-08-14 05:30:41 +08:00
|
|
|
#define SN_AUX_ADDR_19_16_REG 0x74
|
|
|
|
#define SN_AUX_ADDR_15_8_REG 0x75
|
|
|
|
#define SN_AUX_ADDR_7_0_REG 0x76
|
2020-11-03 02:11:41 +08:00
|
|
|
#define SN_AUX_ADDR_MASK GENMASK(19, 0)
|
2018-08-14 05:30:41 +08:00
|
|
|
#define SN_AUX_LENGTH_REG 0x77
|
|
|
|
#define SN_AUX_CMD_REG 0x78
|
2018-11-30 17:27:45 +08:00
|
|
|
#define AUX_CMD_SEND BIT(0)
|
2018-08-14 05:30:41 +08:00
|
|
|
#define AUX_CMD_REQ(x) ((x) << 4)
|
2018-08-14 05:30:42 +08:00
|
|
|
#define SN_AUX_RDATA_REG(x) (0x79 + (x))
|
2018-08-14 05:30:41 +08:00
|
|
|
#define SN_SSC_CONFIG_REG 0x93
|
|
|
|
#define DP_NUM_LANES_MASK GENMASK(5, 4)
|
|
|
|
#define DP_NUM_LANES(x) ((x) << 4)
|
|
|
|
#define SN_DATARATE_CONFIG_REG 0x94
|
|
|
|
#define DP_DATARATE_MASK GENMASK(7, 5)
|
|
|
|
#define DP_DATARATE(x) ((x) << 5)
|
|
|
|
#define SN_ML_TX_MODE_REG 0x96
|
|
|
|
#define ML_TX_MAIN_LINK_OFF 0
|
|
|
|
#define ML_TX_NORMAL_MODE BIT(0)
|
2018-08-14 05:30:42 +08:00
|
|
|
#define SN_AUX_CMD_STATUS_REG 0xF4
|
|
|
|
#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
|
|
|
|
#define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
|
|
|
|
#define AUX_IRQ_STATUS_NAT_I2C_FAIL BIT(6)
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
#define MIN_DSI_CLK_FREQ_MHZ 40
|
|
|
|
|
|
|
|
/* fudge factor required to account for 8b/10b encoding */
|
|
|
|
#define DP_CLK_FUDGE_NUM 10
|
|
|
|
#define DP_CLK_FUDGE_DEN 8
|
|
|
|
|
2018-08-14 05:30:42 +08:00
|
|
|
/* Matches DP_AUX_MAX_PAYLOAD_BYTES (for now) */
|
|
|
|
#define SN_AUX_MAX_PAYLOAD_BYTES 16
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
#define SN_REGULATOR_SUPPLY_NUM 4
|
|
|
|
|
2020-05-19 02:47:17 +08:00
|
|
|
#define SN_MAX_DP_LANES 4
|
2020-05-08 05:34:55 +08:00
|
|
|
#define SN_NUM_GPIOS 4
|
|
|
|
#define SN_GPIO_PHYSICAL_OFFSET 1
|
|
|
|
|
2020-10-03 05:03:51 +08:00
|
|
|
#define SN_LINK_TRAINING_TRIES 10
|
|
|
|
|
2020-05-08 05:34:55 +08:00
|
|
|
/**
|
2021-04-24 00:58:48 +08:00
|
|
|
* struct ti_sn65dsi86 - Platform data for ti-sn65dsi86 driver.
|
2021-04-24 00:58:55 +08:00
|
|
|
* @bridge_aux: AUX-bus sub device for MIPI-to-eDP bridge functionality.
|
|
|
|
* @gpio_aux: AUX-bus sub device for GPIO controller functionality.
|
|
|
|
*
|
|
|
|
* @dev: Pointer to the top level (i2c) device.
|
2020-05-08 05:34:55 +08:00
|
|
|
* @regmap: Regmap for accessing i2c.
|
|
|
|
* @aux: Our aux channel.
|
|
|
|
* @bridge: Our bridge.
|
|
|
|
* @connector: Our connector.
|
|
|
|
* @host_node: Remote DSI node.
|
|
|
|
* @dsi: Our MIPI DSI source.
|
2020-11-03 02:11:43 +08:00
|
|
|
* @edid: Detected EDID of eDP panel.
|
2020-05-08 05:34:55 +08:00
|
|
|
* @refclk: Our reference clock.
|
|
|
|
* @panel: Our panel.
|
|
|
|
* @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.
|
2020-05-19 02:47:17 +08:00
|
|
|
* @ln_assign: Value to program to the LN_ASSIGN register.
|
2020-06-13 03:30:49 +08:00
|
|
|
* @ln_polrs: Value for the 4-bit LN_POLRS field of SN_ENH_FRAME_REG.
|
2021-04-24 00:58:59 +08:00
|
|
|
* @comms_enabled: If true then communication over the aux channel is enabled.
|
|
|
|
* @comms_mutex: Protects modification of comms_enabled.
|
2020-05-08 05:34:55 +08:00
|
|
|
*
|
|
|
|
* @gchip: If we expose our GPIOs, this is used.
|
|
|
|
* @gchip_output: A cache of whether we've set GPIOs to output. This
|
|
|
|
* serves double-duty of keeping track of the direction and
|
|
|
|
* also keeping track of whether we've incremented the
|
|
|
|
* pm_runtime reference count for this pin, which we do
|
|
|
|
* whenever a pin is configured as an output. This is a
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 {
|
2021-04-24 00:58:55 +08:00
|
|
|
struct auxiliary_device bridge_aux;
|
|
|
|
struct auxiliary_device gpio_aux;
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
struct device *dev;
|
|
|
|
struct regmap *regmap;
|
2018-08-14 05:30:42 +08:00
|
|
|
struct drm_dp_aux aux;
|
2018-07-20 14:54:02 +08:00
|
|
|
struct drm_bridge bridge;
|
|
|
|
struct drm_connector connector;
|
2020-11-03 02:11:43 +08:00
|
|
|
struct edid *edid;
|
2018-07-20 14:54:02 +08:00
|
|
|
struct device_node *host_node;
|
|
|
|
struct mipi_dsi_device *dsi;
|
|
|
|
struct clk *refclk;
|
|
|
|
struct drm_panel *panel;
|
|
|
|
struct gpio_desc *enable_gpio;
|
|
|
|
struct regulator_bulk_data supplies[SN_REGULATOR_SUPPLY_NUM];
|
2019-12-19 06:35:24 +08:00
|
|
|
int dp_lanes;
|
2020-05-19 02:47:17 +08:00
|
|
|
u8 ln_assign;
|
|
|
|
u8 ln_polrs;
|
2021-04-24 00:58:59 +08:00
|
|
|
bool comms_enabled;
|
|
|
|
struct mutex comms_mutex;
|
2020-05-08 05:34:55 +08:00
|
|
|
|
2020-06-13 03:30:47 +08:00
|
|
|
#if defined(CONFIG_OF_GPIO)
|
2020-05-08 05:34:55 +08:00
|
|
|
struct gpio_chip gchip;
|
|
|
|
DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS);
|
2020-06-13 03:30:47 +08:00
|
|
|
#endif
|
2018-07-20 14:54:02 +08:00
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static const struct regmap_range ti_sn65dsi86_volatile_ranges[] = {
|
2018-07-20 14:54:02 +08:00
|
|
|
{ .range_min = 0, .range_max = 0xFF },
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct regmap_access_table ti_sn_bridge_volatile_table = {
|
2021-04-24 00:58:49 +08:00
|
|
|
.yes_ranges = ti_sn65dsi86_volatile_ranges,
|
|
|
|
.n_yes_ranges = ARRAY_SIZE(ti_sn65dsi86_volatile_ranges),
|
2018-07-20 14:54:02 +08:00
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static const struct regmap_config ti_sn65dsi86_regmap_config = {
|
2018-07-20 14:54:02 +08:00
|
|
|
.reg_bits = 8,
|
|
|
|
.val_bits = 8,
|
|
|
|
.volatile_table = &ti_sn_bridge_volatile_table,
|
|
|
|
.cache_type = REGCACHE_NONE,
|
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static void ti_sn65dsi86_write_u16(struct ti_sn65dsi86 *pdata,
|
2018-07-20 14:54:02 +08:00
|
|
|
unsigned int reg, u16 val)
|
|
|
|
{
|
|
|
|
regmap_write(pdata->regmap, reg, val & 0xFF);
|
|
|
|
regmap_write(pdata->regmap, reg + 1, val >> 8);
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:58 +08:00
|
|
|
static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn65dsi86 *pdata)
|
|
|
|
{
|
|
|
|
u32 bit_rate_khz, clk_freq_khz;
|
|
|
|
struct drm_display_mode *mode =
|
|
|
|
&pdata->bridge.encoder->crtc->state->adjusted_mode;
|
|
|
|
|
|
|
|
bit_rate_khz = mode->clock *
|
|
|
|
mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
|
|
|
|
clk_freq_khz = bit_rate_khz / (pdata->dsi->lanes * 2);
|
|
|
|
|
|
|
|
return clk_freq_khz;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* clk frequencies supported by bridge in Hz in case derived from REFCLK pin */
|
|
|
|
static const u32 ti_sn_bridge_refclk_lut[] = {
|
|
|
|
12000000,
|
|
|
|
19200000,
|
|
|
|
26000000,
|
|
|
|
27000000,
|
|
|
|
38400000,
|
|
|
|
};
|
|
|
|
|
|
|
|
/* clk frequencies supported by bridge in Hz in case derived from DACP/N pin */
|
|
|
|
static const u32 ti_sn_bridge_dsiclk_lut[] = {
|
|
|
|
468000000,
|
|
|
|
384000000,
|
|
|
|
416000000,
|
|
|
|
486000000,
|
|
|
|
460800000,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void ti_sn_bridge_set_refclk_freq(struct ti_sn65dsi86 *pdata)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
u32 refclk_rate;
|
|
|
|
const u32 *refclk_lut;
|
|
|
|
size_t refclk_lut_size;
|
|
|
|
|
|
|
|
if (pdata->refclk) {
|
|
|
|
refclk_rate = clk_get_rate(pdata->refclk);
|
|
|
|
refclk_lut = ti_sn_bridge_refclk_lut;
|
|
|
|
refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_refclk_lut);
|
|
|
|
clk_prepare_enable(pdata->refclk);
|
|
|
|
} else {
|
|
|
|
refclk_rate = ti_sn_bridge_get_dsi_freq(pdata) * 1000;
|
|
|
|
refclk_lut = ti_sn_bridge_dsiclk_lut;
|
|
|
|
refclk_lut_size = ARRAY_SIZE(ti_sn_bridge_dsiclk_lut);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* for i equals to refclk_lut_size means default frequency */
|
|
|
|
for (i = 0; i < refclk_lut_size; i++)
|
|
|
|
if (refclk_lut[i] == refclk_rate)
|
|
|
|
break;
|
|
|
|
|
|
|
|
regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK,
|
|
|
|
REFCLK_FREQ(i));
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
static void ti_sn65dsi86_enable_comms(struct ti_sn65dsi86 *pdata)
|
|
|
|
{
|
|
|
|
mutex_lock(&pdata->comms_mutex);
|
|
|
|
|
|
|
|
/* configure bridge ref_clk */
|
|
|
|
ti_sn_bridge_set_refclk_freq(pdata);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* HPD on this bridge chip is a bit useless. This is an eDP bridge
|
|
|
|
* so the HPD is an internal signal that's only there to signal that
|
|
|
|
* the panel is done powering up. ...but the bridge chip debounces
|
|
|
|
* this signal by between 100 ms and 400 ms (depending on process,
|
|
|
|
* voltage, and temperate--I measured it at about 200 ms). One
|
|
|
|
* particular panel asserted HPD 84 ms after it was powered on meaning
|
|
|
|
* that we saw HPD 284 ms after power on. ...but the same panel said
|
|
|
|
* that instead of looking at HPD you could just hardcode a delay of
|
|
|
|
* 200 ms. We'll assume that the panel driver will have the hardcoded
|
|
|
|
* delay in its prepare and always disable HPD.
|
|
|
|
*
|
|
|
|
* If HPD somehow makes sense on some future panel we'll have to
|
|
|
|
* change this to be conditional on someone specifying that HPD should
|
|
|
|
* be used.
|
|
|
|
*/
|
|
|
|
regmap_update_bits(pdata->regmap, SN_HPD_DISABLE_REG, HPD_DISABLE,
|
|
|
|
HPD_DISABLE);
|
|
|
|
|
|
|
|
pdata->comms_enabled = true;
|
|
|
|
|
|
|
|
mutex_unlock(&pdata->comms_mutex);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ti_sn65dsi86_disable_comms(struct ti_sn65dsi86 *pdata)
|
|
|
|
{
|
|
|
|
mutex_lock(&pdata->comms_mutex);
|
|
|
|
|
|
|
|
pdata->comms_enabled = false;
|
|
|
|
clk_disable_unprepare(pdata->refclk);
|
|
|
|
|
|
|
|
mutex_unlock(&pdata->comms_mutex);
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static int __maybe_unused ti_sn65dsi86_resume(struct device *dev)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = dev_get_drvdata(dev);
|
2018-07-20 14:54:02 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = regulator_bulk_enable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies);
|
|
|
|
if (ret) {
|
|
|
|
DRM_ERROR("failed to enable supplies %d\n", ret);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
gpiod_set_value(pdata->enable_gpio, 1);
|
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
/*
|
|
|
|
* If we have a reference clock we can enable communication w/ the
|
|
|
|
* panel (including the aux channel) w/out any need for an input clock
|
|
|
|
* so we can do it in resume which lets us read the EDID before
|
|
|
|
* pre_enable(). Without a reference clock we need the MIPI reference
|
|
|
|
* clock so reading early doesn't work.
|
|
|
|
*/
|
|
|
|
if (pdata->refclk)
|
|
|
|
ti_sn65dsi86_enable_comms(pdata);
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static int __maybe_unused ti_sn65dsi86_suspend(struct device *dev)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = dev_get_drvdata(dev);
|
2018-07-20 14:54:02 +08:00
|
|
|
int ret;
|
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
if (pdata->refclk)
|
|
|
|
ti_sn65dsi86_disable_comms(pdata);
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
gpiod_set_value(pdata->enable_gpio, 0);
|
|
|
|
|
|
|
|
ret = regulator_bulk_disable(SN_REGULATOR_SUPPLY_NUM, pdata->supplies);
|
|
|
|
if (ret)
|
|
|
|
DRM_ERROR("failed to disable supplies %d\n", ret);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static const struct dev_pm_ops ti_sn65dsi86_pm_ops = {
|
|
|
|
SET_RUNTIME_PM_OPS(ti_sn65dsi86_suspend, ti_sn65dsi86_resume, NULL)
|
2020-06-09 20:04:55 +08:00
|
|
|
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
|
|
|
pm_runtime_force_resume)
|
2018-07-20 14:54:02 +08:00
|
|
|
};
|
|
|
|
|
2019-07-02 23:44:17 +08:00
|
|
|
static int status_show(struct seq_file *s, void *data)
|
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = s->private;
|
2019-07-02 23:44:17 +08:00
|
|
|
unsigned int reg, val;
|
|
|
|
|
|
|
|
seq_puts(s, "STATUS REGISTERS:\n");
|
|
|
|
|
|
|
|
pm_runtime_get_sync(pdata->dev);
|
|
|
|
|
|
|
|
/* IRQ Status Registers, see Table 31 in datasheet */
|
|
|
|
for (reg = 0xf0; reg <= 0xf8; reg++) {
|
|
|
|
regmap_read(pdata->regmap, reg, &val);
|
|
|
|
seq_printf(s, "[0x%02x] = 0x%08x\n", reg, val);
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:57 +08:00
|
|
|
pm_runtime_put_autosuspend(pdata->dev);
|
2019-07-02 23:44:17 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_SHOW_ATTRIBUTE(status);
|
|
|
|
|
2021-04-24 00:58:51 +08:00
|
|
|
static void ti_sn65dsi86_debugfs_remove(void *data)
|
2019-07-02 23:44:17 +08:00
|
|
|
{
|
2021-04-24 00:58:51 +08:00
|
|
|
debugfs_remove_recursive(data);
|
2019-07-02 23:44:17 +08:00
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:51 +08:00
|
|
|
static void ti_sn65dsi86_debugfs_init(struct ti_sn65dsi86 *pdata)
|
2019-07-02 23:44:17 +08:00
|
|
|
{
|
2021-04-24 00:58:51 +08:00
|
|
|
struct device *dev = pdata->dev;
|
|
|
|
struct dentry *debugfs;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
debugfs = debugfs_create_dir(dev_name(dev), NULL);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We might get an error back if debugfs wasn't enabled in the kernel
|
|
|
|
* so let's just silently return upon failure.
|
|
|
|
*/
|
|
|
|
if (IS_ERR_OR_NULL(debugfs))
|
|
|
|
return;
|
|
|
|
|
|
|
|
ret = devm_add_action_or_reset(dev, ti_sn65dsi86_debugfs_remove, debugfs);
|
|
|
|
if (ret)
|
|
|
|
return;
|
|
|
|
|
|
|
|
debugfs_create_file("status", 0600, debugfs, pdata, &status_fops);
|
2019-07-02 23:44:17 +08:00
|
|
|
}
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
/* Connector funcs */
|
2021-04-24 00:58:48 +08:00
|
|
|
static struct ti_sn65dsi86 *
|
2021-04-24 00:58:49 +08:00
|
|
|
connector_to_ti_sn65dsi86(struct drm_connector *connector)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
return container_of(connector, struct ti_sn65dsi86, connector);
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ti_sn_bridge_connector_get_modes(struct drm_connector *connector)
|
|
|
|
{
|
2021-04-24 00:58:49 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = connector_to_ti_sn65dsi86(connector);
|
2020-11-03 02:11:43 +08:00
|
|
|
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);
|
2021-04-24 00:58:57 +08:00
|
|
|
pm_runtime_put_autosuspend(pdata->dev);
|
2020-11-03 02:11:43 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2019-12-07 22:03:34 +08:00
|
|
|
return drm_panel_get_modes(pdata->panel, connector);
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static enum drm_mode_status
|
|
|
|
ti_sn_bridge_connector_mode_valid(struct drm_connector *connector,
|
|
|
|
struct drm_display_mode *mode)
|
|
|
|
{
|
|
|
|
/* maximum supported resolution is 4K at 60 fps */
|
|
|
|
if (mode->clock > 594000)
|
|
|
|
return MODE_CLOCK_HIGH;
|
|
|
|
|
|
|
|
return MODE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct drm_connector_helper_funcs ti_sn_bridge_connector_helper_funcs = {
|
|
|
|
.get_modes = ti_sn_bridge_connector_get_modes,
|
|
|
|
.mode_valid = ti_sn_bridge_connector_mode_valid,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct drm_connector_funcs ti_sn_bridge_connector_funcs = {
|
|
|
|
.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,
|
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static struct ti_sn65dsi86 *bridge_to_ti_sn65dsi86(struct drm_bridge *bridge)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
return container_of(bridge, struct ti_sn65dsi86, bridge);
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static int ti_sn65dsi86_parse_regulators(struct ti_sn65dsi86 *pdata)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
|
|
|
unsigned int i;
|
|
|
|
const char * const ti_sn_bridge_supply_names[] = {
|
|
|
|
"vcca", "vcc", "vccio", "vpll",
|
|
|
|
};
|
|
|
|
|
|
|
|
for (i = 0; i < SN_REGULATOR_SUPPLY_NUM; i++)
|
|
|
|
pdata->supplies[i].supply = ti_sn_bridge_supply_names[i];
|
|
|
|
|
|
|
|
return devm_regulator_bulk_get(pdata->dev, SN_REGULATOR_SUPPLY_NUM,
|
|
|
|
pdata->supplies);
|
|
|
|
}
|
|
|
|
|
drm/bridge: Extend bridge API to disable connector creation
Most bridge drivers create a DRM connector to model the connector at the
output of the bridge. This model is historical and has worked pretty
well so far, but causes several issues:
- It prevents supporting more complex display pipelines where DRM
connector operations are split over multiple components. For instance a
pipeline with a bridge connected to the DDC signals to read EDID data,
and another one connected to the HPD signal to detect connection and
disconnection, will not be possible to support through this model.
- It requires every bridge driver to implement similar connector
handling code, resulting in code duplication.
- It assumes that a bridge will either be wired to a connector or to
another bridge, but doesn't support bridges that can be used in both
positions very well (although there is some ad-hoc support for this in
the analogix_dp bridge driver).
In order to solve these issues, ownership of the connector should be
moved to the display controller driver (where it can be implemented
using helpers provided by the core).
Extend the bridge API to allow disabling connector creation in bridge
drivers as a first step towards the new model. The new flags argument to
the bridge .attach() operation allows instructing the bridge driver to
skip creating a connector. Unconditionally set the new flags argument to
0 for now to keep the existing behaviour, and modify all existing bridge
drivers to return an error when connector creation is not requested as
they don't support this feature yet.
The change is based on the following semantic patch, with manual review
and edits.
@ rule1 @
identifier funcs;
identifier fn;
@@
struct drm_bridge_funcs funcs = {
...,
.attach = fn
};
@ depends on rule1 @
identifier rule1.fn;
identifier bridge;
statement S, S1;
@@
int fn(
struct drm_bridge *bridge
+ , enum drm_bridge_attach_flags flags
)
{
... when != S
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
S1
...
}
@ depends on rule1 @
identifier rule1.fn;
identifier bridge, flags;
expression E1, E2, E3;
@@
int fn(
struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags
) {
<...
drm_bridge_attach(E1, E2, E3
+ , flags
)
...>
}
@@
expression E1, E2, E3;
@@
drm_bridge_attach(E1, E2, E3
+ , 0
)
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200226112514.12455-10-laurent.pinchart@ideasonboard.com
2020-02-26 19:24:29 +08:00
|
|
|
static int ti_sn_bridge_attach(struct drm_bridge *bridge,
|
|
|
|
enum drm_bridge_attach_flags flags)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
|
|
|
int ret, val;
|
2021-04-24 00:58:49 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
|
2018-07-20 14:54:02 +08:00
|
|
|
struct mipi_dsi_host *host;
|
|
|
|
struct mipi_dsi_device *dsi;
|
|
|
|
const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
|
|
|
|
.channel = 0,
|
|
|
|
.node = NULL,
|
|
|
|
};
|
|
|
|
|
drm/bridge: Extend bridge API to disable connector creation
Most bridge drivers create a DRM connector to model the connector at the
output of the bridge. This model is historical and has worked pretty
well so far, but causes several issues:
- It prevents supporting more complex display pipelines where DRM
connector operations are split over multiple components. For instance a
pipeline with a bridge connected to the DDC signals to read EDID data,
and another one connected to the HPD signal to detect connection and
disconnection, will not be possible to support through this model.
- It requires every bridge driver to implement similar connector
handling code, resulting in code duplication.
- It assumes that a bridge will either be wired to a connector or to
another bridge, but doesn't support bridges that can be used in both
positions very well (although there is some ad-hoc support for this in
the analogix_dp bridge driver).
In order to solve these issues, ownership of the connector should be
moved to the display controller driver (where it can be implemented
using helpers provided by the core).
Extend the bridge API to allow disabling connector creation in bridge
drivers as a first step towards the new model. The new flags argument to
the bridge .attach() operation allows instructing the bridge driver to
skip creating a connector. Unconditionally set the new flags argument to
0 for now to keep the existing behaviour, and modify all existing bridge
drivers to return an error when connector creation is not requested as
they don't support this feature yet.
The change is based on the following semantic patch, with manual review
and edits.
@ rule1 @
identifier funcs;
identifier fn;
@@
struct drm_bridge_funcs funcs = {
...,
.attach = fn
};
@ depends on rule1 @
identifier rule1.fn;
identifier bridge;
statement S, S1;
@@
int fn(
struct drm_bridge *bridge
+ , enum drm_bridge_attach_flags flags
)
{
... when != S
+ if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
+ DRM_ERROR("Fix bridge driver to make connector optional!");
+ return -EINVAL;
+ }
+
S1
...
}
@ depends on rule1 @
identifier rule1.fn;
identifier bridge, flags;
expression E1, E2, E3;
@@
int fn(
struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags
) {
<...
drm_bridge_attach(E1, E2, E3
+ , flags
)
...>
}
@@
expression E1, E2, E3;
@@
drm_bridge_attach(E1, E2, E3
+ , 0
)
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Reviewed-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Tested-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Reviewed-by: Sebastian Reichel <sebastian.reichel@collabora.com>
Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
Signed-off-by: Tomi Valkeinen <tomi.valkeinen@ti.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20200226112514.12455-10-laurent.pinchart@ideasonboard.com
2020-02-26 19:24:29 +08:00
|
|
|
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
|
|
|
DRM_ERROR("Fix bridge driver to make connector optional!");
|
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2021-04-24 02:42:55 +08:00
|
|
|
pdata->aux.drm_dev = bridge->dev;
|
2021-02-20 05:53:02 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
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");
|
2021-02-20 05:53:02 +08:00
|
|
|
goto err_conn_init;
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
drm_connector_helper_add(&pdata->connector,
|
|
|
|
&ti_sn_bridge_connector_helper_funcs);
|
2018-07-31 01:42:21 +08:00
|
|
|
drm_connector_attach_encoder(&pdata->connector, bridge->encoder);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-12-19 06:35:26 +08:00
|
|
|
/* TODO: setting to 4 MIPI lanes always for now */
|
2018-07-20 14:54:02 +08:00
|
|
|
dsi->lanes = 4;
|
|
|
|
dsi->format = MIPI_DSI_FMT_RGB888;
|
2019-07-02 23:44:18 +08:00
|
|
|
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
/* check if continuous dsi clock is required or not */
|
|
|
|
pm_runtime_get_sync(pdata->dev);
|
|
|
|
regmap_read(pdata->regmap, SN_DPPLL_SRC_REG, &val);
|
2021-04-24 00:58:57 +08:00
|
|
|
pm_runtime_put_autosuspend(pdata->dev);
|
2018-07-20 14:54:02 +08:00
|
|
|
if (!(val & DPPLL_CLK_SRC_DSICLK))
|
|
|
|
dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
|
|
|
|
|
|
|
ret = mipi_dsi_attach(dsi);
|
|
|
|
if (ret < 0) {
|
|
|
|
DRM_ERROR("failed to attach dsi to host\n");
|
|
|
|
goto err_dsi_attach;
|
|
|
|
}
|
|
|
|
pdata->dsi = dsi;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_dsi_attach:
|
|
|
|
mipi_dsi_device_unregister(dsi);
|
|
|
|
err_dsi_host:
|
|
|
|
drm_connector_cleanup(&pdata->connector);
|
2021-02-20 05:53:02 +08:00
|
|
|
err_conn_init:
|
|
|
|
drm_dp_aux_unregister(&pdata->aux);
|
2018-07-20 14:54:02 +08:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-02-20 05:53:02 +08:00
|
|
|
static void ti_sn_bridge_detach(struct drm_bridge *bridge)
|
|
|
|
{
|
2021-04-24 00:58:49 +08:00
|
|
|
drm_dp_aux_unregister(&bridge_to_ti_sn65dsi86(bridge)->aux);
|
2021-02-20 05:53:02 +08:00
|
|
|
}
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
static void ti_sn_bridge_disable(struct drm_bridge *bridge)
|
|
|
|
{
|
2021-04-24 00:58:49 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
drm_panel_disable(pdata->panel);
|
|
|
|
|
|
|
|
/* disable video stream */
|
2018-08-14 05:30:41 +08:00
|
|
|
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE, 0);
|
2018-07-20 14:54:02 +08:00
|
|
|
/* 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);
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static void ti_sn_bridge_set_dsi_rate(struct ti_sn65dsi86 *pdata)
|
2019-12-19 06:35:22 +08:00
|
|
|
{
|
|
|
|
unsigned int bit_rate_mhz, clk_freq_mhz;
|
|
|
|
unsigned int val;
|
|
|
|
struct drm_display_mode *mode =
|
|
|
|
&pdata->bridge.encoder->crtc->state->adjusted_mode;
|
|
|
|
|
|
|
|
/* set DSIA clk frequency */
|
|
|
|
bit_rate_mhz = (mode->clock / 1000) *
|
|
|
|
mipi_dsi_pixel_format_to_bpp(pdata->dsi->format);
|
|
|
|
clk_freq_mhz = bit_rate_mhz / (pdata->dsi->lanes * 2);
|
|
|
|
|
|
|
|
/* for each increment in val, frequency increases by 5MHz */
|
|
|
|
val = (MIN_DSI_CLK_FREQ_MHZ / 5) +
|
|
|
|
(((clk_freq_mhz - MIN_DSI_CLK_FREQ_MHZ) / 5) & 0xFF);
|
|
|
|
regmap_write(pdata->regmap, SN_DSIA_CLK_FREQ_REG, val);
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static unsigned int ti_sn_bridge_get_bpp(struct ti_sn65dsi86 *pdata)
|
2019-12-19 06:35:27 +08:00
|
|
|
{
|
|
|
|
if (pdata->connector.display_info.bpc <= 6)
|
|
|
|
return 18;
|
|
|
|
else
|
|
|
|
return 24;
|
|
|
|
}
|
|
|
|
|
2020-06-13 03:30:48 +08:00
|
|
|
/*
|
2018-07-20 14:54:02 +08:00
|
|
|
* LUT index corresponds to register value and
|
|
|
|
* LUT values corresponds to dp data rate supported
|
|
|
|
* by the bridge in Mbps unit.
|
|
|
|
*/
|
|
|
|
static const unsigned int ti_sn_bridge_dp_rate_lut[] = {
|
|
|
|
0, 1620, 2160, 2430, 2700, 3240, 4320, 5400
|
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static int ti_sn_bridge_calc_min_dp_rate_idx(struct ti_sn65dsi86 *pdata)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
2019-12-19 06:35:27 +08:00
|
|
|
unsigned int bit_rate_khz, dp_rate_mhz;
|
2019-12-19 06:35:22 +08:00
|
|
|
unsigned int i;
|
2018-07-20 14:54:02 +08:00
|
|
|
struct drm_display_mode *mode =
|
|
|
|
&pdata->bridge.encoder->crtc->state->adjusted_mode;
|
|
|
|
|
2019-12-19 06:35:27 +08:00
|
|
|
/* Calculate minimum bit rate based on our pixel clock. */
|
|
|
|
bit_rate_khz = mode->clock * ti_sn_bridge_get_bpp(pdata);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2019-12-19 06:35:24 +08:00
|
|
|
/* Calculate minimum DP data rate, taking 80% as per DP spec */
|
2019-12-19 06:35:27 +08:00
|
|
|
dp_rate_mhz = DIV_ROUND_UP(bit_rate_khz * DP_CLK_FUDGE_NUM,
|
|
|
|
1000 * pdata->dp_lanes * DP_CLK_FUDGE_DEN);
|
2019-12-19 06:35:24 +08:00
|
|
|
|
2019-12-19 06:35:23 +08:00
|
|
|
for (i = 1; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut) - 1; i++)
|
2020-05-05 12:32:29 +08:00
|
|
|
if (ti_sn_bridge_dp_rate_lut[i] >= dp_rate_mhz)
|
2018-07-20 14:54:02 +08:00
|
|
|
break;
|
|
|
|
|
2019-12-19 06:35:29 +08:00
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static void ti_sn_bridge_read_valid_rates(struct ti_sn65dsi86 *pdata,
|
2019-12-19 06:35:30 +08:00
|
|
|
bool rate_valid[])
|
2019-12-19 06:35:29 +08:00
|
|
|
{
|
2019-12-19 06:35:30 +08:00
|
|
|
unsigned int rate_per_200khz;
|
|
|
|
unsigned int rate_mhz;
|
|
|
|
u8 dpcd_val;
|
2019-12-19 06:35:29 +08:00
|
|
|
int ret;
|
2019-12-19 06:35:30 +08:00
|
|
|
int i, j;
|
|
|
|
|
|
|
|
ret = drm_dp_dpcd_readb(&pdata->aux, DP_EDP_DPCD_REV, &dpcd_val);
|
|
|
|
if (ret != 1) {
|
|
|
|
DRM_DEV_ERROR(pdata->dev,
|
|
|
|
"Can't read eDP rev (%d), assuming 1.1\n", ret);
|
|
|
|
dpcd_val = DP_EDP_11;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dpcd_val >= DP_EDP_14) {
|
|
|
|
/* eDP 1.4 devices must provide a custom table */
|
|
|
|
__le16 sink_rates[DP_MAX_SUPPORTED_RATES];
|
|
|
|
|
|
|
|
ret = drm_dp_dpcd_read(&pdata->aux, DP_SUPPORTED_LINK_RATES,
|
|
|
|
sink_rates, sizeof(sink_rates));
|
|
|
|
|
|
|
|
if (ret != sizeof(sink_rates)) {
|
|
|
|
DRM_DEV_ERROR(pdata->dev,
|
|
|
|
"Can't read supported rate table (%d)\n", ret);
|
|
|
|
|
|
|
|
/* By zeroing we'll fall back to DP_MAX_LINK_RATE. */
|
|
|
|
memset(sink_rates, 0, sizeof(sink_rates));
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sink_rates); i++) {
|
|
|
|
rate_per_200khz = le16_to_cpu(sink_rates[i]);
|
|
|
|
|
|
|
|
if (!rate_per_200khz)
|
|
|
|
break;
|
|
|
|
|
|
|
|
rate_mhz = rate_per_200khz * 200 / 1000;
|
|
|
|
for (j = 0;
|
|
|
|
j < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
|
|
|
|
j++) {
|
|
|
|
if (ti_sn_bridge_dp_rate_lut[j] == rate_mhz)
|
|
|
|
rate_valid[j] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut); i++) {
|
|
|
|
if (rate_valid[i])
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
DRM_DEV_ERROR(pdata->dev,
|
|
|
|
"No matching eDP rates in table; falling back\n");
|
|
|
|
}
|
2019-12-19 06:35:29 +08:00
|
|
|
|
2019-12-19 06:35:30 +08:00
|
|
|
/* On older versions best we can do is use DP_MAX_LINK_RATE */
|
|
|
|
ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LINK_RATE, &dpcd_val);
|
2019-12-19 06:35:29 +08:00
|
|
|
if (ret != 1) {
|
|
|
|
DRM_DEV_ERROR(pdata->dev,
|
|
|
|
"Can't read max rate (%d); assuming 5.4 GHz\n",
|
|
|
|
ret);
|
2019-12-19 06:35:30 +08:00
|
|
|
dpcd_val = DP_LINK_BW_5_4;
|
2019-12-19 06:35:29 +08:00
|
|
|
}
|
|
|
|
|
2019-12-19 06:35:30 +08:00
|
|
|
switch (dpcd_val) {
|
|
|
|
default:
|
|
|
|
DRM_DEV_ERROR(pdata->dev,
|
|
|
|
"Unexpected max rate (%#x); assuming 5.4 GHz\n",
|
|
|
|
(int)dpcd_val);
|
2020-08-24 06:36:59 +08:00
|
|
|
fallthrough;
|
2019-12-19 06:35:29 +08:00
|
|
|
case DP_LINK_BW_5_4:
|
2019-12-19 06:35:30 +08:00
|
|
|
rate_valid[7] = 1;
|
2020-08-24 06:36:59 +08:00
|
|
|
fallthrough;
|
2019-12-19 06:35:30 +08:00
|
|
|
case DP_LINK_BW_2_7:
|
|
|
|
rate_valid[4] = 1;
|
2020-08-24 06:36:59 +08:00
|
|
|
fallthrough;
|
2019-12-19 06:35:30 +08:00
|
|
|
case DP_LINK_BW_1_62:
|
|
|
|
rate_valid[1] = 1;
|
|
|
|
break;
|
2019-12-19 06:35:29 +08:00
|
|
|
}
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static void ti_sn_bridge_set_video_timings(struct ti_sn65dsi86 *pdata)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
|
|
|
struct drm_display_mode *mode =
|
|
|
|
&pdata->bridge.encoder->crtc->state->adjusted_mode;
|
|
|
|
u8 hsync_polarity = 0, vsync_polarity = 0;
|
|
|
|
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
2018-08-14 05:30:41 +08:00
|
|
|
hsync_polarity = CHA_HSYNC_POLARITY;
|
2018-07-20 14:54:02 +08:00
|
|
|
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
2018-08-14 05:30:41 +08:00
|
|
|
vsync_polarity = CHA_VSYNC_POLARITY;
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
ti_sn65dsi86_write_u16(pdata, SN_CHA_ACTIVE_LINE_LENGTH_LOW_REG,
|
2018-07-20 14:54:02 +08:00
|
|
|
mode->hdisplay);
|
2021-04-24 00:58:49 +08:00
|
|
|
ti_sn65dsi86_write_u16(pdata, SN_CHA_VERTICAL_DISPLAY_SIZE_LOW_REG,
|
2018-07-20 14:54:02 +08:00
|
|
|
mode->vdisplay);
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_LOW_REG,
|
|
|
|
(mode->hsync_end - mode->hsync_start) & 0xFF);
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_HSYNC_PULSE_WIDTH_HIGH_REG,
|
|
|
|
(((mode->hsync_end - mode->hsync_start) >> 8) & 0x7F) |
|
|
|
|
hsync_polarity);
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_LOW_REG,
|
|
|
|
(mode->vsync_end - mode->vsync_start) & 0xFF);
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_VSYNC_PULSE_WIDTH_HIGH_REG,
|
|
|
|
(((mode->vsync_end - mode->vsync_start) >> 8) & 0x7F) |
|
|
|
|
vsync_polarity);
|
|
|
|
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_BACK_PORCH_REG,
|
|
|
|
(mode->htotal - mode->hsync_end) & 0xFF);
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_VERTICAL_BACK_PORCH_REG,
|
|
|
|
(mode->vtotal - mode->vsync_end) & 0xFF);
|
|
|
|
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_HORIZONTAL_FRONT_PORCH_REG,
|
|
|
|
(mode->hsync_start - mode->hdisplay) & 0xFF);
|
|
|
|
regmap_write(pdata->regmap, SN_CHA_VERTICAL_FRONT_PORCH_REG,
|
|
|
|
(mode->vsync_start - mode->vdisplay) & 0xFF);
|
|
|
|
|
|
|
|
usleep_range(10000, 10500); /* 10ms delay recommended by spec */
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static unsigned int ti_sn_get_max_lanes(struct ti_sn65dsi86 *pdata)
|
2019-12-19 06:35:26 +08:00
|
|
|
{
|
|
|
|
u8 data;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = drm_dp_dpcd_readb(&pdata->aux, DP_MAX_LANE_COUNT, &data);
|
|
|
|
if (ret != 1) {
|
|
|
|
DRM_DEV_ERROR(pdata->dev,
|
|
|
|
"Can't read lane count (%d); assuming 4\n", ret);
|
|
|
|
return 4;
|
|
|
|
}
|
|
|
|
|
|
|
|
return data & DP_LANE_COUNT_MASK;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static int ti_sn_link_training(struct ti_sn65dsi86 *pdata, int dp_rate_idx,
|
2019-12-19 06:35:29 +08:00
|
|
|
const char **last_err_str)
|
2019-12-19 06:35:28 +08:00
|
|
|
{
|
|
|
|
unsigned int val;
|
|
|
|
int ret;
|
2020-10-03 05:03:51 +08:00
|
|
|
int i;
|
2019-12-19 06:35:28 +08:00
|
|
|
|
|
|
|
/* set dp clk frequency value */
|
2019-12-19 06:35:29 +08:00
|
|
|
regmap_update_bits(pdata->regmap, SN_DATARATE_CONFIG_REG,
|
|
|
|
DP_DATARATE_MASK, DP_DATARATE(dp_rate_idx));
|
2019-12-19 06:35:28 +08:00
|
|
|
|
|
|
|
/* enable DP PLL */
|
|
|
|
regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 1);
|
|
|
|
|
|
|
|
ret = regmap_read_poll_timeout(pdata->regmap, SN_DPPLL_SRC_REG, val,
|
|
|
|
val & DPPLL_SRC_DP_PLL_LOCK, 1000,
|
|
|
|
50 * 1000);
|
|
|
|
if (ret) {
|
2019-12-19 06:35:29 +08:00
|
|
|
*last_err_str = "DP_PLL_LOCK polling failed";
|
2019-12-19 06:35:28 +08:00
|
|
|
goto exit;
|
|
|
|
}
|
|
|
|
|
2020-10-03 05:03:51 +08:00
|
|
|
/*
|
|
|
|
* We'll try to link train several times. As part of link training
|
|
|
|
* the bridge chip will write DP_SET_POWER_D0 to DP_SET_POWER. If
|
|
|
|
* the panel isn't ready quite it might respond NAK here which means
|
|
|
|
* we need to try again.
|
|
|
|
*/
|
|
|
|
for (i = 0; i < SN_LINK_TRAINING_TRIES; i++) {
|
|
|
|
/* Semi auto link training mode */
|
|
|
|
regmap_write(pdata->regmap, SN_ML_TX_MODE_REG, 0x0A);
|
|
|
|
ret = regmap_read_poll_timeout(pdata->regmap, SN_ML_TX_MODE_REG, val,
|
|
|
|
val == ML_TX_MAIN_LINK_OFF ||
|
|
|
|
val == ML_TX_NORMAL_MODE, 1000,
|
|
|
|
500 * 1000);
|
|
|
|
if (ret) {
|
|
|
|
*last_err_str = "Training complete polling failed";
|
|
|
|
} else if (val == ML_TX_MAIN_LINK_OFF) {
|
|
|
|
*last_err_str = "Link training failed, link is off";
|
|
|
|
ret = -EIO;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
2019-12-19 06:35:28 +08:00
|
|
|
}
|
|
|
|
|
2020-10-03 05:03:51 +08:00
|
|
|
/* If we saw quite a few retries, add a note about it */
|
|
|
|
if (!ret && i > SN_LINK_TRAINING_TRIES / 2)
|
|
|
|
DRM_DEV_INFO(pdata->dev, "Link training needed %d retries\n", i);
|
|
|
|
|
2019-12-19 06:35:28 +08:00
|
|
|
exit:
|
|
|
|
/* Disable the PLL if we failed */
|
|
|
|
if (ret)
|
|
|
|
regmap_write(pdata->regmap, SN_PLL_ENABLE_REG, 0);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
static void ti_sn_bridge_enable(struct drm_bridge *bridge)
|
|
|
|
{
|
2021-04-24 00:58:49 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
|
2019-12-19 06:35:30 +08:00
|
|
|
bool rate_valid[ARRAY_SIZE(ti_sn_bridge_dp_rate_lut)] = { };
|
2019-12-19 06:35:29 +08:00
|
|
|
const char *last_err_str = "No supported DP rate";
|
|
|
|
int dp_rate_idx;
|
2018-07-20 14:54:02 +08:00
|
|
|
unsigned int val;
|
2019-12-19 06:35:29 +08:00
|
|
|
int ret = -EINVAL;
|
2020-05-19 02:47:17 +08:00
|
|
|
int max_dp_lanes;
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2020-05-19 02:47:17 +08:00
|
|
|
max_dp_lanes = ti_sn_get_max_lanes(pdata);
|
|
|
|
pdata->dp_lanes = min(pdata->dp_lanes, max_dp_lanes);
|
2019-12-19 06:35:26 +08:00
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
/* DSI_A lane config */
|
2020-05-19 02:47:17 +08:00
|
|
|
val = CHA_DSI_LANES(SN_MAX_DP_LANES - pdata->dsi->lanes);
|
2018-07-20 14:54:02 +08:00
|
|
|
regmap_update_bits(pdata->regmap, SN_DSI_LANES_REG,
|
2018-08-14 05:30:41 +08:00
|
|
|
CHA_DSI_LANES_MASK, val);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2020-05-19 02:47:17 +08:00
|
|
|
regmap_write(pdata->regmap, SN_LN_ASSIGN_REG, pdata->ln_assign);
|
|
|
|
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, LN_POLRS_MASK,
|
|
|
|
pdata->ln_polrs << LN_POLRS_OFFSET);
|
|
|
|
|
2019-12-19 06:35:28 +08:00
|
|
|
/* set dsi clk frequency value */
|
2019-12-19 06:35:22 +08:00
|
|
|
ti_sn_bridge_set_dsi_rate(pdata);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-17 06:39:26 +08:00
|
|
|
/*
|
2018-07-20 14:54:02 +08:00
|
|
|
* The SN65DSI86 only supports ASSR Display Authentication method and
|
|
|
|
* this method is enabled by default. An eDP panel must support this
|
|
|
|
* authentication method. We need to enable this method in the eDP panel
|
|
|
|
* at DisplayPort address 0x0010A prior to link training.
|
|
|
|
*/
|
2018-08-14 05:30:42 +08:00
|
|
|
drm_dp_dpcd_writeb(&pdata->aux, DP_EDP_CONFIGURATION_SET,
|
|
|
|
DP_ALTERNATE_SCRAMBLER_RESET_ENABLE);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2019-12-19 06:35:28 +08:00
|
|
|
/* Set the DP output format (18 bpp or 24 bpp) */
|
|
|
|
val = (ti_sn_bridge_get_bpp(pdata) == 18) ? BPP_18_RGB : 0;
|
|
|
|
regmap_update_bits(pdata->regmap, SN_DATA_FORMAT_REG, BPP_18_RGB, val);
|
|
|
|
|
|
|
|
/* DP lane config */
|
|
|
|
val = DP_NUM_LANES(min(pdata->dp_lanes, 3));
|
|
|
|
regmap_update_bits(pdata->regmap, SN_SSC_CONFIG_REG, DP_NUM_LANES_MASK,
|
|
|
|
val);
|
|
|
|
|
2019-12-19 06:35:30 +08:00
|
|
|
ti_sn_bridge_read_valid_rates(pdata, rate_valid);
|
|
|
|
|
2019-12-19 06:35:29 +08:00
|
|
|
/* Train until we run out of rates */
|
|
|
|
for (dp_rate_idx = ti_sn_bridge_calc_min_dp_rate_idx(pdata);
|
2019-12-19 06:35:30 +08:00
|
|
|
dp_rate_idx < ARRAY_SIZE(ti_sn_bridge_dp_rate_lut);
|
2019-12-19 06:35:29 +08:00
|
|
|
dp_rate_idx++) {
|
2019-12-19 06:35:30 +08:00
|
|
|
if (!rate_valid[dp_rate_idx])
|
|
|
|
continue;
|
|
|
|
|
2019-12-19 06:35:29 +08:00
|
|
|
ret = ti_sn_link_training(pdata, dp_rate_idx, &last_err_str);
|
|
|
|
if (!ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (ret) {
|
|
|
|
DRM_DEV_ERROR(pdata->dev, "%s (%d)\n", last_err_str, ret);
|
2018-08-14 05:30:45 +08:00
|
|
|
return;
|
2019-12-19 06:35:29 +08:00
|
|
|
}
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
/* config video parameters */
|
|
|
|
ti_sn_bridge_set_video_timings(pdata);
|
|
|
|
|
|
|
|
/* enable video stream */
|
2018-08-14 05:30:41 +08:00
|
|
|
regmap_update_bits(pdata->regmap, SN_ENH_FRAME_REG, VSTREAM_ENABLE,
|
|
|
|
VSTREAM_ENABLE);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
drm_panel_enable(pdata->panel);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ti_sn_bridge_pre_enable(struct drm_bridge *bridge)
|
|
|
|
{
|
2021-04-24 00:58:49 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
pm_runtime_get_sync(pdata->dev);
|
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
if (!pdata->refclk)
|
|
|
|
ti_sn65dsi86_enable_comms(pdata);
|
2018-08-14 05:30:43 +08:00
|
|
|
|
|
|
|
drm_panel_prepare(pdata->panel);
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ti_sn_bridge_post_disable(struct drm_bridge *bridge)
|
|
|
|
{
|
2021-04-24 00:58:49 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-17 06:39:28 +08:00
|
|
|
drm_panel_unprepare(pdata->panel);
|
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
if (!pdata->refclk)
|
|
|
|
ti_sn65dsi86_disable_comms(pdata);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
pm_runtime_put_sync(pdata->dev);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct drm_bridge_funcs ti_sn_bridge_funcs = {
|
|
|
|
.attach = ti_sn_bridge_attach,
|
2021-02-20 05:53:02 +08:00
|
|
|
.detach = ti_sn_bridge_detach,
|
2018-07-20 14:54:02 +08:00
|
|
|
.pre_enable = ti_sn_bridge_pre_enable,
|
|
|
|
.enable = ti_sn_bridge_enable,
|
|
|
|
.disable = ti_sn_bridge_disable,
|
|
|
|
.post_disable = ti_sn_bridge_post_disable,
|
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static struct ti_sn65dsi86 *aux_to_ti_sn65dsi86(struct drm_dp_aux *aux)
|
2018-08-14 05:30:42 +08:00
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
return container_of(aux, struct ti_sn65dsi86, aux);
|
2018-08-14 05:30:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t ti_sn_aux_transfer(struct drm_dp_aux *aux,
|
|
|
|
struct drm_dp_aux_msg *msg)
|
|
|
|
{
|
2021-04-24 00:58:49 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = aux_to_ti_sn65dsi86(aux);
|
2020-11-03 02:11:44 +08:00
|
|
|
u32 request = msg->request & ~(DP_AUX_I2C_MOT | DP_AUX_I2C_WRITE_STATUS_UPDATE);
|
2018-08-14 05:30:42 +08:00
|
|
|
u32 request_val = AUX_CMD_REQ(msg->request);
|
2020-11-03 02:11:41 +08:00
|
|
|
u8 *buf = msg->buffer;
|
|
|
|
unsigned int len = msg->size;
|
2018-08-14 05:30:42 +08:00
|
|
|
unsigned int val;
|
2020-11-03 02:11:41 +08:00
|
|
|
int ret;
|
|
|
|
u8 addr_len[SN_AUX_LENGTH_REG + 1 - SN_AUX_ADDR_19_16_REG];
|
2018-08-14 05:30:42 +08:00
|
|
|
|
2020-11-03 02:11:41 +08:00
|
|
|
if (len > SN_AUX_MAX_PAYLOAD_BYTES)
|
2018-08-14 05:30:42 +08:00
|
|
|
return -EINVAL;
|
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-08-14 05:30:42 +08:00
|
|
|
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);
|
2020-11-03 02:11:44 +08:00
|
|
|
/* Assume it's good */
|
|
|
|
msg->reply = 0;
|
2018-08-14 05:30:42 +08:00
|
|
|
break;
|
|
|
|
default:
|
2021-04-24 00:58:59 +08:00
|
|
|
ret = -EINVAL;
|
|
|
|
goto exit;
|
2018-08-14 05:30:42 +08:00
|
|
|
}
|
|
|
|
|
2020-11-03 02:11:41 +08:00
|
|
|
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));
|
2018-08-14 05:30:42 +08:00
|
|
|
|
2020-11-03 02:11:41 +08:00
|
|
|
if (request == DP_AUX_NATIVE_WRITE || request == DP_AUX_I2C_WRITE)
|
|
|
|
regmap_bulk_write(pdata->regmap, SN_AUX_WDATA_REG(0), buf, len);
|
2018-08-14 05:30:42 +08:00
|
|
|
|
2020-05-09 07:33:29 +08:00
|
|
|
/* 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);
|
|
|
|
|
2018-08-14 05:30:42 +08:00
|
|
|
regmap_write(pdata->regmap, SN_AUX_CMD_REG, request_val | AUX_CMD_SEND);
|
|
|
|
|
2020-11-03 02:11:42 +08:00
|
|
|
/* Zero delay loop because i2c transactions are slow already */
|
2018-08-14 05:30:42 +08:00
|
|
|
ret = regmap_read_poll_timeout(pdata->regmap, SN_AUX_CMD_REG, val,
|
2020-11-03 02:11:42 +08:00
|
|
|
!(val & AUX_CMD_SEND), 0, 50 * 1000);
|
2018-08-14 05:30:42 +08:00
|
|
|
if (ret)
|
2021-04-24 00:58:59 +08:00
|
|
|
goto exit;
|
2018-08-14 05:30:42 +08:00
|
|
|
|
|
|
|
ret = regmap_read(pdata->regmap, SN_AUX_CMD_STATUS_REG, &val);
|
|
|
|
if (ret)
|
2021-04-24 00:58:59 +08:00
|
|
|
goto exit;
|
2020-11-03 02:11:44 +08:00
|
|
|
|
|
|
|
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.
|
|
|
|
*/
|
2021-04-24 00:58:59 +08:00
|
|
|
ret = -ETIMEDOUT;
|
|
|
|
goto exit;
|
2020-11-03 02:11:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (val & AUX_IRQ_STATUS_AUX_SHORT) {
|
|
|
|
ret = regmap_read(pdata->regmap, SN_AUX_LENGTH_REG, &len);
|
|
|
|
if (ret)
|
2021-04-24 00:58:59 +08:00
|
|
|
goto exit;
|
2020-11-03 02:11:44 +08:00
|
|
|
} 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;
|
|
|
|
}
|
2021-04-24 00:58:59 +08:00
|
|
|
len = 0;
|
|
|
|
goto exit;
|
2020-11-03 02:11:44 +08:00
|
|
|
}
|
2018-08-14 05:30:42 +08:00
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
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);
|
2018-08-14 05:30:42 +08:00
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
exit:
|
|
|
|
mutex_unlock(&pdata->comms_mutex);
|
|
|
|
pm_runtime_mark_last_busy(pdata->dev);
|
|
|
|
pm_runtime_put_autosuspend(pdata->dev);
|
2018-08-14 05:30:42 +08:00
|
|
|
|
2021-05-18 17:19:30 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
return len;
|
2018-08-14 05:30:42 +08:00
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static int ti_sn_bridge_parse_dsi_host(struct ti_sn65dsi86 *pdata)
|
2018-07-20 14:54:02 +08:00
|
|
|
{
|
|
|
|
struct device_node *np = pdata->dev->of_node;
|
|
|
|
|
|
|
|
pdata->host_node = of_graph_get_remote_node(np, 0, 0);
|
|
|
|
|
|
|
|
if (!pdata->host_node) {
|
|
|
|
DRM_ERROR("remote dsi host node not found\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-06-13 03:30:47 +08:00
|
|
|
#if defined(CONFIG_OF_GPIO)
|
|
|
|
|
2020-05-08 05:34:55 +08:00
|
|
|
static int tn_sn_bridge_of_xlate(struct gpio_chip *chip,
|
|
|
|
const struct of_phandle_args *gpiospec,
|
|
|
|
u32 *flags)
|
|
|
|
{
|
|
|
|
if (WARN_ON(gpiospec->args_count < chip->of_gpio_n_cells))
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (gpiospec->args[0] > chip->ngpio || gpiospec->args[0] < 1)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if (flags)
|
|
|
|
*flags = gpiospec->args[1];
|
|
|
|
|
|
|
|
return gpiospec->args[0] - SN_GPIO_PHYSICAL_OFFSET;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ti_sn_bridge_gpio_get_direction(struct gpio_chip *chip,
|
|
|
|
unsigned int offset)
|
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
|
2020-05-08 05:34:55 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* We already have to keep track of the direction because we use
|
|
|
|
* that to figure out whether we've powered the device. We can
|
|
|
|
* just return that rather than (maybe) powering up the device
|
|
|
|
* to ask its direction.
|
|
|
|
*/
|
|
|
|
return test_bit(offset, pdata->gchip_output) ?
|
|
|
|
GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ti_sn_bridge_gpio_get(struct gpio_chip *chip, unsigned int offset)
|
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
|
2020-05-08 05:34:55 +08:00
|
|
|
unsigned int val;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* When the pin is an input we don't forcibly keep the bridge
|
|
|
|
* powered--we just power it on to read the pin. NOTE: part of
|
|
|
|
* the reason this works is that the bridge defaults (when
|
|
|
|
* powered back on) to all 4 GPIOs being configured as GPIO input.
|
|
|
|
* Also note that if something else is keeping the chip powered the
|
|
|
|
* pm_runtime functions are lightweight increments of a refcount.
|
|
|
|
*/
|
|
|
|
pm_runtime_get_sync(pdata->dev);
|
|
|
|
ret = regmap_read(pdata->regmap, SN_GPIO_IO_REG, &val);
|
2021-04-24 00:58:57 +08:00
|
|
|
pm_runtime_put_autosuspend(pdata->dev);
|
2020-05-08 05:34:55 +08:00
|
|
|
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return !!(val & BIT(SN_GPIO_INPUT_SHIFT + offset));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ti_sn_bridge_gpio_set(struct gpio_chip *chip, unsigned int offset,
|
|
|
|
int val)
|
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
|
2020-05-08 05:34:55 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!test_bit(offset, pdata->gchip_output)) {
|
|
|
|
dev_err(pdata->dev, "Ignoring GPIO set while input\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
val &= 1;
|
|
|
|
ret = regmap_update_bits(pdata->regmap, SN_GPIO_IO_REG,
|
|
|
|
BIT(SN_GPIO_OUTPUT_SHIFT + offset),
|
|
|
|
val << (SN_GPIO_OUTPUT_SHIFT + offset));
|
2020-06-13 03:30:50 +08:00
|
|
|
if (ret)
|
|
|
|
dev_warn(pdata->dev,
|
|
|
|
"Failed to set bridge GPIO %u: %d\n", offset, ret);
|
2020-05-08 05:34:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ti_sn_bridge_gpio_direction_input(struct gpio_chip *chip,
|
|
|
|
unsigned int offset)
|
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
|
2020-05-08 05:34:55 +08:00
|
|
|
int shift = offset * 2;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!test_and_clear_bit(offset, pdata->gchip_output))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = regmap_update_bits(pdata->regmap, SN_GPIO_CTRL_REG,
|
|
|
|
SN_GPIO_MUX_MASK << shift,
|
|
|
|
SN_GPIO_MUX_INPUT << shift);
|
|
|
|
if (ret) {
|
|
|
|
set_bit(offset, pdata->gchip_output);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* NOTE: if nobody else is powering the device this may fully power
|
|
|
|
* it off and when it comes back it will have lost all state, but
|
|
|
|
* that's OK because the default is input and we're now an input.
|
|
|
|
*/
|
2021-04-24 00:58:57 +08:00
|
|
|
pm_runtime_put_autosuspend(pdata->dev);
|
2020-05-08 05:34:55 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ti_sn_bridge_gpio_direction_output(struct gpio_chip *chip,
|
|
|
|
unsigned int offset, int val)
|
|
|
|
{
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
|
2020-05-08 05:34:55 +08:00
|
|
|
int shift = offset * 2;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (test_and_set_bit(offset, pdata->gchip_output))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pm_runtime_get_sync(pdata->dev);
|
|
|
|
|
|
|
|
/* Set value first to avoid glitching */
|
|
|
|
ti_sn_bridge_gpio_set(chip, offset, val);
|
|
|
|
|
|
|
|
/* Set direction */
|
|
|
|
ret = regmap_update_bits(pdata->regmap, SN_GPIO_CTRL_REG,
|
|
|
|
SN_GPIO_MUX_MASK << shift,
|
|
|
|
SN_GPIO_MUX_OUTPUT << shift);
|
|
|
|
if (ret) {
|
|
|
|
clear_bit(offset, pdata->gchip_output);
|
2021-04-24 00:58:57 +08:00
|
|
|
pm_runtime_put_autosuspend(pdata->dev);
|
2020-05-08 05:34:55 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ti_sn_bridge_gpio_free(struct gpio_chip *chip, unsigned int offset)
|
|
|
|
{
|
|
|
|
/* We won't keep pm_runtime if we're input, so switch there on free */
|
|
|
|
ti_sn_bridge_gpio_direction_input(chip, offset);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const char * const ti_sn_bridge_gpio_names[SN_NUM_GPIOS] = {
|
|
|
|
"GPIO1", "GPIO2", "GPIO3", "GPIO4"
|
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
static int ti_sn_gpio_probe(struct auxiliary_device *adev,
|
|
|
|
const struct auxiliary_device_id *id)
|
2020-05-08 05:34:55 +08:00
|
|
|
{
|
2021-04-24 00:58:55 +08:00
|
|
|
struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
|
2020-05-08 05:34:55 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
/* Only init if someone is going to use us as a GPIO controller */
|
|
|
|
if (!of_property_read_bool(pdata->dev->of_node, "gpio-controller"))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
pdata->gchip.label = dev_name(pdata->dev);
|
|
|
|
pdata->gchip.parent = pdata->dev;
|
|
|
|
pdata->gchip.owner = THIS_MODULE;
|
|
|
|
pdata->gchip.of_xlate = tn_sn_bridge_of_xlate;
|
|
|
|
pdata->gchip.of_gpio_n_cells = 2;
|
|
|
|
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;
|
|
|
|
pdata->gchip.direction_output = ti_sn_bridge_gpio_direction_output;
|
|
|
|
pdata->gchip.get = ti_sn_bridge_gpio_get;
|
|
|
|
pdata->gchip.set = ti_sn_bridge_gpio_set;
|
|
|
|
pdata->gchip.can_sleep = true;
|
|
|
|
pdata->gchip.names = ti_sn_bridge_gpio_names;
|
|
|
|
pdata->gchip.ngpio = SN_NUM_GPIOS;
|
|
|
|
pdata->gchip.base = -1;
|
2021-04-24 00:58:55 +08:00
|
|
|
ret = devm_gpiochip_add_data(&adev->dev, &pdata->gchip, pdata);
|
2020-05-08 05:34:55 +08:00
|
|
|
if (ret)
|
|
|
|
dev_err(pdata->dev, "can't add gpio chip\n");
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
static const struct auxiliary_device_id ti_sn_gpio_id_table[] = {
|
|
|
|
{ .name = "ti_sn65dsi86.gpio", },
|
|
|
|
{},
|
|
|
|
};
|
2020-06-13 03:30:47 +08:00
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
MODULE_DEVICE_TABLE(auxiliary, ti_sn_gpio_id_table);
|
|
|
|
|
|
|
|
static struct auxiliary_driver ti_sn_gpio_driver = {
|
|
|
|
.name = "gpio",
|
|
|
|
.probe = ti_sn_gpio_probe,
|
|
|
|
.id_table = ti_sn_gpio_id_table,
|
|
|
|
};
|
|
|
|
|
|
|
|
static int __init ti_sn_gpio_register(void)
|
2020-06-13 03:30:47 +08:00
|
|
|
{
|
2021-04-24 00:58:55 +08:00
|
|
|
return auxiliary_driver_register(&ti_sn_gpio_driver);
|
2020-06-13 03:30:47 +08:00
|
|
|
}
|
|
|
|
|
2021-05-04 22:38:54 +08:00
|
|
|
static void ti_sn_gpio_unregister(void)
|
2021-04-24 00:58:55 +08:00
|
|
|
{
|
|
|
|
auxiliary_driver_unregister(&ti_sn_gpio_driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
static inline int ti_sn_gpio_register(void) { return 0; }
|
|
|
|
static inline void ti_sn_gpio_unregister(void) {}
|
|
|
|
|
2020-06-13 03:30:47 +08:00
|
|
|
#endif
|
|
|
|
|
2021-04-24 00:58:48 +08:00
|
|
|
static void ti_sn_bridge_parse_lanes(struct ti_sn65dsi86 *pdata,
|
2020-05-19 02:47:17 +08:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2021-04-24 00:58:50 +08:00
|
|
|
static void ti_sn65dsi86_runtime_disable(void *data)
|
|
|
|
{
|
|
|
|
pm_runtime_disable(data);
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
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)
|
|
|
|
{
|
|
|
|
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;
|
|
|
|
|
|
|
|
ret = auxiliary_device_add(aux);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
ret = devm_add_action_or_reset(dev, ti_sn65dsi86_delete_aux, aux);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static int ti_sn65dsi86_probe(struct i2c_client *client,
|
2018-07-20 14:54:02 +08:00
|
|
|
const struct i2c_device_id *id)
|
|
|
|
{
|
2021-04-24 00:58:52 +08:00
|
|
|
struct device *dev = &client->dev;
|
2021-04-24 00:58:48 +08:00
|
|
|
struct ti_sn65dsi86 *pdata;
|
2018-07-20 14:54:02 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
|
|
|
|
DRM_ERROR("device doesn't support I2C\n");
|
|
|
|
return -ENODEV;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:52 +08:00
|
|
|
pdata = devm_kzalloc(dev, sizeof(struct ti_sn65dsi86), GFP_KERNEL);
|
2018-07-20 14:54:02 +08:00
|
|
|
if (!pdata)
|
|
|
|
return -ENOMEM;
|
2021-04-24 00:58:53 +08:00
|
|
|
dev_set_drvdata(dev, pdata);
|
|
|
|
pdata->dev = dev;
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-24 00:58:59 +08:00
|
|
|
mutex_init(&pdata->comms_mutex);
|
|
|
|
|
2018-07-20 14:54:02 +08:00
|
|
|
pdata->regmap = devm_regmap_init_i2c(client,
|
2021-04-24 00:58:49 +08:00
|
|
|
&ti_sn65dsi86_regmap_config);
|
2018-07-20 14:54:02 +08:00
|
|
|
if (IS_ERR(pdata->regmap)) {
|
|
|
|
DRM_ERROR("regmap i2c init failed\n");
|
|
|
|
return PTR_ERR(pdata->regmap);
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:52 +08:00
|
|
|
pdata->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
|
2018-07-20 14:54:02 +08:00
|
|
|
if (IS_ERR(pdata->enable_gpio)) {
|
|
|
|
DRM_ERROR("failed to get enable gpio from DT\n");
|
|
|
|
ret = PTR_ERR(pdata->enable_gpio);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
ret = ti_sn65dsi86_parse_regulators(pdata);
|
2018-07-20 14:54:02 +08:00
|
|
|
if (ret) {
|
|
|
|
DRM_ERROR("failed to parse regulators\n");
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:52 +08:00
|
|
|
pdata->refclk = devm_clk_get_optional(dev, "refclk");
|
2021-04-17 06:39:25 +08:00
|
|
|
if (IS_ERR(pdata->refclk))
|
|
|
|
return PTR_ERR(pdata->refclk);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-24 00:58:54 +08:00
|
|
|
pm_runtime_enable(dev);
|
|
|
|
ret = devm_add_action_or_reset(dev, ti_sn65dsi86_runtime_disable, dev);
|
2018-07-20 14:54:02 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2021-04-24 00:58:57 +08:00
|
|
|
pm_runtime_set_autosuspend_delay(pdata->dev, 500);
|
|
|
|
pm_runtime_use_autosuspend(pdata->dev);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-24 00:58:54 +08:00
|
|
|
ti_sn65dsi86_debugfs_init(pdata);
|
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
/*
|
|
|
|
* Break ourselves up into a collection of aux devices. The only real
|
|
|
|
* 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.
|
|
|
|
*/
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
if (IS_ENABLED(CONFIG_OF_GPIO)) {
|
|
|
|
ret = ti_sn65dsi86_add_aux_device(pdata, &pdata->gpio_aux, "gpio");
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:55 +08:00
|
|
|
return ti_sn65dsi86_add_aux_device(pdata, &pdata->bridge_aux, "bridge");
|
2018-07-20 14:54:02 +08:00
|
|
|
}
|
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static struct i2c_device_id ti_sn65dsi86_id[] = {
|
2018-07-20 14:54:02 +08:00
|
|
|
{ "ti,sn65dsi86", 0},
|
|
|
|
{},
|
|
|
|
};
|
2021-04-24 00:58:49 +08:00
|
|
|
MODULE_DEVICE_TABLE(i2c, ti_sn65dsi86_id);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static const struct of_device_id ti_sn65dsi86_match_table[] = {
|
2018-07-20 14:54:02 +08:00
|
|
|
{.compatible = "ti,sn65dsi86"},
|
|
|
|
{},
|
|
|
|
};
|
2021-04-24 00:58:49 +08:00
|
|
|
MODULE_DEVICE_TABLE(of, ti_sn65dsi86_match_table);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
2021-04-24 00:58:49 +08:00
|
|
|
static struct i2c_driver ti_sn65dsi86_driver = {
|
2018-07-20 14:54:02 +08:00
|
|
|
.driver = {
|
|
|
|
.name = "ti_sn65dsi86",
|
2021-04-24 00:58:49 +08:00
|
|
|
.of_match_table = ti_sn65dsi86_match_table,
|
|
|
|
.pm = &ti_sn65dsi86_pm_ops,
|
2018-07-20 14:54:02 +08:00
|
|
|
},
|
2021-04-24 00:58:49 +08:00
|
|
|
.probe = ti_sn65dsi86_probe,
|
|
|
|
.id_table = ti_sn65dsi86_id,
|
2018-07-20 14:54:02 +08:00
|
|
|
};
|
2021-04-24 00:58:55 +08:00
|
|
|
|
|
|
|
static int __init ti_sn65dsi86_init(void)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = i2c_add_driver(&ti_sn65dsi86_driver);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
ret = ti_sn_gpio_register();
|
|
|
|
if (ret)
|
|
|
|
goto err_main_was_registered;
|
|
|
|
|
|
|
|
ret = auxiliary_driver_register(&ti_sn_bridge_driver);
|
|
|
|
if (ret)
|
|
|
|
goto err_gpio_was_registered;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
err_gpio_was_registered:
|
|
|
|
ti_sn_gpio_unregister();
|
|
|
|
err_main_was_registered:
|
|
|
|
i2c_del_driver(&ti_sn65dsi86_driver);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
module_init(ti_sn65dsi86_init);
|
|
|
|
|
|
|
|
static void __exit ti_sn65dsi86_exit(void)
|
|
|
|
{
|
|
|
|
auxiliary_driver_unregister(&ti_sn_bridge_driver);
|
|
|
|
ti_sn_gpio_unregister();
|
|
|
|
i2c_del_driver(&ti_sn65dsi86_driver);
|
|
|
|
}
|
|
|
|
module_exit(ti_sn65dsi86_exit);
|
2018-07-20 14:54:02 +08:00
|
|
|
|
|
|
|
MODULE_AUTHOR("Sandeep Panda <spanda@codeaurora.org>");
|
|
|
|
MODULE_DESCRIPTION("sn65dsi86 DSI to eDP bridge driver");
|
|
|
|
MODULE_LICENSE("GPL v2");
|