364 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * Copyright (c) 2013 Broadcom4 * Copyright (C) 2020 Rafał Miłecki <rafal@milecki.pl>5 */6 7#include <dt-bindings/soc/bcm-pmb.h>8#include <linux/io.h>9#include <linux/module.h>10#include <linux/of.h>11#include <linux/platform_device.h>12#include <linux/pm_domain.h>13#include <linux/reset/bcm63xx_pmb.h>14 15#define BPCM_ID_REG 0x0016#define BPCM_CAPABILITIES 0x0417#define BPCM_CAP_NUM_ZONES 0x000000ff18#define BPCM_CAP_SR_REG_BITS 0x0000ff0019#define BPCM_CAP_PLLTYPE 0x0003000020#define BPCM_CAP_UBUS 0x0008000021#define BPCM_CONTROL 0x0822#define BPCM_STATUS 0x0c23#define BPCM_ROSC_CONTROL 0x1024#define BPCM_ROSC_THRESH_H 0x1425#define BPCM_ROSC_THRESHOLD_BCM6838 0x1426#define BPCM_ROSC_THRESH_S 0x1827#define BPCM_ROSC_COUNT_BCM6838 0x1828#define BPCM_ROSC_COUNT 0x1c29#define BPCM_PWD_CONTROL_BCM6838 0x1c30#define BPCM_PWD_CONTROL 0x2031#define BPCM_SR_CONTROL_BCM6838 0x2032#define BPCM_PWD_ACCUM_CONTROL 0x2433#define BPCM_SR_CONTROL 0x2834#define BPCM_GLOBAL_CONTROL 0x2c35#define BPCM_MISC_CONTROL 0x3036#define BPCM_MISC_CONTROL2 0x3437#define BPCM_SGPHY_CNTL 0x3838#define BPCM_SGPHY_STATUS 0x3c39#define BPCM_ZONE0 0x4040#define BPCM_ZONE_CONTROL 0x0041#define BPCM_ZONE_CONTROL_MANUAL_CLK_EN 0x0000000142#define BPCM_ZONE_CONTROL_MANUAL_RESET_CTL 0x0000000243#define BPCM_ZONE_CONTROL_FREQ_SCALE_USED 0x00000004 /* R/O */44#define BPCM_ZONE_CONTROL_DPG_CAPABLE 0x00000008 /* R/O */45#define BPCM_ZONE_CONTROL_MANUAL_MEM_PWR 0x0000003046#define BPCM_ZONE_CONTROL_MANUAL_ISO_CTL 0x0000004047#define BPCM_ZONE_CONTROL_MANUAL_CTL 0x0000008048#define BPCM_ZONE_CONTROL_DPG_CTL_EN 0x0000010049#define BPCM_ZONE_CONTROL_PWR_DN_REQ 0x0000020050#define BPCM_ZONE_CONTROL_PWR_UP_REQ 0x0000040051#define BPCM_ZONE_CONTROL_MEM_PWR_CTL_EN 0x0000080052#define BPCM_ZONE_CONTROL_BLK_RESET_ASSERT 0x0000100053#define BPCM_ZONE_CONTROL_MEM_STBY 0x0000200054#define BPCM_ZONE_CONTROL_RESERVED 0x0007c00055#define BPCM_ZONE_CONTROL_PWR_CNTL_STATE 0x00f8000056#define BPCM_ZONE_CONTROL_FREQ_SCALAR_DYN_SEL 0x01000000 /* R/O */57#define BPCM_ZONE_CONTROL_PWR_OFF_STATE 0x02000000 /* R/O */58#define BPCM_ZONE_CONTROL_PWR_ON_STATE 0x04000000 /* R/O */59#define BPCM_ZONE_CONTROL_PWR_GOOD 0x08000000 /* R/O */60#define BPCM_ZONE_CONTROL_DPG_PWR_STATE 0x10000000 /* R/O */61#define BPCM_ZONE_CONTROL_MEM_PWR_STATE 0x20000000 /* R/O */62#define BPCM_ZONE_CONTROL_ISO_STATE 0x40000000 /* R/O */63#define BPCM_ZONE_CONTROL_RESET_STATE 0x80000000 /* R/O */64#define BPCM_ZONE_CONFIG1 0x0465#define BPCM_ZONE_CONFIG2 0x0866#define BPCM_ZONE_FREQ_SCALAR_CONTROL 0x0c67#define BPCM_ZONE_SIZE 0x1068 69struct bcm_pmb {70 struct device *dev;71 void __iomem *base;72 spinlock_t lock;73 bool little_endian;74 struct genpd_onecell_data genpd_onecell_data;75};76 77struct bcm_pmb_pd_data {78 const char * const name;79 int id;80 u8 bus;81 u8 device;82};83 84struct bcm_pmb_pm_domain {85 struct bcm_pmb *pmb;86 const struct bcm_pmb_pd_data *data;87 struct generic_pm_domain genpd;88};89 90static int bcm_pmb_bpcm_read(struct bcm_pmb *pmb, int bus, u8 device,91 int offset, u32 *val)92{93 void __iomem *base = pmb->base + bus * 0x20;94 unsigned long flags;95 int err;96 97 spin_lock_irqsave(&pmb->lock, flags);98 err = bpcm_rd(base, device, offset, val);99 spin_unlock_irqrestore(&pmb->lock, flags);100 101 if (!err)102 *val = pmb->little_endian ? le32_to_cpu(*val) : be32_to_cpu(*val);103 104 return err;105}106 107static int bcm_pmb_bpcm_write(struct bcm_pmb *pmb, int bus, u8 device,108 int offset, u32 val)109{110 void __iomem *base = pmb->base + bus * 0x20;111 unsigned long flags;112 int err;113 114 val = pmb->little_endian ? cpu_to_le32(val) : cpu_to_be32(val);115 116 spin_lock_irqsave(&pmb->lock, flags);117 err = bpcm_wr(base, device, offset, val);118 spin_unlock_irqrestore(&pmb->lock, flags);119 120 return err;121}122 123static int bcm_pmb_power_off_zone(struct bcm_pmb *pmb, int bus, u8 device,124 int zone)125{126 int offset;127 u32 val;128 int err;129 130 offset = BPCM_ZONE0 + zone * BPCM_ZONE_SIZE + BPCM_ZONE_CONTROL;131 132 err = bcm_pmb_bpcm_read(pmb, bus, device, offset, &val);133 if (err)134 return err;135 136 val |= BPCM_ZONE_CONTROL_PWR_DN_REQ;137 val &= ~BPCM_ZONE_CONTROL_PWR_UP_REQ;138 139 err = bcm_pmb_bpcm_write(pmb, bus, device, offset, val);140 141 return err;142}143 144static int bcm_pmb_power_on_zone(struct bcm_pmb *pmb, int bus, u8 device,145 int zone)146{147 int offset;148 u32 val;149 int err;150 151 offset = BPCM_ZONE0 + zone * BPCM_ZONE_SIZE + BPCM_ZONE_CONTROL;152 153 err = bcm_pmb_bpcm_read(pmb, bus, device, offset, &val);154 if (err)155 return err;156 157 if (!(val & BPCM_ZONE_CONTROL_PWR_ON_STATE)) {158 val &= ~BPCM_ZONE_CONTROL_PWR_DN_REQ;159 val |= BPCM_ZONE_CONTROL_DPG_CTL_EN;160 val |= BPCM_ZONE_CONTROL_PWR_UP_REQ;161 val |= BPCM_ZONE_CONTROL_MEM_PWR_CTL_EN;162 val |= BPCM_ZONE_CONTROL_BLK_RESET_ASSERT;163 164 err = bcm_pmb_bpcm_write(pmb, bus, device, offset, val);165 }166 167 return err;168}169 170static int bcm_pmb_power_off_device(struct bcm_pmb *pmb, int bus, u8 device)171{172 int offset;173 u32 val;174 int err;175 176 /* Entire device can be powered off by powering off the 0th zone */177 offset = BPCM_ZONE0 + BPCM_ZONE_CONTROL;178 179 err = bcm_pmb_bpcm_read(pmb, bus, device, offset, &val);180 if (err)181 return err;182 183 if (!(val & BPCM_ZONE_CONTROL_PWR_OFF_STATE)) {184 val = BPCM_ZONE_CONTROL_PWR_DN_REQ;185 186 err = bcm_pmb_bpcm_write(pmb, bus, device, offset, val);187 }188 189 return err;190}191 192static int bcm_pmb_power_on_device(struct bcm_pmb *pmb, int bus, u8 device)193{194 u32 val;195 int err;196 int i;197 198 err = bcm_pmb_bpcm_read(pmb, bus, device, BPCM_CAPABILITIES, &val);199 if (err)200 return err;201 202 for (i = 0; i < (val & BPCM_CAP_NUM_ZONES); i++) {203 err = bcm_pmb_power_on_zone(pmb, bus, device, i);204 if (err)205 return err;206 }207 208 return err;209}210 211static int bcm_pmb_power_on_sata(struct bcm_pmb *pmb, int bus, u8 device)212{213 int err;214 215 err = bcm_pmb_power_on_zone(pmb, bus, device, 0);216 if (err)217 return err;218 219 /* Does not apply to the BCM963158 */220 err = bcm_pmb_bpcm_write(pmb, bus, device, BPCM_MISC_CONTROL, 0);221 if (err)222 return err;223 224 err = bcm_pmb_bpcm_write(pmb, bus, device, BPCM_SR_CONTROL, 0xffffffff);225 if (err)226 return err;227 228 err = bcm_pmb_bpcm_write(pmb, bus, device, BPCM_SR_CONTROL, 0);229 230 return err;231}232 233static int bcm_pmb_power_on(struct generic_pm_domain *genpd)234{235 struct bcm_pmb_pm_domain *pd = container_of(genpd, struct bcm_pmb_pm_domain, genpd);236 const struct bcm_pmb_pd_data *data = pd->data;237 struct bcm_pmb *pmb = pd->pmb;238 239 switch (data->id) {240 case BCM_PMB_PCIE0:241 case BCM_PMB_PCIE1:242 case BCM_PMB_PCIE2:243 return bcm_pmb_power_on_zone(pmb, data->bus, data->device, 0);244 case BCM_PMB_HOST_USB:245 return bcm_pmb_power_on_device(pmb, data->bus, data->device);246 case BCM_PMB_SATA:247 return bcm_pmb_power_on_sata(pmb, data->bus, data->device);248 default:249 dev_err(pmb->dev, "unsupported device id: %d\n", data->id);250 return -EINVAL;251 }252}253 254static int bcm_pmb_power_off(struct generic_pm_domain *genpd)255{256 struct bcm_pmb_pm_domain *pd = container_of(genpd, struct bcm_pmb_pm_domain, genpd);257 const struct bcm_pmb_pd_data *data = pd->data;258 struct bcm_pmb *pmb = pd->pmb;259 260 switch (data->id) {261 case BCM_PMB_PCIE0:262 case BCM_PMB_PCIE1:263 case BCM_PMB_PCIE2:264 return bcm_pmb_power_off_zone(pmb, data->bus, data->device, 0);265 case BCM_PMB_HOST_USB:266 return bcm_pmb_power_off_device(pmb, data->bus, data->device);267 default:268 dev_err(pmb->dev, "unsupported device id: %d\n", data->id);269 return -EINVAL;270 }271}272 273static int bcm_pmb_probe(struct platform_device *pdev)274{275 struct device *dev = &pdev->dev;276 const struct bcm_pmb_pd_data *table;277 const struct bcm_pmb_pd_data *e;278 struct bcm_pmb *pmb;279 int max_id;280 int err;281 282 pmb = devm_kzalloc(dev, sizeof(*pmb), GFP_KERNEL);283 if (!pmb)284 return -ENOMEM;285 286 pmb->dev = dev;287 288 pmb->base = devm_platform_ioremap_resource(pdev, 0);289 if (IS_ERR(pmb->base))290 return PTR_ERR(pmb->base);291 292 spin_lock_init(&pmb->lock);293 294 pmb->little_endian = !of_device_is_big_endian(dev->of_node);295 296 table = of_device_get_match_data(dev);297 if (!table)298 return -EINVAL;299 300 max_id = 0;301 for (e = table; e->name; e++)302 max_id = max(max_id, e->id);303 304 pmb->genpd_onecell_data.num_domains = max_id + 1;305 pmb->genpd_onecell_data.domains =306 devm_kcalloc(dev, pmb->genpd_onecell_data.num_domains,307 sizeof(struct generic_pm_domain *), GFP_KERNEL);308 if (!pmb->genpd_onecell_data.domains)309 return -ENOMEM;310 311 for (e = table; e->name; e++) {312 struct bcm_pmb_pm_domain *pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);313 314 if (!pd)315 return -ENOMEM;316 317 pd->pmb = pmb;318 pd->data = e;319 pd->genpd.name = e->name;320 pd->genpd.power_on = bcm_pmb_power_on;321 pd->genpd.power_off = bcm_pmb_power_off;322 323 pm_genpd_init(&pd->genpd, NULL, true);324 pmb->genpd_onecell_data.domains[e->id] = &pd->genpd;325 }326 327 err = of_genpd_add_provider_onecell(dev->of_node, &pmb->genpd_onecell_data);328 if (err) {329 dev_err(dev, "failed to add genpd provider: %d\n", err);330 return err;331 }332 333 return 0;334}335 336static const struct bcm_pmb_pd_data bcm_pmb_bcm4908_data[] = {337 { .name = "pcie2", .id = BCM_PMB_PCIE2, .bus = 0, .device = 2, },338 { .name = "pcie0", .id = BCM_PMB_PCIE0, .bus = 1, .device = 14, },339 { .name = "pcie1", .id = BCM_PMB_PCIE1, .bus = 1, .device = 15, },340 { .name = "usb", .id = BCM_PMB_HOST_USB, .bus = 1, .device = 17, },341 { },342};343 344static const struct bcm_pmb_pd_data bcm_pmb_bcm63138_data[] = {345 { .name = "sata", .id = BCM_PMB_SATA, .bus = 0, .device = 3, },346 { },347};348 349static const struct of_device_id bcm_pmb_of_match[] = {350 { .compatible = "brcm,bcm4908-pmb", .data = &bcm_pmb_bcm4908_data, },351 { .compatible = "brcm,bcm63138-pmb", .data = &bcm_pmb_bcm63138_data, },352 { },353};354 355static struct platform_driver bcm_pmb_driver = {356 .driver = {357 .name = "bcm-pmb",358 .of_match_table = bcm_pmb_of_match,359 },360 .probe = bcm_pmb_probe,361};362 363builtin_platform_driver(bcm_pmb_driver);364