OMAP3+: ABB: initialization & transition functions
[beagleboard-validation:linux.git] / arch / arm / mach-omap2 / abb.c
1 /*
2  * OMAP Adaptive Body-Bias core
3  *
4  * Copyright (C) 2011 Texas Instruments, Inc.
5  * Mike Turquette <mturquette@ti.com>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License version 2 as
9  * published by the Free Software Foundation.
10  */
11
12 #include <linux/init.h>
13 #include <linux/delay.h>
14
15 #include "abb.h"
16 #include "voltage.h"
17
18 /*
19  * omap_abb_set_opp - program ABB ldo based on new voltage
20  *
21  * @voltdm - pointer to voltage domain that just finished scaling voltage
22  *
23  * Look up the ABB ldo state for the new voltage that voltdm just finished
24  * transitioning to and compare it to current ldo state.  If a change is needed
25  * then clear appropriate PRM_IRQSTATUS bit, transition ldo and then clear
26  * PRM_IRQSTATUS bit again.  Returns 0 on success, -EERROR otherwise.
27  */
28 int omap_abb_set_opp(struct voltagedomain *voltdm)
29 {
30         struct omap_abb_instance *abb = voltdm->abb;
31         struct omap_volt_data *volt_data;
32         int ret, timeout;
33         u8 opp_sel;
34
35         /* fetch the ABB ldo OPP_SEL value for the new voltage */
36         volt_data = omap_voltage_get_voltdata(voltdm, voltdm->nominal_volt);
37
38         if (IS_ERR_OR_NULL(volt_data))
39                 return -EINVAL;
40
41         opp_sel = volt_data->opp_sel;
42
43         /* bail early if no transition is necessary */
44         if (opp_sel == abb->_opp_sel)
45                 return 0;
46
47         /* clear interrupt status */
48         timeout = 0;
49         while (timeout++ < ABB_TRANXDONE_TIMEOUT) {
50                 abb->common->ops->clear_tranxdone(abb->prm_irq_id);
51
52                 ret = abb->common->ops->check_tranxdone(abb->prm_irq_id);
53                 if (!ret)
54                         break;
55
56                 udelay(1);
57         }
58
59         if (timeout>= ABB_TRANXDONE_TIMEOUT) {
60                 pr_warning("%s: vdd_%s ABB TRANXDONE timeout\n",
61                                 __func__, voltdm->name);
62                 return -ETIMEDOUT;
63         }
64
65         /* program next state of ABB ldo */
66         voltdm->rmw(abb->common->opp_sel_mask,
67                         opp_sel << __ffs(abb->common->opp_sel_mask),
68                         abb->ctrl_offs);
69
70         /* initiate ABB ldo change */
71         voltdm->rmw(abb->common->opp_change_mask,
72                         abb->common->opp_change_mask,
73                         abb->ctrl_offs);
74
75         /* clear interrupt status */
76         timeout = 0;
77         while (timeout++ < ABB_TRANXDONE_TIMEOUT) {
78                 abb->common->ops->clear_tranxdone(abb->prm_irq_id);
79
80                 ret = abb->common->ops->check_tranxdone(abb->prm_irq_id);
81                 if (!ret)
82                         break;
83
84                 udelay(1);
85         }
86
87         if (timeout>= ABB_TRANXDONE_TIMEOUT) {
88                 pr_warning("%s: vdd_%s ABB TRANXDONE timeout\n",
89                                 __func__, voltdm->name);
90                 return -ETIMEDOUT;
91         }
92
93         /* track internal state */
94         abb->_opp_sel = opp_sel;
95
96         return 0;
97 }
98
99 /*
100  * omap_abb_enable - enable ABB ldo on a particular voltage domain
101  *
102  * @voltdm - pointer to particular voltage domain
103  */
104 void omap_abb_enable(struct voltagedomain *voltdm)
105 {
106         struct omap_abb_instance *abb = voltdm->abb;
107
108         if (abb->enabled)
109                 return;
110
111         abb->enabled = true;
112
113         voltdm->rmw(abb->common->sr2en_mask, abb->common->sr2en_mask,
114                         abb->setup_offs);
115 }
116
117 /*
118  * omap_abb_disable - disable ABB ldo on a particular voltage domain
119  *
120  * @voltdm - pointer to particular voltage domain
121  *
122  * Included for completeness.  Not currently used but will be needed in the
123  * future if ABB is converted to a loadable module.
124  */
125 void omap_abb_disable(struct voltagedomain *voltdm)
126 {
127         struct omap_abb_instance *abb = voltdm->abb;
128
129         if (!abb->enabled)
130                 return;
131
132         abb->enabled = false;
133
134         voltdm->rmw(abb->common->sr2en_mask,
135                         (0 << __ffs(abb->common->sr2en_mask)),
136                         abb->setup_offs);
137 }
138
139 /*
140  * omap_abb_init - Initialize an ABB ldo instance
141  *
142  * @voltdm: voltage domain upon which ABB ldo resides
143  *
144  * Initializes an individual ABB ldo for Forward Body-Bias.  FBB is used to
145  * insure stability at higher voltages.  Note that some older OMAP chips have a
146  * Reverse Body-Bias mode meant to save power at low voltage, but that mode is
147  * unsupported and phased out on newer chips.
148  */
149 void __init omap_abb_init(struct voltagedomain *voltdm)
150 {
151         struct omap_abb_instance *abb = voltdm->abb;
152         u32 sys_clk_rate;
153         u32 sr2_wt_cnt_val;
154         u32 clock_cycles;
155         u32 settling_time;
156         u32 val;
157
158         if(IS_ERR_OR_NULL(abb))
159                 return;
160
161         /*
162          * SR2_WTCNT_VALUE is the settling time for the ABB ldo after a
163          * transition and must be programmed with the correct time at boot.
164          * The value programmed into the register is the number of SYS_CLK
165          * clock cycles that match a given wall time profiled for the ldo.
166          * This value depends on:
167          *      settling time of ldo in micro-seconds (varies per OMAP family)
168          *      # of clock cycles per SYS_CLK period (varies per OMAP family)
169          *      the SYS_CLK frequency in MHz (varies per board)
170          * The formula is:
171          *
172          *                      ldo settling time (in micro-seconds)
173          * SR2_WTCNT_VALUE = ------------------------------------------
174          *                   (# system clock cycles) * (sys_clk period)
175          *
176          * Put another way:
177          *
178          * SR2_WTCNT_VALUE = settling time / (# SYS_CLK cycles / SYS_CLK rate))
179          *
180          * To avoid dividing by zero multiply both "# clock cycles" and
181          * "settling time" by 10 such that the final result is the one we want.
182          */
183
184         /* convert SYS_CLK rate to MHz & prevent divide by zero */
185         sys_clk_rate = DIV_ROUND_CLOSEST(voltdm->sys_clk.rate, 1000000);
186         clock_cycles = abb->common->clock_cycles * 10;
187         settling_time = abb->common->settling_time * 10;
188
189         /* calculate cycle rate */
190         clock_cycles = DIV_ROUND_CLOSEST(clock_cycles, sys_clk_rate);
191
192         /* calulate SR2_WTCNT_VALUE */
193         sr2_wt_cnt_val = DIV_ROUND_CLOSEST(settling_time, clock_cycles);
194
195         voltdm->rmw(abb->common->sr2_wtcnt_value_mask,
196                         (sr2_wt_cnt_val << __ffs(abb->common->sr2_wtcnt_value_mask)),
197                         abb->setup_offs);
198
199         /* allow Forward Body-Bias */
200         voltdm->rmw(abb->common->active_fbb_sel_mask,
201                         abb->common->active_fbb_sel_mask,
202                         abb->setup_offs);
203
204         /* did bootloader set OPP_SEL? */
205         val = voltdm->read(abb->ctrl_offs);
206         val &= abb->common->opp_sel_mask;
207         abb->_opp_sel = val >> __ffs(abb->common->opp_sel_mask);
208
209         /* enable the ldo if not done by bootloader */
210         val = voltdm->read(abb->setup_offs);
211         val &= abb->common->sr2en_mask;
212         if (val)
213                 abb->enabled = true;
214         else
215                 omap_abb_enable(voltdm);
216
217         return;
218 }