diff options
Diffstat (limited to 'drivers/platform/mips')
-rw-r--r-- | drivers/platform/mips/Kconfig | 33 | ||||
-rw-r--r-- | drivers/platform/mips/Makefile | 3 | ||||
-rw-r--r-- | drivers/platform/mips/cpu_hwmon.c | 170 | ||||
-rw-r--r-- | drivers/platform/mips/rs780e-acpi.c | 169 |
4 files changed, 375 insertions, 0 deletions
diff --git a/drivers/platform/mips/Kconfig b/drivers/platform/mips/Kconfig new file mode 100644 index 000000000..495da331c --- /dev/null +++ b/drivers/platform/mips/Kconfig @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# MIPS Platform Specific Drivers +# + +menuconfig MIPS_PLATFORM_DEVICES + bool "MIPS Platform Specific Device Drivers" + default y + help + Say Y here to get to see options for device drivers of various + MIPS platforms, including vendor-specific netbook/laptop/desktop + extension and hardware monitor drivers. This option itself does + not add any kernel code. + + If you say N, all options in this submenu will be skipped and disabled. + +if MIPS_PLATFORM_DEVICES + +config CPU_HWMON + bool "Loongson-3 CPU HWMon Driver" + depends on MACH_LOONGSON64 + select HWMON + default y + help + Loongson-3A/3B CPU Hwmon (temperature sensor) driver. + +config RS780E_ACPI + bool "Loongson RS780E ACPI Controller" + depends on MACH_LOONGSON64 || COMPILE_TEST + help + Loongson RS780E PCH ACPI Controller driver. + +endif # MIPS_PLATFORM_DEVICES diff --git a/drivers/platform/mips/Makefile b/drivers/platform/mips/Makefile new file mode 100644 index 000000000..178149098 --- /dev/null +++ b/drivers/platform/mips/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_CPU_HWMON) += cpu_hwmon.o +obj-$(CONFIG_RS780E_ACPI) += rs780e-acpi.o diff --git a/drivers/platform/mips/cpu_hwmon.c b/drivers/platform/mips/cpu_hwmon.c new file mode 100644 index 000000000..d8c5f9195 --- /dev/null +++ b/drivers/platform/mips/cpu_hwmon.c @@ -0,0 +1,170 @@ +// SPDX-License-Identifier: GPL-2.0-only +#include <linux/err.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/jiffies.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +#include <loongson.h> +#include <boot_param.h> +#include <loongson_hwmon.h> +#include <loongson_regs.h> + +static int csr_temp_enable; + +/* + * Loongson-3 series cpu has two sensors inside, + * each of them from 0 to 255, + * if more than 127, that is dangerous. + * here only provide sensor1 data, because it always hot than sensor0 + */ +int loongson3_cpu_temp(int cpu) +{ + u32 reg, prid_rev; + + if (csr_temp_enable) { + reg = (csr_readl(LOONGSON_CSR_CPUTEMP) & 0xff); + goto out; + } + + reg = LOONGSON_CHIPTEMP(cpu); + prid_rev = read_c0_prid() & PRID_REV_MASK; + + switch (prid_rev) { + case PRID_REV_LOONGSON3A_R1: + reg = (reg >> 8) & 0xff; + break; + case PRID_REV_LOONGSON3B_R1: + case PRID_REV_LOONGSON3B_R2: + case PRID_REV_LOONGSON3A_R2_0: + case PRID_REV_LOONGSON3A_R2_1: + reg = ((reg >> 8) & 0xff) - 100; + break; + case PRID_REV_LOONGSON3A_R3_0: + case PRID_REV_LOONGSON3A_R3_1: + default: + reg = (reg & 0xffff) * 731 / 0x4000 - 273; + break; + } + +out: + return (int)reg * 1000; +} + +static int nr_packages; +static struct device *cpu_hwmon_dev; + +static ssize_t cpu_temp_label(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + + return sprintf(buf, "CPU %d Temperature\n", id); +} + +static ssize_t get_cpu_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int id = (to_sensor_dev_attr(attr))->index - 1; + int value = loongson3_cpu_temp(id); + + return sprintf(buf, "%d\n", value); +} + +static SENSOR_DEVICE_ATTR(temp1_input, 0444, get_cpu_temp, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_label, 0444, cpu_temp_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, 0444, get_cpu_temp, NULL, 2); +static SENSOR_DEVICE_ATTR(temp2_label, 0444, cpu_temp_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_input, 0444, get_cpu_temp, NULL, 3); +static SENSOR_DEVICE_ATTR(temp3_label, 0444, cpu_temp_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_input, 0444, get_cpu_temp, NULL, 4); +static SENSOR_DEVICE_ATTR(temp4_label, 0444, cpu_temp_label, NULL, 4); + +static struct attribute *cpu_hwmon_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_label.dev_attr.attr, + NULL +}; + +static umode_t cpu_hwmon_is_visible(struct kobject *kobj, + struct attribute *attr, int i) +{ + int id = i / 2; + + if (id < nr_packages) + return attr->mode; + return 0; +} + +static struct attribute_group cpu_hwmon_group = { + .attrs = cpu_hwmon_attributes, + .is_visible = cpu_hwmon_is_visible, +}; + +static const struct attribute_group *cpu_hwmon_groups[] = { + &cpu_hwmon_group, + NULL +}; + +#define CPU_THERMAL_THRESHOLD 90000 +static struct delayed_work thermal_work; + +static void do_thermal_timer(struct work_struct *work) +{ + int i, value; + + for (i = 0; i < nr_packages; i++) { + value = loongson3_cpu_temp(i); + if (value > CPU_THERMAL_THRESHOLD) { + pr_emerg("Power off due to high temp: %d\n", value); + orderly_poweroff(true); + } + } + + schedule_delayed_work(&thermal_work, msecs_to_jiffies(5000)); +} + +static int __init loongson_hwmon_init(void) +{ + pr_info("Loongson Hwmon Enter...\n"); + + if (cpu_has_csr()) + csr_temp_enable = csr_readl(LOONGSON_CSR_FEATURES) & + LOONGSON_CSRF_TEMP; + + nr_packages = loongson_sysconf.nr_cpus / + loongson_sysconf.cores_per_package; + + cpu_hwmon_dev = hwmon_device_register_with_groups(NULL, "cpu_hwmon", + NULL, cpu_hwmon_groups); + if (IS_ERR(cpu_hwmon_dev)) { + pr_err("hwmon_device_register fail!\n"); + return PTR_ERR(cpu_hwmon_dev); + } + + INIT_DEFERRABLE_WORK(&thermal_work, do_thermal_timer); + schedule_delayed_work(&thermal_work, msecs_to_jiffies(20000)); + + return 0; +} + +static void __exit loongson_hwmon_exit(void) +{ + cancel_delayed_work_sync(&thermal_work); + hwmon_device_unregister(cpu_hwmon_dev); +} + +module_init(loongson_hwmon_init); +module_exit(loongson_hwmon_exit); + +MODULE_AUTHOR("Yu Xiang <xiangy@lemote.com>"); +MODULE_AUTHOR("Huacai Chen <chenhc@lemote.com>"); +MODULE_DESCRIPTION("Loongson CPU Hwmon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/mips/rs780e-acpi.c b/drivers/platform/mips/rs780e-acpi.c new file mode 100644 index 000000000..bb0e8ae0e --- /dev/null +++ b/drivers/platform/mips/rs780e-acpi.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/io.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/export.h> +#include <linux/of.h> +#include <linux/platform_device.h> + +static unsigned long acpi_iobase; + +#define ACPI_PM_EVT_BLK (acpi_iobase + 0x00) /* 4 bytes */ +#define ACPI_PM_CNT_BLK (acpi_iobase + 0x04) /* 2 bytes */ +#define ACPI_PMA_CNT_BLK (acpi_iobase + 0x0F) /* 1 byte */ +#define ACPI_PM_TMR_BLK (acpi_iobase + 0x18) /* 4 bytes */ +#define ACPI_GPE0_BLK (acpi_iobase + 0x10) /* 8 bytes */ +#define ACPI_END (acpi_iobase + 0x80) + +#define PM_INDEX 0xCD6 +#define PM_DATA 0xCD7 +#define PM2_INDEX 0xCD0 +#define PM2_DATA 0xCD1 + +static void pmio_write_index(u16 index, u8 reg, u8 value) +{ + outb(reg, index); + outb(value, index + 1); +} + +static u8 pmio_read_index(u16 index, u8 reg) +{ + outb(reg, index); + return inb(index + 1); +} + +void pm_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM_INDEX, reg, value); +} +EXPORT_SYMBOL(pm_iowrite); + +u8 pm_ioread(u8 reg) +{ + return pmio_read_index(PM_INDEX, reg); +} +EXPORT_SYMBOL(pm_ioread); + +void pm2_iowrite(u8 reg, u8 value) +{ + pmio_write_index(PM2_INDEX, reg, value); +} +EXPORT_SYMBOL(pm2_iowrite); + +u8 pm2_ioread(u8 reg) +{ + return pmio_read_index(PM2_INDEX, reg); +} +EXPORT_SYMBOL(pm2_ioread); + +static void acpi_hw_clear_status(void) +{ + u16 value; + + /* PMStatus: Clear WakeStatus/PwrBtnStatus */ + value = inw(ACPI_PM_EVT_BLK); + value |= (1 << 8 | 1 << 15); + outw(value, ACPI_PM_EVT_BLK); + + /* GPEStatus: Clear all generated events */ + outl(inl(ACPI_GPE0_BLK), ACPI_GPE0_BLK); +} + +static void acpi_registers_setup(void) +{ + u32 value; + + /* PM Status Base */ + pm_iowrite(0x20, ACPI_PM_EVT_BLK & 0xff); + pm_iowrite(0x21, ACPI_PM_EVT_BLK >> 8); + + /* PM Control Base */ + pm_iowrite(0x22, ACPI_PM_CNT_BLK & 0xff); + pm_iowrite(0x23, ACPI_PM_CNT_BLK >> 8); + + /* GPM Base */ + pm_iowrite(0x28, ACPI_GPE0_BLK & 0xff); + pm_iowrite(0x29, ACPI_GPE0_BLK >> 8); + + /* ACPI End */ + pm_iowrite(0x2e, ACPI_END & 0xff); + pm_iowrite(0x2f, ACPI_END >> 8); + + /* IO Decode: When AcpiDecodeEnable set, South-Bridge uses the contents + * of the PM registers at index 0x20~0x2B to decode ACPI I/O address. */ + pm_iowrite(0x0e, 1 << 3); + + /* SCI_EN set */ + outw(1, ACPI_PM_CNT_BLK); + + /* Enable to generate SCI */ + pm_iowrite(0x10, pm_ioread(0x10) | 1); + + /* GPM3/GPM9 enable */ + value = inl(ACPI_GPE0_BLK + 4); + outl(value | (1 << 14) | (1 << 22), ACPI_GPE0_BLK + 4); + + /* Set GPM9 as input */ + pm_iowrite(0x8d, pm_ioread(0x8d) & (~(1 << 1))); + + /* Set GPM9 as non-output */ + pm_iowrite(0x94, pm_ioread(0x94) | (1 << 3)); + + /* GPM3 config ACPI trigger SCIOUT */ + pm_iowrite(0x33, pm_ioread(0x33) & (~(3 << 4))); + + /* GPM9 config ACPI trigger SCIOUT */ + pm_iowrite(0x3d, pm_ioread(0x3d) & (~(3 << 2))); + + /* GPM3 config falling edge trigger */ + pm_iowrite(0x37, pm_ioread(0x37) & (~(1 << 6))); + + /* No wait for STPGNT# in ACPI Sx state */ + pm_iowrite(0x7c, pm_ioread(0x7c) | (1 << 6)); + + /* Set GPM3 pull-down enable */ + value = pm2_ioread(0xf6); + value |= ((1 << 7) | (1 << 3)); + pm2_iowrite(0xf6, value); + + /* Set GPM9 pull-down enable */ + value = pm2_ioread(0xf8); + value |= ((1 << 5) | (1 << 1)); + pm2_iowrite(0xf8, value); +} + +static int rs780e_acpi_probe(struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_IO, 0); + if (!res) + return -ENODEV; + + /* SCI interrupt need acpi space, allocate here */ + if (!request_region(res->start, resource_size(res), "acpi")) { + pr_err("RS780E-ACPI: Failed to request IO Region\n"); + return -EBUSY; + } + + acpi_iobase = res->start; + + acpi_registers_setup(); + acpi_hw_clear_status(); + + return 0; +} + +static const struct of_device_id rs780e_acpi_match[] = { + { .compatible = "loongson,rs780e-acpi" }, + {}, +}; + +static struct platform_driver rs780e_acpi_driver = { + .probe = rs780e_acpi_probe, + .driver = { + .name = "RS780E-ACPI", + .of_match_table = rs780e_acpi_match, + }, +}; +builtin_platform_driver(rs780e_acpi_driver); |