1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
/*
* Copyright (c) 2013-2014, 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.
*
*/
#ifndef UFS_QCOM_PHY_H_
#define UFS_QCOM_PHY_H_
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/time.h>
#include <linux/clk.h>
#include <linux/of.h>
#include <linux/iopoll.h>
#include <linux/platform_device.h>
#include <linux/phy/phy.h>
#include <linux/msm-bus.h>
#include <linux/scsi/ufs/ufshcd.h>
#include <linux/scsi/ufs/unipro.h>
#include <linux/scsi/ufs/ufs-qcom.h>
#define UFS_QCOM_PHY_CAL_ENTRY(reg, val) \
{ \
.reg_offset = reg, \
.cfg_value = val, \
}
#define UFS_QCOM_PHY_NAME_LEN 30
enum {
MASK_SERDES_START = 0x1,
MASK_PCS_READY = 0x1,
};
enum {
OFFSET_SERDES_START = 0x0,
};
struct ufs_qcom_phy_stored_attributes {
u32 att;
u32 value;
};
struct ufs_qcom_phy_calibration {
u32 reg_offset;
u32 cfg_value;
};
struct ufs_qcom_phy {
struct list_head list;
struct device *dev;
void __iomem *mmio;
struct clk *tx_iface_clk;
struct clk *rx_iface_clk;
bool is_iface_clk_enabled;
struct clk *ref_clk_src;
struct clk *ref_clk_parent;
struct clk *ref_clk;
bool is_ref_clk_enabled;
struct ufs_qcom_phy_vreg vdda_pll;
struct ufs_qcom_phy_vreg vdda_phy;
struct ufs_qcom_phy_vreg vddp_ref_clk;
unsigned int quirks;
u8 host_ctrl_rev_major;
u16 host_ctrl_rev_minor;
u16 host_ctrl_rev_step;
/*
* As part of UFS power management, UFS link would be put in hibernate
* and UFS device would be put in SLEEP mode as part of runtime/system
* suspend callback. But when system goes into suspend with VDD
* minimization, UFS PHY states are being reset which means UFS link
* hibernate exit command on system resume would fail.
* If this quirk is enabled then above issue is workaround by saving
* the UFS PHY state information before system goes into suspend and
* restoring the saved state information during system resume but
* before executing the hibern8 exit command.
* Note that this quirk will help restoring the PHY state if even when
* link in not kept in hibern8 during suspend.
*
* Here is the list of steps to save/restore the configuration:
* Before entering into system suspend:
* 1. Read Critical PCS SWI Registers + less critical PHY CSR
* 2. Read RMMI Attributes
* Enter into system suspend
* After exiting from system suspend:
* 1. Set UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS
* Controller
* 2. Write 0x01 to the UFS_PHY_POWER_DOWN_CONTROL register in the
* UFS PHY
* 3. Write back the values of the PHY SWI registers
* 4. Clear UFS_PHY_SOFT_RESET bit in UFS_CFG1 register of the UFS
* Controller
* 5. Write 0x01 to the UFS_PHY_PHY_START in the UFS PHY. This will
* start the PLL calibration and bring-up of the PHY.
* 6. Write back the values to the PHY RMMI Attributes
* 7. Wait for UFS_PHY_PCS_READY_STATUS[0] to be '1'
*/
#define UFS_QCOM_PHY_QUIRK_CFG_RESTORE (1 << 0)
/*
* If UFS PHY power down is deasserted and power is restored to analog
* circuits, the rx_sigdet can glitch. If the glitch is wide enough,
* it can trigger the digital logic to think it saw a DIF-N and cause
* it to exit Hibern8. Disabling the rx_sigdet during power-up masks
* the glitch.
*/
#define UFS_QCOM_PHY_DIS_SIGDET_BEFORE_PWR_COLLAPSE (1 << 1)
/*
* If UFS link is put into Hibern8 and if UFS PHY analog hardware is
* power collapsed (by clearing UFS_PHY_POWER_DOWN_CONTROL), Hibern8
* exit might fail even after powering on UFS PHY analog hardware.
* Enabling this quirk will help to solve above issue by doing
* custom PHY settings just before PHY analog power collapse.
*/
#define UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE (1 << 2)
char name[UFS_QCOM_PHY_NAME_LEN];
struct ufs_qcom_phy_calibration *cached_regs;
int cached_regs_table_size;
bool is_powered_on;
struct ufs_qcom_phy_specific_ops *phy_spec_ops;
};
/**
* struct ufs_qcom_phy_specific_ops - set of pointers to functions which have a
* specific implementation per phy. Each UFS phy, should implement
* those functions according to its spec and requirements
* @calibrate_phy: pointer to a function that calibrate the phy
* @start_serdes: pointer to a function that starts the serdes
* @save_configuration: pointer to a function that saves phy
* configuration
* @is_physical_coding_sublayer_ready: pointer to a function that
* checks pcs readiness
* @set_tx_lane_enable: pointer to a function that enable tx lanes
* @power_control: pointer to a function that controls analog rail of phy
* and writes to QSERDES_RX_SIGDET_CNTRL attribute
*/
struct ufs_qcom_phy_specific_ops {
int (*calibrate_phy) (struct ufs_qcom_phy *phy);
void (*start_serdes) (struct ufs_qcom_phy *phy);
void (*save_configuration)(struct ufs_qcom_phy *phy);
void (*restore_configuration)(struct ufs_qcom_phy *phy);
int (*is_physical_coding_sublayer_ready) (struct ufs_qcom_phy *phy);
void (*set_tx_lane_enable) (struct ufs_qcom_phy *phy, u32 val);
void (*power_control) (struct ufs_qcom_phy *phy, bool val);
};
int ufs_qcom_phy_cfg_vreg(struct phy *phy,
struct ufs_qcom_phy_vreg *vreg, bool on);
int ufs_qcom_phy_enable_vreg(struct phy *phy,
struct ufs_qcom_phy_vreg *vreg);
int ufs_qcom_phy_disable_vreg(struct phy *phy,
struct ufs_qcom_phy_vreg *vreg);
int ufs_qcom_phy_enable_ref_clk(struct phy *phy);
void ufs_qcom_phy_disable_ref_clk(struct phy *phy);
void ufs_qcom_phy_enable_dev_ref_clk(struct phy *);
void ufs_qcom_phy_disable_dev_ref_clk(struct phy *);
int ufs_qcom_phy_enable_iface_clk(struct phy *phy);
void ufs_qcom_phy_disable_iface_clk(struct phy *phy);
void ufs_qcom_phy_restore_swi_regs(struct phy *phy);
int ufs_qcom_phy_link_startup_post_change(struct phy *phy,
struct ufs_hba *hba);
int ufs_qcom_phy_base_init(struct platform_device *pdev,
struct ufs_qcom_phy *ufs_qcom_phy_ops);
int ufs_qcom_phy_is_cfg_restore_quirk_enabled(struct phy *phy);
struct ufs_qcom_phy *get_ufs_qcom_phy(struct phy *generic_phy);
int ufs_qcom_phy_start_serdes(struct phy *generic_phy);
int ufs_qcom_phy_set_tx_lane_enable(struct phy *generic_phy, u32 tx_lanes);
int ufs_qcom_phy_calibrate_phy(struct phy *generic_phy);
int ufs_qcom_phy_is_pcs_ready(struct phy *generic_phy);
int ufs_qcom_phy_save_configuration(struct phy *generic_phy);
int ufs_qcom_phy_restore_configuration(struct phy *generic_phy);
void ufs_qcom_phy_save_controller_version(struct phy *generic_phy,
u8 major, u16 minor, u16 step);
int ufs_qcom_phy_power_on(struct phy *generic_phy);
int ufs_qcom_phy_power_off(struct phy *generic_phy);
int ufs_qcom_phy_exit(struct phy *generic_phy);
int ufs_qcom_phy_init_clks(struct phy *generic_phy,
struct ufs_qcom_phy *phy_common);
int ufs_qcom_phy_init_vregulators(struct phy *generic_phy,
struct ufs_qcom_phy *phy_common);
int ufs_qcom_phy_remove(struct phy *generic_phy,
struct ufs_qcom_phy *ufs_qcom_phy);
struct phy *ufs_qcom_phy_generic_probe(struct platform_device *pdev,
struct ufs_qcom_phy *common_cfg,
struct phy_ops *ufs_qcom_phy_gen_ops,
struct ufs_qcom_phy_specific_ops *phy_spec_ops);
int ufs_qcom_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
struct ufs_qcom_phy_calibration *tbl_A, int tbl_size_A,
struct ufs_qcom_phy_calibration *tbl_B, int tbl_size_B,
int rate);
#endif
|