// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
* Copyright (c) 2019-2020. Linaro Limited.
*/
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <sound/hdmi-codec.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_edid.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#define EDID_SEG_SIZE 256
#define EDID_LEN 32
#define EDID_LOOP 8
#define KEY_DDC_ACCS_DONE 0 x02
#define DDC_NO_ACK 0 x50
#define LT9611_4LANES 0
struct lt9611 {
struct device *dev;
struct drm_bridge bridge;
struct drm_bridge *next_bridge;
struct regmap *regmap;
struct device_node *dsi0_node;
struct device_node *dsi1_node;
struct mipi_dsi_device *dsi0;
struct mipi_dsi_device *dsi1;
bool ac_mode;
struct gpio_desc *reset_gpio;
struct gpio_desc *enable_gpio;
bool power_on;
bool sleep;
struct regulator_bulk_data supplies[2 ];
struct i2c_client *client;
enum drm_connector_status status;
u8 edid_buf[EDID_SEG_SIZE];
};
#define LT9611_PAGE_CONTROL 0 xff
static const struct regmap_range_cfg lt9611_ranges[] = {
{
.name = "register_range" ,
.range_min = 0 ,
.range_max = 0 x85ff,
.selector_reg = LT9611_PAGE_CONTROL,
.selector_mask = 0 xff,
.selector_shift = 0 ,
.window_start = 0 ,
.window_len = 0 x100,
},
};
static const struct regmap_config lt9611_regmap_config = {
.reg_bits = 8 ,
.val_bits = 8 ,
.max_register = 0 xffff,
.ranges = lt9611_ranges,
.num_ranges = ARRAY_SIZE(lt9611_ranges),
};
static struct lt9611 *bridge_to_lt9611(struct drm_bridge *bridge)
{
return container_of(bridge, struct lt9611, bridge);
}
static int lt9611_mipi_input_analog(struct lt9611 *lt9611)
{
const struct reg_sequence reg_cfg[] = {
{ 0 x8106, 0 x40 }, /* port A rx current */
{ 0 x810a, 0 xfe }, /* port A ldo voltage set */
{ 0 x810b, 0 xbf }, /* enable port A lprx */
{ 0 x8111, 0 x40 }, /* port B rx current */
{ 0 x8115, 0 xfe }, /* port B ldo voltage set */
{ 0 x8116, 0 xbf }, /* enable port B lprx */
{ 0 x811c, 0 x03 }, /* PortA clk lane no-LP mode */
{ 0 x8120, 0 x03 }, /* PortB clk lane with-LP mode */
};
return regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg));
}
static int lt9611_mipi_input_digital(struct lt9611 *lt9611,
const struct drm_display_mode *mode)
{
struct reg_sequence reg_cfg[] = {
{ 0 x8300, LT9611_4LANES },
{ 0 x830a, 0 x00 },
{ 0 x824f, 0 x80 },
{ 0 x8250, 0 x10 },
{ 0 x8302, 0 x0a },
{ 0 x8306, 0 x0a },
};
if (lt9611->dsi1_node)
reg_cfg[1 ].def = 0 x03;
return regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg));
}
static void lt9611_mipi_video_setup(struct lt9611 *lt9611,
const struct drm_display_mode *mode)
{
u32 h_total, hactive, hsync_len, hfront_porch, hsync_porch;
u32 v_total, vactive, vsync_len, vfront_porch, vsync_porch;
h_total = mode->htotal;
v_total = mode->vtotal;
hactive = mode->hdisplay;
hsync_len = mode->hsync_end - mode->hsync_start;
hfront_porch = mode->hsync_start - mode->hdisplay;
hsync_porch = mode->htotal - mode->hsync_start;
vactive = mode->vdisplay;
vsync_len = mode->vsync_end - mode->vsync_start;
vfront_porch = mode->vsync_start - mode->vdisplay;
vsync_porch = mode->vtotal - mode->vsync_start;
regmap_write(lt9611->regmap, 0 x830d, (u8)(v_total / 256 ));
regmap_write(lt9611->regmap, 0 x830e, (u8)(v_total % 256 ));
regmap_write(lt9611->regmap, 0 x830f, (u8)(vactive / 256 ));
regmap_write(lt9611->regmap, 0 x8310, (u8)(vactive % 256 ));
regmap_write(lt9611->regmap, 0 x8311, (u8)(h_total / 256 ));
regmap_write(lt9611->regmap, 0 x8312, (u8)(h_total % 256 ));
regmap_write(lt9611->regmap, 0 x8313, (u8)(hactive / 256 ));
regmap_write(lt9611->regmap, 0 x8314, (u8)(hactive % 256 ));
regmap_write(lt9611->regmap, 0 x8315, (u8)(vsync_len % 256 ));
regmap_write(lt9611->regmap, 0 x8316, (u8)(hsync_len % 256 ));
regmap_write(lt9611->regmap, 0 x8317, (u8)(vfront_porch % 256 ));
regmap_write(lt9611->regmap, 0 x8318, (u8)(vsync_porch % 256 ));
regmap_write(lt9611->regmap, 0 x8319, (u8)(hfront_porch % 256 ));
regmap_write(lt9611->regmap, 0 x831a, (u8)(hsync_porch / 256 ) |
((hfront_porch / 256 ) << 4 ));
regmap_write(lt9611->regmap, 0 x831b, (u8)(hsync_porch % 256 ));
}
static void lt9611_pcr_setup(struct lt9611 *lt9611, const struct drm_display_mode *mode, unsigned int postdiv)
{
unsigned int pcr_m = mode->clock * 5 * postdiv / 27000 ;
const struct reg_sequence reg_cfg[] = {
{ 0 x830b, 0 x01 },
{ 0 x830c, 0 x10 },
{ 0 x8348, 0 x00 },
{ 0 x8349, 0 x81 },
/* stage 1 */
{ 0 x8321, 0 x4a },
{ 0 x8324, 0 x71 },
{ 0 x8325, 0 x30 },
{ 0 x832a, 0 x01 },
/* stage 2 */
{ 0 x834a, 0 x40 },
/* MK limit */
{ 0 x832d, 0 x38 },
{ 0 x8331, 0 x08 },
};
u8 pol = 0 x10;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
pol |= 0 x2;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
pol |= 0 x1;
regmap_write(lt9611->regmap, 0 x831d, pol);
regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg));
if (lt9611->dsi1_node) {
unsigned int hact = mode->hdisplay;
hact >>= 2 ;
hact += 0 x50;
hact = min(hact, 0 x3e0U);
regmap_write(lt9611->regmap, 0 x830b, hact / 256 );
regmap_write(lt9611->regmap, 0 x830c, hact % 256 );
regmap_write(lt9611->regmap, 0 x8348, hact / 256 );
regmap_write(lt9611->regmap, 0 x8349, hact % 256 );
}
regmap_write(lt9611->regmap, 0 x8326, pcr_m);
/* pcr rst */
regmap_write(lt9611->regmap, 0 x8011, 0 x5a);
regmap_write(lt9611->regmap, 0 x8011, 0 xfa);
}
static int lt9611_pll_setup(struct lt9611 *lt9611, const struct drm_display_mode *mode, unsigned int *postdiv)
{
unsigned int pclk = mode->clock;
const struct reg_sequence reg_cfg[] = {
/* txpll init */
{ 0 x8123, 0 x40 },
{ 0 x8124, 0 x64 },
{ 0 x8125, 0 x80 },
{ 0 x8126, 0 x55 },
{ 0 x812c, 0 x37 },
{ 0 x812f, 0 x01 },
{ 0 x8126, 0 x55 },
{ 0 x8127, 0 x66 },
{ 0 x8128, 0 x88 },
{ 0 x812a, 0 x20 },
};
regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg));
if (pclk > 150000 ) {
regmap_write(lt9611->regmap, 0 x812d, 0 x88);
*postdiv = 1 ;
} else if (pclk > 70000 ) {
regmap_write(lt9611->regmap, 0 x812d, 0 x99);
*postdiv = 2 ;
} else {
regmap_write(lt9611->regmap, 0 x812d, 0 xaa);
*postdiv = 4 ;
}
/*
* first divide pclk by 2 first
* - write divide by 64k to 19:16 bits which means shift by 17
* - write divide by 256 to 15:8 bits which means shift by 9
* - write remainder to 7:0 bits, which means shift by 1
*/
regmap_write(lt9611->regmap, 0 x82e3, pclk >> 17 ); /* pclk[19:16] */
regmap_write(lt9611->regmap, 0 x82e4, pclk >> 9 ); /* pclk[15:8] */
regmap_write(lt9611->regmap, 0 x82e5, pclk >> 1 ); /* pclk[7:0] */
regmap_write(lt9611->regmap, 0 x82de, 0 x20);
regmap_write(lt9611->regmap, 0 x82de, 0 xe0);
regmap_write(lt9611->regmap, 0 x8016, 0 xf1);
regmap_write(lt9611->regmap, 0 x8016, 0 xf3);
return 0 ;
}
static int lt9611_read_video_check(struct lt9611 *lt9611, unsigned int reg)
{
unsigned int temp, temp2;
int ret;
ret = regmap_read(lt9611->regmap, reg, &temp);
if (ret)
return ret;
temp <<= 8 ;
ret = regmap_read(lt9611->regmap, reg + 1 , &temp2);
if (ret)
return ret;
return (temp + temp2);
}
static int lt9611_video_check(struct lt9611 *lt9611)
{
u32 v_total, vactive, hactive_a, hactive_b, h_total_sysclk;
int temp;
/* top module video check */
/* vactive */
temp = lt9611_read_video_check(lt9611, 0 x8282);
if (temp < 0 )
goto end;
vactive = temp;
/* v_total */
temp = lt9611_read_video_check(lt9611, 0 x826c);
if (temp < 0 )
goto end;
v_total = temp;
/* h_total_sysclk */
temp = lt9611_read_video_check(lt9611, 0 x8286);
if (temp < 0 )
goto end;
h_total_sysclk = temp;
/* hactive_a */
temp = lt9611_read_video_check(lt9611, 0 x8382);
if (temp < 0 )
goto end;
hactive_a = temp / 3 ;
/* hactive_b */
temp = lt9611_read_video_check(lt9611, 0 x8386);
if (temp < 0 )
goto end;
hactive_b = temp / 3 ;
dev_info(lt9611->dev,
"video check: hactive_a=%d, hactive_b=%d, vactive=%d, v_total=%d, h_total_sysclk=%d\n" ,
hactive_a, hactive_b, vactive, v_total, h_total_sysclk);
return 0 ;
end:
dev_err(lt9611->dev, "read video check error\n" );
return temp;
}
static void lt9611_hdmi_tx_digital(struct lt9611 *lt9611, bool is_hdmi)
{
if (is_hdmi)
regmap_write(lt9611->regmap, 0 x82d6, 0 x8c);
else
regmap_write(lt9611->regmap, 0 x82d6, 0 x0c);
regmap_write(lt9611->regmap, 0 x82d7, 0 x04);
}
static void lt9611_hdmi_tx_phy(struct lt9611 *lt9611)
{
struct reg_sequence reg_cfg[] = {
{ 0 x8130, 0 x6a },
{ 0 x8131, 0 x44 }, /* HDMI DC mode */
{ 0 x8132, 0 x4a },
{ 0 x8133, 0 x0b },
{ 0 x8134, 0 x00 },
{ 0 x8135, 0 x00 },
{ 0 x8136, 0 x00 },
{ 0 x8137, 0 x44 },
{ 0 x813f, 0 x0f },
{ 0 x8140, 0 xa0 },
{ 0 x8141, 0 xa0 },
{ 0 x8142, 0 xa0 },
{ 0 x8143, 0 xa0 },
{ 0 x8144, 0 x0a },
};
/* HDMI AC mode */
if (lt9611->ac_mode)
reg_cfg[2 ].def = 0 x73;
regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg));
}
static irqreturn_t lt9611_irq_thread_handler(int irq, void *dev_id)
{
struct lt9611 *lt9611 = dev_id;
unsigned int irq_flag0 = 0 ;
unsigned int irq_flag3 = 0 ;
regmap_read(lt9611->regmap, 0 x820f, &irq_flag3);
regmap_read(lt9611->regmap, 0 x820c, &irq_flag0);
/* hpd changed low */
if (irq_flag3 & 0 x80) {
dev_info(lt9611->dev, "hdmi cable disconnected\n" );
regmap_write(lt9611->regmap, 0 x8207, 0 xbf);
regmap_write(lt9611->regmap, 0 x8207, 0 x3f);
}
/* hpd changed high */
if (irq_flag3 & 0 x40) {
dev_info(lt9611->dev, "hdmi cable connected\n" );
regmap_write(lt9611->regmap, 0 x8207, 0 x7f);
regmap_write(lt9611->regmap, 0 x8207, 0 x3f);
}
if (irq_flag3 & 0 xc0 && lt9611->bridge.dev)
drm_kms_helper_hotplug_event(lt9611->bridge.dev);
/* video input changed */
if (irq_flag0 & 0 x01) {
dev_info(lt9611->dev, "video input changed\n" );
regmap_write(lt9611->regmap, 0 x829e, 0 xff);
regmap_write(lt9611->regmap, 0 x829e, 0 xf7);
regmap_write(lt9611->regmap, 0 x8204, 0 xff);
regmap_write(lt9611->regmap, 0 x8204, 0 xfe);
}
return IRQ_HANDLED;
}
static void lt9611_enable_hpd_interrupts(struct lt9611 *lt9611)
{
unsigned int val;
regmap_read(lt9611->regmap, 0 x8203, &val);
val &= ~0 xc0;
regmap_write(lt9611->regmap, 0 x8203, val);
regmap_write(lt9611->regmap, 0 x8207, 0 xff); /* clear */
regmap_write(lt9611->regmap, 0 x8207, 0 x3f);
}
static void lt9611_sleep_setup(struct lt9611 *lt9611)
{
const struct reg_sequence sleep_setup[] = {
{ 0 x8024, 0 x76 },
{ 0 x8023, 0 x01 },
{ 0 x8157, 0 x03 }, /* set addr pin as output */
{ 0 x8149, 0 x0b },
{ 0 x8102, 0 x48 }, /* MIPI Rx power down */
{ 0 x8123, 0 x80 },
{ 0 x8130, 0 x00 },
{ 0 x8011, 0 x0a },
};
regmap_multi_reg_write(lt9611->regmap,
sleep_setup, ARRAY_SIZE(sleep_setup));
lt9611->sleep = true ;
}
static int lt9611_power_on(struct lt9611 *lt9611)
{
int ret;
const struct reg_sequence seq[] = {
/* LT9611_System_Init */
{ 0 x8101, 0 x18 }, /* sel xtal clock */
/* timer for frequency meter */
{ 0 x821b, 0 x69 }, /* timer 2 */
{ 0 x821c, 0 x78 },
{ 0 x82cb, 0 x69 }, /* timer 1 */
{ 0 x82cc, 0 x78 },
/* irq init */
{ 0 x8251, 0 x01 },
{ 0 x8258, 0 x0a }, /* hpd irq */
{ 0 x8259, 0 x80 }, /* hpd debounce width */
{ 0 x829e, 0 xf7 }, /* video check irq */
/* power consumption for work */
{ 0 x8004, 0 xf0 },
{ 0 x8006, 0 xf0 },
{ 0 x800a, 0 x80 },
{ 0 x800b, 0 x40 },
{ 0 x800d, 0 xef },
{ 0 x8011, 0 xfa },
};
if (lt9611->power_on)
return 0 ;
ret = regmap_multi_reg_write(lt9611->regmap, seq, ARRAY_SIZE(seq));
if (!ret)
lt9611->power_on = true ;
return ret;
}
static int lt9611_power_off(struct lt9611 *lt9611)
{
int ret;
ret = regmap_write(lt9611->regmap, 0 x8130, 0 x6a);
if (!ret)
lt9611->power_on = false ;
return ret;
}
static void lt9611_reset(struct lt9611 *lt9611)
{
gpiod_set_value_cansleep(lt9611->reset_gpio, 1 );
msleep(20 );
gpiod_set_value_cansleep(lt9611->reset_gpio, 0 );
msleep(20 );
gpiod_set_value_cansleep(lt9611->reset_gpio, 1 );
msleep(100 );
}
static void lt9611_assert_5v(struct lt9611 *lt9611)
{
if (!lt9611->enable_gpio)
return ;
gpiod_set_value_cansleep(lt9611->enable_gpio, 1 );
msleep(20 );
}
static int lt9611_regulator_init(struct lt9611 *lt9611)
{
int ret;
lt9611->supplies[0 ].supply = "vdd" ;
lt9611->supplies[1 ].supply = "vcc" ;
ret = devm_regulator_bulk_get(lt9611->dev, 2 , lt9611->supplies);
if (ret < 0 )
return ret;
return regulator_set_load(lt9611->supplies[0 ].consumer, 300000 );
}
static int lt9611_regulator_enable(struct lt9611 *lt9611)
{
int ret;
ret = regulator_enable(lt9611->supplies[0 ].consumer);
if (ret < 0 )
return ret;
usleep_range(1000 , 10000 );
ret = regulator_enable(lt9611->supplies[1 ].consumer);
if (ret < 0 ) {
regulator_disable(lt9611->supplies[0 ].consumer);
return ret;
}
return 0 ;
}
static enum drm_connector_status
lt9611_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
unsigned int reg_val = 0 ;
int connected = 0 ;
regmap_read(lt9611->regmap, 0 x825e, ®_val);
connected = (reg_val & (BIT(2 ) | BIT(0 )));
lt9611->status = connected ? connector_status_connected :
connector_status_disconnected;
return lt9611->status;
}
static int lt9611_read_edid(struct lt9611 *lt9611)
{
unsigned int temp;
int ret = 0 ;
int i, j;
/* memset to clear old buffer, if any */
memset(lt9611->edid_buf, 0 , sizeof (lt9611->edid_buf));
regmap_write(lt9611->regmap, 0 x8503, 0 xc9);
/* 0xA0 is EDID device address */
regmap_write(lt9611->regmap, 0 x8504, 0 xa0);
/* 0x00 is EDID offset address */
regmap_write(lt9611->regmap, 0 x8505, 0 x00);
/* length for read */
regmap_write(lt9611->regmap, 0 x8506, EDID_LEN);
regmap_write(lt9611->regmap, 0 x8514, 0 x7f);
for (i = 0 ; i < EDID_LOOP; i++) {
/* offset address */
regmap_write(lt9611->regmap, 0 x8505, i * EDID_LEN);
regmap_write(lt9611->regmap, 0 x8507, 0 x36);
regmap_write(lt9611->regmap, 0 x8507, 0 x31);
regmap_write(lt9611->regmap, 0 x8507, 0 x37);
usleep_range(5000 , 10000 );
regmap_read(lt9611->regmap, 0 x8540, &temp);
if (temp & KEY_DDC_ACCS_DONE) {
for (j = 0 ; j < EDID_LEN; j++) {
regmap_read(lt9611->regmap, 0 x8583, &temp);
lt9611->edid_buf[i * EDID_LEN + j] = temp;
}
} else if (temp & DDC_NO_ACK) { /* DDC No Ack or Abitration lost */
dev_err(lt9611->dev, "read edid failed: no ack\n" );
ret = -EIO;
goto end;
} else {
dev_err(lt9611->dev, "read edid failed: access not done\n" );
ret = -EIO;
goto end;
}
}
end:
regmap_write(lt9611->regmap, 0 x8507, 0 x1f);
return ret;
}
static int
lt9611_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len)
{
struct lt9611 *lt9611 = data;
int ret;
if (len > 128 )
return -EINVAL;
/* supports up to 1 extension block */
/* TODO: add support for more extension blocks */
if (block > 1 )
return -EINVAL;
if (block == 0 ) {
ret = lt9611_read_edid(lt9611);
if (ret) {
dev_err(lt9611->dev, "edid read failed\n" );
return ret;
}
}
block %= 2 ;
memcpy(buf, lt9611->edid_buf + (block * 128 ), len);
return 0 ;
}
/* bridge funcs */
static void lt9611_bridge_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
struct drm_connector *connector;
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
struct drm_display_mode *mode;
unsigned int postdiv;
connector = drm_atomic_get_new_connector_for_encoder(state, bridge->encoder);
if (WARN_ON(!connector))
return ;
conn_state = drm_atomic_get_new_connector_state(state, connector);
if (WARN_ON(!conn_state))
return ;
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
if (WARN_ON(!crtc_state))
return ;
mode = &crtc_state->adjusted_mode;
lt9611_mipi_input_digital(lt9611, mode);
lt9611_pll_setup(lt9611, mode, &postdiv);
lt9611_mipi_video_setup(lt9611, mode);
lt9611_pcr_setup(lt9611, mode, postdiv);
if (lt9611_power_on(lt9611)) {
dev_err(lt9611->dev, "power on failed\n" );
return ;
}
lt9611_mipi_input_analog(lt9611);
drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
lt9611_hdmi_tx_digital(lt9611, connector->display_info.is_hdmi);
lt9611_hdmi_tx_phy(lt9611);
msleep(500 );
lt9611_video_check(lt9611);
/* Enable HDMI output */
regmap_write(lt9611->regmap, 0 x8130, 0 xea);
}
static void lt9611_bridge_atomic_disable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
int ret;
/* Disable HDMI output */
ret = regmap_write(lt9611->regmap, 0 x8130, 0 x6a);
if (ret) {
dev_err(lt9611->dev, "video on failed\n" );
return ;
}
if (lt9611_power_off(lt9611)) {
dev_err(lt9611->dev, "power on failed\n" );
return ;
}
}
static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611,
struct device_node *dsi_node)
{
const struct mipi_dsi_device_info info = { "lt9611" , 0 , lt9611->dev->of_node};
struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host;
struct device *dev = lt9611->dev;
int ret;
host = of_find_mipi_dsi_host_by_node(dsi_node);
if (!host)
return ERR_PTR(dev_err_probe(lt9611->dev, -EPROBE_DEFER, "failed to find dsi host\n" ));
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) {
dev_err(lt9611->dev, "failed to create dsi device\n" );
return dsi;
}
dsi->lanes = 4 ;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_VIDEO_HSE;
ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0 ) {
dev_err(dev, "failed to attach dsi to host\n" );
return ERR_PTR(ret);
}
return dsi;
}
static int lt9611_bridge_attach(struct drm_bridge *bridge,
struct drm_encoder *encoder,
enum drm_bridge_attach_flags flags)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
return drm_bridge_attach(encoder, lt9611->next_bridge,
bridge, flags);
}
static enum drm_mode_status lt9611_bridge_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
if (mode->hdisplay > 3840 )
return MODE_BAD_HVALUE;
if (mode->hdisplay > 2000 && !lt9611->dsi1_node)
return MODE_PANEL;
return MODE_OK;
}
static void lt9611_bridge_atomic_pre_enable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
static const struct reg_sequence reg_cfg[] = {
{ 0 x8102, 0 x12 },
{ 0 x8123, 0 x40 },
{ 0 x8130, 0 xea },
{ 0 x8011, 0 xfa },
};
if (!lt9611->sleep)
return ;
regmap_multi_reg_write(lt9611->regmap,
reg_cfg, ARRAY_SIZE(reg_cfg));
lt9611->sleep = false ;
}
static void lt9611_bridge_atomic_post_disable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
lt9611_sleep_setup(lt9611);
}
static const struct drm_edid *lt9611_bridge_edid_read(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
lt9611_power_on(lt9611);
return drm_edid_read_custom(connector, lt9611_get_edid_block, lt9611);
}
static void lt9611_bridge_hpd_enable(struct drm_bridge *bridge)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
lt9611_enable_hpd_interrupts(lt9611);
}
#define MAX_INPUT_SEL_FORMATS 1
static u32 *
lt9611_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
struct drm_bridge_state *bridge_state,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state,
u32 output_fmt,
unsigned int *num_input_fmts)
{
u32 *input_fmts;
*num_input_fmts = 0 ;
input_fmts = kcalloc(MAX_INPUT_SEL_FORMATS, sizeof (*input_fmts),
GFP_KERNEL);
if (!input_fmts)
return NULL;
/* This is the DSI-end bus format */
input_fmts[0 ] = MEDIA_BUS_FMT_RGB888_1X24;
*num_input_fmts = 1 ;
return input_fmts;
}
/*
* Other working frames:
* - 0x01, 0x84df
* - 0x04, 0x84c0
*/
#define LT9611_INFOFRAME_AUDIO 0 x02
#define LT9611_INFOFRAME_AVI 0 x08
#define LT9611_INFOFRAME_SPD 0 x10
#define LT9611_INFOFRAME_VENDOR 0 x20
static int lt9611_hdmi_clear_infoframe(struct drm_bridge *bridge,
enum hdmi_infoframe_type type)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
unsigned int mask;
switch (type) {
case HDMI_INFOFRAME_TYPE_AUDIO:
mask = LT9611_INFOFRAME_AUDIO;
break ;
case HDMI_INFOFRAME_TYPE_AVI:
mask = LT9611_INFOFRAME_AVI;
break ;
case HDMI_INFOFRAME_TYPE_SPD:
mask = LT9611_INFOFRAME_SPD;
break ;
case HDMI_INFOFRAME_TYPE_VENDOR:
mask = LT9611_INFOFRAME_VENDOR;
break ;
default :
drm_dbg_driver(lt9611->bridge.dev, "Unsupported HDMI InfoFrame %x\n" , type);
mask = 0 ;
break ;
}
if (mask)
regmap_update_bits(lt9611->regmap, 0 x843d, mask, 0 );
return 0 ;
}
static int lt9611_hdmi_write_infoframe(struct drm_bridge *bridge,
enum hdmi_infoframe_type type,
const u8 *buffer, size_t len)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
unsigned int mask, addr;
int i;
switch (type) {
case HDMI_INFOFRAME_TYPE_AUDIO:
mask = LT9611_INFOFRAME_AUDIO;
addr = 0 x84b2;
break ;
case HDMI_INFOFRAME_TYPE_AVI:
mask = LT9611_INFOFRAME_AVI;
addr = 0 x8440;
break ;
case HDMI_INFOFRAME_TYPE_SPD:
mask = LT9611_INFOFRAME_SPD;
addr = 0 x8493;
break ;
case HDMI_INFOFRAME_TYPE_VENDOR:
mask = LT9611_INFOFRAME_VENDOR;
addr = 0 x8474;
break ;
default :
drm_dbg_driver(lt9611->bridge.dev, "Unsupported HDMI InfoFrame %x\n" , type);
mask = 0 ;
break ;
}
if (mask) {
for (i = 0 ; i < len; i++)
regmap_write(lt9611->regmap, addr + i, buffer[i]);
regmap_update_bits(lt9611->regmap, 0 x843d, mask, mask);
}
return 0 ;
}
static enum drm_mode_status
lt9611_hdmi_tmds_char_rate_valid(const struct drm_bridge *bridge,
const struct drm_display_mode *mode,
unsigned long long tmds_rate)
{
/* 297 MHz for 4k@30 mode */
if (tmds_rate > 297000000 )
return MODE_CLOCK_HIGH;
return MODE_OK;
}
static int lt9611_hdmi_audio_startup(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
regmap_write(lt9611->regmap, 0 x82d6, 0 x8c);
regmap_write(lt9611->regmap, 0 x82d7, 0 x04);
regmap_write(lt9611->regmap, 0 x8406, 0 x08);
regmap_write(lt9611->regmap, 0 x8407, 0 x10);
regmap_write(lt9611->regmap, 0 x8434, 0 xd5);
return 0 ;
}
static int lt9611_hdmi_audio_prepare(struct drm_bridge *bridge,
struct drm_connector *connector,
struct hdmi_codec_daifmt *fmt,
struct hdmi_codec_params *hparms)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
if (hparms->sample_rate == 48000 )
regmap_write(lt9611->regmap, 0 x840f, 0 x2b);
else if (hparms->sample_rate == 96000 )
regmap_write(lt9611->regmap, 0 x840f, 0 xab);
else
return -EINVAL;
regmap_write(lt9611->regmap, 0 x8435, 0 x00);
regmap_write(lt9611->regmap, 0 x8436, 0 x18);
regmap_write(lt9611->regmap, 0 x8437, 0 x00);
return drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector,
&hparms->cea);
}
static void lt9611_hdmi_audio_shutdown(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector);
regmap_write(lt9611->regmap, 0 x8406, 0 x00);
regmap_write(lt9611->regmap, 0 x8407, 0 x00);
}
static const struct drm_bridge_funcs lt9611_bridge_funcs = {
.attach = lt9611_bridge_attach,
.mode_valid = lt9611_bridge_mode_valid,
.detect = lt9611_bridge_detect,
.edid_read = lt9611_bridge_edid_read,
.hpd_enable = lt9611_bridge_hpd_enable,
.atomic_pre_enable = lt9611_bridge_atomic_pre_enable,
.atomic_enable = lt9611_bridge_atomic_enable,
.atomic_disable = lt9611_bridge_atomic_disable,
.atomic_post_disable = lt9611_bridge_atomic_post_disable,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_get_input_bus_fmts = lt9611_atomic_get_input_bus_fmts,
.hdmi_tmds_char_rate_valid = lt9611_hdmi_tmds_char_rate_valid,
.hdmi_write_infoframe = lt9611_hdmi_write_infoframe,
.hdmi_clear_infoframe = lt9611_hdmi_clear_infoframe,
.hdmi_audio_startup = lt9611_hdmi_audio_startup,
.hdmi_audio_prepare = lt9611_hdmi_audio_prepare,
.hdmi_audio_shutdown = lt9611_hdmi_audio_shutdown,
};
static int lt9611_parse_dt(struct device *dev,
struct lt9611 *lt9611)
{
lt9611->dsi0_node = of_graph_get_remote_node(dev->of_node, 0 , -1 );
if (!lt9611->dsi0_node) {
dev_err(lt9611->dev, "failed to get remote node for primary dsi\n" );
return -ENODEV;
}
lt9611->dsi1_node = of_graph_get_remote_node(dev->of_node, 1 , -1 );
lt9611->ac_mode = of_property_read_bool(dev->of_node, "lt,ac-mode" );
return drm_of_find_panel_or_bridge(dev->of_node, 2 , -1 , NULL, <9611->next_bridge);
}
static int lt9611_gpio_init(struct lt9611 *lt9611)
{
struct device *dev = lt9611->dev;
lt9611->reset_gpio = devm_gpiod_get(dev, "reset" , GPIOD_OUT_HIGH);
if (IS_ERR(lt9611->reset_gpio)) {
dev_err(dev, "failed to acquire reset gpio\n" );
return PTR_ERR(lt9611->reset_gpio);
}
lt9611->enable_gpio = devm_gpiod_get_optional(dev, "enable" ,
GPIOD_OUT_LOW);
if (IS_ERR(lt9611->enable_gpio)) {
dev_err(dev, "failed to acquire enable gpio\n" );
return PTR_ERR(lt9611->enable_gpio);
}
return 0 ;
}
static int lt9611_read_device_rev(struct lt9611 *lt9611)
{
unsigned int rev;
int ret;
regmap_write(lt9611->regmap, 0 x80ee, 0 x01);
ret = regmap_read(lt9611->regmap, 0 x8002, &rev);
if (ret)
dev_err(lt9611->dev, "failed to read revision: %d\n" , ret);
else
dev_info(lt9611->dev, "LT9611 revision: 0x%x\n" , rev);
return ret;
}
static int lt9611_probe(struct i2c_client *client)
{
struct lt9611 *lt9611;
struct device *dev = &client->dev;
int ret;
if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
dev_err(dev, "device doesn't support I2C\n" );
return -ENODEV;
}
lt9611 = devm_drm_bridge_alloc(dev, struct lt9611, bridge,
<9611_bridge_funcs);
if (IS_ERR(lt9611))
return PTR_ERR(lt9611);
lt9611->dev = dev;
lt9611->client = client;
lt9611->sleep = false ;
lt9611->regmap = devm_regmap_init_i2c(client, <9611_regmap_config);
if (IS_ERR(lt9611->regmap)) {
dev_err(lt9611->dev, "regmap i2c init failed\n" );
return PTR_ERR(lt9611->regmap);
}
ret = lt9611_parse_dt(dev, lt9611);
if (ret) {
dev_err(dev, "failed to parse device tree\n" );
return ret;
}
ret = lt9611_gpio_init(lt9611);
if (ret < 0 )
goto err_of_put;
ret = lt9611_regulator_init(lt9611);
if (ret < 0 )
goto err_of_put;
lt9611_assert_5v(lt9611);
ret = lt9611_regulator_enable(lt9611);
if (ret)
goto err_of_put;
lt9611_reset(lt9611);
ret = lt9611_read_device_rev(lt9611);
if (ret) {
dev_err(dev, "failed to read chip rev\n" );
goto err_disable_regulators;
}
ret = devm_request_threaded_irq(dev, client->irq, NULL,
lt9611_irq_thread_handler,
IRQF_ONESHOT, "lt9611" , lt9611);
if (ret) {
dev_err(dev, "failed to request irq\n" );
goto err_disable_regulators;
}
i2c_set_clientdata(client, lt9611);
/* Disable Audio InfoFrame, enabled by default */
regmap_update_bits(lt9611->regmap, 0 x843d, LT9611_INFOFRAME_AUDIO, 0 );
lt9611->bridge.of_node = client->dev.of_node;
lt9611->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID |
DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_MODES |
DRM_BRIDGE_OP_HDMI | DRM_BRIDGE_OP_HDMI_AUDIO;
lt9611->bridge.type = DRM_MODE_CONNECTOR_HDMIA;
lt9611->bridge.vendor = "Lontium" ;
lt9611->bridge.product = "LT9611" ;
lt9611->bridge.hdmi_audio_dev = dev;
lt9611->bridge.hdmi_audio_max_i2s_playback_channels = 8 ;
lt9611->bridge.hdmi_audio_dai_port = 2 ;
drm_bridge_add(<9611->bridge);
/* Attach primary DSI */
lt9611->dsi0 = lt9611_attach_dsi(lt9611, lt9611->dsi0_node);
if (IS_ERR(lt9611->dsi0)) {
ret = PTR_ERR(lt9611->dsi0);
goto err_remove_bridge;
}
/* Attach secondary DSI, if specified */
if (lt9611->dsi1_node) {
lt9611->dsi1 = lt9611_attach_dsi(lt9611, lt9611->dsi1_node);
if (IS_ERR(lt9611->dsi1)) {
ret = PTR_ERR(lt9611->dsi1);
goto err_remove_bridge;
}
}
lt9611_enable_hpd_interrupts(lt9611);
return 0 ;
err_remove_bridge:
drm_bridge_remove(<9611->bridge);
err_disable_regulators:
regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies);
err_of_put:
of_node_put(lt9611->dsi1_node);
of_node_put(lt9611->dsi0_node);
return ret;
}
static void lt9611_remove(struct i2c_client *client)
{
struct lt9611 *lt9611 = i2c_get_clientdata(client);
disable_irq(client->irq);
drm_bridge_remove(<9611->bridge);
regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies);
of_node_put(lt9611->dsi1_node);
of_node_put(lt9611->dsi0_node);
}
static const struct i2c_device_id lt9611_id[] = {
{ "lontium,lt9611" },
{}
};
MODULE_DEVICE_TABLE(i2c, lt9611_id);
static const struct of_device_id lt9611_match_table[] = {
{ .compatible = "lontium,lt9611" },
{ }
};
MODULE_DEVICE_TABLE(of, lt9611_match_table);
static struct i2c_driver lt9611_driver = {
.driver = {
.name = "lt9611" ,
.of_match_table = lt9611_match_table,
},
.probe = lt9611_probe,
.remove = lt9611_remove,
.id_table = lt9611_id,
};
module_i2c_driver(lt9611_driver);
MODULE_DESCRIPTION("Lontium LT9611 DSI/HDMI bridge driver" );
MODULE_LICENSE("GPL v2" );
Messung V0.5 in Prozent C=93 H=92 G=92
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland