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");