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