Commit 13afcdd7 authored by Philip Chen's avatar Philip Chen Committed by Douglas Anderson
Browse files

drm/bridge: parade-ps8640: Add support for AUX channel



Implement the first version of AUX support, which will be useful as
we expand the driver to support varied use cases.

Signed-off-by: default avatarPhilip Chen <philipchen@chromium.org>
Reviewed-by: default avatarSam Ravnborg <sam@ravnborg.org>
Reviewed-by: default avatarDouglas Anderson <dianders@chromium.org>
Reviewed-by: default avatarStephen Boyd <swboyd@chromium.org>
[dianders: whitespace fixes reported by dim apply-branch]
Signed-off-by: default avatarDouglas Anderson <dianders@chromium.org>
Link: https://patchwork.freedesktop.org/patch/msgid/20210921110556.v6.2.I1d6ea362dc76efa77cca2b46253d31b7651eaf17@changeid
parent 692d8db0
Loading
Loading
Loading
Loading
+179 −1
Original line number Diff line number Diff line
@@ -13,11 +13,36 @@
#include <linux/regulator/consumer.h>

#include <drm/drm_bridge.h>
#include <drm/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>

#define PAGE0_AUXCH_CFG3	0x76
#define  AUXCH_CFG3_RESET	0xff
#define PAGE0_SWAUX_ADDR_7_0	0x7d
#define PAGE0_SWAUX_ADDR_15_8	0x7e
#define PAGE0_SWAUX_ADDR_23_16	0x7f
#define  SWAUX_ADDR_MASK	GENMASK(19, 0)
#define PAGE0_SWAUX_LENGTH	0x80
#define  SWAUX_LENGTH_MASK	GENMASK(3, 0)
#define  SWAUX_NO_PAYLOAD	BIT(7)
#define PAGE0_SWAUX_WDATA	0x81
#define PAGE0_SWAUX_RDATA	0x82
#define PAGE0_SWAUX_CTRL	0x83
#define  SWAUX_SEND		BIT(0)
#define PAGE0_SWAUX_STATUS	0x84
#define  SWAUX_M_MASK		GENMASK(4, 0)
#define  SWAUX_STATUS_MASK	GENMASK(7, 5)
#define  SWAUX_STATUS_NACK	(0x1 << 5)
#define  SWAUX_STATUS_DEFER	(0x2 << 5)
#define  SWAUX_STATUS_ACKM	(0x3 << 5)
#define  SWAUX_STATUS_INVALID	(0x4 << 5)
#define  SWAUX_STATUS_I2C_NACK	(0x5 << 5)
#define  SWAUX_STATUS_I2C_DEFER	(0x6 << 5)
#define  SWAUX_STATUS_TIMEOUT	(0x7 << 5)

#define PAGE2_GPIO_H		0xa7
#define  PS_GPIO9		BIT(1)
#define PAGE2_I2C_BYPASS	0xea
@@ -68,6 +93,7 @@ enum ps8640_vdo_control {
struct ps8640 {
	struct drm_bridge bridge;
	struct drm_bridge *panel_bridge;
	struct drm_dp_aux aux;
	struct mipi_dsi_device *dsi;
	struct i2c_client *page[MAX_DEVS];
	struct regmap	*regmap[MAX_DEVS];
@@ -117,6 +143,137 @@ static inline struct ps8640 *bridge_to_ps8640(struct drm_bridge *e)
	return container_of(e, struct ps8640, bridge);
}

static inline struct ps8640 *aux_to_ps8640(struct drm_dp_aux *aux)
{
	return container_of(aux, struct ps8640, aux);
}

static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
				   struct drm_dp_aux_msg *msg)
{
	struct ps8640 *ps_bridge = aux_to_ps8640(aux);
	struct regmap *map = ps_bridge->regmap[PAGE0_DP_CNTL];
	struct device *dev = &ps_bridge->page[PAGE0_DP_CNTL]->dev;
	unsigned int len = msg->size;
	unsigned int data;
	unsigned int base;
	int ret;
	u8 request = msg->request &
		     ~(DP_AUX_I2C_MOT | DP_AUX_I2C_WRITE_STATUS_UPDATE);
	u8 *buf = msg->buffer;
	u8 addr_len[PAGE0_SWAUX_LENGTH + 1 - PAGE0_SWAUX_ADDR_7_0];
	u8 i;
	bool is_native_aux = false;

	if (len > DP_AUX_MAX_PAYLOAD_BYTES)
		return -EINVAL;

	if (msg->address & ~SWAUX_ADDR_MASK)
		return -EINVAL;

	switch (request) {
	case DP_AUX_NATIVE_WRITE:
	case DP_AUX_NATIVE_READ:
		is_native_aux = true;
		fallthrough;
	case DP_AUX_I2C_WRITE:
	case DP_AUX_I2C_READ:
		break;
	default:
		return -EINVAL;
	}

	ret = regmap_write(map, PAGE0_AUXCH_CFG3, AUXCH_CFG3_RESET);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to write PAGE0_AUXCH_CFG3: %d\n",
			      ret);
		return ret;
	}

	/* Assume it's good */
	msg->reply = 0;

	base = PAGE0_SWAUX_ADDR_7_0;
	addr_len[PAGE0_SWAUX_ADDR_7_0 - base] = msg->address;
	addr_len[PAGE0_SWAUX_ADDR_15_8 - base] = msg->address >> 8;
	addr_len[PAGE0_SWAUX_ADDR_23_16 - base] = (msg->address >> 16) |
						  (msg->request << 4);
	addr_len[PAGE0_SWAUX_LENGTH - base] = (len == 0) ? SWAUX_NO_PAYLOAD :
					      ((len - 1) & SWAUX_LENGTH_MASK);

	regmap_bulk_write(map, PAGE0_SWAUX_ADDR_7_0, addr_len,
			  ARRAY_SIZE(addr_len));

	if (len && (request == DP_AUX_NATIVE_WRITE ||
		    request == DP_AUX_I2C_WRITE)) {
		/* Write to the internal FIFO buffer */
		for (i = 0; i < len; i++) {
			ret = regmap_write(map, PAGE0_SWAUX_WDATA, buf[i]);
			if (ret) {
				DRM_DEV_ERROR(dev,
					      "failed to write WDATA: %d\n",
					      ret);
				return ret;
			}
		}
	}

	regmap_write(map, PAGE0_SWAUX_CTRL, SWAUX_SEND);

	/* Zero delay loop because i2c transactions are slow already */
	regmap_read_poll_timeout(map, PAGE0_SWAUX_CTRL, data,
				 !(data & SWAUX_SEND), 0, 50 * 1000);

	regmap_read(map, PAGE0_SWAUX_STATUS, &data);
	if (ret) {
		DRM_DEV_ERROR(dev, "failed to read PAGE0_SWAUX_STATUS: %d\n",
			      ret);
		return ret;
	}

	switch (data & SWAUX_STATUS_MASK) {
	/* Ignore the DEFER cases as they are already handled in hardware */
	case SWAUX_STATUS_NACK:
	case SWAUX_STATUS_I2C_NACK:
		/*
		 * The programming guide is not clear about whether a I2C NACK
		 * would trigger SWAUX_STATUS_NACK or SWAUX_STATUS_I2C_NACK. So
		 * we handle both cases together.
		 */
		if (is_native_aux)
			msg->reply |= DP_AUX_NATIVE_REPLY_NACK;
		else
			msg->reply |= DP_AUX_I2C_REPLY_NACK;

		fallthrough;
	case SWAUX_STATUS_ACKM:
		len = data & SWAUX_M_MASK;
		break;
	case SWAUX_STATUS_INVALID:
		return -EOPNOTSUPP;
	case SWAUX_STATUS_TIMEOUT:
		return -ETIMEDOUT;
	}

	if (len && (request == DP_AUX_NATIVE_READ ||
		    request == DP_AUX_I2C_READ)) {
		/* Read from the internal FIFO buffer */
		for (i = 0; i < len; i++) {
			ret = regmap_read(map, PAGE0_SWAUX_RDATA, &data);
			if (ret) {
				DRM_DEV_ERROR(dev,
					      "failed to read RDATA: %d\n",
					      ret);
				return ret;
			}

			buf[i] = data;
		}
	}

	return len;
}

static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge,
				     const enum ps8640_vdo_control ctrl)
{
@@ -286,18 +443,33 @@ static int ps8640_bridge_attach(struct drm_bridge *bridge,
	dsi->format = MIPI_DSI_FMT_RGB888;
	dsi->lanes = NUM_MIPI_LANES;
	ret = mipi_dsi_attach(dsi);
	if (ret)
	if (ret) {
		dev_err(dev, "failed to attach dsi device: %d\n", ret);
		goto err_dsi_attach;
	}

	ret = drm_dp_aux_register(&ps_bridge->aux);
	if (ret) {
		dev_err(dev, "failed to register DP AUX channel: %d\n", ret);
		goto err_aux_register;
	}

	/* Attach the panel-bridge to the dsi bridge */
	return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge,
				 &ps_bridge->bridge, flags);

err_aux_register:
	mipi_dsi_detach(dsi);
err_dsi_attach:
	mipi_dsi_device_unregister(dsi);
	return ret;
}

static void ps8640_bridge_detach(struct drm_bridge *bridge)
{
	drm_dp_aux_unregister(&bridge_to_ps8640(bridge)->aux);
}

static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,
					   struct drm_connector *connector)
{
@@ -334,6 +506,7 @@ static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,

static const struct drm_bridge_funcs ps8640_bridge_funcs = {
	.attach = ps8640_bridge_attach,
	.detach = ps8640_bridge_detach,
	.get_edid = ps8640_bridge_get_edid,
	.post_disable = ps8640_post_disable,
	.pre_enable = ps8640_pre_enable,
@@ -409,6 +582,11 @@ static int ps8640_probe(struct i2c_client *client)

	i2c_set_clientdata(client, ps_bridge);

	ps_bridge->aux.name = "parade-ps8640-aux";
	ps_bridge->aux.dev = dev;
	ps_bridge->aux.transfer = ps8640_aux_transfer;
	drm_dp_aux_init(&ps_bridge->aux);

	drm_bridge_add(&ps_bridge->bridge);

	return 0;