1 /*
2  * UFS Host driver for Synopsys Designware Core
3  *
4  * Copyright (C) 2015-2016 Synopsys, Inc. (www.synopsys.com)
5  *
6  * Authors: Joao Pinto <jpinto@synopsys.com>
7  *
8  * This program is free software; you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License version 2 as
10  * published by the Free Software Foundation.
11  */
12 
13 #include "ufshcd.h"
14 #include "unipro.h"
15 
16 #include "ufshcd-dwc.h"
17 #include "ufshci-dwc.h"
18 
ufshcd_dwc_dme_set_attrs(struct ufs_hba * hba,const struct ufshcd_dme_attr_val * v,int n)19 int ufshcd_dwc_dme_set_attrs(struct ufs_hba *hba,
20 				const struct ufshcd_dme_attr_val *v, int n)
21 {
22 	int ret = 0;
23 	int attr_node = 0;
24 
25 	for (attr_node = 0; attr_node < n; attr_node++) {
26 		ret = ufshcd_dme_set_attr(hba, v[attr_node].attr_sel,
27 			ATTR_SET_NOR, v[attr_node].mib_val, v[attr_node].peer);
28 
29 		if (ret)
30 			return ret;
31 	}
32 
33 	return 0;
34 }
35 EXPORT_SYMBOL(ufshcd_dwc_dme_set_attrs);
36 
37 /**
38  * ufshcd_dwc_program_clk_div()
39  * This function programs the clk divider value. This value is needed to
40  * provide 1 microsecond tick to unipro layer.
41  * @hba: Private Structure pointer
42  * @divider_val: clock divider value to be programmed
43  *
44  */
ufshcd_dwc_program_clk_div(struct ufs_hba * hba,u32 divider_val)45 static void ufshcd_dwc_program_clk_div(struct ufs_hba *hba, u32 divider_val)
46 {
47 	ufshcd_writel(hba, divider_val, DWC_UFS_REG_HCLKDIV);
48 }
49 
50 /**
51  * ufshcd_dwc_link_is_up()
52  * Check if link is up
53  * @hba: private structure poitner
54  *
55  * Returns 0 on success, non-zero value on failure
56  */
ufshcd_dwc_link_is_up(struct ufs_hba * hba)57 static int ufshcd_dwc_link_is_up(struct ufs_hba *hba)
58 {
59 	int dme_result = 0;
60 
61 	ufshcd_dme_get(hba, UIC_ARG_MIB(VS_POWERSTATE), &dme_result);
62 
63 	if (dme_result == UFSHCD_LINK_IS_UP) {
64 		ufshcd_set_link_active(hba);
65 		return 0;
66 	}
67 
68 	return 1;
69 }
70 
71 /**
72  * ufshcd_dwc_connection_setup()
73  * This function configures both the local side (host) and the peer side
74  * (device) unipro attributes to establish the connection to application/
75  * cport.
76  * This function is not required if the hardware is properly configured to
77  * have this connection setup on reset. But invoking this function does no
78  * harm and should be fine even working with any ufs device.
79  *
80  * @hba: pointer to drivers private data
81  *
82  * Returns 0 on success non-zero value on failure
83  */
ufshcd_dwc_connection_setup(struct ufs_hba * hba)84 static int ufshcd_dwc_connection_setup(struct ufs_hba *hba)
85 {
86 	const struct ufshcd_dme_attr_val setup_attrs[] = {
87 		{ UIC_ARG_MIB(T_CONNECTIONSTATE), 0, DME_LOCAL },
88 		{ UIC_ARG_MIB(N_DEVICEID), 0, DME_LOCAL },
89 		{ UIC_ARG_MIB(N_DEVICEID_VALID), 0, DME_LOCAL },
90 		{ UIC_ARG_MIB(T_PEERDEVICEID), 1, DME_LOCAL },
91 		{ UIC_ARG_MIB(T_PEERCPORTID), 0, DME_LOCAL },
92 		{ UIC_ARG_MIB(T_TRAFFICCLASS), 0, DME_LOCAL },
93 		{ UIC_ARG_MIB(T_CPORTFLAGS), 0x6, DME_LOCAL },
94 		{ UIC_ARG_MIB(T_CPORTMODE), 1, DME_LOCAL },
95 		{ UIC_ARG_MIB(T_CONNECTIONSTATE), 1, DME_LOCAL },
96 		{ UIC_ARG_MIB(T_CONNECTIONSTATE), 0, DME_PEER },
97 		{ UIC_ARG_MIB(N_DEVICEID), 1, DME_PEER },
98 		{ UIC_ARG_MIB(N_DEVICEID_VALID), 1, DME_PEER },
99 		{ UIC_ARG_MIB(T_PEERDEVICEID), 1, DME_PEER },
100 		{ UIC_ARG_MIB(T_PEERCPORTID), 0, DME_PEER },
101 		{ UIC_ARG_MIB(T_TRAFFICCLASS), 0, DME_PEER },
102 		{ UIC_ARG_MIB(T_CPORTFLAGS), 0x6, DME_PEER },
103 		{ UIC_ARG_MIB(T_CPORTMODE), 1, DME_PEER },
104 		{ UIC_ARG_MIB(T_CONNECTIONSTATE), 1, DME_PEER }
105 	};
106 
107 	return ufshcd_dwc_dme_set_attrs(hba, setup_attrs, ARRAY_SIZE(setup_attrs));
108 }
109 
110 /**
111  * ufshcd_dwc_link_startup_notify()
112  * UFS Host DWC specific link startup sequence
113  * @hba: private structure poitner
114  * @status: Callback notify status
115  *
116  * Returns 0 on success, non-zero value on failure
117  */
ufshcd_dwc_link_startup_notify(struct ufs_hba * hba,enum ufs_notify_change_status status)118 int ufshcd_dwc_link_startup_notify(struct ufs_hba *hba,
119 					enum ufs_notify_change_status status)
120 {
121 	int err = 0;
122 
123 	if (status == PRE_CHANGE) {
124 		ufshcd_dwc_program_clk_div(hba, DWC_UFS_REG_HCLKDIV_DIV_125);
125 
126 		if (hba->vops->phy_initialization) {
127 			err = hba->vops->phy_initialization(hba);
128 			if (err) {
129 				dev_err(hba->dev, "Phy setup failed (%d)\n",
130 									err);
131 				goto out;
132 			}
133 		}
134 	} else { /* POST_CHANGE */
135 		err = ufshcd_dwc_link_is_up(hba);
136 		if (err) {
137 			dev_err(hba->dev, "Link is not up\n");
138 			goto out;
139 		}
140 
141 		err = ufshcd_dwc_connection_setup(hba);
142 		if (err)
143 			dev_err(hba->dev, "Connection setup failed (%d)\n",
144 									err);
145 	}
146 
147 out:
148 	return err;
149 }
150 EXPORT_SYMBOL(ufshcd_dwc_link_startup_notify);
151 
152 MODULE_AUTHOR("Joao Pinto <Joao.Pinto@synopsys.com>");
153 MODULE_DESCRIPTION("UFS Host driver for Synopsys Designware Core");
154 MODULE_LICENSE("Dual BSD/GPL");
155