Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+484
View File
@@ -0,0 +1,484 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2014 The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/gpio/consumer.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#include <linux/pinctrl/consumer.h>
#include <linux/platform_device.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_of.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include "msm_kms.h"
#include "hdmi.h"
void msm_hdmi_set_mode(struct hdmi *hdmi, bool power_on)
{
u32 ctrl = 0;
unsigned long flags;
spin_lock_irqsave(&hdmi->reg_lock, flags);
if (power_on) {
ctrl |= HDMI_CTRL_ENABLE;
if (!hdmi->connector->display_info.is_hdmi) {
ctrl |= HDMI_CTRL_HDMI;
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
ctrl &= ~HDMI_CTRL_HDMI;
} else {
ctrl |= HDMI_CTRL_HDMI;
}
} else {
ctrl = HDMI_CTRL_HDMI;
}
hdmi_write(hdmi, REG_HDMI_CTRL, ctrl);
spin_unlock_irqrestore(&hdmi->reg_lock, flags);
DBG("HDMI Core: %s, HDMI_CTRL=0x%08x",
power_on ? "Enable" : "Disable", ctrl);
}
static irqreturn_t msm_hdmi_irq(int irq, void *dev_id)
{
struct hdmi *hdmi = dev_id;
/* Process HPD: */
msm_hdmi_hpd_irq(hdmi->bridge);
/* Process DDC: */
msm_hdmi_i2c_irq(hdmi->i2c);
/* Process HDCP: */
if (hdmi->hdcp_ctrl)
msm_hdmi_hdcp_irq(hdmi->hdcp_ctrl);
/* TODO audio.. */
return IRQ_HANDLED;
}
static void msm_hdmi_destroy(struct hdmi *hdmi)
{
/*
* at this point, hpd has been disabled,
* after flush workq, it's safe to deinit hdcp
*/
if (hdmi->workq)
destroy_workqueue(hdmi->workq);
msm_hdmi_hdcp_destroy(hdmi);
if (hdmi->i2c)
msm_hdmi_i2c_destroy(hdmi->i2c);
}
static void msm_hdmi_put_phy(struct hdmi *hdmi)
{
if (hdmi->phy_dev) {
put_device(hdmi->phy_dev);
hdmi->phy = NULL;
hdmi->phy_dev = NULL;
}
}
static int msm_hdmi_get_phy(struct hdmi *hdmi)
{
struct platform_device *pdev = hdmi->pdev;
struct platform_device *phy_pdev;
struct device_node *phy_node;
phy_node = of_parse_phandle(dev_of_node(&pdev->dev), "phys", 0);
if (!phy_node) {
DRM_DEV_ERROR(&pdev->dev, "cannot find phy device\n");
return -ENXIO;
}
phy_pdev = of_find_device_by_node(phy_node);
of_node_put(phy_node);
if (!phy_pdev)
return dev_err_probe(&pdev->dev, -EPROBE_DEFER, "phy driver is not ready\n");
hdmi->phy = platform_get_drvdata(phy_pdev);
if (!hdmi->phy) {
put_device(&phy_pdev->dev);
return dev_err_probe(&pdev->dev, -EPROBE_DEFER, "phy driver is not ready\n");
}
hdmi->phy_dev = &phy_pdev->dev;
return 0;
}
/* construct hdmi at bind/probe time, grab all the resources. If
* we are to EPROBE_DEFER we want to do it here, rather than later
* at modeset_init() time
*/
static int msm_hdmi_init(struct hdmi *hdmi)
{
struct platform_device *pdev = hdmi->pdev;
int ret;
hdmi->workq = alloc_ordered_workqueue("msm_hdmi", 0);
if (!hdmi->workq) {
ret = -ENOMEM;
goto fail;
}
hdmi->i2c = msm_hdmi_i2c_init(hdmi);
if (IS_ERR(hdmi->i2c)) {
ret = PTR_ERR(hdmi->i2c);
DRM_DEV_ERROR(&pdev->dev, "failed to get i2c: %d\n", ret);
hdmi->i2c = NULL;
goto fail;
}
hdmi->hdcp_ctrl = msm_hdmi_hdcp_init(hdmi);
if (IS_ERR(hdmi->hdcp_ctrl)) {
dev_warn(&pdev->dev, "failed to init hdcp: disabled\n");
hdmi->hdcp_ctrl = NULL;
}
return 0;
fail:
msm_hdmi_destroy(hdmi);
return ret;
}
/* Second part of initialization, the drm/kms level modeset_init,
* constructs/initializes mode objects, etc, is called from master
* driver (not hdmi sub-device's probe/bind!)
*
* Any resource (regulator/clk/etc) which could be missing at boot
* should be handled in msm_hdmi_init() so that failure happens from
* hdmi sub-device's probe.
*/
int msm_hdmi_modeset_init(struct hdmi *hdmi,
struct drm_device *dev, struct drm_encoder *encoder)
{
int ret;
hdmi->dev = dev;
hdmi->encoder = encoder;
ret = msm_hdmi_bridge_init(hdmi);
if (ret) {
DRM_DEV_ERROR(dev->dev, "failed to create HDMI bridge: %d\n", ret);
goto fail;
}
if (hdmi->next_bridge) {
ret = drm_bridge_attach(hdmi->encoder, hdmi->next_bridge, hdmi->bridge,
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret) {
DRM_DEV_ERROR(dev->dev, "failed to attach next HDMI bridge: %d\n", ret);
goto fail;
}
}
hdmi->connector = drm_bridge_connector_init(hdmi->dev, encoder);
if (IS_ERR(hdmi->connector)) {
ret = PTR_ERR(hdmi->connector);
DRM_DEV_ERROR(dev->dev, "failed to create HDMI connector: %d\n", ret);
hdmi->connector = NULL;
goto fail;
}
ret = devm_request_irq(dev->dev, hdmi->irq,
msm_hdmi_irq, IRQF_TRIGGER_HIGH,
"hdmi_isr", hdmi);
if (ret < 0) {
DRM_DEV_ERROR(dev->dev, "failed to request IRQ%u: %d\n",
hdmi->irq, ret);
goto fail;
}
return 0;
fail:
if (hdmi->connector) {
hdmi->connector->funcs->destroy(hdmi->connector);
hdmi->connector = NULL;
}
return ret;
}
/*
* The hdmi device:
*/
static const char * const pwr_reg_names_8960[] = {"core-vdda"};
static const char * const pwr_clk_names_8960[] = {"core", "master_iface", "slave_iface"};
static const struct hdmi_platform_config hdmi_tx_8960_config = {
.pwr_reg_names = pwr_reg_names_8960,
.pwr_reg_cnt = ARRAY_SIZE(pwr_reg_names_8960),
.pwr_clk_names = pwr_clk_names_8960,
.pwr_clk_cnt = ARRAY_SIZE(pwr_clk_names_8960),
};
static const char * const pwr_reg_names_8x74[] = {"core-vdda", "core-vcc"};
static const char * const pwr_clk_names_8x74[] = {"iface", "core", "mdp_core", "alt_iface"};
static const struct hdmi_platform_config hdmi_tx_8974_config = {
.pwr_reg_names = pwr_reg_names_8x74,
.pwr_reg_cnt = ARRAY_SIZE(pwr_reg_names_8x74),
.pwr_clk_names = pwr_clk_names_8x74,
.pwr_clk_cnt = ARRAY_SIZE(pwr_clk_names_8x74),
};
static int msm_hdmi_bind(struct device *dev, struct device *master, void *data)
{
struct msm_drm_private *priv = dev_get_drvdata(master);
struct hdmi *hdmi = dev_get_drvdata(dev);
int err;
err = msm_hdmi_init(hdmi);
if (err)
return err;
priv->kms->hdmi = hdmi;
return 0;
}
static void msm_hdmi_unbind(struct device *dev, struct device *master,
void *data)
{
struct msm_drm_private *priv = dev_get_drvdata(master);
if (priv->kms->hdmi) {
msm_hdmi_destroy(priv->kms->hdmi);
priv->kms->hdmi = NULL;
}
}
static const struct component_ops msm_hdmi_ops = {
.bind = msm_hdmi_bind,
.unbind = msm_hdmi_unbind,
};
static int msm_hdmi_dev_probe(struct platform_device *pdev)
{
const struct hdmi_platform_config *config;
struct device *dev = &pdev->dev;
struct hdmi *hdmi;
struct resource *res;
int i, ret;
config = of_device_get_match_data(dev);
if (!config)
return -EINVAL;
hdmi = devm_kzalloc(dev, sizeof(*hdmi), GFP_KERNEL);
if (!hdmi)
return -ENOMEM;
hdmi->pdev = pdev;
hdmi->config = config;
spin_lock_init(&hdmi->reg_lock);
mutex_init(&hdmi->state_mutex);
hdmi->next_bridge = of_drm_get_bridge_by_endpoint(dev_of_node(dev), 1, 0);
if (IS_ERR(hdmi->next_bridge)) {
if (PTR_ERR(hdmi->next_bridge) != -ENODEV)
return PTR_ERR(hdmi->next_bridge);
hdmi->next_bridge = NULL;
}
hdmi->mmio = msm_ioremap(pdev, "core_physical");
if (IS_ERR(hdmi->mmio)) {
ret = PTR_ERR(hdmi->mmio);
goto err_put_bridge;
}
/* HDCP needs physical address of hdmi register */
res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
"core_physical");
if (!res) {
ret = -EINVAL;
goto err_put_bridge;
}
hdmi->mmio_phy_addr = res->start;
hdmi->qfprom_mmio = msm_ioremap(pdev, "qfprom_physical");
if (IS_ERR(hdmi->qfprom_mmio)) {
DRM_DEV_INFO(dev, "can't find qfprom resource\n");
hdmi->qfprom_mmio = NULL;
}
hdmi->irq = platform_get_irq(pdev, 0);
if (hdmi->irq < 0) {
ret = hdmi->irq;
goto err_put_bridge;
}
hdmi->pwr_regs = devm_kcalloc(dev, config->pwr_reg_cnt,
sizeof(hdmi->pwr_regs[0]),
GFP_KERNEL);
if (!hdmi->pwr_regs) {
ret = -ENOMEM;
goto err_put_bridge;
}
for (i = 0; i < config->pwr_reg_cnt; i++)
hdmi->pwr_regs[i].supply = config->pwr_reg_names[i];
ret = devm_regulator_bulk_get(dev, config->pwr_reg_cnt, hdmi->pwr_regs);
if (ret) {
dev_err_probe(dev, ret, "failed to get pwr regulators\n");
goto err_put_bridge;
}
hdmi->pwr_clks = devm_kcalloc(dev, config->pwr_clk_cnt,
sizeof(hdmi->pwr_clks[0]),
GFP_KERNEL);
if (!hdmi->pwr_clks) {
ret = -ENOMEM;
goto err_put_bridge;
}
for (i = 0; i < config->pwr_clk_cnt; i++)
hdmi->pwr_clks[i].id = config->pwr_clk_names[i];
ret = devm_clk_bulk_get(dev, config->pwr_clk_cnt, hdmi->pwr_clks);
if (ret)
goto err_put_bridge;
hdmi->extp_clk = devm_clk_get_optional(dev, "extp");
if (IS_ERR(hdmi->extp_clk)) {
ret = dev_err_probe(dev, PTR_ERR(hdmi->extp_clk),
"failed to get extp clock\n");
goto err_put_bridge;
}
hdmi->hpd_gpiod = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
/* This will catch e.g. -EPROBE_DEFER */
if (IS_ERR(hdmi->hpd_gpiod)) {
ret = dev_err_probe(dev, PTR_ERR(hdmi->hpd_gpiod),
"failed to get hpd gpio\n");
goto err_put_bridge;
}
if (!hdmi->hpd_gpiod)
DBG("failed to get HPD gpio");
if (hdmi->hpd_gpiod)
gpiod_set_consumer_name(hdmi->hpd_gpiod, "HDMI_HPD");
ret = msm_hdmi_get_phy(hdmi);
if (ret)
goto err_put_bridge;
ret = devm_pm_runtime_enable(dev);
if (ret)
goto err_put_phy;
platform_set_drvdata(pdev, hdmi);
ret = component_add(dev, &msm_hdmi_ops);
if (ret)
goto err_put_phy;
return 0;
err_put_phy:
msm_hdmi_put_phy(hdmi);
err_put_bridge:
drm_bridge_put(hdmi->next_bridge);
return ret;
}
static void msm_hdmi_dev_remove(struct platform_device *pdev)
{
struct hdmi *hdmi = dev_get_drvdata(&pdev->dev);
component_del(&pdev->dev, &msm_hdmi_ops);
msm_hdmi_put_phy(hdmi);
drm_bridge_put(hdmi->next_bridge);
}
static int msm_hdmi_runtime_suspend(struct device *dev)
{
struct hdmi *hdmi = dev_get_drvdata(dev);
const struct hdmi_platform_config *config = hdmi->config;
clk_bulk_disable_unprepare(config->pwr_clk_cnt, hdmi->pwr_clks);
pinctrl_pm_select_sleep_state(dev);
regulator_bulk_disable(config->pwr_reg_cnt, hdmi->pwr_regs);
return 0;
}
static int msm_hdmi_runtime_resume(struct device *dev)
{
struct hdmi *hdmi = dev_get_drvdata(dev);
const struct hdmi_platform_config *config = hdmi->config;
int ret;
ret = regulator_bulk_enable(config->pwr_reg_cnt, hdmi->pwr_regs);
if (ret)
return ret;
ret = pinctrl_pm_select_default_state(dev);
if (ret)
goto fail;
ret = clk_bulk_prepare_enable(config->pwr_clk_cnt, hdmi->pwr_clks);
if (ret)
goto fail;
return 0;
fail:
pinctrl_pm_select_sleep_state(dev);
return ret;
}
static DEFINE_RUNTIME_DEV_PM_OPS(msm_hdmi_pm_ops, msm_hdmi_runtime_suspend, msm_hdmi_runtime_resume, NULL);
static const struct of_device_id msm_hdmi_dt_match[] = {
{ .compatible = "qcom,hdmi-tx-8998", .data = &hdmi_tx_8974_config },
{ .compatible = "qcom,hdmi-tx-8996", .data = &hdmi_tx_8974_config },
{ .compatible = "qcom,hdmi-tx-8994", .data = &hdmi_tx_8974_config },
{ .compatible = "qcom,hdmi-tx-8084", .data = &hdmi_tx_8974_config },
{ .compatible = "qcom,hdmi-tx-8974", .data = &hdmi_tx_8974_config },
{ .compatible = "qcom,hdmi-tx-8960", .data = &hdmi_tx_8960_config },
{ .compatible = "qcom,hdmi-tx-8660", .data = &hdmi_tx_8960_config },
{}
};
MODULE_DEVICE_TABLE(of, msm_hdmi_dt_match);
static struct platform_driver msm_hdmi_driver = {
.probe = msm_hdmi_dev_probe,
.remove = msm_hdmi_dev_remove,
.driver = {
.name = "hdmi_msm",
.of_match_table = msm_hdmi_dt_match,
.pm = &msm_hdmi_pm_ops,
},
};
void __init msm_hdmi_register(void)
{
msm_hdmi_phy_driver_register();
platform_driver_register(&msm_hdmi_driver);
}
void __exit msm_hdmi_unregister(void)
{
platform_driver_unregister(&msm_hdmi_driver);
msm_hdmi_phy_driver_unregister();
}
+250
View File
@@ -0,0 +1,250 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#ifndef __HDMI_CONNECTOR_H__
#define __HDMI_CONNECTOR_H__
#include <linux/i2c.h>
#include <linux/clk.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/gpio/consumer.h>
#include <linux/hdmi.h>
#include <drm/drm_bridge.h>
#include "msm_drv.h"
#include "hdmi.xml.h"
struct hdmi_phy;
struct hdmi_platform_config;
struct hdmi_audio {
bool enabled;
int rate;
int channels;
};
struct hdmi_hdcp_ctrl;
struct hdmi {
struct drm_device *dev;
struct platform_device *pdev;
const struct hdmi_platform_config *config;
/* audio state: */
struct hdmi_audio audio;
/* video state: */
bool power_on;
bool hpd_enabled;
struct mutex state_mutex; /* protects two booleans */
unsigned long pixclock;
void __iomem *mmio;
void __iomem *qfprom_mmio;
phys_addr_t mmio_phy_addr;
struct regulator_bulk_data *pwr_regs;
struct clk_bulk_data *pwr_clks;
struct clk *extp_clk;
struct gpio_desc *hpd_gpiod;
struct hdmi_phy *phy;
struct device *phy_dev;
struct i2c_adapter *i2c;
struct drm_connector *connector;
struct drm_bridge *bridge;
struct drm_bridge *next_bridge;
/* the encoder we are hooked to (outside of hdmi block) */
struct drm_encoder *encoder;
int irq;
struct workqueue_struct *workq;
struct hdmi_hdcp_ctrl *hdcp_ctrl;
/*
* spinlock to protect registers shared by different execution
* REG_HDMI_CTRL
* REG_HDMI_DDC_ARBITRATION
* REG_HDMI_HDCP_INT_CTRL
* REG_HDMI_HPD_CTRL
*/
spinlock_t reg_lock;
};
/* platform config data (ie. from DT, or pdata) */
struct hdmi_platform_config {
/* regulators that need to be on for screen pwr: */
const char * const *pwr_reg_names;
int pwr_reg_cnt;
/* clks that need to be on: */
const char * const *pwr_clk_names;
int pwr_clk_cnt;
};
struct hdmi_bridge {
struct drm_bridge base;
struct hdmi *hdmi;
struct work_struct hpd_work;
};
#define to_hdmi_bridge(x) container_of(x, struct hdmi_bridge, base)
void msm_hdmi_set_mode(struct hdmi *hdmi, bool power_on);
static inline void hdmi_write(struct hdmi *hdmi, u32 reg, u32 data)
{
writel(data, hdmi->mmio + reg);
}
static inline u32 hdmi_read(struct hdmi *hdmi, u32 reg)
{
return readl(hdmi->mmio + reg);
}
static inline u32 hdmi_qfprom_read(struct hdmi *hdmi, u32 reg)
{
return readl(hdmi->qfprom_mmio + reg);
}
/*
* hdmi phy:
*/
enum hdmi_phy_type {
MSM_HDMI_PHY_8x60,
MSM_HDMI_PHY_8960,
MSM_HDMI_PHY_8x74,
MSM_HDMI_PHY_8996,
MSM_HDMI_PHY_8998,
MSM_HDMI_PHY_MAX,
};
struct hdmi_phy_cfg {
enum hdmi_phy_type type;
void (*powerup)(struct hdmi_phy *phy, unsigned long pixclock);
void (*powerdown)(struct hdmi_phy *phy);
const char * const *reg_names;
int num_regs;
const char * const *clk_names;
int num_clks;
};
extern const struct hdmi_phy_cfg msm_hdmi_phy_8x60_cfg;
extern const struct hdmi_phy_cfg msm_hdmi_phy_8960_cfg;
extern const struct hdmi_phy_cfg msm_hdmi_phy_8x74_cfg;
extern const struct hdmi_phy_cfg msm_hdmi_phy_8996_cfg;
extern const struct hdmi_phy_cfg msm_hdmi_phy_8998_cfg;
struct hdmi_phy {
struct platform_device *pdev;
void __iomem *mmio;
struct hdmi_phy_cfg *cfg;
const struct hdmi_phy_funcs *funcs;
struct regulator_bulk_data *regs;
struct clk **clks;
};
static inline void hdmi_phy_write(struct hdmi_phy *phy, u32 reg, u32 data)
{
writel(data, phy->mmio + reg);
}
static inline u32 hdmi_phy_read(struct hdmi_phy *phy, u32 reg)
{
return readl(phy->mmio + reg);
}
int msm_hdmi_phy_resource_enable(struct hdmi_phy *phy);
void msm_hdmi_phy_resource_disable(struct hdmi_phy *phy);
void msm_hdmi_phy_powerup(struct hdmi_phy *phy, unsigned long pixclock);
void msm_hdmi_phy_powerdown(struct hdmi_phy *phy);
void __init msm_hdmi_phy_driver_register(void);
void __exit msm_hdmi_phy_driver_unregister(void);
#ifdef CONFIG_COMMON_CLK
int msm_hdmi_pll_8960_init(struct platform_device *pdev);
int msm_hdmi_pll_8996_init(struct platform_device *pdev);
int msm_hdmi_pll_8998_init(struct platform_device *pdev);
#else
static inline int msm_hdmi_pll_8960_init(struct platform_device *pdev)
{
return -ENODEV;
}
static inline int msm_hdmi_pll_8996_init(struct platform_device *pdev)
{
return -ENODEV;
}
static inline int msm_hdmi_pll_8998_init(struct platform_device *pdev)
{
return -ENODEV;
}
#endif
/*
* audio:
*/
struct hdmi_codec_daifmt;
struct hdmi_codec_params;
int msm_hdmi_audio_update(struct hdmi *hdmi);
int msm_hdmi_bridge_audio_prepare(struct drm_bridge *bridge,
struct drm_connector *connector,
struct hdmi_codec_daifmt *daifmt,
struct hdmi_codec_params *params);
void msm_hdmi_bridge_audio_shutdown(struct drm_bridge *bridge,
struct drm_connector *connector);
/*
* hdmi bridge:
*/
int msm_hdmi_bridge_init(struct hdmi *hdmi);
void msm_hdmi_hpd_irq(struct drm_bridge *bridge);
enum drm_connector_status msm_hdmi_bridge_detect(
struct drm_bridge *bridge, struct drm_connector *connector);
void msm_hdmi_hpd_enable(struct drm_bridge *bridge);
void msm_hdmi_hpd_disable(struct drm_bridge *bridge);
/*
* i2c adapter for ddc:
*/
void msm_hdmi_i2c_irq(struct i2c_adapter *i2c);
void msm_hdmi_i2c_destroy(struct i2c_adapter *i2c);
struct i2c_adapter *msm_hdmi_i2c_init(struct hdmi *hdmi);
/*
* hdcp
*/
#ifdef CONFIG_DRM_MSM_HDMI_HDCP
struct hdmi_hdcp_ctrl *msm_hdmi_hdcp_init(struct hdmi *hdmi);
void msm_hdmi_hdcp_destroy(struct hdmi *hdmi);
void msm_hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl);
void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl);
void msm_hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl);
#else
static inline struct hdmi_hdcp_ctrl *msm_hdmi_hdcp_init(struct hdmi *hdmi)
{
return ERR_PTR(-ENXIO);
}
static inline void msm_hdmi_hdcp_destroy(struct hdmi *hdmi) {}
static inline void msm_hdmi_hdcp_on(struct hdmi_hdcp_ctrl *hdcp_ctrl) {}
static inline void msm_hdmi_hdcp_off(struct hdmi_hdcp_ctrl *hdcp_ctrl) {}
static inline void msm_hdmi_hdcp_irq(struct hdmi_hdcp_ctrl *hdcp_ctrl) {}
#endif
#endif /* __HDMI_CONNECTOR_H__ */
@@ -0,0 +1,178 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include <linux/hdmi.h>
#include <sound/hdmi-codec.h>
#include "hdmi.h"
int msm_hdmi_audio_update(struct hdmi *hdmi)
{
struct hdmi_audio *audio = &hdmi->audio;
bool enabled = audio->enabled;
u32 acr_pkt_ctrl, vbi_pkt_ctrl, aud_pkt_ctrl, audio_config;
if (!hdmi->connector->display_info.is_hdmi)
return -EINVAL;
DBG("audio: enabled=%d, channels=%d, rate=%d",
audio->enabled, audio->channels, audio->rate);
DBG("video: power_on=%d, pixclock=%lu", hdmi->power_on, hdmi->pixclock);
if (enabled && !(hdmi->power_on && hdmi->pixclock)) {
DBG("disabling audio: no video");
enabled = false;
}
/* Read first before writing */
acr_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_ACR_PKT_CTRL);
vbi_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_VBI_PKT_CTRL);
aud_pkt_ctrl = hdmi_read(hdmi, REG_HDMI_AUDIO_PKT_CTRL1);
audio_config = hdmi_read(hdmi, REG_HDMI_AUDIO_CFG);
/* Clear N/CTS selection bits */
acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SELECT__MASK;
if (enabled) {
u32 n, cts, multiplier;
enum hdmi_acr_cts select;
drm_hdmi_acr_get_n_cts(hdmi->pixclock, audio->rate, &n, &cts);
if (audio->rate == 192000 || audio->rate == 176400) {
multiplier = 4;
n >>= 2; /* divide N by 4 and use multiplier */
} else if (audio->rate == 96000 || audio->rate == 88200) {
multiplier = 2;
n >>= 1; /* divide N by 2 and use multiplier */
} else {
multiplier = 1;
}
DBG("n=%u, cts=%u, multiplier=%u", n, cts, multiplier);
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SOURCE;
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_AUDIO_PRIORITY;
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_N_MULTIPLIER(multiplier);
if (audio->rate == 48000 || audio->rate == 96000 ||
audio->rate == 192000)
select = ACR_48;
else if (audio->rate == 44100 || audio->rate == 88200 ||
audio->rate == 176400)
select = ACR_44;
else /* default to 32k */
select = ACR_32;
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SELECT(select);
hdmi_write(hdmi, REG_HDMI_ACR_0(select - 1),
HDMI_ACR_0_CTS(cts));
hdmi_write(hdmi, REG_HDMI_ACR_1(select - 1),
HDMI_ACR_1_N(n));
hdmi_write(hdmi, REG_HDMI_AUDIO_PKT_CTRL2,
COND(audio->channels != 2, HDMI_AUDIO_PKT_CTRL2_LAYOUT) |
HDMI_AUDIO_PKT_CTRL2_OVERRIDE);
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_CONT;
acr_pkt_ctrl |= HDMI_ACR_PKT_CTRL_SEND;
hdmi_write(hdmi, REG_HDMI_GC, 0);
vbi_pkt_ctrl |= HDMI_VBI_PKT_CTRL_GC_ENABLE;
vbi_pkt_ctrl |= HDMI_VBI_PKT_CTRL_GC_EVERY_FRAME;
aud_pkt_ctrl |= HDMI_AUDIO_PKT_CTRL1_AUDIO_SAMPLE_SEND;
audio_config &= ~HDMI_AUDIO_CFG_FIFO_WATERMARK__MASK;
audio_config |= HDMI_AUDIO_CFG_FIFO_WATERMARK(4);
audio_config |= HDMI_AUDIO_CFG_ENGINE_ENABLE;
} else {
acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_CONT;
acr_pkt_ctrl &= ~HDMI_ACR_PKT_CTRL_SEND;
vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_ENABLE;
vbi_pkt_ctrl &= ~HDMI_VBI_PKT_CTRL_GC_EVERY_FRAME;
aud_pkt_ctrl &= ~HDMI_AUDIO_PKT_CTRL1_AUDIO_SAMPLE_SEND;
audio_config &= ~HDMI_AUDIO_CFG_ENGINE_ENABLE;
}
hdmi_write(hdmi, REG_HDMI_ACR_PKT_CTRL, acr_pkt_ctrl);
hdmi_write(hdmi, REG_HDMI_VBI_PKT_CTRL, vbi_pkt_ctrl);
hdmi_write(hdmi, REG_HDMI_AUDIO_PKT_CTRL1, aud_pkt_ctrl);
hdmi_write(hdmi, REG_HDMI_AUD_INT,
COND(enabled, HDMI_AUD_INT_AUD_FIFO_URUN_INT) |
COND(enabled, HDMI_AUD_INT_AUD_SAM_DROP_INT));
hdmi_write(hdmi, REG_HDMI_AUDIO_CFG, audio_config);
DBG("audio %sabled", enabled ? "en" : "dis");
return 0;
}
int msm_hdmi_bridge_audio_prepare(struct drm_bridge *bridge,
struct drm_connector *connector,
struct hdmi_codec_daifmt *daifmt,
struct hdmi_codec_params *params)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
int ret;
drm_dbg_driver(bridge->dev, "%u Hz, %d bit, %d channels\n",
params->sample_rate,
params->sample_width,
params->cea.channels);
switch (params->sample_rate) {
case 32000:
case 44100:
case 48000:
case 88200:
case 96000:
case 176400:
case 192000:
break;
default:
drm_err(bridge->dev, "rate[%d] not supported!\n",
params->sample_rate);
return -EINVAL;
}
ret = drm_atomic_helper_connector_hdmi_update_audio_infoframe(connector,
&params->cea);
if (ret)
return ret;
hdmi->audio.rate = params->sample_rate;
hdmi->audio.channels = params->cea.channels;
hdmi->audio.enabled = true;
return msm_hdmi_audio_update(hdmi);
}
void msm_hdmi_bridge_audio_shutdown(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
drm_atomic_helper_connector_hdmi_clear_audio_infoframe(connector);
hdmi->audio.rate = 0;
hdmi->audio.channels = 2;
hdmi->audio.enabled = false;
msm_hdmi_audio_update(hdmi);
}
@@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/delay.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_edid.h>
#include <drm/display/drm_hdmi_helper.h>
#include <drm/display/drm_hdmi_state_helper.h>
#include "msm_kms.h"
#include "hdmi.h"
static void msm_hdmi_power_on(struct drm_bridge *bridge)
{
struct drm_device *dev = bridge->dev;
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
int ret;
pm_runtime_resume_and_get(&hdmi->pdev->dev);
if (hdmi->extp_clk) {
DBG("pixclock: %lu", hdmi->pixclock);
ret = clk_set_rate(hdmi->extp_clk, hdmi->pixclock);
if (ret)
DRM_DEV_ERROR(dev->dev, "failed to set extp clk rate: %d\n", ret);
ret = clk_prepare_enable(hdmi->extp_clk);
if (ret)
DRM_DEV_ERROR(dev->dev, "failed to enable extp clk: %d\n", ret);
}
}
static void power_off(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
/* TODO do we need to wait for final vblank somewhere before
* cutting the clocks?
*/
mdelay(16 + 4);
if (hdmi->extp_clk)
clk_disable_unprepare(hdmi->extp_clk);
pm_runtime_put(&hdmi->pdev->dev);
}
#define AVI_IFRAME_LINE_NUMBER 1
#define SPD_IFRAME_LINE_NUMBER 1
#define VENSPEC_IFRAME_LINE_NUMBER 3
static int msm_hdmi_bridge_clear_avi_infoframe(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 val;
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0);
val &= ~(HDMI_INFOFRAME_CTRL0_AVI_SEND |
HDMI_INFOFRAME_CTRL0_AVI_CONT);
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val);
return 0;
}
static int msm_hdmi_bridge_clear_audio_infoframe(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 val;
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0);
val &= ~(HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE);
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val &= ~HDMI_INFOFRAME_CTRL1_AUDIO_INFO_LINE__MASK;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val);
return 0;
}
static int msm_hdmi_bridge_clear_spd_infoframe(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 val;
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val &= ~(HDMI_GEN_PKT_CTRL_GENERIC1_SEND |
HDMI_GEN_PKT_CTRL_GENERIC1_CONT |
HDMI_GEN_PKT_CTRL_GENERIC1_LINE__MASK);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
return 0;
}
static int msm_hdmi_bridge_clear_hdmi_infoframe(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 val;
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val &= ~(HDMI_GEN_PKT_CTRL_GENERIC0_SEND |
HDMI_GEN_PKT_CTRL_GENERIC0_CONT |
HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE |
HDMI_GEN_PKT_CTRL_GENERIC0_LINE__MASK);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
return 0;
}
static int msm_hdmi_bridge_write_avi_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 buf[4] = {};
u32 val;
int i;
if (len != HDMI_INFOFRAME_SIZE(AVI) || len - 3 > sizeof(buf)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure avi infoframe\n");
return -EINVAL;
}
msm_hdmi_bridge_clear_avi_infoframe(bridge);
/*
* the AVI_INFOx registers don't map exactly to how the AVI infoframes
* are packed according to the spec. The checksum from the header is
* written to the LSB byte of AVI_INFO0 and the version is written to
* the third byte from the LSB of AVI_INFO3
*/
memcpy(buf, &buffer[3], len - 3);
buf[3] |= buffer[1] << 24;
for (i = 0; i < ARRAY_SIZE(buf); i++)
hdmi_write(hdmi, REG_HDMI_AVI_INFO(i), buf[i]);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0);
val |= HDMI_INFOFRAME_CTRL0_AVI_SEND |
HDMI_INFOFRAME_CTRL0_AVI_CONT;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL1);
val &= ~HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE__MASK;
val |= HDMI_INFOFRAME_CTRL1_AVI_INFO_LINE(AVI_IFRAME_LINE_NUMBER);
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL1, val);
return 0;
}
static int msm_hdmi_bridge_write_audio_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 val;
if (len != HDMI_INFOFRAME_SIZE(AUDIO)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure audio infoframe\n");
return -EINVAL;
}
msm_hdmi_bridge_clear_audio_infoframe(bridge);
hdmi_write(hdmi, REG_HDMI_AUDIO_INFO0,
buffer[3] |
buffer[4] << 8 |
buffer[5] << 16 |
buffer[6] << 24);
hdmi_write(hdmi, REG_HDMI_AUDIO_INFO1,
buffer[7] |
buffer[8] << 8 |
buffer[9] << 16 |
buffer[10] << 24);
val = hdmi_read(hdmi, REG_HDMI_INFOFRAME_CTRL0);
val |= HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SEND |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_CONT |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_SOURCE |
HDMI_INFOFRAME_CTRL0_AUDIO_INFO_UPDATE;
hdmi_write(hdmi, REG_HDMI_INFOFRAME_CTRL0, val);
return 0;
}
static int msm_hdmi_bridge_write_spd_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 buf[7] = {};
u32 val;
int i;
if (len != HDMI_INFOFRAME_SIZE(SPD) || len - 3 > sizeof(buf)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure SPD infoframe\n");
return -EINVAL;
}
msm_hdmi_bridge_clear_spd_infoframe(bridge);
/* checksum gets written together with the body of the frame */
hdmi_write(hdmi, REG_HDMI_GENERIC1_HDR,
buffer[0] |
buffer[1] << 8 |
buffer[2] << 16);
memcpy(buf, &buffer[3], len - 3);
for (i = 0; i < ARRAY_SIZE(buf); i++)
hdmi_write(hdmi, REG_HDMI_GENERIC1(i), buf[i]);
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val |= HDMI_GEN_PKT_CTRL_GENERIC1_SEND |
HDMI_GEN_PKT_CTRL_GENERIC1_CONT |
HDMI_GEN_PKT_CTRL_GENERIC1_LINE(SPD_IFRAME_LINE_NUMBER);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
return 0;
}
static int msm_hdmi_bridge_write_hdmi_infoframe(struct drm_bridge *bridge,
const u8 *buffer, size_t len)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 buf[7] = {};
u32 val;
int i;
if (len < HDMI_INFOFRAME_HEADER_SIZE + HDMI_VENDOR_INFOFRAME_SIZE ||
len - 3 > sizeof(buf)) {
DRM_DEV_ERROR(&hdmi->pdev->dev,
"failed to configure HDMI infoframe\n");
return -EINVAL;
}
msm_hdmi_bridge_clear_hdmi_infoframe(bridge);
/* checksum gets written together with the body of the frame */
hdmi_write(hdmi, REG_HDMI_GENERIC0_HDR,
buffer[0] |
buffer[1] << 8 |
buffer[2] << 16);
memcpy(buf, &buffer[3], len - 3);
for (i = 0; i < ARRAY_SIZE(buf); i++)
hdmi_write(hdmi, REG_HDMI_GENERIC0(i), buf[i]);
val = hdmi_read(hdmi, REG_HDMI_GEN_PKT_CTRL);
val |= HDMI_GEN_PKT_CTRL_GENERIC0_SEND |
HDMI_GEN_PKT_CTRL_GENERIC0_CONT |
HDMI_GEN_PKT_CTRL_GENERIC0_UPDATE |
HDMI_GEN_PKT_CTRL_GENERIC0_LINE(VENSPEC_IFRAME_LINE_NUMBER);
hdmi_write(hdmi, REG_HDMI_GEN_PKT_CTRL, val);
return 0;
}
static void msm_hdmi_set_timings(struct hdmi *hdmi,
const struct drm_display_mode *mode);
static void msm_hdmi_bridge_atomic_pre_enable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct hdmi_phy *phy = hdmi->phy;
struct drm_encoder *encoder = bridge->encoder;
struct drm_connector *connector;
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
DBG("power up");
connector = drm_atomic_get_new_connector_for_encoder(state, encoder);
conn_state = drm_atomic_get_new_connector_state(state, connector);
crtc_state = drm_atomic_get_new_crtc_state(state, conn_state->crtc);
hdmi->pixclock = conn_state->hdmi.tmds_char_rate;
msm_hdmi_set_timings(hdmi, &crtc_state->adjusted_mode);
mutex_lock(&hdmi->state_mutex);
if (!hdmi->power_on) {
msm_hdmi_phy_resource_enable(phy);
msm_hdmi_power_on(bridge);
hdmi->power_on = true;
}
mutex_unlock(&hdmi->state_mutex);
if (connector->display_info.is_hdmi)
msm_hdmi_audio_update(hdmi);
drm_atomic_helper_connector_hdmi_update_infoframes(connector, state);
msm_hdmi_phy_powerup(phy, hdmi->pixclock);
msm_hdmi_set_mode(hdmi, true);
if (hdmi->hdcp_ctrl)
msm_hdmi_hdcp_on(hdmi->hdcp_ctrl);
}
static void msm_hdmi_bridge_atomic_post_disable(struct drm_bridge *bridge,
struct drm_atomic_commit *state)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct hdmi_phy *phy = hdmi->phy;
if (hdmi->hdcp_ctrl)
msm_hdmi_hdcp_off(hdmi->hdcp_ctrl);
DBG("power down");
/* Keep the HDMI enabled if the HPD is enabled */
mutex_lock(&hdmi->state_mutex);
msm_hdmi_set_mode(hdmi, hdmi->hpd_enabled);
msm_hdmi_phy_powerdown(phy);
if (hdmi->power_on) {
power_off(bridge);
hdmi->power_on = false;
if (hdmi->connector->display_info.is_hdmi)
msm_hdmi_audio_update(hdmi);
msm_hdmi_phy_resource_disable(phy);
}
mutex_unlock(&hdmi->state_mutex);
}
static void msm_hdmi_set_timings(struct hdmi *hdmi,
const struct drm_display_mode *mode)
{
int hstart, hend, vstart, vend;
u32 frame_ctrl;
hstart = mode->htotal - mode->hsync_start;
hend = mode->htotal - mode->hsync_start + mode->hdisplay;
vstart = mode->vtotal - mode->vsync_start - 1;
vend = mode->vtotal - mode->vsync_start + mode->vdisplay - 1;
DBG("htotal=%d, vtotal=%d, hstart=%d, hend=%d, vstart=%d, vend=%d",
mode->htotal, mode->vtotal, hstart, hend, vstart, vend);
hdmi_write(hdmi, REG_HDMI_TOTAL,
HDMI_TOTAL_H_TOTAL(mode->htotal - 1) |
HDMI_TOTAL_V_TOTAL(mode->vtotal - 1));
hdmi_write(hdmi, REG_HDMI_ACTIVE_HSYNC,
HDMI_ACTIVE_HSYNC_START(hstart) |
HDMI_ACTIVE_HSYNC_END(hend));
hdmi_write(hdmi, REG_HDMI_ACTIVE_VSYNC,
HDMI_ACTIVE_VSYNC_START(vstart) |
HDMI_ACTIVE_VSYNC_END(vend));
if (mode->flags & DRM_MODE_FLAG_INTERLACE) {
hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
HDMI_VSYNC_TOTAL_F2_V_TOTAL(mode->vtotal));
hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
HDMI_VSYNC_ACTIVE_F2_START(vstart + 1) |
HDMI_VSYNC_ACTIVE_F2_END(vend + 1));
} else {
hdmi_write(hdmi, REG_HDMI_VSYNC_TOTAL_F2,
HDMI_VSYNC_TOTAL_F2_V_TOTAL(0));
hdmi_write(hdmi, REG_HDMI_VSYNC_ACTIVE_F2,
HDMI_VSYNC_ACTIVE_F2_START(0) |
HDMI_VSYNC_ACTIVE_F2_END(0));
}
frame_ctrl = 0;
if (mode->flags & DRM_MODE_FLAG_NHSYNC)
frame_ctrl |= HDMI_FRAME_CTRL_HSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
frame_ctrl |= HDMI_FRAME_CTRL_VSYNC_LOW;
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
frame_ctrl |= HDMI_FRAME_CTRL_INTERLACED_EN;
DBG("frame_ctrl=%08x", frame_ctrl);
hdmi_write(hdmi, REG_HDMI_FRAME_CTRL, frame_ctrl);
}
static const struct drm_edid *msm_hdmi_bridge_edid_read(struct drm_bridge *bridge,
struct drm_connector *connector)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
const struct drm_edid *drm_edid;
u32 hdmi_ctrl;
hdmi_ctrl = hdmi_read(hdmi, REG_HDMI_CTRL);
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl | HDMI_CTRL_ENABLE);
drm_edid = drm_edid_read_ddc(connector, hdmi->i2c);
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl);
return drm_edid;
}
static enum drm_mode_status msm_hdmi_bridge_tmds_char_rate_valid(const struct drm_bridge *bridge,
const struct drm_display_mode *mode,
unsigned long long tmds_rate)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct msm_drm_private *priv = bridge->dev->dev_private;
struct msm_kms *kms = priv->kms;
long actual;
/* for mdp5/apq8074, we manage our own pixel clk (as opposed to
* mdp4/dtv stuff where pixel clk is assigned to mdp/encoder
* instead):
*/
if (kms->funcs->round_pixclk)
actual = kms->funcs->round_pixclk(kms,
tmds_rate,
hdmi_bridge->hdmi->encoder);
else if (hdmi->extp_clk)
actual = clk_round_rate(hdmi->extp_clk, tmds_rate);
else
actual = tmds_rate;
DBG("requested=%lld, actual=%ld", tmds_rate, actual);
if (actual != tmds_rate)
return MODE_CLOCK_RANGE;
return 0;
}
static const struct drm_bridge_funcs msm_hdmi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.atomic_pre_enable = msm_hdmi_bridge_atomic_pre_enable,
.atomic_post_disable = msm_hdmi_bridge_atomic_post_disable,
.edid_read = msm_hdmi_bridge_edid_read,
.detect = msm_hdmi_bridge_detect,
.hpd_enable = msm_hdmi_hpd_enable,
.hpd_disable = msm_hdmi_hpd_disable,
.hdmi_tmds_char_rate_valid = msm_hdmi_bridge_tmds_char_rate_valid,
.hdmi_clear_audio_infoframe = msm_hdmi_bridge_clear_audio_infoframe,
.hdmi_write_audio_infoframe = msm_hdmi_bridge_write_audio_infoframe,
.hdmi_clear_avi_infoframe = msm_hdmi_bridge_clear_avi_infoframe,
.hdmi_write_avi_infoframe = msm_hdmi_bridge_write_avi_infoframe,
.hdmi_clear_spd_infoframe = msm_hdmi_bridge_clear_spd_infoframe,
.hdmi_write_spd_infoframe = msm_hdmi_bridge_write_spd_infoframe,
.hdmi_clear_hdmi_infoframe = msm_hdmi_bridge_clear_hdmi_infoframe,
.hdmi_write_hdmi_infoframe = msm_hdmi_bridge_write_hdmi_infoframe,
.hdmi_audio_prepare = msm_hdmi_bridge_audio_prepare,
.hdmi_audio_shutdown = msm_hdmi_bridge_audio_shutdown,
};
static void
msm_hdmi_hotplug_work(struct work_struct *work)
{
struct hdmi_bridge *hdmi_bridge =
container_of(work, struct hdmi_bridge, hpd_work);
struct drm_bridge *bridge = &hdmi_bridge->base;
drm_bridge_hpd_notify(bridge, drm_bridge_detect(bridge, hdmi_bridge->hdmi->connector));
}
/* initialize bridge */
int msm_hdmi_bridge_init(struct hdmi *hdmi)
{
struct drm_bridge *bridge = NULL;
struct hdmi_bridge *hdmi_bridge;
int ret;
hdmi_bridge = devm_drm_bridge_alloc(hdmi->dev->dev, struct hdmi_bridge, base,
&msm_hdmi_bridge_funcs);
if (IS_ERR(hdmi_bridge))
return PTR_ERR(hdmi_bridge);
hdmi_bridge->hdmi = hdmi;
INIT_WORK(&hdmi_bridge->hpd_work, msm_hdmi_hotplug_work);
bridge = &hdmi_bridge->base;
bridge->ddc = hdmi->i2c;
bridge->type = DRM_MODE_CONNECTOR_HDMIA;
bridge->vendor = "Qualcomm";
bridge->product = "Snapdragon";
bridge->ops = DRM_BRIDGE_OP_HPD |
DRM_BRIDGE_OP_DETECT |
DRM_BRIDGE_OP_HDMI |
DRM_BRIDGE_OP_HDMI_AUDIO |
DRM_BRIDGE_OP_EDID;
bridge->hdmi_audio_max_i2s_playback_channels = 8;
bridge->hdmi_audio_dev = &hdmi->pdev->dev;
bridge->hdmi_audio_dai_port = -1;
ret = devm_drm_bridge_add(hdmi->dev->dev, bridge);
if (ret)
return ret;
ret = drm_bridge_attach(hdmi->encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
if (ret)
return ret;
hdmi->bridge = bridge;
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/pinctrl/consumer.h>
#include "msm_kms.h"
#include "hdmi.h"
static void msm_hdmi_phy_reset(struct hdmi *hdmi)
{
unsigned int val;
val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
} else {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
}
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
} else {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET_PLL);
}
msleep(100);
if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
} else {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
}
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
/* pull high */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET_PLL);
} else {
/* pull low */
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
}
}
void msm_hdmi_hpd_enable(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct device *dev = &hdmi->pdev->dev;
u32 hpd_ctrl;
int ret;
unsigned long flags;
if (hdmi->hpd_gpiod)
gpiod_set_value_cansleep(hdmi->hpd_gpiod, 1);
ret = pm_runtime_resume_and_get(dev);
if (WARN_ON(ret))
return;
mutex_lock(&hdmi->state_mutex);
msm_hdmi_set_mode(hdmi, false);
msm_hdmi_phy_reset(hdmi);
msm_hdmi_set_mode(hdmi, true);
hdmi->hpd_enabled = true;
mutex_unlock(&hdmi->state_mutex);
hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);
/* enable HPD events: */
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
HDMI_HPD_INT_CTRL_INT_CONNECT |
HDMI_HPD_INT_CTRL_INT_EN);
/* set timeout to 4.1ms (max) for hardware debounce */
spin_lock_irqsave(&hdmi->reg_lock, flags);
hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
/* Toggle HPD circuit to trigger HPD sense */
hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
spin_unlock_irqrestore(&hdmi->reg_lock, flags);
}
void msm_hdmi_hpd_disable(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
struct device *dev = &hdmi->pdev->dev;
/* Disable HPD interrupt */
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
mutex_lock(&hdmi->state_mutex);
hdmi->hpd_enabled = false;
msm_hdmi_set_mode(hdmi, hdmi->power_on);
mutex_unlock(&hdmi->state_mutex);
pm_runtime_put(dev);
}
void msm_hdmi_hpd_irq(struct drm_bridge *bridge)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
u32 hpd_int_status, hpd_int_ctrl;
/* Process HPD: */
hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
hpd_int_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL);
if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) &&
(hpd_int_status & HDMI_HPD_INT_STATUS_INT)) {
bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED);
/* ack & disable (temporarily) HPD events: */
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
HDMI_HPD_INT_CTRL_INT_ACK);
DBG("status=%04x, ctrl=%04x", hpd_int_status, hpd_int_ctrl);
/* detect disconnect if we are connected or visa versa: */
hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN;
if (!detected)
hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
queue_work(hdmi->workq, &hdmi_bridge->hpd_work);
}
}
static enum drm_connector_status detect_reg(struct hdmi *hdmi)
{
u32 hpd_int_status = 0;
int ret;
ret = pm_runtime_resume_and_get(&hdmi->pdev->dev);
if (ret)
goto out;
hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
out:
pm_runtime_put(&hdmi->pdev->dev);
return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ?
connector_status_connected : connector_status_disconnected;
}
#define HPD_GPIO_INDEX 2
static enum drm_connector_status detect_gpio(struct hdmi *hdmi)
{
return gpiod_get_value(hdmi->hpd_gpiod) ?
connector_status_connected :
connector_status_disconnected;
}
enum drm_connector_status
msm_hdmi_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
{
struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
struct hdmi *hdmi = hdmi_bridge->hdmi;
enum drm_connector_status stat_gpio, stat_reg;
int retry = 20;
/*
* some platforms may not have hpd gpio. Rely only on the status
* provided by REG_HDMI_HPD_INT_STATUS in this case.
*/
if (!hdmi->hpd_gpiod)
return detect_reg(hdmi);
do {
stat_gpio = detect_gpio(hdmi);
stat_reg = detect_reg(hdmi);
if (stat_gpio == stat_reg)
break;
mdelay(10);
} while (--retry);
/* the status we get from reading gpio seems to be more reliable,
* so trust that one the most if we didn't manage to get hdmi and
* gpio status to agree:
*/
if (stat_gpio != stat_reg) {
DBG("HDMI_HPD_INT_STATUS tells us: %d", stat_reg);
DBG("hpd gpio tells us: %d", stat_gpio);
}
return stat_gpio;
}
@@ -0,0 +1,276 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include "hdmi.h"
struct hdmi_i2c_adapter {
struct i2c_adapter base;
struct hdmi *hdmi;
bool sw_done;
wait_queue_head_t ddc_event;
};
#define to_hdmi_i2c_adapter(x) container_of(x, struct hdmi_i2c_adapter, base)
static void init_ddc(struct hdmi_i2c_adapter *hdmi_i2c)
{
struct hdmi *hdmi = hdmi_i2c->hdmi;
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
HDMI_DDC_CTRL_SW_STATUS_RESET);
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
HDMI_DDC_CTRL_SOFT_RESET);
hdmi_write(hdmi, REG_HDMI_DDC_SPEED,
HDMI_DDC_SPEED_THRESHOLD(2) |
HDMI_DDC_SPEED_PRESCALE(10));
hdmi_write(hdmi, REG_HDMI_DDC_SETUP,
HDMI_DDC_SETUP_TIMEOUT(0xff));
/* enable reference timer for 27us */
hdmi_write(hdmi, REG_HDMI_DDC_REF,
HDMI_DDC_REF_REFTIMER_ENABLE |
HDMI_DDC_REF_REFTIMER(27));
}
static int ddc_clear_irq(struct hdmi_i2c_adapter *hdmi_i2c)
{
struct hdmi *hdmi = hdmi_i2c->hdmi;
struct drm_device *dev = hdmi->dev;
u32 retry = 0xffff;
u32 ddc_int_ctrl;
do {
--retry;
hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
HDMI_DDC_INT_CTRL_SW_DONE_ACK |
HDMI_DDC_INT_CTRL_SW_DONE_MASK);
ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
} while ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT) && retry);
if (!retry) {
DRM_DEV_ERROR(dev->dev, "timeout waiting for DDC\n");
return -ETIMEDOUT;
}
hdmi_i2c->sw_done = false;
return 0;
}
#define MAX_TRANSACTIONS 4
static bool sw_done(struct hdmi_i2c_adapter *hdmi_i2c)
{
struct hdmi *hdmi = hdmi_i2c->hdmi;
if (!hdmi_i2c->sw_done) {
u32 ddc_int_ctrl;
ddc_int_ctrl = hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL);
if ((ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_MASK) &&
(ddc_int_ctrl & HDMI_DDC_INT_CTRL_SW_DONE_INT)) {
hdmi_i2c->sw_done = true;
hdmi_write(hdmi, REG_HDMI_DDC_INT_CTRL,
HDMI_DDC_INT_CTRL_SW_DONE_ACK);
}
}
return hdmi_i2c->sw_done;
}
static int msm_hdmi_i2c_xfer(struct i2c_adapter *i2c,
struct i2c_msg *msgs, int num)
{
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
struct hdmi *hdmi = hdmi_i2c->hdmi;
struct drm_device *dev = hdmi->dev;
static const u32 nack[] = {
HDMI_DDC_SW_STATUS_NACK0, HDMI_DDC_SW_STATUS_NACK1,
HDMI_DDC_SW_STATUS_NACK2, HDMI_DDC_SW_STATUS_NACK3,
};
int indices[MAX_TRANSACTIONS];
int ret, i, j, index = 0;
u32 ddc_status, ddc_data, i2c_trans;
num = min(num, MAX_TRANSACTIONS);
WARN_ON(!(hdmi_read(hdmi, REG_HDMI_CTRL) & HDMI_CTRL_ENABLE));
if (num == 0)
return num;
ret = pm_runtime_resume_and_get(&hdmi->pdev->dev);
if (ret)
return ret;
init_ddc(hdmi_i2c);
ret = ddc_clear_irq(hdmi_i2c);
if (ret)
goto fail;
for (i = 0; i < num; i++) {
struct i2c_msg *p = &msgs[i];
u32 raw_addr = p->addr << 1;
if (p->flags & I2C_M_RD)
raw_addr |= 1;
ddc_data = HDMI_DDC_DATA_DATA(raw_addr) |
HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
if (i == 0) {
ddc_data |= HDMI_DDC_DATA_INDEX(0) |
HDMI_DDC_DATA_INDEX_WRITE;
}
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
index++;
indices[i] = index;
if (p->flags & I2C_M_RD) {
index += p->len;
} else {
for (j = 0; j < p->len; j++) {
ddc_data = HDMI_DDC_DATA_DATA(p->buf[j]) |
HDMI_DDC_DATA_DATA_RW(DDC_WRITE);
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
index++;
}
}
i2c_trans = HDMI_I2C_TRANSACTION_REG_CNT(p->len) |
HDMI_I2C_TRANSACTION_REG_RW(
(p->flags & I2C_M_RD) ? DDC_READ : DDC_WRITE) |
HDMI_I2C_TRANSACTION_REG_START;
if (i == (num - 1))
i2c_trans |= HDMI_I2C_TRANSACTION_REG_STOP;
hdmi_write(hdmi, REG_HDMI_I2C_TRANSACTION(i), i2c_trans);
}
/* trigger the transfer: */
hdmi_write(hdmi, REG_HDMI_DDC_CTRL,
HDMI_DDC_CTRL_TRANSACTION_CNT(num - 1) |
HDMI_DDC_CTRL_GO);
ret = wait_event_timeout(hdmi_i2c->ddc_event, sw_done(hdmi_i2c), HZ/4);
if (ret <= 0) {
if (ret == 0)
ret = -ETIMEDOUT;
dev_warn(dev->dev, "DDC timeout: %d\n", ret);
DBG("sw_status=%08x, hw_status=%08x, int_ctrl=%08x",
hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS),
hdmi_read(hdmi, REG_HDMI_DDC_HW_STATUS),
hdmi_read(hdmi, REG_HDMI_DDC_INT_CTRL));
goto fail;
}
ddc_status = hdmi_read(hdmi, REG_HDMI_DDC_SW_STATUS);
/* read back results of any read transactions: */
for (i = 0; i < num; i++) {
struct i2c_msg *p = &msgs[i];
if (!(p->flags & I2C_M_RD))
continue;
/* check for NACK: */
if (ddc_status & nack[i]) {
DBG("ddc_status=%08x", ddc_status);
break;
}
ddc_data = HDMI_DDC_DATA_DATA_RW(DDC_READ) |
HDMI_DDC_DATA_INDEX(indices[i]) |
HDMI_DDC_DATA_INDEX_WRITE;
hdmi_write(hdmi, REG_HDMI_DDC_DATA, ddc_data);
/* discard first byte: */
hdmi_read(hdmi, REG_HDMI_DDC_DATA);
for (j = 0; j < p->len; j++) {
ddc_data = hdmi_read(hdmi, REG_HDMI_DDC_DATA);
p->buf[j] = FIELD(ddc_data, HDMI_DDC_DATA_DATA);
}
}
pm_runtime_put(&hdmi->pdev->dev);
return i;
fail:
pm_runtime_put(&hdmi->pdev->dev);
return ret;
}
static u32 msm_hdmi_i2c_func(struct i2c_adapter *adapter)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static const struct i2c_algorithm msm_hdmi_i2c_algorithm = {
.master_xfer = msm_hdmi_i2c_xfer,
.functionality = msm_hdmi_i2c_func,
};
void msm_hdmi_i2c_irq(struct i2c_adapter *i2c)
{
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
if (sw_done(hdmi_i2c))
wake_up_all(&hdmi_i2c->ddc_event);
}
void msm_hdmi_i2c_destroy(struct i2c_adapter *i2c)
{
struct hdmi_i2c_adapter *hdmi_i2c = to_hdmi_i2c_adapter(i2c);
i2c_del_adapter(i2c);
kfree(hdmi_i2c);
}
struct i2c_adapter *msm_hdmi_i2c_init(struct hdmi *hdmi)
{
struct hdmi_i2c_adapter *hdmi_i2c;
struct i2c_adapter *i2c = NULL;
int ret;
hdmi_i2c = kzalloc_obj(*hdmi_i2c);
if (!hdmi_i2c) {
ret = -ENOMEM;
goto fail;
}
i2c = &hdmi_i2c->base;
hdmi_i2c->hdmi = hdmi;
init_waitqueue_head(&hdmi_i2c->ddc_event);
i2c->owner = THIS_MODULE;
snprintf(i2c->name, sizeof(i2c->name), "msm hdmi i2c");
i2c->dev.parent = &hdmi->pdev->dev;
i2c->algo = &msm_hdmi_i2c_algorithm;
ret = i2c_add_adapter(i2c);
if (ret)
goto fail;
return i2c;
fail:
if (i2c)
msm_hdmi_i2c_destroy(i2c);
return ERR_PTR(ret);
}
@@ -0,0 +1,226 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
*/
#include <linux/of.h>
#include <linux/platform_device.h>
#include "hdmi.h"
static int msm_hdmi_phy_resource_init(struct hdmi_phy *phy)
{
struct hdmi_phy_cfg *cfg = phy->cfg;
struct device *dev = &phy->pdev->dev;
int i, ret;
phy->regs = devm_kcalloc(dev, cfg->num_regs, sizeof(phy->regs[0]),
GFP_KERNEL);
if (!phy->regs)
return -ENOMEM;
phy->clks = devm_kcalloc(dev, cfg->num_clks, sizeof(phy->clks[0]),
GFP_KERNEL);
if (!phy->clks)
return -ENOMEM;
for (i = 0; i < cfg->num_regs; i++)
phy->regs[i].supply = cfg->reg_names[i];
ret = devm_regulator_bulk_get(dev, cfg->num_regs, phy->regs);
if (ret) {
if (ret != -EPROBE_DEFER)
DRM_DEV_ERROR(dev, "failed to get phy regulators: %d\n", ret);
return ret;
}
for (i = 0; i < cfg->num_clks; i++) {
struct clk *clk;
clk = msm_clk_get(phy->pdev, cfg->clk_names[i]);
if (IS_ERR(clk)) {
ret = PTR_ERR(clk);
DRM_DEV_ERROR(dev, "failed to get phy clock: %s (%d)\n",
cfg->clk_names[i], ret);
return ret;
}
phy->clks[i] = clk;
}
return 0;
}
int msm_hdmi_phy_resource_enable(struct hdmi_phy *phy)
{
struct hdmi_phy_cfg *cfg = phy->cfg;
struct device *dev = &phy->pdev->dev;
int i, ret = 0;
ret = pm_runtime_resume_and_get(dev);
if (ret) {
DRM_DEV_ERROR(dev, "runtime resume failed: %d\n", ret);
return ret;
}
ret = regulator_bulk_enable(cfg->num_regs, phy->regs);
if (ret) {
DRM_DEV_ERROR(dev, "failed to enable regulators: (%d)\n", ret);
return ret;
}
for (i = 0; i < cfg->num_clks; i++) {
ret = clk_prepare_enable(phy->clks[i]);
if (ret)
DRM_DEV_ERROR(dev, "failed to enable clock: %s (%d)\n",
cfg->clk_names[i], ret);
}
return ret;
}
void msm_hdmi_phy_resource_disable(struct hdmi_phy *phy)
{
struct hdmi_phy_cfg *cfg = phy->cfg;
struct device *dev = &phy->pdev->dev;
int i;
for (i = cfg->num_clks - 1; i >= 0; i--)
clk_disable_unprepare(phy->clks[i]);
regulator_bulk_disable(cfg->num_regs, phy->regs);
pm_runtime_put_sync(dev);
}
void msm_hdmi_phy_powerup(struct hdmi_phy *phy, unsigned long pixclock)
{
if (!phy || !phy->cfg->powerup)
return;
phy->cfg->powerup(phy, pixclock);
}
void msm_hdmi_phy_powerdown(struct hdmi_phy *phy)
{
if (!phy || !phy->cfg->powerdown)
return;
phy->cfg->powerdown(phy);
}
static int msm_hdmi_phy_pll_init(struct platform_device *pdev,
enum hdmi_phy_type type)
{
int ret;
switch (type) {
case MSM_HDMI_PHY_8960:
ret = msm_hdmi_pll_8960_init(pdev);
break;
case MSM_HDMI_PHY_8996:
ret = msm_hdmi_pll_8996_init(pdev);
break;
case MSM_HDMI_PHY_8998:
ret = msm_hdmi_pll_8998_init(pdev);
break;
/*
* we don't have PLL support for these, don't report an error for now
*/
case MSM_HDMI_PHY_8x60:
case MSM_HDMI_PHY_8x74:
default:
ret = 0;
break;
}
return ret;
}
static int msm_hdmi_phy_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct hdmi_phy *phy;
int ret;
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
if (!phy)
return -ENODEV;
phy->cfg = (struct hdmi_phy_cfg *)of_device_get_match_data(dev);
if (!phy->cfg)
return -ENODEV;
phy->mmio = msm_ioremap(pdev, "hdmi_phy");
if (IS_ERR(phy->mmio)) {
DRM_DEV_ERROR(dev, "%s: failed to map phy base\n", __func__);
return -ENOMEM;
}
phy->pdev = pdev;
ret = msm_hdmi_phy_resource_init(phy);
if (ret)
return ret;
pm_runtime_enable(&pdev->dev);
ret = msm_hdmi_phy_resource_enable(phy);
if (ret)
return ret;
ret = msm_hdmi_phy_pll_init(pdev, phy->cfg->type);
if (ret) {
DRM_DEV_ERROR(dev, "couldn't init PLL\n");
msm_hdmi_phy_resource_disable(phy);
return ret;
}
msm_hdmi_phy_resource_disable(phy);
platform_set_drvdata(pdev, phy);
return 0;
}
static void msm_hdmi_phy_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
}
static const struct of_device_id msm_hdmi_phy_dt_match[] = {
{ .compatible = "qcom,hdmi-phy-8660",
.data = &msm_hdmi_phy_8x60_cfg },
{ .compatible = "qcom,hdmi-phy-8960",
.data = &msm_hdmi_phy_8960_cfg },
{ .compatible = "qcom,hdmi-phy-8974",
.data = &msm_hdmi_phy_8x74_cfg },
{ .compatible = "qcom,hdmi-phy-8084",
.data = &msm_hdmi_phy_8x74_cfg },
{ .compatible = "qcom,hdmi-phy-8996",
.data = &msm_hdmi_phy_8996_cfg },
{ .compatible = "qcom,hdmi-phy-8998",
.data = &msm_hdmi_phy_8998_cfg },
{}
};
MODULE_DEVICE_TABLE(of, msm_hdmi_phy_dt_match);
static struct platform_driver msm_hdmi_phy_platform_driver = {
.probe = msm_hdmi_phy_probe,
.remove = msm_hdmi_phy_remove,
.driver = {
.name = "msm_hdmi_phy",
.of_match_table = msm_hdmi_phy_dt_match,
},
};
void __init msm_hdmi_phy_driver_register(void)
{
platform_driver_register(&msm_hdmi_phy_platform_driver);
}
void __exit msm_hdmi_phy_driver_unregister(void)
{
platform_driver_unregister(&msm_hdmi_phy_platform_driver);
}
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include "hdmi.h"
static void hdmi_phy_8960_powerup(struct hdmi_phy *phy,
unsigned long pixclock)
{
DBG("pixclock: %lu", pixclock);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG2, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG0, 0x1b);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG1, 0xf2);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG4, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG5, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG6, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG7, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG8, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG9, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG10, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG11, 0x00);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG3, 0x20);
}
static void hdmi_phy_8960_powerdown(struct hdmi_phy *phy)
{
DBG("");
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG2, 0x7f);
}
static const char * const hdmi_phy_8960_reg_names[] = {
"core-vdda",
};
static const char * const hdmi_phy_8960_clk_names[] = {
"slave_iface",
};
const struct hdmi_phy_cfg msm_hdmi_phy_8960_cfg = {
.type = MSM_HDMI_PHY_8960,
.powerup = hdmi_phy_8960_powerup,
.powerdown = hdmi_phy_8960_powerdown,
.reg_names = hdmi_phy_8960_reg_names,
.num_regs = ARRAY_SIZE(hdmi_phy_8960_reg_names),
.clk_names = hdmi_phy_8960_clk_names,
.num_clks = ARRAY_SIZE(hdmi_phy_8960_clk_names),
};
@@ -0,0 +1,759 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include "hdmi.h"
#define HDMI_VCO_MAX_FREQ 12000000000UL
#define HDMI_VCO_MIN_FREQ 8000000000UL
#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL
#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL
#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000UL
#define HDMI_CORECLK_DIV 5
#define HDMI_DEFAULT_REF_CLOCK 19200000
#define HDMI_PLL_CMP_CNT 1024
#define HDMI_PLL_POLL_MAX_READS 100
#define HDMI_PLL_POLL_TIMEOUT_US 150
#define HDMI_NUM_TX_CHANNEL 4
struct hdmi_pll_8996 {
struct platform_device *pdev;
struct clk_hw clk_hw;
/* pll mmio base */
void __iomem *mmio_qserdes_com;
/* tx channel base */
void __iomem *mmio_qserdes_tx[HDMI_NUM_TX_CHANNEL];
};
#define hw_clk_to_pll(x) container_of(x, struct hdmi_pll_8996, clk_hw)
struct hdmi_8996_phy_pll_reg_cfg {
u32 tx_lx_lane_mode[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_tx_band[HDMI_NUM_TX_CHANNEL];
u32 com_svs_mode_clk_sel;
u32 com_hsclk_sel;
u32 com_pll_cctrl_mode0;
u32 com_pll_rctrl_mode0;
u32 com_cp_ctrl_mode0;
u32 com_dec_start_mode0;
u32 com_div_frac_start1_mode0;
u32 com_div_frac_start2_mode0;
u32 com_div_frac_start3_mode0;
u32 com_integloop_gain0_mode0;
u32 com_integloop_gain1_mode0;
u32 com_lock_cmp_en;
u32 com_lock_cmp1_mode0;
u32 com_lock_cmp2_mode0;
u32 com_lock_cmp3_mode0;
u32 com_core_clk_en;
u32 com_coreclk_div;
u32 com_vco_tune_ctrl;
u32 tx_lx_tx_drv_lvl[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_tx_emp_post1_lvl[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_vmode_ctrl1[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_vmode_ctrl2[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_res_code_lane_tx[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_hp_pd_enables[HDMI_NUM_TX_CHANNEL];
u32 phy_mode;
};
struct hdmi_8996_post_divider {
u64 vco_freq;
int hsclk_divsel;
int vco_ratio;
int tx_band_sel;
int half_rate_mode;
};
static inline struct hdmi_phy *pll_get_phy(struct hdmi_pll_8996 *pll)
{
return platform_get_drvdata(pll->pdev);
}
static inline void hdmi_pll_write(struct hdmi_pll_8996 *pll, int offset,
u32 data)
{
writel(data, pll->mmio_qserdes_com + offset);
}
static inline u32 hdmi_pll_read(struct hdmi_pll_8996 *pll, int offset)
{
return readl(pll->mmio_qserdes_com + offset);
}
static inline void hdmi_tx_chan_write(struct hdmi_pll_8996 *pll, int channel,
int offset, int data)
{
writel(data, pll->mmio_qserdes_tx[channel] + offset);
}
static inline u32 pll_get_cpctrl(u64 frac_start, unsigned long ref_clk,
bool gen_ssc)
{
if ((frac_start != 0) || gen_ssc)
return (11000000 / (ref_clk / 20));
return 0x23;
}
static inline u32 pll_get_rctrl(u64 frac_start, bool gen_ssc)
{
if ((frac_start != 0) || gen_ssc)
return 0x16;
return 0x10;
}
static inline u32 pll_get_cctrl(u64 frac_start, bool gen_ssc)
{
if ((frac_start != 0) || gen_ssc)
return 0x28;
return 0x1;
}
static inline u32 pll_get_integloop_gain(u64 frac_start, u64 bclk, u32 ref_clk,
bool gen_ssc)
{
int digclk_divsel = bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2;
u64 base;
if ((frac_start != 0) || gen_ssc)
base = (64 * ref_clk) / HDMI_DEFAULT_REF_CLOCK;
else
base = (1022 * ref_clk) / 100;
base <<= digclk_divsel;
return (base <= 2046 ? base : 2046);
}
static inline u32 pll_get_pll_cmp(u64 fdata, unsigned long ref_clk)
{
u64 dividend = HDMI_PLL_CMP_CNT * fdata;
u32 divisor = ref_clk * 10;
u32 rem;
rem = do_div(dividend, divisor);
if (rem > (divisor >> 1))
dividend++;
return dividend - 1;
}
static inline u64 pll_cmp_to_fdata(u32 pll_cmp, unsigned long ref_clk)
{
u64 fdata = ((u64)pll_cmp) * ref_clk * 10;
do_div(fdata, HDMI_PLL_CMP_CNT);
return fdata;
}
static int pll_get_post_div(struct hdmi_8996_post_divider *pd, u64 bclk)
{
int ratio[] = { 2, 3, 4, 5, 6, 9, 10, 12, 14, 15, 20, 21, 25, 28, 35 };
int hs_divsel[] = { 0, 4, 8, 12, 1, 5, 2, 9, 3, 13, 10, 7, 14, 11, 15 };
int tx_band_sel[] = { 0, 1, 2, 3 };
u64 vco_freq[60];
u64 vco, vco_optimal;
int half_rate_mode = 0;
int vco_optimal_index, vco_freq_index;
int i, j;
retry:
vco_optimal = HDMI_VCO_MAX_FREQ;
vco_optimal_index = -1;
vco_freq_index = 0;
for (i = 0; i < 15; i++) {
for (j = 0; j < 4; j++) {
u32 ratio_mult = ratio[i] << tx_band_sel[j];
vco = bclk >> half_rate_mode;
vco *= ratio_mult;
vco_freq[vco_freq_index++] = vco;
}
}
for (i = 0; i < 60; i++) {
u64 vco_tmp = vco_freq[i];
if ((vco_tmp >= HDMI_VCO_MIN_FREQ) &&
(vco_tmp <= vco_optimal)) {
vco_optimal = vco_tmp;
vco_optimal_index = i;
}
}
if (vco_optimal_index == -1) {
if (!half_rate_mode) {
half_rate_mode = 1;
goto retry;
}
} else {
pd->vco_freq = vco_optimal;
pd->tx_band_sel = tx_band_sel[vco_optimal_index % 4];
pd->vco_ratio = ratio[vco_optimal_index / 4];
pd->hsclk_divsel = hs_divsel[vco_optimal_index / 4];
return 0;
}
return -EINVAL;
}
static int pll_calculate(unsigned long pix_clk, unsigned long ref_clk,
struct hdmi_8996_phy_pll_reg_cfg *cfg)
{
struct hdmi_8996_post_divider pd;
u64 bclk;
u64 tmds_clk;
u64 dec_start;
u64 frac_start;
u64 fdata;
u32 pll_divisor;
u32 rem;
u32 cpctrl;
u32 rctrl;
u32 cctrl;
u32 integloop_gain;
u32 pll_cmp;
int i, ret;
/* bit clk = 10 * pix_clk */
bclk = ((u64)pix_clk) * 10;
if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD)
tmds_clk = pix_clk >> 2;
else
tmds_clk = pix_clk;
ret = pll_get_post_div(&pd, bclk);
if (ret)
return ret;
dec_start = pd.vco_freq;
pll_divisor = 4 * ref_clk;
do_div(dec_start, pll_divisor);
frac_start = pd.vco_freq * (1 << 20);
rem = do_div(frac_start, pll_divisor);
frac_start -= dec_start * (1 << 20);
if (rem > (pll_divisor >> 1))
frac_start++;
cpctrl = pll_get_cpctrl(frac_start, ref_clk, false);
rctrl = pll_get_rctrl(frac_start, false);
cctrl = pll_get_cctrl(frac_start, false);
integloop_gain = pll_get_integloop_gain(frac_start, bclk,
ref_clk, false);
fdata = pd.vco_freq;
do_div(fdata, pd.vco_ratio);
pll_cmp = pll_get_pll_cmp(fdata, ref_clk);
DBG("VCO freq: %llu", pd.vco_freq);
DBG("fdata: %llu", fdata);
DBG("pix_clk: %lu", pix_clk);
DBG("tmds clk: %llu", tmds_clk);
DBG("HSCLK_SEL: %d", pd.hsclk_divsel);
DBG("DEC_START: %llu", dec_start);
DBG("DIV_FRAC_START: %llu", frac_start);
DBG("PLL_CPCTRL: %u", cpctrl);
DBG("PLL_RCTRL: %u", rctrl);
DBG("PLL_CCTRL: %u", cctrl);
DBG("INTEGLOOP_GAIN: %u", integloop_gain);
DBG("TX_BAND: %d", pd.tx_band_sel);
DBG("PLL_CMP: %u", pll_cmp);
/* Convert these values to register specific values */
if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD)
cfg->com_svs_mode_clk_sel = 1;
else
cfg->com_svs_mode_clk_sel = 2;
cfg->com_hsclk_sel = (0x20 | pd.hsclk_divsel);
cfg->com_pll_cctrl_mode0 = cctrl;
cfg->com_pll_rctrl_mode0 = rctrl;
cfg->com_cp_ctrl_mode0 = cpctrl;
cfg->com_dec_start_mode0 = dec_start;
cfg->com_div_frac_start1_mode0 = (frac_start & 0xff);
cfg->com_div_frac_start2_mode0 = ((frac_start & 0xff00) >> 8);
cfg->com_div_frac_start3_mode0 = ((frac_start & 0xf0000) >> 16);
cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xff);
cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xf00) >> 8);
cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xff);
cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xff00) >> 8);
cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16);
cfg->com_lock_cmp_en = 0x0;
cfg->com_core_clk_en = 0x2c;
cfg->com_coreclk_div = HDMI_CORECLK_DIV;
cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0;
cfg->com_vco_tune_ctrl = 0x0;
cfg->tx_lx_lane_mode[0] =
cfg->tx_lx_lane_mode[2] = 0x43;
cfg->tx_lx_hp_pd_enables[0] =
cfg->tx_lx_hp_pd_enables[1] =
cfg->tx_lx_hp_pd_enables[2] = 0x0c;
cfg->tx_lx_hp_pd_enables[3] = 0x3;
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++)
cfg->tx_lx_tx_band[i] = pd.tx_band_sel + 4;
if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) {
cfg->tx_lx_tx_drv_lvl[0] =
cfg->tx_lx_tx_drv_lvl[1] =
cfg->tx_lx_tx_drv_lvl[2] = 0x25;
cfg->tx_lx_tx_drv_lvl[3] = 0x22;
cfg->tx_lx_tx_emp_post1_lvl[0] =
cfg->tx_lx_tx_emp_post1_lvl[1] =
cfg->tx_lx_tx_emp_post1_lvl[2] = 0x23;
cfg->tx_lx_tx_emp_post1_lvl[3] = 0x27;
cfg->tx_lx_vmode_ctrl1[0] =
cfg->tx_lx_vmode_ctrl1[1] =
cfg->tx_lx_vmode_ctrl1[2] =
cfg->tx_lx_vmode_ctrl1[3] = 0x00;
cfg->tx_lx_vmode_ctrl2[0] =
cfg->tx_lx_vmode_ctrl2[1] =
cfg->tx_lx_vmode_ctrl2[2] = 0x0D;
cfg->tx_lx_vmode_ctrl2[3] = 0x00;
} else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) {
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
cfg->tx_lx_tx_drv_lvl[i] = 0x25;
cfg->tx_lx_tx_emp_post1_lvl[i] = 0x23;
cfg->tx_lx_vmode_ctrl1[i] = 0x00;
}
cfg->tx_lx_vmode_ctrl2[0] =
cfg->tx_lx_vmode_ctrl2[1] =
cfg->tx_lx_vmode_ctrl2[2] = 0x0D;
cfg->tx_lx_vmode_ctrl2[3] = 0x00;
} else {
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
cfg->tx_lx_tx_drv_lvl[i] = 0x20;
cfg->tx_lx_tx_emp_post1_lvl[i] = 0x20;
cfg->tx_lx_vmode_ctrl1[i] = 0x00;
cfg->tx_lx_vmode_ctrl2[i] = 0x0E;
}
}
DBG("com_svs_mode_clk_sel = 0x%x", cfg->com_svs_mode_clk_sel);
DBG("com_hsclk_sel = 0x%x", cfg->com_hsclk_sel);
DBG("com_lock_cmp_en = 0x%x", cfg->com_lock_cmp_en);
DBG("com_pll_cctrl_mode0 = 0x%x", cfg->com_pll_cctrl_mode0);
DBG("com_pll_rctrl_mode0 = 0x%x", cfg->com_pll_rctrl_mode0);
DBG("com_cp_ctrl_mode0 = 0x%x", cfg->com_cp_ctrl_mode0);
DBG("com_dec_start_mode0 = 0x%x", cfg->com_dec_start_mode0);
DBG("com_div_frac_start1_mode0 = 0x%x", cfg->com_div_frac_start1_mode0);
DBG("com_div_frac_start2_mode0 = 0x%x", cfg->com_div_frac_start2_mode0);
DBG("com_div_frac_start3_mode0 = 0x%x", cfg->com_div_frac_start3_mode0);
DBG("com_integloop_gain0_mode0 = 0x%x", cfg->com_integloop_gain0_mode0);
DBG("com_integloop_gain1_mode0 = 0x%x", cfg->com_integloop_gain1_mode0);
DBG("com_lock_cmp1_mode0 = 0x%x", cfg->com_lock_cmp1_mode0);
DBG("com_lock_cmp2_mode0 = 0x%x", cfg->com_lock_cmp2_mode0);
DBG("com_lock_cmp3_mode0 = 0x%x", cfg->com_lock_cmp3_mode0);
DBG("com_core_clk_en = 0x%x", cfg->com_core_clk_en);
DBG("com_coreclk_div = 0x%x", cfg->com_coreclk_div);
DBG("phy_mode = 0x%x", cfg->phy_mode);
DBG("tx_l0_lane_mode = 0x%x", cfg->tx_lx_lane_mode[0]);
DBG("tx_l2_lane_mode = 0x%x", cfg->tx_lx_lane_mode[2]);
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
DBG("tx_l%d_tx_band = 0x%x", i, cfg->tx_lx_tx_band[i]);
DBG("tx_l%d_tx_drv_lvl = 0x%x", i, cfg->tx_lx_tx_drv_lvl[i]);
DBG("tx_l%d_tx_emp_post1_lvl = 0x%x", i,
cfg->tx_lx_tx_emp_post1_lvl[i]);
DBG("tx_l%d_vmode_ctrl1 = 0x%x", i, cfg->tx_lx_vmode_ctrl1[i]);
DBG("tx_l%d_vmode_ctrl2 = 0x%x", i, cfg->tx_lx_vmode_ctrl2[i]);
}
return 0;
}
static int hdmi_8996_pll_set_clk_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
struct hdmi_8996_phy_pll_reg_cfg cfg;
int i, ret;
memset(&cfg, 0x00, sizeof(cfg));
ret = pll_calculate(rate, parent_rate, &cfg);
if (ret) {
DRM_ERROR("PLL calculation failed\n");
return ret;
}
/* Initially shut down PHY */
DBG("Disabling PHY");
hdmi_phy_write(phy, REG_HDMI_8996_PHY_PD_CTL, 0x0);
udelay(500);
/* Power up sequence */
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BG_CTRL, 0x04);
hdmi_phy_write(phy, REG_HDMI_8996_PHY_PD_CTL, 0x1);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_RESETSM_CNTRL, 0x20);
hdmi_phy_write(phy, REG_HDMI_8996_PHY_TX0_TX1_LANE_CTL, 0x0F);
hdmi_phy_write(phy, REG_HDMI_8996_PHY_TX2_TX3_LANE_CTL, 0x0F);
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_CLKBUF_ENABLE,
0x03);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_TX_BAND,
cfg.tx_lx_tx_band[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_RESET_TSYNC_EN,
0x03);
}
hdmi_tx_chan_write(pll, 0, REG_HDMI_PHY_QSERDES_TX_LX_LANE_MODE,
cfg.tx_lx_lane_mode[0]);
hdmi_tx_chan_write(pll, 2, REG_HDMI_PHY_QSERDES_TX_LX_LANE_MODE,
cfg.tx_lx_lane_mode[2]);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1E);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x07);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SYSCLK_EN_SEL, 0x37);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SYS_CLK_CTRL, 0x02);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CLK_ENABLE1, 0x0E);
/* Bypass VCO calibration */
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SVS_MODE_CLK_SEL,
cfg.com_svs_mode_clk_sel);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BG_TRIM, 0x0F);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_PLL_IVCO, 0x0F);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_VCO_TUNE_CTRL,
cfg.com_vco_tune_ctrl);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_BG_CTRL, 0x06);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CLK_SELECT, 0x30);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_HSCLK_SEL,
cfg.com_hsclk_sel);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP_EN,
cfg.com_lock_cmp_en);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_PLL_CCTRL_MODE0,
cfg.com_pll_cctrl_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_PLL_RCTRL_MODE0,
cfg.com_pll_rctrl_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CP_CTRL_MODE0,
cfg.com_cp_ctrl_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DEC_START_MODE0,
cfg.com_dec_start_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DIV_FRAC_START1_MODE0,
cfg.com_div_frac_start1_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DIV_FRAC_START2_MODE0,
cfg.com_div_frac_start2_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_DIV_FRAC_START3_MODE0,
cfg.com_div_frac_start3_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_INTEGLOOP_GAIN0_MODE0,
cfg.com_integloop_gain0_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_INTEGLOOP_GAIN1_MODE0,
cfg.com_integloop_gain1_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP1_MODE0,
cfg.com_lock_cmp1_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP2_MODE0,
cfg.com_lock_cmp2_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP3_MODE0,
cfg.com_lock_cmp3_mode0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_VCO_TUNE_MAP, 0x00);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CORE_CLK_EN,
cfg.com_core_clk_en);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CORECLK_DIV,
cfg.com_coreclk_div);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_CMN_CONFIG, 0x02);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_RESCODE_DIV_NUM, 0x15);
/* TX lanes setup (TX 0/1/2/3) */
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_TX_DRV_LVL,
cfg.tx_lx_tx_drv_lvl[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_TX_EMP_POST1_LVL,
cfg.tx_lx_tx_emp_post1_lvl[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_VMODE_CTRL1,
cfg.tx_lx_vmode_ctrl1[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_VMODE_CTRL2,
cfg.tx_lx_vmode_ctrl2[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_TX_DRV_LVL_OFFSET,
0x00);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_RES_CODE_LANE_OFFSET,
0x00);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_TRAN_DRVR_EMP_EN,
0x03);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_PARRATE_REC_DETECT_IDLE_EN,
0x40);
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_HP_PD_ENABLES,
cfg.tx_lx_hp_pd_enables[i]);
}
hdmi_phy_write(phy, REG_HDMI_8996_PHY_MODE, cfg.phy_mode);
hdmi_phy_write(phy, REG_HDMI_8996_PHY_PD_CTL, 0x1F);
/*
* Ensure that vco configuration gets flushed to hardware before
* enabling the PLL
*/
wmb();
return 0;
}
static int hdmi_8996_phy_ready_status(struct hdmi_phy *phy)
{
u32 nb_tries = HDMI_PLL_POLL_MAX_READS;
unsigned long timeout = HDMI_PLL_POLL_TIMEOUT_US;
u32 status;
int phy_ready = 0;
DBG("Waiting for PHY ready");
while (nb_tries--) {
status = hdmi_phy_read(phy, REG_HDMI_8996_PHY_STATUS);
phy_ready = status & BIT(0);
if (phy_ready)
break;
udelay(timeout);
}
DBG("PHY is %sready", phy_ready ? "" : "*not* ");
return phy_ready;
}
static int hdmi_8996_pll_lock_status(struct hdmi_pll_8996 *pll)
{
u32 status;
int nb_tries = HDMI_PLL_POLL_MAX_READS;
unsigned long timeout = HDMI_PLL_POLL_TIMEOUT_US;
int pll_locked = 0;
DBG("Waiting for PLL lock");
while (nb_tries--) {
status = hdmi_pll_read(pll,
REG_HDMI_PHY_QSERDES_COM_C_READY_STATUS);
pll_locked = status & BIT(0);
if (pll_locked)
break;
udelay(timeout);
}
DBG("HDMI PLL is %slocked", pll_locked ? "" : "*not* ");
return pll_locked;
}
static int hdmi_8996_pll_prepare(struct clk_hw *hw)
{
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
int i, ret = 0;
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x1);
udelay(100);
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x19);
udelay(100);
ret = hdmi_8996_pll_lock_status(pll);
if (!ret)
return ret;
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++)
hdmi_tx_chan_write(pll, i,
REG_HDMI_PHY_QSERDES_TX_LX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN,
0x6F);
/* Disable SSC */
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_PER1, 0x0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_PER2, 0x0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_STEP_SIZE1, 0x0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_STEP_SIZE2, 0x0);
hdmi_pll_write(pll, REG_HDMI_PHY_QSERDES_COM_SSC_EN_CENTER, 0x2);
ret = hdmi_8996_phy_ready_status(phy);
if (!ret)
return ret;
/* Restart the retiming buffer */
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x18);
udelay(1);
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x19);
return 0;
}
static int hdmi_8996_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
req->rate = clamp_t(unsigned long, req->rate, HDMI_TMDS_CHAR_RATE_MIN_HZ,
HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ);
return 0;
}
static unsigned long hdmi_8996_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
u64 fdata;
u32 cmp1, cmp2, cmp3, pll_cmp;
cmp1 = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP1_MODE0);
cmp2 = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP2_MODE0);
cmp3 = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_LOCK_CMP3_MODE0);
pll_cmp = cmp1 | (cmp2 << 8) | (cmp3 << 16);
fdata = pll_cmp_to_fdata(pll_cmp + 1, parent_rate);
do_div(fdata, 10);
return fdata;
}
static void hdmi_8996_pll_unprepare(struct clk_hw *hw)
{
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
hdmi_phy_write(phy, REG_HDMI_8996_PHY_CFG, 0x6);
usleep_range(100, 150);
}
static int hdmi_8996_pll_is_enabled(struct clk_hw *hw)
{
struct hdmi_pll_8996 *pll = hw_clk_to_pll(hw);
u32 status;
int pll_locked;
status = hdmi_pll_read(pll, REG_HDMI_PHY_QSERDES_COM_C_READY_STATUS);
pll_locked = status & BIT(0);
return pll_locked;
}
static const struct clk_ops hdmi_8996_pll_ops = {
.set_rate = hdmi_8996_pll_set_clk_rate,
.determine_rate = hdmi_8996_pll_determine_rate,
.recalc_rate = hdmi_8996_pll_recalc_rate,
.prepare = hdmi_8996_pll_prepare,
.unprepare = hdmi_8996_pll_unprepare,
.is_enabled = hdmi_8996_pll_is_enabled,
};
static const struct clk_init_data pll_init = {
.name = "hdmipll",
.ops = &hdmi_8996_pll_ops,
.parent_data = (const struct clk_parent_data[]){
{ .fw_name = "xo", .name = "xo_board" },
},
.num_parents = 1,
.flags = CLK_IGNORE_UNUSED,
};
int msm_hdmi_pll_8996_init(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct hdmi_pll_8996 *pll;
int i, ret;
pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
if (!pll)
return -ENOMEM;
pll->pdev = pdev;
pll->mmio_qserdes_com = msm_ioremap(pdev, "hdmi_pll");
if (IS_ERR(pll->mmio_qserdes_com)) {
DRM_DEV_ERROR(dev, "failed to map pll base\n");
return -ENOMEM;
}
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
char name[32];
snprintf(name, sizeof(name), "hdmi_tx_l%d", i);
pll->mmio_qserdes_tx[i] = msm_ioremap(pdev, name);
if (IS_ERR(pll->mmio_qserdes_tx[i])) {
DRM_DEV_ERROR(dev, "failed to map pll base\n");
return -ENOMEM;
}
}
pll->clk_hw.init = &pll_init;
ret = devm_clk_hw_register(dev, &pll->clk_hw);
if (ret) {
DRM_DEV_ERROR(dev, "failed to register pll clock\n");
return ret;
}
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &pll->clk_hw);
if (ret) {
DRM_DEV_ERROR(dev, "%s: failed to register clk provider: %d\n", __func__, ret);
return ret;
}
return 0;
}
static const char * const hdmi_phy_8996_reg_names[] = {
"vddio",
"vcca",
};
static const char * const hdmi_phy_8996_clk_names[] = {
"iface", "ref",
};
const struct hdmi_phy_cfg msm_hdmi_phy_8996_cfg = {
.type = MSM_HDMI_PHY_8996,
.reg_names = hdmi_phy_8996_reg_names,
.num_regs = ARRAY_SIZE(hdmi_phy_8996_reg_names),
.clk_names = hdmi_phy_8996_clk_names,
.num_clks = ARRAY_SIZE(hdmi_phy_8996_clk_names),
};
@@ -0,0 +1,763 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
* Copyright (c) 2024 Freebox SAS
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include "hdmi.h"
#define HDMI_VCO_MAX_FREQ 12000000000UL
#define HDMI_VCO_MIN_FREQ 8000000000UL
#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL
#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL
#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000UL
#define HDMI_CORECLK_DIV 5
#define HDMI_DEFAULT_REF_CLOCK 19200000
#define HDMI_PLL_CMP_CNT 1024
#define HDMI_PLL_POLL_MAX_READS 100
#define HDMI_PLL_POLL_TIMEOUT_US 150
#define HDMI_NUM_TX_CHANNEL 4
struct hdmi_pll_8998 {
struct platform_device *pdev;
struct clk_hw clk_hw;
unsigned long rate;
/* pll mmio base */
void __iomem *mmio_qserdes_com;
/* tx channel base */
void __iomem *mmio_qserdes_tx[HDMI_NUM_TX_CHANNEL];
};
#define hw_clk_to_pll(x) container_of(x, struct hdmi_pll_8998, clk_hw)
struct hdmi_8998_phy_pll_reg_cfg {
u32 com_svs_mode_clk_sel;
u32 com_hsclk_sel;
u32 com_pll_cctrl_mode0;
u32 com_pll_rctrl_mode0;
u32 com_cp_ctrl_mode0;
u32 com_dec_start_mode0;
u32 com_div_frac_start1_mode0;
u32 com_div_frac_start2_mode0;
u32 com_div_frac_start3_mode0;
u32 com_integloop_gain0_mode0;
u32 com_integloop_gain1_mode0;
u32 com_lock_cmp_en;
u32 com_lock_cmp1_mode0;
u32 com_lock_cmp2_mode0;
u32 com_lock_cmp3_mode0;
u32 com_core_clk_en;
u32 com_coreclk_div_mode0;
u32 tx_lx_tx_band[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_tx_drv_lvl[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_tx_emp_post1_lvl[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_pre_driver_1[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_pre_driver_2[HDMI_NUM_TX_CHANNEL];
u32 tx_lx_res_code_offset[HDMI_NUM_TX_CHANNEL];
u32 phy_mode;
};
struct hdmi_8998_post_divider {
u64 vco_freq;
int hsclk_divsel;
int vco_ratio;
int tx_band_sel;
int half_rate_mode;
};
static inline struct hdmi_phy *pll_get_phy(struct hdmi_pll_8998 *pll)
{
return platform_get_drvdata(pll->pdev);
}
static inline void hdmi_pll_write(struct hdmi_pll_8998 *pll, int offset,
u32 data)
{
writel(data, pll->mmio_qserdes_com + offset);
}
static inline u32 hdmi_pll_read(struct hdmi_pll_8998 *pll, int offset)
{
return readl(pll->mmio_qserdes_com + offset);
}
static inline void hdmi_tx_chan_write(struct hdmi_pll_8998 *pll, int channel,
int offset, int data)
{
writel(data, pll->mmio_qserdes_tx[channel] + offset);
}
static inline u32 pll_get_cpctrl(u64 frac_start, unsigned long ref_clk,
bool gen_ssc)
{
if ((frac_start != 0) || gen_ssc)
return 0x8;
return 0x30;
}
static inline u32 pll_get_rctrl(u64 frac_start, bool gen_ssc)
{
if ((frac_start != 0) || gen_ssc)
return 0x16;
return 0x18;
}
static inline u32 pll_get_cctrl(u64 frac_start, bool gen_ssc)
{
if ((frac_start != 0) || gen_ssc)
return 0x34;
return 0x2;
}
static inline u32 pll_get_integloop_gain(u64 frac_start, u64 bclk, u32 ref_clk,
bool gen_ssc)
{
int digclk_divsel = bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2;
u64 base;
if ((frac_start != 0) || gen_ssc)
base = 0x3F;
else
base = 0xC4;
base <<= (digclk_divsel == 2 ? 1 : 0);
return base;
}
static inline u32 pll_get_pll_cmp(u64 fdata, unsigned long ref_clk)
{
u64 dividend = HDMI_PLL_CMP_CNT * fdata;
u32 divisor = ref_clk * 10;
u32 rem;
rem = do_div(dividend, divisor);
if (rem > (divisor >> 1))
dividend++;
return dividend - 1;
}
#define HDMI_REF_CLOCK_HZ ((u64)19200000)
#define HDMI_MHZ_TO_HZ ((u64)1000000)
static int pll_get_post_div(struct hdmi_8998_post_divider *pd, u64 bclk)
{
static const u32 ratio_list[] = {1, 2, 3, 4, 5, 6, 9, 10, 12, 15, 25};
static const u32 band_list[] = {0, 1, 2, 3};
u32 const sz_ratio = ARRAY_SIZE(ratio_list);
u32 const sz_band = ARRAY_SIZE(band_list);
u32 const cmp_cnt = 1024;
u32 const th_min = 500, th_max = 1000;
u32 half_rate_mode = 0;
u32 list_elements;
int optimal_index;
u32 i, j, k;
u32 found_hsclk_divsel = 0, found_vco_ratio;
u32 found_tx_band_sel;
u64 const min_freq = HDMI_VCO_MIN_FREQ, max_freq = HDMI_VCO_MAX_FREQ;
u64 freq_list[ARRAY_SIZE(ratio_list) * ARRAY_SIZE(band_list)];
u64 found_vco_freq;
u64 freq_optimal;
find_optimal_index:
freq_optimal = max_freq;
optimal_index = -1;
list_elements = 0;
for (i = 0; i < sz_ratio; i++) {
for (j = 0; j < sz_band; j++) {
u64 freq = div_u64(bclk, (1 << half_rate_mode));
freq *= (ratio_list[i] * (1 << band_list[j]));
freq_list[list_elements++] = freq;
}
}
for (k = 0; k < ARRAY_SIZE(freq_list); k++) {
u32 const clks_pll_div = 2, core_clk_div = 5;
u32 const rng1 = 16, rng2 = 8;
u32 th1, th2;
u64 core_clk, rvar1, rem;
core_clk = div_u64(freq_list[k],
ratio_list[k / sz_band] * clks_pll_div *
core_clk_div);
rvar1 = HDMI_REF_CLOCK_HZ * rng1 * HDMI_MHZ_TO_HZ;
rvar1 = div64_u64_rem(rvar1, (cmp_cnt * core_clk), &rem);
if (rem > ((cmp_cnt * core_clk) >> 1))
rvar1++;
th1 = rvar1;
rvar1 = HDMI_REF_CLOCK_HZ * rng2 * HDMI_MHZ_TO_HZ;
rvar1 = div64_u64_rem(rvar1, (cmp_cnt * core_clk), &rem);
if (rem > ((cmp_cnt * core_clk) >> 1))
rvar1++;
th2 = rvar1;
if (freq_list[k] >= min_freq &&
freq_list[k] <= max_freq) {
if ((th1 >= th_min && th1 <= th_max) ||
(th2 >= th_min && th2 <= th_max)) {
if (freq_list[k] <= freq_optimal) {
freq_optimal = freq_list[k];
optimal_index = k;
}
}
}
}
if (optimal_index == -1) {
if (!half_rate_mode) {
half_rate_mode = 1;
goto find_optimal_index;
} else {
return -EINVAL;
}
} else {
found_vco_ratio = ratio_list[optimal_index / sz_band];
found_tx_band_sel = band_list[optimal_index % sz_band];
found_vco_freq = freq_optimal;
}
switch (found_vco_ratio) {
case 1:
found_hsclk_divsel = 15;
break;
case 2:
found_hsclk_divsel = 0;
break;
case 3:
found_hsclk_divsel = 4;
break;
case 4:
found_hsclk_divsel = 8;
break;
case 5:
found_hsclk_divsel = 12;
break;
case 6:
found_hsclk_divsel = 1;
break;
case 9:
found_hsclk_divsel = 5;
break;
case 10:
found_hsclk_divsel = 2;
break;
case 12:
found_hsclk_divsel = 9;
break;
case 15:
found_hsclk_divsel = 13;
break;
case 25:
found_hsclk_divsel = 14;
break;
}
pd->vco_freq = found_vco_freq;
pd->tx_band_sel = found_tx_band_sel;
pd->vco_ratio = found_vco_ratio;
pd->hsclk_divsel = found_hsclk_divsel;
return 0;
}
static int pll_calculate(unsigned long pix_clk, unsigned long ref_clk,
struct hdmi_8998_phy_pll_reg_cfg *cfg)
{
struct hdmi_8998_post_divider pd;
u64 bclk;
u64 dec_start;
u64 frac_start;
u64 fdata;
u32 pll_divisor;
u32 rem;
u32 cpctrl;
u32 rctrl;
u32 cctrl;
u32 integloop_gain;
u32 pll_cmp;
int i, ret;
/* bit clk = 10 * pix_clk */
bclk = ((u64)pix_clk) * 10;
ret = pll_get_post_div(&pd, bclk);
if (ret)
return ret;
dec_start = pd.vco_freq;
pll_divisor = 4 * ref_clk;
do_div(dec_start, pll_divisor);
frac_start = pd.vco_freq * (1 << 20);
rem = do_div(frac_start, pll_divisor);
frac_start -= dec_start * (1 << 20);
if (rem > (pll_divisor >> 1))
frac_start++;
cpctrl = pll_get_cpctrl(frac_start, ref_clk, false);
rctrl = pll_get_rctrl(frac_start, false);
cctrl = pll_get_cctrl(frac_start, false);
integloop_gain = pll_get_integloop_gain(frac_start, bclk,
ref_clk, false);
fdata = pd.vco_freq;
do_div(fdata, pd.vco_ratio);
pll_cmp = pll_get_pll_cmp(fdata, ref_clk);
/* Convert these values to register specific values */
if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD)
cfg->com_svs_mode_clk_sel = 1;
else
cfg->com_svs_mode_clk_sel = 2;
cfg->com_hsclk_sel = (0x20 | pd.hsclk_divsel);
cfg->com_pll_cctrl_mode0 = cctrl;
cfg->com_pll_rctrl_mode0 = rctrl;
cfg->com_cp_ctrl_mode0 = cpctrl;
cfg->com_dec_start_mode0 = dec_start;
cfg->com_div_frac_start1_mode0 = (frac_start & 0xff);
cfg->com_div_frac_start2_mode0 = ((frac_start & 0xff00) >> 8);
cfg->com_div_frac_start3_mode0 = ((frac_start & 0xf0000) >> 16);
cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xff);
cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xf00) >> 8);
cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xff);
cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xff00) >> 8);
cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16);
cfg->com_lock_cmp_en = 0x0;
cfg->com_core_clk_en = 0x2c;
cfg->com_coreclk_div_mode0 = HDMI_CORECLK_DIV;
cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x5 : 0x4;
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++)
cfg->tx_lx_tx_band[i] = pd.tx_band_sel;
if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) {
cfg->tx_lx_tx_drv_lvl[0] = 0x0f;
cfg->tx_lx_tx_drv_lvl[1] = 0x0f;
cfg->tx_lx_tx_drv_lvl[2] = 0x0f;
cfg->tx_lx_tx_drv_lvl[3] = 0x0f;
cfg->tx_lx_tx_emp_post1_lvl[0] = 0x03;
cfg->tx_lx_tx_emp_post1_lvl[1] = 0x02;
cfg->tx_lx_tx_emp_post1_lvl[2] = 0x03;
cfg->tx_lx_tx_emp_post1_lvl[3] = 0x00;
cfg->tx_lx_pre_driver_1[0] = 0x00;
cfg->tx_lx_pre_driver_1[1] = 0x00;
cfg->tx_lx_pre_driver_1[2] = 0x00;
cfg->tx_lx_pre_driver_1[3] = 0x00;
cfg->tx_lx_pre_driver_2[0] = 0x1C;
cfg->tx_lx_pre_driver_2[1] = 0x1C;
cfg->tx_lx_pre_driver_2[2] = 0x1C;
cfg->tx_lx_pre_driver_2[3] = 0x00;
cfg->tx_lx_res_code_offset[0] = 0x03;
cfg->tx_lx_res_code_offset[1] = 0x00;
cfg->tx_lx_res_code_offset[2] = 0x00;
cfg->tx_lx_res_code_offset[3] = 0x03;
} else if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD) {
cfg->tx_lx_tx_drv_lvl[0] = 0x0f;
cfg->tx_lx_tx_drv_lvl[1] = 0x0f;
cfg->tx_lx_tx_drv_lvl[2] = 0x0f;
cfg->tx_lx_tx_drv_lvl[3] = 0x0f;
cfg->tx_lx_tx_emp_post1_lvl[0] = 0x03;
cfg->tx_lx_tx_emp_post1_lvl[1] = 0x03;
cfg->tx_lx_tx_emp_post1_lvl[2] = 0x03;
cfg->tx_lx_tx_emp_post1_lvl[3] = 0x00;
cfg->tx_lx_pre_driver_1[0] = 0x00;
cfg->tx_lx_pre_driver_1[1] = 0x00;
cfg->tx_lx_pre_driver_1[2] = 0x00;
cfg->tx_lx_pre_driver_1[3] = 0x00;
cfg->tx_lx_pre_driver_2[0] = 0x16;
cfg->tx_lx_pre_driver_2[1] = 0x16;
cfg->tx_lx_pre_driver_2[2] = 0x16;
cfg->tx_lx_pre_driver_2[3] = 0x18;
cfg->tx_lx_res_code_offset[0] = 0x03;
cfg->tx_lx_res_code_offset[1] = 0x00;
cfg->tx_lx_res_code_offset[2] = 0x00;
cfg->tx_lx_res_code_offset[3] = 0x00;
} else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) {
cfg->tx_lx_tx_drv_lvl[0] = 0x0f;
cfg->tx_lx_tx_drv_lvl[1] = 0x0f;
cfg->tx_lx_tx_drv_lvl[2] = 0x0f;
cfg->tx_lx_tx_drv_lvl[3] = 0x0f;
cfg->tx_lx_tx_emp_post1_lvl[0] = 0x05;
cfg->tx_lx_tx_emp_post1_lvl[1] = 0x05;
cfg->tx_lx_tx_emp_post1_lvl[2] = 0x05;
cfg->tx_lx_tx_emp_post1_lvl[3] = 0x00;
cfg->tx_lx_pre_driver_1[0] = 0x00;
cfg->tx_lx_pre_driver_1[1] = 0x00;
cfg->tx_lx_pre_driver_1[2] = 0x00;
cfg->tx_lx_pre_driver_1[3] = 0x00;
cfg->tx_lx_pre_driver_2[0] = 0x0E;
cfg->tx_lx_pre_driver_2[1] = 0x0E;
cfg->tx_lx_pre_driver_2[2] = 0x0E;
cfg->tx_lx_pre_driver_2[3] = 0x0E;
cfg->tx_lx_res_code_offset[0] = 0x00;
cfg->tx_lx_res_code_offset[1] = 0x00;
cfg->tx_lx_res_code_offset[2] = 0x00;
cfg->tx_lx_res_code_offset[3] = 0x00;
} else {
cfg->tx_lx_tx_drv_lvl[0] = 0x01;
cfg->tx_lx_tx_drv_lvl[1] = 0x01;
cfg->tx_lx_tx_drv_lvl[2] = 0x01;
cfg->tx_lx_tx_drv_lvl[3] = 0x00;
cfg->tx_lx_tx_emp_post1_lvl[0] = 0x00;
cfg->tx_lx_tx_emp_post1_lvl[1] = 0x00;
cfg->tx_lx_tx_emp_post1_lvl[2] = 0x00;
cfg->tx_lx_tx_emp_post1_lvl[3] = 0x00;
cfg->tx_lx_pre_driver_1[0] = 0x00;
cfg->tx_lx_pre_driver_1[1] = 0x00;
cfg->tx_lx_pre_driver_1[2] = 0x00;
cfg->tx_lx_pre_driver_1[3] = 0x00;
cfg->tx_lx_pre_driver_2[0] = 0x16;
cfg->tx_lx_pre_driver_2[1] = 0x16;
cfg->tx_lx_pre_driver_2[2] = 0x16;
cfg->tx_lx_pre_driver_2[3] = 0x18;
cfg->tx_lx_res_code_offset[0] = 0x00;
cfg->tx_lx_res_code_offset[1] = 0x00;
cfg->tx_lx_res_code_offset[2] = 0x00;
cfg->tx_lx_res_code_offset[3] = 0x00;
}
return 0;
}
static int hdmi_8998_pll_set_clk_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct hdmi_pll_8998 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
struct hdmi_8998_phy_pll_reg_cfg cfg = {};
int i, ret;
ret = pll_calculate(rate, parent_rate, &cfg);
if (ret) {
DRM_ERROR("PLL calculation failed\n");
return ret;
}
/* Initially shut down PHY */
hdmi_phy_write(phy, REG_HDMI_8998_PHY_PD_CTL, 0x0);
udelay(500);
/* Power up sequence */
hdmi_phy_write(phy, REG_HDMI_8998_PHY_PD_CTL, 0x1);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_RESETSM_CNTRL, 0x20);
hdmi_phy_write(phy, REG_HDMI_8998_PHY_CMN_CTRL, 0x6);
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_INTERFACE_SELECT_TX_BAND,
cfg.tx_lx_tx_band[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_CLKBUF_TERM_ENABLE,
0x1);
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_LANE_MODE,
0x20);
}
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_SYSCLK_BUF_ENABLE, 0x02);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x0B);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_SYSCLK_EN_SEL, 0x37);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_SYS_CLK_CTRL, 0x02);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_CLK_ENABLE1, 0x0E);
/* Bypass VCO calibration */
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_SVS_MODE_CLK_SEL,
cfg.com_svs_mode_clk_sel);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_PLL_IVCO, 0x07);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_VCO_TUNE_CTRL, 0x00);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_CLK_SEL, 0x30);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_HSCLK_SEL,
cfg.com_hsclk_sel);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_LOCK_CMP_EN,
cfg.com_lock_cmp_en);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_PLL_CCTRL_MODE0,
cfg.com_pll_cctrl_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_PLL_RCTRL_MODE0,
cfg.com_pll_rctrl_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_CP_CTRL_MODE0,
cfg.com_cp_ctrl_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_DEC_START_MODE0,
cfg.com_dec_start_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_DIV_FRAC_START1_MODE0,
cfg.com_div_frac_start1_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_DIV_FRAC_START2_MODE0,
cfg.com_div_frac_start2_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_DIV_FRAC_START3_MODE0,
cfg.com_div_frac_start3_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_INTEGLOOP_GAIN0_MODE0,
cfg.com_integloop_gain0_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_INTEGLOOP_GAIN1_MODE0,
cfg.com_integloop_gain1_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_LOCK_CMP1_MODE0,
cfg.com_lock_cmp1_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_LOCK_CMP2_MODE0,
cfg.com_lock_cmp2_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_LOCK_CMP3_MODE0,
cfg.com_lock_cmp3_mode0);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_VCO_TUNE_MAP, 0x00);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_CORE_CLK_EN,
cfg.com_core_clk_en);
hdmi_pll_write(pll, REG_HDMI_8998_PHY_QSERDES_COM_CORECLK_DIV_MODE0,
cfg.com_coreclk_div_mode0);
/* TX lanes setup (TX 0/1/2/3) */
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_DRV_LVL,
cfg.tx_lx_tx_drv_lvl[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_EMP_POST1_LVL,
cfg.tx_lx_tx_emp_post1_lvl[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_PRE_DRIVER_1,
cfg.tx_lx_pre_driver_1[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_PRE_DRIVER_2,
cfg.tx_lx_pre_driver_2[i]);
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_DRV_LVL_RES_CODE_OFFSET,
cfg.tx_lx_res_code_offset[i]);
}
hdmi_phy_write(phy, REG_HDMI_8998_PHY_MODE, cfg.phy_mode);
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_LANE_CONFIG,
0x10);
}
/*
* Ensure that vco configuration gets flushed to hardware before
* enabling the PLL
*/
wmb();
pll->rate = rate;
return 0;
}
static int hdmi_8998_phy_ready_status(struct hdmi_phy *phy)
{
u32 nb_tries = HDMI_PLL_POLL_MAX_READS;
unsigned long timeout = HDMI_PLL_POLL_TIMEOUT_US;
u32 status;
int phy_ready = 0;
while (nb_tries--) {
status = hdmi_phy_read(phy, REG_HDMI_8998_PHY_STATUS);
phy_ready = status & BIT(0);
if (phy_ready)
break;
udelay(timeout);
}
return phy_ready;
}
static int hdmi_8998_pll_lock_status(struct hdmi_pll_8998 *pll)
{
u32 status;
int nb_tries = HDMI_PLL_POLL_MAX_READS;
unsigned long timeout = HDMI_PLL_POLL_TIMEOUT_US;
int pll_locked = 0;
while (nb_tries--) {
status = hdmi_pll_read(pll,
REG_HDMI_8998_PHY_QSERDES_COM_C_READY_STATUS);
pll_locked = status & BIT(0);
if (pll_locked)
break;
udelay(timeout);
}
return pll_locked;
}
static int hdmi_8998_pll_prepare(struct clk_hw *hw)
{
struct hdmi_pll_8998 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
int i, ret = 0;
hdmi_phy_write(phy, REG_HDMI_8998_PHY_CFG, 0x1);
udelay(100);
hdmi_phy_write(phy, REG_HDMI_8998_PHY_CFG, 0x59);
udelay(100);
ret = hdmi_8998_pll_lock_status(pll);
if (!ret)
return ret;
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
hdmi_tx_chan_write(pll, i,
REG_HDMI_8998_PHY_TXn_LANE_CONFIG, 0x1F);
}
/* Ensure all registers are flushed to hardware */
wmb();
ret = hdmi_8998_phy_ready_status(phy);
if (!ret)
return ret;
/* Restart the retiming buffer */
hdmi_phy_write(phy, REG_HDMI_8998_PHY_CFG, 0x58);
udelay(1);
hdmi_phy_write(phy, REG_HDMI_8998_PHY_CFG, 0x59);
/* Ensure all registers are flushed to hardware */
wmb();
return 0;
}
static int hdmi_8998_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
req->rate = clamp_t(unsigned long, req->rate, HDMI_TMDS_CHAR_RATE_MIN_HZ,
HDMI_2_0_TMDS_CHAR_RATE_MAX_HZ);
return 0;
}
static unsigned long hdmi_8998_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct hdmi_pll_8998 *pll = hw_clk_to_pll(hw);
return pll->rate;
}
static void hdmi_8998_pll_unprepare(struct clk_hw *hw)
{
struct hdmi_pll_8998 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
hdmi_phy_write(phy, REG_HDMI_8998_PHY_PD_CTL, 0);
usleep_range(100, 150);
}
static int hdmi_8998_pll_is_enabled(struct clk_hw *hw)
{
struct hdmi_pll_8998 *pll = hw_clk_to_pll(hw);
u32 status;
int pll_locked;
status = hdmi_pll_read(pll, REG_HDMI_8998_PHY_QSERDES_COM_C_READY_STATUS);
pll_locked = status & BIT(0);
return pll_locked;
}
static const struct clk_ops hdmi_8998_pll_ops = {
.set_rate = hdmi_8998_pll_set_clk_rate,
.determine_rate = hdmi_8998_pll_determine_rate,
.recalc_rate = hdmi_8998_pll_recalc_rate,
.prepare = hdmi_8998_pll_prepare,
.unprepare = hdmi_8998_pll_unprepare,
.is_enabled = hdmi_8998_pll_is_enabled,
};
static const struct clk_init_data pll_init = {
.name = "hdmipll",
.ops = &hdmi_8998_pll_ops,
.parent_data = (const struct clk_parent_data[]){
{ .fw_name = "xo", .name = "xo_board" },
},
.num_parents = 1,
.flags = CLK_IGNORE_UNUSED,
};
int msm_hdmi_pll_8998_init(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct hdmi_pll_8998 *pll;
int ret, i;
pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
if (!pll)
return -ENOMEM;
pll->pdev = pdev;
pll->mmio_qserdes_com = msm_ioremap(pdev, "hdmi_pll");
if (IS_ERR(pll->mmio_qserdes_com)) {
DRM_DEV_ERROR(dev, "failed to map pll base\n");
return -ENOMEM;
}
for (i = 0; i < HDMI_NUM_TX_CHANNEL; i++) {
char name[32];
snprintf(name, sizeof(name), "hdmi_tx_l%d", i);
pll->mmio_qserdes_tx[i] = msm_ioremap(pdev, name);
if (IS_ERR(pll->mmio_qserdes_tx[i])) {
DRM_DEV_ERROR(dev, "failed to map pll base\n");
return -ENOMEM;
}
}
pll->clk_hw.init = &pll_init;
ret = devm_clk_hw_register(dev, &pll->clk_hw);
if (ret) {
DRM_DEV_ERROR(dev, "failed to register pll clock\n");
return ret;
}
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &pll->clk_hw);
if (ret) {
DRM_DEV_ERROR(dev, "failed to register clk provider: %d\n", ret);
return ret;
}
return 0;
}
static const char * const hdmi_phy_8998_reg_names[] = {
"vddio",
"vcca",
};
static const char * const hdmi_phy_8998_clk_names[] = {
"iface", "ref", "xo",
};
const struct hdmi_phy_cfg msm_hdmi_phy_8998_cfg = {
.type = MSM_HDMI_PHY_8998,
.reg_names = hdmi_phy_8998_reg_names,
.num_regs = ARRAY_SIZE(hdmi_phy_8998_reg_names),
.clk_names = hdmi_phy_8998_clk_names,
.num_clks = ARRAY_SIZE(hdmi_phy_8998_clk_names),
};
@@ -0,0 +1,141 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/delay.h>
#include "hdmi.h"
static void hdmi_phy_8x60_powerup(struct hdmi_phy *phy,
unsigned long pixclock)
{
/* De-serializer delay D/C for non-lbk mode: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG0,
HDMI_8x60_PHY_REG0_DESER_DEL_CTRL(3));
if (pixclock == 27000000) {
/* video_format == HDMI_VFRMT_720x480p60_16_9 */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG1,
HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) |
HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(3));
} else {
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG1,
HDMI_8x60_PHY_REG1_DTEST_MUX_SEL(5) |
HDMI_8x60_PHY_REG1_OUTVOL_SWING_CTRL(4));
}
/* No matter what, start from the power down mode: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_PWRGEN |
HDMI_8x60_PHY_REG2_PD_PLL |
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
/* Turn PowerGen on: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_PLL |
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
/* Turn PLL power on: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
/* Write to HIGH after PLL power down de-assert: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG3,
HDMI_8x60_PHY_REG3_PLL_ENABLE);
/* ASIC power on; PHY REG9 = 0 */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG9, 0);
/* Enable PLL lock detect, PLL lock det will go high after lock
* Enable the re-time logic
*/
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG12,
HDMI_8x60_PHY_REG12_RETIMING_EN |
HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN);
/* Drivers are on: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_DESER);
/* If the RX detector is needed: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
HDMI_8x60_PHY_REG2_PD_DESER);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG4, 0);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG5, 0);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG6, 0);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG7, 0);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG8, 0);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG9, 0);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG10, 0);
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG11, 0);
/* If we want to use lock enable based on counting: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG12,
HDMI_8x60_PHY_REG12_RETIMING_EN |
HDMI_8x60_PHY_REG12_PLL_LOCK_DETECT_EN |
HDMI_8x60_PHY_REG12_FORCE_LOCK);
}
static void hdmi_phy_8x60_powerdown(struct hdmi_phy *phy)
{
/* Assert RESET PHY from controller */
hdmi_phy_write(phy, REG_HDMI_PHY_CTRL,
HDMI_PHY_CTRL_SW_RESET);
udelay(10);
/* De-assert RESET PHY from controller */
hdmi_phy_write(phy, REG_HDMI_PHY_CTRL, 0);
/* Turn off Driver */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
udelay(10);
/* Disable PLL */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG3, 0);
/* Power down PHY, but keep RX-sense: */
hdmi_phy_write(phy, REG_HDMI_8x60_PHY_REG2,
HDMI_8x60_PHY_REG2_RCV_SENSE_EN |
HDMI_8x60_PHY_REG2_PD_PWRGEN |
HDMI_8x60_PHY_REG2_PD_PLL |
HDMI_8x60_PHY_REG2_PD_DRIVE_4 |
HDMI_8x60_PHY_REG2_PD_DRIVE_3 |
HDMI_8x60_PHY_REG2_PD_DRIVE_2 |
HDMI_8x60_PHY_REG2_PD_DRIVE_1 |
HDMI_8x60_PHY_REG2_PD_DESER);
}
static const char * const hdmi_phy_8x60_reg_names[] = {
"core-vdda",
};
static const char * const hdmi_phy_8x60_clk_names[] = {
"slave_iface",
};
const struct hdmi_phy_cfg msm_hdmi_phy_8x60_cfg = {
.type = MSM_HDMI_PHY_8x60,
.powerup = hdmi_phy_8x60_powerup,
.powerdown = hdmi_phy_8x60_powerdown,
.reg_names = hdmi_phy_8x60_reg_names,
.num_regs = ARRAY_SIZE(hdmi_phy_8x60_reg_names),
.clk_names = hdmi_phy_8x60_clk_names,
.num_clks = ARRAY_SIZE(hdmi_phy_8x60_clk_names),
};
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include "hdmi.h"
static void hdmi_phy_8x74_powerup(struct hdmi_phy *phy,
unsigned long pixclock)
{
hdmi_phy_write(phy, REG_HDMI_8x74_ANA_CFG0, 0x1b);
hdmi_phy_write(phy, REG_HDMI_8x74_ANA_CFG1, 0xf2);
hdmi_phy_write(phy, REG_HDMI_8x74_BIST_CFG0, 0x0);
hdmi_phy_write(phy, REG_HDMI_8x74_BIST_PATN0, 0x0);
hdmi_phy_write(phy, REG_HDMI_8x74_BIST_PATN1, 0x0);
hdmi_phy_write(phy, REG_HDMI_8x74_BIST_PATN2, 0x0);
hdmi_phy_write(phy, REG_HDMI_8x74_BIST_PATN3, 0x0);
hdmi_phy_write(phy, REG_HDMI_8x74_PD_CTRL1, 0x20);
}
static void hdmi_phy_8x74_powerdown(struct hdmi_phy *phy)
{
hdmi_phy_write(phy, REG_HDMI_8x74_PD_CTRL0, 0x7f);
}
static const char * const hdmi_phy_8x74_reg_names[] = {
"core-vdda",
"vddio",
};
static const char * const hdmi_phy_8x74_clk_names[] = {
"iface", "alt_iface"
};
const struct hdmi_phy_cfg msm_hdmi_phy_8x74_cfg = {
.type = MSM_HDMI_PHY_8x74,
.powerup = hdmi_phy_8x74_powerup,
.powerdown = hdmi_phy_8x74_powerdown,
.reg_names = hdmi_phy_8x74_reg_names,
.num_regs = ARRAY_SIZE(hdmi_phy_8x74_reg_names),
.clk_names = hdmi_phy_8x74_clk_names,
.num_clks = ARRAY_SIZE(hdmi_phy_8x74_clk_names),
};
@@ -0,0 +1,460 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*/
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include "hdmi.h"
struct hdmi_pll_8960 {
struct platform_device *pdev;
struct clk_hw clk_hw;
void __iomem *mmio;
unsigned long pixclk;
};
#define hw_clk_to_pll(x) container_of(x, struct hdmi_pll_8960, clk_hw)
/*
* HDMI PLL:
*
* To get the parent clock setup properly, we need to plug in hdmi pll
* configuration into common-clock-framework.
*/
struct pll_rate {
unsigned long rate;
int num_reg;
struct {
u32 val;
u32 reg;
} conf[32];
};
/* NOTE: keep sorted highest freq to lowest: */
static const struct pll_rate freqtbl[] = {
{ 154000000, 14, {
{ 0x08, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x0d, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x4d, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0x5e, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0x42, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
}
},
/* 1080p60/1080p50 case */
{ 148500000, 27, {
{ 0x02, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG },
{ 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG },
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
{ 0x76, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x01, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
{ 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG3 },
{ 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 },
{ 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 },
{ 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 },
{ 0xe6, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
}
},
{ 108000000, 13, {
{ 0x08, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x21, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x1c, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x49, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x49, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
}
},
/* 720p60/720p50/1080i60/1080i50/1080p24/1080p30/1080p25 */
{ 74250000, 8, {
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
{ 0x12, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x76, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0xe6, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
}
},
{ 74176000, 14, {
{ 0x18, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0xe5, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x0c, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0x7d, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0xbc, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
}
},
{ 65000000, 14, {
{ 0x18, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0xf9, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x8a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x0b, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x4b, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0x7b, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0x09, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
}
},
/* 480p60/480i60 */
{ 27030000, 18, {
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
{ 0x38, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
{ 0x20, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0xff, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x4e, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0xd7, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0x03, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
{ 0x2a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
}
},
/* 576p50/576i50 */
{ 27000000, 27, {
{ 0x32, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG },
{ 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG },
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
{ 0x7b, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x01, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
{ 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG3 },
{ 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 },
{ 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 },
{ 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 },
{ 0x2a, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x03, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
}
},
/* 640x480p60 */
{ 25200000, 27, {
{ 0x32, REG_HDMI_8960_PHY_PLL_REFCLK_CFG },
{ 0x02, REG_HDMI_8960_PHY_PLL_CHRG_PUMP_CFG },
{ 0x01, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG0 },
{ 0x33, REG_HDMI_8960_PHY_PLL_LOOP_FLT_CFG1 },
{ 0x2c, REG_HDMI_8960_PHY_PLL_IDAC_ADJ_CFG },
{ 0x06, REG_HDMI_8960_PHY_PLL_I_VI_KVCO_CFG },
{ 0x0a, REG_HDMI_8960_PHY_PLL_PWRDN_B },
{ 0x77, REG_HDMI_8960_PHY_PLL_SDM_CFG0 },
{ 0x4c, REG_HDMI_8960_PHY_PLL_SDM_CFG1 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG2 },
{ 0xc0, REG_HDMI_8960_PHY_PLL_SDM_CFG3 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SDM_CFG4 },
{ 0x9a, REG_HDMI_8960_PHY_PLL_SSC_CFG0 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG1 },
{ 0x00, REG_HDMI_8960_PHY_PLL_SSC_CFG2 },
{ 0x20, REG_HDMI_8960_PHY_PLL_SSC_CFG3 },
{ 0x10, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0 },
{ 0x1a, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1 },
{ 0x0d, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2 },
{ 0xf4, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG0 },
{ 0x02, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG1 },
{ 0x3b, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG2 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG3 },
{ 0x86, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG4 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG5 },
{ 0x33, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG6 },
{ 0x00, REG_HDMI_8960_PHY_PLL_VCOCAL_CFG7 },
}
},
};
static inline void pll_write(struct hdmi_pll_8960 *pll, u32 reg, u32 data)
{
writel(data, pll->mmio + reg);
}
static inline u32 pll_read(struct hdmi_pll_8960 *pll, u32 reg)
{
return readl(pll->mmio + reg);
}
static inline struct hdmi_phy *pll_get_phy(struct hdmi_pll_8960 *pll)
{
return platform_get_drvdata(pll->pdev);
}
static int hdmi_pll_enable(struct clk_hw *hw)
{
struct hdmi_pll_8960 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
int timeout_count, pll_lock_retry = 10;
unsigned int val;
DBG("");
/* Assert PLL S/W reset */
pll_write(pll, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x8d);
pll_write(pll, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG0, 0x10);
pll_write(pll, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG1, 0x1a);
/* Wait for a short time before de-asserting
* to allow the hardware to complete its job.
* This much of delay should be fine for hardware
* to assert and de-assert.
*/
udelay(10);
/* De-assert PLL S/W reset */
pll_write(pll, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x0d);
val = hdmi_phy_read(phy, REG_HDMI_8960_PHY_REG12);
val |= HDMI_8960_PHY_REG12_SW_RESET;
/* Assert PHY S/W reset */
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val);
val &= ~HDMI_8960_PHY_REG12_SW_RESET;
/*
* Wait for a short time before de-asserting to allow the hardware to
* complete its job. This much of delay should be fine for hardware to
* assert and de-assert.
*/
udelay(10);
/* De-assert PHY S/W reset */
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG2, 0x3f);
val = hdmi_phy_read(phy, REG_HDMI_8960_PHY_REG12);
val |= HDMI_8960_PHY_REG12_PWRDN_B;
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val);
/* Wait 10 us for enabling global power for PHY */
mb();
udelay(10);
val = pll_read(pll, REG_HDMI_8960_PHY_PLL_PWRDN_B);
val |= HDMI_8960_PHY_PLL_PWRDN_B_PLL_PWRDN_B;
val &= ~HDMI_8960_PHY_PLL_PWRDN_B_PD_PLL;
pll_write(pll, REG_HDMI_8960_PHY_PLL_PWRDN_B, val);
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG2, 0x80);
timeout_count = 1000;
while (--pll_lock_retry > 0) {
/* are we there yet? */
val = pll_read(pll, REG_HDMI_8960_PHY_PLL_STATUS0);
if (val & HDMI_8960_PHY_PLL_STATUS0_PLL_LOCK)
break;
udelay(1);
if (--timeout_count > 0)
continue;
/*
* PLL has still not locked.
* Do a software reset and try again
* Assert PLL S/W reset first
*/
pll_write(pll, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x8d);
udelay(10);
pll_write(pll, REG_HDMI_8960_PHY_PLL_LOCKDET_CFG2, 0x0d);
/*
* Wait for a short duration for the PLL calibration
* before checking if the PLL gets locked
*/
udelay(350);
timeout_count = 1000;
}
return 0;
}
static void hdmi_pll_disable(struct clk_hw *hw)
{
struct hdmi_pll_8960 *pll = hw_clk_to_pll(hw);
struct hdmi_phy *phy = pll_get_phy(pll);
unsigned int val;
DBG("");
val = hdmi_phy_read(phy, REG_HDMI_8960_PHY_REG12);
val &= ~HDMI_8960_PHY_REG12_PWRDN_B;
hdmi_phy_write(phy, REG_HDMI_8960_PHY_REG12, val);
val = pll_read(pll, REG_HDMI_8960_PHY_PLL_PWRDN_B);
val |= HDMI_8960_PHY_REG12_SW_RESET;
val &= ~HDMI_8960_PHY_REG12_PWRDN_B;
pll_write(pll, REG_HDMI_8960_PHY_PLL_PWRDN_B, val);
/* Make sure HDMI PHY/PLL are powered down */
mb();
}
static const struct pll_rate *find_rate(unsigned long rate)
{
int i;
for (i = 1; i < ARRAY_SIZE(freqtbl); i++)
if (rate > freqtbl[i].rate)
return &freqtbl[i - 1];
return &freqtbl[i - 1];
}
static unsigned long hdmi_pll_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct hdmi_pll_8960 *pll = hw_clk_to_pll(hw);
return pll->pixclk;
}
static int hdmi_pll_determine_rate(struct clk_hw *hw,
struct clk_rate_request *req)
{
const struct pll_rate *pll_rate = find_rate(req->rate);
req->rate = pll_rate->rate;
return 0;
}
static int hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct hdmi_pll_8960 *pll = hw_clk_to_pll(hw);
const struct pll_rate *pll_rate = find_rate(rate);
int i;
DBG("rate=%lu", rate);
for (i = 0; i < pll_rate->num_reg; i++)
pll_write(pll, pll_rate->conf[i].reg, pll_rate->conf[i].val);
pll->pixclk = rate;
return 0;
}
static const struct clk_ops hdmi_pll_ops = {
.enable = hdmi_pll_enable,
.disable = hdmi_pll_disable,
.recalc_rate = hdmi_pll_recalc_rate,
.determine_rate = hdmi_pll_determine_rate,
.set_rate = hdmi_pll_set_rate,
};
static const struct clk_parent_data hdmi_pll_parents[] = {
{ .fw_name = "pxo", .name = "pxo_board" },
};
static struct clk_init_data pll_init = {
.name = "hdmi_pll",
.ops = &hdmi_pll_ops,
.parent_data = hdmi_pll_parents,
.num_parents = ARRAY_SIZE(hdmi_pll_parents),
.flags = CLK_IGNORE_UNUSED,
};
int msm_hdmi_pll_8960_init(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct hdmi_pll_8960 *pll;
int i, ret;
/* sanity check: */
for (i = 0; i < (ARRAY_SIZE(freqtbl) - 1); i++)
if (WARN_ON(freqtbl[i].rate < freqtbl[i + 1].rate))
return -EINVAL;
pll = devm_kzalloc(dev, sizeof(*pll), GFP_KERNEL);
if (!pll)
return -ENOMEM;
pll->mmio = msm_ioremap(pdev, "hdmi_pll");
if (IS_ERR(pll->mmio)) {
DRM_DEV_ERROR(dev, "failed to map pll base\n");
return -ENOMEM;
}
pll->pdev = pdev;
pll->clk_hw.init = &pll_init;
ret = devm_clk_hw_register(dev, &pll->clk_hw);
if (ret < 0) {
DRM_DEV_ERROR(dev, "failed to register pll clock\n");
return ret;
}
ret = devm_of_clk_add_hw_provider(dev, of_clk_hw_simple_get, &pll->clk_hw);
if (ret) {
DRM_DEV_ERROR(dev, "%s: failed to register clk provider: %d\n", __func__, ret);
return ret;
}
return 0;
}