1/*
2// Copyright (c) 2014 Intel Corporation 
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15*/
16
17#include <common/utils/HwcTrace.h>
18#include <common/base/Drm.h>
19#include <DrmConfig.h>
20#include <Hwcomposer.h>
21#include <ExternalDevice.h>
22
23namespace android {
24namespace intel {
25
26ExternalDevice::ExternalDevice(Hwcomposer& hwc, DisplayPlaneManager& dpm)
27    : PhysicalDevice(DEVICE_EXTERNAL, hwc, dpm),
28      mHdcpControl(NULL),
29      mAbortModeSettingCond(),
30      mPendingDrmMode(),
31      mHotplugEventPending(false),
32      mExpectedRefreshRate(0)
33{
34    CTRACE();
35}
36
37ExternalDevice::~ExternalDevice()
38{
39    CTRACE();
40}
41
42bool ExternalDevice::initialize()
43{
44    if (!PhysicalDevice::initialize()) {
45        DEINIT_AND_RETURN_FALSE("failed to initialize physical device");
46    }
47
48    mHdcpControl = createHdcpControl();
49    if (!mHdcpControl) {
50        DEINIT_AND_RETURN_FALSE("failed to create HDCP control");
51    }
52
53    mHotplugEventPending = false;
54    if (mConnected) {
55        mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
56    }
57
58    UeventObserver *observer = Hwcomposer::getInstance().getUeventObserver();
59    if (observer) {
60        observer->registerListener(
61            DrmConfig::getHotplugString(),
62            hotplugEventListener,
63            this);
64    } else {
65        ELOGTRACE("Uevent observer is NULL");
66    }
67    return true;
68}
69
70void ExternalDevice::deinitialize()
71{
72    // abort mode settings if it is in the middle
73    mAbortModeSettingCond.signal();
74    if (mThread.get()) {
75        mThread->join();
76        mThread = NULL;
77    }
78
79    if (mHdcpControl) {
80        mHdcpControl->stopHdcp();
81        delete mHdcpControl;
82        mHdcpControl = 0;
83    }
84
85    mHotplugEventPending = false;
86    PhysicalDevice::deinitialize();
87}
88
89bool ExternalDevice::blank(bool blank)
90{
91    if (!PhysicalDevice::blank(blank)) {
92        return false;
93    }
94
95    if (blank) {
96        mHdcpControl->stopHdcp();
97    } else if (mConnected) {
98        mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
99    }
100    return true;
101}
102
103bool ExternalDevice::setDrmMode(drmModeModeInfo& value)
104{
105    if (!mConnected) {
106        WLOGTRACE("external device is not connected");
107        return false;
108    }
109
110    if (mThread.get()) {
111        mThread->join();
112        mThread = NULL;
113    }
114
115    Drm *drm = Hwcomposer::getInstance().getDrm();
116    drmModeModeInfo mode;
117    drm->getModeInfo(mType, mode);
118    if (drm->isSameDrmMode(&value, &mode))
119        return true;
120
121    // any issue here by faking connection status?
122    mConnected = false;
123    mPendingDrmMode = value;
124
125    // setting mode in a working thread
126    mThread = new ModeSettingThread(this);
127    if (!mThread.get()) {
128        ELOGTRACE("failed to create mode settings thread");
129        return false;
130    }
131
132    mThread->run("ModeSettingsThread", PRIORITY_URGENT_DISPLAY);
133    return true;
134}
135
136bool ExternalDevice::threadLoop()
137{
138    // one-time execution
139    setDrmMode();
140    return false;
141}
142
143void ExternalDevice::setDrmMode()
144{
145    ILOGTRACE("start mode setting...");
146
147    Drm *drm = Hwcomposer::getInstance().getDrm();
148
149    mConnected = false;
150    mHwc.hotplug(mType, false);
151
152    {
153        Mutex::Autolock lock(mLock);
154        // TODO: make timeout value flexible, or wait until surface flinger
155        // acknowledges hot unplug event.
156        status_t err = mAbortModeSettingCond.waitRelative(mLock, milliseconds(20));
157        if (err != -ETIMEDOUT) {
158            ILOGTRACE("Mode settings is interrupted");
159            mHwc.hotplug(mType, true);
160            return;
161        }
162    }
163
164    // TODO: potential threading issue with onHotplug callback
165    mHdcpControl->stopHdcp();
166    if (!drm->setDrmMode(mType, mPendingDrmMode)) {
167        ELOGTRACE("failed to set Drm mode");
168        mHwc.hotplug(mType, true);
169        return;
170    }
171
172    if (!PhysicalDevice::updateDisplayConfigs()) {
173        ELOGTRACE("failed to update display configs");
174        mHwc.hotplug(mType, true);
175        return;
176    }
177    mConnected = true;
178    mHotplugEventPending = true;
179    // delay sending hotplug event until HDCP is authenticated
180    if (mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this) == false) {
181        ELOGTRACE("startHdcpAsync() failed; HDCP is not enabled");
182        mHotplugEventPending = false;
183        mHwc.hotplug(mType, true);
184    }
185    mExpectedRefreshRate = 0;
186}
187
188
189void ExternalDevice::HdcpLinkStatusListener(bool success, void *userData)
190{
191    if (userData == NULL) {
192        return;
193    }
194
195    ExternalDevice *p = (ExternalDevice*)userData;
196    p->HdcpLinkStatusListener(success);
197}
198
199void ExternalDevice::HdcpLinkStatusListener(bool success)
200{
201    if (mHotplugEventPending) {
202        DLOGTRACE("HDCP authentication status %d, sending hotplug event...", success);
203        mHwc.hotplug(mType, mConnected);
204        mHotplugEventPending = false;
205    }
206}
207
208void ExternalDevice::hotplugEventListener(void *data)
209{
210    ExternalDevice *pThis = (ExternalDevice*)data;
211    if (pThis) {
212        pThis->hotplugListener();
213    }
214}
215
216void ExternalDevice::hotplugListener()
217{
218    bool ret;
219
220    CTRACE();
221
222    // abort mode settings if it is in the middle
223    mAbortModeSettingCond.signal();
224
225    // remember the current connection status before detection
226    bool connected = mConnected;
227
228    // detect display configs
229    ret = detectDisplayConfigs();
230    if (ret == false) {
231        ELOGTRACE("failed to detect display config");
232        return;
233    }
234
235    ILOGTRACE("hotpug event: %d", mConnected);
236
237    if (connected == mConnected) {
238        WLOGTRACE("same connection status detected, hotplug event ignored");
239        return;
240    }
241
242    if (mConnected == false) {
243        mHotplugEventPending = false;
244        mHdcpControl->stopHdcp();
245        mHwc.hotplug(mType, mConnected);
246    } else {
247        DLOGTRACE("start HDCP asynchronously...");
248        // delay sending hotplug event till HDCP is authenticated.
249        mHotplugEventPending = true;
250        ret = mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
251        if (ret == false) {
252            ELOGTRACE("failed to start HDCP");
253            mHotplugEventPending = false;
254            mHwc.hotplug(mType, mConnected);
255        }
256    }
257    mActiveDisplayConfig = 0;
258}
259
260void ExternalDevice::setRefreshRate(int hz)
261{
262    RETURN_VOID_IF_NOT_INIT();
263
264    ILOGTRACE("setting refresh rate to %d", hz);
265
266    if (mBlank) {
267        WLOGTRACE("external device is blank");
268        return;
269    }
270
271    Drm *drm = Hwcomposer::getInstance().getDrm();
272    drmModeModeInfo mode;
273    if (!drm->getModeInfo(IDisplayDevice::DEVICE_EXTERNAL, mode))
274        return;
275
276    if (hz == 0 && (mode.type & DRM_MODE_TYPE_PREFERRED))
277        return;
278
279    if (hz == (int)mode.vrefresh)
280        return;
281
282    if (mExpectedRefreshRate != 0 &&
283            mExpectedRefreshRate == hz && mHotplugEventPending) {
284        ILOGTRACE("Ignore a new refresh setting event because there is a same event is handling");
285        return;
286    }
287    mExpectedRefreshRate = hz;
288
289    ILOGTRACE("changing refresh rate from %d to %d", mode.vrefresh, hz);
290
291    mHdcpControl->stopHdcp();
292
293    drm->setRefreshRate(IDisplayDevice::DEVICE_EXTERNAL, hz);
294
295    mHotplugEventPending = false;
296    mHdcpControl->startHdcpAsync(HdcpLinkStatusListener, this);
297}
298
299
300bool ExternalDevice::getDisplaySize(int *width, int *height)
301{
302#ifndef INTEL_SUPPORT_HDMI_PRIMARY
303    return PhysicalDevice::getDisplaySize(width, height);
304#else
305    if (mConnected)
306        return PhysicalDevice::getDisplaySize(width, height);
307
308    if (!width || !height)
309        return false;
310
311    *width = 1920;
312    *height = 1080;
313    return true;
314#endif
315}
316
317bool ExternalDevice::getDisplayConfigs(uint32_t *configs, size_t *numConfigs)
318{
319#ifndef INTEL_SUPPORT_HDMI_PRIMARY
320    return PhysicalDevice::getDisplayConfigs(configs, numConfigs);
321#else
322    if (mConnected)
323        return PhysicalDevice::getDisplayConfigs(configs, numConfigs);
324
325    if (!configs || !numConfigs)
326        return false;
327
328    *configs = 0;
329    *numConfigs = 1;
330    return true;
331#endif
332}
333
334bool ExternalDevice::getDisplayAttributes(uint32_t config,
335                                      const uint32_t *attributes,
336                                      int32_t *values)
337{
338#ifndef INTEL_SUPPORT_HDMI_PRIMARY
339    return PhysicalDevice::getDisplayAttributes(config, attributes, values);
340#else
341    if (mConnected)
342        return PhysicalDevice::getDisplayAttributes(config, attributes, values);
343    if (!attributes || !values)
344        return false;
345    int i = 0;
346    while (attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE) {
347        switch (attributes[i]) {
348        case HWC_DISPLAY_VSYNC_PERIOD:
349            values[i] = 1e9 / 60;
350            break;
351        case HWC_DISPLAY_WIDTH:
352            values[i] = 1920;
353            break;
354        case HWC_DISPLAY_HEIGHT:
355            values[i] = 1080;
356            break;
357        case HWC_DISPLAY_DPI_X:
358            values[i] = 1;
359            break;
360        case HWC_DISPLAY_DPI_Y:
361            values[i] = 1;
362            break;
363        default:
364            ELOGTRACE("unknown attribute %d", attributes[i]);
365            break;
366        }
367        i++;
368    }
369    return true;
370#endif
371}
372
373int ExternalDevice::getActiveConfig()
374{
375    if (!mConnected) {
376        return 0;
377    }
378    return mActiveDisplayConfig;
379}
380
381bool ExternalDevice::setActiveConfig(int index)
382{
383    if (!mConnected) {
384        if (index == 0)
385            return true;
386        else
387            return false;
388    }
389
390    // for now we will only permit the frequency change.  In the future
391    // we may need to set mode as well.
392    if (index >= 0 && index < static_cast<int>(mDisplayConfigs.size())) {
393        DisplayConfig *config = mDisplayConfigs.itemAt(index);
394        setRefreshRate(config->getRefreshRate());
395        mActiveDisplayConfig = index;
396        return true;
397    } else {
398        return false;
399    }
400    return true;
401}
402
403
404
405} // namespace intel
406} // namespace android
407