forked from Minki/linux
e2248617ec
New revision (v3) of QMP PHY uses different offsets for almost all of the registers. Hence, move these definitions to header file so that updated offsets can be added for QMP v3. Signed-off-by: Manu Gautam <mgautam@codeaurora.org> Reviewed-by: Vivek Gautam <vivek.gautam@codeaurora.org> Signed-off-by: Kishon Vijay Abraham I <kishon@ti.com>
1136 lines
33 KiB
C
1136 lines
33 KiB
C
/*
|
|
* Copyright (c) 2017, The Linux Foundation. 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/clk.h>
|
|
#include <linux/clk-provider.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/io.h>
|
|
#include <linux/iopoll.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/of_address.h>
|
|
#include <linux/phy/phy.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <dt-bindings/phy/phy.h>
|
|
|
|
#include "phy-qcom-qmp.h"
|
|
|
|
/* QPHY_SW_RESET bit */
|
|
#define SW_RESET BIT(0)
|
|
/* QPHY_POWER_DOWN_CONTROL */
|
|
#define SW_PWRDN BIT(0)
|
|
#define REFCLK_DRV_DSBL BIT(1)
|
|
/* QPHY_START_CONTROL bits */
|
|
#define SERDES_START BIT(0)
|
|
#define PCS_START BIT(1)
|
|
#define PLL_READY_GATE_EN BIT(3)
|
|
/* QPHY_PCS_STATUS bit */
|
|
#define PHYSTATUS BIT(6)
|
|
/* QPHY_COM_PCS_READY_STATUS bit */
|
|
#define PCS_READY BIT(0)
|
|
|
|
#define PHY_INIT_COMPLETE_TIMEOUT 1000
|
|
#define POWER_DOWN_DELAY_US_MIN 10
|
|
#define POWER_DOWN_DELAY_US_MAX 11
|
|
|
|
#define MAX_PROP_NAME 32
|
|
|
|
struct qmp_phy_init_tbl {
|
|
unsigned int offset;
|
|
unsigned int val;
|
|
/*
|
|
* register part of layout ?
|
|
* if yes, then offset gives index in the reg-layout
|
|
*/
|
|
int in_layout;
|
|
};
|
|
|
|
#define QMP_PHY_INIT_CFG(o, v) \
|
|
{ \
|
|
.offset = o, \
|
|
.val = v, \
|
|
}
|
|
|
|
#define QMP_PHY_INIT_CFG_L(o, v) \
|
|
{ \
|
|
.offset = o, \
|
|
.val = v, \
|
|
.in_layout = 1, \
|
|
}
|
|
|
|
/* set of registers with offsets different per-PHY */
|
|
enum qphy_reg_layout {
|
|
/* Common block control registers */
|
|
QPHY_COM_SW_RESET,
|
|
QPHY_COM_POWER_DOWN_CONTROL,
|
|
QPHY_COM_START_CONTROL,
|
|
QPHY_COM_PCS_READY_STATUS,
|
|
/* PCS registers */
|
|
QPHY_PLL_LOCK_CHK_DLY_TIME,
|
|
QPHY_FLL_CNTRL1,
|
|
QPHY_FLL_CNTRL2,
|
|
QPHY_FLL_CNT_VAL_L,
|
|
QPHY_FLL_CNT_VAL_H_TOL,
|
|
QPHY_FLL_MAN_CODE,
|
|
QPHY_SW_RESET,
|
|
QPHY_START_CTRL,
|
|
QPHY_PCS_READY_STATUS,
|
|
};
|
|
|
|
static const unsigned int pciephy_regs_layout[] = {
|
|
[QPHY_COM_SW_RESET] = 0x400,
|
|
[QPHY_COM_POWER_DOWN_CONTROL] = 0x404,
|
|
[QPHY_COM_START_CONTROL] = 0x408,
|
|
[QPHY_COM_PCS_READY_STATUS] = 0x448,
|
|
[QPHY_PLL_LOCK_CHK_DLY_TIME] = 0xa8,
|
|
[QPHY_FLL_CNTRL1] = 0xc4,
|
|
[QPHY_FLL_CNTRL2] = 0xc8,
|
|
[QPHY_FLL_CNT_VAL_L] = 0xcc,
|
|
[QPHY_FLL_CNT_VAL_H_TOL] = 0xd0,
|
|
[QPHY_FLL_MAN_CODE] = 0xd4,
|
|
[QPHY_SW_RESET] = 0x00,
|
|
[QPHY_START_CTRL] = 0x08,
|
|
[QPHY_PCS_READY_STATUS] = 0x174,
|
|
};
|
|
|
|
static const unsigned int usb3phy_regs_layout[] = {
|
|
[QPHY_FLL_CNTRL1] = 0xc0,
|
|
[QPHY_FLL_CNTRL2] = 0xc4,
|
|
[QPHY_FLL_CNT_VAL_L] = 0xc8,
|
|
[QPHY_FLL_CNT_VAL_H_TOL] = 0xcc,
|
|
[QPHY_FLL_MAN_CODE] = 0xd0,
|
|
[QPHY_SW_RESET] = 0x00,
|
|
[QPHY_START_CTRL] = 0x08,
|
|
[QPHY_PCS_READY_STATUS] = 0x17c,
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_pcie_serdes_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1c),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x42),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0xff),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x1f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0x0a),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x09),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x1a),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x0a),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x02),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x04),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x02),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x15),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_RESCODE_DIV_NUM, 0x40),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_pcie_tx_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_pcie_rx_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_BAND, 0x18),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN_HALF, 0x04),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x19),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_pcie_pcs_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QPHY_RX_IDLE_DTCT_CNTRL, 0x4c),
|
|
QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x00),
|
|
QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x01),
|
|
|
|
QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x05),
|
|
|
|
QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x05),
|
|
QMP_PHY_INIT_CFG(QPHY_POWER_DOWN_CONTROL, 0x02),
|
|
QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG4, 0x00),
|
|
QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG1, 0xa3),
|
|
QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0x0e),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_usb3_serdes_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0x14),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x08),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x30),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x06),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0x0f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0x0f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x04),
|
|
/* PLL and Loop filter settings */
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x03),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0x0b),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0x15),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0x34),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_CFG, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0x0a),
|
|
/* SSC settings */
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x01),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x00),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0xde),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x07),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_usb3_tx_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12),
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x06),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_usb3_rx_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_FASTLOCK_FO_GAIN, 0x0b),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x04),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x02),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x4c),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xbb),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQ_OFFSET_ADAPTOR_CNTRL1, 0x77),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_OFFSET_ADAPTOR_CNTRL2, 0x80),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x03),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_LVL, 0x18),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x16),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl msm8996_usb3_pcs_tbl[] = {
|
|
/* FLL settings */
|
|
QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL2, 0x03),
|
|
QMP_PHY_INIT_CFG_L(QPHY_FLL_CNTRL1, 0x02),
|
|
QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_L, 0x09),
|
|
QMP_PHY_INIT_CFG_L(QPHY_FLL_CNT_VAL_H_TOL, 0x42),
|
|
QMP_PHY_INIT_CFG_L(QPHY_FLL_MAN_CODE, 0x85),
|
|
|
|
/* Lock Det settings */
|
|
QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG1, 0xd1),
|
|
QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG2, 0x1f),
|
|
QMP_PHY_INIT_CFG(QPHY_LOCK_DETECT_CONFIG3, 0x47),
|
|
QMP_PHY_INIT_CFG(QPHY_POWER_STATE_CONFIG2, 0x08),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl ipq8074_pcie_serdes_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x18),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_ENABLE1, 0x10),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TRIM, 0xf),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP_EN, 0x1),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_MAP, 0x0),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER1, 0x1f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_TIMER2, 0x3f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CMN_CONFIG, 0x6),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_IVCO, 0xf),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_HSCLK_SEL, 0x0),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SVS_MODE_CLK_SEL, 0x1),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CORE_CLK_EN, 0x20),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CORECLK_DIV, 0xa),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_RESETSM_CNTRL, 0x20),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BG_TIMER, 0xa),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_EN_SEL, 0xa),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DEC_START_MODE0, 0x82),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START3_MODE0, 0x3),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START2_MODE0, 0x55),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_DIV_FRAC_START1_MODE0, 0x55),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP3_MODE0, 0x0),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP2_MODE0, 0xD),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_LOCK_CMP1_MODE0, 0xD04),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_SELECT, 0x33),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYS_CLK_CTRL, 0x2),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CP_CTRL_MODE0, 0xb),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_RCTRL_MODE0, 0x16),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_PLL_CCTRL_MODE0, 0x28),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN1_MODE0, 0x0),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_INTEGLOOP_GAIN0_MODE0, 0x80),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_BIAS_EN_CTRL_BY_PSM, 0x1),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_VCO_TUNE_CTRL, 0xa),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_EN_CENTER, 0x1),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER1, 0x31),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_PER2, 0x1),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER1, 0x2),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_ADJ_PER2, 0x0),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE1, 0x2f),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_SSC_STEP_SIZE2, 0x19),
|
|
QMP_PHY_INIT_CFG(QSERDES_COM_CLK_EP_DIV, 0x19),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_CNTRL, 0x7),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl ipq8074_pcie_tx_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x45),
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_LANE_MODE, 0x6),
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_RES_CODE_LANE_OFFSET, 0x2),
|
|
QMP_PHY_INIT_CFG(QSERDES_TX_RCV_DETECT_LVL_2, 0x12),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl ipq8074_pcie_rx_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_ENABLES, 0x1c),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_SIGDET_DEGLITCH_CNTRL, 0x14),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL2, 0x1),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL3, 0x0),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_RX_EQU_ADAPTOR_CNTRL4, 0xdb),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_SATURATION_AND_ENABLE, 0x4b),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN, 0x4),
|
|
QMP_PHY_INIT_CFG(QSERDES_RX_UCDR_SO_GAIN_HALF, 0x4),
|
|
};
|
|
|
|
static const struct qmp_phy_init_tbl ipq8074_pcie_pcs_tbl[] = {
|
|
QMP_PHY_INIT_CFG(QPHY_ENDPOINT_REFCLK_DRIVE, 0x4),
|
|
QMP_PHY_INIT_CFG(QPHY_OSC_DTCT_ACTIONS, 0x0),
|
|
QMP_PHY_INIT_CFG(QPHY_PWRUP_RESET_DLY_TIME_AUXCLK, 0x40),
|
|
QMP_PHY_INIT_CFG(QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_MSB, 0x0),
|
|
QMP_PHY_INIT_CFG(QPHY_L1SS_WAKEUP_DLY_TIME_AUXCLK_LSB, 0x40),
|
|
QMP_PHY_INIT_CFG(QPHY_PLL_LOCK_CHK_DLY_TIME_AUXCLK_LSB, 0x0),
|
|
QMP_PHY_INIT_CFG(QPHY_LP_WAKEUP_DLY_TIME_AUXCLK, 0x40),
|
|
QMP_PHY_INIT_CFG_L(QPHY_PLL_LOCK_CHK_DLY_TIME, 0x73),
|
|
QMP_PHY_INIT_CFG(QPHY_RX_SIGDET_LVL, 0x99),
|
|
QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M6DB_V0, 0x15),
|
|
QMP_PHY_INIT_CFG(QPHY_TXDEEMPH_M3P5DB_V0, 0xe),
|
|
QMP_PHY_INIT_CFG_L(QPHY_SW_RESET, 0x0),
|
|
QMP_PHY_INIT_CFG_L(QPHY_START_CTRL, 0x3),
|
|
};
|
|
|
|
/* struct qmp_phy_cfg - per-PHY initialization config */
|
|
struct qmp_phy_cfg {
|
|
/* phy-type - PCIE/UFS/USB */
|
|
unsigned int type;
|
|
/* number of lanes provided by phy */
|
|
int nlanes;
|
|
|
|
/* Init sequence for PHY blocks - serdes, tx, rx, pcs */
|
|
const struct qmp_phy_init_tbl *serdes_tbl;
|
|
int serdes_tbl_num;
|
|
const struct qmp_phy_init_tbl *tx_tbl;
|
|
int tx_tbl_num;
|
|
const struct qmp_phy_init_tbl *rx_tbl;
|
|
int rx_tbl_num;
|
|
const struct qmp_phy_init_tbl *pcs_tbl;
|
|
int pcs_tbl_num;
|
|
|
|
/* clock ids to be requested */
|
|
const char * const *clk_list;
|
|
int num_clks;
|
|
/* resets to be requested */
|
|
const char * const *reset_list;
|
|
int num_resets;
|
|
/* regulators to be requested */
|
|
const char * const *vreg_list;
|
|
int num_vregs;
|
|
|
|
/* array of registers with different offsets */
|
|
const unsigned int *regs;
|
|
|
|
unsigned int start_ctrl;
|
|
unsigned int pwrdn_ctrl;
|
|
unsigned int mask_pcs_ready;
|
|
unsigned int mask_com_pcs_ready;
|
|
|
|
/* true, if PHY has a separate PHY_COM control block */
|
|
bool has_phy_com_ctrl;
|
|
/* true, if PHY has a reset for individual lanes */
|
|
bool has_lane_rst;
|
|
/* true, if PHY needs delay after POWER_DOWN */
|
|
bool has_pwrdn_delay;
|
|
/* power_down delay in usec */
|
|
int pwrdn_delay_min;
|
|
int pwrdn_delay_max;
|
|
};
|
|
|
|
/**
|
|
* struct qmp_phy - per-lane phy descriptor
|
|
*
|
|
* @phy: generic phy
|
|
* @tx: iomapped memory space for lane's tx
|
|
* @rx: iomapped memory space for lane's rx
|
|
* @pcs: iomapped memory space for lane's pcs
|
|
* @pipe_clk: pipe lock
|
|
* @index: lane index
|
|
* @qmp: QMP phy to which this lane belongs
|
|
* @lane_rst: lane's reset controller
|
|
*/
|
|
struct qmp_phy {
|
|
struct phy *phy;
|
|
void __iomem *tx;
|
|
void __iomem *rx;
|
|
void __iomem *pcs;
|
|
struct clk *pipe_clk;
|
|
unsigned int index;
|
|
struct qcom_qmp *qmp;
|
|
struct reset_control *lane_rst;
|
|
};
|
|
|
|
/**
|
|
* struct qcom_qmp - structure holding QMP phy block attributes
|
|
*
|
|
* @dev: device
|
|
* @serdes: iomapped memory space for phy's serdes
|
|
*
|
|
* @clks: array of clocks required by phy
|
|
* @resets: array of resets required by phy
|
|
* @vregs: regulator supplies bulk data
|
|
*
|
|
* @cfg: phy specific configuration
|
|
* @phys: array of per-lane phy descriptors
|
|
* @phy_mutex: mutex lock for PHY common block initialization
|
|
* @init_count: phy common block initialization count
|
|
*/
|
|
struct qcom_qmp {
|
|
struct device *dev;
|
|
void __iomem *serdes;
|
|
|
|
struct clk_bulk_data *clks;
|
|
struct reset_control **resets;
|
|
struct regulator_bulk_data *vregs;
|
|
|
|
const struct qmp_phy_cfg *cfg;
|
|
struct qmp_phy **phys;
|
|
|
|
struct mutex phy_mutex;
|
|
int init_count;
|
|
};
|
|
|
|
static inline void qphy_setbits(void __iomem *base, u32 offset, u32 val)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = readl(base + offset);
|
|
reg |= val;
|
|
writel(reg, base + offset);
|
|
|
|
/* ensure that above write is through */
|
|
readl(base + offset);
|
|
}
|
|
|
|
static inline void qphy_clrbits(void __iomem *base, u32 offset, u32 val)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = readl(base + offset);
|
|
reg &= ~val;
|
|
writel(reg, base + offset);
|
|
|
|
/* ensure that above write is through */
|
|
readl(base + offset);
|
|
}
|
|
|
|
/* list of clocks required by phy */
|
|
static const char * const msm8996_phy_clk_l[] = {
|
|
"aux", "cfg_ahb", "ref",
|
|
};
|
|
|
|
/* list of resets */
|
|
static const char * const msm8996_pciephy_reset_l[] = {
|
|
"phy", "common", "cfg",
|
|
};
|
|
|
|
static const char * const msm8996_usb3phy_reset_l[] = {
|
|
"phy", "common",
|
|
};
|
|
|
|
/* list of regulators */
|
|
static const char * const msm8996_phy_vreg_l[] = {
|
|
"vdda-phy", "vdda-pll",
|
|
};
|
|
|
|
static const struct qmp_phy_cfg msm8996_pciephy_cfg = {
|
|
.type = PHY_TYPE_PCIE,
|
|
.nlanes = 3,
|
|
|
|
.serdes_tbl = msm8996_pcie_serdes_tbl,
|
|
.serdes_tbl_num = ARRAY_SIZE(msm8996_pcie_serdes_tbl),
|
|
.tx_tbl = msm8996_pcie_tx_tbl,
|
|
.tx_tbl_num = ARRAY_SIZE(msm8996_pcie_tx_tbl),
|
|
.rx_tbl = msm8996_pcie_rx_tbl,
|
|
.rx_tbl_num = ARRAY_SIZE(msm8996_pcie_rx_tbl),
|
|
.pcs_tbl = msm8996_pcie_pcs_tbl,
|
|
.pcs_tbl_num = ARRAY_SIZE(msm8996_pcie_pcs_tbl),
|
|
.clk_list = msm8996_phy_clk_l,
|
|
.num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
|
|
.reset_list = msm8996_pciephy_reset_l,
|
|
.num_resets = ARRAY_SIZE(msm8996_pciephy_reset_l),
|
|
.vreg_list = msm8996_phy_vreg_l,
|
|
.num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l),
|
|
.regs = pciephy_regs_layout,
|
|
|
|
.start_ctrl = PCS_START | PLL_READY_GATE_EN,
|
|
.pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL,
|
|
.mask_com_pcs_ready = PCS_READY,
|
|
|
|
.has_phy_com_ctrl = true,
|
|
.has_lane_rst = true,
|
|
.has_pwrdn_delay = true,
|
|
.pwrdn_delay_min = POWER_DOWN_DELAY_US_MIN,
|
|
.pwrdn_delay_max = POWER_DOWN_DELAY_US_MAX,
|
|
};
|
|
|
|
static const struct qmp_phy_cfg msm8996_usb3phy_cfg = {
|
|
.type = PHY_TYPE_USB3,
|
|
.nlanes = 1,
|
|
|
|
.serdes_tbl = msm8996_usb3_serdes_tbl,
|
|
.serdes_tbl_num = ARRAY_SIZE(msm8996_usb3_serdes_tbl),
|
|
.tx_tbl = msm8996_usb3_tx_tbl,
|
|
.tx_tbl_num = ARRAY_SIZE(msm8996_usb3_tx_tbl),
|
|
.rx_tbl = msm8996_usb3_rx_tbl,
|
|
.rx_tbl_num = ARRAY_SIZE(msm8996_usb3_rx_tbl),
|
|
.pcs_tbl = msm8996_usb3_pcs_tbl,
|
|
.pcs_tbl_num = ARRAY_SIZE(msm8996_usb3_pcs_tbl),
|
|
.clk_list = msm8996_phy_clk_l,
|
|
.num_clks = ARRAY_SIZE(msm8996_phy_clk_l),
|
|
.reset_list = msm8996_usb3phy_reset_l,
|
|
.num_resets = ARRAY_SIZE(msm8996_usb3phy_reset_l),
|
|
.vreg_list = msm8996_phy_vreg_l,
|
|
.num_vregs = ARRAY_SIZE(msm8996_phy_vreg_l),
|
|
.regs = usb3phy_regs_layout,
|
|
|
|
.start_ctrl = SERDES_START | PCS_START,
|
|
.pwrdn_ctrl = SW_PWRDN,
|
|
.mask_pcs_ready = PHYSTATUS,
|
|
};
|
|
|
|
/* list of resets */
|
|
static const char * const ipq8074_pciephy_reset_l[] = {
|
|
"phy", "common",
|
|
};
|
|
|
|
static const struct qmp_phy_cfg ipq8074_pciephy_cfg = {
|
|
.type = PHY_TYPE_PCIE,
|
|
.nlanes = 1,
|
|
|
|
.serdes_tbl = ipq8074_pcie_serdes_tbl,
|
|
.serdes_tbl_num = ARRAY_SIZE(ipq8074_pcie_serdes_tbl),
|
|
.tx_tbl = ipq8074_pcie_tx_tbl,
|
|
.tx_tbl_num = ARRAY_SIZE(ipq8074_pcie_tx_tbl),
|
|
.rx_tbl = ipq8074_pcie_rx_tbl,
|
|
.rx_tbl_num = ARRAY_SIZE(ipq8074_pcie_rx_tbl),
|
|
.pcs_tbl = ipq8074_pcie_pcs_tbl,
|
|
.pcs_tbl_num = ARRAY_SIZE(ipq8074_pcie_pcs_tbl),
|
|
.clk_list = NULL,
|
|
.num_clks = 0,
|
|
.reset_list = ipq8074_pciephy_reset_l,
|
|
.num_resets = ARRAY_SIZE(ipq8074_pciephy_reset_l),
|
|
.vreg_list = NULL,
|
|
.num_vregs = 0,
|
|
.regs = pciephy_regs_layout,
|
|
|
|
.start_ctrl = SERDES_START | PCS_START,
|
|
.pwrdn_ctrl = SW_PWRDN | REFCLK_DRV_DSBL,
|
|
.mask_pcs_ready = PHYSTATUS,
|
|
|
|
.has_phy_com_ctrl = false,
|
|
.has_lane_rst = false,
|
|
.has_pwrdn_delay = true,
|
|
.pwrdn_delay_min = 995, /* us */
|
|
.pwrdn_delay_max = 1005, /* us */
|
|
};
|
|
|
|
static void qcom_qmp_phy_configure(void __iomem *base,
|
|
const unsigned int *regs,
|
|
const struct qmp_phy_init_tbl tbl[],
|
|
int num)
|
|
{
|
|
int i;
|
|
const struct qmp_phy_init_tbl *t = tbl;
|
|
|
|
if (!t)
|
|
return;
|
|
|
|
for (i = 0; i < num; i++, t++) {
|
|
if (t->in_layout)
|
|
writel(t->val, base + regs[t->offset]);
|
|
else
|
|
writel(t->val, base + t->offset);
|
|
}
|
|
}
|
|
|
|
static int qcom_qmp_phy_poweron(struct phy *phy)
|
|
{
|
|
struct qmp_phy *qphy = phy_get_drvdata(phy);
|
|
struct qcom_qmp *qmp = qphy->qmp;
|
|
int ret;
|
|
|
|
ret = clk_prepare_enable(qphy->pipe_clk);
|
|
if (ret)
|
|
dev_err(qmp->dev, "pipe_clk enable failed, err=%d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qcom_qmp_phy_com_init(struct qcom_qmp *qmp)
|
|
{
|
|
const struct qmp_phy_cfg *cfg = qmp->cfg;
|
|
void __iomem *serdes = qmp->serdes;
|
|
int ret, i;
|
|
|
|
mutex_lock(&qmp->phy_mutex);
|
|
if (qmp->init_count++) {
|
|
mutex_unlock(&qmp->phy_mutex);
|
|
return 0;
|
|
}
|
|
|
|
/* turn on regulator supplies */
|
|
ret = regulator_bulk_enable(cfg->num_vregs, qmp->vregs);
|
|
if (ret) {
|
|
dev_err(qmp->dev, "failed to enable regulators, err=%d\n", ret);
|
|
goto err_reg_enable;
|
|
}
|
|
|
|
for (i = 0; i < cfg->num_resets; i++) {
|
|
ret = reset_control_assert(qmp->resets[i]);
|
|
if (ret) {
|
|
dev_err(qmp->dev, "%s reset assert failed\n",
|
|
cfg->reset_list[i]);
|
|
goto err_rst_assert;
|
|
}
|
|
}
|
|
|
|
for (i = cfg->num_resets - 1; i >= 0; i--) {
|
|
ret = reset_control_deassert(qmp->resets[i]);
|
|
if (ret) {
|
|
dev_err(qmp->dev, "%s reset deassert failed\n",
|
|
qmp->cfg->reset_list[i]);
|
|
goto err_rst;
|
|
}
|
|
}
|
|
|
|
ret = clk_bulk_prepare_enable(cfg->num_clks, qmp->clks);
|
|
if (ret) {
|
|
dev_err(qmp->dev, "failed to enable clks, err=%d\n", ret);
|
|
goto err_rst;
|
|
}
|
|
|
|
if (cfg->has_phy_com_ctrl)
|
|
qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL],
|
|
SW_PWRDN);
|
|
|
|
/* Serdes configuration */
|
|
qcom_qmp_phy_configure(serdes, cfg->regs, cfg->serdes_tbl,
|
|
cfg->serdes_tbl_num);
|
|
|
|
if (cfg->has_phy_com_ctrl) {
|
|
void __iomem *status;
|
|
unsigned int mask, val;
|
|
|
|
qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET], SW_RESET);
|
|
qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
|
|
SERDES_START | PCS_START);
|
|
|
|
status = serdes + cfg->regs[QPHY_COM_PCS_READY_STATUS];
|
|
mask = cfg->mask_com_pcs_ready;
|
|
|
|
ret = readl_poll_timeout(status, val, (val & mask), 10,
|
|
PHY_INIT_COMPLETE_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(qmp->dev,
|
|
"phy common block init timed-out\n");
|
|
goto err_com_init;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&qmp->phy_mutex);
|
|
|
|
return 0;
|
|
|
|
err_com_init:
|
|
clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks);
|
|
err_rst:
|
|
while (++i < cfg->num_resets)
|
|
reset_control_assert(qmp->resets[i]);
|
|
err_rst_assert:
|
|
regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
|
|
err_reg_enable:
|
|
mutex_unlock(&qmp->phy_mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qcom_qmp_phy_com_exit(struct qcom_qmp *qmp)
|
|
{
|
|
const struct qmp_phy_cfg *cfg = qmp->cfg;
|
|
void __iomem *serdes = qmp->serdes;
|
|
int i = cfg->num_resets;
|
|
|
|
mutex_lock(&qmp->phy_mutex);
|
|
if (--qmp->init_count) {
|
|
mutex_unlock(&qmp->phy_mutex);
|
|
return 0;
|
|
}
|
|
|
|
if (cfg->has_phy_com_ctrl) {
|
|
qphy_setbits(serdes, cfg->regs[QPHY_COM_START_CONTROL],
|
|
SERDES_START | PCS_START);
|
|
qphy_clrbits(serdes, cfg->regs[QPHY_COM_SW_RESET],
|
|
SW_RESET);
|
|
qphy_setbits(serdes, cfg->regs[QPHY_COM_POWER_DOWN_CONTROL],
|
|
SW_PWRDN);
|
|
}
|
|
|
|
while (--i >= 0)
|
|
reset_control_assert(qmp->resets[i]);
|
|
|
|
clk_bulk_disable_unprepare(cfg->num_clks, qmp->clks);
|
|
|
|
regulator_bulk_disable(cfg->num_vregs, qmp->vregs);
|
|
|
|
mutex_unlock(&qmp->phy_mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* PHY Initialization */
|
|
static int qcom_qmp_phy_init(struct phy *phy)
|
|
{
|
|
struct qmp_phy *qphy = phy_get_drvdata(phy);
|
|
struct qcom_qmp *qmp = qphy->qmp;
|
|
const struct qmp_phy_cfg *cfg = qmp->cfg;
|
|
void __iomem *tx = qphy->tx;
|
|
void __iomem *rx = qphy->rx;
|
|
void __iomem *pcs = qphy->pcs;
|
|
void __iomem *status;
|
|
unsigned int mask, val;
|
|
int ret;
|
|
|
|
dev_vdbg(qmp->dev, "Initializing QMP phy\n");
|
|
|
|
ret = qcom_qmp_phy_com_init(qmp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (cfg->has_lane_rst) {
|
|
ret = reset_control_deassert(qphy->lane_rst);
|
|
if (ret) {
|
|
dev_err(qmp->dev, "lane%d reset deassert failed\n",
|
|
qphy->index);
|
|
goto err_lane_rst;
|
|
}
|
|
}
|
|
|
|
/* Tx, Rx, and PCS configurations */
|
|
qcom_qmp_phy_configure(tx, cfg->regs, cfg->tx_tbl, cfg->tx_tbl_num);
|
|
qcom_qmp_phy_configure(rx, cfg->regs, cfg->rx_tbl, cfg->rx_tbl_num);
|
|
qcom_qmp_phy_configure(pcs, cfg->regs, cfg->pcs_tbl, cfg->pcs_tbl_num);
|
|
|
|
/*
|
|
* Pull out PHY from POWER DOWN state.
|
|
* This is active low enable signal to power-down PHY.
|
|
*/
|
|
qphy_setbits(pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl);
|
|
|
|
if (cfg->has_pwrdn_delay)
|
|
usleep_range(cfg->pwrdn_delay_min, cfg->pwrdn_delay_max);
|
|
|
|
/* Pull PHY out of reset state */
|
|
qphy_clrbits(pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
|
|
|
|
/* start SerDes and Phy-Coding-Sublayer */
|
|
qphy_setbits(pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
|
|
|
|
status = pcs + cfg->regs[QPHY_PCS_READY_STATUS];
|
|
mask = cfg->mask_pcs_ready;
|
|
|
|
ret = readl_poll_timeout(status, val, !(val & mask), 1,
|
|
PHY_INIT_COMPLETE_TIMEOUT);
|
|
if (ret) {
|
|
dev_err(qmp->dev, "phy initialization timed-out\n");
|
|
goto err_pcs_ready;
|
|
}
|
|
|
|
return ret;
|
|
|
|
err_pcs_ready:
|
|
if (cfg->has_lane_rst)
|
|
reset_control_assert(qphy->lane_rst);
|
|
err_lane_rst:
|
|
qcom_qmp_phy_com_exit(qmp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int qcom_qmp_phy_exit(struct phy *phy)
|
|
{
|
|
struct qmp_phy *qphy = phy_get_drvdata(phy);
|
|
struct qcom_qmp *qmp = qphy->qmp;
|
|
const struct qmp_phy_cfg *cfg = qmp->cfg;
|
|
|
|
clk_disable_unprepare(qphy->pipe_clk);
|
|
|
|
/* PHY reset */
|
|
qphy_setbits(qphy->pcs, cfg->regs[QPHY_SW_RESET], SW_RESET);
|
|
|
|
/* stop SerDes and Phy-Coding-Sublayer */
|
|
qphy_clrbits(qphy->pcs, cfg->regs[QPHY_START_CTRL], cfg->start_ctrl);
|
|
|
|
/* Put PHY into POWER DOWN state: active low */
|
|
qphy_clrbits(qphy->pcs, QPHY_POWER_DOWN_CONTROL, cfg->pwrdn_ctrl);
|
|
|
|
if (cfg->has_lane_rst)
|
|
reset_control_assert(qphy->lane_rst);
|
|
|
|
qcom_qmp_phy_com_exit(qmp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qcom_qmp_phy_vreg_init(struct device *dev)
|
|
{
|
|
struct qcom_qmp *qmp = dev_get_drvdata(dev);
|
|
int num = qmp->cfg->num_vregs;
|
|
int i;
|
|
|
|
qmp->vregs = devm_kcalloc(dev, num, sizeof(*qmp->vregs), GFP_KERNEL);
|
|
if (!qmp->vregs)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < num; i++)
|
|
qmp->vregs[i].supply = qmp->cfg->vreg_list[i];
|
|
|
|
return devm_regulator_bulk_get(dev, num, qmp->vregs);
|
|
}
|
|
|
|
static int qcom_qmp_phy_reset_init(struct device *dev)
|
|
{
|
|
struct qcom_qmp *qmp = dev_get_drvdata(dev);
|
|
int i;
|
|
|
|
qmp->resets = devm_kcalloc(dev, qmp->cfg->num_resets,
|
|
sizeof(*qmp->resets), GFP_KERNEL);
|
|
if (!qmp->resets)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < qmp->cfg->num_resets; i++) {
|
|
struct reset_control *rst;
|
|
const char *name = qmp->cfg->reset_list[i];
|
|
|
|
rst = devm_reset_control_get(dev, name);
|
|
if (IS_ERR(rst)) {
|
|
dev_err(dev, "failed to get %s reset\n", name);
|
|
return PTR_ERR(rst);
|
|
}
|
|
qmp->resets[i] = rst;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int qcom_qmp_phy_clk_init(struct device *dev)
|
|
{
|
|
struct qcom_qmp *qmp = dev_get_drvdata(dev);
|
|
int num = qmp->cfg->num_clks;
|
|
int i;
|
|
|
|
qmp->clks = devm_kcalloc(dev, num, sizeof(*qmp->clks), GFP_KERNEL);
|
|
if (!qmp->clks)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < num; i++)
|
|
qmp->clks[i].id = qmp->cfg->clk_list[i];
|
|
|
|
return devm_clk_bulk_get(dev, num, qmp->clks);
|
|
}
|
|
|
|
/*
|
|
* Register a fixed rate pipe clock.
|
|
*
|
|
* The <s>_pipe_clksrc generated by PHY goes to the GCC that gate
|
|
* controls it. The <s>_pipe_clk coming out of the GCC is requested
|
|
* by the PHY driver for its operations.
|
|
* We register the <s>_pipe_clksrc here. The gcc driver takes care
|
|
* of assigning this <s>_pipe_clksrc as parent to <s>_pipe_clk.
|
|
* Below picture shows this relationship.
|
|
*
|
|
* +---------------+
|
|
* | PHY block |<<---------------------------------------+
|
|
* | | |
|
|
* | +-------+ | +-----+ |
|
|
* I/P---^-->| PLL |---^--->pipe_clksrc--->| GCC |--->pipe_clk---+
|
|
* clk | +-------+ | +-----+
|
|
* +---------------+
|
|
*/
|
|
static int phy_pipe_clk_register(struct qcom_qmp *qmp, struct device_node *np)
|
|
{
|
|
struct clk_fixed_rate *fixed;
|
|
struct clk_init_data init = { };
|
|
int ret;
|
|
|
|
if ((qmp->cfg->type != PHY_TYPE_USB3) &&
|
|
(qmp->cfg->type != PHY_TYPE_PCIE)) {
|
|
/* not all phys register pipe clocks, so return success */
|
|
return 0;
|
|
}
|
|
|
|
ret = of_property_read_string(np, "clock-output-names", &init.name);
|
|
if (ret) {
|
|
dev_err(qmp->dev, "%s: No clock-output-names\n", np->name);
|
|
return ret;
|
|
}
|
|
|
|
fixed = devm_kzalloc(qmp->dev, sizeof(*fixed), GFP_KERNEL);
|
|
if (!fixed)
|
|
return -ENOMEM;
|
|
|
|
init.ops = &clk_fixed_rate_ops;
|
|
|
|
/* controllers using QMP phys use 125MHz pipe clock interface */
|
|
fixed->fixed_rate = 125000000;
|
|
fixed->hw.init = &init;
|
|
|
|
return devm_clk_hw_register(qmp->dev, &fixed->hw);
|
|
}
|
|
|
|
static const struct phy_ops qcom_qmp_phy_gen_ops = {
|
|
.init = qcom_qmp_phy_init,
|
|
.exit = qcom_qmp_phy_exit,
|
|
.power_on = qcom_qmp_phy_poweron,
|
|
.owner = THIS_MODULE,
|
|
};
|
|
|
|
static
|
|
int qcom_qmp_phy_create(struct device *dev, struct device_node *np, int id)
|
|
{
|
|
struct qcom_qmp *qmp = dev_get_drvdata(dev);
|
|
struct phy *generic_phy;
|
|
struct qmp_phy *qphy;
|
|
char prop_name[MAX_PROP_NAME];
|
|
int ret;
|
|
|
|
qphy = devm_kzalloc(dev, sizeof(*qphy), GFP_KERNEL);
|
|
if (!qphy)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Get memory resources for each phy lane:
|
|
* Resources are indexed as: tx -> 0; rx -> 1; pcs -> 2.
|
|
*/
|
|
qphy->tx = of_iomap(np, 0);
|
|
if (!qphy->tx)
|
|
return -ENOMEM;
|
|
|
|
qphy->rx = of_iomap(np, 1);
|
|
if (!qphy->rx)
|
|
return -ENOMEM;
|
|
|
|
qphy->pcs = of_iomap(np, 2);
|
|
if (!qphy->pcs)
|
|
return -ENOMEM;
|
|
|
|
/*
|
|
* Get PHY's Pipe clock, if any. USB3 and PCIe are PIPE3
|
|
* based phys, so they essentially have pipe clock. So,
|
|
* we return error in case phy is USB3 or PIPE type.
|
|
* Otherwise, we initialize pipe clock to NULL for
|
|
* all phys that don't need this.
|
|
*/
|
|
snprintf(prop_name, sizeof(prop_name), "pipe%d", id);
|
|
qphy->pipe_clk = of_clk_get_by_name(np, prop_name);
|
|
if (IS_ERR(qphy->pipe_clk)) {
|
|
if (qmp->cfg->type == PHY_TYPE_PCIE ||
|
|
qmp->cfg->type == PHY_TYPE_USB3) {
|
|
ret = PTR_ERR(qphy->pipe_clk);
|
|
if (ret != -EPROBE_DEFER)
|
|
dev_err(dev,
|
|
"failed to get lane%d pipe_clk, %d\n",
|
|
id, ret);
|
|
return ret;
|
|
}
|
|
qphy->pipe_clk = NULL;
|
|
}
|
|
|
|
/* Get lane reset, if any */
|
|
if (qmp->cfg->has_lane_rst) {
|
|
snprintf(prop_name, sizeof(prop_name), "lane%d", id);
|
|
qphy->lane_rst = of_reset_control_get(np, prop_name);
|
|
if (IS_ERR(qphy->lane_rst)) {
|
|
dev_err(dev, "failed to get lane%d reset\n", id);
|
|
return PTR_ERR(qphy->lane_rst);
|
|
}
|
|
}
|
|
|
|
generic_phy = devm_phy_create(dev, np, &qcom_qmp_phy_gen_ops);
|
|
if (IS_ERR(generic_phy)) {
|
|
ret = PTR_ERR(generic_phy);
|
|
dev_err(dev, "failed to create qphy %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
qphy->phy = generic_phy;
|
|
qphy->index = id;
|
|
qphy->qmp = qmp;
|
|
qmp->phys[id] = qphy;
|
|
phy_set_drvdata(generic_phy, qphy);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id qcom_qmp_phy_of_match_table[] = {
|
|
{
|
|
.compatible = "qcom,msm8996-qmp-pcie-phy",
|
|
.data = &msm8996_pciephy_cfg,
|
|
}, {
|
|
.compatible = "qcom,msm8996-qmp-usb3-phy",
|
|
.data = &msm8996_usb3phy_cfg,
|
|
}, {
|
|
.compatible = "qcom,ipq8074-qmp-pcie-phy",
|
|
.data = &ipq8074_pciephy_cfg,
|
|
},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, qcom_qmp_phy_of_match_table);
|
|
|
|
static int qcom_qmp_phy_probe(struct platform_device *pdev)
|
|
{
|
|
struct qcom_qmp *qmp;
|
|
struct device *dev = &pdev->dev;
|
|
struct resource *res;
|
|
struct device_node *child;
|
|
struct phy_provider *phy_provider;
|
|
void __iomem *base;
|
|
int num, id;
|
|
int ret;
|
|
|
|
qmp = devm_kzalloc(dev, sizeof(*qmp), GFP_KERNEL);
|
|
if (!qmp)
|
|
return -ENOMEM;
|
|
|
|
qmp->dev = dev;
|
|
dev_set_drvdata(dev, qmp);
|
|
|
|
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
base = devm_ioremap_resource(dev, res);
|
|
if (IS_ERR(base))
|
|
return PTR_ERR(base);
|
|
|
|
/* per PHY serdes; usually located at base address */
|
|
qmp->serdes = base;
|
|
|
|
mutex_init(&qmp->phy_mutex);
|
|
|
|
/* Get the specific init parameters of QMP phy */
|
|
qmp->cfg = of_device_get_match_data(dev);
|
|
|
|
ret = qcom_qmp_phy_clk_init(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = qcom_qmp_phy_reset_init(dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = qcom_qmp_phy_vreg_init(dev);
|
|
if (ret) {
|
|
dev_err(dev, "failed to get regulator supplies\n");
|
|
return ret;
|
|
}
|
|
|
|
num = of_get_available_child_count(dev->of_node);
|
|
/* do we have a rogue child node ? */
|
|
if (num > qmp->cfg->nlanes)
|
|
return -EINVAL;
|
|
|
|
qmp->phys = devm_kcalloc(dev, num, sizeof(*qmp->phys), GFP_KERNEL);
|
|
if (!qmp->phys)
|
|
return -ENOMEM;
|
|
|
|
id = 0;
|
|
for_each_available_child_of_node(dev->of_node, child) {
|
|
/* Create per-lane phy */
|
|
ret = qcom_qmp_phy_create(dev, child, id);
|
|
if (ret) {
|
|
dev_err(dev, "failed to create lane%d phy, %d\n",
|
|
id, ret);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Register the pipe clock provided by phy.
|
|
* See function description to see details of this pipe clock.
|
|
*/
|
|
ret = phy_pipe_clk_register(qmp, child);
|
|
if (ret) {
|
|
dev_err(qmp->dev,
|
|
"failed to register pipe clock source\n");
|
|
return ret;
|
|
}
|
|
id++;
|
|
}
|
|
|
|
phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate);
|
|
if (!IS_ERR(phy_provider))
|
|
dev_info(dev, "Registered Qcom-QMP phy\n");
|
|
|
|
return PTR_ERR_OR_ZERO(phy_provider);
|
|
}
|
|
|
|
static struct platform_driver qcom_qmp_phy_driver = {
|
|
.probe = qcom_qmp_phy_probe,
|
|
.driver = {
|
|
.name = "qcom-qmp-phy",
|
|
.of_match_table = qcom_qmp_phy_of_match_table,
|
|
},
|
|
};
|
|
|
|
module_platform_driver(qcom_qmp_phy_driver);
|
|
|
|
MODULE_AUTHOR("Vivek Gautam <vivek.gautam@codeaurora.org>");
|
|
MODULE_DESCRIPTION("Qualcomm QMP PHY driver");
|
|
MODULE_LICENSE("GPL v2");
|