Linux Audio

Check our new training course

Loading...
Note: File does not exist in v6.9.4.
  1/*
  2 * Copyright (c) 2013-2015, Linux Foundation. All rights reserved.
  3 *
  4 * This program is free software; you can redistribute it and/or modify
  5 * it under the terms of the GNU General Public License version 2 and
  6 * only version 2 as published by the Free Software Foundation.
  7 *
  8 * This program is distributed in the hope that it will be useful,
  9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 11 * GNU General Public License for more details.
 12 *
 13 */
 14
 15#include "phy-qcom-ufs-qmp-20nm.h"
 16
 17#define UFS_PHY_NAME "ufs_phy_qmp_20nm"
 18
 19static
 20int ufs_qcom_phy_qmp_20nm_phy_calibrate(struct ufs_qcom_phy *ufs_qcom_phy,
 21					bool is_rate_B)
 22{
 23	struct ufs_qcom_phy_calibration *tbl_A, *tbl_B;
 24	int tbl_size_A, tbl_size_B;
 25	u8 major = ufs_qcom_phy->host_ctrl_rev_major;
 26	u16 minor = ufs_qcom_phy->host_ctrl_rev_minor;
 27	u16 step = ufs_qcom_phy->host_ctrl_rev_step;
 28	int err;
 29
 30	if ((major == 0x1) && (minor == 0x002) && (step == 0x0000)) {
 31		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_2_0);
 32		tbl_A = phy_cal_table_rate_A_1_2_0;
 33	} else if ((major == 0x1) && (minor == 0x003) && (step == 0x0000)) {
 34		tbl_size_A = ARRAY_SIZE(phy_cal_table_rate_A_1_3_0);
 35		tbl_A = phy_cal_table_rate_A_1_3_0;
 36	} else {
 37		dev_err(ufs_qcom_phy->dev, "%s: Unknown UFS-PHY version, no calibration values\n",
 38			__func__);
 39		err = -ENODEV;
 40		goto out;
 41	}
 42
 43	tbl_size_B = ARRAY_SIZE(phy_cal_table_rate_B);
 44	tbl_B = phy_cal_table_rate_B;
 45
 46	err = ufs_qcom_phy_calibrate(ufs_qcom_phy, tbl_A, tbl_size_A,
 47						tbl_B, tbl_size_B, is_rate_B);
 48
 49	if (err)
 50		dev_err(ufs_qcom_phy->dev, "%s: ufs_qcom_phy_calibrate() failed %d\n",
 51			__func__, err);
 52
 53out:
 54	return err;
 55}
 56
 57static
 58void ufs_qcom_phy_qmp_20nm_advertise_quirks(struct ufs_qcom_phy *phy_common)
 59{
 60	phy_common->quirks =
 61		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
 62}
 63
 64static int ufs_qcom_phy_qmp_20nm_init(struct phy *generic_phy)
 65{
 66	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
 67	bool is_rate_B = false;
 68	int ret;
 69
 70	if (phy_common->mode == PHY_MODE_UFS_HS_B)
 71		is_rate_B = true;
 72
 73	ret = ufs_qcom_phy_qmp_20nm_phy_calibrate(phy_common, is_rate_B);
 74	if (!ret)
 75		/* phy calibrated, but yet to be started */
 76		phy_common->is_started = false;
 77
 78	return ret;
 79}
 80
 81static int ufs_qcom_phy_qmp_20nm_exit(struct phy *generic_phy)
 82{
 83	return 0;
 84}
 85
 86static
 87int ufs_qcom_phy_qmp_20nm_set_mode(struct phy *generic_phy, enum phy_mode mode)
 88{
 89	struct ufs_qcom_phy *phy_common = get_ufs_qcom_phy(generic_phy);
 90
 91	phy_common->mode = PHY_MODE_INVALID;
 92
 93	if (mode > 0)
 94		phy_common->mode = mode;
 95
 96	return 0;
 97}
 98
 99static
100void ufs_qcom_phy_qmp_20nm_power_control(struct ufs_qcom_phy *phy, bool val)
101{
102	bool hibern8_exit_after_pwr_collapse = phy->quirks &
103		UFS_QCOM_PHY_QUIRK_HIBERN8_EXIT_AFTER_PHY_PWR_COLLAPSE;
104
105	if (val) {
106		writel_relaxed(0x1, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
107		/*
108		 * Before any transactions involving PHY, ensure PHY knows
109		 * that it's analog rail is powered ON.
110		 */
111		mb();
112
113		if (hibern8_exit_after_pwr_collapse) {
114			/*
115			 * Give atleast 1us delay after restoring PHY analog
116			 * power.
117			 */
118			usleep_range(1, 2);
119			writel_relaxed(0x0A, phy->mmio +
120				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
121			writel_relaxed(0x08, phy->mmio +
122				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
123			/*
124			 * Make sure workaround is deactivated before proceeding
125			 * with normal PHY operations.
126			 */
127			mb();
128		}
129	} else {
130		if (hibern8_exit_after_pwr_collapse) {
131			writel_relaxed(0x0A, phy->mmio +
132				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
133			writel_relaxed(0x02, phy->mmio +
134				       QSERDES_COM_SYSCLK_EN_SEL_TXBAND);
135			/*
136			 * Make sure that above workaround is activated before
137			 * PHY analog power collapse.
138			 */
139			mb();
140		}
141
142		writel_relaxed(0x0, phy->mmio + UFS_PHY_POWER_DOWN_CONTROL);
143		/*
144		 * ensure that PHY knows its PHY analog rail is going
145		 * to be powered down
146		 */
147		mb();
148	}
149}
150
151static
152void ufs_qcom_phy_qmp_20nm_set_tx_lane_enable(struct ufs_qcom_phy *phy, u32 val)
153{
154	writel_relaxed(val & UFS_PHY_TX_LANE_ENABLE_MASK,
155			phy->mmio + UFS_PHY_TX_LANE_ENABLE);
156	mb();
157}
158
159static inline void ufs_qcom_phy_qmp_20nm_start_serdes(struct ufs_qcom_phy *phy)
160{
161	u32 tmp;
162
163	tmp = readl_relaxed(phy->mmio + UFS_PHY_PHY_START);
164	tmp &= ~MASK_SERDES_START;
165	tmp |= (1 << OFFSET_SERDES_START);
166	writel_relaxed(tmp, phy->mmio + UFS_PHY_PHY_START);
167	mb();
168}
169
170static int ufs_qcom_phy_qmp_20nm_is_pcs_ready(struct ufs_qcom_phy *phy_common)
171{
172	int err = 0;
173	u32 val;
174
175	err = readl_poll_timeout(phy_common->mmio + UFS_PHY_PCS_READY_STATUS,
176			val, (val & MASK_PCS_READY), 10, 1000000);
177	if (err)
178		dev_err(phy_common->dev, "%s: poll for pcs failed err = %d\n",
179			__func__, err);
180	return err;
181}
182
183static const struct phy_ops ufs_qcom_phy_qmp_20nm_phy_ops = {
184	.init		= ufs_qcom_phy_qmp_20nm_init,
185	.exit		= ufs_qcom_phy_qmp_20nm_exit,
186	.power_on	= ufs_qcom_phy_power_on,
187	.power_off	= ufs_qcom_phy_power_off,
188	.set_mode	= ufs_qcom_phy_qmp_20nm_set_mode,
189	.owner		= THIS_MODULE,
190};
191
192static struct ufs_qcom_phy_specific_ops phy_20nm_ops = {
193	.start_serdes		= ufs_qcom_phy_qmp_20nm_start_serdes,
194	.is_physical_coding_sublayer_ready = ufs_qcom_phy_qmp_20nm_is_pcs_ready,
195	.set_tx_lane_enable	= ufs_qcom_phy_qmp_20nm_set_tx_lane_enable,
196	.power_control		= ufs_qcom_phy_qmp_20nm_power_control,
197};
198
199static int ufs_qcom_phy_qmp_20nm_probe(struct platform_device *pdev)
200{
201	struct device *dev = &pdev->dev;
202	struct phy *generic_phy;
203	struct ufs_qcom_phy_qmp_20nm *phy;
204	struct ufs_qcom_phy *phy_common;
205	int err = 0;
206
207	phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
208	if (!phy) {
209		err = -ENOMEM;
210		goto out;
211	}
212	phy_common = &phy->common_cfg;
213
214	generic_phy = ufs_qcom_phy_generic_probe(pdev, phy_common,
215				&ufs_qcom_phy_qmp_20nm_phy_ops, &phy_20nm_ops);
216
217	if (!generic_phy) {
218		err = -EIO;
219		goto out;
220	}
221
222	err = ufs_qcom_phy_init_clks(phy_common);
223	if (err)
224		goto out;
225
226	err = ufs_qcom_phy_init_vregulators(phy_common);
227	if (err)
228		goto out;
229
230	ufs_qcom_phy_qmp_20nm_advertise_quirks(phy_common);
231
232	phy_set_drvdata(generic_phy, phy);
233
234	strlcpy(phy_common->name, UFS_PHY_NAME, sizeof(phy_common->name));
235
236out:
237	return err;
238}
239
240static const struct of_device_id ufs_qcom_phy_qmp_20nm_of_match[] = {
241	{.compatible = "qcom,ufs-phy-qmp-20nm"},
242	{},
243};
244MODULE_DEVICE_TABLE(of, ufs_qcom_phy_qmp_20nm_of_match);
245
246static struct platform_driver ufs_qcom_phy_qmp_20nm_driver = {
247	.probe = ufs_qcom_phy_qmp_20nm_probe,
248	.driver = {
249		.of_match_table = ufs_qcom_phy_qmp_20nm_of_match,
250		.name = "ufs_qcom_phy_qmp_20nm",
251	},
252};
253
254module_platform_driver(ufs_qcom_phy_qmp_20nm_driver);
255
256MODULE_DESCRIPTION("Universal Flash Storage (UFS) QCOM PHY QMP 20nm");
257MODULE_LICENSE("GPL v2");