1/*
2 *  Copyright (C) 2014 Linaro Ltd
3 *
4 * Author: Ulf Hansson <ulf.hansson@linaro.org>
5 *
6 * License terms: GNU General Public License (GPL) version 2
7 *
8 *  Simple MMC power sequence management
9 */
10#include <linux/clk.h>
11#include <linux/kernel.h>
12#include <linux/slab.h>
13#include <linux/device.h>
14#include <linux/err.h>
15#include <linux/of_gpio.h>
16#include <linux/gpio/consumer.h>
17
18#include <linux/mmc/host.h>
19
20#include "pwrseq.h"
21
22struct mmc_pwrseq_simple {
23	struct mmc_pwrseq pwrseq;
24	bool clk_enabled;
25	struct clk *ext_clk;
26	struct gpio_descs *reset_gpios;
27};
28
29static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq,
30					      int value)
31{
32	int i;
33	struct gpio_descs *reset_gpios = pwrseq->reset_gpios;
34	int values[reset_gpios->ndescs];
35
36	for (i = 0; i < reset_gpios->ndescs; i++)
37		values[i] = value;
38
39	gpiod_set_array_value_cansleep(reset_gpios->ndescs, reset_gpios->desc,
40				       values);
41}
42
43static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host)
44{
45	struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq,
46					struct mmc_pwrseq_simple, pwrseq);
47
48	if (!IS_ERR(pwrseq->ext_clk) && !pwrseq->clk_enabled) {
49		clk_prepare_enable(pwrseq->ext_clk);
50		pwrseq->clk_enabled = true;
51	}
52
53	mmc_pwrseq_simple_set_gpios_value(pwrseq, 1);
54}
55
56static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host)
57{
58	struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq,
59					struct mmc_pwrseq_simple, pwrseq);
60
61	mmc_pwrseq_simple_set_gpios_value(pwrseq, 0);
62}
63
64static void mmc_pwrseq_simple_power_off(struct mmc_host *host)
65{
66	struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq,
67					struct mmc_pwrseq_simple, pwrseq);
68
69	mmc_pwrseq_simple_set_gpios_value(pwrseq, 1);
70
71	if (!IS_ERR(pwrseq->ext_clk) && pwrseq->clk_enabled) {
72		clk_disable_unprepare(pwrseq->ext_clk);
73		pwrseq->clk_enabled = false;
74	}
75}
76
77static void mmc_pwrseq_simple_free(struct mmc_host *host)
78{
79	struct mmc_pwrseq_simple *pwrseq = container_of(host->pwrseq,
80					struct mmc_pwrseq_simple, pwrseq);
81
82	gpiod_put_array(pwrseq->reset_gpios);
83
84	if (!IS_ERR(pwrseq->ext_clk))
85		clk_put(pwrseq->ext_clk);
86
87	kfree(pwrseq);
88}
89
90static struct mmc_pwrseq_ops mmc_pwrseq_simple_ops = {
91	.pre_power_on = mmc_pwrseq_simple_pre_power_on,
92	.post_power_on = mmc_pwrseq_simple_post_power_on,
93	.power_off = mmc_pwrseq_simple_power_off,
94	.free = mmc_pwrseq_simple_free,
95};
96
97struct mmc_pwrseq *mmc_pwrseq_simple_alloc(struct mmc_host *host,
98					   struct device *dev)
99{
100	struct mmc_pwrseq_simple *pwrseq;
101	int ret = 0;
102
103	pwrseq = kzalloc(sizeof(*pwrseq), GFP_KERNEL);
104	if (!pwrseq)
105		return ERR_PTR(-ENOMEM);
106
107	pwrseq->ext_clk = clk_get(dev, "ext_clock");
108	if (IS_ERR(pwrseq->ext_clk) &&
109	    PTR_ERR(pwrseq->ext_clk) != -ENOENT) {
110		ret = PTR_ERR(pwrseq->ext_clk);
111		goto free;
112	}
113
114	pwrseq->reset_gpios = gpiod_get_array(dev, "reset", GPIOD_OUT_HIGH);
115	if (IS_ERR(pwrseq->reset_gpios)) {
116		ret = PTR_ERR(pwrseq->reset_gpios);
117		goto clk_put;
118	}
119
120	pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops;
121
122	return &pwrseq->pwrseq;
123clk_put:
124	if (!IS_ERR(pwrseq->ext_clk))
125		clk_put(pwrseq->ext_clk);
126free:
127	kfree(pwrseq);
128	return ERR_PTR(ret);
129}
130