You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1600 lines
42 KiB
1600 lines
42 KiB
/* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
|
|
*
|
|
* Copyright(c) 2016, Analogix Semiconductor. All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 and
|
|
* only version 2 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/types.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/component.h>
|
|
|
|
#include <drm/drmP.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_crtc.h>
|
|
#include <drm/drm_crtc_helper.h>
|
|
#include <drm/drm_dp_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
|
|
#include "analogix-anx7625.h"
|
|
#include <soc/qcom/boot_stats.h>
|
|
|
|
#define TX_P0 0x70
|
|
#define TX_P1 0x7A
|
|
#define TX_P2 0x72
|
|
#define RX_P0 0x7e
|
|
#define RX_P1 0x84
|
|
#define RX_P2 0x54
|
|
#define TCPC_INTERFACE 0x58
|
|
|
|
#define ReadReg(addr, offset) ({\
|
|
unsigned int buf;\
|
|
reg_read(anx7625, addr, offset, &buf);\
|
|
buf;\
|
|
})
|
|
|
|
#define Read_Reg(addr, offset, buf) ({\
|
|
reg_read(anx7625, addr, offset, buf);\
|
|
})
|
|
|
|
#define ReadBlockReg(addr, offset, len, dat)\
|
|
reg_read_block(anx7625, addr, offset, dat, len)
|
|
|
|
#define WriteReg(addr, offset, val) ({\
|
|
reg_write(anx7625, addr, offset, val);\
|
|
})
|
|
|
|
#define WriteBlockReg(addr, offset, len, dat)\
|
|
reg_write_block(anx7625, addr, offset, dat, len)
|
|
|
|
#define sp_write_reg_or(address, offset, mask) \
|
|
{ WriteReg(address, offset, ((unsigned char)ReadReg(address, offset) \
|
|
| (mask))); }
|
|
#define sp_write_reg_and(address, offset, mask) \
|
|
{ WriteReg(address, offset, ((unsigned char)ReadReg(address, offset) \
|
|
&(mask))); }
|
|
|
|
#define sp_write_reg_and_or(address, offset, and_mask, or_mask) \
|
|
{ WriteReg(address, offset, (((unsigned char)ReadReg(address, offset)) \
|
|
&and_mask) | (or_mask)); }
|
|
#define sp_write_reg_or_and(address, offset, or_mask, and_mask) \
|
|
{ WriteReg(address, offset, (((unsigned char)ReadReg(address, offset)) \
|
|
| or_mask) & (and_mask)); }
|
|
|
|
struct anx7625_platform_data {
|
|
struct gpio_desc *gpiod_cdet;
|
|
struct gpio_desc *gpiod_p_on;
|
|
struct gpio_desc *gpiod_reset;
|
|
|
|
int cdet_irq;
|
|
int intp_irq;
|
|
};
|
|
|
|
struct MIPI_Video_Format {
|
|
unsigned char timing_id;
|
|
unsigned char MIPI_video_type[32];
|
|
unsigned char MIPI_lane_count;
|
|
unsigned long MIPI_pixel_frequency; /*Hz*/
|
|
|
|
unsigned long M;
|
|
unsigned long N;
|
|
unsigned char post_divider;
|
|
/* bit[7:4]: DIFF_I_RATIO, bit[3:0]: DIFF_K_RATIO; i.e. 0x84:0x1B.
|
|
* These settings affect ODFC PLL locking range.
|
|
*/
|
|
unsigned char diff_ratio;
|
|
|
|
unsigned char compress_ratio;
|
|
unsigned char video_3D_type;
|
|
unsigned char *pps_reg;
|
|
const struct RegisterValueConfig *custom_reg0;
|
|
const struct RegisterValueConfig *custom_reg1;
|
|
|
|
struct TimingInfor {
|
|
unsigned int MIPI_HTOTAL;
|
|
unsigned int MIPI_HActive;
|
|
unsigned int MIPI_VTOTAL;
|
|
unsigned int MIPI_VActive;
|
|
|
|
unsigned int MIPI_H_Front_Porch;
|
|
unsigned int MIPI_H_Sync_Width;
|
|
unsigned int MIPI_H_Back_Porch;
|
|
|
|
|
|
unsigned int MIPI_V_Front_Porch;
|
|
unsigned int MIPI_V_Sync_Width;
|
|
unsigned int MIPI_V_Back_Porch;
|
|
} MIPI_inputl[2];
|
|
};
|
|
|
|
struct anx7625 {
|
|
struct drm_dp_aux aux;
|
|
struct drm_bridge bridge;
|
|
struct i2c_client *client;
|
|
struct edid *edid;
|
|
struct drm_dp_link link;
|
|
struct anx7625_platform_data pdata;
|
|
struct mutex lock;
|
|
int mode_idx;
|
|
|
|
u16 chipid;
|
|
|
|
bool powered;
|
|
bool enabled;
|
|
#ifdef CONFIG_PM_SLEEP
|
|
bool out_of_hibr;
|
|
#endif
|
|
int connected;
|
|
bool hpd_status;
|
|
bool skip_enable;
|
|
u8 sys_sta_bak;
|
|
|
|
unsigned char last_read_DevAddr;
|
|
};
|
|
|
|
static void Reg_Access_Conflict_Workaround(struct anx7625 *anx7625,
|
|
unsigned char DevAddr)
|
|
{
|
|
unsigned char RegAddr;
|
|
int ret = 0;
|
|
|
|
if (DevAddr != anx7625->last_read_DevAddr) {
|
|
switch (DevAddr) {
|
|
case 0x54:
|
|
case 0x72:
|
|
default:
|
|
RegAddr = 0x00;
|
|
break;
|
|
|
|
case 0x58:
|
|
RegAddr = 0x00;
|
|
break;
|
|
|
|
case 0x70:
|
|
RegAddr = 0xD1;
|
|
break;
|
|
|
|
case 0x7A:
|
|
RegAddr = 0x60;
|
|
break;
|
|
|
|
case 0x7E:
|
|
RegAddr = 0x39;
|
|
break;
|
|
|
|
case 0x84:
|
|
RegAddr = 0x7F;
|
|
break;
|
|
}
|
|
|
|
anx7625->client->addr = (DevAddr >> 1);
|
|
ret = i2c_smbus_write_byte_data(anx7625->client,
|
|
RegAddr, 0x00);
|
|
if (ret < 0)
|
|
pr_err("failed to write i2c addr=%x:%x...\n",
|
|
DevAddr, RegAddr);
|
|
anx7625->last_read_DevAddr = DevAddr;
|
|
}
|
|
}
|
|
|
|
static int reg_read(struct anx7625 *anx7625,
|
|
int addr, int offset, unsigned int *buf)
|
|
{
|
|
int ret;
|
|
|
|
Reg_Access_Conflict_Workaround(anx7625, addr);
|
|
anx7625->client->addr = (addr >> 1);
|
|
ret = i2c_smbus_read_byte_data(
|
|
anx7625->client, offset);
|
|
if (ret < 0)
|
|
pr_err("failed to read anx7625 %x:%x\n",
|
|
addr, offset);
|
|
|
|
*buf = ret;
|
|
return (ret < 0);
|
|
}
|
|
|
|
static int reg_write(struct anx7625 *anx7625,
|
|
int addr, int offset, unsigned int val)
|
|
{
|
|
int ret;
|
|
|
|
Reg_Access_Conflict_Workaround(anx7625, addr);
|
|
anx7625->client->addr = (addr >> 1);
|
|
ret = i2c_smbus_write_byte_data(
|
|
anx7625->client, offset, val);
|
|
if (ret < 0)
|
|
pr_err("failed to write anx7625 %x:%x\n",
|
|
addr, offset);
|
|
return 0;
|
|
}
|
|
|
|
static int reg_read_block(struct anx7625 *anx7625,
|
|
int addr, int offset, u8 *buf, int len)
|
|
{
|
|
int ret;
|
|
|
|
Reg_Access_Conflict_Workaround(anx7625, addr);
|
|
anx7625->client->addr = (addr >> 1);
|
|
ret = i2c_smbus_read_i2c_block_data(
|
|
anx7625->client, offset, len, buf);
|
|
if (ret < 0)
|
|
pr_err("failed to read anx7625 %x:%x\n",
|
|
addr, offset);
|
|
return 0;
|
|
}
|
|
|
|
static int reg_write_block(struct anx7625 *anx7625,
|
|
int addr, int offset, u8 *buf, int len)
|
|
{
|
|
int ret;
|
|
|
|
Reg_Access_Conflict_Workaround(anx7625, addr);
|
|
anx7625->client->addr = (addr >> 1);
|
|
ret = i2c_smbus_write_i2c_block_data(
|
|
anx7625->client, offset, len, buf);
|
|
if (ret < 0)
|
|
pr_err("failed to write anx7625 %x:%x\n",
|
|
addr, offset);
|
|
return 0;
|
|
}
|
|
|
|
#define mipi_pixel_frequency(id) \
|
|
mipi_video_timing_table[id].MIPI_pixel_frequency
|
|
#define mipi_lane_count(id) \
|
|
mipi_video_timing_table[id].MIPI_lane_count
|
|
#define mipi_m_value(id) \
|
|
mipi_video_timing_table[id].M
|
|
#define mipi_n_value(id) \
|
|
mipi_video_timing_table[id].N
|
|
#define mipi_post_divider(id) \
|
|
mipi_video_timing_table[id].post_divider
|
|
#define mipi_diff_ratio(id) \
|
|
mipi_video_timing_table[id].diff_ratio
|
|
#define mipi_compress_ratio(id) \
|
|
mipi_video_timing_table[id].compress_ratio
|
|
|
|
#define mipi_original_htotal(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_HTOTAL
|
|
#define mipi_original_hactive(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_HActive
|
|
#define mipi_original_vtotal(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_VTOTAL
|
|
#define mipi_original_vactive(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_VActive
|
|
#define mipi_original_hfp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_H_Front_Porch
|
|
#define mipi_original_hsw(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_H_Sync_Width
|
|
#define mipi_original_hbp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_H_Back_Porch
|
|
#define mipi_original_vfp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_V_Front_Porch
|
|
#define mipi_original_vsw(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_V_Sync_Width
|
|
#define mipi_original_vbp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[0].MIPI_V_Back_Porch
|
|
|
|
#define mipi_decompressed_htotal(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_HTOTAL
|
|
#define mipi_decompressed_hactive(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_HActive
|
|
#define mipi_decompressed_vtotal(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_VTOTAL
|
|
#define mipi_decompressed_vactive(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_VActive
|
|
#define mipi_decompressed_hfp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_H_Front_Porch
|
|
#define mipi_decompressed_hsw(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_H_Sync_Width
|
|
#define mipi_decompressed_hbp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_H_Back_Porch
|
|
#define mipi_decompressed_vfp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_V_Front_Porch
|
|
#define mipi_decompressed_vsw(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_V_Sync_Width
|
|
#define mipi_decompressed_vbp(id) \
|
|
mipi_video_timing_table[id].MIPI_inputl[1].MIPI_V_Back_Porch
|
|
|
|
#define video_3d(id) mipi_video_timing_table[id].video_3D_type
|
|
|
|
static unsigned char PPS_4K[] = { /*VC707 (DPI+DSC)*/
|
|
0x11, 0x00, 0x00, 0x89, 0x10, 0x80, 0x08, 0x70,
|
|
0x0f, 0x00, 0x00, 0x08, 0x07, 0x80, 0x07, 0x80,
|
|
0x02, 0x00, 0x04, 0xc0, 0x00, 0x20, 0x01, 0x1e,
|
|
0x00, 0x1a, 0x00, 0x0c, 0x0d, 0xb7, 0x03, 0x94,
|
|
0x18, 0x00, 0x10, 0xf0, 0x03, 0x0c, 0x20, 0x00,
|
|
0x06, 0x0b, 0x0b, 0x33, 0x0e, 0x1c, 0x2a, 0x38,
|
|
0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b,
|
|
0x7d, 0x7e, 0x01, 0x02, 0x01, 0x00, 0x09, 0x40,
|
|
0x09, 0xbe, 0x19, 0xfc, 0x19, 0xfa, 0x19, 0xf8,
|
|
0x1a, 0x38, 0x1a, 0x78, 0x1a, 0xb6, 0x2a, 0xf6,
|
|
0x2b, 0x34, 0x2b, 0x74, 0x3b, 0x74, 0x6b, 0xf4,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
static unsigned char PPS_AR[] = {/*1440x2560@70*/
|
|
0x11, 0x00, 0x00, 0x89, 0x30, 0x80, 0x0A, 0x00,
|
|
0x05, 0xA0, 0x00, 0x10, 0x05, 0xa0, 0x05, 0xa0,
|
|
0x02, 0x00, 0x03, 0xd0, 0x00, 0x20, 0x02, 0x33,
|
|
0x00, 0x14, 0x00, 0x0c, 0x06, 0x67, 0x02, 0x63,
|
|
0x18, 0x00, 0x10, 0xf0, 0x03, 0x0c, 0x20, 0x00,
|
|
0x06, 0x0b, 0x0b, 0x33, 0x0e, 0x1c, 0x2a, 0x38,
|
|
0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7b,
|
|
0x7d, 0x7e, 0x01, 0x02, 0x01, 0x00, 0x09, 0x40,
|
|
0x09, 0xbe, 0x19, 0xfc, 0x19, 0xfa, 0x19, 0xf8,
|
|
0x1a, 0x38, 0x1a, 0x78, 0x1a, 0xb6, 0x2a, 0xf6,
|
|
0x2b, 0x34, 0x2b, 0x74, 0x3b, 0x74, 0x6b, 0xf4,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
static unsigned char PPS_Custom[] = {
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
|
};
|
|
|
|
static const struct RegisterValueConfig Bit_Matrix[] = {
|
|
{TX_P2, AUDIO_CONTROL_REGISTER, 0x80},
|
|
{TX_P2, VIDEO_BIT_MATRIX_12, 0x18},
|
|
{TX_P2, VIDEO_BIT_MATRIX_13, 0x19},
|
|
{TX_P2, VIDEO_BIT_MATRIX_14, 0x1a},
|
|
{TX_P2, VIDEO_BIT_MATRIX_15, 0x1b},
|
|
{TX_P2, VIDEO_BIT_MATRIX_16, 0x1c},
|
|
{TX_P2, VIDEO_BIT_MATRIX_17, 0x1d},
|
|
{TX_P2, VIDEO_BIT_MATRIX_18, 0x1e},
|
|
{TX_P2, VIDEO_BIT_MATRIX_19, 0x1f},
|
|
{TX_P2, VIDEO_BIT_MATRIX_20, 0x20},
|
|
{TX_P2, VIDEO_BIT_MATRIX_21, 0x21},
|
|
{TX_P2, VIDEO_BIT_MATRIX_22, 0x22},
|
|
{TX_P2, VIDEO_BIT_MATRIX_23, 0x23},
|
|
{0x00, 0x00, 0x00}
|
|
};
|
|
|
|
static struct MIPI_Video_Format mipi_video_timing_table[] = {
|
|
/* lane_count--pixel_clk-----M---N--div- */
|
|
/* -diff--compr--3d--table--custom0--custom1*/
|
|
/* original timing */
|
|
/* total-H active-Vtotal-V active-HFP-HSW-HBP-VFP-VSW-VBP*/
|
|
/* decompressed timing */
|
|
/* tota-H active-Vtotal-V active-HFP-HSW-HBP-VFP-VSW-VBP*/
|
|
{
|
|
0, "720x480@60", 3, 27000000, 0xC00000, 0x100000, 0x0B,
|
|
0x3B, 0, VIDEO_3D_NONE, NULL, Bit_Matrix, NULL,
|
|
{ { 858, 720, 525, 480, 16, 60, 62, 10, 6, 29 } }
|
|
},
|
|
{
|
|
1, "1280X720P@60", 3, 74250000, 0xB00000, 0x080000, 0x07,
|
|
0x3A, 0, VIDEO_3D_NONE, NULL, Bit_Matrix, NULL,
|
|
{ { 1650, 1280, 750, 720, 110, 40, 220, 5, 5, 20 } }
|
|
},
|
|
{
|
|
2, "1920x1080p@30", 3, 74000000, 0x940000, 0x06C000, 0x07,
|
|
0x3B, 0, VIDEO_3D_NONE, NULL, Bit_Matrix, NULL,
|
|
{ { 2200, 1920, 1125, 1080, 88, 44, 148, 4, 5, 36 } }
|
|
},
|
|
{
|
|
3, "1920x1080p@60", 3, 148500000, 0xB00000, 0x080000, 0x03,
|
|
0x37, 0, VIDEO_3D_NONE, NULL, Bit_Matrix, NULL,
|
|
{ { 2200, 1920, 1125, 1080, 88, 44, 148, 4, 5, 36 } }
|
|
},
|
|
/*MTK 4K24 DPI*/
|
|
{
|
|
4, "3840x2160@24", 3, 297000000, 0xB00000, 0x080000, 0x01,
|
|
0x37, 3, VIDEO_3D_NONE, PPS_4K, Bit_Matrix, NULL,
|
|
{ { 1650, 1280, 2250, 2160, 242, 30, 98, 8, 10, 72 },
|
|
{ 4950, 3840, 2250, 2160, 726, 90, 294, 8, 10, 72 }
|
|
}
|
|
},
|
|
/*MTK 4K30 DPI*/
|
|
{
|
|
5, "3840x2160@30", 3, 297000000, 0xB00000, 0x080000, 0x01,
|
|
0x37, 3, VIDEO_3D_NONE, PPS_4K, Bit_Matrix, NULL,
|
|
{ { 1474, 1280, 2250, 2160, 66, 30, 98, 8, 10, 72 },
|
|
{ 4422, 3840, 2250, 2160, 198, 90, 294, 8, 10, 72 }
|
|
}
|
|
},
|
|
|
|
{/*DSI*/
|
|
6, "720x480@60", 3, 27000000, 0xC00000, 0x100000, 0x0B,
|
|
0x3B, 0, VIDEO_3D_NONE, NULL, NULL, NULL,
|
|
{ { 858, 720, 525, 480, 16, 60, 62, 10, 6, 29 } }
|
|
},
|
|
{/*DSI*/
|
|
7, "1280X720P@60", 3, 74250000, 0xB00000, 0x080000, 0x07,
|
|
0x3A, 0, VIDEO_3D_NONE, NULL, NULL, NULL,
|
|
{ { 1650, 1280, 750, 720, 110, 40, 220, 5, 5, 20 } }
|
|
},
|
|
{/*DSI*/
|
|
8, "1920x1080p@30", 3, 74250000, 0xB00000, 0x080000, 0x07,
|
|
0x3B, 0, VIDEO_3D_NONE, NULL, NULL, NULL,
|
|
{ { 2200, 1920, 1125, 1080, 88, 44, 148, 4, 5, 36 } }
|
|
},
|
|
{/*DSI*/
|
|
9, "1920x1080p@60", 3, 148500000, 0xB00000, 0x080000, 0x03,
|
|
0x37, 0, VIDEO_3D_NONE, NULL, NULL, NULL,
|
|
{ { 2200, 1920, 1125, 1080, 88, 44, 148, 4, 5, 36 } }
|
|
},
|
|
|
|
/* 3840x2160p24 - MTK X30 -DSI*/
|
|
{/*DSI*/
|
|
10, "3840x2160p24", 3, 268176696, 0xAA808D, 0x089544, 0x01,
|
|
0x37, 3, VIDEO_3D_NONE, PPS_4K, NULL, NULL,
|
|
{ { 1650, 1280, 2250, 2160, 242, 30, 98, 8, 10, 72 },
|
|
{ 4950, 3840, 2250, 2160, 726, 90, 294, 8, 10, 72 }
|
|
}
|
|
},
|
|
/* 3840x2160p30 3:1 DSC - MTK X30 -DSI*/
|
|
{/*DSI*/
|
|
11, "1280x2160p30", 3, 297000000, 0xA7B3AB, 0x07A120, 0x01,
|
|
0x37, 3, VIDEO_3D_NONE, PPS_4K, NULL, NULL,
|
|
{ { 1467, 1280, 2250, 2160, 66, 30, 91, 8, 10, 72 },
|
|
{ 4400, 3840, 2250, 2160, 198, 90, 272, 8, 10, 72 }
|
|
}
|
|
},
|
|
|
|
{
|
|
12, "1440X2560P@70", 3, 285000000, 0xB00000, 0x080000, 0x01,
|
|
0x37, 3, VIDEO_3D_NONE, PPS_AR, Bit_Matrix, NULL,
|
|
{ {524, 480, 2576, 2560, 24, 10, 12, 6, 8, 2 },
|
|
{1580, 1440, 2576, 2560, 80, 20, 40, 6, 8, 2}
|
|
}
|
|
},
|
|
{
|
|
13, "********@60", 0, 0, 0, 0, 0,
|
|
0, 0, VIDEO_3D_NONE, NULL, NULL, NULL,
|
|
{ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }
|
|
},
|
|
{
|
|
14, "********@60", 0, 0, 0, 0, 0,
|
|
0, 0, VIDEO_3D_NONE, NULL, NULL, NULL,
|
|
{ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } }
|
|
},
|
|
{
|
|
15, "custom@DPI/DSI", 3, 297000000, 0xB00000, 0x080000, 0x01,
|
|
0x37, 3, VIDEO_3D_NONE, PPS_Custom, Bit_Matrix, NULL,
|
|
{ { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
|
|
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
|
|
}
|
|
},
|
|
};
|
|
|
|
static inline struct anx7625 *bridge_to_anx7625(struct drm_bridge *bridge)
|
|
{
|
|
return container_of(bridge, struct anx7625, bridge);
|
|
}
|
|
|
|
#define write_dpcd_addr(addrh, addrm, addrl) \
|
|
do { \
|
|
unsigned int temp; \
|
|
if (ReadReg(RX_P0, AP_AUX_ADDR_7_0) != (unchar)addrl) \
|
|
WriteReg(RX_P0, AP_AUX_ADDR_7_0, (unchar)addrl); \
|
|
if (ReadReg(RX_P0, AP_AUX_ADDR_15_8) != (unchar)addrm) \
|
|
WriteReg(RX_P0, AP_AUX_ADDR_15_8, (unchar)addrm); \
|
|
Read_Reg(RX_P0, AP_AUX_ADDR_19_16, &temp); \
|
|
if ((unchar)(temp & 0x0F) != ((unchar)addrh & 0x0F)) \
|
|
WriteReg(RX_P0, AP_AUX_ADDR_19_16, \
|
|
(temp & 0xF0) | ((unchar)addrh)); \
|
|
} while (0)
|
|
|
|
static void wait_aux_op_finish(struct anx7625 *anx7625, unchar *err_flag)
|
|
{
|
|
unchar cnt;
|
|
uint c;
|
|
|
|
*err_flag = 0;
|
|
cnt = 150;
|
|
while (ReadReg(RX_P0, AP_AUX_CTRL_STATUS) & AP_AUX_CTRL_OP_EN) {
|
|
usleep_range(2000, 2100);
|
|
if ((cnt--) == 0) {
|
|
TRACE("aux operate failed!\n");
|
|
*err_flag = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
Read_Reg(RX_P0, AP_AUX_CTRL_STATUS, &c);
|
|
if (c & 0x0F) {
|
|
TRACE1("wait aux operation status %02x\n", (uint)c);
|
|
*err_flag = 1;
|
|
}
|
|
}
|
|
|
|
unchar sp_tx_aux_dpcdread_bytes(struct anx7625 *anx7625,
|
|
unchar addrh, unchar addrm, unchar addrl,
|
|
unchar cCount, unchar *pBuf)
|
|
{
|
|
uint c, i;
|
|
unchar bOK;
|
|
|
|
/*command and length*/
|
|
c = ((cCount - 1) << 4) | 0x09;
|
|
WriteReg(RX_P0, AP_AUX_COMMAND, c);
|
|
/*address*/
|
|
write_dpcd_addr(addrh, addrm, addrl);
|
|
/*aux en*/
|
|
sp_write_reg_or(RX_P0, AP_AUX_CTRL_STATUS, AP_AUX_CTRL_OP_EN);
|
|
usleep_range(2000, 2100);
|
|
/* TRACE3("auxch addr = 0x%02x%02x%02x\n", addrh,addrm,addrl);*/
|
|
wait_aux_op_finish(anx7625, &bOK);
|
|
if (bOK == AUX_ERR) {
|
|
TRACE("aux read failed\n");
|
|
return AUX_ERR;
|
|
}
|
|
|
|
for (i = 0; i < cCount; i++) {
|
|
Read_Reg(RX_P0, AP_AUX_BUFF_START + i, &c);
|
|
*(pBuf + i) = c;
|
|
/*TRACE2("Buf[%d] = 0x%02x\n", (uint)i, *(pBuf + i));*/
|
|
if (i >= MAX_BUF_CNT)
|
|
break;
|
|
}
|
|
|
|
return AUX_OK;
|
|
}
|
|
|
|
unchar sp_tx_aux_dpcdwrite_bytes(struct anx7625 *anx7625,
|
|
unchar addrh, unchar addrm, unchar addrl,
|
|
unchar cCount, unchar *pBuf)
|
|
{
|
|
unchar c, i, ret;
|
|
|
|
/*command and length*/
|
|
c = ((cCount - 1) << 4) | 0x08;
|
|
WriteReg(RX_P0, AP_AUX_COMMAND, c);
|
|
/*address*/
|
|
write_dpcd_addr(addrh, addrm, addrl);
|
|
/*data*/
|
|
for (i = 0; i < cCount; i++) {
|
|
c = *pBuf;
|
|
pBuf++;
|
|
WriteReg(RX_P0, AP_AUX_BUFF_START + i, c);
|
|
|
|
if (i >= 15)
|
|
break;
|
|
}
|
|
/*aux en*/
|
|
sp_write_reg_or(RX_P0, AP_AUX_CTRL_STATUS, AP_AUX_CTRL_OP_EN);
|
|
wait_aux_op_finish(anx7625, &ret);
|
|
TRACE("aux write done\n");
|
|
return ret;
|
|
}
|
|
|
|
static unchar sp_tx_aux_wr(struct anx7625 *anx7625, unchar offset)
|
|
{
|
|
unchar c;
|
|
|
|
WriteReg(RX_P0, AP_AUX_BUFF_START, offset);
|
|
WriteReg(RX_P0, AP_AUX_COMMAND, 0x04);
|
|
sp_write_reg_or(RX_P0, AP_AUX_CTRL_STATUS, AP_AUX_CTRL_OP_EN);
|
|
wait_aux_op_finish(anx7625, &c);
|
|
|
|
return c;
|
|
}
|
|
|
|
static unchar sp_tx_aux_rd(struct anx7625 *anx7625, unchar len_cmd)
|
|
{
|
|
unchar c;
|
|
|
|
WriteReg(RX_P0, AP_AUX_COMMAND, len_cmd);
|
|
sp_write_reg_or(RX_P0, AP_AUX_CTRL_STATUS, AP_AUX_CTRL_OP_EN);
|
|
wait_aux_op_finish(anx7625, &c);
|
|
|
|
return c;
|
|
}
|
|
|
|
static ssize_t anx7625_aux_transfer(struct drm_dp_aux *aux,
|
|
struct drm_dp_aux_msg *msg)
|
|
{
|
|
struct anx7625 *anx7625 = container_of(aux, struct anx7625, aux);
|
|
u8 *buffer = msg->buffer;
|
|
int err = 0;
|
|
|
|
if (!buffer || !msg->size)
|
|
return 0;
|
|
|
|
if ((msg->request & DP_AUX_NATIVE_READ) == DP_AUX_NATIVE_READ) {
|
|
err = sp_tx_aux_dpcdread_bytes(anx7625,
|
|
(msg->address >> 16) & 0xff,
|
|
(msg->address >> 8) & 0xff,
|
|
(msg->address) & 0xff,
|
|
msg->size,
|
|
buffer);
|
|
} else if ((msg->request & DP_AUX_NATIVE_WRITE) ==
|
|
DP_AUX_NATIVE_WRITE) {
|
|
err = sp_tx_aux_dpcdwrite_bytes(anx7625,
|
|
(msg->address >> 16) & 0xff,
|
|
(msg->address >> 8) & 0xff,
|
|
(msg->address) & 0xff,
|
|
msg->size,
|
|
buffer);
|
|
} else if ((msg->request & DP_AUX_I2C_READ) == DP_AUX_I2C_READ) {
|
|
err = sp_tx_aux_rd(anx7625, ((msg->size - 1) << 4) | 0x01);
|
|
if (!err) {
|
|
ReadBlockReg(RX_P0, AP_AUX_BUFF_START,
|
|
msg->size, buffer);
|
|
}
|
|
} else if ((msg->request & ~DP_AUX_I2C_MOT) == DP_AUX_I2C_WRITE) {
|
|
WriteReg(RX_P0, AP_AUX_ADDR_7_0, (msg->address) & 0xff);
|
|
WriteReg(RX_P0, AP_AUX_ADDR_15_8, 0);
|
|
sp_write_reg_and(RX_P0, AP_AUX_ADDR_19_16, 0xf0);
|
|
err = sp_tx_aux_wr(anx7625, buffer[0]);
|
|
}
|
|
|
|
msg->reply = DP_AUX_I2C_REPLY_ACK;
|
|
|
|
if (err)
|
|
pr_err("anx7625 aux transfer failed %d\n", err);
|
|
|
|
return msg->size;
|
|
}
|
|
|
|
static int anx7625_enable_interrupts(struct anx7625 *anx7625)
|
|
{
|
|
/* enable all interrupts */
|
|
WriteReg(RX_P0, INTERFACE_INTR_MASK, 0x7f);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int anx7625_disable_interrupts(struct anx7625 *anx7625)
|
|
{
|
|
/* disable all interrupts */
|
|
WriteReg(RX_P0, INTERFACE_INTR_MASK, 0xff);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int anx7625_poweroff(struct anx7625 *anx7625)
|
|
{
|
|
struct anx7625_platform_data *pdata = &anx7625->pdata;
|
|
|
|
if (!anx7625->powered)
|
|
return 0;
|
|
|
|
anx7625_disable_interrupts(anx7625);
|
|
|
|
gpiod_set_value_cansleep(pdata->gpiod_reset, 0);
|
|
usleep_range(1000, 2000);
|
|
|
|
gpiod_set_value_cansleep(pdata->gpiod_p_on, 0);
|
|
usleep_range(1000, 2000);
|
|
|
|
anx7625->powered = false;
|
|
return 0;
|
|
}
|
|
|
|
static int anx7625_poweron(struct anx7625 *anx7625)
|
|
{
|
|
struct anx7625_platform_data *pdata = &anx7625->pdata;
|
|
|
|
if (anx7625->powered)
|
|
return 0;
|
|
|
|
gpiod_set_value_cansleep(pdata->gpiod_p_on, 1);
|
|
usleep_range(10000, 11000);
|
|
|
|
gpiod_set_value_cansleep(pdata->gpiod_reset, 1);
|
|
usleep_range(10000, 11000);
|
|
|
|
/* setup clock */
|
|
WriteReg(RX_P0, XTAL_FRQ_SEL, XTAL_FRQ_27M);
|
|
|
|
/*First, reset main ocm*/
|
|
WriteReg(RX_P0, 0x88, 0x40);
|
|
|
|
/* disable PD */
|
|
WriteReg(RX_P0, AP_AV_STATUS, AP_DISABLE_PD);
|
|
|
|
/*after configuration, start main ocm running.*/
|
|
WriteReg(RX_P0, 0x88, 0x00);
|
|
|
|
/* enable interrupt */
|
|
anx7625_enable_interrupts(anx7625);
|
|
|
|
anx7625->powered = true;
|
|
return 0;
|
|
}
|
|
|
|
static void DSI_Video_Timing_Configuration(struct anx7625 *anx7625)
|
|
{
|
|
int table_id = anx7625->mode_idx;
|
|
|
|
/*configure clock*/
|
|
WriteReg(RX_P0, PIXEL_CLOCK_L,
|
|
(mipi_pixel_frequency(table_id) / 1000000) & 0xFF);
|
|
WriteReg(RX_P0, PIXEL_CLOCK_H,
|
|
(mipi_pixel_frequency(table_id) / 1000000) >> 8);
|
|
/*lane count*/
|
|
sp_write_reg_and(RX_P1, MIPI_LANE_CTRL_0, 0xfc);
|
|
sp_write_reg_or(RX_P1, MIPI_LANE_CTRL_0,
|
|
mipi_lane_count(table_id));
|
|
/*Htotal*/
|
|
WriteReg(RX_P2, HORIZONTAL_TOTAL_PIXELS_L,
|
|
mipi_original_htotal(table_id) & 0xFF);
|
|
WriteReg(RX_P2, HORIZONTAL_TOTAL_PIXELS_H,
|
|
mipi_original_htotal(table_id) >> 8);
|
|
/*Hactive*/
|
|
WriteReg(RX_P2, HORIZONTAL_ACTIVE_PIXELS_L,
|
|
mipi_original_hactive(table_id) & 0xFF);
|
|
WriteReg(RX_P2, HORIZONTAL_ACTIVE_PIXELS_H,
|
|
mipi_original_hactive(table_id) >> 8);
|
|
/*HFP*/
|
|
WriteReg(RX_P2, HORIZONTAL_FRONT_PORCH_L,
|
|
mipi_original_hfp(table_id) & 0xFF);
|
|
WriteReg(RX_P2, HORIZONTAL_FRONT_PORCH_H,
|
|
mipi_original_hfp(table_id) >> 8);
|
|
/*HWS*/
|
|
WriteReg(RX_P2, HORIZONTAL_SYNC_WIDTH_L,
|
|
mipi_original_hsw(table_id) & 0xFF);
|
|
WriteReg(RX_P2, HORIZONTAL_SYNC_WIDTH_H,
|
|
mipi_original_hsw(table_id) >> 8);
|
|
/*HBP*/
|
|
WriteReg(RX_P2, HORIZONTAL_BACK_PORCH_L,
|
|
mipi_original_hbp(table_id) & 0xFF);
|
|
WriteReg(RX_P2, HORIZONTAL_BACK_PORCH_H,
|
|
mipi_original_hbp(table_id) >> 8);
|
|
/*Vactive*/
|
|
WriteReg(RX_P2, ACTIVE_LINES_L,
|
|
mipi_original_vactive(table_id) & 0xFF);
|
|
WriteReg(RX_P2, ACTIVE_LINES_H,
|
|
mipi_original_vactive(table_id) >> 8);
|
|
/*VFP*/
|
|
WriteReg(RX_P2, VERTICAL_FRONT_PORCH,
|
|
mipi_original_vfp(table_id));
|
|
/*VWS*/
|
|
WriteReg(RX_P2, VERTICAL_SYNC_WIDTH,
|
|
mipi_original_vsw(table_id));
|
|
/*VBP*/
|
|
WriteReg(RX_P2, VERTICAL_BACK_PORCH,
|
|
mipi_original_vbp(table_id));
|
|
/*M value*/
|
|
WriteReg(RX_P1, MIPI_PLL_M_NUM_23_16,
|
|
(mipi_m_value(table_id) >> 16) & 0xff);
|
|
WriteReg(RX_P1, MIPI_PLL_M_NUM_15_8,
|
|
(mipi_m_value(table_id) >> 8) & 0xff);
|
|
WriteReg(RX_P1, MIPI_PLL_M_NUM_7_0,
|
|
mipi_m_value(table_id) & 0xff);
|
|
/*N value*/
|
|
WriteReg(RX_P1, MIPI_PLL_N_NUM_23_16,
|
|
(mipi_n_value(table_id) >> 16) & 0xff);
|
|
WriteReg(RX_P1, MIPI_PLL_N_NUM_15_8,
|
|
(mipi_n_value(table_id) >> 8) & 0xff);
|
|
WriteReg(RX_P1, MIPI_PLL_N_NUM_7_0,
|
|
mipi_n_value(table_id) & 0xff);
|
|
/*diff*/
|
|
WriteReg(RX_P1, MIPI_DIGITAL_ADJ_1,
|
|
mipi_diff_ratio(table_id));
|
|
}
|
|
|
|
static void API_ODFC_Configuration(struct anx7625 *anx7625)
|
|
{
|
|
int table_id = anx7625->mode_idx;
|
|
|
|
/*config input reference clock frequency 27MHz/19.2MHz*/
|
|
sp_write_reg_and(RX_P1, MIPI_DIGITAL_PLL_16,
|
|
~(REF_CLK_27000kHz << MIPI_FREF_D_IND));
|
|
sp_write_reg_or(RX_P1, MIPI_DIGITAL_PLL_16,
|
|
(((XTAL_FRQ >= 26000000UL) && (XTAL_FRQ <= 27000000UL)) ?
|
|
(REF_CLK_27000kHz << MIPI_FREF_D_IND)
|
|
: (REF_CLK_19200kHz << MIPI_FREF_D_IND)));
|
|
/*post divider*/
|
|
sp_write_reg_and(RX_P1, MIPI_DIGITAL_PLL_8, 0x0f);
|
|
sp_write_reg_or(RX_P1, MIPI_DIGITAL_PLL_8,
|
|
mipi_post_divider(table_id) << 4);
|
|
|
|
/*add patch for MIS2-125 (5pcs ANX7625 fail ATE MBIST test)*/
|
|
sp_write_reg_and(RX_P1, MIPI_DIGITAL_PLL_7,
|
|
~MIPI_PLL_VCO_TUNE_REG_VAL);
|
|
|
|
/*reset ODFC PLL*/
|
|
sp_write_reg_and(RX_P1, MIPI_DIGITAL_PLL_7,
|
|
~MIPI_PLL_RESET_N);
|
|
sp_write_reg_or(RX_P1, MIPI_DIGITAL_PLL_7,
|
|
MIPI_PLL_RESET_N);
|
|
/*force PLL lock*/
|
|
//WriteReg(TX_P0, DP_CONFIG_24, 0x0c);
|
|
}
|
|
|
|
static void DSC_Video_Timing_Configuration(struct anx7625 *anx7625,
|
|
unsigned char table_id)
|
|
{
|
|
unchar i;
|
|
|
|
/*config uncompressed video format*/
|
|
/*Htotal*/
|
|
WriteReg(TX_P2, HORIZONTAL_TOTAL_PIXELS_L,
|
|
(mipi_original_htotal(table_id) * mipi_compress_ratio(table_id))
|
|
& 0xFF);
|
|
WriteReg(TX_P2, HORIZONTAL_TOTAL_PIXELS_H,
|
|
(mipi_original_htotal(table_id) * mipi_compress_ratio(table_id))
|
|
>> 8);
|
|
/*Hactive*/
|
|
WriteReg(TX_P2, HORIZONTAL_ACTIVE_PIXELS_L,
|
|
(mipi_original_hactive(table_id)
|
|
* mipi_compress_ratio(table_id)) & 0xFF);
|
|
WriteReg(TX_P2, HORIZONTAL_ACTIVE_PIXELS_H,
|
|
(mipi_original_hactive(table_id)
|
|
* mipi_compress_ratio(table_id)) >> 8);
|
|
/*HFP*/
|
|
WriteReg(TX_P2, HORIZONTAL_FRONT_PORCH_L,
|
|
(mipi_original_hfp(table_id) * mipi_compress_ratio(table_id))
|
|
& 0xFF);
|
|
WriteReg(TX_P2, HORIZONTAL_FRONT_PORCH_H,
|
|
(mipi_original_hfp(table_id) * mipi_compress_ratio(table_id))
|
|
>> 8);
|
|
/*HWS*/
|
|
WriteReg(TX_P2, HORIZONTAL_SYNC_WIDTH_L,
|
|
(mipi_original_hsw(table_id) * mipi_compress_ratio(table_id))
|
|
& 0xFF);
|
|
WriteReg(TX_P2, HORIZONTAL_SYNC_WIDTH_H,
|
|
(mipi_original_hsw(table_id) * mipi_compress_ratio(table_id))
|
|
>> 8);
|
|
/*HBP*/
|
|
WriteReg(TX_P2, HORIZONTAL_BACK_PORCH_L,
|
|
(mipi_original_hbp(table_id) * mipi_compress_ratio(table_id))
|
|
& 0xFF);
|
|
WriteReg(TX_P2, HORIZONTAL_BACK_PORCH_H,
|
|
(mipi_original_hbp(table_id) * mipi_compress_ratio(table_id))
|
|
>> 8);
|
|
/*Vtotal*/
|
|
WriteReg(TX_P2, TOTAL_LINES_L,
|
|
mipi_original_vtotal(table_id) & 0xFF);
|
|
WriteReg(TX_P2, TOTAL_LINES_H,
|
|
mipi_original_vtotal(table_id) >> 8);
|
|
/*Vactive*/
|
|
WriteReg(TX_P2, ACTIVE_LINES_L,
|
|
mipi_original_vactive(table_id) & 0xFF);
|
|
WriteReg(TX_P2, ACTIVE_LINES_H,
|
|
mipi_original_vactive(table_id) >> 8);
|
|
/*VFP*/
|
|
WriteReg(TX_P2, VERTICAL_FRONT_PORCH,
|
|
mipi_original_vfp(table_id));
|
|
/*VWS*/
|
|
WriteReg(TX_P2, VERTICAL_SYNC_WIDTH,
|
|
mipi_original_vsw(table_id));
|
|
/*VBP*/
|
|
WriteReg(TX_P2, VERTICAL_BACK_PORCH,
|
|
mipi_original_vbp(table_id));
|
|
|
|
/*config uncompressed video format to woraround */
|
|
/* downstream compatibility issues*/
|
|
/*Htotal*/
|
|
WriteReg(RX_P0, TOTAL_PIXEL_L_7E,
|
|
mipi_decompressed_htotal(table_id) & 0xFF);
|
|
WriteReg(RX_P0, TOTAL_PIXEL_H_7E,
|
|
mipi_decompressed_htotal(table_id) >> 8);
|
|
/*Hactive*/
|
|
WriteReg(RX_P0, ACTIVE_PIXEL_L_7E,
|
|
mipi_decompressed_hactive(table_id) & 0xFF);
|
|
WriteReg(RX_P0, ACTIVE_PIXEL_H_7E,
|
|
mipi_decompressed_hactive(table_id) >> 8);
|
|
/*HFP*/
|
|
WriteReg(RX_P0, HORIZON_FRONT_PORCH_L_7E,
|
|
mipi_decompressed_hfp(table_id) & 0xFF);
|
|
WriteReg(RX_P0, HORIZON_FRONT_PORCH_H_7E,
|
|
mipi_decompressed_hfp(table_id) >> 8);
|
|
/*HWS*/
|
|
WriteReg(RX_P0, HORIZON_SYNC_WIDTH_L_7E,
|
|
mipi_decompressed_hsw(table_id) & 0xFF);
|
|
WriteReg(RX_P0, HORIZON_SYNC_WIDTH_H_7E,
|
|
mipi_decompressed_hsw(table_id) >> 8);
|
|
/*HBP*/
|
|
WriteReg(RX_P0, HORIZON_BACK_PORCH_L_7E,
|
|
mipi_decompressed_hbp(table_id) & 0xFF);
|
|
WriteReg(RX_P0, HORIZON_BACK_PORCH_H_7E,
|
|
mipi_decompressed_hbp(table_id) >> 8);
|
|
|
|
/*config DSC decoder internal blank timing for decoder to start*/
|
|
WriteReg(RX_P1, H_BLANK_L, ((mipi_original_htotal(table_id)
|
|
- mipi_original_hactive(table_id))) & 0xFF);
|
|
WriteReg(RX_P1, H_BLANK_H, ((mipi_original_htotal(table_id)
|
|
- mipi_original_hactive(table_id))) >> 8);
|
|
|
|
/*compress ratio RATIO [7:6] 3:div2; 0,1,2:div3*/
|
|
sp_write_reg_and(RX_P0, R_I2C_1, 0x3f);
|
|
sp_write_reg_or(RX_P0, R_I2C_1,
|
|
(5 - mipi_compress_ratio(table_id)) << 6);
|
|
|
|
/*PPS table*/
|
|
if (mipi_video_timing_table[table_id].pps_reg != NULL) {
|
|
for (i = 0; i < 0x80; i += 0x10)
|
|
WriteBlockReg(RX_P2, R_PPS_REG_0 + i, 0x10,
|
|
(unsigned char *)mipi_video_timing_table
|
|
[table_id].pps_reg + i);
|
|
}
|
|
}
|
|
|
|
static void API_Custom_Register0_Configuration(struct anx7625 *anx7625,
|
|
unsigned char table_id)
|
|
{
|
|
unchar i = 0;
|
|
/*custom specific register*/
|
|
if (mipi_video_timing_table[table_id].custom_reg0 != NULL) {
|
|
while (mipi_video_timing_table[table_id].custom_reg0[i]
|
|
.slave_addr) {
|
|
WriteReg(mipi_video_timing_table[table_id]
|
|
.custom_reg0[i].slave_addr,
|
|
mipi_video_timing_table[table_id]
|
|
.custom_reg0[i].reg,
|
|
mipi_video_timing_table[table_id]
|
|
.custom_reg0[i].val);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void API_Custom_Register1_Configuration(struct anx7625 *anx7625,
|
|
unsigned char table_id)
|
|
{
|
|
unchar i = 0;
|
|
/*custom specific register*/
|
|
if (mipi_video_timing_table[table_id].custom_reg1 != NULL) {
|
|
while (mipi_video_timing_table[table_id].custom_reg1[i]
|
|
.slave_addr) {
|
|
WriteReg(mipi_video_timing_table[table_id]
|
|
.custom_reg1[i].slave_addr,
|
|
mipi_video_timing_table[table_id]
|
|
.custom_reg1[i].reg,
|
|
mipi_video_timing_table[table_id]
|
|
.custom_reg1[i].val);
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void swap_DSI_lane3(struct anx7625 *anx7625)
|
|
{
|
|
unsigned char RegValue;
|
|
/* swap MIPI-DSI data lane 3 P and N */
|
|
RegValue = ReadReg(RX_P1, MIPI_SWAP);
|
|
RegValue |= (1 << MIPI_SWAP_CH3);
|
|
WriteReg(RX_P1, MIPI_SWAP, RegValue);
|
|
}
|
|
|
|
static void API_DSI_Configuration(struct anx7625 *anx7625,
|
|
unsigned char table_id)
|
|
{
|
|
unsigned char RegValue;
|
|
|
|
/* swap MIPI-DSI data lane 3 P and N */
|
|
swap_DSI_lane3(anx7625);
|
|
|
|
/* DSI clock settings */
|
|
RegValue = (0 << MIPI_HS_PWD_CLK) |
|
|
(0 << MIPI_HS_RT_CLK) |
|
|
(0 << MIPI_PD_CLK) |
|
|
(1 << MIPI_CLK_RT_MANUAL_PD_EN) |
|
|
(1 << MIPI_CLK_HS_MANUAL_PD_EN) |
|
|
(0 << MIPI_CLK_DET_DET_BYPASS) |
|
|
(0 << MIPI_CLK_MISS_CTRL) |
|
|
(0 << MIPI_PD_LPTX_CH_MANUAL_PD_EN);
|
|
WriteReg(RX_P1, MIPI_PHY_CONTROL_3, RegValue);
|
|
|
|
/* Decreased HS prepare timing delay from 160ns to 80ns work with
|
|
* a) Dragon board 810 series (Qualcomm Technologies, Inc AP)
|
|
* b) Moving DSI source (PG3A pattern generator +
|
|
* P332 D-PHY Probe) default D-PHY timing
|
|
*/
|
|
WriteReg(RX_P1, MIPI_TIME_HS_PRPR, 0x10); /* 5ns/step */
|
|
|
|
sp_write_reg_or(RX_P1, MIPI_DIGITAL_PLL_18,
|
|
SELECT_DSI<<MIPI_DPI_SELECT); /* enable DSI mode*/
|
|
|
|
DSI_Video_Timing_Configuration(anx7625);
|
|
|
|
API_ODFC_Configuration(anx7625);
|
|
|
|
/*toggle m, n ready*/
|
|
sp_write_reg_and(RX_P1, MIPI_DIGITAL_PLL_6,
|
|
~(MIPI_M_NUM_READY | MIPI_N_NUM_READY));
|
|
usleep_range(1000, 1100);
|
|
sp_write_reg_or(RX_P1, MIPI_DIGITAL_PLL_6,
|
|
MIPI_M_NUM_READY | MIPI_N_NUM_READY);
|
|
|
|
/*configure integer stable register*/
|
|
WriteReg(RX_P1, MIPI_VIDEO_STABLE_CNT, 0x02);
|
|
/*power on MIPI RX*/
|
|
WriteReg(RX_P1, MIPI_LANE_CTRL_10, 0x00);
|
|
WriteReg(RX_P1, MIPI_LANE_CTRL_10, 0x80);
|
|
}
|
|
|
|
static void DSI_Configuration(struct anx7625 *anx7625, unsigned char table_id)
|
|
{
|
|
TRACE1("%s Input Index = %02X\n", __func__, table_id);
|
|
|
|
API_Custom_Register0_Configuration(anx7625, table_id);
|
|
/*DSC disable*/
|
|
sp_write_reg_and(RX_P0, R_DSC_CTRL_0, ~DSC_EN);
|
|
API_DSI_Configuration(anx7625, table_id);
|
|
API_Custom_Register1_Configuration(anx7625, table_id);
|
|
|
|
/*set MIPI RX EN*/
|
|
sp_write_reg_or(RX_P0, AP_AV_STATUS, AP_MIPI_RX_EN);
|
|
/*clear mute flag*/
|
|
sp_write_reg_and(RX_P0, AP_AV_STATUS, ~AP_MIPI_MUTE);
|
|
}
|
|
|
|
static void DSI_DSC_Configuration(struct anx7625 *anx7625,
|
|
unsigned char table_id)
|
|
{
|
|
TRACE1("%s Input Index = %02X\n", __func__, table_id);
|
|
|
|
API_Custom_Register0_Configuration(anx7625, table_id);
|
|
|
|
DSC_Video_Timing_Configuration(anx7625, table_id);
|
|
/*DSC enable*/
|
|
sp_write_reg_or(RX_P0, R_DSC_CTRL_0, DSC_EN);
|
|
|
|
API_DSI_Configuration(anx7625, table_id);
|
|
API_Custom_Register1_Configuration(anx7625, table_id);
|
|
|
|
/*set MIPI RX EN*/
|
|
sp_write_reg_or(RX_P0, AP_AV_STATUS, AP_MIPI_RX_EN);
|
|
/*clear mute flag*/
|
|
sp_write_reg_and(RX_P0, AP_AV_STATUS, ~AP_MIPI_MUTE);
|
|
}
|
|
|
|
static int anx7625_start(struct anx7625 *anx7625)
|
|
{
|
|
/*not support HDCP*/
|
|
sp_write_reg_and(RX_P1, 0xee, 0x9f);
|
|
|
|
/*try auth flag*/
|
|
sp_write_reg_or(RX_P1, 0xec, 0x10);
|
|
/* interrupt for DRM*/
|
|
sp_write_reg_or(RX_P1, 0xff, 0x01);
|
|
|
|
if (!mipi_compress_ratio(anx7625->mode_idx))
|
|
DSI_Configuration(anx7625, anx7625->mode_idx);
|
|
else
|
|
DSI_DSC_Configuration(anx7625, anx7625->mode_idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int anx7625_stop(struct anx7625 *anx7625)
|
|
{
|
|
/*set mute flag*/
|
|
sp_write_reg_or(RX_P0, AP_AV_STATUS, AP_MIPI_MUTE);
|
|
|
|
/*clear mipi RX en*/
|
|
sp_write_reg_and(RX_P0, AP_AV_STATUS, ~AP_MIPI_RX_EN);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define STS_HPD_CHANGE \
|
|
(((sys_status&HPD_STATUS) != (anx7625->sys_sta_bak&HPD_STATUS)) ?\
|
|
HPD_STATUS_CHANGE:0)
|
|
|
|
static void handle_intr_vector(struct anx7625 *anx7625)
|
|
{
|
|
unsigned char sys_status;
|
|
u8 intr_vector = ReadReg(RX_P0, INTERFACE_CHANGE_INT);
|
|
|
|
WriteReg(RX_P0, INTERFACE_CHANGE_INT,
|
|
intr_vector & (~intr_vector));
|
|
|
|
sys_status = ReadReg(RX_P0, SYSTEM_STSTUS);
|
|
|
|
if ((~INTR_MASK_SETTING) &
|
|
((intr_vector & HPD_STATUS_CHANGE) | STS_HPD_CHANGE)) {
|
|
if (!(sys_status & HPD_STATUS)) {
|
|
anx7625->hpd_status = 0;
|
|
TRACE1("HPD low\n");
|
|
if (anx7625->enabled)
|
|
anx7625_stop(anx7625);
|
|
} else {
|
|
anx7625->hpd_status = 1;
|
|
TRACE1("HPD high\n");
|
|
if (anx7625->enabled)
|
|
anx7625_start(anx7625);
|
|
}
|
|
}
|
|
|
|
anx7625->sys_sta_bak = sys_status;
|
|
}
|
|
|
|
static int anx7625_init_pdata(struct anx7625 *anx7625)
|
|
{
|
|
struct anx7625_platform_data *pdata = &anx7625->pdata;
|
|
struct device *dev = &anx7625->client->dev;
|
|
int gpio_state = GPIOD_OUT_LOW;
|
|
|
|
if (anx7625->skip_enable)
|
|
gpio_state = GPIOD_OUT_HIGH;
|
|
|
|
/* GPIO for HPD */
|
|
pdata->gpiod_cdet = devm_gpiod_get(dev, "cbl_det", GPIOD_IN);
|
|
if (IS_ERR(pdata->gpiod_cdet))
|
|
return PTR_ERR(pdata->gpiod_cdet);
|
|
|
|
/* GPIO for chip power enable */
|
|
pdata->gpiod_p_on = devm_gpiod_get(dev, "power_en", gpio_state);
|
|
if (IS_ERR(pdata->gpiod_p_on))
|
|
return PTR_ERR(pdata->gpiod_p_on);
|
|
|
|
/* GPIO for chip reset */
|
|
pdata->gpiod_reset = devm_gpiod_get(dev, "reset_n", gpio_state);
|
|
|
|
return PTR_ERR_OR_ZERO(pdata->gpiod_reset);
|
|
}
|
|
|
|
static int anx7625_get_mode_idx(const struct drm_display_mode *mode)
|
|
{
|
|
struct MIPI_Video_Format *fmt;
|
|
int mode_idx = -1, categoly = 0, i;
|
|
|
|
if (mode->htotal >= 3840)
|
|
categoly = 1;
|
|
|
|
for (i = 6; i < sizeof(mipi_video_timing_table) /
|
|
sizeof(mipi_video_timing_table[0]); i++) {
|
|
fmt = &mipi_video_timing_table[i];
|
|
if (fmt->MIPI_pixel_frequency == mode->clock * 1000 &&
|
|
fmt->MIPI_inputl[categoly].MIPI_HTOTAL ==
|
|
mode->htotal &&
|
|
fmt->MIPI_inputl[categoly].MIPI_VTOTAL ==
|
|
mode->vtotal &&
|
|
fmt->MIPI_inputl[categoly].MIPI_HActive ==
|
|
mode->hdisplay &&
|
|
fmt->MIPI_inputl[categoly].MIPI_VActive ==
|
|
mode->vdisplay &&
|
|
fmt->MIPI_inputl[categoly].MIPI_H_Front_Porch ==
|
|
mode->hsync_start - mode->hdisplay &&
|
|
fmt->MIPI_inputl[categoly].MIPI_H_Sync_Width ==
|
|
mode->hsync_end - mode->hsync_start &&
|
|
fmt->MIPI_inputl[categoly].MIPI_H_Back_Porch ==
|
|
mode->htotal - mode->hsync_end &&
|
|
fmt->MIPI_inputl[categoly].MIPI_V_Front_Porch ==
|
|
mode->vsync_start - mode->vdisplay &&
|
|
fmt->MIPI_inputl[categoly].MIPI_V_Sync_Width ==
|
|
mode->vsync_end - mode->vsync_start &&
|
|
fmt->MIPI_inputl[categoly].MIPI_V_Back_Porch ==
|
|
mode->vtotal - mode->vsync_end) {
|
|
mode_idx = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return mode_idx;
|
|
}
|
|
|
|
static int anx7625_bridge_attach(struct drm_bridge *bridge)
|
|
{
|
|
struct anx7625 *anx7625 = bridge_to_anx7625(bridge);
|
|
int err;
|
|
|
|
if (!bridge->encoder) {
|
|
DRM_ERROR("Parent encoder object not found");
|
|
return -ENODEV;
|
|
}
|
|
|
|
/* Register aux channel */
|
|
anx7625->aux.name = "DP-AUX";
|
|
anx7625->aux.dev = &anx7625->client->dev;
|
|
anx7625->aux.transfer = anx7625_aux_transfer;
|
|
|
|
err = drm_dp_aux_register(&anx7625->aux);
|
|
if (err < 0) {
|
|
DRM_ERROR("Failed to register aux channel: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
device_link_add(bridge->dev->dev, &anx7625->client->dev,
|
|
DL_FLAG_PM_RUNTIME);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum drm_mode_status
|
|
anx7625_bridge_mode_valid(struct drm_bridge *bridge,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
if (anx7625_get_mode_idx(mode) < 0) {
|
|
pr_err("failed to find valid index\n");
|
|
return MODE_NOMODE;
|
|
}
|
|
|
|
return MODE_OK;
|
|
}
|
|
|
|
static void anx7625_bridge_disable(struct drm_bridge *bridge)
|
|
{
|
|
struct anx7625 *anx7625 = bridge_to_anx7625(bridge);
|
|
|
|
mutex_lock(&anx7625->lock);
|
|
|
|
anx7625->enabled = false;
|
|
|
|
if (!anx7625->powered)
|
|
goto out;
|
|
|
|
anx7625_stop(anx7625);
|
|
|
|
out:
|
|
mutex_unlock(&anx7625->lock);
|
|
|
|
TRACE("anx7625 disabled\n");
|
|
}
|
|
|
|
static void anx7625_bridge_mode_set(struct drm_bridge *bridge,
|
|
struct drm_display_mode *mode,
|
|
struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct anx7625 *anx7625 = bridge_to_anx7625(bridge);
|
|
int mode_idx;
|
|
|
|
mode_idx = anx7625_get_mode_idx(adjusted_mode);
|
|
|
|
mutex_lock(&anx7625->lock);
|
|
|
|
if (mode_idx >= 0)
|
|
anx7625->mode_idx = mode_idx;
|
|
else
|
|
DRM_ERROR("Failed to find pre-defined mode for %s\n",
|
|
mode->name);
|
|
|
|
mutex_unlock(&anx7625->lock);
|
|
}
|
|
|
|
static void anx7625_bridge_enable(struct drm_bridge *bridge)
|
|
{
|
|
struct anx7625 *anx7625 = bridge_to_anx7625(bridge);
|
|
int err;
|
|
|
|
mutex_lock(&anx7625->lock);
|
|
|
|
anx7625->enabled = true;
|
|
#ifdef CONFIG_PM_SLEEP
|
|
if (anx7625->out_of_hibr) {
|
|
anx7625->out_of_hibr = false;
|
|
update_marker("Hiber: Display up");
|
|
}
|
|
#endif
|
|
if (!anx7625->powered)
|
|
goto out;
|
|
|
|
if (anx7625->skip_enable) {
|
|
anx7625->skip_enable = false;
|
|
goto out;
|
|
}
|
|
|
|
if (!anx7625->connected)
|
|
DRM_ERROR("cable is not connected\n");
|
|
|
|
if (!anx7625->hpd_status)
|
|
DRM_ERROR("hpd is not set\n");
|
|
|
|
err = anx7625_start(anx7625);
|
|
if (err)
|
|
DRM_ERROR("Failed to start: %d\n", err);
|
|
|
|
out:
|
|
mutex_unlock(&anx7625->lock);
|
|
|
|
TRACE("anx7625 enabled\n");
|
|
}
|
|
|
|
static const struct drm_bridge_funcs anx7625_bridge_funcs = {
|
|
.attach = anx7625_bridge_attach,
|
|
.mode_valid = anx7625_bridge_mode_valid,
|
|
.disable = anx7625_bridge_disable,
|
|
.mode_set = anx7625_bridge_mode_set,
|
|
.enable = anx7625_bridge_enable,
|
|
};
|
|
|
|
static irqreturn_t anx7625_cdet_threaded_handler(int irq, void *data)
|
|
{
|
|
struct anx7625 *anx7625 = data;
|
|
int connected;
|
|
|
|
mutex_lock(&anx7625->lock);
|
|
|
|
connected = gpiod_get_value_cansleep(anx7625->pdata.gpiod_cdet);
|
|
|
|
if (anx7625->connected != connected) {
|
|
anx7625->connected = connected;
|
|
TRACE("cable status %d\n", connected);
|
|
}
|
|
|
|
mutex_unlock(&anx7625->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static irqreturn_t anx7625_intp_threaded_handler(int unused, void *data)
|
|
{
|
|
struct anx7625 *anx7625 = data;
|
|
unsigned char c;
|
|
|
|
mutex_lock(&anx7625->lock);
|
|
|
|
c = ReadReg(TCPC_INTERFACE, INTR_ALERT_1);
|
|
|
|
if (c & INTR_SOFTWARE_INT)
|
|
handle_intr_vector(anx7625);
|
|
|
|
while (ReadReg(RX_P0,
|
|
INTERFACE_CHANGE_INT) != 0)
|
|
handle_intr_vector(anx7625);
|
|
|
|
if (c)
|
|
WriteReg(TCPC_INTERFACE, INTR_ALERT_1, 0xFF);
|
|
|
|
mutex_unlock(&anx7625->lock);
|
|
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static const u16 anx7625_chipid_list[] = {
|
|
0x7625,
|
|
};
|
|
|
|
static int anx7625_i2c_probe(struct i2c_client *client,
|
|
const struct i2c_device_id *id)
|
|
{
|
|
struct anx7625 *anx7625;
|
|
struct anx7625_platform_data *pdata;
|
|
unsigned int i, idl, idh, version[2];
|
|
bool found = false;
|
|
int err;
|
|
|
|
anx7625 = devm_kzalloc(&client->dev, sizeof(*anx7625), GFP_KERNEL);
|
|
if (!anx7625)
|
|
return -ENOMEM;
|
|
|
|
pdata = &anx7625->pdata;
|
|
|
|
mutex_init(&anx7625->lock);
|
|
|
|
anx7625->client = client;
|
|
i2c_set_clientdata(client, anx7625);
|
|
|
|
/* Check if Bridge Already Powered On */
|
|
err = Read_Reg(TCPC_INTERFACE, PRODUCT_ID_L, &idl);
|
|
if (err) {
|
|
anx7625->skip_enable = false;
|
|
DRM_DEBUG("ANX7625 Bridge Not powered in Bootloader");
|
|
} else {
|
|
/* Match software state */
|
|
anx7625->powered = true;
|
|
anx7625->skip_enable = true;
|
|
}
|
|
|
|
err = anx7625_init_pdata(anx7625);
|
|
if (err) {
|
|
DRM_ERROR("Failed to initialize pdata: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
pdata->cdet_irq = gpiod_to_irq(pdata->gpiod_cdet);
|
|
if (pdata->cdet_irq < 0) {
|
|
DRM_ERROR("Failed to get CDET IRQ: %d\n", pdata->cdet_irq);
|
|
return -ENODEV;
|
|
}
|
|
|
|
pdata->intp_irq = client->irq;
|
|
if (!pdata->intp_irq) {
|
|
DRM_ERROR("Failed to get INTP IRQ\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
if (!anx7625->skip_enable) {
|
|
/* Power on chip */
|
|
err = anx7625_poweron(anx7625);
|
|
if (err)
|
|
goto err_poweroff;
|
|
}
|
|
|
|
/* Look for supported chip ID */
|
|
err = Read_Reg(TCPC_INTERFACE, PRODUCT_ID_L, &idl);
|
|
if (err)
|
|
goto err_poweroff;
|
|
|
|
err = Read_Reg(TCPC_INTERFACE, PRODUCT_ID_H, &idh);
|
|
if (err)
|
|
goto err_poweroff;
|
|
|
|
err = Read_Reg(RX_P0, OCM_FW_VERSION, &version[0]);
|
|
if (err)
|
|
goto err_poweroff;
|
|
|
|
err = Read_Reg(RX_P0, OCM_FW_REVERSION, &version[1]);
|
|
if (err)
|
|
goto err_poweroff;
|
|
|
|
anx7625->chipid = (u8)idl | ((u8)idh << 8);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(anx7625_chipid_list); i++) {
|
|
if (anx7625->chipid == anx7625_chipid_list[i]) {
|
|
DRM_INFO("Found ANX%x (ver. %x%x) Transmitter\n",
|
|
anx7625->chipid, version[0], version[1]);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
DRM_ERROR("ANX%x (ver. %x%x) not supported by this driver\n",
|
|
anx7625->chipid, version[0], version[1]);
|
|
err = -ENODEV;
|
|
goto err_poweroff;
|
|
}
|
|
|
|
err = devm_request_threaded_irq(&client->dev, pdata->cdet_irq, NULL,
|
|
anx7625_cdet_threaded_handler,
|
|
IRQF_TRIGGER_RISING
|
|
| IRQF_TRIGGER_RISING
|
|
| IRQF_ONESHOT,
|
|
"anx7625-hpd", anx7625);
|
|
if (err) {
|
|
DRM_ERROR("Failed to request CABLE_DET threaded IRQ: %d\n",
|
|
err);
|
|
goto err_poweroff;
|
|
}
|
|
|
|
err = devm_request_threaded_irq(&client->dev, pdata->intp_irq, NULL,
|
|
anx7625_intp_threaded_handler,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"anx7625-intp", anx7625);
|
|
if (err) {
|
|
DRM_ERROR("Failed to request INTP threaded IRQ: %d\n", err);
|
|
goto err_poweroff;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_OF)
|
|
anx7625->bridge.of_node = client->dev.of_node;
|
|
#endif
|
|
|
|
anx7625->bridge.funcs = &anx7625_bridge_funcs;
|
|
|
|
drm_bridge_add(&anx7625->bridge);
|
|
|
|
/* init connected status */
|
|
anx7625->connected =
|
|
gpiod_get_value_cansleep(anx7625->pdata.gpiod_cdet);
|
|
|
|
/* init hpd status */
|
|
anx7625->sys_sta_bak = ReadReg(RX_P0, SYSTEM_STSTUS);
|
|
anx7625->hpd_status = (anx7625->sys_sta_bak & HPD_STATUS) ?
|
|
true : false;
|
|
|
|
return 0;
|
|
|
|
err_poweroff:
|
|
anx7625_poweroff(anx7625);
|
|
DRM_ERROR("Failed to load anx7625 driver: %d\n", err);
|
|
return err;
|
|
}
|
|
|
|
static int anx7625_i2c_remove(struct i2c_client *client)
|
|
{
|
|
struct anx7625 *anx7625 = i2c_get_clientdata(client);
|
|
|
|
anx7625_poweroff(anx7625);
|
|
|
|
drm_bridge_remove(&anx7625->bridge);
|
|
|
|
kfree(anx7625->edid);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct i2c_device_id anx7625_id[] = {
|
|
{ "anx7625", 0 },
|
|
{ /* sentinel */ }
|
|
};
|
|
MODULE_DEVICE_TABLE(i2c, anx7625_id);
|
|
|
|
#if IS_ENABLED(CONFIG_OF)
|
|
static const struct of_device_id anx7625_id_match_table[] = {
|
|
{ .compatible = "analogix,anx7625", },
|
|
{ /* sentinel */ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, anx7625_id_match_table);
|
|
#endif
|
|
|
|
#ifdef CONFIG_PM_SLEEP
|
|
static int anx7625_freeze(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct anx7625 *anx7625 = i2c_get_clientdata(client);
|
|
|
|
mutex_lock(&anx7625->lock);
|
|
|
|
anx7625_poweroff(anx7625);
|
|
|
|
mutex_unlock(&anx7625->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int anx7625_restore(struct device *dev)
|
|
{
|
|
struct i2c_client *client = to_i2c_client(dev);
|
|
struct anx7625 *anx7625 = i2c_get_clientdata(client);
|
|
|
|
mutex_lock(&anx7625->lock);
|
|
|
|
anx7625->last_read_DevAddr = 0;
|
|
|
|
anx7625_poweron(anx7625);
|
|
|
|
if (anx7625->enabled) {
|
|
/* wait until ocm is initialized */
|
|
usleep_range(10000, 11000);
|
|
|
|
anx7625_start(anx7625);
|
|
}
|
|
anx7625->out_of_hibr = true;
|
|
|
|
mutex_unlock(&anx7625->lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dev_pm_ops anx7625_pm = {
|
|
SET_SYSTEM_SLEEP_PM_OPS(anx7625_freeze, anx7625_restore)
|
|
};
|
|
#endif
|
|
|
|
static struct i2c_driver anx7625_driver = {
|
|
.driver = {
|
|
.name = "anx7625",
|
|
.owner = THIS_MODULE,
|
|
#ifdef CONFIG_PM_SLEEP
|
|
.pm = &anx7625_pm,
|
|
#endif
|
|
#ifdef CONFIG_OF
|
|
.of_match_table = anx7625_id_match_table,
|
|
#endif
|
|
},
|
|
.probe = anx7625_i2c_probe,
|
|
.remove = anx7625_i2c_remove,
|
|
.id_table = anx7625_id,
|
|
};
|
|
|
|
module_i2c_driver(anx7625_driver);
|
|
MODULE_DESCRIPTION("anx7625 driver");
|
|
MODULE_LICENSE("GPL v2");
|
|
|