Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ properties:
vcc-supply:
description: Regulator for 3.3V IO power.

lontium,port-select:
$ref: /schemas/types.yaml#/definitions/uint32
enum: [0, 1, 2]
default: 0
description: |
Selects which DSI input port(s) the bridge uses. Only relevant for
the lontium,lt9611c compatible.
0 = PORT_SELECT_A - single DSI port A (default)
1 = PORT_SELECT_B - single DSI port B
2 = PORT_SELECT_AB - dual DSI ports A and B

ports:
$ref: /schemas/graph.yaml#/properties/ports

Expand Down Expand Up @@ -104,6 +115,8 @@ examples:
vdd-supply = <&lt9611_1v8>;
vcc-supply = <&lt9611_3v3>;

lontium,port-select = <1>;

ports {
#address-cells = <1>;
#size-cells = <0>;
Expand Down
1 change: 1 addition & 0 deletions arch/arm64/configs/defconfig
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,7 @@ CONFIG_DRM_FSL_LDB=m
CONFIG_DRM_ITE_IT6263=m
CONFIG_DRM_LONTIUM_LT8912B=m
CONFIG_DRM_LONTIUM_LT9611=m
CONFIG_DRM_LONTIUM_LT9611C=m
CONFIG_DRM_LONTIUM_LT9611UXC=m
CONFIG_DRM_LONTIUM_LT8713SX=m
CONFIG_DRM_ITE_IT66121=m
Expand Down
118 changes: 104 additions & 14 deletions drivers/gpu/drm/bridge/lontium-lt9611c.c
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ enum lt9611_chip_type {
CHIP_LT9611UXD,
};

enum lt9611c_PORT_SELECT {
PORT_SELECT_A = 0,
PORT_SELECT_B = 1,
PORT_SELECT_AB = 2,
};

struct lt9611c {
struct device *dev;
struct i2c_client *client;
Expand All @@ -60,6 +66,8 @@ struct lt9611c {
enum lt9611_chip_type chip_type;
/* HDMI cable connection status */
bool hdmi_connected;
/* Selected DSI port configuration */
int selected_port;
};

DECLARE_CRC8_TABLE(lt9611c_crc8_table);
Expand Down Expand Up @@ -97,7 +105,7 @@ static int lt9611c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
regmap_write(lt9611c->regmap, 0xe0de, 0x01);

ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp,
temp == 0x01, 1000, 100 * 1000);
temp == 0x01, 1000, 200 * 1000);
if (ret)
return -ETIMEDOUT;

Expand All @@ -107,14 +115,42 @@ static int lt9611c_read_write_flow(struct lt9611c *lt9611c, u8 *params,
regmap_write(lt9611c->regmap, 0xe0de, 0x02);

ret = regmap_read_poll_timeout(lt9611c->regmap, 0xe0ae, temp,
temp == 0x02, 1000, 100 * 1000);
temp == 0x02, 1000, 200 * 1000);
if (ret)
return -ETIMEDOUT;

return regmap_bulk_read(lt9611c->regmap, 0xe085, return_buffer,
return_count);
}

static int lt9611c_select_port(struct lt9611c *lt9611c, int port_select)
{
int ret;
u8 set_port_select_cmd[6] = {0x57, 0x4d, 0x31, 0x3a, 0x01, 0xc0};
u8 set_port_select_ret[5];

if (port_select == PORT_SELECT_B) {
set_port_select_cmd[5] = 0x40;
} else if (port_select == PORT_SELECT_AB) {
set_port_select_cmd[4] = 0x02;
set_port_select_cmd[5] = 0xd0;
} else if (port_select != PORT_SELECT_A) {
return -EINVAL;
}

/* MCU must be running (0xe0ee=0x00) for lt9611c_read_write_flow */
guard(mutex)(&lt9611c->ocm_lock);

ret = lt9611c_read_write_flow(lt9611c, set_port_select_cmd,
ARRAY_SIZE(set_port_select_cmd),
set_port_select_ret,
ARRAY_SIZE(set_port_select_ret));
if (ret < 0 || set_port_select_ret[4] == 0)
return ret < 0 ? ret : -EIO;

return 0;
}

static void lt9611c_config_parameters(struct lt9611c *lt9611c)
{
const struct reg_sequence seq_write_paras[] = {
Expand Down Expand Up @@ -622,7 +658,6 @@ static void lt9611c_bridge_atomic_pre_enable(struct drm_bridge *bridge,
ret = regulator_bulk_enable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
if (ret)
dev_err(lt9611c->dev, "regulator bulk enable failed.\n");
lt9611c_reset(lt9611c);
}

static void lt9611c_bridge_atomic_enable(struct drm_bridge *bridge,
Expand Down Expand Up @@ -676,10 +711,12 @@ lt9611c_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector
guard(mutex)(&lt9611c->ocm_lock);

ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd), data, ARRAY_SIZE(data));
if (ret)
if (ret) {
dev_err(dev, "failed to read HPD status (err=%d)\n", ret);
else
connected = lt9611c->hdmi_connected;
} else {
connected = (data[4] == 0x02);
}

lt9611c->hdmi_connected = connected;

Expand Down Expand Up @@ -777,6 +814,17 @@ static int lt9611c_hdmi_clear_avi_infoframe(struct drm_bridge *bridge)
return 0;
}

static int lt9611c_hdmi_write_hdmi_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
return 0;
}

static int lt9611c_hdmi_clear_hdmi_infoframe(struct drm_bridge *bridge)
{
return 0;
}

static int lt9611c_hdmi_write_audio_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
Expand Down Expand Up @@ -875,6 +923,23 @@ static void lt9611c_hdmi_audio_shutdown(struct drm_bridge *bridge,
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector);
}

static void lt9611c_bridge_hpd_enable(struct drm_bridge *bridge)
{
struct lt9611c *lt9611c = bridge_to_lt9611c(bridge);
u8 cmd[5] = {0x52, 0x48, 0x31, 0x3a, 0x00};
u8 data[5];
int ret;

mutex_lock(&lt9611c->ocm_lock);
ret = lt9611c_read_write_flow(lt9611c, cmd, ARRAY_SIZE(cmd),
data, ARRAY_SIZE(data));
if (!ret)
lt9611c->hdmi_connected = (data[4] == 0x02);
mutex_unlock(&lt9611c->ocm_lock);

schedule_work(&lt9611c->work);
}

static int lt9611c_hdmi_audio_startup(struct drm_bridge *bridge,
struct drm_connector *connector)
{
Expand All @@ -891,10 +956,13 @@ static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
.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,
.hpd_enable = lt9611c_bridge_hpd_enable,

.hdmi_tmds_char_rate_valid = lt9611c_hdmi_tmds_char_rate_valid,
.hdmi_write_avi_infoframe = lt9611c_hdmi_write_avi_infoframe,
.hdmi_clear_avi_infoframe = lt9611c_hdmi_clear_avi_infoframe,
.hdmi_write_hdmi_infoframe = lt9611c_hdmi_write_hdmi_infoframe,
.hdmi_clear_hdmi_infoframe = lt9611c_hdmi_clear_hdmi_infoframe,
.hdmi_write_audio_infoframe = lt9611c_hdmi_write_audio_infoframe,
.hdmi_clear_audio_infoframe = lt9611c_hdmi_clear_audio_infoframe,

Expand All @@ -906,13 +974,26 @@ static const struct drm_bridge_funcs lt9611c_bridge_funcs = {
static int lt9611c_parse_dt(struct device *dev,
struct lt9611c *lt9611c)
{
int ret;

lt9611c->dsi0_node = of_graph_get_remote_node(dev->of_node, 0, -1);
if (!lt9611c->dsi0_node)
return dev_err_probe(dev, -ENODEV, "failed to get remote node for primary dsi\n");

lt9611c->dsi1_node = of_graph_get_remote_node(dev->of_node, 1, -1);

return drm_of_find_panel_or_bridge(dev->of_node, 2, -1, NULL, &lt9611c->bridge.next_bridge);
if (of_property_read_u32(dev->of_node, "lontium,port-select",
&lt9611c->selected_port))
lt9611c->selected_port = 0;

ret = drm_of_find_panel_or_bridge(dev->of_node, 2, -1, NULL, &lt9611c->bridge.next_bridge);
if (ret) {
of_node_put(lt9611c->dsi1_node);
of_node_put(lt9611c->dsi0_node);
return ret;
}
drm_bridge_get(lt9611c->bridge.next_bridge);
return 0;
}

static int lt9611c_gpio_init(struct lt9611c *lt9611c)
Expand Down Expand Up @@ -1024,7 +1105,13 @@ static int lt9611c_probe(struct i2c_client *client)

lt9611c->dev = dev;
lt9611c->client = client;
lt9611c->chip_type = id->driver_data;

if (dev->of_node) {
lt9611c->chip_type = (uintptr_t)of_device_get_match_data(dev);
} else {
lt9611c->chip_type = id->driver_data;
}

ret = devm_mutex_init(dev, &lt9611c->ocm_lock);
if (ret)
return dev_err_probe(dev, ret, "failed to init mutex\n");
Expand All @@ -1051,6 +1138,10 @@ static int lt9611c_probe(struct i2c_client *client)

lt9611c_reset(lt9611c);

ret = lt9611c_select_port(lt9611c, lt9611c->selected_port);
if (ret < 0)
dev_err(lt9611c->dev, "failed to select port %d\n", lt9611c->selected_port);

lt9611c_lock(lt9611c);

ret = lt9611c_read_chipid(lt9611c);
Expand Down Expand Up @@ -1111,6 +1202,9 @@ static int lt9611c_probe(struct i2c_client *client)
DRM_BRIDGE_OP_HDMI_AUDIO;
lt9611c->bridge.type = DRM_MODE_CONNECTOR_HDMIA;

lt9611c->bridge.vendor = "Lontium";
lt9611c->bridge.product = "LT9611C";

lt9611c->bridge.hdmi_audio_dev = dev;
lt9611c->bridge.hdmi_audio_max_i2s_playback_channels = 8;
lt9611c->bridge.hdmi_audio_dai_port = 2;
Expand All @@ -1136,14 +1230,11 @@ static int lt9611c_probe(struct i2c_client *client)
lt9611c->hdmi_connected = false;
i2c_set_clientdata(client, lt9611c);
enable_irq(client->irq);
lt9611c_reset(lt9611c);

return 0;

err_remove_bridge:
free_irq(client->irq, lt9611c);
cancel_work_sync(&lt9611c->work);
drm_bridge_remove(&lt9611c->bridge);

err_disable_regulators:
regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
Expand All @@ -1159,7 +1250,6 @@ static void lt9611c_remove(struct i2c_client *client)
{
struct lt9611c *lt9611c = i2c_get_clientdata(client);

free_irq(client->irq, lt9611c);
cancel_work_sync(&lt9611c->work);
regulator_bulk_disable(ARRAY_SIZE(lt9611c->supplies), lt9611c->supplies);
of_node_put(lt9611c->dsi1_node);
Expand Down Expand Up @@ -1214,9 +1304,9 @@ static struct i2c_device_id lt9611c_id[] = {
};

static const struct of_device_id lt9611c_match_table[] = {
{ .compatible = "lontium,lt9611c" },
{ .compatible = "lontium,lt9611ex" },
{ .compatible = "lontium,lt9611uxd" },
{ .compatible = "lontium,lt9611c", .data = (void *)CHIP_LT9611C },
{ .compatible = "lontium,lt9611ex", .data = (void *)CHIP_LT9611EX },
{ .compatible = "lontium,lt9611uxd", .data = (void *)CHIP_LT9611UXD },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, lt9611c_match_table);
Expand Down