// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2013--2024 Intel Corporation
*/
#include <linux/bitfield.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/iopoll.h>
#include <linux/math64.h>
#include "ipu6-bus.h"
#include "ipu6-isys.h"
#include "ipu6-platform-isys-csi2-reg.h"
#define IPU6_DWC_DPHY_BASE(i) (0 x238038 + 0 x34 * (i))
#define IPU6_DWC_DPHY_RSTZ 0 x00
#define IPU6_DWC_DPHY_SHUTDOWNZ 0 x04
#define IPU6_DWC_DPHY_HSFREQRANGE 0 x08
#define IPU6_DWC_DPHY_CFGCLKFREQRANGE 0 x0c
#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE 0 x10
#define IPU6_DWC_DPHY_TEST_IFC_REQ 0 x14
#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION 0 x18
#define IPU6_DWC_DPHY_DFT_CTRL0 0 x28
#define IPU6_DWC_DPHY_DFT_CTRL1 0 x2c
#define IPU6_DWC_DPHY_DFT_CTRL2 0 x30
/*
* test IFC request definition:
* - req: 0 for read, 1 for write
* - 12 bits address
* - 8bits data (will ignore for read)
* --24----16------4-----0
* --|-data-|-addr-|-req-|
*/
#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23 , 16 ), data) | \
FIELD_PREP(GENMASK(15 , 4 ), addr) | \
FIELD_PREP(GENMASK(1 , 0 ), req))
#define TEST_IFC_REQ_READ 0
#define TEST_IFC_REQ_WRITE 1
#define TEST_IFC_REQ_RESET 2
#define TEST_IFC_ACCESS_MODE_FSM 0
#define TEST_IFC_ACCESS_MODE_IFC_CTL 1
enum phy_fsm_state {
PHY_FSM_STATE_POWERON = 0 ,
PHY_FSM_STATE_BGPON = 1 ,
PHY_FSM_STATE_CAL_TYPE = 2 ,
PHY_FSM_STATE_BURNIN_CAL = 3 ,
PHY_FSM_STATE_TERMCAL = 4 ,
PHY_FSM_STATE_OFFSETCAL = 5 ,
PHY_FSM_STATE_OFFSET_LANE = 6 ,
PHY_FSM_STATE_IDLE = 7 ,
PHY_FSM_STATE_ULP = 8 ,
PHY_FSM_STATE_DDLTUNNING = 9 ,
PHY_FSM_STATE_SKEW_BACKWARD = 10 ,
PHY_FSM_STATE_INVALID,
};
static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
dev_dbg(dev, "write: reg 0x%zx = data 0x%x" , base + addr - isys_base,
data);
writel(data, base + addr);
}
static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
u32 data;
data = readl(base + addr);
dev_dbg(dev, "read: reg 0x%zx = data 0x%x" , base + addr - isys_base,
data);
return data;
}
static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data, u8 shift, u8 width)
{
u32 temp;
u32 mask;
mask = (1 << width) - 1 ;
temp = dwc_dphy_read(isys, phy_id, addr);
temp &= ~(mask << shift);
temp |= (data & mask) << shift;
dwc_dphy_write(isys, phy_id, addr, temp);
}
static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id,
u32 addr, u8 shift, u8 width)
{
u32 val;
val = dwc_dphy_read(isys, phy_id, addr) >> shift;
return val & ((1 << width) - 1 );
}
#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC)
static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 *val)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
void __iomem *reg;
u32 completion;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
IFC_REQ(TEST_IFC_REQ_READ, addr, 0 ));
reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
ret = readl_poll_timeout(reg, completion, !(completion & BIT(0 )),
10 , DWC_DPHY_TIMEOUT);
if (ret) {
dev_err(dev, "DWC ifc request read timeout\n" );
return ret;
}
*val = completion >> 8 & 0 xff;
*val = FIELD_GET(GENMASK(15 , 8 ), completion);
dev_dbg(dev, "DWC ifc read 0x%x = 0x%x" , addr, *val);
return 0 ;
}
static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u32 data)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
void __iomem *reg;
u32 completion;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
IFC_REQ(TEST_IFC_REQ_WRITE, addr, data));
completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION);
reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
ret = readl_poll_timeout(reg, completion, !(completion & BIT(0 )),
10 , DWC_DPHY_TIMEOUT);
if (ret)
dev_err(dev, "DWC ifc request write timeout\n" );
return ret;
}
static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id,
u32 addr, u32 data, u8 shift, u8 width)
{
u32 temp, mask;
int ret;
ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp);
if (ret)
return ;
mask = (1 << width) - 1 ;
temp &= ~(mask << shift);
temp |= (data & mask) << shift;
dwc_dphy_ifc_write(isys, phy_id, addr, temp);
}
static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
u8 shift, u8 width)
{
int ret;
u32 val;
ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val);
if (ret)
return 0 ;
return ((val >> shift) & ((1 << width) - 1 ));
}
static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id)
{
struct device *dev = &isys->adev->auxdev.dev;
u32 fsm_state;
int ret;
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1 );
usleep_range(10 , 20 );
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1 );
ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state,
(fsm_state == PHY_FSM_STATE_IDLE ||
fsm_state == PHY_FSM_STATE_ULP),
100 , DWC_DPHY_TIMEOUT, false , isys,
phy_id, 0 x1e, 0 , 4 );
if (ret)
dev_err(dev, "Dphy %d power up failed, state 0x%x" , phy_id,
fsm_state);
return ret;
}
struct dwc_dphy_freq_range {
u8 hsfreq;
u16 min;
u16 max;
u16 default_mbps;
u16 osc_freq_target;
};
#define DPHY_FREQ_RANGE_NUM (63 )
#define DPHY_FREQ_RANGE_INVALID_INDEX (0 xff)
static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = {
{0 x00, 80 , 97 , 80 , 335 },
{0 x10, 80 , 107 , 90 , 335 },
{0 x20, 84 , 118 , 100 , 335 },
{0 x30, 93 , 128 , 110 , 335 },
{0 x01, 103 , 139 , 120 , 335 },
{0 x11, 112 , 149 , 130 , 335 },
{0 x21, 122 , 160 , 140 , 335 },
{0 x31, 131 , 170 , 150 , 335 },
{0 x02, 141 , 181 , 160 , 335 },
{0 x12, 150 , 191 , 170 , 335 },
{0 x22, 160 , 202 , 180 , 335 },
{0 x32, 169 , 212 , 190 , 335 },
{0 x03, 183 , 228 , 205 , 335 },
{0 x13, 198 , 244 , 220 , 335 },
{0 x23, 212 , 259 , 235 , 335 },
{0 x33, 226 , 275 , 250 , 335 },
{0 x04, 250 , 301 , 275 , 335 },
{0 x14, 274 , 328 , 300 , 335 },
{0 x25, 297 , 354 , 325 , 335 },
{0 x35, 321 , 380 , 350 , 335 },
{0 x05, 369 , 433 , 400 , 335 },
{0 x16, 416 , 485 , 450 , 335 },
{0 x26, 464 , 538 , 500 , 335 },
{0 x37, 511 , 590 , 550 , 335 },
{0 x07, 559 , 643 , 600 , 335 },
{0 x18, 606 , 695 , 650 , 335 },
{0 x28, 654 , 748 , 700 , 335 },
{0 x39, 701 , 800 , 750 , 335 },
{0 x09, 749 , 853 , 800 , 335 },
{0 x19, 796 , 905 , 850 , 335 },
{0 x29, 844 , 958 , 900 , 335 },
{0 x3a, 891 , 1010 , 950 , 335 },
{0 x0a, 939 , 1063 , 1000 , 335 },
{0 x1a, 986 , 1115 , 1050 , 335 },
{0 x2a, 1034 , 1168 , 1100 , 335 },
{0 x3b, 1081 , 1220 , 1150 , 335 },
{0 x0b, 1129 , 1273 , 1200 , 335 },
{0 x1b, 1176 , 1325 , 1250 , 335 },
{0 x2b, 1224 , 1378 , 1300 , 335 },
{0 x3c, 1271 , 1430 , 1350 , 335 },
{0 x0c, 1319 , 1483 , 1400 , 335 },
{0 x1c, 1366 , 1535 , 1450 , 335 },
{0 x2c, 1414 , 1588 , 1500 , 335 },
{0 x3d, 1461 , 1640 , 1550 , 208 },
{0 x0d, 1509 , 1693 , 1600 , 214 },
{0 x1d, 1556 , 1745 , 1650 , 221 },
{0 x2e, 1604 , 1798 , 1700 , 228 },
{0 x3e, 1651 , 1850 , 1750 , 234 },
{0 x0e, 1699 , 1903 , 1800 , 241 },
{0 x1e, 1746 , 1955 , 1850 , 248 },
{0 x2f, 1794 , 2008 , 1900 , 255 },
{0 x3f, 1841 , 2060 , 1950 , 261 },
{0 x0f, 1889 , 2113 , 2000 , 268 },
{0 x40, 1936 , 2165 , 2050 , 275 },
{0 x41, 1984 , 2218 , 2100 , 281 },
{0 x42, 2031 , 2270 , 2150 , 288 },
{0 x43, 2079 , 2323 , 2200 , 294 },
{0 x44, 2126 , 2375 , 2250 , 302 },
{0 x45, 2174 , 2428 , 2300 , 308 },
{0 x46, 2221 , 2480 , 2350 , 315 },
{0 x47, 2269 , 2500 , 2400 , 321 },
{0 x48, 2316 , 2500 , 2450 , 328 },
{0 x49, 2364 , 2500 , 2500 , 335 }
};
static u16 get_hsfreq_by_mbps(u32 mbps)
{
unsigned int i = DPHY_FREQ_RANGE_NUM;
while (i--) {
if (freqranges[i].default_mbps == mbps ||
(mbps >= freqranges[i].min && mbps <= freqranges[i].max))
return i;
}
return DPHY_FREQ_RANGE_INVALID_INDEX;
}
static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys,
u32 phy_id, u32 mbps)
{
struct ipu6_bus_device *adev = isys->adev;
struct device *dev = &adev->auxdev.dev;
struct ipu6_device *isp = adev->isp;
u32 cfg_clk_freqrange;
u32 osc_freq_target;
u32 index;
dev_dbg(dev, "config Dphy %u with %u mbps" , phy_id, mbps);
index = get_hsfreq_by_mbps(mbps);
if (index == DPHY_FREQ_RANGE_INVALID_INDEX) {
dev_err(dev, "link freq not found for mbps %u" , mbps);
return -EINVAL;
}
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE,
freqranges[index].hsfreq, 0 , 7 );
/* Force termination Calibration */
if (isys->phy_termcal_val) {
dwc_dphy_ifc_write_mask(isys, phy_id, 0 x20a, 0 x1, 0 , 1 );
dwc_dphy_ifc_write_mask(isys, phy_id, 0 x209, 0 x3, 0 , 2 );
dwc_dphy_ifc_write_mask(isys, phy_id, 0 x209,
isys->phy_termcal_val, 4 , 4 );
}
/*
* Enable override to configure the DDL target oscillation
* frequency on bit 0 of register 0xe4
*/
dwc_dphy_ifc_write_mask(isys, phy_id, 0 xe4, 0 x1, 0 , 1 );
/*
* configure registers 0xe2, 0xe3 with the
* appropriate DDL target oscillation frequency
* 0x1cc(460)
*/
osc_freq_target = freqranges[index].osc_freq_target;
dwc_dphy_ifc_write_mask(isys, phy_id, 0 xe2,
osc_freq_target & 0 xff, 0 , 8 );
dwc_dphy_ifc_write_mask(isys, phy_id, 0 xe3,
(osc_freq_target >> 8 ) & 0 xf, 0 , 4 );
if (mbps < 1500 ) {
/* deskew_polarity_rw, for < 1.5Gbps */
dwc_dphy_ifc_write_mask(isys, phy_id, 0 x8, 0 x1, 5 , 1 );
}
/*
* Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4]
* (38.4 - 17) * 4 = ~85 (0x55)
*/
cfg_clk_freqrange = (isp->buttress.ref_clk - 170 ) * 4 / 10 ;
dev_dbg(dev, "ref_clk = %u clk_freqrange = %u" ,
isp->buttress.ref_clk, cfg_clk_freqrange);
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE,
cfg_clk_freqrange, 0 , 8 );
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0 x1, 4 , 1 );
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0 x1, 8 , 1 );
return 0 ;
}
static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master,
u32 slave, u32 mbps)
{
/* Config mastermacro */
dwc_dphy_ifc_write_mask(isys, master, 0 x133, 0 x1, 0 , 1 );
dwc_dphy_ifc_write_mask(isys, slave, 0 x133, 0 x0, 0 , 1 );
/* Config master PHY clk lane to drive long channel clk */
dwc_dphy_ifc_write_mask(isys, master, 0 x307, 0 x1, 2 , 1 );
dwc_dphy_ifc_write_mask(isys, slave, 0 x307, 0 x0, 2 , 1 );
/* Config both PHYs data lanes to get clk from long channel */
dwc_dphy_ifc_write_mask(isys, master, 0 x508, 0 x1, 5 , 1 );
dwc_dphy_ifc_write_mask(isys, slave, 0 x508, 0 x1, 5 , 1 );
dwc_dphy_ifc_write_mask(isys, master, 0 x708, 0 x1, 5 , 1 );
dwc_dphy_ifc_write_mask(isys, slave, 0 x708, 0 x1, 5 , 1 );
/* Config slave PHY clk lane to bypass long channel clk to DDR clk */
dwc_dphy_ifc_write_mask(isys, master, 0 x308, 0 x0, 3 , 1 );
dwc_dphy_ifc_write_mask(isys, slave, 0 x308, 0 x1, 3 , 1 );
/* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */
dwc_dphy_ifc_write_mask(isys, slave, 0 xe0, 0 x3, 0 , 2 );
/* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */
dwc_dphy_ifc_write_mask(isys, slave, 0 xe1, 0 x1, 1 , 1 );
dwc_dphy_ifc_write_mask(isys, slave, 0 x307, 0 x1, 3 , 1 );
/* Turn off slave PHY LP-RX clk lane */
dwc_dphy_ifc_write_mask(isys, slave, 0 x304, 0 x1, 7 , 1 );
dwc_dphy_ifc_write_mask(isys, slave, 0 x305, 0 xa, 0 , 5 );
}
#define PHY_E 4
static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id)
{
struct device *dev = &isys->adev->auxdev.dev;
u32 rescal_done;
int ret;
ret = dwc_dphy_pwr_up(isys, phy_id);
if (ret != 0 ) {
dev_err(dev, "Dphy %u power up failed(%d)" , phy_id, ret);
return ret;
}
/* reset forcerxmode */
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0 , 4 , 1 );
dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0 , 8 , 1 );
dev_dbg(dev, "Dphy %u is ready!" , phy_id);
if (phy_id != PHY_E || isys->phy_termcal_val)
return 0 ;
usleep_range(100 , 200 );
rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0 x221, 7 , 1 );
if (rescal_done) {
isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id,
0 x220, 2 , 4 );
dev_dbg(dev, "termcal done with value = %u" ,
isys->phy_termcal_val);
}
return 0 ;
}
static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id)
{
dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u" , phy_id);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0 );
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0 );
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE,
TEST_IFC_ACCESS_MODE_FSM);
dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
TEST_IFC_REQ_RESET);
}
int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
struct ipu6_isys_csi2_config *cfg,
const struct ipu6_isys_csi2_timing *timing,
bool on)
{
struct device *dev = &isys->adev->auxdev.dev;
void __iomem *isys_base = isys->pdata->base;
u32 phy_id, primary, secondary;
u32 nlanes, port, mbps;
s64 link_freq;
int ret;
port = cfg->port;
if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
dev_warn(dev, "invalid port ID %d\n" , port);
return -EINVAL;
}
nlanes = cfg->nlanes;
/* only port 0, 2 and 4 support 4 lanes */
if (nlanes == 4 && port % 2 ) {
dev_err(dev, "invalid csi-port %u with %u lanes\n" , port,
nlanes);
return -EINVAL;
}
link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]);
if (link_freq < 0 ) {
dev_err(dev, "get link freq failed(%lld).\n" , link_freq);
return link_freq;
}
mbps = div_u64(link_freq, 500000 );
phy_id = port;
primary = port & ~1 ;
secondary = primary + 1 ;
if (on) {
if (nlanes == 4 ) {
dev_dbg(dev, "config phy %u and %u in aggr mode\n" ,
primary, secondary);
ipu6_isys_dwc_phy_reset(isys, primary);
ipu6_isys_dwc_phy_reset(isys, secondary);
ipu6_isys_dwc_phy_aggr_setup(isys, primary,
secondary, mbps);
ret = ipu6_isys_dwc_phy_config(isys, primary, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary);
return ret;
}
dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n" ,
phy_id, nlanes);
ipu6_isys_dwc_phy_reset(isys, phy_id);
ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps);
if (ret)
return ret;
ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id);
return ret;
}
if (nlanes == 4 ) {
dev_dbg(dev, "Power down phy %u and phy %u for port %u\n" ,
primary, secondary, port);
ipu6_isys_dwc_phy_reset(isys, secondary);
ipu6_isys_dwc_phy_reset(isys, primary);
return 0 ;
}
dev_dbg(dev, "Powerdown phy %u with %u lanes\n" , phy_id, nlanes);
ipu6_isys_dwc_phy_reset(isys, phy_id);
return 0 ;
}
Messung V0.5 in Prozent C=93 H=88 G=90
¤ Dauer der Verarbeitung: 0.13 Sekunden
(vorverarbeitet am 2026-06-08)
¤
*© Formatika GbR, Deutschland