Skip to content

Commit 15b4511

Browse files
Hans Verkuilanholt
authored andcommitted
drm/vc4: add HDMI CEC support
This patch adds support to VC4 for CEC. It is under a separate Kconfig option to keep everyone using VC4 from needing to pull in the CEC core. Thanks to Eric Anholt for providing me with the CEC register information. Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Eric Anholt <eric@anholt.net> Reviewed-by: Eric Anholt <eric@anholt.net> Link: https://patchwork.freedesktop.org/patch/msgid/20170716104804.48308-4-hverkuil@xs4all.nl
1 parent 10ee275 commit 15b4511

File tree

3 files changed

+345
-6
lines changed

3 files changed

+345
-6
lines changed

drivers/gpu/drm/vc4/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,11 @@ config DRM_VC4
1919
This driver requires that "avoid_warnings=2" be present in
2020
the config.txt for the firmware, to keep it from smashing
2121
our display setup.
22+
23+
config DRM_VC4_HDMI_CEC
24+
bool "Broadcom VC4 HDMI CEC Support"
25+
depends on DRM_VC4
26+
select CEC_CORE
27+
help
28+
Choose this option if you have a Broadcom VC4 GPU
29+
and want to use CEC.

drivers/gpu/drm/vc4/vc4_hdmi.c

Lines changed: 224 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,14 @@
5757
#include <sound/pcm_drm_eld.h>
5858
#include <sound/pcm_params.h>
5959
#include <sound/soc.h>
60+
#include "media/cec.h"
6061
#include "vc4_drv.h"
6162
#include "vc4_regs.h"
6263

64+
#define HSM_CLOCK_FREQ 163682864
65+
#define CEC_CLOCK_FREQ 40000
66+
#define CEC_CLOCK_DIV (HSM_CLOCK_FREQ / CEC_CLOCK_FREQ)
67+
6368
/* HDMI audio information */
6469
struct vc4_hdmi_audio {
6570
struct snd_soc_card card;
@@ -85,6 +90,11 @@ struct vc4_hdmi {
8590
int hpd_gpio;
8691
bool hpd_active_low;
8792

93+
struct cec_adapter *cec_adap;
94+
struct cec_msg cec_rx_msg;
95+
bool cec_tx_ok;
96+
bool cec_irq_was_rx;
97+
8898
struct clk *pixel_clock;
8999
struct clk *hsm_clock;
90100
};
@@ -149,6 +159,23 @@ static const struct {
149159
HDMI_REG(VC4_HDMI_VERTB1),
150160
HDMI_REG(VC4_HDMI_TX_PHY_RESET_CTL),
151161
HDMI_REG(VC4_HDMI_TX_PHY_CTL0),
162+
163+
HDMI_REG(VC4_HDMI_CEC_CNTRL_1),
164+
HDMI_REG(VC4_HDMI_CEC_CNTRL_2),
165+
HDMI_REG(VC4_HDMI_CEC_CNTRL_3),
166+
HDMI_REG(VC4_HDMI_CEC_CNTRL_4),
167+
HDMI_REG(VC4_HDMI_CEC_CNTRL_5),
168+
HDMI_REG(VC4_HDMI_CPU_STATUS),
169+
HDMI_REG(VC4_HDMI_CPU_MASK_STATUS),
170+
171+
HDMI_REG(VC4_HDMI_CEC_RX_DATA_1),
172+
HDMI_REG(VC4_HDMI_CEC_RX_DATA_2),
173+
HDMI_REG(VC4_HDMI_CEC_RX_DATA_3),
174+
HDMI_REG(VC4_HDMI_CEC_RX_DATA_4),
175+
HDMI_REG(VC4_HDMI_CEC_TX_DATA_1),
176+
HDMI_REG(VC4_HDMI_CEC_TX_DATA_2),
177+
HDMI_REG(VC4_HDMI_CEC_TX_DATA_3),
178+
HDMI_REG(VC4_HDMI_CEC_TX_DATA_4),
152179
};
153180

154181
static const struct {
@@ -216,17 +243,17 @@ vc4_hdmi_connector_detect(struct drm_connector *connector, bool force)
216243
if (gpio_get_value_cansleep(vc4->hdmi->hpd_gpio) ^
217244
vc4->hdmi->hpd_active_low)
218245
return connector_status_connected;
219-
else
220-
return connector_status_disconnected;
246+
cec_phys_addr_invalidate(vc4->hdmi->cec_adap);
247+
return connector_status_disconnected;
221248
}
222249

223250
if (drm_probe_ddc(vc4->hdmi->ddc))
224251
return connector_status_connected;
225252

226253
if (HDMI_READ(VC4_HDMI_HOTPLUG) & VC4_HDMI_HOTPLUG_CONNECTED)
227254
return connector_status_connected;
228-
else
229-
return connector_status_disconnected;
255+
cec_phys_addr_invalidate(vc4->hdmi->cec_adap);
256+
return connector_status_disconnected;
230257
}
231258

232259
static void vc4_hdmi_connector_destroy(struct drm_connector *connector)
@@ -247,6 +274,7 @@ static int vc4_hdmi_connector_get_modes(struct drm_connector *connector)
247274
struct edid *edid;
248275

249276
edid = drm_get_edid(connector, vc4->hdmi->ddc);
277+
cec_s_phys_addr_from_edid(vc4->hdmi->cec_adap, edid);
250278
if (!edid)
251279
return -ENODEV;
252280

@@ -1121,6 +1149,159 @@ static void vc4_hdmi_audio_cleanup(struct vc4_hdmi *hdmi)
11211149
snd_soc_unregister_codec(dev);
11221150
}
11231151

1152+
#ifdef CONFIG_DRM_VC4_HDMI_CEC
1153+
static irqreturn_t vc4_cec_irq_handler_thread(int irq, void *priv)
1154+
{
1155+
struct vc4_dev *vc4 = priv;
1156+
struct vc4_hdmi *hdmi = vc4->hdmi;
1157+
1158+
if (hdmi->cec_irq_was_rx) {
1159+
if (hdmi->cec_rx_msg.len)
1160+
cec_received_msg(hdmi->cec_adap, &hdmi->cec_rx_msg);
1161+
} else if (hdmi->cec_tx_ok) {
1162+
cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_OK,
1163+
0, 0, 0, 0);
1164+
} else {
1165+
/*
1166+
* This CEC implementation makes 1 retry, so if we
1167+
* get a NACK, then that means it made 2 attempts.
1168+
*/
1169+
cec_transmit_done(hdmi->cec_adap, CEC_TX_STATUS_NACK,
1170+
0, 2, 0, 0);
1171+
}
1172+
return IRQ_HANDLED;
1173+
}
1174+
1175+
static void vc4_cec_read_msg(struct vc4_dev *vc4, u32 cntrl1)
1176+
{
1177+
struct cec_msg *msg = &vc4->hdmi->cec_rx_msg;
1178+
unsigned int i;
1179+
1180+
msg->len = 1 + ((cntrl1 & VC4_HDMI_CEC_REC_WRD_CNT_MASK) >>
1181+
VC4_HDMI_CEC_REC_WRD_CNT_SHIFT);
1182+
for (i = 0; i < msg->len; i += 4) {
1183+
u32 val = HDMI_READ(VC4_HDMI_CEC_RX_DATA_1 + i);
1184+
1185+
msg->msg[i] = val & 0xff;
1186+
msg->msg[i + 1] = (val >> 8) & 0xff;
1187+
msg->msg[i + 2] = (val >> 16) & 0xff;
1188+
msg->msg[i + 3] = (val >> 24) & 0xff;
1189+
}
1190+
}
1191+
1192+
static irqreturn_t vc4_cec_irq_handler(int irq, void *priv)
1193+
{
1194+
struct vc4_dev *vc4 = priv;
1195+
struct vc4_hdmi *hdmi = vc4->hdmi;
1196+
u32 stat = HDMI_READ(VC4_HDMI_CPU_STATUS);
1197+
u32 cntrl1, cntrl5;
1198+
1199+
if (!(stat & VC4_HDMI_CPU_CEC))
1200+
return IRQ_NONE;
1201+
hdmi->cec_rx_msg.len = 0;
1202+
cntrl1 = HDMI_READ(VC4_HDMI_CEC_CNTRL_1);
1203+
cntrl5 = HDMI_READ(VC4_HDMI_CEC_CNTRL_5);
1204+
hdmi->cec_irq_was_rx = cntrl5 & VC4_HDMI_CEC_RX_CEC_INT;
1205+
if (hdmi->cec_irq_was_rx) {
1206+
vc4_cec_read_msg(vc4, cntrl1);
1207+
cntrl1 |= VC4_HDMI_CEC_CLEAR_RECEIVE_OFF;
1208+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1);
1209+
cntrl1 &= ~VC4_HDMI_CEC_CLEAR_RECEIVE_OFF;
1210+
} else {
1211+
hdmi->cec_tx_ok = cntrl1 & VC4_HDMI_CEC_TX_STATUS_GOOD;
1212+
cntrl1 &= ~VC4_HDMI_CEC_START_XMIT_BEGIN;
1213+
}
1214+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, cntrl1);
1215+
HDMI_WRITE(VC4_HDMI_CPU_CLEAR, VC4_HDMI_CPU_CEC);
1216+
1217+
return IRQ_WAKE_THREAD;
1218+
}
1219+
1220+
static int vc4_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
1221+
{
1222+
struct vc4_dev *vc4 = cec_get_drvdata(adap);
1223+
/* clock period in microseconds */
1224+
const u32 usecs = 1000000 / CEC_CLOCK_FREQ;
1225+
u32 val = HDMI_READ(VC4_HDMI_CEC_CNTRL_5);
1226+
1227+
val &= ~(VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET |
1228+
VC4_HDMI_CEC_CNT_TO_4700_US_MASK |
1229+
VC4_HDMI_CEC_CNT_TO_4500_US_MASK);
1230+
val |= ((4700 / usecs) << VC4_HDMI_CEC_CNT_TO_4700_US_SHIFT) |
1231+
((4500 / usecs) << VC4_HDMI_CEC_CNT_TO_4500_US_SHIFT);
1232+
1233+
if (enable) {
1234+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val |
1235+
VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
1236+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val);
1237+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_2,
1238+
((1500 / usecs) << VC4_HDMI_CEC_CNT_TO_1500_US_SHIFT) |
1239+
((1300 / usecs) << VC4_HDMI_CEC_CNT_TO_1300_US_SHIFT) |
1240+
((800 / usecs) << VC4_HDMI_CEC_CNT_TO_800_US_SHIFT) |
1241+
((600 / usecs) << VC4_HDMI_CEC_CNT_TO_600_US_SHIFT) |
1242+
((400 / usecs) << VC4_HDMI_CEC_CNT_TO_400_US_SHIFT));
1243+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_3,
1244+
((2750 / usecs) << VC4_HDMI_CEC_CNT_TO_2750_US_SHIFT) |
1245+
((2400 / usecs) << VC4_HDMI_CEC_CNT_TO_2400_US_SHIFT) |
1246+
((2050 / usecs) << VC4_HDMI_CEC_CNT_TO_2050_US_SHIFT) |
1247+
((1700 / usecs) << VC4_HDMI_CEC_CNT_TO_1700_US_SHIFT));
1248+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_4,
1249+
((4300 / usecs) << VC4_HDMI_CEC_CNT_TO_4300_US_SHIFT) |
1250+
((3900 / usecs) << VC4_HDMI_CEC_CNT_TO_3900_US_SHIFT) |
1251+
((3600 / usecs) << VC4_HDMI_CEC_CNT_TO_3600_US_SHIFT) |
1252+
((3500 / usecs) << VC4_HDMI_CEC_CNT_TO_3500_US_SHIFT));
1253+
1254+
HDMI_WRITE(VC4_HDMI_CPU_MASK_CLEAR, VC4_HDMI_CPU_CEC);
1255+
} else {
1256+
HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, VC4_HDMI_CPU_CEC);
1257+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_5, val |
1258+
VC4_HDMI_CEC_TX_SW_RESET | VC4_HDMI_CEC_RX_SW_RESET);
1259+
}
1260+
return 0;
1261+
}
1262+
1263+
static int vc4_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
1264+
{
1265+
struct vc4_dev *vc4 = cec_get_drvdata(adap);
1266+
1267+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1,
1268+
(HDMI_READ(VC4_HDMI_CEC_CNTRL_1) & ~VC4_HDMI_CEC_ADDR_MASK) |
1269+
(log_addr & 0xf) << VC4_HDMI_CEC_ADDR_SHIFT);
1270+
return 0;
1271+
}
1272+
1273+
static int vc4_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
1274+
u32 signal_free_time, struct cec_msg *msg)
1275+
{
1276+
struct vc4_dev *vc4 = cec_get_drvdata(adap);
1277+
u32 val;
1278+
unsigned int i;
1279+
1280+
for (i = 0; i < msg->len; i += 4)
1281+
HDMI_WRITE(VC4_HDMI_CEC_TX_DATA_1 + i,
1282+
(msg->msg[i]) |
1283+
(msg->msg[i + 1] << 8) |
1284+
(msg->msg[i + 2] << 16) |
1285+
(msg->msg[i + 3] << 24));
1286+
1287+
val = HDMI_READ(VC4_HDMI_CEC_CNTRL_1);
1288+
val &= ~VC4_HDMI_CEC_START_XMIT_BEGIN;
1289+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val);
1290+
val &= ~VC4_HDMI_CEC_MESSAGE_LENGTH_MASK;
1291+
val |= (msg->len - 1) << VC4_HDMI_CEC_MESSAGE_LENGTH_SHIFT;
1292+
val |= VC4_HDMI_CEC_START_XMIT_BEGIN;
1293+
1294+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, val);
1295+
return 0;
1296+
}
1297+
1298+
static const struct cec_adap_ops vc4_hdmi_cec_adap_ops = {
1299+
.adap_enable = vc4_hdmi_cec_adap_enable,
1300+
.adap_log_addr = vc4_hdmi_cec_adap_log_addr,
1301+
.adap_transmit = vc4_hdmi_cec_adap_transmit,
1302+
};
1303+
#endif
1304+
11241305
static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
11251306
{
11261307
struct platform_device *pdev = to_platform_device(dev);
@@ -1180,7 +1361,7 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
11801361
* needs to be a bit higher than the pixel clock rate
11811362
* (generally 148.5Mhz).
11821363
*/
1183-
ret = clk_set_rate(hdmi->hsm_clock, 163682864);
1364+
ret = clk_set_rate(hdmi->hsm_clock, HSM_CLOCK_FREQ);
11841365
if (ret) {
11851366
DRM_ERROR("Failed to set HSM clock rate: %d\n", ret);
11861367
goto err_put_i2c;
@@ -1231,13 +1412,50 @@ static int vc4_hdmi_bind(struct device *dev, struct device *master, void *data)
12311412
ret = PTR_ERR(hdmi->connector);
12321413
goto err_destroy_encoder;
12331414
}
1415+
#ifdef CONFIG_DRM_VC4_HDMI_CEC
1416+
hdmi->cec_adap = cec_allocate_adapter(&vc4_hdmi_cec_adap_ops,
1417+
vc4, "vc4",
1418+
CEC_CAP_TRANSMIT |
1419+
CEC_CAP_LOG_ADDRS |
1420+
CEC_CAP_PASSTHROUGH |
1421+
CEC_CAP_RC, 1);
1422+
ret = PTR_ERR_OR_ZERO(hdmi->cec_adap);
1423+
if (ret < 0)
1424+
goto err_destroy_conn;
1425+
HDMI_WRITE(VC4_HDMI_CPU_MASK_SET, 0xffffffff);
1426+
value = HDMI_READ(VC4_HDMI_CEC_CNTRL_1);
1427+
value &= ~VC4_HDMI_CEC_DIV_CLK_CNT_MASK;
1428+
/*
1429+
* Set the logical address to Unregistered and set the clock
1430+
* divider: the hsm_clock rate and this divider setting will
1431+
* give a 40 kHz CEC clock.
1432+
*/
1433+
value |= VC4_HDMI_CEC_ADDR_MASK |
1434+
(4091 << VC4_HDMI_CEC_DIV_CLK_CNT_SHIFT);
1435+
HDMI_WRITE(VC4_HDMI_CEC_CNTRL_1, value);
1436+
ret = devm_request_threaded_irq(dev, platform_get_irq(pdev, 0),
1437+
vc4_cec_irq_handler,
1438+
vc4_cec_irq_handler_thread, 0,
1439+
"vc4 hdmi cec", vc4);
1440+
if (ret)
1441+
goto err_delete_cec_adap;
1442+
ret = cec_register_adapter(hdmi->cec_adap, dev);
1443+
if (ret < 0)
1444+
goto err_delete_cec_adap;
1445+
#endif
12341446

12351447
ret = vc4_hdmi_audio_init(hdmi);
12361448
if (ret)
12371449
goto err_destroy_encoder;
12381450

12391451
return 0;
12401452

1453+
#ifdef CONFIG_DRM_VC4_HDMI_CEC
1454+
err_delete_cec_adap:
1455+
cec_delete_adapter(hdmi->cec_adap);
1456+
err_destroy_conn:
1457+
vc4_hdmi_connector_destroy(hdmi->connector);
1458+
#endif
12411459
err_destroy_encoder:
12421460
vc4_hdmi_encoder_destroy(hdmi->encoder);
12431461
err_unprepare_hsm:
@@ -1257,7 +1475,7 @@ static void vc4_hdmi_unbind(struct device *dev, struct device *master,
12571475
struct vc4_hdmi *hdmi = vc4->hdmi;
12581476

12591477
vc4_hdmi_audio_cleanup(hdmi);
1260-
1478+
cec_unregister_adapter(hdmi->cec_adap);
12611479
vc4_hdmi_connector_destroy(hdmi->connector);
12621480
vc4_hdmi_encoder_destroy(hdmi->encoder);
12631481

0 commit comments

Comments
 (0)