diff options
author | Dave Airlie <airlied@redhat.com> | 2014-04-05 16:13:08 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2014-04-05 16:13:08 +1000 |
commit | 82c68b6ccd54117a87cb2d9b91c2ee6e1280cf9d (patch) | |
tree | f7835f0e4a1884253cbd46bfdb7cbc4fa920b6cc /drivers/gpu/drm/tegra/dpaux.c | |
parent | 243b930f8b228428c2a14ebbb09045041ed0bc70 (diff) | |
parent | d105a6c97e43e35fd4f852928bb8a19df235c6e7 (diff) |
Merge tag 'drm/tegra/for-3.15-rc1' of git://anongit.freedesktop.org/tegra/linux into drm-next
drm/tegra: Changes for v3.15-rc1
Implement eDP support for Tegra124 and support the PRIME vmap()/vunmap()
operations.
A symbol that is required for upcoming V4L2 support is now exported by
the host1x driver.
Relicense drivers under the GPL v2 for consistency. One exception is the
public header file, which is relicensed under MIT to abide by the common
rule.
* tag 'drm/tegra/for-3.15-rc1' of git://anongit.freedesktop.org/tegra/linux:
drm/tegra: Use standard GPL v2 license text
drm/tegra: Relicense under GPL v2
drm/tegra: Relicense public header under MIT
drm/tegra: Add eDP support
gpu: host1x: export host1x_syncpt_incr_max() function
drm/tegra: prime: Add vmap support
Diffstat (limited to 'drivers/gpu/drm/tegra/dpaux.c')
-rw-r--r-- | drivers/gpu/drm/tegra/dpaux.c | 544 |
1 files changed, 544 insertions, 0 deletions
diff --git a/drivers/gpu/drm/tegra/dpaux.c b/drivers/gpu/drm/tegra/dpaux.c new file mode 100644 index 00000000000..d536ed381fb --- /dev/null +++ b/drivers/gpu/drm/tegra/dpaux.c @@ -0,0 +1,544 @@ +/* + * Copyright (C) 2013 NVIDIA Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/of_gpio.h> +#include <linux/platform_device.h> +#include <linux/reset.h> +#include <linux/regulator/consumer.h> + +#include <drm/drm_dp_helper.h> +#include <drm/drm_panel.h> + +#include "dpaux.h" +#include "drm.h" + +static DEFINE_MUTEX(dpaux_lock); +static LIST_HEAD(dpaux_list); + +struct tegra_dpaux { + struct drm_dp_aux aux; + struct device *dev; + + void __iomem *regs; + int irq; + + struct tegra_output *output; + + struct reset_control *rst; + struct clk *clk_parent; + struct clk *clk; + + struct regulator *vdd; + + struct completion complete; + struct list_head list; +}; + +static inline struct tegra_dpaux *to_dpaux(struct drm_dp_aux *aux) +{ + return container_of(aux, struct tegra_dpaux, aux); +} + +static inline unsigned long tegra_dpaux_readl(struct tegra_dpaux *dpaux, + unsigned long offset) +{ + return readl(dpaux->regs + (offset << 2)); +} + +static inline void tegra_dpaux_writel(struct tegra_dpaux *dpaux, + unsigned long value, + unsigned long offset) +{ + writel(value, dpaux->regs + (offset << 2)); +} + +static void tegra_dpaux_write_fifo(struct tegra_dpaux *dpaux, const u8 *buffer, + size_t size) +{ + unsigned long offset = DPAUX_DP_AUXDATA_WRITE(0); + size_t i, j; + + for (i = 0; i < size; i += 4) { + size_t num = min_t(size_t, size - i, 4); + unsigned long value = 0; + + for (j = 0; j < num; j++) + value |= buffer[i + j] << (j * 8); + + tegra_dpaux_writel(dpaux, value, offset++); + } +} + +static void tegra_dpaux_read_fifo(struct tegra_dpaux *dpaux, u8 *buffer, + size_t size) +{ + unsigned long offset = DPAUX_DP_AUXDATA_READ(0); + size_t i, j; + + for (i = 0; i < size; i += 4) { + size_t num = min_t(size_t, size - i, 4); + unsigned long value; + + value = tegra_dpaux_readl(dpaux, offset++); + + for (j = 0; j < num; j++) + buffer[i + j] = value >> (j * 8); + } +} + +static ssize_t tegra_dpaux_transfer(struct drm_dp_aux *aux, + struct drm_dp_aux_msg *msg) +{ + unsigned long value = DPAUX_DP_AUXCTL_TRANSACTREQ; + unsigned long timeout = msecs_to_jiffies(250); + struct tegra_dpaux *dpaux = to_dpaux(aux); + unsigned long status; + ssize_t ret = 0; + + if (msg->size < 1 || msg->size > 16) + return -EINVAL; + + tegra_dpaux_writel(dpaux, msg->address, DPAUX_DP_AUXADDR); + + switch (msg->request & ~DP_AUX_I2C_MOT) { + case DP_AUX_I2C_WRITE: + if (msg->request & DP_AUX_I2C_MOT) + value = DPAUX_DP_AUXCTL_CMD_MOT_WR; + else + value = DPAUX_DP_AUXCTL_CMD_I2C_WR; + + break; + + case DP_AUX_I2C_READ: + if (msg->request & DP_AUX_I2C_MOT) + value = DPAUX_DP_AUXCTL_CMD_MOT_RD; + else + value = DPAUX_DP_AUXCTL_CMD_I2C_RD; + + break; + + case DP_AUX_I2C_STATUS: + if (msg->request & DP_AUX_I2C_MOT) + value = DPAUX_DP_AUXCTL_CMD_MOT_RQ; + else + value = DPAUX_DP_AUXCTL_CMD_I2C_RQ; + + break; + + case DP_AUX_NATIVE_WRITE: + value = DPAUX_DP_AUXCTL_CMD_AUX_WR; + break; + + case DP_AUX_NATIVE_READ: + value = DPAUX_DP_AUXCTL_CMD_AUX_RD; + break; + + default: + return -EINVAL; + } + + value |= DPAUX_DP_AUXCTL_CMDLEN(msg->size - 1); + tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXCTL); + + if ((msg->request & DP_AUX_I2C_READ) == 0) { + tegra_dpaux_write_fifo(dpaux, msg->buffer, msg->size); + ret = msg->size; + } + + /* start transaction */ + value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXCTL); + value |= DPAUX_DP_AUXCTL_TRANSACTREQ; + tegra_dpaux_writel(dpaux, value, DPAUX_DP_AUXCTL); + + status = wait_for_completion_timeout(&dpaux->complete, timeout); + if (!status) + return -ETIMEDOUT; + + /* read status and clear errors */ + value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT); + tegra_dpaux_writel(dpaux, 0xf00, DPAUX_DP_AUXSTAT); + + if (value & DPAUX_DP_AUXSTAT_TIMEOUT_ERROR) + return -ETIMEDOUT; + + if ((value & DPAUX_DP_AUXSTAT_RX_ERROR) || + (value & DPAUX_DP_AUXSTAT_SINKSTAT_ERROR) || + (value & DPAUX_DP_AUXSTAT_NO_STOP_ERROR)) + return -EIO; + + switch ((value & DPAUX_DP_AUXSTAT_REPLY_TYPE_MASK) >> 16) { + case 0x00: + msg->reply = DP_AUX_NATIVE_REPLY_ACK; + break; + + case 0x01: + msg->reply = DP_AUX_NATIVE_REPLY_NACK; + break; + + case 0x02: + msg->reply = DP_AUX_NATIVE_REPLY_DEFER; + break; + + case 0x04: + msg->reply = DP_AUX_I2C_REPLY_NACK; + break; + + case 0x08: + msg->reply = DP_AUX_I2C_REPLY_DEFER; + break; + } + + if (msg->reply == DP_AUX_NATIVE_REPLY_ACK) { + if (msg->request & DP_AUX_I2C_READ) { + size_t count = value & DPAUX_DP_AUXSTAT_REPLY_MASK; + + if (WARN_ON(count != msg->size)) + count = min_t(size_t, count, msg->size); + + tegra_dpaux_read_fifo(dpaux, msg->buffer, count); + ret = count; + } + } + + return ret; +} + +static irqreturn_t tegra_dpaux_irq(int irq, void *data) +{ + struct tegra_dpaux *dpaux = data; + irqreturn_t ret = IRQ_HANDLED; + unsigned long value; + + /* clear interrupts */ + value = tegra_dpaux_readl(dpaux, DPAUX_INTR_AUX); + tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX); + + if (value & DPAUX_INTR_PLUG_EVENT) { + if (dpaux->output) { + drm_helper_hpd_irq_event(dpaux->output->connector.dev); + } + } + + if (value & DPAUX_INTR_UNPLUG_EVENT) { + if (dpaux->output) + drm_helper_hpd_irq_event(dpaux->output->connector.dev); + } + + if (value & DPAUX_INTR_IRQ_EVENT) { + /* TODO: handle this */ + } + + if (value & DPAUX_INTR_AUX_DONE) + complete(&dpaux->complete); + + return ret; +} + +static int tegra_dpaux_probe(struct platform_device *pdev) +{ + struct tegra_dpaux *dpaux; + struct resource *regs; + unsigned long value; + int err; + + dpaux = devm_kzalloc(&pdev->dev, sizeof(*dpaux), GFP_KERNEL); + if (!dpaux) + return -ENOMEM; + + init_completion(&dpaux->complete); + INIT_LIST_HEAD(&dpaux->list); + dpaux->dev = &pdev->dev; + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dpaux->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(dpaux->regs)) + return PTR_ERR(dpaux->regs); + + dpaux->irq = platform_get_irq(pdev, 0); + if (dpaux->irq < 0) { + dev_err(&pdev->dev, "failed to get IRQ\n"); + return -ENXIO; + } + + dpaux->rst = devm_reset_control_get(&pdev->dev, "dpaux"); + if (IS_ERR(dpaux->rst)) + return PTR_ERR(dpaux->rst); + + dpaux->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(dpaux->clk)) + return PTR_ERR(dpaux->clk); + + err = clk_prepare_enable(dpaux->clk); + if (err < 0) + return err; + + reset_control_deassert(dpaux->rst); + + dpaux->clk_parent = devm_clk_get(&pdev->dev, "parent"); + if (IS_ERR(dpaux->clk_parent)) + return PTR_ERR(dpaux->clk_parent); + + err = clk_prepare_enable(dpaux->clk_parent); + if (err < 0) + return err; + + err = clk_set_rate(dpaux->clk_parent, 270000000); + if (err < 0) { + dev_err(&pdev->dev, "failed to set clock to 270 MHz: %d\n", + err); + return err; + } + + dpaux->vdd = devm_regulator_get(&pdev->dev, "vdd"); + if (IS_ERR(dpaux->vdd)) + return PTR_ERR(dpaux->vdd); + + err = devm_request_irq(dpaux->dev, dpaux->irq, tegra_dpaux_irq, 0, + dev_name(dpaux->dev), dpaux); + if (err < 0) { + dev_err(dpaux->dev, "failed to request IRQ#%u: %d\n", + dpaux->irq, err); + return err; + } + + dpaux->aux.transfer = tegra_dpaux_transfer; + dpaux->aux.dev = &pdev->dev; + + err = drm_dp_aux_register_i2c_bus(&dpaux->aux); + if (err < 0) + return err; + + /* enable and clear all interrupts */ + value = DPAUX_INTR_AUX_DONE | DPAUX_INTR_IRQ_EVENT | + DPAUX_INTR_UNPLUG_EVENT | DPAUX_INTR_PLUG_EVENT; + tegra_dpaux_writel(dpaux, value, DPAUX_INTR_EN_AUX); + tegra_dpaux_writel(dpaux, value, DPAUX_INTR_AUX); + + mutex_lock(&dpaux_lock); + list_add_tail(&dpaux->list, &dpaux_list); + mutex_unlock(&dpaux_lock); + + platform_set_drvdata(pdev, dpaux); + + return 0; +} + +static int tegra_dpaux_remove(struct platform_device *pdev) +{ + struct tegra_dpaux *dpaux = platform_get_drvdata(pdev); + + drm_dp_aux_unregister_i2c_bus(&dpaux->aux); + + mutex_lock(&dpaux_lock); + list_del(&dpaux->list); + mutex_unlock(&dpaux_lock); + + clk_disable_unprepare(dpaux->clk_parent); + reset_control_assert(dpaux->rst); + clk_disable_unprepare(dpaux->clk); + + return 0; +} + +static const struct of_device_id tegra_dpaux_of_match[] = { + { .compatible = "nvidia,tegra124-dpaux", }, + { }, +}; + +struct platform_driver tegra_dpaux_driver = { + .driver = { + .name = "tegra-dpaux", + .of_match_table = tegra_dpaux_of_match, + }, + .probe = tegra_dpaux_probe, + .remove = tegra_dpaux_remove, +}; + +struct tegra_dpaux *tegra_dpaux_find_by_of_node(struct device_node *np) +{ + struct tegra_dpaux *dpaux; + + mutex_lock(&dpaux_lock); + + list_for_each_entry(dpaux, &dpaux_list, list) + if (np == dpaux->dev->of_node) { + mutex_unlock(&dpaux_lock); + return dpaux; + } + + mutex_unlock(&dpaux_lock); + + return NULL; +} + +int tegra_dpaux_attach(struct tegra_dpaux *dpaux, struct tegra_output *output) +{ + unsigned long timeout; + int err; + + dpaux->output = output; + + err = regulator_enable(dpaux->vdd); + if (err < 0) + return err; + + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + enum drm_connector_status status; + + status = tegra_dpaux_detect(dpaux); + if (status == connector_status_connected) + return 0; + + usleep_range(1000, 2000); + } + + return -ETIMEDOUT; +} + +int tegra_dpaux_detach(struct tegra_dpaux *dpaux) +{ + unsigned long timeout; + int err; + + err = regulator_disable(dpaux->vdd); + if (err < 0) + return err; + + timeout = jiffies + msecs_to_jiffies(250); + + while (time_before(jiffies, timeout)) { + enum drm_connector_status status; + + status = tegra_dpaux_detect(dpaux); + if (status == connector_status_disconnected) { + dpaux->output = NULL; + return 0; + } + + usleep_range(1000, 2000); + } + + return -ETIMEDOUT; +} + +enum drm_connector_status tegra_dpaux_detect(struct tegra_dpaux *dpaux) +{ + unsigned long value; + + value = tegra_dpaux_readl(dpaux, DPAUX_DP_AUXSTAT); + + if (value & DPAUX_DP_AUXSTAT_HPD_STATUS) + return connector_status_connected; + + return connector_status_disconnected; +} + +int tegra_dpaux_enable(struct tegra_dpaux *dpaux) +{ + unsigned long value; + + value = DPAUX_HYBRID_PADCTL_AUX_CMH(2) | + DPAUX_HYBRID_PADCTL_AUX_DRVZ(4) | + DPAUX_HYBRID_PADCTL_AUX_DRVI(0x18) | + DPAUX_HYBRID_PADCTL_AUX_INPUT_RCV | + DPAUX_HYBRID_PADCTL_MODE_AUX; + tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_PADCTL); + + value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); + value &= ~DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; + tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); + + return 0; +} + +int tegra_dpaux_disable(struct tegra_dpaux *dpaux) +{ + unsigned long value; + + value = tegra_dpaux_readl(dpaux, DPAUX_HYBRID_SPARE); + value |= DPAUX_HYBRID_SPARE_PAD_POWER_DOWN; + tegra_dpaux_writel(dpaux, value, DPAUX_HYBRID_SPARE); + + return 0; +} + +int tegra_dpaux_prepare(struct tegra_dpaux *dpaux, u8 encoding) +{ + int err; + + err = drm_dp_dpcd_writeb(&dpaux->aux, DP_MAIN_LINK_CHANNEL_CODING_SET, + encoding); + if (err < 0) + return err; + + return 0; +} + +int tegra_dpaux_train(struct tegra_dpaux *dpaux, struct drm_dp_link *link, + u8 pattern) +{ + u8 tp = pattern & DP_TRAINING_PATTERN_MASK; + u8 status[DP_LINK_STATUS_SIZE], values[4]; + unsigned int i; + int err; + + err = drm_dp_dpcd_writeb(&dpaux->aux, DP_TRAINING_PATTERN_SET, pattern); + if (err < 0) + return err; + + if (tp == DP_TRAINING_PATTERN_DISABLE) + return 0; + + for (i = 0; i < link->num_lanes; i++) + values[i] = DP_TRAIN_MAX_PRE_EMPHASIS_REACHED | + DP_TRAIN_PRE_EMPHASIS_0 | + DP_TRAIN_MAX_SWING_REACHED | + DP_TRAIN_VOLTAGE_SWING_400; + + err = drm_dp_dpcd_write(&dpaux->aux, DP_TRAINING_LANE0_SET, values, + link->num_lanes); + if (err < 0) + return err; + + usleep_range(500, 1000); + + err = drm_dp_dpcd_read_link_status(&dpaux->aux, status); + if (err < 0) + return err; + + switch (tp) { + case DP_TRAINING_PATTERN_1: + if (!drm_dp_clock_recovery_ok(status, link->num_lanes)) + return -EAGAIN; + + break; + + case DP_TRAINING_PATTERN_2: + if (!drm_dp_channel_eq_ok(status, link->num_lanes)) + return -EAGAIN; + + break; + + default: + dev_err(dpaux->dev, "unsupported training pattern %u\n", tp); + return -EINVAL; + } + + err = drm_dp_dpcd_writeb(&dpaux->aux, DP_EDP_CONFIGURATION_SET, 0); + if (err < 0) + return err; + + return 0; +} |