summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/adp/adp-mipi.c
blob: ad80542b60ed6de3fe76a4d134b87d20df98ee33 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
// SPDX-License-Identifier: GPL-2.0-only

#include <linux/component.h>
#include <linux/iopoll.h>
#include <linux/of.h>
#include <linux/platform_device.h>

#include <drm/drm_bridge.h>
#include <drm/drm_mipi_dsi.h>

#define DSI_GEN_HDR 0x6c
#define DSI_GEN_PLD_DATA 0x70

#define DSI_CMD_PKT_STATUS 0x74

#define GEN_PLD_R_EMPTY BIT(4)
#define GEN_PLD_W_FULL BIT(3)
#define GEN_PLD_W_EMPTY BIT(2)
#define GEN_CMD_FULL BIT(1)
#define GEN_CMD_EMPTY BIT(0)
#define GEN_RD_CMD_BUSY BIT(6)
#define CMD_PKT_STATUS_TIMEOUT_US 20000

struct adp_mipi_drv_private {
	struct mipi_dsi_host dsi;
	struct drm_bridge bridge;
	struct drm_bridge *next_bridge;
	void __iomem *mipi;
};

#define mipi_to_adp(x) container_of(x, struct adp_mipi_drv_private, dsi)

static int adp_dsi_gen_pkt_hdr_write(struct adp_mipi_drv_private *adp, u32 hdr_val)
{
	int ret;
	u32 val, mask;

	ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
				 val, !(val & GEN_CMD_FULL), 1000,
				 CMD_PKT_STATUS_TIMEOUT_US);
	if (ret) {
		dev_err(adp->dsi.dev, "failed to get available command FIFO\n");
		return ret;
	}

	writel(hdr_val, adp->mipi + DSI_GEN_HDR);

	mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY;
	ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
				 val, (val & mask) == mask,
				 1000, CMD_PKT_STATUS_TIMEOUT_US);
	if (ret) {
		dev_err(adp->dsi.dev, "failed to write command FIFO\n");
		return ret;
	}

	return 0;
}

static int adp_dsi_write(struct adp_mipi_drv_private *adp,
			 const struct mipi_dsi_packet *packet)
{
	const u8 *tx_buf = packet->payload;
	int len = packet->payload_length, pld_data_bytes = sizeof(u32), ret;
	__le32 word;
	u32 val;

	while (len) {
		if (len < pld_data_bytes) {
			word = 0;
			memcpy(&word, tx_buf, len);
			writel(le32_to_cpu(word), adp->mipi + DSI_GEN_PLD_DATA);
			len = 0;
		} else {
			memcpy(&word, tx_buf, pld_data_bytes);
			writel(le32_to_cpu(word), adp->mipi + DSI_GEN_PLD_DATA);
			tx_buf += pld_data_bytes;
			len -= pld_data_bytes;
		}

		ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
					 val, !(val & GEN_PLD_W_FULL), 1000,
					 CMD_PKT_STATUS_TIMEOUT_US);
		if (ret) {
			dev_err(adp->dsi.dev,
				"failed to get available write payload FIFO\n");
			return ret;
		}
	}

	word = 0;
	memcpy(&word, packet->header, sizeof(packet->header));
	return adp_dsi_gen_pkt_hdr_write(adp, le32_to_cpu(word));
}

static int adp_dsi_read(struct adp_mipi_drv_private *adp,
			const struct mipi_dsi_msg *msg)
{
	int i, j, ret, len = msg->rx_len;
	u8 *buf = msg->rx_buf;
	u32 val;

	/* Wait end of the read operation */
	ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
				 val, !(val & GEN_RD_CMD_BUSY),
				 1000, CMD_PKT_STATUS_TIMEOUT_US);
	if (ret) {
		dev_err(adp->dsi.dev, "Timeout during read operation\n");
		return ret;
	}

	for (i = 0; i < len; i += 4) {
		/* Read fifo must not be empty before all bytes are read */
		ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
					 val, !(val & GEN_PLD_R_EMPTY),
					 1000, CMD_PKT_STATUS_TIMEOUT_US);
		if (ret) {
			dev_err(adp->dsi.dev, "Read payload FIFO is empty\n");
			return ret;
		}

		val = readl(adp->mipi + DSI_GEN_PLD_DATA);
		for (j = 0; j < 4 && j + i < len; j++)
			buf[i + j] = val >> (8 * j);
	}

	return ret;
}

static ssize_t adp_dsi_host_transfer(struct mipi_dsi_host *host,
				     const struct mipi_dsi_msg *msg)
{
	struct adp_mipi_drv_private *adp = mipi_to_adp(host);
	struct mipi_dsi_packet packet;
	int ret, nb_bytes;

	ret = mipi_dsi_create_packet(&packet, msg);
	if (ret) {
		dev_err(adp->dsi.dev, "failed to create packet: %d\n", ret);
		return ret;
	}

	ret = adp_dsi_write(adp, &packet);
	if (ret)
		return ret;

	if (msg->rx_buf && msg->rx_len) {
		ret = adp_dsi_read(adp, msg);
		if (ret)
			return ret;
		nb_bytes = msg->rx_len;
	} else {
		nb_bytes = packet.size;
	}

	return nb_bytes;
}

static int adp_dsi_bind(struct device *dev, struct device *master, void *data)
{
	return 0;
}

static void adp_dsi_unbind(struct device *dev, struct device *master, void *data)
{
}

static const struct component_ops adp_dsi_component_ops = {
	.bind	= adp_dsi_bind,
	.unbind	= adp_dsi_unbind,
};

static int adp_dsi_host_attach(struct mipi_dsi_host *host,
			       struct mipi_dsi_device *dev)
{
	struct adp_mipi_drv_private *adp = mipi_to_adp(host);
	struct drm_bridge *next;
	int ret;

	next = devm_drm_of_get_bridge(adp->dsi.dev, adp->dsi.dev->of_node, 1, 0);
	if (IS_ERR(next))
		return PTR_ERR(next);

	adp->next_bridge = next;

	drm_bridge_add(&adp->bridge);

	ret = component_add(host->dev, &adp_dsi_component_ops);
	if (ret) {
		pr_err("failed to add dsi_host component: %d\n", ret);
		drm_bridge_remove(&adp->bridge);
		return ret;
	}

	return 0;
}

static int adp_dsi_host_detach(struct mipi_dsi_host *host,
			       struct mipi_dsi_device *dev)
{
	struct adp_mipi_drv_private *adp = mipi_to_adp(host);

	component_del(host->dev, &adp_dsi_component_ops);
	drm_bridge_remove(&adp->bridge);
	return 0;
}

static const struct mipi_dsi_host_ops adp_dsi_host_ops = {
	.transfer = adp_dsi_host_transfer,
	.attach = adp_dsi_host_attach,
	.detach = adp_dsi_host_detach,
};

static int adp_dsi_bridge_attach(struct drm_bridge *bridge,
				 enum drm_bridge_attach_flags flags)
{
	struct adp_mipi_drv_private *adp =
		container_of(bridge, struct adp_mipi_drv_private, bridge);

	return drm_bridge_attach(bridge->encoder, adp->next_bridge, bridge, flags);
}

static const struct drm_bridge_funcs adp_dsi_bridge_funcs = {
	.attach	= adp_dsi_bridge_attach,
};

static int adp_mipi_probe(struct platform_device *pdev)
{
	struct adp_mipi_drv_private *adp;

	adp = devm_kzalloc(&pdev->dev, sizeof(*adp), GFP_KERNEL);
	if (!adp)
		return -ENOMEM;

	adp->mipi = devm_platform_ioremap_resource(pdev, 0);
	if (IS_ERR(adp->mipi)) {
		dev_err(&pdev->dev, "failed to map mipi mmio");
		return PTR_ERR(adp->mipi);
	}

	adp->dsi.dev = &pdev->dev;
	adp->dsi.ops = &adp_dsi_host_ops;
	adp->bridge.funcs = &adp_dsi_bridge_funcs;
	adp->bridge.of_node = pdev->dev.of_node;
	adp->bridge.type = DRM_MODE_CONNECTOR_DSI;
	dev_set_drvdata(&pdev->dev, adp);
	return mipi_dsi_host_register(&adp->dsi);
}

static void adp_mipi_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct adp_mipi_drv_private *adp = dev_get_drvdata(dev);

	mipi_dsi_host_unregister(&adp->dsi);
}

static const struct of_device_id adp_mipi_of_match[] = {
	{ .compatible = "apple,h7-display-pipe-mipi", },
	{ },
};
MODULE_DEVICE_TABLE(of, adp_mipi_of_match);

static struct platform_driver adp_mipi_platform_driver = {
	.driver = {
		.name = "adp-mipi",
		.of_match_table = adp_mipi_of_match,
	},
	.probe = adp_mipi_probe,
	.remove = adp_mipi_remove,
};

module_platform_driver(adp_mipi_platform_driver);

MODULE_DESCRIPTION("Apple Display Pipe MIPI driver");
MODULE_LICENSE("GPL");