|
| 1 | +// SPDX-License-Identifier: GPL-2.0+ |
| 2 | +/* |
| 3 | + * SoC driver for Cirrus EP93xx chips. |
| 4 | + * Copyright (C) 2022 Nikita Shubin <[email protected]> |
| 5 | + * |
| 6 | + * Based on a rewrite of arch/arm/mach-ep93xx/core.c |
| 7 | + * Copyright (C) 2006 Lennert Buytenhek <[email protected]> |
| 8 | + * Copyright (C) 2007 Herbert Valerio Riedel <[email protected]> |
| 9 | + * |
| 10 | + * Thanks go to Michael Burian and Ray Lehtiniemi for their key |
| 11 | + * role in the ep93xx Linux community. |
| 12 | + */ |
| 13 | + |
| 14 | +#include <linux/bits.h> |
| 15 | +#include <linux/cleanup.h> |
| 16 | +#include <linux/init.h> |
| 17 | +#include <linux/mfd/syscon.h> |
| 18 | +#include <linux/of.h> |
| 19 | +#include <linux/of_fdt.h> |
| 20 | +#include <linux/platform_device.h> |
| 21 | +#include <linux/regmap.h> |
| 22 | +#include <linux/slab.h> |
| 23 | +#include <linux/spinlock.h> |
| 24 | +#include <linux/sys_soc.h> |
| 25 | + |
| 26 | +#include <linux/soc/cirrus/ep93xx.h> |
| 27 | + |
| 28 | +#define EP93XX_SYSCON_DEVCFG 0x80 |
| 29 | + |
| 30 | +#define EP93XX_SWLOCK_MAGICK 0xaa |
| 31 | +#define EP93XX_SYSCON_SWLOCK 0xc0 |
| 32 | +#define EP93XX_SYSCON_SYSCFG 0x9c |
| 33 | +#define EP93XX_SYSCON_SYSCFG_REV_MASK GENMASK(31, 28) |
| 34 | +#define EP93XX_SYSCON_SYSCFG_REV_SHIFT 28 |
| 35 | + |
| 36 | +struct ep93xx_map_info { |
| 37 | + spinlock_t lock; |
| 38 | + void __iomem *base; |
| 39 | + struct regmap *map; |
| 40 | +}; |
| 41 | + |
| 42 | +/* |
| 43 | + * EP93xx System Controller software locked register write |
| 44 | + * |
| 45 | + * Logic safeguards are included to condition the control signals for |
| 46 | + * power connection to the matrix to prevent part damage. In addition, a |
| 47 | + * software lock register is included that must be written with 0xAA |
| 48 | + * before each register write to change the values of the four switch |
| 49 | + * matrix control registers. |
| 50 | + */ |
| 51 | +static void ep93xx_regmap_write(struct regmap *map, spinlock_t *lock, |
| 52 | + unsigned int reg, unsigned int val) |
| 53 | +{ |
| 54 | + guard(spinlock_irqsave)(lock); |
| 55 | + |
| 56 | + regmap_write(map, EP93XX_SYSCON_SWLOCK, EP93XX_SWLOCK_MAGICK); |
| 57 | + regmap_write(map, reg, val); |
| 58 | +} |
| 59 | + |
| 60 | +static void ep93xx_regmap_update_bits(struct regmap *map, spinlock_t *lock, |
| 61 | + unsigned int reg, unsigned int mask, |
| 62 | + unsigned int val) |
| 63 | +{ |
| 64 | + guard(spinlock_irqsave)(lock); |
| 65 | + |
| 66 | + regmap_write(map, EP93XX_SYSCON_SWLOCK, EP93XX_SWLOCK_MAGICK); |
| 67 | + /* force write is required to clear swlock if no changes are made */ |
| 68 | + regmap_update_bits_base(map, reg, mask, val, NULL, false, true); |
| 69 | +} |
| 70 | + |
| 71 | +static void ep93xx_unregister_adev(void *_adev) |
| 72 | +{ |
| 73 | + struct auxiliary_device *adev = _adev; |
| 74 | + |
| 75 | + auxiliary_device_delete(adev); |
| 76 | + auxiliary_device_uninit(adev); |
| 77 | +} |
| 78 | + |
| 79 | +static void ep93xx_adev_release(struct device *dev) |
| 80 | +{ |
| 81 | + struct auxiliary_device *adev = to_auxiliary_dev(dev); |
| 82 | + struct ep93xx_regmap_adev *rdev = to_ep93xx_regmap_adev(adev); |
| 83 | + |
| 84 | + kfree(rdev); |
| 85 | +} |
| 86 | + |
| 87 | +static struct auxiliary_device __init *ep93xx_adev_alloc(struct device *parent, |
| 88 | + const char *name, |
| 89 | + struct ep93xx_map_info *info) |
| 90 | +{ |
| 91 | + struct ep93xx_regmap_adev *rdev __free(kfree) = NULL; |
| 92 | + struct auxiliary_device *adev; |
| 93 | + int ret; |
| 94 | + |
| 95 | + rdev = kzalloc(sizeof(*rdev), GFP_KERNEL); |
| 96 | + if (!rdev) |
| 97 | + return ERR_PTR(-ENOMEM); |
| 98 | + |
| 99 | + rdev->map = info->map; |
| 100 | + rdev->base = info->base; |
| 101 | + rdev->lock = &info->lock; |
| 102 | + rdev->write = ep93xx_regmap_write; |
| 103 | + rdev->update_bits = ep93xx_regmap_update_bits; |
| 104 | + |
| 105 | + adev = &rdev->adev; |
| 106 | + adev->name = name; |
| 107 | + adev->dev.parent = parent; |
| 108 | + adev->dev.release = ep93xx_adev_release; |
| 109 | + |
| 110 | + ret = auxiliary_device_init(adev); |
| 111 | + if (ret) |
| 112 | + return ERR_PTR(ret); |
| 113 | + |
| 114 | + return &no_free_ptr(rdev)->adev; |
| 115 | +} |
| 116 | + |
| 117 | +static int __init ep93xx_controller_register(struct device *parent, const char *name, |
| 118 | + struct ep93xx_map_info *info) |
| 119 | +{ |
| 120 | + struct auxiliary_device *adev; |
| 121 | + int ret; |
| 122 | + |
| 123 | + adev = ep93xx_adev_alloc(parent, name, info); |
| 124 | + if (IS_ERR(adev)) |
| 125 | + return PTR_ERR(adev); |
| 126 | + |
| 127 | + ret = auxiliary_device_add(adev); |
| 128 | + if (ret) { |
| 129 | + auxiliary_device_uninit(adev); |
| 130 | + return ret; |
| 131 | + } |
| 132 | + |
| 133 | + return devm_add_action_or_reset(parent, ep93xx_unregister_adev, adev); |
| 134 | +} |
| 135 | + |
| 136 | +static unsigned int __init ep93xx_soc_revision(struct regmap *map) |
| 137 | +{ |
| 138 | + unsigned int val; |
| 139 | + |
| 140 | + regmap_read(map, EP93XX_SYSCON_SYSCFG, &val); |
| 141 | + val &= EP93XX_SYSCON_SYSCFG_REV_MASK; |
| 142 | + val >>= EP93XX_SYSCON_SYSCFG_REV_SHIFT; |
| 143 | + return val; |
| 144 | +} |
| 145 | + |
| 146 | +static const char __init *ep93xx_get_soc_rev(unsigned int rev) |
| 147 | +{ |
| 148 | + switch (rev) { |
| 149 | + case EP93XX_CHIP_REV_D0: |
| 150 | + return "D0"; |
| 151 | + case EP93XX_CHIP_REV_D1: |
| 152 | + return "D1"; |
| 153 | + case EP93XX_CHIP_REV_E0: |
| 154 | + return "E0"; |
| 155 | + case EP93XX_CHIP_REV_E1: |
| 156 | + return "E1"; |
| 157 | + case EP93XX_CHIP_REV_E2: |
| 158 | + return "E2"; |
| 159 | + default: |
| 160 | + return "unknown"; |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +static const char *pinctrl_names[] __initconst = { |
| 165 | + "pinctrl-ep9301", /* EP93XX_9301_SOC */ |
| 166 | + "pinctrl-ep9307", /* EP93XX_9307_SOC */ |
| 167 | + "pinctrl-ep9312", /* EP93XX_9312_SOC */ |
| 168 | +}; |
| 169 | + |
| 170 | +static int __init ep93xx_syscon_probe(struct platform_device *pdev) |
| 171 | +{ |
| 172 | + enum ep93xx_soc_model model; |
| 173 | + struct ep93xx_map_info *map_info; |
| 174 | + struct soc_device_attribute *attrs; |
| 175 | + struct soc_device *soc_dev; |
| 176 | + struct device *dev = &pdev->dev; |
| 177 | + struct regmap *map; |
| 178 | + void __iomem *base; |
| 179 | + unsigned int rev; |
| 180 | + int ret; |
| 181 | + |
| 182 | + model = (enum ep93xx_soc_model)(uintptr_t)device_get_match_data(dev); |
| 183 | + |
| 184 | + map = device_node_to_regmap(dev->of_node); |
| 185 | + if (IS_ERR(map)) |
| 186 | + return PTR_ERR(map); |
| 187 | + |
| 188 | + base = devm_platform_ioremap_resource(pdev, 0); |
| 189 | + if (IS_ERR(base)) |
| 190 | + return PTR_ERR(base); |
| 191 | + |
| 192 | + attrs = devm_kzalloc(dev, sizeof(*attrs), GFP_KERNEL); |
| 193 | + if (!attrs) |
| 194 | + return -ENOMEM; |
| 195 | + |
| 196 | + rev = ep93xx_soc_revision(map); |
| 197 | + |
| 198 | + attrs->machine = of_flat_dt_get_machine_name(); |
| 199 | + attrs->family = "Cirrus Logic EP93xx"; |
| 200 | + attrs->revision = ep93xx_get_soc_rev(rev); |
| 201 | + |
| 202 | + soc_dev = soc_device_register(attrs); |
| 203 | + if (IS_ERR(soc_dev)) |
| 204 | + return PTR_ERR(soc_dev); |
| 205 | + |
| 206 | + map_info = devm_kzalloc(dev, sizeof(*map_info), GFP_KERNEL); |
| 207 | + if (!map_info) |
| 208 | + return -ENOMEM; |
| 209 | + |
| 210 | + spin_lock_init(&map_info->lock); |
| 211 | + map_info->map = map; |
| 212 | + map_info->base = base; |
| 213 | + |
| 214 | + ret = ep93xx_controller_register(dev, pinctrl_names[model], map_info); |
| 215 | + if (ret) |
| 216 | + dev_err(dev, "registering pinctrl controller failed\n"); |
| 217 | + |
| 218 | + /* |
| 219 | + * EP93xx SSP clock rate was doubled in version E2. For more information |
| 220 | + * see section 6 "2x SSP (Synchronous Serial Port) Clock – Revision E2 only": |
| 221 | + * http://www.cirrus.com/en/pubs/appNote/AN273REV4.pdf |
| 222 | + */ |
| 223 | + if (rev == EP93XX_CHIP_REV_E2) |
| 224 | + ret = ep93xx_controller_register(dev, "clk-ep93xx.e2", map_info); |
| 225 | + else |
| 226 | + ret = ep93xx_controller_register(dev, "clk-ep93xx", map_info); |
| 227 | + if (ret) |
| 228 | + dev_err(dev, "registering clock controller failed\n"); |
| 229 | + |
| 230 | + ret = ep93xx_controller_register(dev, "reset-ep93xx", map_info); |
| 231 | + if (ret) |
| 232 | + dev_err(dev, "registering reset controller failed\n"); |
| 233 | + |
| 234 | + return 0; |
| 235 | +} |
| 236 | + |
| 237 | +static const struct of_device_id ep9301_syscon_of_device_ids[] = { |
| 238 | + { .compatible = "cirrus,ep9301-syscon", .data = (void *)EP93XX_9301_SOC }, |
| 239 | + { .compatible = "cirrus,ep9302-syscon", .data = (void *)EP93XX_9301_SOC }, |
| 240 | + { .compatible = "cirrus,ep9307-syscon", .data = (void *)EP93XX_9307_SOC }, |
| 241 | + { .compatible = "cirrus,ep9312-syscon", .data = (void *)EP93XX_9312_SOC }, |
| 242 | + { .compatible = "cirrus,ep9315-syscon", .data = (void *)EP93XX_9312_SOC }, |
| 243 | + { /* sentinel */ } |
| 244 | +}; |
| 245 | + |
| 246 | +static struct platform_driver ep9301_syscon_driver = { |
| 247 | + .driver = { |
| 248 | + .name = "ep9301-syscon", |
| 249 | + .of_match_table = ep9301_syscon_of_device_ids, |
| 250 | + }, |
| 251 | +}; |
| 252 | +builtin_platform_driver_probe(ep9301_syscon_driver, ep93xx_syscon_probe); |
0 commit comments