cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

exynos-usi.c (7164B)


      1// SPDX-License-Identifier: GPL-2.0
      2/*
      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	0x0
     21#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			0x04
     29#define USI_OPTION		0x08
     30
     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 mode
     97 * @usi: USI driver object
     98 * @mode: Mode index
     99 *
    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 block
    126 * @usi: USI driver object
    127 *
    128 * USI IP-core start state is "reset" (on startup and after CPU resume). This
    129 * routine enables the USI block by clearing the reset flag. It also configures
    130 * HWACG behavior (needed e.g. for UART Rx). It should be performed before
    131 * 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");