* option) any later version.
*/
+#include "drmP.h"
+#include "drm_crtc_helper.h"
+
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
+#include <linux/gpio.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <video/exynos_dp.h>
#include "exynos_drm_drv.h"
+#include "exynos_drm_display.h"
#include <plat/cpu.h>
+#include <plat/gpio-cfg.h>
#include "exynos_dp_core.h"
{
int timeout_loop = 0;
+ if (gpio_is_valid(dp->hpd_gpio))
+ return !gpio_get_value(dp->hpd_gpio);
+
while (exynos_dp_get_plug_in_status(dp) != 0) {
timeout_loop++;
if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) {
return ret;
lane_count = dp->link_train.lane_count;
+ if (lane_count == 0)
+ return 0;
+
adjust_request = link_status + 4;
if (exynos_dp_clock_recovery_ok(link_status, lane_count) == 0) {
{
int retval = 0;
int timeout_loop = 0;
- int done_count = 0;
exynos_dp_config_video_slave_mode(dp);
for (;;) {
timeout_loop++;
- if (exynos_dp_is_slave_video_stream_clock_on(dp) == 0)
+ if (!exynos_dp_is_slave_video_stream_clock_on(dp))
break;
if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) {
- dev_err(dp->dev, "Timeout of video streamclk ok\n");
+ dev_err(dp->dev, "Wait for stream clock timed out\n");
return -ETIMEDOUT;
}
- mdelay(100);
+ usleep_range(1000, 5000);
}
/* Set to use the register calculated M/N video */
for (;;) {
timeout_loop++;
- if (exynos_dp_is_video_stream_on(dp) == 0) {
- done_count++;
- if (done_count > 10)
- break;
- } else if (done_count) {
- done_count = 0;
- }
+ if (!exynos_dp_is_video_stream_on(dp))
+ break;
+
if (DP_TIMEOUT_LOOP_COUNT < timeout_loop) {
- dev_err(dp->dev, "Timeout of video streamclk ok\n");
+ dev_err(dp->dev, "Wait for video stream timed out\n");
return -ETIMEDOUT;
}
- mdelay(100);
+ usleep_range(1000, 5000);
}
if (retval != 0)
irq_type = exynos_dp_get_irq_type(dp);
switch (irq_type) {
case DP_IRQ_TYPE_HP_CABLE_IN:
- dev_dbg(dp->dev, "Received irq - cable in\n");
- schedule_work(&dp->hotplug_work);
- exynos_dp_clear_hotplug_interrupts(dp);
- break;
case DP_IRQ_TYPE_HP_CABLE_OUT:
- dev_dbg(dp->dev, "Received irq - cable out\n");
+ dev_dbg(dp->dev, "Received irq - type=%d\n", irq_type);
+ schedule_work(&dp->hotplug_work);
exynos_dp_clear_hotplug_interrupts(dp);
break;
case DP_IRQ_TYPE_HP_CHANGE:
static void exynos_dp_hotplug(struct work_struct *work)
{
struct exynos_dp_device *dp;
- int ret;
dp = container_of(work, struct exynos_dp_device, hotplug_work);
- ret = exynos_dp_detect_hpd(dp);
- if (ret) {
- /* Cable has been disconnected, we're done */
- return;
- }
+ drm_helper_hpd_irq_event(dp->drm_dev);
+}
+
+static void exynos_dp_train_link(struct exynos_dp_device *dp)
+{
+ int ret;
ret = exynos_dp_handle_edid(dp);
if (ret) {
if (dp->training_type == SW_LINK_TRAINING)
ret = exynos_dp_set_link_train(dp, dp->video_info->lane_count,
- dp->video_info->link_rate);
+ dp->video_info->link_rate);
else
ret = exynos_dp_set_hw_link_train(dp,
dp->video_info->lane_count, dp->video_info->link_rate);
exynos_dp_set_link_bandwidth(dp, dp->video_info->link_rate);
exynos_dp_init_video(dp);
+}
+
+static int exynos_dp_power_off(struct exynos_dp_device *dp)
+{
+ if (!dp->enabled)
+ return 0;
+
+ dp->enabled = false;
+ exynos_dp_disable_hpd(dp);
+
+ if (work_pending(&dp->hotplug_work))
+ flush_work_sync(&dp->hotplug_work);
+
+ if (dp->phy_ops.phy_exit)
+ dp->phy_ops.phy_exit();
+
+ clk_disable(dp->clock);
+ return 0;
+}
+
+static int exynos_dp_power_on(struct exynos_dp_device *dp)
+{
+ if (dp->enabled)
+ return 0;
+
+ if (dp->phy_ops.phy_init)
+ dp->phy_ops.phy_init();
+
+ clk_enable(dp->clock);
+
+ exynos_dp_init_dp(dp);
+
+ /*
+ * DP controller is reset and needs HPD interrupt to trigger
+ * re-configuration. If we don't have valid IRQ, this is never
+ * going to happen. Let's reconfigure it here in this case.
+ */
+ if (dp->irq < 0 && !exynos_dp_detect_hpd(dp))
+ schedule_work(&dp->hotplug_work);
+
+ exynos_dp_train_link(dp);
exynos_dp_config_video(dp);
+
+ dp->enabled = true;
+ return 0;
}
+static int exynos_dp_dpms(void *ctx, int mode)
+{
+ struct exynos_dp_device *dp = ctx;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ return exynos_dp_power_on(dp);
+
+ case DRM_MODE_DPMS_STANDBY:
+ case DRM_MODE_DPMS_SUSPEND:
+ case DRM_MODE_DPMS_OFF:
+ return exynos_dp_power_off(dp);
+
+ default:
+ DRM_ERROR("Unknown dpms mode %d\n", mode);
+ }
+ return -EINVAL;
+}
+
+static int exynos_dp_check_timing(void *ctx, void *timing)
+{
+ /*
+ * TODO(seanpaul): The datasheet isn't terribly descriptive about the
+ * limitations we have here. It's not vitally important to implement
+ * this right now, but should be implemented once we use EDID to mode
+ * set.
+ */
+ return 0;
+}
+
+static bool exynos_dp_is_connected(void *ctx)
+{
+ struct exynos_dp_device *dp = ctx;
+
+ if (dp->force_connected)
+ return true;
+ else
+ return !exynos_dp_detect_hpd(dp);
+}
+
+static int exynos_dp_subdrv_probe(void *ctx, struct drm_device *drm_dev)
+{
+ struct exynos_dp_device *dp = ctx;
+ int ret;
+
+ dp->drm_dev = drm_dev;
+
+ if (dp->irq >= 0) {
+ ret = request_irq(dp->irq, exynos_dp_irq_handler, dp->irq_flags,
+ "exynos-dp", dp);
+ if (ret) {
+ dev_err(dp->dev, "failed to request irq\n");
+ return ret;
+ }
+ }
+
+ exynos_dp_dpms(dp, DRM_MODE_DPMS_ON);
+
+ return 0;
+}
+
+static struct exynos_panel_ops dp_panel_ops = {
+ .subdrv_probe = exynos_dp_subdrv_probe,
+ .is_connected = exynos_dp_is_connected,
+ .check_timing = exynos_dp_check_timing,
+ .dpms = exynos_dp_dpms,
+};
+
static int __devinit exynos_dp_probe(struct platform_device *pdev)
{
struct resource *res;
goto err_req_region;
}
- dp->irq = platform_get_irq(pdev, 0);
- if (dp->irq == -ENXIO) {
- dev_err(&pdev->dev, "failed to get irq\n");
- ret = -ENODEV;
- goto err_ioremap;
+ if (gpio_is_valid(pdata->hpd_gpio)) {
+ dp->hpd_gpio = pdata->hpd_gpio;
+ ret = gpio_request_one(dp->hpd_gpio, GPIOF_IN, "dp_hpd");
+ if (ret)
+ goto err_ioremap;
+#ifdef CONFIG_S5P_GPIO_INT
+ ret = s5p_register_gpio_interrupt(dp->hpd_gpio);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "cannot register/get GPIO irq\n");
+ goto err_gpio;
+ }
+ s3c_gpio_cfgpin(dp->hpd_gpio, S3C_GPIO_SFN(0xf));
+#endif
+ dp->irq = gpio_to_irq(dp->hpd_gpio);
+ dp->irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
+ } else {
+ dp->hpd_gpio = -ENODEV;
+ dp->irq = platform_get_irq(pdev, 0);
+ dp->irq_flags = 0;
}
+ dp->enabled = false;
dp->training_type = pdata->training_type;
dp->video_info = pdata->video_info;
- if (pdata->phy_init)
- pdata->phy_init();
-
- INIT_WORK(&dp->hotplug_work, exynos_dp_hotplug);
-
- ret = request_irq(dp->irq, exynos_dp_irq_handler, 0,
- "exynos-dp", dp);
- if (ret) {
- dev_err(&pdev->dev, "failed to request irq\n");
- goto err_ioremap;
+ dp->force_connected = pdata->force_connected;
+ if (pdata->phy_init) {
+ dp->phy_ops.phy_init = pdata->phy_init;
+ dp->phy_ops.phy_init();
}
+ if (pdata->phy_exit)
+ dp->phy_ops.phy_exit = pdata->phy_exit;
- disable_irq(dp->irq);
+ INIT_WORK(&dp->hotplug_work, exynos_dp_hotplug);
platform_set_drvdata(pdev, dp);
- exynos_fimd_dp_attach(dp->dev);
+ exynos_display_attach_panel(EXYNOS_DRM_DISPLAY_TYPE_FIMD, &dp_panel_ops,
+ dp);
return 0;
+err_gpio:
+ if (gpio_is_valid(dp->hpd_gpio))
+ gpio_free(dp->hpd_gpio);
err_ioremap:
iounmap(dp->reg_base);
err_req_region:
static int __devexit exynos_dp_remove(struct platform_device *pdev)
{
- struct exynos_dp_platdata *pdata = pdev->dev.platform_data;
struct exynos_dp_device *dp = platform_get_drvdata(pdev);
- if (work_pending(&dp->hotplug_work))
- flush_work_sync(&dp->hotplug_work);
+ /* power_off will take care of flushing the hotplug_work */
+ exynos_dp_dpms(dp, DRM_MODE_DPMS_OFF);
- if (pdata && pdata->phy_exit)
- pdata->phy_exit();
+ if (gpio_is_valid(dp->hpd_gpio))
+ gpio_free(dp->hpd_gpio);
free_irq(dp->irq, dp);
iounmap(dp->reg_base);
return 0;
}
-#ifdef CONFIG_PM_SLEEP
-int exynos_dp_suspend(struct device *dev)
-{
- struct platform_device *pdev = to_platform_device(dev);
- struct exynos_dp_platdata *pdata = pdev->dev.platform_data;
- struct exynos_dp_device *dp = platform_get_drvdata(pdev);
-
- disable_irq(dp->irq);
-
- if (work_pending(&dp->hotplug_work))
- flush_work_sync(&dp->hotplug_work);
-
- if (pdata && pdata->phy_exit)
- pdata->phy_exit();
-
- clk_disable(dp->clock);
-
- return 0;
-}
-
-int exynos_dp_resume(struct device *dev)
-{
- struct platform_device *pdev = to_platform_device(dev);
- struct exynos_dp_platdata *pdata = pdev->dev.platform_data;
- struct exynos_dp_device *dp = platform_get_drvdata(pdev);
-
- if (pdata && pdata->phy_init)
- pdata->phy_init();
-
- clk_enable(dp->clock);
-
- exynos_dp_init_dp(dp);
-
- enable_irq(dp->irq);
-
- return 0;
-}
-#else
-int exynos_dp_suspend(struct device *dev)
-{
- return 0;
-}
-
-int exynos_dp_resume(struct device *dev)
-{
- return 0;
-}
-#endif
-
struct platform_driver dp_driver = {
.probe = exynos_dp_probe,
.remove = __devexit_p(exynos_dp_remove),
.owner = THIS_MODULE,
},
};
-
-