aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/nvmem/qcom-spmi-sdam.c
blob: 8682cda448d682b9c653502a64006fefe3837faa (plain) (blame)
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2017 The Linux Foundation. All rights reserved.
 */

#include <linux/device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/nvmem-provider.h>
#include <linux/regmap.h>

#define SDAM_MEM_START			0x40
#define REGISTER_MAP_ID			0x40
#define REGISTER_MAP_VERSION		0x41
#define SDAM_SIZE			0x44
#define SDAM_PBS_TRIG_SET		0xE5
#define SDAM_PBS_TRIG_CLR		0xE6

struct sdam_chip {
	struct platform_device		*pdev;
	struct regmap			*regmap;
	struct nvmem_config		sdam_config;
	unsigned int			base;
	unsigned int			size;
};

/* read only register offsets */
static const u8 sdam_ro_map[] = {
	REGISTER_MAP_ID,
	REGISTER_MAP_VERSION,
	SDAM_SIZE
};

static bool sdam_is_valid(struct sdam_chip *sdam, unsigned int offset,
				size_t len)
{
	unsigned int sdam_mem_end = SDAM_MEM_START + sdam->size - 1;

	if (!len)
		return false;

	if (offset >= SDAM_MEM_START && offset <= sdam_mem_end
				&& (offset + len - 1) <= sdam_mem_end)
		return true;
	else if ((offset == SDAM_PBS_TRIG_SET || offset == SDAM_PBS_TRIG_CLR)
				&& (len == 1))
		return true;

	return false;
}

static bool sdam_is_ro(unsigned int offset, size_t len)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(sdam_ro_map); i++)
		if (offset <= sdam_ro_map[i] && (offset + len) > sdam_ro_map[i])
			return true;

	return false;
}

static int sdam_read(void *priv, unsigned int offset, void *val,
				size_t bytes)
{
	struct sdam_chip *sdam = priv;
	struct device *dev = &sdam->pdev->dev;
	int rc;

	if (!sdam_is_valid(sdam, offset, bytes)) {
		dev_err(dev, "Invalid SDAM offset %#x len=%zd\n",
			offset, bytes);
		return -EINVAL;
	}

	rc = regmap_bulk_read(sdam->regmap, sdam->base + offset, val, bytes);
	if (rc < 0)
		dev_err(dev, "Failed to read SDAM offset %#x len=%zd, rc=%d\n",
						offset, bytes, rc);

	return rc;
}

static int sdam_write(void *priv, unsigned int offset, void *val,
				size_t bytes)
{
	struct sdam_chip *sdam = priv;
	struct device *dev = &sdam->pdev->dev;
	int rc;

	if (!sdam_is_valid(sdam, offset, bytes)) {
		dev_err(dev, "Invalid SDAM offset %#x len=%zd\n",
			offset, bytes);
		return -EINVAL;
	}

	if (sdam_is_ro(offset, bytes)) {
		dev_err(dev, "Invalid write offset %#x len=%zd\n",
			offset, bytes);
		return -EINVAL;
	}

	rc = regmap_bulk_write(sdam->regmap, sdam->base + offset, val, bytes);
	if (rc < 0)
		dev_err(dev, "Failed to write SDAM offset %#x len=%zd, rc=%d\n",
						offset, bytes, rc);

	return rc;
}

static int sdam_probe(struct platform_device *pdev)
{
	struct sdam_chip *sdam;
	struct nvmem_device *nvmem;
	unsigned int val;
	int rc;

	sdam = devm_kzalloc(&pdev->dev, sizeof(*sdam), GFP_KERNEL);
	if (!sdam)
		return -ENOMEM;

	sdam->regmap = dev_get_regmap(pdev->dev.parent, NULL);
	if (!sdam->regmap) {
		dev_err(&pdev->dev, "Failed to get regmap handle\n");
		return -ENXIO;
	}

	rc = of_property_read_u32(pdev->dev.of_node, "reg", &sdam->base);
	if (rc < 0) {
		dev_err(&pdev->dev, "Failed to get SDAM base, rc=%d\n", rc);
		return -EINVAL;
	}

	rc = regmap_read(sdam->regmap, sdam->base + SDAM_SIZE, &val);
	if (rc < 0) {
		dev_err(&pdev->dev, "Failed to read SDAM_SIZE rc=%d\n", rc);
		return -EINVAL;
	}
	sdam->size = val * 32;

	sdam->sdam_config.dev = &pdev->dev;
	sdam->sdam_config.name = "spmi_sdam";
	sdam->sdam_config.id = pdev->id;
	sdam->sdam_config.owner = THIS_MODULE,
	sdam->sdam_config.stride = 1;
	sdam->sdam_config.word_size = 1;
	sdam->sdam_config.reg_read = sdam_read;
	sdam->sdam_config.reg_write = sdam_write;
	sdam->sdam_config.priv = sdam;

	nvmem = devm_nvmem_register(&pdev->dev, &sdam->sdam_config);
	if (IS_ERR(nvmem)) {
		dev_err(&pdev->dev,
			"Failed to register SDAM nvmem device rc=%ld\n",
			PTR_ERR(nvmem));
		return -ENXIO;
	}
	dev_dbg(&pdev->dev,
		"SDAM base=%#x size=%u registered successfully\n",
		sdam->base, sdam->size);

	return 0;
}

static const struct of_device_id sdam_match_table[] = {
	{ .compatible = "qcom,spmi-sdam" },
	{},
};

static struct platform_driver sdam_driver = {
	.driver = {
		.name = "qcom,spmi-sdam",
		.of_match_table = sdam_match_table,
	},
	.probe		= sdam_probe,
};

static int __init sdam_init(void)
{
	return platform_driver_register(&sdam_driver);
}
subsys_initcall(sdam_init);

static void __exit sdam_exit(void)
{
	return platform_driver_unregister(&sdam_driver);
}
module_exit(sdam_exit);

MODULE_DESCRIPTION("QCOM SPMI SDAM driver");
MODULE_LICENSE("GPL v2");