// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
*/
#include <linux/device.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/media-bus-format.h>
#include <linux/regmap.h>
#include <drm/drm_probe_helper.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_edid.h>
#include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h>
#include <video/videomode.h>
#define I2C_MAIN 0
#define I2C_ADDR_MAIN 0x48
#define I2C_CEC_DSI 1
#define I2C_ADDR_CEC_DSI 0x49
#define I2C_MAX_IDX 2
struct lt8912 {
struct device *dev;
struct drm_bridge bridge;
struct drm_connector connector;
struct i2c_client *i2c_client[I2C_MAX_IDX];
struct regmap *regmap[I2C_MAX_IDX];
struct device_node *host_node;
struct mipi_dsi_device *dsi;
struct gpio_desc *gp_reset;
struct videomode mode;
struct regulator_bulk_data supplies[7];
u8 data_lanes;
bool is_power_on;
};
static int lt8912_write_init_config(struct lt8912 *lt)
{
const struct reg_sequence seq[] = {
/* Digital clock en*/
{0x08, 0xff},
{0x09, 0xff},
{0x0a, 0xff},
{0x0b, 0x7c},
{0x0c, 0xff},
{0x42, 0x04},
/*Tx Analog*/
{0x31, 0xb1},
{0x32, 0xb1},
{0x33, 0x0e},
{0x37, 0x00},
{0x38, 0x22},
{0x60, 0x82},
/*Cbus Analog*/
{0x39, 0x45},
{0x3a, 0x00},
{0x3b, 0x00},
/*HDMI Pll Analog*/
{0x44, 0x31},
{0x55, 0x44},
{0x57, 0x01},
{0x5a, 0x02},
/*MIPI Analog*/
{0x3e, 0xd6},
{0x3f, 0xd4},
{0x41, 0x3c},
{0xB2, 0x00},
};
return regmap_multi_reg_write(lt->regmap[I2C_MAIN], seq, ARRAY_SIZE(seq));
}
static int lt8912_write_mipi_basic_config(struct lt8912 *lt)
{
const struct reg_sequence seq[] = {
{0x12, 0x04},
{0x14, 0x00},
{0x15, 0x00},
{0x1a, 0x03},
{0x1b, 0x03},
};
return regmap_multi_reg_write(lt->regmap[I2C_CEC_DSI], seq, ARRAY_SIZE(seq));
};
static int lt8912_write_dds_config(struct lt8912 *lt)
{
const struct reg_sequence seq[] = {
{0x4e, 0xff},
{0x4f, 0x56},
{0x50, 0x69},
{0x51, 0x80},
{0x1f, 0x5e},
{0x20, 0x01},
{0x21, 0x2c},
{0x22, 0x01},
{0x23, 0xfa},
{0x24, 0x00},
{0x25, 0xc8},
{0x26, 0x00},
{0x27, 0x5e},
{0x28, 0x01},
{0x29, 0x2c},
{0x2a, 0x01},
{0x2b, 0xfa},
{0x2c, 0x00},
{0x2d, 0xc8},
{0x2e, 0x00},
{0x42, 0x64},
{0x43, 0x00},
{0x44, 0x04},
{0x45, 0x00},
{0x46, 0x59},
{0x47, 0x00},
{0x48, 0xf2},
{0x49, 0x06},
{0x4a, 0x00},
{0x4b, 0x72},
{0x4c, 0x45},
{0x4d, 0x00},
{0x52, 0x08},
{0x53, 0x00},
{0x54, 0xb2},
{0x55, 0x00},
{0x56, 0xe4},
{0x57, 0x0d},
{0x58, 0x00},
{0x59, 0xe4},
{0x5a, 0x8a},
{0x5b, 0x00},
{0x5c, 0x34},
{0x1e, 0x4f},
{0x51, 0x00},
};
return regmap_multi_reg_write(lt->regmap[I2C_CEC_DSI], seq, ARRAY_SIZE(seq));
}
static int lt8912_write_rxlogicres_config(struct lt8912 *lt)
{
int ret;
ret = regmap_write(lt->regmap[I2C_MAIN], 0x03, 0x7f);
usleep_range(10000, 20000);
ret |= regmap_write(lt->regmap[I2C_MAIN], 0x03, 0xff);
return ret;
};
/* enable LVDS output with some hardcoded configuration, not required for the HDMI output */
static int lt8912_write_lvds_config(struct lt8912 *lt)
{
const struct reg_sequence seq[] = {
// lvds power up
{0x44, 0x30},
{0x51, 0x05},
// core pll bypass
{0x50, 0x24}, // cp=50uA
{0x51, 0x2d}, // Pix_clk as reference, second order passive LPF PLL
{0x52, 0x04}, // loopdiv=0, use second-order PLL
{0x69, 0x0e}, // CP_PRESET_DIV_RATIO
{0x69, 0x8e},
{0x6a, 0x00},
{0x6c, 0xb8}, // RGD_CP_SOFT_K_EN,RGD_CP_SOFT_K[13:8]
{0x6b, 0x51},
{0x04, 0xfb}, // core pll reset
{0x04, 0xff},
// scaler bypass
{0x7f, 0x00}, // disable scaler
{0xa8, 0x13}, // 0x13: JEIDA, 0x33: VESA
{0x02<