summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/display/drm_hdmi_cec_helper.c
blob: 3651ad0f76e0c49cc7b3a8e2148dccb1f290e95a (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
// SPDX-License-Identifier: MIT
/*
 * Copyright (c) 2024 Linaro Ltd
 */

#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_managed.h>
#include <drm/display/drm_hdmi_cec_helper.h>

#include <linux/export.h>
#include <linux/mutex.h>

#include <media/cec.h>

struct drm_connector_hdmi_cec_data {
	struct cec_adapter *adapter;
	const struct drm_connector_hdmi_cec_funcs *funcs;
};

static int drm_connector_hdmi_cec_adap_enable(struct cec_adapter *adap, bool enable)
{
	struct drm_connector *connector = cec_get_drvdata(adap);
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	return data->funcs->enable(connector, enable);
}

static int drm_connector_hdmi_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
{
	struct drm_connector *connector = cec_get_drvdata(adap);
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	return data->funcs->log_addr(connector, logical_addr);
}

static int drm_connector_hdmi_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
						u32 signal_free_time, struct cec_msg *msg)
{
	struct drm_connector *connector = cec_get_drvdata(adap);
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	return data->funcs->transmit(connector, attempts, signal_free_time, msg);
}

static const struct cec_adap_ops drm_connector_hdmi_cec_adap_ops = {
	.adap_enable = drm_connector_hdmi_cec_adap_enable,
	.adap_log_addr = drm_connector_hdmi_cec_adap_log_addr,
	.adap_transmit = drm_connector_hdmi_cec_adap_transmit,
};

static void drm_connector_hdmi_cec_adapter_phys_addr_invalidate(struct drm_connector *connector)
{
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	cec_phys_addr_invalidate(data->adapter);
}

static void drm_connector_hdmi_cec_adapter_phys_addr_set(struct drm_connector *connector,
							 u16 addr)
{
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	cec_s_phys_addr(data->adapter, addr, false);
}

static void drm_connector_hdmi_cec_adapter_unregister(struct drm_device *dev, void *res)
{
	struct drm_connector *connector = res;
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	cec_unregister_adapter(data->adapter);

	if (data->funcs->uninit)
		data->funcs->uninit(connector);

	kfree(data);
	connector->cec.data = NULL;
}

static struct drm_connector_cec_funcs drm_connector_hdmi_cec_adapter_funcs = {
	.phys_addr_invalidate = drm_connector_hdmi_cec_adapter_phys_addr_invalidate,
	.phys_addr_set = drm_connector_hdmi_cec_adapter_phys_addr_set,
};

int drmm_connector_hdmi_cec_register(struct drm_connector *connector,
				     const struct drm_connector_hdmi_cec_funcs *funcs,
				     const char *name,
				     u8 available_las,
				     struct device *dev)
{
	struct drm_connector_hdmi_cec_data *data;
	struct cec_connector_info conn_info;
	struct cec_adapter *cec_adap;
	int ret;

	if (!funcs->init || !funcs->enable || !funcs->log_addr || !funcs->transmit)
		return -EINVAL;

	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	data->funcs = funcs;

	cec_adap = cec_allocate_adapter(&drm_connector_hdmi_cec_adap_ops, connector, name,
					CEC_CAP_DEFAULTS | CEC_CAP_CONNECTOR_INFO,
					available_las ? : CEC_MAX_LOG_ADDRS);
	ret = PTR_ERR_OR_ZERO(cec_adap);
	if (ret < 0)
		goto err_free;

	cec_fill_conn_info_from_drm(&conn_info, connector);
	cec_s_conn_info(cec_adap, &conn_info);

	data->adapter = cec_adap;

	mutex_lock(&connector->cec.mutex);

	connector->cec.data = data;
	connector->cec.funcs = &drm_connector_hdmi_cec_adapter_funcs;

	ret = funcs->init(connector);
	if (ret < 0)
		goto err_delete_adapter;

	/*
	 * NOTE: the CEC adapter will be unregistered by drmm cleanup from
	 * drm_managed_release(), which is called from drm_dev_release()
	 * during device unbind.
	 *
	 * However, the CEC framework cleans up the CEC adapter only when the
	 * last user has closed its file descriptor, so we don't need to handle
	 * it in DRM.
	 *
	 * Before that CEC framework makes sure that even if the userspace
	 * still holds CEC device open, all calls will be shortcut via
	 * cec_is_registered(), making sure that there is no access to the
	 * freed memory.
	 */
	ret = cec_register_adapter(cec_adap, dev);
	if (ret < 0)
		goto err_delete_adapter;

	mutex_unlock(&connector->cec.mutex);

	return drmm_add_action_or_reset(connector->dev,
					drm_connector_hdmi_cec_adapter_unregister,
					connector);

err_delete_adapter:
	cec_delete_adapter(cec_adap);

	connector->cec.data = NULL;

	mutex_unlock(&connector->cec.mutex);

err_free:
	kfree(data);

	return ret;
}
EXPORT_SYMBOL(drmm_connector_hdmi_cec_register);

void drm_connector_hdmi_cec_received_msg(struct drm_connector *connector,
					 struct cec_msg *msg)
{
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	cec_received_msg(data->adapter, msg);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_received_msg);

void drm_connector_hdmi_cec_transmit_attempt_done(struct drm_connector *connector,
						  u8 status)
{
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	cec_transmit_attempt_done(data->adapter, status);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_attempt_done);

void drm_connector_hdmi_cec_transmit_done(struct drm_connector *connector,
					  u8 status,
					  u8 arb_lost_cnt, u8 nack_cnt,
					  u8 low_drive_cnt, u8 error_cnt)
{
	struct drm_connector_hdmi_cec_data *data = connector->cec.data;

	cec_transmit_done(data->adapter, status,
			  arb_lost_cnt, nack_cnt, low_drive_cnt, error_cnt);
}
EXPORT_SYMBOL(drm_connector_hdmi_cec_transmit_done);