mirror of
https://github.com/gnuton/asuswrt-merlin.ng.git
synced 2025-05-19 16:02:36 +02:00
415 lines
18 KiB
C
415 lines
18 KiB
C
/*
|
|
<:copyright-BRCM:2013:DUAL/GPL:standard
|
|
|
|
Copyright (c) 2013 Broadcom
|
|
All Rights Reserved
|
|
|
|
Unless you and Broadcom execute a separate written software license
|
|
agreement governing use of this software, this software is licensed
|
|
to you under the terms of the GNU General Public License version 2
|
|
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
|
|
with the following added to such license:
|
|
|
|
As a special exception, the copyright holders of this software give
|
|
you permission to link this software with independent modules, and
|
|
to copy and distribute the resulting executable under terms of your
|
|
choice, provided that you also meet, for each linked independent
|
|
module, the terms and conditions of the license of that module.
|
|
An independent module is a module which is not derived from this
|
|
software. The special exception does not apply to any modifications
|
|
of the software.
|
|
|
|
Not withstanding the above, under no circumstances may you combine
|
|
this software in any way with any other Broadcom software provided
|
|
under a license other than the GPL, without Broadcom's express prior
|
|
written consent.
|
|
|
|
:>
|
|
*/
|
|
|
|
#include <linux/gfp.h>
|
|
#include <linux/kobject.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/sysfs.h>
|
|
#include "pmc_drv.h"
|
|
#include "BPCM.h"
|
|
|
|
struct bpcm_device {
|
|
unsigned char *name;
|
|
unsigned devno;
|
|
unsigned zones;
|
|
};
|
|
|
|
/* device blocks with their device and zone numbers */
|
|
static const struct bpcm_device bpcm_devs[] = {
|
|
/* name dev zones */
|
|
#if defined(CONFIG_BCM963138) || defined(CONFIG_BCM963148)
|
|
{ "apm", PMB_ADDR_APM, PMB_ZONES_APM },
|
|
{ "switch", PMB_ADDR_SWITCH, PMB_ZONES_SWITCH },
|
|
{ "chip_clkrst", PMB_ADDR_CHIP_CLKRST, PMB_ZONES_CHIP_CLKRST },
|
|
{ "sata", PMB_ADDR_SATA, PMB_ZONES_SATA },
|
|
{ "aip", PMB_ADDR_AIP, PMB_ZONES_AIP },
|
|
{ "dect_ubus", PMB_ADDR_DECT_UBUS, PMB_ZONES_DECT_UBUS },
|
|
{ "sar", PMB_ADDR_SAR, PMB_ZONES_SAR },
|
|
{ "rdp", PMB_ADDR_RDP, PMB_ZONES_RDP },
|
|
{ "memc", PMB_ADDR_MEMC, PMB_ZONES_MEMC },
|
|
{ "periph", PMB_ADDR_PERIPH, PMB_ZONES_PERIPH },
|
|
{ "syspll", PMB_ADDR_SYSPLL, PMB_ZONES_SYSPLL },
|
|
{ "rdppll", PMB_ADDR_RDPPLL, PMB_ZONES_RDPPLL },
|
|
{ "pcie0", PMB_ADDR_PCIE0, PMB_ZONES_PCIE0 },
|
|
{ "pcie1", PMB_ADDR_PCIE1, PMB_ZONES_PCIE1 },
|
|
{ "usb30_2x", PMB_ADDR_USB30_2X, PMB_ZONES_USB30_2X },
|
|
{ "vdsl3_mips", PMB_ADDR_VDSL3_MIPS, PMB_ZONES_VDSL3_MIPS },
|
|
{ "vdsl3_core", PMB_ADDR_VDSL3_CORE, PMB_ZONES_VDSL3_CORE },
|
|
{ "vdsl3_afepll", AFEPLL_PMB_ADDR_VDSL3_CORE, AFEPLL_PMB_ZONES_VDSL3_CORE },
|
|
#elif defined(CONFIG_BCM963381)
|
|
{ "chip_clkrst", PMB_ADDR_CHIP_CLKRST, PMB_ZONES_CHIP_CLKRST },
|
|
{ "memc", PMB_ADDR_MEMC, PMB_ZONES_MEMC },
|
|
{ "mips", PMB_ADDR_MIPS, PMB_ZONES_MIPS },
|
|
{ "pcie0", PMB_ADDR_PCIE0, PMB_ZONES_PCIE0 },
|
|
{ "pcm", PMB_ADDR_PCM, PMB_ZONES_PCM },
|
|
{ "periph", PMB_ADDR_PERIPH, PMB_ZONES_PERIPH },
|
|
{ "sar", PMB_ADDR_SAR, PMB_ZONES_SAR },
|
|
{ "switch", PMB_ADDR_SWITCH, PMB_ZONES_SWITCH },
|
|
{ "syspll", PMB_ADDR_SYSPLL, PMB_ZONES_SYSPLL },
|
|
{ "syspll1", PMB_ADDR_SYSPLL1, PMB_ZONES_SYSPLL1 },
|
|
{ "usb2x", PMB_ADDR_USB2X, PMB_ZONES_USB2X },
|
|
{ "usb30", PMB_ADDR_USB30, PMB_ZONES_USB30 },
|
|
{ "vdsl3_afepll", AFEPLL_PMB_ADDR_VDSL3_CORE, AFEPLL_PMB_ZONES_VDSL3_CORE },
|
|
{ "vdsl3_core", PMB_ADDR_VDSL3_CORE, PMB_ZONES_VDSL3_CORE },
|
|
{ "vdsl3_mips", PMB_ADDR_VDSL3_MIPS, PMB_ZONES_VDSL3_MIPS },
|
|
#elif defined(CONFIG_BCM96838)
|
|
{ "apm", PMB_ADDR_APM, PMB_ZONES_APM },
|
|
{ "fpm", PMB_ADDR_FPM, PMB_ZONES_FPM },
|
|
{ "chip_clkrst", PMB_ADDR_CHIP_CLKRST, PMB_ZONES_CHIP_CLKRST },
|
|
{ "cpu4355_bcm_mips0", PMB_ADDR_CPU4355_BCM_MIPS0, PMB_ZONES_CPU4355_BCM_MIPS0 },
|
|
{ "wan", PMB_ADDR_WAN, PMB_ZONES_WAN },
|
|
{ "rdp", PMB_ADDR_RDP, PMB_ZONES_RDP },
|
|
{ "memc", PMB_ADDR_MEMC, PMB_ZONES_MEMC },
|
|
{ "periph", PMB_ADDR_PERIPH, PMB_ZONES_PERIPH },
|
|
{ "ugb", PMB_ADDR_UGB, PMB_ZONES_UGB },
|
|
{ "unimac_mbdma", PMB_ADDR_UNIMAC_MBDMA, PMB_ZONES_UNIMAC_MBDMA },
|
|
{ "pcie0", PMB_ADDR_PCIE_UBUS_0, PMB_ZONES_PCIE_UBUS_0 },
|
|
{ "pcie1", PMB_ADDR_PCIE_UBUS_1, PMB_ZONES_PCIE_UBUS_1 },
|
|
{ "usb30_2x", PMB_ADDR_USB30_2X, PMB_ZONES_USB30_2X },
|
|
{ "psab", PMB_ADDR_PSAB, PMB_ZONES_PSAB },
|
|
{ "psbc", PMB_ADDR_PSBC, PMB_ZONES_PSBC },
|
|
{ "egphy", PMB_ADDR_EGPHY, PMB_ZONES_EGPHY },
|
|
#endif
|
|
};
|
|
|
|
/* block common register templates */
|
|
static const struct attribute blockreg_attrs[] = {
|
|
#if defined(CONFIG_BCM963138) || defined(CONFIG_BCM963381) || defined(CONFIG_BCM963148)
|
|
{ "id", 0644 }, /* offset = 0x00, actual offset = 0 */
|
|
{ "capabilities", 0644 }, /* offset = 0x04, actual offset = 1 */
|
|
{ "control", 0644 }, /* offset = 0x08, actual offset = 2 */
|
|
{ "status", 0644 }, /* offset = 0x0c, actual offset = 3 */
|
|
{ "rosc_control", 0644 }, /* offset = 0x10, actual offset = 4 */
|
|
{ "rosc_thresh_h", 0644 }, /* offset = 0x14, actual offset = 5 */
|
|
{ "rosc_thresh_s", 0644 }, /* offset = 0x18, actual offset = 6 */
|
|
{ "rosc_count", 0644 }, /* offset = 0x1c, actual offset = 7 */
|
|
{ "pwd_control", 0644 }, /* offset = 0x20, actual offset = 8 */
|
|
{ "pwd_accum_control", 0644 }, /* offset = 0x24, actual offset = 9 */
|
|
{ "sr_control", 0644 }, /* offset = 0x28, actual offset = 10 */
|
|
{ "rgmii_tx_clk_250", 0644 }, /* offset = 0x2c, actual offset = 11 */
|
|
{ "misc_control", 0644 }, /* offset = 0x30, actual offset = 12 */
|
|
{ "rsvd13", 0644 }, /* offset = 0x34, actual offset = 13 */
|
|
{ "rsvd14", 0644 }, /* offset = 0x38, actual offset = 14 */
|
|
{ "rsvd15", 0644 }, /* offset = 0x3c, actual offset = 15 */
|
|
#elif defined(CONFIG_BCM96838)
|
|
{ "id", 0644 }, /* offset = 0x00, actual offset = 0 */
|
|
{ "capabilities", 0644 }, /* offset = 0x04, actual offset = 1 */
|
|
{ "control", 0644 }, /* offset = 0x08, actual offset = 2 */
|
|
{ "status", 0644 }, /* offset = 0x0c, actual offset = 3 */
|
|
{ "rosc_control", 0644 }, /* offset = 0x10, actual offset = 4 */
|
|
{ "rosc_threshold", 0644 }, /* offset = 0x14, actual offset = 5 */
|
|
{ "rosc_count", 0644 }, /* offset = 0x18, actual offset = 7 */
|
|
{ "pwd_control", 0644 }, /* offset = 0x1c, actual offset = 8 */
|
|
#endif
|
|
};
|
|
|
|
/* block common register templates */
|
|
static const struct attribute zonereg_attrs[] = {
|
|
{ "control", 0644 },
|
|
{ "config1", 0644 },
|
|
{ "config2", 0644 },
|
|
{ "freq_scalar_control", 0644 },
|
|
};
|
|
|
|
/* attribute subclass with device and zone */
|
|
struct bpcm_attribute {
|
|
struct attribute attr;
|
|
ssize_t (*show)(struct kobject *kobj, struct kobj_attribute *attr,
|
|
char *buf);
|
|
ssize_t (*store)(struct kobject *kobj, struct kobj_attribute *attr,
|
|
const char *buf, size_t count);
|
|
int devno;
|
|
int zoneno;
|
|
int regno;
|
|
};
|
|
|
|
static ssize_t power_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct bpcm_attribute *pmc = (struct bpcm_attribute *) attr;
|
|
unsigned zone = pmc->zoneno >= 0 ? pmc->zoneno : 0;
|
|
BPCM_PWR_ZONE_N_CONTROL control;
|
|
unsigned ctlreg = 0;
|
|
int rc;
|
|
|
|
rc = ReadZoneRegister(pmc->devno, zone, ctlreg, &control.Reg32);
|
|
if (rc)
|
|
return sprintf(buf, "name %s, dev %d, error %x\n", pmc->attr.name, pmc->devno, rc);
|
|
if (control.Reg32 == 0xdeaddead)
|
|
return sprintf(buf, "%x\n", (unsigned) control.Reg32);
|
|
return sprintf(buf, "%d\n", control.Bits.pwr_on_state);
|
|
}
|
|
|
|
static ssize_t power_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct bpcm_attribute *pmc = (struct bpcm_attribute *) attr;
|
|
unsigned value;
|
|
|
|
if (kstrtouint(buf, 0, &value)) {
|
|
printk("%s: not a number\n", buf);
|
|
return -EEXIST;
|
|
}
|
|
|
|
if (value) {
|
|
if (pmc->zoneno < 0)
|
|
PowerOnDevice(pmc->devno);
|
|
else
|
|
PowerOnZone(pmc->devno, pmc->zoneno);
|
|
} else {
|
|
if (pmc->zoneno < 0)
|
|
PowerOffDevice(pmc->devno, 0); // dev, repower
|
|
else
|
|
PowerOffZone(pmc->devno, pmc->zoneno);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t reset_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct bpcm_attribute *pmc = (struct bpcm_attribute *) attr;
|
|
unsigned zone = pmc->zoneno >= 0 ? pmc->zoneno : 0;
|
|
BPCM_PWR_ZONE_N_CONTROL control;
|
|
unsigned ctlreg = 0;
|
|
int rc;
|
|
|
|
rc = ReadZoneRegister(pmc->devno, zone, ctlreg, &control.Reg32);
|
|
if (rc)
|
|
return sprintf(buf, "name %s, dev %d, error %x\n", pmc->attr.name, pmc->devno, rc);
|
|
if (control.Reg32 == 0xdeaddead)
|
|
return sprintf(buf, "%x\n", (unsigned) control.Reg32);
|
|
return sprintf(buf, "%d\n", control.Bits.reset_state);
|
|
}
|
|
|
|
static ssize_t reset_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct bpcm_attribute *pmc = (struct bpcm_attribute *) attr;
|
|
unsigned value;
|
|
|
|
if (kstrtouint(buf, 0, &value)) {
|
|
printk("%s: not a number\n", buf);
|
|
return -EEXIST;
|
|
}
|
|
if (value) {
|
|
if (pmc->zoneno < 0)
|
|
ResetDevice(pmc->devno);
|
|
else
|
|
ResetZone(pmc->devno, pmc->zoneno);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* node templates */
|
|
static const struct bpcm_attribute pseudo_attrs[] = {
|
|
{ { "power", 0644, }, power_show, power_store },
|
|
{ { "reset", 0644, }, reset_show, reset_store },
|
|
};
|
|
|
|
static ssize_t regs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
|
|
{
|
|
struct bpcm_attribute *pmc = (struct bpcm_attribute *) attr;
|
|
uint32 value;
|
|
int rc;
|
|
|
|
if (pmc->zoneno < 0)
|
|
rc = ReadBPCMRegister(pmc->devno, pmc->regno, &value);
|
|
else
|
|
rc = ReadZoneRegister(pmc->devno, pmc->zoneno, pmc->regno, &value);
|
|
if (rc == 0)
|
|
return sprintf(buf, "%x\n", (unsigned) value);
|
|
else
|
|
return sprintf(buf, "name %s, dev %d, error %x\n", pmc->attr.name, pmc->devno, rc);
|
|
}
|
|
|
|
static ssize_t regs_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct bpcm_attribute *pmc = (struct bpcm_attribute *) attr;
|
|
uint32 value;
|
|
int rc;
|
|
|
|
if (kstrtou32(buf, 0, (u32 *)&value)) {
|
|
printk("%s: not a number\n", buf);
|
|
return -EEXIST;
|
|
}
|
|
|
|
if (pmc->zoneno < 0)
|
|
rc = WriteBPCMRegister(pmc->devno, pmc->regno, value);
|
|
else
|
|
rc = WriteZoneRegister(pmc->devno, pmc->zoneno, pmc->regno, value);
|
|
if (rc)
|
|
return 0;
|
|
return count;
|
|
}
|
|
|
|
static int pmc_sysfs_init_zones(const struct bpcm_device *dev, struct kobject *kobj)
|
|
{
|
|
unsigned z;
|
|
unsigned r;
|
|
int error;
|
|
|
|
for (z = 0; z < dev->zones; z++) {
|
|
unsigned char name[8];
|
|
struct kobject *zoneobj;
|
|
struct bpcm_attribute *pmcattr;
|
|
|
|
/* create subdirectory for each zone */
|
|
snprintf(name, sizeof name, "%s%d", "zone", z);
|
|
zoneobj = kobject_create_and_add(name, kobj);
|
|
if (zoneobj == 0)
|
|
return -ENOENT;
|
|
|
|
/* create zone's power and reset nodes */
|
|
r = ARRAY_SIZE(pseudo_attrs);
|
|
pmcattr = kmalloc(r * sizeof *pmcattr, GFP_KERNEL);
|
|
while (r--) {
|
|
sysfs_attr_init(&pmcattr[r]);
|
|
pmcattr[r] = pseudo_attrs[r];
|
|
pmcattr[r].devno = dev->devno;
|
|
pmcattr[r].zoneno = z;
|
|
pmcattr[r].regno = -1;
|
|
error = sysfs_create_file(zoneobj, (struct attribute *)&pmcattr[r]);
|
|
}
|
|
|
|
/* create some register nodes */
|
|
r = ARRAY_SIZE(zonereg_attrs);
|
|
pmcattr = kmalloc(r * sizeof *pmcattr, GFP_KERNEL);
|
|
while (r--) {
|
|
sysfs_attr_init(&pmcattr[r]);
|
|
pmcattr[r].attr = zonereg_attrs[r];
|
|
pmcattr[r].store = regs_store;
|
|
pmcattr[r].show = regs_show;
|
|
pmcattr[r].devno = dev->devno;
|
|
pmcattr[r].zoneno = z;
|
|
pmcattr[r].regno = r;
|
|
error = sysfs_create_file(zoneobj, (struct attribute *)&pmcattr[r]);
|
|
}
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static int pmc_sysfs_init_block(const struct bpcm_device *dev, struct kobject *kobj)
|
|
{
|
|
struct bpcm_attribute *pmcattr;
|
|
struct kobject *blkobj;
|
|
unsigned r;
|
|
int error;
|
|
|
|
/* create subdirectory for each block */
|
|
blkobj = kobject_create_and_add(dev->name, kobj);
|
|
if (blkobj == 0)
|
|
return -ENOMEM;
|
|
|
|
/* create block's power and reset nodes */
|
|
if (dev->zones) {
|
|
r = ARRAY_SIZE(pseudo_attrs);
|
|
pmcattr = kmalloc(r * sizeof *pmcattr, GFP_KERNEL);
|
|
while (r--) {
|
|
sysfs_attr_init(&pmcattr[r]);
|
|
pmcattr[r] = pseudo_attrs[r];
|
|
pmcattr[r].devno = dev->devno;
|
|
pmcattr[r].zoneno = -1;
|
|
pmcattr[r].regno = -1;
|
|
error = sysfs_create_file(blkobj, (struct attribute *)&pmcattr[r]);
|
|
}
|
|
}
|
|
|
|
/* create some register nodes */
|
|
r = ARRAY_SIZE(blockreg_attrs);
|
|
pmcattr = kmalloc(r * sizeof *pmcattr, GFP_KERNEL);
|
|
while (r--) {
|
|
sysfs_attr_init(&pmcattr[r]);
|
|
pmcattr[r].attr = blockreg_attrs[r];
|
|
pmcattr[r].store = regs_store;
|
|
pmcattr[r].show = regs_show;
|
|
pmcattr[r].devno = dev->devno;
|
|
pmcattr[r].zoneno = -1;
|
|
pmcattr[r].regno = r;
|
|
error = sysfs_create_file(blkobj, (struct attribute *)&pmcattr[r]);
|
|
}
|
|
|
|
return pmc_sysfs_init_zones(dev, blkobj);
|
|
}
|
|
|
|
static ssize_t select0_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", GetSelect0());
|
|
}
|
|
|
|
static ssize_t select3_show(struct kobject *kobj, struct kobj_attribute *kattr, char *buf)
|
|
{
|
|
return sprintf(buf, "%d\n", GetSelect3());
|
|
}
|
|
|
|
static const struct bpcm_attribute root_attrs[] = {
|
|
{ { "select0", 0444, }, select0_show, NULL },
|
|
{ { "select3", 0444, }, select3_show, NULL },
|
|
};
|
|
|
|
#ifdef CONFIG_PM
|
|
extern
|
|
#endif
|
|
struct kobject *power_kobj;
|
|
|
|
static int __init pmc_sysfs_init(void)
|
|
{
|
|
struct bpcm_attribute *pmcattr;
|
|
struct kobject *bpcmkobj;
|
|
unsigned d;
|
|
int error;
|
|
|
|
/* create power object ourselves? */
|
|
if (!power_kobj) {
|
|
power_kobj = kobject_create_and_add("power", NULL);
|
|
if (!power_kobj)
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* create bpcm subdirectory */
|
|
bpcmkobj = kobject_create_and_add("bpcm", power_kobj);
|
|
if (bpcmkobj == 0)
|
|
return -ENOMEM;
|
|
|
|
/* create root nodes */
|
|
d = ARRAY_SIZE(root_attrs);
|
|
pmcattr = kmalloc(d * sizeof *pmcattr, GFP_KERNEL);
|
|
while (d--) {
|
|
sysfs_attr_init(&pmcattr[d]);
|
|
pmcattr[d] = root_attrs[d];
|
|
pmcattr[d].devno = -1;
|
|
pmcattr[d].zoneno = -1;
|
|
pmcattr[d].regno = -1;
|
|
error = sysfs_create_file(bpcmkobj, (struct attribute *)&pmcattr[d]);
|
|
}
|
|
|
|
for (d = 0; d < ARRAY_SIZE(bpcm_devs); d++)
|
|
pmc_sysfs_init_block(&bpcm_devs[d], bpcmkobj);
|
|
|
|
return 0;
|
|
}
|
|
|
|
late_initcall(pmc_sysfs_init);
|