|  | // 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)			(0x238038 + 0x34 * (i)) | 
|  | #define IPU6_DWC_DPHY_RSTZ			0x00 | 
|  | #define IPU6_DWC_DPHY_SHUTDOWNZ			0x04 | 
|  | #define IPU6_DWC_DPHY_HSFREQRANGE		0x08 | 
|  | #define IPU6_DWC_DPHY_CFGCLKFREQRANGE		0x0c | 
|  | #define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE	0x10 | 
|  | #define IPU6_DWC_DPHY_TEST_IFC_REQ		0x14 | 
|  | #define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION	0x18 | 
|  | #define IPU6_DWC_DPHY_DFT_CTRL0			0x28 | 
|  | #define IPU6_DWC_DPHY_DFT_CTRL1			0x2c | 
|  | #define IPU6_DWC_DPHY_DFT_CTRL2			0x30 | 
|  |  | 
|  | /* | 
|  | * 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%lx = 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%lx = 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 & 0xff; | 
|  | *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, 0x1e, 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	(0xff) | 
|  | static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = { | 
|  | {0x00,	80,	97,	80,	335}, | 
|  | {0x10,	80,	107,	90,	335}, | 
|  | {0x20,	84,	118,	100,	335}, | 
|  | {0x30,	93,	128,	110,	335}, | 
|  | {0x01,	103,	139,	120,	335}, | 
|  | {0x11,	112,	149,	130,	335}, | 
|  | {0x21,	122,	160,	140,	335}, | 
|  | {0x31,	131,	170,	150,	335}, | 
|  | {0x02,	141,	181,	160,	335}, | 
|  | {0x12,	150,	191,	170,	335}, | 
|  | {0x22,	160,	202,	180,	335}, | 
|  | {0x32,	169,	212,	190,	335}, | 
|  | {0x03,	183,	228,	205,	335}, | 
|  | {0x13,	198,	244,	220,	335}, | 
|  | {0x23,	212,	259,	235,	335}, | 
|  | {0x33,	226,	275,	250,	335}, | 
|  | {0x04,	250,	301,	275,	335}, | 
|  | {0x14,	274,	328,	300,	335}, | 
|  | {0x25,	297,	354,	325,	335}, | 
|  | {0x35,	321,	380,	350,	335}, | 
|  | {0x05,	369,	433,	400,	335}, | 
|  | {0x16,	416,	485,	450,	335}, | 
|  | {0x26,	464,	538,	500,	335}, | 
|  | {0x37,	511,	590,	550,	335}, | 
|  | {0x07,	559,	643,	600,	335}, | 
|  | {0x18,	606,	695,	650,	335}, | 
|  | {0x28,	654,	748,	700,	335}, | 
|  | {0x39,	701,	800,	750,	335}, | 
|  | {0x09,	749,	853,	800,	335}, | 
|  | {0x19,	796,	905,	850,	335}, | 
|  | {0x29,	844,	958,	900,	335}, | 
|  | {0x3a,	891,	1010,	950,	335}, | 
|  | {0x0a,	939,	1063,	1000,	335}, | 
|  | {0x1a,	986,	1115,	1050,	335}, | 
|  | {0x2a,	1034,	1168,	1100,	335}, | 
|  | {0x3b,	1081,	1220,	1150,	335}, | 
|  | {0x0b,	1129,	1273,	1200,	335}, | 
|  | {0x1b,	1176,	1325,	1250,	335}, | 
|  | {0x2b,	1224,	1378,	1300,	335}, | 
|  | {0x3c,	1271,	1430,	1350,	335}, | 
|  | {0x0c,	1319,	1483,	1400,	335}, | 
|  | {0x1c,	1366,	1535,	1450,	335}, | 
|  | {0x2c,	1414,	1588,	1500,	335}, | 
|  | {0x3d,	1461,	1640,	1550,	208}, | 
|  | {0x0d,	1509,	1693,	1600,	214}, | 
|  | {0x1d,	1556,	1745,	1650,	221}, | 
|  | {0x2e,	1604,	1798,	1700,	228}, | 
|  | {0x3e,	1651,	1850,	1750,	234}, | 
|  | {0x0e,	1699,	1903,	1800,	241}, | 
|  | {0x1e,	1746,	1955,	1850,	248}, | 
|  | {0x2f,	1794,	2008,	1900,	255}, | 
|  | {0x3f,	1841,	2060,	1950,	261}, | 
|  | {0x0f,	1889,	2113,	2000,	268}, | 
|  | {0x40,	1936,	2165,	2050,	275}, | 
|  | {0x41,	1984,	2218,	2100,	281}, | 
|  | {0x42,	2031,	2270,	2150,	288}, | 
|  | {0x43,	2079,	2323,	2200,	294}, | 
|  | {0x44,	2126,	2375,	2250,	302}, | 
|  | {0x45,	2174,	2428,	2300,	308}, | 
|  | {0x46,	2221,	2480,	2350,	315}, | 
|  | {0x47,	2269,	2500,	2400,	321}, | 
|  | {0x48,	2316,	2500,	2450,	328}, | 
|  | {0x49,	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, 0x20a, 0x1, 0, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2); | 
|  | dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, | 
|  | 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, 0xe4, 0x1, 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, 0xe2, | 
|  | osc_freq_target & 0xff, 0, 8); | 
|  | dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3, | 
|  | (osc_freq_target >> 8) & 0xf, 0, 4); | 
|  |  | 
|  | if (mbps < 1500) { | 
|  | /* deskew_polarity_rw, for < 1.5Gbps */ | 
|  | dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 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, 0x1, 4, 1); | 
|  | dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 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, 0x133, 0x1, 0, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1); | 
|  |  | 
|  | /* Config master PHY clk lane to drive long channel clk */ | 
|  | dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1); | 
|  |  | 
|  | /* Config both PHYs data lanes to get clk from long channel */ | 
|  | dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1); | 
|  |  | 
|  | /* Config slave PHY clk lane to bypass long channel clk to DDR clk */ | 
|  | dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1); | 
|  |  | 
|  | /* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */ | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2); | 
|  |  | 
|  | /* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */ | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1); | 
|  |  | 
|  | /* Turn off slave PHY LP-RX clk lane */ | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1); | 
|  | dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 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, 0x221, 7, 1); | 
|  | if (rescal_done) { | 
|  | isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id, | 
|  | 0x220, 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; | 
|  | } |