Allwinner DRM changes for 4.14
A few changes, but most notably improving the HDMI support merged in 4.13, by reporting the DDC adapter as an i2c bus, and by adding CEC support through the CEC framework. -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJZl0SwAAoJEBx+YmzsjxAg4gkP/Rsyxww/f3+wX2COAc3uS0U9 +COEsvf0ztrVIP9xAIf+m29UxB9u6dP2KTKANFyAf4b42coW6fUimV4iQmdDKAxY Y/BNUV8SyDidFmxUo7Cy3uVFzA61+wsMPOXMpJA5uQa/eNpsbHEQhsZWEsYLymZF WV7h+Hepm+pVPTnbM1j5Pd2J+VISGBdacnXFjW2KzA+PdPoPT1fmKIQ39/OtPQzH RV1J4HPEVWZfZIJz9i2NK3J7GhRR5u9NnzfGZfWf0SHzujURuytJwgIvEm5dgLeD +XuUsCvVzkUB4jTY+T1qmVPQ+GcazyinKIu6ZU9pP1xkc+Apu79OapOdvmx95yIV l9l4Q0BuJvuWsZTP8lT4EMO7oyRE0oDleKgHQ6BbnqwTKY9pltEp4Wl7ovPibXje YjjierdO8WxUq8NhSW50S/EAu2oopbCp0YApdQVmMDlZ4bx41GWDmLlCaDsECakQ vQLtI5eQK1EbrQcJjioemdikt+xcrZ7+J35x1qLQ9aNwa49RI5WSIzOTxsqWHZYw 6wXv/HN6atPbRspnwgRlwtVYUOoSZ1xT+lST0rizRLJW4G8srzfPpjtXLaP8zWp+ IvJ+ntbn1O9CSBz70IovX/AnrWxMK3Guf1dFWE68JfSw/fxhUpF1RpHdi+yvZJpS xgedqXmmlYAMNlro9Ikg =6GJs -----END PGP SIGNATURE----- Merge tag 'sunxi-drm-for-4.14' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux into drm-next Allwinner DRM changes for 4.14 A few changes, but most notably improving the HDMI support merged in 4.13, by reporting the DDC adapter as an i2c bus, and by adding CEC support through the CEC framework. * tag 'sunxi-drm-for-4.14' of https://git.kernel.org/pub/scm/linux/kernel/git/mripard/linux: sun4i_hdmi: add CEC support dt-bindings: display: sunxi: Improve endpoint ID scheme readability drm/sun4i: tcon: remove unused function drm/sun4i: Remove useless atomic_check drm/sun4i: Add if statement instead of depends on drm/sun4i: hdmi: Implement I2C adapter for A10s DDC bus drm/sun4i: constify drm_plane_helper_funcs
This commit is contained in:
commit
5fd27c2a1f
|
@ -4,15 +4,33 @@ Allwinner A10 Display Pipeline
|
|||
The Allwinner A10 Display pipeline is composed of several components
|
||||
that are going to be documented below:
|
||||
|
||||
For the input port of all components up to the TCON in the display
|
||||
pipeline, if there are multiple components, the local endpoint IDs
|
||||
must correspond to the index of the upstream block. For example, if
|
||||
the remote endpoint is Frontend 1, then the local endpoint ID must
|
||||
be 1.
|
||||
For all connections between components up to the TCONs in the display
|
||||
pipeline, when there are multiple components of the same type at the
|
||||
same depth, the local endpoint ID must be the same as the remote
|
||||
component's index. For example, if the remote endpoint is Frontend 1,
|
||||
then the local endpoint ID must be 1.
|
||||
|
||||
Conversely, for the output ports of the same group, the remote endpoint
|
||||
ID must be the index of the local hardware block. If the local backend
|
||||
is backend 1, then the remote endpoint ID must be 1.
|
||||
Frontend 0 [0] ------- [0] Backend 0 [0] ------- [0] TCON 0
|
||||
[1] -- -- [1] [1] -- -- [1]
|
||||
\ / \ /
|
||||
X X
|
||||
/ \ / \
|
||||
[0] -- -- [0] [0] -- -- [0]
|
||||
Frontend 1 [1] ------- [1] Backend 1 [1] ------- [1] TCON 1
|
||||
|
||||
For a two pipeline system such as the one depicted above, the lines
|
||||
represent the connections between the components, while the numbers
|
||||
within the square brackets corresponds to the ID of the local endpoint.
|
||||
|
||||
The same rule also applies to DE 2.0 mixer-TCON connections:
|
||||
|
||||
Mixer 0 [0] ----------- [0] TCON 0
|
||||
[1] ---- ---- [1]
|
||||
\ /
|
||||
X
|
||||
/ \
|
||||
[0] ---- ---- [0]
|
||||
Mixer 1 [1] ----------- [1] TCON 1
|
||||
|
||||
HDMI Encoder
|
||||
------------
|
||||
|
|
|
@ -13,17 +13,26 @@ config DRM_SUN4I
|
|||
Display Engine. If M is selected the module will be called
|
||||
sun4i-drm.
|
||||
|
||||
if DRM_SUN4I
|
||||
|
||||
config DRM_SUN4I_HDMI
|
||||
tristate "Allwinner A10 HDMI Controller Support"
|
||||
depends on DRM_SUN4I
|
||||
default DRM_SUN4I
|
||||
help
|
||||
Choose this option if you have an Allwinner SoC with an HDMI
|
||||
controller.
|
||||
|
||||
config DRM_SUN4I_HDMI_CEC
|
||||
bool "Allwinner A10 HDMI CEC Support"
|
||||
depends on DRM_SUN4I_HDMI
|
||||
select CEC_CORE
|
||||
depends on CEC_PIN
|
||||
help
|
||||
Choose this option if you have an Allwinner SoC with an HDMI
|
||||
controller and want to use CEC.
|
||||
|
||||
config DRM_SUN4I_BACKEND
|
||||
tristate "Support for Allwinner A10 Display Engine Backend"
|
||||
depends on DRM_SUN4I
|
||||
default DRM_SUN4I
|
||||
help
|
||||
Choose this option if you have an Allwinner SoC with the
|
||||
|
@ -33,10 +42,11 @@ config DRM_SUN4I_BACKEND
|
|||
|
||||
config DRM_SUN8I_MIXER
|
||||
tristate "Support for Allwinner Display Engine 2.0 Mixer"
|
||||
depends on DRM_SUN4I
|
||||
default MACH_SUN8I
|
||||
help
|
||||
Choose this option if you have an Allwinner SoC with the
|
||||
Allwinner Display Engine 2.0, which has a mixer to do some
|
||||
graphics mixture and feed graphics to TCON, If M is
|
||||
selected the module will be called sun8i-mixer.
|
||||
|
||||
endif
|
||||
|
|
|
@ -2,6 +2,7 @@ sun4i-drm-y += sun4i_drv.o
|
|||
sun4i-drm-y += sun4i_framebuffer.o
|
||||
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_enc.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_i2c.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_ddc_clk.o
|
||||
sun4i-drm-hdmi-y += sun4i_hdmi_tmds_clk.o
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#include <drm/drm_connector.h>
|
||||
#include <drm/drm_encoder.h>
|
||||
|
||||
#include <media/cec.h>
|
||||
|
||||
#define SUN4I_HDMI_CTRL_REG 0x004
|
||||
#define SUN4I_HDMI_CTRL_ENABLE BIT(31)
|
||||
|
||||
|
@ -86,6 +88,11 @@
|
|||
#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_MASK BIT(21)
|
||||
#define SUN4I_HDMI_PLL_DBG0_TMDS_PARENT_SHIFT 21
|
||||
|
||||
#define SUN4I_HDMI_CEC 0x214
|
||||
#define SUN4I_HDMI_CEC_ENABLE BIT(11)
|
||||
#define SUN4I_HDMI_CEC_TX BIT(9)
|
||||
#define SUN4I_HDMI_CEC_RX BIT(8)
|
||||
|
||||
#define SUN4I_HDMI_PKT_CTRL_REG(n) (0x2f0 + (4 * (n)))
|
||||
#define SUN4I_HDMI_PKT_CTRL_TYPE(n, t) ((t) << (((n) % 4) * 4))
|
||||
|
||||
|
@ -96,6 +103,7 @@
|
|||
#define SUN4I_HDMI_DDC_CTRL_ENABLE BIT(31)
|
||||
#define SUN4I_HDMI_DDC_CTRL_START_CMD BIT(30)
|
||||
#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK BIT(8)
|
||||
#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE (1 << 8)
|
||||
#define SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ (0 << 8)
|
||||
#define SUN4I_HDMI_DDC_CTRL_RESET BIT(0)
|
||||
|
||||
|
@ -105,14 +113,34 @@
|
|||
#define SUN4I_HDMI_DDC_ADDR_OFFSET(off) (((off) & 0xff) << 8)
|
||||
#define SUN4I_HDMI_DDC_ADDR_SLAVE(addr) ((addr) & 0xff)
|
||||
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_REG 0x50c
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION BIT(7)
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW BIT(6)
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW BIT(5)
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST BIT(4)
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR BIT(3)
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR BIT(2)
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR BIT(1)
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE BIT(0)
|
||||
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_REG 0x510
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR BIT(31)
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(n) (((n) & 0xf) << 4)
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK GENMASK(7, 4)
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX (BIT(4) - 1)
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(n) ((n) & 0xf)
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK GENMASK(3, 0)
|
||||
#define SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MAX (BIT(4) - 1)
|
||||
|
||||
#define SUN4I_HDMI_DDC_FIFO_DATA_REG 0x518
|
||||
|
||||
#define SUN4I_HDMI_DDC_BYTE_COUNT_REG 0x51c
|
||||
#define SUN4I_HDMI_DDC_BYTE_COUNT_MAX (BIT(10) - 1)
|
||||
|
||||
#define SUN4I_HDMI_DDC_CMD_REG 0x520
|
||||
#define SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ 6
|
||||
#define SUN4I_HDMI_DDC_CMD_IMPLICIT_READ 5
|
||||
#define SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE 3
|
||||
|
||||
#define SUN4I_HDMI_DDC_CLK_REG 0x528
|
||||
#define SUN4I_HDMI_DDC_CLK_M(m) (((m) & 0x7) << 3)
|
||||
|
@ -146,12 +174,16 @@ struct sun4i_hdmi {
|
|||
struct clk *ddc_clk;
|
||||
struct clk *tmds_clk;
|
||||
|
||||
struct i2c_adapter *i2c;
|
||||
|
||||
struct sun4i_drv *drv;
|
||||
|
||||
bool hdmi_monitor;
|
||||
struct cec_adapter *cec_adap;
|
||||
};
|
||||
|
||||
int sun4i_ddc_create(struct sun4i_hdmi *hdmi, struct clk *clk);
|
||||
int sun4i_tmds_create(struct sun4i_hdmi *hdmi);
|
||||
int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi);
|
||||
|
||||
#endif /* _SUN4I_HDMI_H_ */
|
||||
|
|
|
@ -29,8 +29,6 @@
|
|||
#include "sun4i_hdmi.h"
|
||||
#include "sun4i_tcon.h"
|
||||
|
||||
#define DDC_SEGMENT_ADDR 0x30
|
||||
|
||||
static inline struct sun4i_hdmi *
|
||||
drm_encoder_to_sun4i_hdmi(struct drm_encoder *encoder)
|
||||
{
|
||||
|
@ -184,93 +182,13 @@ static const struct drm_encoder_funcs sun4i_hdmi_funcs = {
|
|||
.destroy = drm_encoder_cleanup,
|
||||
};
|
||||
|
||||
static int sun4i_hdmi_read_sub_block(struct sun4i_hdmi *hdmi,
|
||||
unsigned int blk, unsigned int offset,
|
||||
u8 *buf, unsigned int count)
|
||||
{
|
||||
unsigned long reg;
|
||||
int i;
|
||||
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
|
||||
writel(reg | SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
|
||||
writel(SUN4I_HDMI_DDC_ADDR_SEGMENT(offset >> 8) |
|
||||
SUN4I_HDMI_DDC_ADDR_EDDC(DDC_SEGMENT_ADDR << 1) |
|
||||
SUN4I_HDMI_DDC_ADDR_OFFSET(offset) |
|
||||
SUN4I_HDMI_DDC_ADDR_SLAVE(DDC_ADDR),
|
||||
hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
|
||||
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
|
||||
writel(reg | SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR,
|
||||
hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
|
||||
|
||||
writel(count, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
|
||||
writel(SUN4I_HDMI_DDC_CMD_EXPLICIT_EDDC_READ,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
|
||||
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
|
||||
!(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
|
||||
100, 100000))
|
||||
return -EIO;
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
buf[i] = readb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_hdmi_read_edid_block(void *data, u8 *buf, unsigned int blk,
|
||||
size_t length)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = data;
|
||||
int retry = 2, i;
|
||||
|
||||
do {
|
||||
for (i = 0; i < length; i += SUN4I_HDMI_DDC_FIFO_SIZE) {
|
||||
unsigned char offset = blk * EDID_LENGTH + i;
|
||||
unsigned int count = min((unsigned int)SUN4I_HDMI_DDC_FIFO_SIZE,
|
||||
length - i);
|
||||
int ret;
|
||||
|
||||
ret = sun4i_hdmi_read_sub_block(hdmi, blk, offset,
|
||||
buf + i, count);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
} while (!drm_edid_block_valid(buf, blk, true, NULL) && (retry--));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_hdmi_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = drm_connector_to_sun4i_hdmi(connector);
|
||||
unsigned long reg;
|
||||
struct edid *edid;
|
||||
int ret;
|
||||
|
||||
/* Reset i2c controller */
|
||||
writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
|
||||
!(reg & SUN4I_HDMI_DDC_CTRL_RESET),
|
||||
100, 2000))
|
||||
return -EIO;
|
||||
|
||||
writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
|
||||
SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
|
||||
hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
|
||||
|
||||
clk_prepare_enable(hdmi->ddc_clk);
|
||||
clk_set_rate(hdmi->ddc_clk, 100000);
|
||||
|
||||
edid = drm_do_get_edid(connector, sun4i_hdmi_read_edid_block, hdmi);
|
||||
edid = drm_get_edid(connector, hdmi->i2c);
|
||||
if (!edid)
|
||||
return 0;
|
||||
|
||||
|
@ -279,11 +197,10 @@ static int sun4i_hdmi_get_modes(struct drm_connector *connector)
|
|||
hdmi->hdmi_monitor ? "an HDMI" : "a DVI");
|
||||
|
||||
drm_mode_connector_update_edid_property(connector, edid);
|
||||
cec_s_phys_addr_from_edid(hdmi->cec_adap, edid);
|
||||
ret = drm_add_edid_modes(connector, edid);
|
||||
kfree(edid);
|
||||
|
||||
clk_disable_unprepare(hdmi->ddc_clk);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -299,8 +216,10 @@ sun4i_hdmi_connector_detect(struct drm_connector *connector, bool force)
|
|||
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_HPD_REG, reg,
|
||||
reg & SUN4I_HDMI_HPD_HIGH,
|
||||
0, 500000))
|
||||
0, 500000)) {
|
||||
cec_phys_addr_invalidate(hdmi->cec_adap);
|
||||
return connector_status_disconnected;
|
||||
}
|
||||
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
@ -314,6 +233,40 @@ static const struct drm_connector_funcs sun4i_hdmi_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
|
||||
static bool sun4i_hdmi_cec_pin_read(struct cec_adapter *adap)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
|
||||
|
||||
return readl(hdmi->base + SUN4I_HDMI_CEC) & SUN4I_HDMI_CEC_RX;
|
||||
}
|
||||
|
||||
static void sun4i_hdmi_cec_pin_low(struct cec_adapter *adap)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
|
||||
|
||||
/* Start driving the CEC pin low */
|
||||
writel(SUN4I_HDMI_CEC_ENABLE, hdmi->base + SUN4I_HDMI_CEC);
|
||||
}
|
||||
|
||||
static void sun4i_hdmi_cec_pin_high(struct cec_adapter *adap)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = cec_get_drvdata(adap);
|
||||
|
||||
/*
|
||||
* Stop driving the CEC pin, the pull up will take over
|
||||
* unless another CEC device is driving the pin low.
|
||||
*/
|
||||
writel(0, hdmi->base + SUN4I_HDMI_CEC);
|
||||
}
|
||||
|
||||
static const struct cec_pin_ops sun4i_hdmi_cec_pin_ops = {
|
||||
.read = sun4i_hdmi_cec_pin_read,
|
||||
.low = sun4i_hdmi_cec_pin_low,
|
||||
.high = sun4i_hdmi_cec_pin_high,
|
||||
};
|
||||
#endif
|
||||
|
||||
static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
|
@ -406,9 +359,9 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
|||
SUN4I_HDMI_PLL_CTRL_PLL_EN;
|
||||
writel(reg, hdmi->base + SUN4I_HDMI_PLL_CTRL_REG);
|
||||
|
||||
ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
|
||||
ret = sun4i_hdmi_i2c_create(dev, hdmi);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't create the DDC clock\n");
|
||||
dev_err(dev, "Couldn't create the HDMI I2C adapter\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -421,13 +374,26 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
|||
NULL);
|
||||
if (ret) {
|
||||
dev_err(dev, "Couldn't initialise the HDMI encoder\n");
|
||||
return ret;
|
||||
goto err_del_i2c_adapter;
|
||||
}
|
||||
|
||||
hdmi->encoder.possible_crtcs = drm_of_find_possible_crtcs(drm,
|
||||
dev->of_node);
|
||||
if (!hdmi->encoder.possible_crtcs)
|
||||
return -EPROBE_DEFER;
|
||||
if (!hdmi->encoder.possible_crtcs) {
|
||||
ret = -EPROBE_DEFER;
|
||||
goto err_del_i2c_adapter;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_DRM_SUN4I_HDMI_CEC
|
||||
hdmi->cec_adap = cec_pin_allocate_adapter(&sun4i_hdmi_cec_pin_ops,
|
||||
hdmi, "sun4i", CEC_CAP_TRANSMIT | CEC_CAP_LOG_ADDRS |
|
||||
CEC_CAP_PASSTHROUGH | CEC_CAP_RC);
|
||||
ret = PTR_ERR_OR_ZERO(hdmi->cec_adap);
|
||||
if (ret < 0)
|
||||
goto err_cleanup_connector;
|
||||
writel(readl(hdmi->base + SUN4I_HDMI_CEC) & ~SUN4I_HDMI_CEC_TX,
|
||||
hdmi->base + SUN4I_HDMI_CEC);
|
||||
#endif
|
||||
|
||||
drm_connector_helper_add(&hdmi->connector,
|
||||
&sun4i_hdmi_connector_helper_funcs);
|
||||
|
@ -444,12 +410,18 @@ static int sun4i_hdmi_bind(struct device *dev, struct device *master,
|
|||
hdmi->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
|
||||
DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
|
||||
ret = cec_register_adapter(hdmi->cec_adap, dev);
|
||||
if (ret < 0)
|
||||
goto err_cleanup_connector;
|
||||
drm_mode_connector_attach_encoder(&hdmi->connector, &hdmi->encoder);
|
||||
|
||||
return 0;
|
||||
|
||||
err_cleanup_connector:
|
||||
cec_delete_adapter(hdmi->cec_adap);
|
||||
drm_encoder_cleanup(&hdmi->encoder);
|
||||
err_del_i2c_adapter:
|
||||
i2c_del_adapter(hdmi->i2c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
@ -458,8 +430,10 @@ static void sun4i_hdmi_unbind(struct device *dev, struct device *master,
|
|||
{
|
||||
struct sun4i_hdmi *hdmi = dev_get_drvdata(dev);
|
||||
|
||||
cec_unregister_adapter(hdmi->cec_adap);
|
||||
drm_connector_cleanup(&hdmi->connector);
|
||||
drm_encoder_cleanup(&hdmi->encoder);
|
||||
i2c_del_adapter(hdmi->i2c);
|
||||
}
|
||||
|
||||
static const struct component_ops sun4i_hdmi_ops = {
|
||||
|
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* Copyright (C) 2016 Maxime Ripard <maxime.ripard@free-electrons.com>
|
||||
* Copyright (C) 2017 Jonathan Liu <net147@gmail.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
#include "sun4i_hdmi.h"
|
||||
|
||||
#define SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK ( \
|
||||
SUN4I_HDMI_DDC_INT_STATUS_ILLEGAL_FIFO_OPERATION | \
|
||||
SUN4I_HDMI_DDC_INT_STATUS_DDC_RX_FIFO_UNDERFLOW | \
|
||||
SUN4I_HDMI_DDC_INT_STATUS_DDC_TX_FIFO_OVERFLOW | \
|
||||
SUN4I_HDMI_DDC_INT_STATUS_ARBITRATION_ERROR | \
|
||||
SUN4I_HDMI_DDC_INT_STATUS_ACK_ERROR | \
|
||||
SUN4I_HDMI_DDC_INT_STATUS_BUS_ERROR \
|
||||
)
|
||||
|
||||
/* FIFO request bit is set when FIFO level is above RX_THRESHOLD during read */
|
||||
#define RX_THRESHOLD SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MAX
|
||||
/* FIFO request bit is set when FIFO level is below TX_THRESHOLD during write */
|
||||
#define TX_THRESHOLD 1
|
||||
|
||||
static int fifo_transfer(struct sun4i_hdmi *hdmi, u8 *buf, int len, bool read)
|
||||
{
|
||||
/*
|
||||
* 1 byte takes 9 clock cycles (8 bits + 1 ACK) = 90 us for 100 kHz
|
||||
* clock. As clock rate is fixed, just round it up to 100 us.
|
||||
*/
|
||||
const unsigned long byte_time_ns = 100;
|
||||
const u32 mask = SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE;
|
||||
u32 reg;
|
||||
|
||||
/* Limit transfer length by FIFO threshold */
|
||||
len = min_t(int, len, read ? (RX_THRESHOLD + 1) :
|
||||
(SUN4I_HDMI_DDC_FIFO_SIZE - TX_THRESHOLD + 1));
|
||||
|
||||
/* Wait until error, FIFO request bit set or transfer complete */
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG, reg,
|
||||
reg & mask, len * byte_time_ns, 100000))
|
||||
return -ETIMEDOUT;
|
||||
|
||||
if (reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK)
|
||||
return -EIO;
|
||||
|
||||
if (read)
|
||||
readsb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
|
||||
else
|
||||
writesb(hdmi->base + SUN4I_HDMI_DDC_FIFO_DATA_REG, buf, len);
|
||||
|
||||
/* Clear FIFO request bit */
|
||||
writel(SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST,
|
||||
hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int xfer_msg(struct sun4i_hdmi *hdmi, struct i2c_msg *msg)
|
||||
{
|
||||
int i, len;
|
||||
u32 reg;
|
||||
|
||||
/* Set FIFO direction */
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
reg &= ~SUN4I_HDMI_DDC_CTRL_FIFO_DIR_MASK;
|
||||
reg |= (msg->flags & I2C_M_RD) ?
|
||||
SUN4I_HDMI_DDC_CTRL_FIFO_DIR_READ :
|
||||
SUN4I_HDMI_DDC_CTRL_FIFO_DIR_WRITE;
|
||||
writel(reg, hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
|
||||
/* Set I2C address */
|
||||
writel(SUN4I_HDMI_DDC_ADDR_SLAVE(msg->addr),
|
||||
hdmi->base + SUN4I_HDMI_DDC_ADDR_REG);
|
||||
|
||||
/* Set FIFO RX/TX thresholds and clear FIFO */
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
|
||||
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR;
|
||||
reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES_MASK;
|
||||
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_RX_THRES(RX_THRESHOLD);
|
||||
reg &= ~SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES_MASK;
|
||||
reg |= SUN4I_HDMI_DDC_FIFO_CTRL_TX_THRES(TX_THRESHOLD);
|
||||
writel(reg, hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG);
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_FIFO_CTRL_REG,
|
||||
reg,
|
||||
!(reg & SUN4I_HDMI_DDC_FIFO_CTRL_CLEAR),
|
||||
100, 2000))
|
||||
return -EIO;
|
||||
|
||||
/* Set transfer length */
|
||||
writel(msg->len, hdmi->base + SUN4I_HDMI_DDC_BYTE_COUNT_REG);
|
||||
|
||||
/* Set command */
|
||||
writel(msg->flags & I2C_M_RD ?
|
||||
SUN4I_HDMI_DDC_CMD_IMPLICIT_READ :
|
||||
SUN4I_HDMI_DDC_CMD_IMPLICIT_WRITE,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CMD_REG);
|
||||
|
||||
/* Clear interrupt status bits */
|
||||
writel(SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_FIFO_REQUEST |
|
||||
SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE,
|
||||
hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
|
||||
|
||||
/* Start command */
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
writel(reg | SUN4I_HDMI_DDC_CTRL_START_CMD,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
|
||||
/* Transfer bytes */
|
||||
for (i = 0; i < msg->len; i += len) {
|
||||
len = fifo_transfer(hdmi, msg->buf + i, msg->len - i,
|
||||
msg->flags & I2C_M_RD);
|
||||
if (len <= 0)
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Wait for command to finish */
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG,
|
||||
reg,
|
||||
!(reg & SUN4I_HDMI_DDC_CTRL_START_CMD),
|
||||
100, 100000))
|
||||
return -EIO;
|
||||
|
||||
/* Check for errors */
|
||||
reg = readl(hdmi->base + SUN4I_HDMI_DDC_INT_STATUS_REG);
|
||||
if ((reg & SUN4I_HDMI_DDC_INT_STATUS_ERROR_MASK) ||
|
||||
!(reg & SUN4I_HDMI_DDC_INT_STATUS_TRANSFER_COMPLETE)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sun4i_hdmi_i2c_xfer(struct i2c_adapter *adap,
|
||||
struct i2c_msg *msgs, int num)
|
||||
{
|
||||
struct sun4i_hdmi *hdmi = i2c_get_adapdata(adap);
|
||||
u32 reg;
|
||||
int err, i, ret = num;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
if (!msgs[i].len)
|
||||
return -EINVAL;
|
||||
if (msgs[i].len > SUN4I_HDMI_DDC_BYTE_COUNT_MAX)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Reset I2C controller */
|
||||
writel(SUN4I_HDMI_DDC_CTRL_ENABLE | SUN4I_HDMI_DDC_CTRL_RESET,
|
||||
hdmi->base + SUN4I_HDMI_DDC_CTRL_REG);
|
||||
if (readl_poll_timeout(hdmi->base + SUN4I_HDMI_DDC_CTRL_REG, reg,
|
||||
!(reg & SUN4I_HDMI_DDC_CTRL_RESET),
|
||||
100, 2000))
|
||||
return -EIO;
|
||||
|
||||
writel(SUN4I_HDMI_DDC_LINE_CTRL_SDA_ENABLE |
|
||||
SUN4I_HDMI_DDC_LINE_CTRL_SCL_ENABLE,
|
||||
hdmi->base + SUN4I_HDMI_DDC_LINE_CTRL_REG);
|
||||
|
||||
clk_prepare_enable(hdmi->ddc_clk);
|
||||
clk_set_rate(hdmi->ddc_clk, 100000);
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
err = xfer_msg(hdmi, &msgs[i]);
|
||||
if (err) {
|
||||
ret = err;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
clk_disable_unprepare(hdmi->ddc_clk);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static u32 sun4i_hdmi_i2c_func(struct i2c_adapter *adap)
|
||||
{
|
||||
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
|
||||
}
|
||||
|
||||
static const struct i2c_algorithm sun4i_hdmi_i2c_algorithm = {
|
||||
.master_xfer = sun4i_hdmi_i2c_xfer,
|
||||
.functionality = sun4i_hdmi_i2c_func,
|
||||
};
|
||||
|
||||
int sun4i_hdmi_i2c_create(struct device *dev, struct sun4i_hdmi *hdmi)
|
||||
{
|
||||
struct i2c_adapter *adap;
|
||||
int ret = 0;
|
||||
|
||||
ret = sun4i_ddc_create(hdmi, hdmi->tmds_clk);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
|
||||
if (!adap)
|
||||
return -ENOMEM;
|
||||
|
||||
adap->owner = THIS_MODULE;
|
||||
adap->class = I2C_CLASS_DDC;
|
||||
adap->algo = &sun4i_hdmi_i2c_algorithm;
|
||||
strlcpy(adap->name, "sun4i_hdmi_i2c adapter", sizeof(adap->name));
|
||||
i2c_set_adapdata(adap, hdmi);
|
||||
|
||||
ret = i2c_add_adapter(adap);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
hdmi->i2c = adap;
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -25,12 +25,6 @@ struct sun4i_plane_desc {
|
|||
uint32_t nformats;
|
||||
};
|
||||
|
||||
static int sun4i_backend_layer_atomic_check(struct drm_plane *plane,
|
||||
struct drm_plane_state *state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sun4i_backend_layer_atomic_disable(struct drm_plane *plane,
|
||||
struct drm_plane_state *old_state)
|
||||
{
|
||||
|
@ -52,8 +46,7 @@ static void sun4i_backend_layer_atomic_update(struct drm_plane *plane,
|
|||
sun4i_backend_layer_enable(backend, layer->id, true);
|
||||
}
|
||||
|
||||
static struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = {
|
||||
.atomic_check = sun4i_backend_layer_atomic_check,
|
||||
static const struct drm_plane_helper_funcs sun4i_backend_layer_helper_funcs = {
|
||||
.atomic_disable = sun4i_backend_layer_atomic_disable,
|
||||
.atomic_update = sun4i_backend_layer_atomic_update,
|
||||
};
|
||||
|
|
|
@ -127,13 +127,6 @@ static const struct drm_connector_funcs sun4i_rgb_con_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static int sun4i_rgb_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sun4i_rgb_encoder_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_rgb *rgb = drm_encoder_to_sun4i_rgb(encoder);
|
||||
|
@ -181,7 +174,6 @@ static void sun4i_rgb_encoder_mode_set(struct drm_encoder *encoder,
|
|||
}
|
||||
|
||||
static struct drm_encoder_helper_funcs sun4i_rgb_enc_helper_funcs = {
|
||||
.atomic_check = sun4i_rgb_atomic_check,
|
||||
.mode_set = sun4i_rgb_encoder_mode_set,
|
||||
.disable = sun4i_rgb_encoder_disable,
|
||||
.enable = sun4i_rgb_encoder_enable,
|
||||
|
|
|
@ -194,8 +194,6 @@ void sun4i_tcon_channel_enable(struct sun4i_tcon *tcon, int channel);
|
|||
void sun4i_tcon_enable_vblank(struct sun4i_tcon *tcon, bool enable);
|
||||
|
||||
/* Mode Related Controls */
|
||||
void sun4i_tcon_switch_interlace(struct sun4i_tcon *tcon,
|
||||
bool enable);
|
||||
void sun4i_tcon_set_mux(struct sun4i_tcon *tcon, int channel,
|
||||
struct drm_encoder *encoder);
|
||||
void sun4i_tcon0_mode_set(struct sun4i_tcon *tcon,
|
||||
|
|
|
@ -341,13 +341,6 @@ static void sun4i_tv_mode_to_drm_mode(const struct tv_mode *tv_mode,
|
|||
mode->vtotal = mode->vsync_end + tv_mode->vback_porch;
|
||||
}
|
||||
|
||||
static int sun4i_tv_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void sun4i_tv_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct sun4i_tv *tv = drm_encoder_to_sun4i_tv(encoder);
|
||||
|
@ -489,7 +482,6 @@ static void sun4i_tv_mode_set(struct drm_encoder *encoder,
|
|||
}
|
||||
|
||||
static struct drm_encoder_helper_funcs sun4i_tv_helper_funcs = {
|
||||
.atomic_check = sun4i_tv_atomic_check,
|
||||
.disable = sun4i_tv_disable,
|
||||
.enable = sun4i_tv_enable,
|
||||
.mode_set = sun4i_tv_mode_set,
|
||||
|
|
Loading…
Reference in New Issue