summaryrefslogtreecommitdiff
path: root/drivers/mmc/host/dw_mmc-k3.c
blob: 68e5e428e8f61662ff4a4b7f13e3e3c16e0b392e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/*
 * Copyright (c) 2013 Linaro Ltd.
 * Copyright (c) 2013 Hisilicon Limited.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 */

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/mmc/host.h>
#include <linux/mmc/dw_mmc.h>
#include <linux/of_address.h>

#include "dw_mmc.h"
#include "dw_mmc-pltfm.h"

#define MAX_NUMS	10
struct dw_mci_k3_priv_data {
	u32	clk_table[MAX_NUMS];
};

static void dw_mci_k3_set_ios(struct dw_mci *host, struct mmc_ios *ios)
{
	struct dw_mci_k3_priv_data *priv = host->priv;
	u32 rate = priv->clk_table[ios->timing];
	int ret;

	if (!rate) {
		dev_warn(host->dev,
			"no specified rate in timing %u\n", ios->timing);
		return;
	}

	ret = clk_set_rate(host->ciu_clk, rate);
	if (ret)
		dev_warn(host->dev, "failed to set clock rate %uHz\n", rate);

	host->bus_hz = clk_get_rate(host->ciu_clk);
}

static int dw_mci_k3_parse_dt(struct dw_mci *host)
{
	struct dw_mci_k3_priv_data *priv;
	struct device_node *node = host->dev->of_node;
	struct property *prop;
	const __be32 *cur;
	u32 val, num = 0;

	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv) {
		dev_err(host->dev, "mem alloc failed for private data\n");
		return -ENOMEM;
	}
	host->priv = priv;

	of_property_for_each_u32(node, "clock-freq-table", prop, cur, val) {
		if (num >= MAX_NUMS)
			break;
		priv->clk_table[num++] = val;
	}
	return 0;
}

static const struct dw_mci_drv_data k3_drv_data = {
	.set_ios		= dw_mci_k3_set_ios,
	.parse_dt		= dw_mci_k3_parse_dt,
};

static const struct of_device_id dw_mci_k3_match[] = {
	{ .compatible = "hisilicon,hi4511-dw-mshc", .data = &k3_drv_data, },
	{},
};
MODULE_DEVICE_TABLE(of, dw_mci_k3_match);

static int dw_mci_k3_probe(struct platform_device *pdev)
{
	const struct dw_mci_drv_data *drv_data;
	const struct of_device_id *match;

	match = of_match_node(dw_mci_k3_match, pdev->dev.of_node);
	drv_data = match->data;

	return dw_mci_pltfm_register(pdev, drv_data);
}

static int dw_mci_k3_suspend(struct device *dev)
{
	struct dw_mci *host = dev_get_drvdata(dev);
	int ret;

	ret = dw_mci_suspend(host);
	if (!ret)
		clk_disable_unprepare(host->ciu_clk);

	return ret;
}

static int dw_mci_k3_resume(struct device *dev)
{
	struct dw_mci *host = dev_get_drvdata(dev);
	int ret;

	ret = clk_prepare_enable(host->ciu_clk);
	if (ret) {
		dev_err(host->dev, "failed to enable ciu clock\n");
		return ret;
	}

	return dw_mci_resume(host);
}

SIMPLE_DEV_PM_OPS(dw_mci_k3_pmops, dw_mci_k3_suspend, dw_mci_k3_resume);

static struct platform_driver dw_mci_k3_pltfm_driver = {
	.probe		= dw_mci_k3_probe,
	.remove		= dw_mci_pltfm_remove,
	.driver		= {
		.name		= "dwmmc_k3",
		.of_match_table	= dw_mci_k3_match,
		.pm		= &dw_mci_k3_pmops,
	},
};

module_platform_driver(dw_mci_k3_pltfm_driver);

MODULE_DESCRIPTION("K3 Specific DW-MSHC Driver Extension");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:dwmmc-k3");