#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
#define SSD2825_REF_MIN_CLK 2000
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;
u32 pd_lines;
u32 dsi_lanes;
u32 pll_freq_kbps;
u32 nibble_freq_khz;
u32 hzd;
u32 hpd;
};
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];
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;
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, reg);
if (ret)
return ret;
ret = ssd2825_write_raw(priv, SSD2825_DAT_BYTE, datal);
if (ret)
return ret;
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;
ret = ssd2825_write_reg(priv, SSD2825_SPI_READ_REG, SSD2825_SPI_READ_REG_RESET);
if (ret)
return ret;
ret = ssd2825_write_raw(priv, SSD2825_COM_BYTE, reg);
if (ret)
return ret;
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;
}
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;
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);
}
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 = MICRO / priv->nibble_freq_khz;
hzd = priv->hzd / nibble_delay;
hpd = (priv->hpd - 4 * nibble_delay) / nibble_delay;
ssd2825_write_reg(priv, SSD2825_PLL_CTRL_REG, 0x0000);
ssd2825_write_reg(priv, SSD2825_LINE_CTRL_REG, 0x0001);
ssd2825_write_reg(priv, SSD2825_DELAY_ADJ_REG_1, (hzd << 8) | hpd);
ssd2825_write_reg(priv, SSD2825_PLL_CONFIGURATION_REG, pll_config);
ssd2825_write_reg(priv, SSD2825_CLOCK_CTRL_REG,
SSD2828_LP_CLOCK_DIVIDER(lp_div));
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;
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);
ssd2825_write_reg(priv, SSD2825_OPERATION_CTRL_REG, 0x0100);
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;
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);
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;
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;
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);
ssd2825_write_reg(priv, SSD2825_CONFIGURATION_REG,
SSD2825_CONF_REG_ECD | SSD2825_CONF_REG_EOT);
ssd2825_write_reg(priv, SSD2825_VC_CTRL_REG, 0);
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)
{
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;
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;
device_property_read_u32(dev, "solomon,hs-zero-delay-ns", &priv->hzd);
priv->hpd = 40;
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" },
{ }
};
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 <[email protected]>");
MODULE_DESCRIPTION("Solomon SSD2825 RGB to MIPI-DSI bridge driver SPI");
MODULE_LICENSE("GPL");