brintos

brintos / linux-shallow public Read only

0
0
Text · 7.0 KiB · 1143526 Raw
286 lines · c
1// SPDX-License-Identifier: GPL-2.02/*3 * Copyright (c) 2021 Linaro Ltd.4 * Author: Sam Protsenko <semen.protsenko@linaro.org>5 *6 * Samsung Exynos USI driver (Universal Serial Interface).7 */8 9#include <linux/clk.h>10#include <linux/mfd/syscon.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/of_platform.h>14#include <linux/platform_device.h>15#include <linux/regmap.h>16 17#include <dt-bindings/soc/samsung,exynos-usi.h>18 19/* USIv2: System Register: SW_CONF register bits */20#define USI_V2_SW_CONF_NONE	0x021#define USI_V2_SW_CONF_UART	BIT(0)22#define USI_V2_SW_CONF_SPI	BIT(1)23#define USI_V2_SW_CONF_I2C	BIT(2)24#define USI_V2_SW_CONF_MASK	(USI_V2_SW_CONF_UART | USI_V2_SW_CONF_SPI | \25				 USI_V2_SW_CONF_I2C)26 27/* USIv2: USI register offsets */28#define USI_CON			0x0429#define USI_OPTION		0x0830 31/* USIv2: USI register bits */32#define USI_CON_RESET		BIT(0)33#define USI_OPTION_CLKREQ_ON	BIT(1)34#define USI_OPTION_CLKSTOP_ON	BIT(2)35 36enum exynos_usi_ver {37	USI_VER2 = 2,38};39 40struct exynos_usi_variant {41	enum exynos_usi_ver ver;	/* USI IP-core version */42	unsigned int sw_conf_mask;	/* SW_CONF mask for all protocols */43	size_t min_mode;		/* first index in exynos_usi_modes[] */44	size_t max_mode;		/* last index in exynos_usi_modes[] */45	size_t num_clks;		/* number of clocks to assert */46	const char * const *clk_names;	/* clock names to assert */47};48 49struct exynos_usi {50	struct device *dev;51	void __iomem *regs;		/* USI register map */52	struct clk_bulk_data *clks;	/* USI clocks */53 54	size_t mode;			/* current USI SW_CONF mode index */55	bool clkreq_on;			/* always provide clock to IP */56 57	/* System Register */58	struct regmap *sysreg;		/* System Register map */59	unsigned int sw_conf;		/* SW_CONF register offset in sysreg */60 61	const struct exynos_usi_variant *data;62};63 64struct exynos_usi_mode {65	const char *name;		/* mode name */66	unsigned int val;		/* mode register value */67};68 69static const struct exynos_usi_mode exynos_usi_modes[] = {70	[USI_V2_NONE] =	{ .name = "none", .val = USI_V2_SW_CONF_NONE },71	[USI_V2_UART] =	{ .name = "uart", .val = USI_V2_SW_CONF_UART },72	[USI_V2_SPI] =	{ .name = "spi",  .val = USI_V2_SW_CONF_SPI },73	[USI_V2_I2C] =	{ .name = "i2c",  .val = USI_V2_SW_CONF_I2C },74};75 76static const char * const exynos850_usi_clk_names[] = { "pclk", "ipclk" };77static const struct exynos_usi_variant exynos850_usi_data = {78	.ver		= USI_VER2,79	.sw_conf_mask	= USI_V2_SW_CONF_MASK,80	.min_mode	= USI_V2_NONE,81	.max_mode	= USI_V2_I2C,82	.num_clks	= ARRAY_SIZE(exynos850_usi_clk_names),83	.clk_names	= exynos850_usi_clk_names,84};85 86static const struct of_device_id exynos_usi_dt_match[] = {87	{88		.compatible = "samsung,exynos850-usi",89		.data = &exynos850_usi_data,90	},91	{ } /* sentinel */92};93MODULE_DEVICE_TABLE(of, exynos_usi_dt_match);94 95/**96 * exynos_usi_set_sw_conf - Set USI block configuration mode97 * @usi: USI driver object98 * @mode: Mode index99 *100 * Select underlying serial protocol (UART/SPI/I2C) in USI IP-core.101 *102 * Return: 0 on success, or negative error code on failure.103 */104static int exynos_usi_set_sw_conf(struct exynos_usi *usi, size_t mode)105{106	unsigned int val;107	int ret;108 109	if (mode < usi->data->min_mode || mode > usi->data->max_mode)110		return -EINVAL;111 112	val = exynos_usi_modes[mode].val;113	ret = regmap_update_bits(usi->sysreg, usi->sw_conf,114				 usi->data->sw_conf_mask, val);115	if (ret)116		return ret;117 118	usi->mode = mode;119	dev_dbg(usi->dev, "protocol: %s\n", exynos_usi_modes[usi->mode].name);120 121	return 0;122}123 124/**125 * exynos_usi_enable - Initialize USI block126 * @usi: USI driver object127 *128 * USI IP-core start state is "reset" (on startup and after CPU resume). This129 * routine enables the USI block by clearing the reset flag. It also configures130 * HWACG behavior (needed e.g. for UART Rx). It should be performed before131 * underlying protocol becomes functional.132 *133 * Return: 0 on success, or negative error code on failure.134 */135static int exynos_usi_enable(const struct exynos_usi *usi)136{137	u32 val;138	int ret;139 140	ret = clk_bulk_prepare_enable(usi->data->num_clks, usi->clks);141	if (ret)142		return ret;143 144	/* Enable USI block */145	val = readl(usi->regs + USI_CON);146	val &= ~USI_CON_RESET;147	writel(val, usi->regs + USI_CON);148	udelay(1);149 150	/* Continuously provide the clock to USI IP w/o gating */151	if (usi->clkreq_on) {152		val = readl(usi->regs + USI_OPTION);153		val &= ~USI_OPTION_CLKSTOP_ON;154		val |= USI_OPTION_CLKREQ_ON;155		writel(val, usi->regs + USI_OPTION);156	}157 158	clk_bulk_disable_unprepare(usi->data->num_clks, usi->clks);159 160	return ret;161}162 163static int exynos_usi_configure(struct exynos_usi *usi)164{165	int ret;166 167	ret = exynos_usi_set_sw_conf(usi, usi->mode);168	if (ret)169		return ret;170 171	if (usi->data->ver == USI_VER2)172		return exynos_usi_enable(usi);173 174	return 0;175}176 177static int exynos_usi_parse_dt(struct device_node *np, struct exynos_usi *usi)178{179	int ret;180	u32 mode;181 182	ret = of_property_read_u32(np, "samsung,mode", &mode);183	if (ret)184		return ret;185	if (mode < usi->data->min_mode || mode > usi->data->max_mode)186		return -EINVAL;187	usi->mode = mode;188 189	usi->sysreg = syscon_regmap_lookup_by_phandle(np, "samsung,sysreg");190	if (IS_ERR(usi->sysreg))191		return PTR_ERR(usi->sysreg);192 193	ret = of_property_read_u32_index(np, "samsung,sysreg", 1,194					 &usi->sw_conf);195	if (ret)196		return ret;197 198	usi->clkreq_on = of_property_read_bool(np, "samsung,clkreq-on");199 200	return 0;201}202 203static int exynos_usi_get_clocks(struct exynos_usi *usi)204{205	const size_t num = usi->data->num_clks;206	struct device *dev = usi->dev;207	size_t i;208 209	if (num == 0)210		return 0;211 212	usi->clks = devm_kcalloc(dev, num, sizeof(*usi->clks), GFP_KERNEL);213	if (!usi->clks)214		return -ENOMEM;215 216	for (i = 0; i < num; ++i)217		usi->clks[i].id = usi->data->clk_names[i];218 219	return devm_clk_bulk_get(dev, num, usi->clks);220}221 222static int exynos_usi_probe(struct platform_device *pdev)223{224	struct device *dev = &pdev->dev;225	struct device_node *np = dev->of_node;226	struct exynos_usi *usi;227	int ret;228 229	usi = devm_kzalloc(dev, sizeof(*usi), GFP_KERNEL);230	if (!usi)231		return -ENOMEM;232 233	usi->dev = dev;234	platform_set_drvdata(pdev, usi);235 236	usi->data = of_device_get_match_data(dev);237	if (!usi->data)238		return -EINVAL;239 240	ret = exynos_usi_parse_dt(np, usi);241	if (ret)242		return ret;243 244	ret = exynos_usi_get_clocks(usi);245	if (ret)246		return ret;247 248	if (usi->data->ver == USI_VER2) {249		usi->regs = devm_platform_ioremap_resource(pdev, 0);250		if (IS_ERR(usi->regs))251			return PTR_ERR(usi->regs);252	}253 254	ret = exynos_usi_configure(usi);255	if (ret)256		return ret;257 258	/* Make it possible to embed protocol nodes into USI np */259	return of_platform_populate(np, NULL, NULL, dev);260}261 262static int __maybe_unused exynos_usi_resume_noirq(struct device *dev)263{264	struct exynos_usi *usi = dev_get_drvdata(dev);265 266	return exynos_usi_configure(usi);267}268 269static const struct dev_pm_ops exynos_usi_pm = {270	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, exynos_usi_resume_noirq)271};272 273static struct platform_driver exynos_usi_driver = {274	.driver = {275		.name		= "exynos-usi",276		.pm		= &exynos_usi_pm,277		.of_match_table	= exynos_usi_dt_match,278	},279	.probe = exynos_usi_probe,280};281module_platform_driver(exynos_usi_driver);282 283MODULE_DESCRIPTION("Samsung USI driver");284MODULE_AUTHOR("Sam Protsenko <semen.protsenko@linaro.org>");285MODULE_LICENSE("GPL");286