776 lines
21 KiB
C
776 lines
21 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mod_devicetable.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/of.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/units.h>
|
|
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_bridge.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_mipi_dsi.h>
|
|
#include <drm/drm_of.h>
|
|
#include <drm/drm_panel.h>
|
|
#include <video/mipi_display.h>
|
|
|
|
#define SSD2825_DEVICE_ID_REG 0xb0
|
|
#define SSD2825_RGB_INTERFACE_CTRL_REG_1 0xb1
|
|
#define SSD2825_RGB_INTERFACE_CTRL_REG_2 0xb2
|
|
#define SSD2825_RGB_INTERFACE_CTRL_REG_3 0xb3
|
|
#define SSD2825_RGB_INTERFACE_CTRL_REG_4 0xb4
|
|
#define SSD2825_RGB_INTERFACE_CTRL_REG_5 0xb5
|
|
#define SSD2825_RGB_INTERFACE_CTRL_REG_6 0xb6
|
|
#define SSD2825_NON_BURST_EV BIT(2)
|
|
#define SSD2825_BURST BIT(3)
|
|
#define SSD2825_PCKL_HIGH BIT(13)
|
|
#define SSD2825_HSYNC_HIGH BIT(14)
|
|
#define SSD2825_VSYNC_HIGH BIT(15)
|
|
#define SSD2825_CONFIGURATION_REG 0xb7
|
|
#define SSD2825_CONF_REG_HS BIT(0)
|
|
#define SSD2825_CONF_REG_CKE BIT(1)
|
|
#define SSD2825_CONF_REG_SLP BIT(2)
|
|
#define SSD2825_CONF_REG_VEN BIT(3)
|
|
#define SSD2825_CONF_REG_HCLK BIT(4)
|
|
#define SSD2825_CONF_REG_CSS BIT(5)
|
|
#define SSD2825_CONF_REG_DCS BIT(6)
|
|
#define SSD2825_CONF_REG_REN BIT(7)
|
|
#define SSD2825_CONF_REG_ECD BIT(8)
|
|
#define SSD2825_CONF_REG_EOT BIT(9)
|
|
#define SSD2825_CONF_REG_LPE BIT(10)
|
|
#define SSD2825_VC_CTRL_REG 0xb8
|
|
#define SSD2825_PLL_CTRL_REG 0xb9
|
|
#define SSD2825_PLL_CONFIGURATION_REG 0xba
|
|
#define SSD2825_CLOCK_CTRL_REG 0xbb
|
|
#define SSD2825_PACKET_SIZE_CTRL_REG_1 0xbc
|
|
#define SSD2825_PACKET_SIZE_CTRL_REG_2 0xbd
|
|
#define SSD2825_PACKET_SIZE_CTRL_REG_3 0xbe
|
|
#define SSD2825_PACKET_DROP_REG 0xbf
|
|
#define SSD2825_OPERATION_CTRL_REG 0xc0
|
|
#define SSD2825_MAX_RETURN_SIZE_REG 0xc1
|
|
#define SSD2825_RETURN_DATA_COUNT_REG 0xc2
|
|
#define SSD2825_ACK_RESPONSE_REG 0xc3
|
|
#define SSD2825_LINE_CTRL_REG 0xc4
|
|
#define SSD2825_INTERRUPT_CTRL_REG 0xc5
|
|
#define SSD2825_INTERRUPT_STATUS_REG 0xc6
|
|
#define SSD2825_ERROR_STATUS_REG 0xc7
|
|
#define SSD2825_DATA_FORMAT_REG 0xc8
|
|
#define SSD2825_DELAY_ADJ_REG_1 0xc9
|
|
#define SSD2825_DELAY_ADJ_REG_2 0xca
|
|
#define SSD2825_DELAY_ADJ_REG_3 0xcb
|
|
#define SSD2825_DELAY_ADJ_REG_4 0xcc
|
|
#define SSD2825_DELAY_ADJ_REG_5 0xcd
|
|
#define SSD2825_DELAY_ADJ_REG_6 0xce
|
|
#define SSD2825_HS_TX_TIMER_REG_1 0xcf
|
|
#define SSD2825_HS_TX_TIMER_REG_2 0xd0
|
|
#define SSD2825_LP_RX_TIMER_REG_1 0xd1
|
|
#define SSD2825_LP_RX_TIMER_REG_2 0xd2
|
|
#define SSD2825_TE_STATUS_REG 0xd3
|
|
#define SSD2825_SPI_READ_REG 0xd4
|
|
#define SSD2825_SPI_READ_REG_RESET 0xfa
|
|
#define SSD2825_PLL_LOCK_REG 0xd5
|
|
#define SSD2825_TEST_REG 0xd6
|
|
#define SSD2825_TE_COUNT_REG 0xd7
|
|
#define SSD2825_ANALOG_CTRL_REG_1 0xd8
|
|
#define SSD2825_ANALOG_CTRL_REG_2 0xd9
|
|
#define SSD2825_ANALOG_CTRL_REG_3 0xda
|
|
#define SSD2825_ANALOG_CTRL_REG_4 0xdb
|
|
#define SSD2825_INTERRUPT_OUT_CTRL_REG 0xdc
|
|
#define SSD2825_RGB_INTERFACE_CTRL_REG_7 0xdd
|
|
#define SSD2825_LANE_CONFIGURATION_REG 0xde
|
|
#define SSD2825_DELAY_ADJ_REG_7 0xdf
|
|
#define SSD2825_INPUT_PIN_CTRL_REG_1 0xe0
|
|
#define SSD2825_INPUT_PIN_CTRL_REG_2 0xe1
|
|
#define SSD2825_BIDIR_PIN_CTRL_REG_1 0xe2
|
|
#define SSD2825_BIDIR_PIN_CTRL_REG_2 0xe3
|
|
#define SSD2825_BIDIR_PIN_CTRL_REG_3 0xe4
|
|
#define SSD2825_BIDIR_PIN_CTRL_REG_4 0xe5
|
|
#define SSD2825_BIDIR_PIN_CTRL_REG_5 0xe6
|
|
#define SSD2825_BIDIR_PIN_CTRL_REG_6 0xe7
|
|
#define SSD2825_BIDIR_PIN_CTRL_REG_7 0xe8
|
|
#define SSD2825_CABC_BRIGHTNESS_CTRL_REG_1 0xe9
|
|
#define SSD2825_CABC_BRIGHTNESS_CTRL_REG_2 0xea
|
|
#define SSD2825_CABC_BRIGHTNESS_STATUS_REG 0xeb
|
|
#define SSD2825_READ_REG 0xff
|
|
|
|
#define SSD2825_COM_BYTE 0x00
|
|
#define SSD2825_DAT_BYTE 0x01
|
|
|
|
#define SSD2828_LP_CLOCK_DIVIDER(n) (((n) - 1) & 0x3f)
|
|
#define SSD2825_LP_MIN_CLK 5000 /* KHz */
|
|
#define SSD2825_REF_MIN_CLK 2000 /* KHz */
|
|
|
|
static const struct regulator_bulk_data ssd2825_supplies[] = {
|
|
{ .supply = "dvdd" },
|
|
{ .supply = "avdd" },
|
|
{ .supply = "vddio" },
|
|
};
|
|
|
|
struct ssd2825_dsi_output {
|
|
struct mipi_dsi_device *dev;
|
|
struct drm_panel *panel;
|
|
struct drm_bridge *bridge;
|
|
};
|
|
|
|
struct ssd2825_priv {
|
|
struct spi_device *spi;
|
|
struct device *dev;
|
|
|
|
struct gpio_desc *reset_gpio;
|
|
struct regulator_bulk_data *supplies;
|
|
|
|
struct clk *tx_clk;
|
|
|
|
struct mipi_dsi_host dsi_host;
|
|
struct drm_bridge bridge;
|
|
struct ssd2825_dsi_output output;
|
|
|
|
struct mutex mlock; /* for host transfer operations */
|
|
|
|
u32 pd_lines; /* number of Parallel Port Input Data Lines */
|
|
u32 dsi_lanes; /* number of DSI Lanes */
|
|
|
|
/* Parameters for PLL programming */
|
|
u32 pll_freq_kbps; /* PLL in kbps */
|
|
u32 nibble_freq_khz; /* PLL div by 4 */
|
|
|
|
u32 hzd; /* HS Zero Delay in ns*/
|
|
u32 hpd; /* HS Prepare Delay is ns */
|
|
};
|
|
|
|
static inline struct ssd2825_priv *dsi_host_to_ssd2825(struct mipi_dsi_host *host)
|
|
{
|
|
return container_of(host, struct ssd2825_priv, dsi_host);
|
|
}
|
|
|
|
static inline struct ssd2825_priv *bridge_to_ssd2825(struct drm_bridge *bridge)
|
|
{
|
|
return container_of(bridge, struct ssd2825_priv, bridge);
|
|
}
|
|
|
|
static int ssd2825_write_raw(struct ssd2825_priv *priv, u8 high_byte, u8 low_byte)
|
|
{
|
|
struct spi_device *spi = priv->spi;
|
|
u8 tx_buf[2];
|
|
|
|
/*
|
|
* Low byte is the value, high byte defines type of
|
|
* write cycle, 0 for command and 1 for data.
|
|
*/
|
|
tx_buf[0] = low_byte;
|
|
tx_buf[1] = high_byte;
|
|
|
|
return spi_write(spi, tx_buf, 2);
|
|
}
|
|
|
|
static int ssd2825_write_reg(struct ssd2825_priv *priv, u8 reg, u16 command)
|
|
{
|
|
u8 datal = (command & 0x00FF);
|
|
u8 datah = (command & 0xFF00) >> 8;
|
|
int ret;
|
|
|
|
/* Command write cycle */
|
|
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, reg);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Data write cycle bits 7-0 */
|
|
ret = ssd2825_write_raw(priv, SSD2825_DAT_BYTE, datal);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Data write cycle bits 15-8 */
|
|
ret = ssd2825_write_raw(priv, SSD2825_DAT_BYTE, datah);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssd2825_write_dsi(struct ssd2825_priv *priv, const u8 *command, int len)
|
|
{
|
|
int ret, i;
|
|
|
|
ret = ssd2825_write_reg(priv, SSD2825_PACKET_SIZE_CTRL_REG_1, len);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, SSD2825_PACKET_DROP_REG);
|
|
if (ret)
|
|
return ret;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
ret = ssd2825_write_raw(priv, SSD2825_DAT_BYTE, command[i]);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssd2825_read_raw(struct ssd2825_priv *priv, u8 cmd, u16 *data)
|
|
{
|
|
struct spi_device *spi = priv->spi;
|
|
struct spi_message msg;
|
|
struct spi_transfer xfer[2];
|
|
u8 tx_buf[2];
|
|
u8 rx_buf[2];
|
|
int ret;
|
|
|
|
memset(&xfer, 0, sizeof(xfer));
|
|
|
|
tx_buf[1] = (cmd & 0xFF00) >> 8;
|
|
tx_buf[0] = (cmd & 0x00FF);
|
|
|
|
xfer[0].tx_buf = tx_buf;
|
|
xfer[0].bits_per_word = 9;
|
|
xfer[0].len = 2;
|
|
|
|
xfer[1].rx_buf = rx_buf;
|
|
xfer[1].bits_per_word = 16;
|
|
xfer[1].len = 2;
|
|
|
|
spi_message_init(&msg);
|
|
spi_message_add_tail(&xfer[0], &msg);
|
|
spi_message_add_tail(&xfer[1], &msg);
|
|
|
|
ret = spi_sync(spi, &msg);
|
|
if (ret) {
|
|
dev_err(&spi->dev, "ssd2825 read raw failed %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
*data = rx_buf[1] | (rx_buf[0] << 8);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssd2825_read_reg(struct ssd2825_priv *priv, u8 reg, u16 *data)
|
|
{
|
|
int ret;
|
|
|
|
/* Reset the read register */
|
|
ret = ssd2825_write_reg(priv, SSD2825_SPI_READ_REG, SSD2825_SPI_READ_REG_RESET);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Push the address to read */
|
|
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, reg);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Perform a reading cycle */
|
|
ret = ssd2825_read_raw(priv, SSD2825_SPI_READ_REG_RESET, data);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssd2825_dsi_host_attach(struct mipi_dsi_host *host, struct mipi_dsi_device *dev)
|
|
{
|
|
struct ssd2825_priv *priv = dsi_host_to_ssd2825(host);
|
|
struct drm_bridge *bridge;
|
|
struct drm_panel *panel;
|
|
struct device_node *ep;
|
|
int ret;
|
|
|
|
if (dev->lanes > 4) {
|
|
dev_err(priv->dev, "unsupported number of data lanes(%u)\n", dev->lanes);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* ssd2825 supports both Video and Pulse mode, but the driver only
|
|
* implements Video (event) mode currently
|
|
*/
|
|
if (!(dev->mode_flags & MIPI_DSI_MODE_VIDEO)) {
|
|
dev_err(priv->dev, "Only MIPI_DSI_MODE_VIDEO is supported\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
ret = drm_of_find_panel_or_bridge(host->dev->of_node, 1, 0, &panel, &bridge);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (panel) {
|
|
bridge = drm_panel_bridge_add_typed(panel, DRM_MODE_CONNECTOR_DSI);
|
|
if (IS_ERR(bridge))
|
|
return PTR_ERR(bridge);
|
|
}
|
|
|
|
priv->output.dev = dev;
|
|
priv->output.bridge = bridge;
|
|
priv->output.panel = panel;
|
|
|
|
priv->dsi_lanes = dev->lanes;
|
|
|
|
/* get input ep (port0/endpoint0) */
|
|
ret = -EINVAL;
|
|
ep = of_graph_get_endpoint_by_regs(host->dev->of_node, 0, 0);
|
|
if (ep) {
|
|
ret = of_property_read_u32(ep, "bus-width", &priv->pd_lines);
|
|
of_node_put(ep);
|
|
}
|
|
|
|
if (ret)
|
|
priv->pd_lines = mipi_dsi_pixel_format_to_bpp(dev->format);
|
|
|
|
drm_bridge_add(&priv->bridge);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssd2825_dsi_host_detach(struct mipi_dsi_host *host, struct mipi_dsi_device *dev)
|
|
{
|
|
struct ssd2825_priv *priv = dsi_host_to_ssd2825(host);
|
|
|
|
drm_bridge_remove(&priv->bridge);
|
|
if (priv->output.panel)
|
|
drm_panel_bridge_remove(priv->output.bridge);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t ssd2825_dsi_host_transfer(struct mipi_dsi_host *host,
|
|
const struct mipi_dsi_msg *msg)
|
|
{
|
|
struct ssd2825_priv *priv = dsi_host_to_ssd2825(host);
|
|
u16 config;
|
|
int ret;
|
|
|
|
if (msg->rx_len) {
|
|
dev_warn(priv->dev, "MIPI rx is not supported\n");
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
guard(mutex)(&priv->mlock);
|
|
|
|
ret = ssd2825_read_reg(priv, SSD2825_CONFIGURATION_REG, &config);
|
|
if (ret)
|
|
return ret;
|
|
|
|
switch (msg->type) {
|
|
case MIPI_DSI_DCS_SHORT_WRITE:
|
|
case MIPI_DSI_DCS_SHORT_WRITE_PARAM:
|
|
case MIPI_DSI_DCS_LONG_WRITE:
|
|
config |= SSD2825_CONF_REG_DCS;
|
|
break;
|
|
case MIPI_DSI_GENERIC_SHORT_WRITE_0_PARAM:
|
|
case MIPI_DSI_GENERIC_SHORT_WRITE_1_PARAM:
|
|
case MIPI_DSI_GENERIC_SHORT_WRITE_2_PARAM:
|
|
case MIPI_DSI_GENERIC_LONG_WRITE:
|
|
config &= ~SSD2825_CONF_REG_DCS;
|
|
break;
|
|
case MIPI_DSI_DCS_READ:
|
|
case MIPI_DSI_GENERIC_READ_REQUEST_0_PARAM:
|
|
case MIPI_DSI_GENERIC_READ_REQUEST_1_PARAM:
|
|
case MIPI_DSI_GENERIC_READ_REQUEST_2_PARAM:
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
ret = ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG, config);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0x0000);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = ssd2825_write_dsi(priv, msg->tx_buf, msg->tx_len);
|
|
if (ret)
|
|
return ret;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct mipi_dsi_host_ops ssd2825_dsi_host_ops = {
|
|
.attach = ssd2825_dsi_host_attach,
|
|
.detach = ssd2825_dsi_host_detach,
|
|
.transfer = ssd2825_dsi_host_transfer,
|
|
};
|
|
|
|
static void ssd2825_hw_reset(struct ssd2825_priv *priv)
|
|
{
|
|
gpiod_set_value_cansleep(priv->reset_gpio, 1);
|
|
usleep_range(5000, 6000);
|
|
gpiod_set_value_cansleep(priv->reset_gpio, 0);
|
|
usleep_range(5000, 6000);
|
|
}
|
|
|
|
/*
|
|
* PLL configuration register settings.
|
|
*
|
|
* See the "PLL Configuration Register Description" in the SSD2825 datasheet.
|
|
*/
|
|
static u16 construct_pll_config(struct ssd2825_priv *priv,
|
|
u32 desired_pll_freq_kbps, u32 reference_freq_khz)
|
|
{
|
|
u32 div_factor = 1, mul_factor, fr = 0;
|
|
|
|
while (reference_freq_khz / (div_factor + 1) >= SSD2825_REF_MIN_CLK)
|
|
div_factor++;
|
|
if (div_factor > 31)
|
|
div_factor = 31;
|
|
|
|
mul_factor = DIV_ROUND_UP(desired_pll_freq_kbps * div_factor,
|
|
reference_freq_khz);
|
|
|
|
priv->pll_freq_kbps = reference_freq_khz * mul_factor / div_factor;
|
|
priv->nibble_freq_khz = priv->pll_freq_kbps / 4;
|
|
|
|
if (priv->pll_freq_kbps >= 501000)
|
|
fr = 3;
|
|
else if (priv->pll_freq_kbps >= 251000)
|
|
fr = 2;
|
|
else if (priv->pll_freq_kbps >= 126000)
|
|
fr = 1;
|
|
|
|
return (fr << 14) | (div_factor << 8) | mul_factor;
|
|
}
|
|
|
|
static int ssd2825_setup_pll(struct ssd2825_priv *priv,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
u16 pll_config, lp_div;
|
|
u32 nibble_delay, pclk_mult, tx_freq_khz;
|
|
u8 hzd, hpd;
|
|
|
|
tx_freq_khz = clk_get_rate(priv->tx_clk) / KILO;
|
|
if (!tx_freq_khz)
|
|
tx_freq_khz = SSD2825_REF_MIN_CLK;
|
|
|
|
pclk_mult = priv->pd_lines / priv->dsi_lanes + 1;
|
|
pll_config = construct_pll_config(priv, pclk_mult * mode->clock,
|
|
tx_freq_khz);
|
|
|
|
lp_div = priv->pll_freq_kbps / (SSD2825_LP_MIN_CLK * 8);
|
|
|
|
/* nibble_delay in nanoseconds */
|
|
nibble_delay = MICRO / priv->nibble_freq_khz;
|
|
|
|
hzd = priv->hzd / nibble_delay;
|
|
hpd = (priv->hpd - 4 * nibble_delay) / nibble_delay;
|
|
|
|
/* Disable PLL */
|
|
ssd2825_write_reg(priv, SSD2825_PLL_CTRL_REG, 0x0000);
|
|
ssd2825_write_reg(priv, SSD2825_LINE_CTRL_REG, 0x0001);
|
|
|
|
/* Set delays */
|
|
ssd2825_write_reg(priv, SSD2825_DELAY_ADJ_REG_1, (hzd << 8) | hpd);
|
|
|
|
/* Set PLL coefficients */
|
|
ssd2825_write_reg(priv, SSD2825_PLL_CONFIGURATION_REG, pll_config);
|
|
|
|
/* Clock Control Register */
|
|
ssd2825_write_reg(priv, SSD2825_CLOCK_CTRL_REG,
|
|
SSD2828_LP_CLOCK_DIVIDER(lp_div));
|
|
|
|
/* Enable PLL */
|
|
ssd2825_write_reg(priv, SSD2825_PLL_CTRL_REG, 0x0001);
|
|
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void ssd2825_bridge_atomic_pre_enable(struct drm_bridge *bridge,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
|
struct mipi_dsi_device *dsi_dev = priv->output.dev;
|
|
const struct drm_crtc_state *crtc_state;
|
|
const struct drm_display_mode *mode;
|
|
struct drm_connector *connector;
|
|
struct drm_crtc *crtc;
|
|
u32 input_bus_flags = bridge->timings->input_bus_flags;
|
|
u16 flags = 0, config;
|
|
u8 pixel_format;
|
|
int ret;
|
|
|
|
/* Power Sequence */
|
|
ret = clk_prepare_enable(priv->tx_clk);
|
|
if (ret)
|
|
dev_err(priv->dev, "error enabling tx_clk (%d)\n", ret);
|
|
|
|
ret = regulator_bulk_enable(ARRAY_SIZE(ssd2825_supplies), priv->supplies);
|
|
if (ret)
|
|
dev_err(priv->dev, "error enabling regulators (%d)\n", ret);
|
|
|
|
usleep_range(1000, 2000);
|
|
|
|
ssd2825_hw_reset(priv);
|
|
|
|
/* Perform SW reset */
|
|
ssd2825_write_reg(priv, SSD2825_OPERATION_CTRL_REG, 0x0100);
|
|
|
|
/* Set pixel format */
|
|
switch (dsi_dev->format) {
|
|
case MIPI_DSI_FMT_RGB565:
|
|
pixel_format = 0x00;
|
|
break;
|
|
case MIPI_DSI_FMT_RGB666_PACKED:
|
|
pixel_format = 0x01;
|
|
break;
|
|
case MIPI_DSI_FMT_RGB666:
|
|
pixel_format = 0x02;
|
|
break;
|
|
case MIPI_DSI_FMT_RGB888:
|
|
default:
|
|
pixel_format = 0x03;
|
|
break;
|
|
}
|
|
|
|
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
|
|
crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
|
|
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
|
mode = &crtc_state->adjusted_mode;
|
|
|
|
/* Set panel timings */
|
|
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_1,
|
|
((mode->vtotal - mode->vsync_end) << 8) |
|
|
(mode->htotal - mode->hsync_end));
|
|
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_2,
|
|
((mode->vtotal - mode->vsync_start) << 8) |
|
|
(mode->htotal - mode->hsync_start));
|
|
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_3,
|
|
((mode->vsync_start - mode->vdisplay) << 8) |
|
|
(mode->hsync_start - mode->hdisplay));
|
|
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_4, mode->hdisplay);
|
|
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_5, mode->vdisplay);
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_PHSYNC)
|
|
flags |= SSD2825_HSYNC_HIGH;
|
|
|
|
if (mode->flags & DRM_MODE_FLAG_PVSYNC)
|
|
flags |= SSD2825_VSYNC_HIGH;
|
|
|
|
if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO)
|
|
flags |= SSD2825_NON_BURST_EV;
|
|
|
|
if (input_bus_flags & DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE)
|
|
flags |= SSD2825_PCKL_HIGH;
|
|
|
|
ssd2825_write_reg(priv, SSD2825_RGB_INTERFACE_CTRL_REG_6, flags | pixel_format);
|
|
ssd2825_write_reg(priv, SSD2825_LANE_CONFIGURATION_REG, dsi_dev->lanes - 1);
|
|
ssd2825_write_reg(priv, SSD2825_TEST_REG, 0x0004);
|
|
|
|
/* Call PLL configuration */
|
|
ssd2825_setup_pll(priv, mode);
|
|
|
|
usleep_range(10000, 11000);
|
|
|
|
config = SSD2825_CONF_REG_HS | SSD2825_CONF_REG_CKE | SSD2825_CONF_REG_DCS |
|
|
SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT;
|
|
|
|
if (dsi_dev->mode_flags & MIPI_DSI_MODE_LPM)
|
|
config &= ~SSD2825_CONF_REG_HS;
|
|
|
|
if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
|
|
config &= ~SSD2825_CONF_REG_EOT;
|
|
|
|
/* Initial DSI configuration register set */
|
|
ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG, config);
|
|
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0);
|
|
|
|
if (priv->output.panel)
|
|
drm_panel_enable(priv->output.panel);
|
|
}
|
|
|
|
static void ssd2825_bridge_atomic_enable(struct drm_bridge *bridge,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
|
struct mipi_dsi_device *dsi_dev = priv->output.dev;
|
|
u16 config;
|
|
|
|
config = SSD2825_CONF_REG_HS | SSD2825_CONF_REG_DCS |
|
|
SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT;
|
|
|
|
if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO)
|
|
config |= SSD2825_CONF_REG_VEN;
|
|
|
|
if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
|
|
config &= ~SSD2825_CONF_REG_EOT;
|
|
|
|
/* Complete configuration after DSI commands were sent */
|
|
ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG, config);
|
|
ssd2825_write_reg(priv, SSD2825_PLL_CTRL_REG, 0x0001);
|
|
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0x0000);
|
|
}
|
|
|
|
static void ssd2825_bridge_atomic_disable(struct drm_bridge *bridge,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
|
int ret;
|
|
|
|
msleep(100);
|
|
|
|
/* Exit DSI configuration register set */
|
|
ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG,
|
|
SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT);
|
|
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0);
|
|
|
|
/* HW disable */
|
|
gpiod_set_value_cansleep(priv->reset_gpio, 1);
|
|
usleep_range(5000, 6000);
|
|
|
|
ret = regulator_bulk_disable(ARRAY_SIZE(ssd2825_supplies),
|
|
priv->supplies);
|
|
if (ret < 0)
|
|
dev_err(priv->dev, "error disabling regulators (%d)\n", ret);
|
|
|
|
clk_disable_unprepare(priv->tx_clk);
|
|
}
|
|
|
|
static int ssd2825_bridge_attach(struct drm_bridge *bridge, struct drm_encoder *encoder,
|
|
enum drm_bridge_attach_flags flags)
|
|
{
|
|
struct ssd2825_priv *priv = bridge_to_ssd2825(bridge);
|
|
|
|
return drm_bridge_attach(bridge->encoder, priv->output.bridge, bridge,
|
|
flags);
|
|
}
|
|
|
|
static enum drm_mode_status
|
|
ssd2825_bridge_mode_valid(struct drm_bridge *bridge,
|
|
const struct drm_display_info *info,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
if (mode->hdisplay > 1366)
|
|
return MODE_H_ILLEGAL;
|
|
|
|
if (mode->vdisplay > 1366)
|
|
return MODE_V_ILLEGAL;
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static bool ssd2825_mode_fixup(struct drm_bridge *bridge,
|
|
const struct drm_display_mode *mode,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
/* Default to positive sync */
|
|
|
|
if (!(adjusted_mode->flags &
|
|
(DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NHSYNC)))
|
|
adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC;
|
|
|
|
if (!(adjusted_mode->flags &
|
|
(DRM_MODE_FLAG_PVSYNC | DRM_MODE_FLAG_NVSYNC)))
|
|
adjusted_mode->flags |= DRM_MODE_FLAG_PVSYNC;
|
|
|
|
return true;
|
|
}
|
|
|
|
static const struct drm_bridge_funcs ssd2825_bridge_funcs = {
|
|
.attach = ssd2825_bridge_attach,
|
|
.mode_valid = ssd2825_bridge_mode_valid,
|
|
.mode_fixup = ssd2825_mode_fixup,
|
|
|
|
.atomic_pre_enable = ssd2825_bridge_atomic_pre_enable,
|
|
.atomic_enable = ssd2825_bridge_atomic_enable,
|
|
.atomic_disable = ssd2825_bridge_atomic_disable,
|
|
|
|
.atomic_reset = drm_atomic_helper_bridge_reset,
|
|
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
|
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
|
};
|
|
|
|
static const struct drm_bridge_timings default_ssd2825_timings = {
|
|
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE
|
|
| DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
|
|
| DRM_BUS_FLAG_DE_HIGH,
|
|
};
|
|
|
|
static int ssd2825_probe(struct spi_device *spi)
|
|
{
|
|
struct ssd2825_priv *priv;
|
|
struct device *dev = &spi->dev;
|
|
struct device_node *np = dev->of_node;
|
|
int ret;
|
|
|
|
/* Driver supports only 8 bit 3 Wire mode */
|
|
spi->bits_per_word = 9;
|
|
|
|
ret = spi_setup(spi);
|
|
if (ret)
|
|
return ret;
|
|
|
|
priv = devm_drm_bridge_alloc(dev, struct ssd2825_priv, bridge, &ssd2825_bridge_funcs);
|
|
if (IS_ERR(priv))
|
|
return PTR_ERR(priv);
|
|
|
|
spi_set_drvdata(spi, priv);
|
|
|
|
priv->spi = spi;
|
|
priv->dev = dev;
|
|
|
|
mutex_init(&priv->mlock);
|
|
|
|
priv->tx_clk = devm_clk_get_optional(dev, NULL);
|
|
if (IS_ERR(priv->tx_clk))
|
|
return dev_err_probe(dev, PTR_ERR(priv->tx_clk),
|
|
"can't retrieve bridge tx_clk\n");
|
|
|
|
priv->reset_gpio = devm_gpiod_get_optional(dev, "reset",
|
|
GPIOD_OUT_HIGH);
|
|
if (IS_ERR(priv->reset_gpio))
|
|
return dev_err_probe(dev, PTR_ERR(priv->reset_gpio),
|
|
"failed to get reset GPIO\n");
|
|
|
|
ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(ssd2825_supplies),
|
|
ssd2825_supplies, &priv->supplies);
|
|
if (ret)
|
|
return dev_err_probe(dev, ret, "failed to get regulators\n");
|
|
|
|
priv->hzd = 133; /* ns */
|
|
device_property_read_u32(dev, "solomon,hs-zero-delay-ns", &priv->hzd);
|
|
|
|
priv->hpd = 40; /* ns */
|
|
device_property_read_u32(dev, "solomon,hs-prep-delay-ns", &priv->hpd);
|
|
|
|
priv->dsi_host.dev = dev;
|
|
priv->dsi_host.ops = &ssd2825_dsi_host_ops;
|
|
|
|
priv->bridge.timings = &default_ssd2825_timings;
|
|
priv->bridge.of_node = np;
|
|
|
|
return mipi_dsi_host_register(&priv->dsi_host);
|
|
}
|
|
|
|
static void ssd2825_remove(struct spi_device *spi)
|
|
{
|
|
struct ssd2825_priv *priv = spi_get_drvdata(spi);
|
|
|
|
mipi_dsi_host_unregister(&priv->dsi_host);
|
|
}
|
|
|
|
static const struct of_device_id ssd2825_of_match[] = {
|
|
{ .compatible = "solomon,ssd2825" },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(of, ssd2825_of_match);
|
|
|
|
static struct spi_driver ssd2825_driver = {
|
|
.driver = {
|
|
.name = "ssd2825",
|
|
.of_match_table = ssd2825_of_match,
|
|
},
|
|
.probe = ssd2825_probe,
|
|
.remove = ssd2825_remove,
|
|
};
|
|
module_spi_driver(ssd2825_driver);
|
|
|
|
MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
|
|
MODULE_DESCRIPTION("Solomon SSD2825 RGB to MIPI-DSI bridge driver SPI");
|
|
MODULE_LICENSE("GPL");
|