Linux-libre 4.14.69-gnu
[librecmc/linux-libre.git] / drivers / scsi / ufs / ufshcd-dwc.c
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
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  */
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  */
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  */
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  */
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");