Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5fd27c2a authored by Dave Airlie's avatar Dave Airlie
Browse files

Merge tag 'sunxi-drm-for-4.14' of...

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
parents f30b8eaa 998140d2
Loading
Loading
Loading
Loading
+27 −9
Original line number Diff line number Diff line
@@ -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.

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.
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.

    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 −3
Original line number Diff line number Diff line
@@ -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
+1 −0
Original line number Diff line number Diff line
@@ -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

+32 −0
Original line number Diff line number Diff line
@@ -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_ */
+65 −91
Original line number Diff line number Diff line
@@ -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 = {
Loading