// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015 Free Electrons
* Copyright (C) 2015 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*/
#include <linux/component.h>
#include <linux/ioport.h>
#include <linux/media-bus-format.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/regmap.h>
#include <linux/reset.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_connector.h>
#include <drm/drm_crtc.h>
#include <drm/drm_encoder.h>
#include <drm/drm_modes.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <uapi/drm/drm_mode.h>
#include "sun4i_crtc.h"
#include "sun4i_dotclock.h"
#include "sun4i_drv.h"
#include "sun4i_lvds.h"
#include "sun4i_rgb.h"
#include "sun4i_tcon.h"
#include "sun6i_mipi_dsi.h"
#include "sun8i_tcon_top.h"
#include "sunxi_engine.h"
static struct drm_connector *sun4i_tcon_get_connector(const struct drm_encoder *encoder)
{
struct drm_connector *connector;
struct drm_connector_list_iter iter;
drm_connector_list_iter_begin(encoder->dev, &iter);
drm_for_each_connector_iter(connector, &iter)
if (connector->encoder == encoder) {
drm_connector_list_iter_end(&iter);
return connector;
}
drm_connector_list_iter_end(&iter);
return NULL;
}
static int sun4i_tcon_get_pixel_depth(const struct drm_encoder *encoder)
{
struct drm_connector *connector;
struct drm_display_info *info;
connector = sun4i_tcon_get_connector(encoder);
if (!connector)
return -EINVAL;
info = &connector->display_info;
if (info->num_bus_formats != 1)
return -EINVAL;
switch (info->bus_formats[0]) {
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
return 18;
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
return 24;
}
return -EINVAL;
}
static void sun4i_tcon_channel_set_status(struct sun4i_tcon *tcon, int channel,
bool enabled)
{
struct clk *clk;
switch (channel) {
case 0:
WARN_ON(!tcon->quirks->has_channel_0);
regmap_update_bits(tcon->regs, SUN4I_TCON0_CTL_REG,
SUN4I_TCON0_CTL_TCON_ENABLE,
enabled ? SUN4I_TCON0_CTL_TCON_ENABLE : 0);
clk = tcon->dclk;
break;
case 1:
WARN_ON(!tcon->quirks->has_channel_1);
regmap_update_bits(tcon->regs, SUN4I_TCON1_CTL_REG,
SUN4I_TCON1_CTL_TCON_ENABLE,
enabled ? SUN4I_TCON1_CTL_TCON_ENABLE : 0);
clk = tcon->sclk1;
break;
default:
DRM_WARN("Unknown channel... doing nothing\n");
return;
}
if (enabled) {
clk_prepare_enable(clk);
clk_rate_exclusive_get(clk);
} else {
clk_rate_exclusive_put(clk);
clk_disable_unprepare(clk);
}
}
static void sun4i_tcon_setup_lvds_phy(struct sun4i_tcon *tcon,
const struct drm_encoder *encoder)
{
regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
SUN4I_TCON0_LVDS_ANA0_CK_EN |
SUN4I_TCON0_LVDS_ANA0_REG_V |
SUN4I_TCON0_LVDS_ANA0_REG_C |
SUN4I_TCON0_LVDS_ANA0_EN_MB |
SUN4I_TCON0_LVDS_ANA0_PD |
SUN4I_TCON0_LVDS_ANA0_DCHS);
udelay(2); /* delay at least 1200 ns */
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG,
SUN4I_TCON0_LVDS_ANA1_INIT,
SUN4I_TCON0_LVDS_ANA1_INIT);
udelay(1); /* delay at least 120 ns */
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA1_REG,
SUN4I_TCON0_LVDS_ANA1_UPDATE,
SUN4I_TCON0_LVDS_ANA1_UPDATE);
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
SUN4I_TCON0_LVDS_ANA0_EN_MB,
SUN4I_TCON0_LVDS_ANA0_EN_MB);
}
static void sun6i_tcon_setup_lvds_phy(struct sun4i_tcon *tcon,
const struct drm_encoder *encoder)
{
u8 val;
regmap_write(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
SUN6I_TCON0_LVDS_ANA0_C(2) |
SUN6I_TCON0_LVDS_ANA0_V(3) |
SUN6I_TCON0_LVDS_ANA0_PD(2) |
SUN6I_TCON0_LVDS_ANA0_EN_LDO);
udelay(2);
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
SUN6I_TCON0_LVDS_ANA0_EN_MB,
SUN6I_TCON0_LVDS_ANA0_EN_MB);
udelay(2);
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
SUN6I_TCON0_LVDS_ANA0_EN_DRVC,
SUN6I_TCON0_LVDS_ANA0_EN_DRVC);
if (sun4i_tcon_get_pixel_depth(encoder) == 18)
val = 7;
else
val = 0xf;
regmap_write_bits(tcon->regs, SUN4I_TCON0_LVDS_ANA0_REG,
SUN6I_TCON0_LVDS_ANA0_EN_DRVD(0xf),
SUN6I_TCON0_LVDS_ANA0_EN_DRVD(val));
}
static void sun4i_tcon_lvds_set_status(struct sun4i_tcon *tcon,
const struct drm_encoder *encoder,
bool enabled)
{
if (enabled) {
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
SUN4I_TCON0_LVDS_IF_EN,
SUN4I_TCON0_LVDS_IF_EN);
if (tcon->quirks->setup_lvds_phy)
tcon->quirks->setup_lvds_phy(tcon, encoder);
} else {
regmap_update_bits(tcon->regs, SUN4I_TCON0_LVDS_IF_REG,
SUN4I_TCON0_LVDS_IF_EN, 0);
}
}
void sun4i_tcon_set_status(struct sun4i_tcon *tcon,
const struct drm_encoder *encoder,
bool enabled)
{
bool is_lvds = false;
int channel;
switch (encoder->encoder_type) {
case DRM_MODE_ENCODER_LVDS:
is_lvds = true;
fallthrough;
case DRM_MODE_ENCODER_DSI:
case DRM_MODE_ENCODER_NONE:
channel = 0;
break;
case DRM_MODE_ENCODER_TMDS:
case DRM_MODE_ENCODER_TVDAC:
channel = 1;
break;
default:
DRM_DEBUG_DRIVER("Unknown encoder type, doing nothing...\n");
return;
}
if (is_lvds && !enabled)
sun4i_tcon_lvds_set_s
|