blob: cdbe463cff7d7b56e213a9cfe18d47baeb50e247 [file] [log] [blame]
Vikas Manocha07e9e412017-02-12 10:25:49 -08001#include <common.h>
Vikas Manocha07e9e412017-02-12 10:25:49 -08002#include <dm.h>
Patrice Chotard05a93192019-06-21 15:39:23 +02003#include <dm/lists.h>
Vikas Manocha07e9e412017-02-12 10:25:49 -08004#include <dm/pinctrl.h>
Benjamin Gaignard16f6f332018-11-27 13:49:53 +01005#include <hwspinlock.h>
Vikas Manochaec8630a2017-04-10 15:02:57 -07006#include <asm/arch/gpio.h>
7#include <asm/gpio.h>
8#include <asm/io.h>
Vikas Manocha07e9e412017-02-12 10:25:49 -08009
10DECLARE_GLOBAL_DATA_PTR;
11
Vikas Manocha40ddb3a2017-04-10 15:03:04 -070012#define MAX_PINS_ONE_IP 70
Vikas Manochaec8630a2017-04-10 15:02:57 -070013#define MODE_BITS_MASK 3
14#define OSPEED_MASK 3
15#define PUPD_MASK 3
16#define OTYPE_MSK 1
17#define AFR_MASK 0xF
18
Patrice Chotardaaf68e82018-10-24 14:10:18 +020019struct stm32_pinctrl_priv {
Benjamin Gaignard16f6f332018-11-27 13:49:53 +010020 struct hwspinlock hws;
Patrice Chotardaaf68e82018-10-24 14:10:18 +020021 int pinctrl_ngpios;
22 struct list_head gpio_dev;
23};
24
25struct stm32_gpio_bank {
26 struct udevice *gpio_dev;
27 struct list_head list;
28};
29
Benjamin Gaignard16f6f332018-11-27 13:49:53 +010030#ifndef CONFIG_SPL_BUILD
31
Patrice Chotard881e8672018-10-24 14:10:19 +020032static char pin_name[PINNAME_SIZE];
Patrice Chotarda46fb392018-10-24 14:10:20 +020033#define PINMUX_MODE_COUNT 5
34static const char * const pinmux_mode[PINMUX_MODE_COUNT] = {
35 "gpio input",
36 "gpio output",
37 "analog",
38 "unknown",
39 "alt function",
40};
41
42static int stm32_pinctrl_get_af(struct udevice *dev, unsigned int offset)
43{
44 struct stm32_gpio_priv *priv = dev_get_priv(dev);
45 struct stm32_gpio_regs *regs = priv->regs;
46 u32 af;
47 u32 alt_shift = (offset % 8) * 4;
48 u32 alt_index = offset / 8;
49
50 af = (readl(&regs->afr[alt_index]) &
51 GENMASK(alt_shift + 3, alt_shift)) >> alt_shift;
52
53 return af;
54}
55
Patrice Chotard7ef91082018-12-03 10:52:50 +010056static int stm32_populate_gpio_dev_list(struct udevice *dev)
57{
58 struct stm32_pinctrl_priv *priv = dev_get_priv(dev);
59 struct udevice *gpio_dev;
60 struct udevice *child;
61 struct stm32_gpio_bank *gpio_bank;
62 int ret;
63
64 /*
65 * parse pin-controller sub-nodes (ie gpio bank nodes) and fill
66 * a list with all gpio device reference which belongs to the
67 * current pin-controller. This list is used to find pin_name and
68 * pin muxing
69 */
70 list_for_each_entry(child, &dev->child_head, sibling_node) {
71 ret = uclass_get_device_by_name(UCLASS_GPIO, child->name,
72 &gpio_dev);
73 if (ret < 0)
74 continue;
75
76 gpio_bank = malloc(sizeof(*gpio_bank));
77 if (!gpio_bank) {
78 dev_err(dev, "Not enough memory\n");
79 return -ENOMEM;
80 }
81
82 gpio_bank->gpio_dev = gpio_dev;
83 list_add_tail(&gpio_bank->list, &priv->gpio_dev);
84 }
85
86 return 0;
87}
88
Patrice Chotardaaf68e82018-10-24 14:10:18 +020089static int stm32_pinctrl_get_pins_count(struct udevice *dev)
90{
91 struct stm32_pinctrl_priv *priv = dev_get_priv(dev);
92 struct gpio_dev_priv *uc_priv;
93 struct stm32_gpio_bank *gpio_bank;
94
95 /*
96 * if get_pins_count has already been executed once on this
97 * pin-controller, no need to run it again
98 */
99 if (priv->pinctrl_ngpios)
100 return priv->pinctrl_ngpios;
101
Patrice Chotard7ef91082018-12-03 10:52:50 +0100102 if (list_empty(&priv->gpio_dev))
103 stm32_populate_gpio_dev_list(dev);
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200104 /*
105 * walk through all banks to retrieve the pin-controller
106 * pins number
107 */
108 list_for_each_entry(gpio_bank, &priv->gpio_dev, list) {
109 uc_priv = dev_get_uclass_priv(gpio_bank->gpio_dev);
110
111 priv->pinctrl_ngpios += uc_priv->gpio_count;
112 }
113
114 return priv->pinctrl_ngpios;
115}
116
Patrice Chotard881e8672018-10-24 14:10:19 +0200117static struct udevice *stm32_pinctrl_get_gpio_dev(struct udevice *dev,
Patrice Chotard0b968002018-12-03 10:52:54 +0100118 unsigned int selector,
119 unsigned int *idx)
Patrice Chotard881e8672018-10-24 14:10:19 +0200120{
121 struct stm32_pinctrl_priv *priv = dev_get_priv(dev);
122 struct stm32_gpio_bank *gpio_bank;
123 struct gpio_dev_priv *uc_priv;
Patrice Chotard0b968002018-12-03 10:52:54 +0100124 int pin_count = 0;
Patrice Chotard881e8672018-10-24 14:10:19 +0200125
Patrice Chotard7ef91082018-12-03 10:52:50 +0100126 if (list_empty(&priv->gpio_dev))
127 stm32_populate_gpio_dev_list(dev);
128
Patrice Chotard881e8672018-10-24 14:10:19 +0200129 /* look up for the bank which owns the requested pin */
130 list_for_each_entry(gpio_bank, &priv->gpio_dev, list) {
131 uc_priv = dev_get_uclass_priv(gpio_bank->gpio_dev);
132
Patrice Chotard0b968002018-12-03 10:52:54 +0100133 if (selector < (pin_count + uc_priv->gpio_count)) {
134 /*
135 * we found the bank, convert pin selector to
136 * gpio bank index
137 */
138 *idx = stm32_offset_to_index(gpio_bank->gpio_dev,
139 selector - pin_count);
Patrick Delaunay4c11a112019-06-21 15:26:52 +0200140 if (IS_ERR_VALUE(*idx))
Patrice Chotard0b968002018-12-03 10:52:54 +0100141 return NULL;
Patrice Chotard881e8672018-10-24 14:10:19 +0200142
Patrice Chotard0b968002018-12-03 10:52:54 +0100143 return gpio_bank->gpio_dev;
144 }
145 pin_count += uc_priv->gpio_count;
Patrice Chotard881e8672018-10-24 14:10:19 +0200146 }
147
148 return NULL;
149}
150
151static const char *stm32_pinctrl_get_pin_name(struct udevice *dev,
152 unsigned int selector)
153{
154 struct gpio_dev_priv *uc_priv;
155 struct udevice *gpio_dev;
Patrice Chotard0b968002018-12-03 10:52:54 +0100156 unsigned int gpio_idx;
Patrice Chotard881e8672018-10-24 14:10:19 +0200157
158 /* look up for the bank which owns the requested pin */
Patrice Chotard0b968002018-12-03 10:52:54 +0100159 gpio_dev = stm32_pinctrl_get_gpio_dev(dev, selector, &gpio_idx);
Patrice Chotard881e8672018-10-24 14:10:19 +0200160 if (!gpio_dev) {
161 snprintf(pin_name, PINNAME_SIZE, "Error");
162 } else {
163 uc_priv = dev_get_uclass_priv(gpio_dev);
164
165 snprintf(pin_name, PINNAME_SIZE, "%s%d",
166 uc_priv->bank_name,
Patrice Chotard0b968002018-12-03 10:52:54 +0100167 gpio_idx);
Patrice Chotard881e8672018-10-24 14:10:19 +0200168 }
169
170 return pin_name;
171}
Patrice Chotarda46fb392018-10-24 14:10:20 +0200172
173static int stm32_pinctrl_get_pin_muxing(struct udevice *dev,
174 unsigned int selector,
175 char *buf,
176 int size)
177{
178 struct udevice *gpio_dev;
179 const char *label;
Patrice Chotarda46fb392018-10-24 14:10:20 +0200180 int mode;
181 int af_num;
Patrice Chotard0b968002018-12-03 10:52:54 +0100182 unsigned int gpio_idx;
Patrice Chotarda46fb392018-10-24 14:10:20 +0200183
184 /* look up for the bank which owns the requested pin */
Patrice Chotard0b968002018-12-03 10:52:54 +0100185 gpio_dev = stm32_pinctrl_get_gpio_dev(dev, selector, &gpio_idx);
Patrice Chotarda46fb392018-10-24 14:10:20 +0200186
187 if (!gpio_dev)
188 return -ENODEV;
189
Patrice Chotard0b968002018-12-03 10:52:54 +0100190 mode = gpio_get_raw_function(gpio_dev, gpio_idx, &label);
Patrice Chotarda46fb392018-10-24 14:10:20 +0200191
Patrice Chotard0b968002018-12-03 10:52:54 +0100192 dev_dbg(dev, "selector = %d gpio_idx = %d mode = %d\n",
193 selector, gpio_idx, mode);
Patrice Chotarda46fb392018-10-24 14:10:20 +0200194
Patrice Chotarda46fb392018-10-24 14:10:20 +0200195
196 switch (mode) {
197 case GPIOF_UNKNOWN:
198 /* should never happen */
199 return -EINVAL;
200 case GPIOF_UNUSED:
201 snprintf(buf, size, "%s", pinmux_mode[mode]);
202 break;
203 case GPIOF_FUNC:
Patrice Chotard0b968002018-12-03 10:52:54 +0100204 af_num = stm32_pinctrl_get_af(gpio_dev, gpio_idx);
Patrice Chotarda46fb392018-10-24 14:10:20 +0200205 snprintf(buf, size, "%s %d", pinmux_mode[mode], af_num);
206 break;
207 case GPIOF_OUTPUT:
208 case GPIOF_INPUT:
209 snprintf(buf, size, "%s %s",
210 pinmux_mode[mode], label ? label : "");
211 break;
212 }
213
214 return 0;
215}
216
Benjamin Gaignard16f6f332018-11-27 13:49:53 +0100217#endif
218
Patrick Delaunay4c11a112019-06-21 15:26:52 +0200219static int stm32_pinctrl_probe(struct udevice *dev)
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200220{
221 struct stm32_pinctrl_priv *priv = dev_get_priv(dev);
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200222 int ret;
223
224 INIT_LIST_HEAD(&priv->gpio_dev);
225
Benjamin Gaignard16f6f332018-11-27 13:49:53 +0100226 /* hwspinlock property is optional, just log the error */
227 ret = hwspinlock_get_by_index(dev, 0, &priv->hws);
228 if (ret)
229 debug("%s: hwspinlock_get_by_index may have failed (%d)\n",
230 __func__, ret);
231
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200232 return 0;
233}
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200234
Vikas Manochaec8630a2017-04-10 15:02:57 -0700235static int stm32_gpio_config(struct gpio_desc *desc,
236 const struct stm32_gpio_ctl *ctl)
237{
238 struct stm32_gpio_priv *priv = dev_get_priv(desc->dev);
239 struct stm32_gpio_regs *regs = priv->regs;
Benjamin Gaignard16f6f332018-11-27 13:49:53 +0100240 struct stm32_pinctrl_priv *ctrl_priv;
241 int ret;
Vikas Manochaec8630a2017-04-10 15:02:57 -0700242 u32 index;
243
244 if (!ctl || ctl->af > 15 || ctl->mode > 3 || ctl->otype > 1 ||
245 ctl->pupd > 2 || ctl->speed > 3)
246 return -EINVAL;
247
Benjamin Gaignard16f6f332018-11-27 13:49:53 +0100248 ctrl_priv = dev_get_priv(dev_get_parent(desc->dev));
249 ret = hwspinlock_lock_timeout(&ctrl_priv->hws, 10);
250 if (ret == -ETIME) {
251 dev_err(desc->dev, "HWSpinlock timeout\n");
252 return ret;
253 }
254
Vikas Manochaec8630a2017-04-10 15:02:57 -0700255 index = (desc->offset & 0x07) * 4;
256 clrsetbits_le32(&regs->afr[desc->offset >> 3], AFR_MASK << index,
257 ctl->af << index);
258
259 index = desc->offset * 2;
260 clrsetbits_le32(&regs->moder, MODE_BITS_MASK << index,
261 ctl->mode << index);
262 clrsetbits_le32(&regs->ospeedr, OSPEED_MASK << index,
263 ctl->speed << index);
264 clrsetbits_le32(&regs->pupdr, PUPD_MASK << index, ctl->pupd << index);
265
266 index = desc->offset;
267 clrsetbits_le32(&regs->otyper, OTYPE_MSK << index, ctl->otype << index);
268
Benjamin Gaignard16f6f332018-11-27 13:49:53 +0100269 hwspinlock_unlock(&ctrl_priv->hws);
270
Vikas Manochaec8630a2017-04-10 15:02:57 -0700271 return 0;
272}
Patrick Delaunayd252d752018-03-12 10:46:13 +0100273
Vikas Manocha07e9e412017-02-12 10:25:49 -0800274static int prep_gpio_dsc(struct stm32_gpio_dsc *gpio_dsc, u32 port_pin)
275{
Patrick Delaunayd252d752018-03-12 10:46:13 +0100276 gpio_dsc->port = (port_pin & 0x1F000) >> 12;
Vikas Manocha07e9e412017-02-12 10:25:49 -0800277 gpio_dsc->pin = (port_pin & 0x0F00) >> 8;
278 debug("%s: GPIO:port= %d, pin= %d\n", __func__, gpio_dsc->port,
279 gpio_dsc->pin);
280
281 return 0;
282}
283
284static int prep_gpio_ctl(struct stm32_gpio_ctl *gpio_ctl, u32 gpio_fn, int node)
285{
286 gpio_fn &= 0x00FF;
Vikas Manochaec8630a2017-04-10 15:02:57 -0700287 gpio_ctl->af = 0;
Vikas Manocha07e9e412017-02-12 10:25:49 -0800288
289 switch (gpio_fn) {
290 case 0:
291 gpio_ctl->mode = STM32_GPIO_MODE_IN;
292 break;
293 case 1 ... 16:
294 gpio_ctl->mode = STM32_GPIO_MODE_AF;
295 gpio_ctl->af = gpio_fn - 1;
296 break;
297 case 17:
298 gpio_ctl->mode = STM32_GPIO_MODE_AN;
299 break;
300 default:
301 gpio_ctl->mode = STM32_GPIO_MODE_OUT;
302 break;
303 }
304
305 gpio_ctl->speed = fdtdec_get_int(gd->fdt_blob, node, "slew-rate", 0);
306
307 if (fdtdec_get_bool(gd->fdt_blob, node, "drive-open-drain"))
308 gpio_ctl->otype = STM32_GPIO_OTYPE_OD;
309 else
310 gpio_ctl->otype = STM32_GPIO_OTYPE_PP;
311
312 if (fdtdec_get_bool(gd->fdt_blob, node, "bias-pull-up"))
313 gpio_ctl->pupd = STM32_GPIO_PUPD_UP;
314 else if (fdtdec_get_bool(gd->fdt_blob, node, "bias-pull-down"))
315 gpio_ctl->pupd = STM32_GPIO_PUPD_DOWN;
316 else
317 gpio_ctl->pupd = STM32_GPIO_PUPD_NO;
318
319 debug("%s: gpio fn= %d, slew-rate= %x, op type= %x, pull-upd is = %x\n",
320 __func__, gpio_fn, gpio_ctl->speed, gpio_ctl->otype,
321 gpio_ctl->pupd);
322
323 return 0;
324}
325
Christophe Kerelloa466d212017-06-20 17:04:18 +0200326static int stm32_pinctrl_config(int offset)
Vikas Manocha07e9e412017-02-12 10:25:49 -0800327{
Vikas Manocha40ddb3a2017-04-10 15:03:04 -0700328 u32 pin_mux[MAX_PINS_ONE_IP];
Vikas Manocha07e9e412017-02-12 10:25:49 -0800329 int rv, len;
330
Vikas Manocha07e9e412017-02-12 10:25:49 -0800331 /*
332 * check for "pinmux" property in each subnode (e.g. pins1 and pins2 for
333 * usart1) of pin controller phandle "pinctrl-0"
334 * */
Christophe Kerelloa466d212017-06-20 17:04:18 +0200335 fdt_for_each_subnode(offset, gd->fdt_blob, offset) {
Vikas Manocha07e9e412017-02-12 10:25:49 -0800336 struct stm32_gpio_dsc gpio_dsc;
337 struct stm32_gpio_ctl gpio_ctl;
338 int i;
339
Christophe Kerelloa466d212017-06-20 17:04:18 +0200340 len = fdtdec_get_int_array_count(gd->fdt_blob, offset,
Vikas Manocha07e9e412017-02-12 10:25:49 -0800341 "pinmux", pin_mux,
342 ARRAY_SIZE(pin_mux));
Christophe Kerelloa466d212017-06-20 17:04:18 +0200343 debug("%s: no of pinmux entries= %d\n", __func__, len);
Vikas Manocha07e9e412017-02-12 10:25:49 -0800344 if (len < 0)
345 return -EINVAL;
346 for (i = 0; i < len; i++) {
Vikas Manocha1a8fde72017-04-10 15:02:59 -0700347 struct gpio_desc desc;
Patrick Delaunayd252d752018-03-12 10:46:13 +0100348
Vikas Manocha07e9e412017-02-12 10:25:49 -0800349 debug("%s: pinmux = %x\n", __func__, *(pin_mux + i));
350 prep_gpio_dsc(&gpio_dsc, *(pin_mux + i));
Christophe Kerelloa466d212017-06-20 17:04:18 +0200351 prep_gpio_ctl(&gpio_ctl, *(pin_mux + i), offset);
Vikas Manocha1a8fde72017-04-10 15:02:59 -0700352 rv = uclass_get_device_by_seq(UCLASS_GPIO,
Patrick Delaunayd252d752018-03-12 10:46:13 +0100353 gpio_dsc.port,
354 &desc.dev);
Vikas Manocha1a8fde72017-04-10 15:02:59 -0700355 if (rv)
356 return rv;
357 desc.offset = gpio_dsc.pin;
358 rv = stm32_gpio_config(&desc, &gpio_ctl);
Vikas Manocha07e9e412017-02-12 10:25:49 -0800359 debug("%s: rv = %d\n\n", __func__, rv);
360 if (rv)
361 return rv;
362 }
Christophe Kerelloa466d212017-06-20 17:04:18 +0200363 }
364
365 return 0;
366}
367
Patrice Chotard05a93192019-06-21 15:39:23 +0200368static int stm32_pinctrl_bind(struct udevice *dev)
369{
370 ofnode node;
371 const char *name;
372 int ret;
373
374 dev_for_each_subnode(node, dev) {
375 debug("%s: bind %s\n", __func__, ofnode_get_name(node));
376
377 ofnode_get_property(node, "gpio-controller", &ret);
378 if (ret < 0)
379 continue;
380 /* Get the name of each gpio node */
381 name = ofnode_get_name(node);
382 if (!name)
383 return -EINVAL;
384
385 /* Bind each gpio node */
386 ret = device_bind_driver_to_node(dev, "gpio_stm32",
387 name, node, NULL);
388 if (ret)
389 return ret;
390
391 debug("%s: bind %s\n", __func__, name);
392 }
393
394 return 0;
395}
396
Christophe Kerellod6661552017-06-20 17:04:19 +0200397#if CONFIG_IS_ENABLED(PINCTRL_FULL)
398static int stm32_pinctrl_set_state(struct udevice *dev, struct udevice *config)
399{
400 return stm32_pinctrl_config(dev_of_offset(config));
401}
402#else /* PINCTRL_FULL */
Christophe Kerelloa466d212017-06-20 17:04:18 +0200403static int stm32_pinctrl_set_state_simple(struct udevice *dev,
404 struct udevice *periph)
405{
406 const void *fdt = gd->fdt_blob;
407 const fdt32_t *list;
408 uint32_t phandle;
409 int config_node;
410 int size, i, ret;
411
412 list = fdt_getprop(fdt, dev_of_offset(periph), "pinctrl-0", &size);
413 if (!list)
414 return -EINVAL;
415
416 debug("%s: periph->name = %s\n", __func__, periph->name);
417
418 size /= sizeof(*list);
419 for (i = 0; i < size; i++) {
420 phandle = fdt32_to_cpu(*list++);
421
422 config_node = fdt_node_offset_by_phandle(fdt, phandle);
423 if (config_node < 0) {
Masahiro Yamada81e10422017-09-16 14:10:41 +0900424 pr_err("prop pinctrl-0 index %d invalid phandle\n", i);
Christophe Kerelloa466d212017-06-20 17:04:18 +0200425 return -EINVAL;
426 }
427
428 ret = stm32_pinctrl_config(config_node);
429 if (ret)
430 return ret;
Vikas Manocha07e9e412017-02-12 10:25:49 -0800431 }
432
433 return 0;
434}
Christophe Kerellod6661552017-06-20 17:04:19 +0200435#endif /* PINCTRL_FULL */
Vikas Manocha07e9e412017-02-12 10:25:49 -0800436
437static struct pinctrl_ops stm32_pinctrl_ops = {
Christophe Kerellod6661552017-06-20 17:04:19 +0200438#if CONFIG_IS_ENABLED(PINCTRL_FULL)
439 .set_state = stm32_pinctrl_set_state,
440#else /* PINCTRL_FULL */
Vikas Manocha07e9e412017-02-12 10:25:49 -0800441 .set_state_simple = stm32_pinctrl_set_state_simple,
Christophe Kerellod6661552017-06-20 17:04:19 +0200442#endif /* PINCTRL_FULL */
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200443#ifndef CONFIG_SPL_BUILD
Patrice Chotard881e8672018-10-24 14:10:19 +0200444 .get_pin_name = stm32_pinctrl_get_pin_name,
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200445 .get_pins_count = stm32_pinctrl_get_pins_count,
Patrice Chotarda46fb392018-10-24 14:10:20 +0200446 .get_pin_muxing = stm32_pinctrl_get_pin_muxing,
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200447#endif
Vikas Manocha07e9e412017-02-12 10:25:49 -0800448};
449
450static const struct udevice_id stm32_pinctrl_ids[] = {
Patrice Chotardb5652b72017-12-12 09:49:35 +0100451 { .compatible = "st,stm32f429-pinctrl" },
452 { .compatible = "st,stm32f469-pinctrl" },
Vikas Manocha07e9e412017-02-12 10:25:49 -0800453 { .compatible = "st,stm32f746-pinctrl" },
Patrice Chotard636768f2018-12-11 14:49:18 +0100454 { .compatible = "st,stm32f769-pinctrl" },
Patrice Chotard6502c472017-09-13 18:00:04 +0200455 { .compatible = "st,stm32h743-pinctrl" },
Patrick Delaunayd252d752018-03-12 10:46:13 +0100456 { .compatible = "st,stm32mp157-pinctrl" },
457 { .compatible = "st,stm32mp157-z-pinctrl" },
Vikas Manocha07e9e412017-02-12 10:25:49 -0800458 { }
459};
460
461U_BOOT_DRIVER(pinctrl_stm32) = {
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200462 .name = "pinctrl_stm32",
463 .id = UCLASS_PINCTRL,
464 .of_match = stm32_pinctrl_ids,
465 .ops = &stm32_pinctrl_ops,
Patrice Chotard05a93192019-06-21 15:39:23 +0200466 .bind = stm32_pinctrl_bind,
Patrice Chotardaaf68e82018-10-24 14:10:18 +0200467 .probe = stm32_pinctrl_probe,
468 .priv_auto_alloc_size = sizeof(struct stm32_pinctrl_priv),
Vikas Manocha07e9e412017-02-12 10:25:49 -0800469};