diff options
author | Hans Verkuil <hans.verkuil@cisco.com> | 2017-07-11 02:30:44 -0400 |
---|---|---|
committer | Maxime Ripard <maxime.ripard@free-electrons.com> | 2017-07-18 12:27:50 -0400 |
commit | 998140d26723bcddef5857e39077898b0d1bdb8f (patch) | |
tree | da37b2a50d4946b3e0d3a5a87233c522414e395c | |
parent | 9181b5bbdff2739efa196c204cb307cc3cab210a (diff) |
sun4i_hdmi: add CEC support
Add HDMI CEC support to the Allwinner A10 SoC.
This SoC uses a poor-man's CEC implementation by polling the CEC pin. It is
using the CEC_PIN core implementation for such devices to do the heavy
lifting. It just provides the callbacks to read/drive the CEC pin.
Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com>
Tested-by: Maxime Ripard <maxime.ripard@free-electrons.com>
Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com>
-rw-r--r-- | drivers/gpu/drm/sun4i/Kconfig | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_hdmi.h | 8 | ||||
-rw-r--r-- | drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | 55 |
3 files changed, 71 insertions, 1 deletions
diff --git a/drivers/gpu/drm/sun4i/Kconfig b/drivers/gpu/drm/sun4i/Kconfig index 5cc116692913..06f05302ee75 100644 --- a/drivers/gpu/drm/sun4i/Kconfig +++ b/drivers/gpu/drm/sun4i/Kconfig | |||
@@ -22,6 +22,15 @@ config DRM_SUN4I_HDMI | |||
22 | Choose this option if you have an Allwinner SoC with an HDMI | 22 | Choose this option if you have an Allwinner SoC with an HDMI |
23 | controller. | 23 | controller. |
24 | 24 | ||
25 | config DRM_SUN4I_HDMI_CEC | ||
26 | bool "Allwinner A10 HDMI CEC Support" | ||
27 | depends on DRM_SUN4I_HDMI | ||
28 | select CEC_CORE | ||
29 | depends on CEC_PIN | ||
30 | help | ||
31 | Choose this option if you have an Allwinner SoC with an HDMI | ||
32 | controller and want to use CEC. | ||
33 | |||
25 | config DRM_SUN4I_BACKEND | 34 | config DRM_SUN4I_BACKEND |
26 | tristate "Support for Allwinner A10 Display Engine Backend" | 35 | tristate "Support for Allwinner A10 Display Engine Backend" |
27 | default DRM_SUN4I | 36 | default DRM_SUN4I |
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi.h b/drivers/gpu/drm/sun4i/sun4i_hdmi.h index 0957ff2076ac..1457750988da 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi.h +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi.h | |||
@@ -15,6 +15,8 @@ | |||
15 | #include <drm/drm_connector.h> | 15 | #include <drm/drm_connector.h> |
16 | #include <drm/drm_encoder.h> | 16 | #include <drm/drm_encoder.h> |
17 | 17 | ||
18 | #include <media/cec.h> | ||
19 | |||
18 | #define SUN4I_HDMI_CTRL_REG 0x004 | 20 | #define SUN4I_HDMI_CTRL_REG 0x004 |
19 | #define SUN4I_HDMI_CTRL_ENABLE BIT(31) | 21 | #define SUN4I_HDMI_CTRL_ENABLE BIT(31) |
20 | 22 | ||
@@ -86,6 +88,11 @@ | |||
86 | #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21) | 88 | #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21) |
87 | #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21 | 89 | #define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21 |
88 | 90 | ||
91 | #define SUN4I_HDMI_CEC 0x214 | ||
92 | #define SUN4I_HDMI_CEC_ENABLE BIT(11) | ||
93 | #define SUN4I_HDMI_CEC_TX BIT(9) | ||
94 | #define SUN4I_HDMI_CEC_RX BIT(8) | ||
95 | |||
89 | #define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n))) | 96 | #define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n))) |
90 | #define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4)) | 97 | #define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4)) |
91 | 98 | ||
@@ -172,6 +179,7 @@ struct sun4i_hdmi { | |||
172 | struct sun4i_drv *drv; | 179 | struct sun4i_drv *drv; |
173 | 180 | ||
174 | bool hdmi_monitor; | 181 | bool hdmi_monitor; |
182 | struct cec_adapter *cec_adap; | ||
175 | }; | 183 | }; |
176 | 184 | ||
177 | int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); | 185 | int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk); |
diff --git a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c index b74607feb35c..863a51618819 100644 --- a/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c +++ b/drivers/gpu/drm/sun4i/sun4i_hdmi_enc.c | |||
@@ -197,6 +197,7 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector) | |||
197 | hdmi->hdmi_monitor ? "an HDMI" : "a DVI"); | 197 | hdmi->hdmi_monitor ? "an HDMI" : "a DVI"); |
198 | 198 | ||
199 | drm_mode_connector_update_edid_property(connector, edid); | 199 | drm_mode_connector_update_edid_property(connector, edid); |
200 | cec_s_phys_addr_from_edid(hdmi->cec_adap, edid); | ||
200 | ret = drm_add_edid_modes(connector, edid); | 201 | ret = drm_add_edid_modes(connector, edid); |
201 | kfree(edid); | 202 | kfree(edid); |
202 | 203 | ||
@@ -215,8 +216,10 @@ sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force) | |||
215 | 216 | ||
216 | if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg, | 217 | if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg, |
217 | reg & SUN4I_HDMI_HPD_HIGH, | 218 | reg & SUN4I_HDMI_HPD_HIGH, |
218 | 0, 500000)) | 219 | 0, 500000)) { |
220 | cec_phys_addr_invalidate(hdmi->cec_adap); | ||
219 | return connector_status_disconnected; | 221 | return connector_status_disconnected; |
222 | } | ||
220 | 223 | ||
221 | return connector_status_connected; | 224 | return connector_status_connected; |
222 | } | 225 | } |
@@ -231,6 +234,40 @@ static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = { | |||
231 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, | 234 | .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, |
232 | }; | 235 | }; |
233 | 236 | ||
237 | #ifdef CONFIG_DRM_SUN4I_HDMI_CEC | ||
238 | static bool sun4i_hdmi_cec_pin_read(struct cec_adapter *adap) | ||
239 | { | ||
240 | struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); | ||
241 | |||
242 | return readl(hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX; | ||
243 | } | ||
244 | |||
245 | static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap) | ||
246 | { | ||
247 | struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); | ||
248 | |||
249 | /* Start driving the CEC pin low */ | ||
250 | writel(SUN4I_HDMI_CEC_ENABLE, hdmi->base + SUN4I_HDMI_CEC); | ||
251 | } | ||
252 | |||
253 | static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap) | ||
254 | { | ||
255 | struct sun4i_hdmi *hdmi = cec_get_drvdata(adap); | ||
256 | |||
257 | /* | ||
258 | * Stop driving the CEC pin, the pull up will take over | ||
259 | * unless another CEC device is driving the pin low. | ||
260 | */ | ||
261 | writel(0, hdmi->base + SUN4I_HDMI_CEC); | ||
262 | } | ||
263 | |||
264 | static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = { | ||
265 | .read = sun4i_hdmi_cec_pin_read, | ||
266 | .low = sun4i_hdmi_cec_pin_low, | ||
267 | .high = sun4i_hdmi_cec_pin_high, | ||
268 | }; | ||
269 | #endif | ||
270 | |||
234 | static int sun4i_hdmi_bind(struct device *dev, struct device *master, | 271 | static int sun4i_hdmi_bind(struct device *dev, struct device *master, |
235 | void *data) | 272 | void *data) |
236 | { | 273 | { |
@@ -348,6 +385,17 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, | |||
348 | goto err_del_i2c_adapter; | 385 | goto err_del_i2c_adapter; |
349 | } | 386 | } |
350 | 387 | ||
388 | #ifdef CONFIG_DRM_SUN4I_HDMI_CEC | ||
389 | hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops, | ||
390 | hdmi, "sun4i", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS | | ||
391 | CEC_CAP_PASSTHROUGH | CEC_CAP_RC); | ||
392 | ret = PTR_ERR_OR_ZERO(hdmi->cec_adap); | ||
393 | if (ret < 0) | ||
394 | goto err_cleanup_connector; | ||
395 | writel(readl(hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX, | ||
396 | hdmi->base + SUN4I_HDMI_CEC); | ||
397 | #endif | ||
398 | |||
351 | drm_connector_helper_add(&hdmi->connector, | 399 | drm_connector_helper_add(&hdmi->connector, |
352 | &sun4i_hdmi_connector_helper_funcs); | 400 | &sun4i_hdmi_connector_helper_funcs); |
353 | ret = drm_connector_init(drm, &hdmi->connector, | 401 | ret = drm_connector_init(drm, &hdmi->connector, |
@@ -363,11 +411,15 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master, | |||
363 | hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | | 411 | hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | |
364 | DRM_CONNECTOR_POLL_DISCONNECT; | 412 | DRM_CONNECTOR_POLL_DISCONNECT; |
365 | 413 | ||
414 | ret = cec_register_adapter(hdmi->cec_adap, dev); | ||
415 | if (ret < 0) | ||
416 | goto err_cleanup_connector; | ||
366 | drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder); | 417 | drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder); |
367 | 418 | ||
368 | return 0; | 419 | return 0; |
369 | 420 | ||
370 | err_cleanup_connector: | 421 | err_cleanup_connector: |
422 | cec_delete_adapter(hdmi->cec_adap); | ||
371 | drm_encoder_cleanup(&hdmi->encoder); | 423 | drm_encoder_cleanup(&hdmi->encoder); |
372 | err_del_i2c_adapter: | 424 | err_del_i2c_adapter: |
373 | i2c_del_adapter(hdmi->i2c); | 425 | i2c_del_adapter(hdmi->i2c); |
@@ -379,6 +431,7 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master, | |||
379 | { | 431 | { |
380 | struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); | 432 | struct sun4i_hdmi *hdmi = dev_get_drvdata(dev); |
381 | 433 | ||
434 | cec_unregister_adapter(hdmi->cec_adap); | ||
382 | drm_connector_cleanup(&hdmi->connector); | 435 | drm_connector_cleanup(&hdmi->connector); |
383 | drm_encoder_cleanup(&hdmi->encoder); | 436 | drm_encoder_cleanup(&hdmi->encoder); |
384 | i2c_del_adapter(hdmi->i2c); | 437 | i2c_del_adapter(hdmi->i2c); |