1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2013 Red Hat |
4 | * Author: Rob Clark <robdclark@gmail.com> |
5 | */ |
6 | |
7 | #include <linux/delay.h> |
8 | #include <linux/gpio/consumer.h> |
9 | #include <linux/pinctrl/consumer.h> |
10 | |
11 | #include "msm_kms.h" |
12 | #include "hdmi.h" |
13 | |
14 | static void msm_hdmi_phy_reset(struct hdmi *hdmi) |
15 | { |
16 | unsigned int val; |
17 | |
18 | val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL); |
19 | |
20 | if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { |
21 | /* pull low */ |
22 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
23 | data: val & ~HDMI_PHY_CTRL_SW_RESET); |
24 | } else { |
25 | /* pull high */ |
26 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
27 | data: val | HDMI_PHY_CTRL_SW_RESET); |
28 | } |
29 | |
30 | if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) { |
31 | /* pull low */ |
32 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
33 | data: val & ~HDMI_PHY_CTRL_SW_RESET_PLL); |
34 | } else { |
35 | /* pull high */ |
36 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
37 | data: val | HDMI_PHY_CTRL_SW_RESET_PLL); |
38 | } |
39 | |
40 | msleep(msecs: 100); |
41 | |
42 | if (val & HDMI_PHY_CTRL_SW_RESET_LOW) { |
43 | /* pull high */ |
44 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
45 | data: val | HDMI_PHY_CTRL_SW_RESET); |
46 | } else { |
47 | /* pull low */ |
48 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
49 | data: val & ~HDMI_PHY_CTRL_SW_RESET); |
50 | } |
51 | |
52 | if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) { |
53 | /* pull high */ |
54 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
55 | data: val | HDMI_PHY_CTRL_SW_RESET_PLL); |
56 | } else { |
57 | /* pull low */ |
58 | hdmi_write(hdmi, REG_HDMI_PHY_CTRL, |
59 | data: val & ~HDMI_PHY_CTRL_SW_RESET_PLL); |
60 | } |
61 | } |
62 | |
63 | static void enable_hpd_clocks(struct hdmi *hdmi, bool enable) |
64 | { |
65 | const struct hdmi_platform_config *config = hdmi->config; |
66 | struct device *dev = &hdmi->pdev->dev; |
67 | int i, ret; |
68 | |
69 | if (enable) { |
70 | for (i = 0; i < config->hpd_clk_cnt; i++) { |
71 | if (config->hpd_freq && config->hpd_freq[i]) { |
72 | ret = clk_set_rate(clk: hdmi->hpd_clks[i], |
73 | rate: config->hpd_freq[i]); |
74 | if (ret) |
75 | dev_warn(dev, |
76 | "failed to set clk %s (%d)\n" , |
77 | config->hpd_clk_names[i], ret); |
78 | } |
79 | |
80 | ret = clk_prepare_enable(clk: hdmi->hpd_clks[i]); |
81 | if (ret) { |
82 | DRM_DEV_ERROR(dev, |
83 | "failed to enable hpd clk: %s (%d)\n" , |
84 | config->hpd_clk_names[i], ret); |
85 | } |
86 | } |
87 | } else { |
88 | for (i = config->hpd_clk_cnt - 1; i >= 0; i--) |
89 | clk_disable_unprepare(clk: hdmi->hpd_clks[i]); |
90 | } |
91 | } |
92 | |
93 | int msm_hdmi_hpd_enable(struct drm_bridge *bridge) |
94 | { |
95 | struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); |
96 | struct hdmi *hdmi = hdmi_bridge->hdmi; |
97 | const struct hdmi_platform_config *config = hdmi->config; |
98 | struct device *dev = &hdmi->pdev->dev; |
99 | uint32_t hpd_ctrl; |
100 | int ret; |
101 | unsigned long flags; |
102 | |
103 | ret = regulator_bulk_enable(num_consumers: config->hpd_reg_cnt, consumers: hdmi->hpd_regs); |
104 | if (ret) { |
105 | DRM_DEV_ERROR(dev, "failed to enable hpd regulators: %d\n" , ret); |
106 | goto fail; |
107 | } |
108 | |
109 | ret = pinctrl_pm_select_default_state(dev); |
110 | if (ret) { |
111 | DRM_DEV_ERROR(dev, "pinctrl state chg failed: %d\n" , ret); |
112 | goto fail; |
113 | } |
114 | |
115 | if (hdmi->hpd_gpiod) |
116 | gpiod_set_value_cansleep(desc: hdmi->hpd_gpiod, value: 1); |
117 | |
118 | pm_runtime_get_sync(dev); |
119 | enable_hpd_clocks(hdmi, enable: true); |
120 | |
121 | msm_hdmi_set_mode(hdmi, power_on: false); |
122 | msm_hdmi_phy_reset(hdmi); |
123 | msm_hdmi_set_mode(hdmi, power_on: true); |
124 | |
125 | hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, data: 0x0001001b); |
126 | |
127 | /* enable HPD events: */ |
128 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, |
129 | HDMI_HPD_INT_CTRL_INT_CONNECT | |
130 | HDMI_HPD_INT_CTRL_INT_EN); |
131 | |
132 | /* set timeout to 4.1ms (max) for hardware debounce */ |
133 | spin_lock_irqsave(&hdmi->reg_lock, flags); |
134 | hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL); |
135 | hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(val: 0x1fff); |
136 | |
137 | /* Toggle HPD circuit to trigger HPD sense */ |
138 | hdmi_write(hdmi, REG_HDMI_HPD_CTRL, |
139 | data: ~HDMI_HPD_CTRL_ENABLE & hpd_ctrl); |
140 | hdmi_write(hdmi, REG_HDMI_HPD_CTRL, |
141 | HDMI_HPD_CTRL_ENABLE | hpd_ctrl); |
142 | spin_unlock_irqrestore(lock: &hdmi->reg_lock, flags); |
143 | |
144 | return 0; |
145 | |
146 | fail: |
147 | return ret; |
148 | } |
149 | |
150 | void msm_hdmi_hpd_disable(struct hdmi *hdmi) |
151 | { |
152 | const struct hdmi_platform_config *config = hdmi->config; |
153 | struct device *dev = &hdmi->pdev->dev; |
154 | int ret; |
155 | |
156 | /* Disable HPD interrupt */ |
157 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, data: 0); |
158 | |
159 | msm_hdmi_set_mode(hdmi, power_on: false); |
160 | |
161 | enable_hpd_clocks(hdmi, enable: false); |
162 | pm_runtime_put(dev); |
163 | |
164 | ret = pinctrl_pm_select_sleep_state(dev); |
165 | if (ret) |
166 | dev_warn(dev, "pinctrl state chg failed: %d\n" , ret); |
167 | |
168 | ret = regulator_bulk_disable(num_consumers: config->hpd_reg_cnt, consumers: hdmi->hpd_regs); |
169 | if (ret) |
170 | dev_warn(dev, "failed to disable hpd regulator: %d\n" , ret); |
171 | } |
172 | |
173 | void msm_hdmi_hpd_irq(struct drm_bridge *bridge) |
174 | { |
175 | struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); |
176 | struct hdmi *hdmi = hdmi_bridge->hdmi; |
177 | uint32_t hpd_int_status, hpd_int_ctrl; |
178 | |
179 | /* Process HPD: */ |
180 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); |
181 | hpd_int_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL); |
182 | |
183 | if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) && |
184 | (hpd_int_status & HDMI_HPD_INT_STATUS_INT)) { |
185 | bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED); |
186 | |
187 | /* ack & disable (temporarily) HPD events: */ |
188 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, |
189 | HDMI_HPD_INT_CTRL_INT_ACK); |
190 | |
191 | DBG("status=%04x, ctrl=%04x" , hpd_int_status, hpd_int_ctrl); |
192 | |
193 | /* detect disconnect if we are connected or visa versa: */ |
194 | hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN; |
195 | if (!detected) |
196 | hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT; |
197 | hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, data: hpd_int_ctrl); |
198 | |
199 | queue_work(wq: hdmi->workq, work: &hdmi_bridge->hpd_work); |
200 | } |
201 | } |
202 | |
203 | static enum drm_connector_status detect_reg(struct hdmi *hdmi) |
204 | { |
205 | uint32_t hpd_int_status; |
206 | |
207 | pm_runtime_get_sync(&hdmi->pdev->dev); |
208 | enable_hpd_clocks(hdmi, enable: true); |
209 | |
210 | hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS); |
211 | |
212 | enable_hpd_clocks(hdmi, enable: false); |
213 | pm_runtime_put(&hdmi->pdev->dev); |
214 | |
215 | return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ? |
216 | connector_status_connected : connector_status_disconnected; |
217 | } |
218 | |
219 | #define HPD_GPIO_INDEX 2 |
220 | static enum drm_connector_status detect_gpio(struct hdmi *hdmi) |
221 | { |
222 | return gpiod_get_value(desc: hdmi->hpd_gpiod) ? |
223 | connector_status_connected : |
224 | connector_status_disconnected; |
225 | } |
226 | |
227 | enum drm_connector_status msm_hdmi_bridge_detect( |
228 | struct drm_bridge *bridge) |
229 | { |
230 | struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge); |
231 | struct hdmi *hdmi = hdmi_bridge->hdmi; |
232 | enum drm_connector_status stat_gpio, stat_reg; |
233 | int retry = 20; |
234 | |
235 | /* |
236 | * some platforms may not have hpd gpio. Rely only on the status |
237 | * provided by REG_HDMI_HPD_INT_STATUS in this case. |
238 | */ |
239 | if (!hdmi->hpd_gpiod) |
240 | return detect_reg(hdmi); |
241 | |
242 | do { |
243 | stat_gpio = detect_gpio(hdmi); |
244 | stat_reg = detect_reg(hdmi); |
245 | |
246 | if (stat_gpio == stat_reg) |
247 | break; |
248 | |
249 | mdelay(10); |
250 | } while (--retry); |
251 | |
252 | /* the status we get from reading gpio seems to be more reliable, |
253 | * so trust that one the most if we didn't manage to get hdmi and |
254 | * gpio status to agree: |
255 | */ |
256 | if (stat_gpio != stat_reg) { |
257 | DBG("HDMI_HPD_INT_STATUS tells us: %d" , stat_reg); |
258 | DBG("hpd gpio tells us: %d" , stat_gpio); |
259 | } |
260 | |
261 | return stat_gpio; |
262 | } |
263 | |