diff options
Diffstat (limited to 'drivers/sbus/char')
-rw-r--r-- | drivers/sbus/char/Kconfig | 77 | ||||
-rw-r--r-- | drivers/sbus/char/Makefile | 19 | ||||
-rw-r--r-- | drivers/sbus/char/bbc_envctrl.c | 601 | ||||
-rw-r--r-- | drivers/sbus/char/bbc_i2c.c | 425 | ||||
-rw-r--r-- | drivers/sbus/char/bbc_i2c.h | 86 | ||||
-rw-r--r-- | drivers/sbus/char/display7seg.c | 270 | ||||
-rw-r--r-- | drivers/sbus/char/envctrl.c | 1135 | ||||
-rw-r--r-- | drivers/sbus/char/flash.c | 216 | ||||
-rw-r--r-- | drivers/sbus/char/max1617.h | 28 | ||||
-rw-r--r-- | drivers/sbus/char/openprom.c | 726 | ||||
-rw-r--r-- | drivers/sbus/char/oradax.c | 990 | ||||
-rw-r--r-- | drivers/sbus/char/uctrl.c | 435 |
12 files changed, 5008 insertions, 0 deletions
diff --git a/drivers/sbus/char/Kconfig b/drivers/sbus/char/Kconfig new file mode 100644 index 000000000..7c0a308e4 --- /dev/null +++ b/drivers/sbus/char/Kconfig @@ -0,0 +1,77 @@ +# SPDX-License-Identifier: GPL-2.0-only + +menu "Misc Linux/SPARC drivers" + +config SUN_OPENPROMIO + tristate "/dev/openprom device support" + help + This driver provides user programs with an interface to the SPARC + PROM device tree. The driver implements a SunOS-compatible + interface and a NetBSD-compatible interface. + + To compile this driver as a module, choose M here: the + module will be called openprom. + + If unsure, say Y. + +config OBP_FLASH + tristate "OBP Flash Device support" + depends on SPARC64 + help + The OpenBoot PROM on Ultra systems is flashable. If you want to be + able to upgrade the OBP firmware, say Y here. + +config TADPOLE_TS102_UCTRL + tristate "Tadpole TS102 Microcontroller support" + help + Say Y here to directly support the TS102 Microcontroller interface + on the Tadpole Sparcbook 3. This device handles power-management + events, and can also notice the attachment/detachment of external + monitors and mice. + +config BBC_I2C + tristate "UltraSPARC-III bootbus i2c controller driver" + depends on PCI && SPARC64 + help + The BBC devices on the UltraSPARC III have two I2C controllers. The + first I2C controller connects mainly to configuration PROMs (NVRAM, + CPU configuration, DIMM types, etc.). The second I2C controller + connects to environmental control devices such as fans and + temperature sensors. The second controller also connects to the + smartcard reader, if present. Say Y to enable support for these. + +config ENVCTRL + tristate "SUNW, envctrl support" + depends on PCI && SPARC64 + help + Kernel support for temperature and fan monitoring on Sun SME + machines. + + To compile this driver as a module, choose M here: the + module will be called envctrl. + +config DISPLAY7SEG + tristate "7-Segment Display support" + depends on PCI && SPARC64 + help + This is the driver for the 7-segment display and LED present on + Sun Microsystems CompactPCI models CP1400 and CP1500. + + To compile this driver as a module, choose M here: the + module will be called display7seg. + + If you do not have a CompactPCI model CP1400 or CP1500, or + another UltraSPARC-IIi-cEngine boardset with a 7-segment display, + you should say N to this option. + +config ORACLE_DAX + tristate "Oracle Data Analytics Accelerator" + depends on SPARC64 + default m + help + Driver for Oracle Data Analytics Accelerator, which is + a coprocessor that performs database operations in hardware. + It is available on M7 and M8 based systems only. + +endmenu + diff --git a/drivers/sbus/char/Makefile b/drivers/sbus/char/Makefile new file mode 100644 index 000000000..44347c918 --- /dev/null +++ b/drivers/sbus/char/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the kernel miscellaneous SPARC device drivers. +# +# Dave Redman Frame Buffer tuning support. +# +# 7 October 2000, Bartlomiej Zolnierkiewicz <bkz@linux-ide.org> +# Rewritten to use lists instead of if-statements. +# + +bbc-objs := bbc_i2c.o bbc_envctrl.o + +obj-$(CONFIG_ENVCTRL) += envctrl.o +obj-$(CONFIG_DISPLAY7SEG) += display7seg.o +obj-$(CONFIG_OBP_FLASH) += flash.o +obj-$(CONFIG_SUN_OPENPROMIO) += openprom.o +obj-$(CONFIG_TADPOLE_TS102_UCTRL) += uctrl.o +obj-$(CONFIG_BBC_I2C) += bbc.o +obj-$(CONFIG_ORACLE_DAX) += oradax.o diff --git a/drivers/sbus/char/bbc_envctrl.c b/drivers/sbus/char/bbc_envctrl.c new file mode 100644 index 000000000..4f2dd21e4 --- /dev/null +++ b/drivers/sbus/char/bbc_envctrl.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0 +/* bbc_envctrl.c: UltraSPARC-III environment control driver. + * + * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) + */ + +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/kmod.h> +#include <linux/reboot.h> +#include <linux/of.h> +#include <linux/slab.h> +#include <linux/of_device.h> +#include <asm/oplib.h> + +#include "bbc_i2c.h" +#include "max1617.h" + +#undef ENVCTRL_TRACE + +/* WARNING: Making changes to this driver is very dangerous. + * If you misprogram the sensor chips they can + * cut the power on you instantly. + */ + +/* Two temperature sensors exist in the SunBLADE-1000 enclosure. + * Both are implemented using max1617 i2c devices. Each max1617 + * monitors 2 temperatures, one for one of the cpu dies and the other + * for the ambient temperature. + * + * The max1617 is capable of being programmed with power-off + * temperature values, one low limit and one high limit. These + * can be controlled independently for the cpu or ambient temperature. + * If a limit is violated, the power is simply shut off. The frequency + * with which the max1617 does temperature sampling can be controlled + * as well. + * + * Three fans exist inside the machine, all three are controlled with + * an i2c digital to analog converter. There is a fan directed at the + * two processor slots, another for the rest of the enclosure, and the + * third is for the power supply. The first two fans may be speed + * controlled by changing the voltage fed to them. The third fan may + * only be completely off or on. The third fan is meant to only be + * disabled/enabled when entering/exiting the lowest power-saving + * mode of the machine. + * + * An environmental control kernel thread periodically monitors all + * temperature sensors. Based upon the samples it will adjust the + * fan speeds to try and keep the system within a certain temperature + * range (the goal being to make the fans as quiet as possible without + * allowing the system to get too hot). + * + * If the temperature begins to rise/fall outside of the acceptable + * operating range, a periodic warning will be sent to the kernel log. + * The fans will be put on full blast to attempt to deal with this + * situation. After exceeding the acceptable operating range by a + * certain threshold, the kernel thread will shut down the system. + * Here, the thread is attempting to shut the machine down cleanly + * before the hardware based power-off event is triggered. + */ + +/* These settings are in Celsius. We use these defaults only + * if we cannot interrogate the cpu-fru SEEPROM. + */ +struct temp_limits { + s8 high_pwroff, high_shutdown, high_warn; + s8 low_warn, low_shutdown, low_pwroff; +}; + +static struct temp_limits cpu_temp_limits[2] = { + { 100, 85, 80, 5, -5, -10 }, + { 100, 85, 80, 5, -5, -10 }, +}; + +static struct temp_limits amb_temp_limits[2] = { + { 65, 55, 40, 5, -5, -10 }, + { 65, 55, 40, 5, -5, -10 }, +}; + +static LIST_HEAD(all_temps); +static LIST_HEAD(all_fans); + +#define CPU_FAN_REG 0xf0 +#define SYS_FAN_REG 0xf2 +#define PSUPPLY_FAN_REG 0xf4 + +#define FAN_SPEED_MIN 0x0c +#define FAN_SPEED_MAX 0x3f + +#define PSUPPLY_FAN_ON 0x1f +#define PSUPPLY_FAN_OFF 0x00 + +static void set_fan_speeds(struct bbc_fan_control *fp) +{ + /* Put temperatures into range so we don't mis-program + * the hardware. + */ + if (fp->cpu_fan_speed < FAN_SPEED_MIN) + fp->cpu_fan_speed = FAN_SPEED_MIN; + if (fp->cpu_fan_speed > FAN_SPEED_MAX) + fp->cpu_fan_speed = FAN_SPEED_MAX; + if (fp->system_fan_speed < FAN_SPEED_MIN) + fp->system_fan_speed = FAN_SPEED_MIN; + if (fp->system_fan_speed > FAN_SPEED_MAX) + fp->system_fan_speed = FAN_SPEED_MAX; +#ifdef ENVCTRL_TRACE + printk("fan%d: Changed fan speed to cpu(%02x) sys(%02x)\n", + fp->index, + fp->cpu_fan_speed, fp->system_fan_speed); +#endif + + bbc_i2c_writeb(fp->client, fp->cpu_fan_speed, CPU_FAN_REG); + bbc_i2c_writeb(fp->client, fp->system_fan_speed, SYS_FAN_REG); + bbc_i2c_writeb(fp->client, + (fp->psupply_fan_on ? + PSUPPLY_FAN_ON : PSUPPLY_FAN_OFF), + PSUPPLY_FAN_REG); +} + +static void get_current_temps(struct bbc_cpu_temperature *tp) +{ + tp->prev_amb_temp = tp->curr_amb_temp; + bbc_i2c_readb(tp->client, + (unsigned char *) &tp->curr_amb_temp, + MAX1617_AMB_TEMP); + tp->prev_cpu_temp = tp->curr_cpu_temp; + bbc_i2c_readb(tp->client, + (unsigned char *) &tp->curr_cpu_temp, + MAX1617_CPU_TEMP); +#ifdef ENVCTRL_TRACE + printk("temp%d: cpu(%d C) amb(%d C)\n", + tp->index, + (int) tp->curr_cpu_temp, (int) tp->curr_amb_temp); +#endif +} + + +static void do_envctrl_shutdown(struct bbc_cpu_temperature *tp) +{ + static int shutting_down = 0; + char *type = "???"; + s8 val = -1; + + if (shutting_down != 0) + return; + + if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown || + tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) { + type = "ambient"; + val = tp->curr_amb_temp; + } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown || + tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) { + type = "CPU"; + val = tp->curr_cpu_temp; + } + + printk(KERN_CRIT "temp%d: Outside of safe %s " + "operating temperature, %d C.\n", + tp->index, type, val); + + printk(KERN_CRIT "kenvctrld: Shutting down the system now.\n"); + + shutting_down = 1; + orderly_poweroff(true); +} + +#define WARN_INTERVAL (30 * HZ) + +static void analyze_ambient_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick) +{ + int ret = 0; + + if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) { + if (tp->curr_amb_temp >= + amb_temp_limits[tp->index].high_warn) { + printk(KERN_WARNING "temp%d: " + "Above safe ambient operating temperature, %d C.\n", + tp->index, (int) tp->curr_amb_temp); + ret = 1; + } else if (tp->curr_amb_temp < + amb_temp_limits[tp->index].low_warn) { + printk(KERN_WARNING "temp%d: " + "Below safe ambient operating temperature, %d C.\n", + tp->index, (int) tp->curr_amb_temp); + ret = 1; + } + if (ret) + *last_warn = jiffies; + } else if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_warn || + tp->curr_amb_temp < amb_temp_limits[tp->index].low_warn) + ret = 1; + + /* Now check the shutdown limits. */ + if (tp->curr_amb_temp >= amb_temp_limits[tp->index].high_shutdown || + tp->curr_amb_temp < amb_temp_limits[tp->index].low_shutdown) { + do_envctrl_shutdown(tp); + ret = 1; + } + + if (ret) { + tp->fan_todo[FAN_AMBIENT] = FAN_FULLBLAST; + } else if ((tick & (8 - 1)) == 0) { + s8 amb_goal_hi = amb_temp_limits[tp->index].high_warn - 10; + s8 amb_goal_lo; + + amb_goal_lo = amb_goal_hi - 3; + + /* We do not try to avoid 'too cold' events. Basically we + * only try to deal with over-heating and fan noise reduction. + */ + if (tp->avg_amb_temp < amb_goal_hi) { + if (tp->avg_amb_temp >= amb_goal_lo) + tp->fan_todo[FAN_AMBIENT] = FAN_SAME; + else + tp->fan_todo[FAN_AMBIENT] = FAN_SLOWER; + } else { + tp->fan_todo[FAN_AMBIENT] = FAN_FASTER; + } + } else { + tp->fan_todo[FAN_AMBIENT] = FAN_SAME; + } +} + +static void analyze_cpu_temp(struct bbc_cpu_temperature *tp, unsigned long *last_warn, int tick) +{ + int ret = 0; + + if (time_after(jiffies, (*last_warn + WARN_INTERVAL))) { + if (tp->curr_cpu_temp >= + cpu_temp_limits[tp->index].high_warn) { + printk(KERN_WARNING "temp%d: " + "Above safe CPU operating temperature, %d C.\n", + tp->index, (int) tp->curr_cpu_temp); + ret = 1; + } else if (tp->curr_cpu_temp < + cpu_temp_limits[tp->index].low_warn) { + printk(KERN_WARNING "temp%d: " + "Below safe CPU operating temperature, %d C.\n", + tp->index, (int) tp->curr_cpu_temp); + ret = 1; + } + if (ret) + *last_warn = jiffies; + } else if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_warn || + tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_warn) + ret = 1; + + /* Now check the shutdown limits. */ + if (tp->curr_cpu_temp >= cpu_temp_limits[tp->index].high_shutdown || + tp->curr_cpu_temp < cpu_temp_limits[tp->index].low_shutdown) { + do_envctrl_shutdown(tp); + ret = 1; + } + + if (ret) { + tp->fan_todo[FAN_CPU] = FAN_FULLBLAST; + } else if ((tick & (8 - 1)) == 0) { + s8 cpu_goal_hi = cpu_temp_limits[tp->index].high_warn - 10; + s8 cpu_goal_lo; + + cpu_goal_lo = cpu_goal_hi - 3; + + /* We do not try to avoid 'too cold' events. Basically we + * only try to deal with over-heating and fan noise reduction. + */ + if (tp->avg_cpu_temp < cpu_goal_hi) { + if (tp->avg_cpu_temp >= cpu_goal_lo) + tp->fan_todo[FAN_CPU] = FAN_SAME; + else + tp->fan_todo[FAN_CPU] = FAN_SLOWER; + } else { + tp->fan_todo[FAN_CPU] = FAN_FASTER; + } + } else { + tp->fan_todo[FAN_CPU] = FAN_SAME; + } +} + +static void analyze_temps(struct bbc_cpu_temperature *tp, unsigned long *last_warn) +{ + tp->avg_amb_temp = (s8)((int)((int)tp->avg_amb_temp + (int)tp->curr_amb_temp) / 2); + tp->avg_cpu_temp = (s8)((int)((int)tp->avg_cpu_temp + (int)tp->curr_cpu_temp) / 2); + + analyze_ambient_temp(tp, last_warn, tp->sample_tick); + analyze_cpu_temp(tp, last_warn, tp->sample_tick); + + tp->sample_tick++; +} + +static enum fan_action prioritize_fan_action(int which_fan) +{ + struct bbc_cpu_temperature *tp; + enum fan_action decision = FAN_STATE_MAX; + + /* Basically, prioritize what the temperature sensors + * recommend we do, and perform that action on all the + * fans. + */ + list_for_each_entry(tp, &all_temps, glob_list) { + if (tp->fan_todo[which_fan] == FAN_FULLBLAST) { + decision = FAN_FULLBLAST; + break; + } + if (tp->fan_todo[which_fan] == FAN_SAME && + decision != FAN_FASTER) + decision = FAN_SAME; + else if (tp->fan_todo[which_fan] == FAN_FASTER) + decision = FAN_FASTER; + else if (decision != FAN_FASTER && + decision != FAN_SAME && + tp->fan_todo[which_fan] == FAN_SLOWER) + decision = FAN_SLOWER; + } + if (decision == FAN_STATE_MAX) + decision = FAN_SAME; + + return decision; +} + +static int maybe_new_ambient_fan_speed(struct bbc_fan_control *fp) +{ + enum fan_action decision = prioritize_fan_action(FAN_AMBIENT); + int ret; + + if (decision == FAN_SAME) + return 0; + + ret = 1; + if (decision == FAN_FULLBLAST) { + if (fp->system_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else + fp->system_fan_speed = FAN_SPEED_MAX; + } else { + if (decision == FAN_FASTER) { + if (fp->system_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else + fp->system_fan_speed += 2; + } else { + int orig_speed = fp->system_fan_speed; + + if (orig_speed <= FAN_SPEED_MIN || + orig_speed <= (fp->cpu_fan_speed - 3)) + ret = 0; + else + fp->system_fan_speed -= 1; + } + } + + return ret; +} + +static int maybe_new_cpu_fan_speed(struct bbc_fan_control *fp) +{ + enum fan_action decision = prioritize_fan_action(FAN_CPU); + int ret; + + if (decision == FAN_SAME) + return 0; + + ret = 1; + if (decision == FAN_FULLBLAST) { + if (fp->cpu_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else + fp->cpu_fan_speed = FAN_SPEED_MAX; + } else { + if (decision == FAN_FASTER) { + if (fp->cpu_fan_speed >= FAN_SPEED_MAX) + ret = 0; + else { + fp->cpu_fan_speed += 2; + if (fp->system_fan_speed < + (fp->cpu_fan_speed - 3)) + fp->system_fan_speed = + fp->cpu_fan_speed - 3; + } + } else { + if (fp->cpu_fan_speed <= FAN_SPEED_MIN) + ret = 0; + else + fp->cpu_fan_speed -= 1; + } + } + + return ret; +} + +static void maybe_new_fan_speeds(struct bbc_fan_control *fp) +{ + int new; + + new = maybe_new_ambient_fan_speed(fp); + new |= maybe_new_cpu_fan_speed(fp); + + if (new) + set_fan_speeds(fp); +} + +static void fans_full_blast(void) +{ + struct bbc_fan_control *fp; + + /* Since we will not be monitoring things anymore, put + * the fans on full blast. + */ + list_for_each_entry(fp, &all_fans, glob_list) { + fp->cpu_fan_speed = FAN_SPEED_MAX; + fp->system_fan_speed = FAN_SPEED_MAX; + fp->psupply_fan_on = 1; + set_fan_speeds(fp); + } +} + +#define POLL_INTERVAL (5 * 1000) +static unsigned long last_warning_jiffies; +static struct task_struct *kenvctrld_task; + +static int kenvctrld(void *__unused) +{ + printk(KERN_INFO "bbc_envctrl: kenvctrld starting...\n"); + last_warning_jiffies = jiffies - WARN_INTERVAL; + for (;;) { + struct bbc_cpu_temperature *tp; + struct bbc_fan_control *fp; + + msleep_interruptible(POLL_INTERVAL); + if (kthread_should_stop()) + break; + + list_for_each_entry(tp, &all_temps, glob_list) { + get_current_temps(tp); + analyze_temps(tp, &last_warning_jiffies); + } + list_for_each_entry(fp, &all_fans, glob_list) + maybe_new_fan_speeds(fp); + } + printk(KERN_INFO "bbc_envctrl: kenvctrld exiting...\n"); + + fans_full_blast(); + + return 0; +} + +static void attach_one_temp(struct bbc_i2c_bus *bp, struct platform_device *op, + int temp_idx) +{ + struct bbc_cpu_temperature *tp; + + tp = kzalloc(sizeof(*tp), GFP_KERNEL); + if (!tp) + return; + + INIT_LIST_HEAD(&tp->bp_list); + INIT_LIST_HEAD(&tp->glob_list); + + tp->client = bbc_i2c_attach(bp, op); + if (!tp->client) { + kfree(tp); + return; + } + + + tp->index = temp_idx; + + list_add(&tp->glob_list, &all_temps); + list_add(&tp->bp_list, &bp->temps); + + /* Tell it to convert once every 5 seconds, clear all cfg + * bits. + */ + bbc_i2c_writeb(tp->client, 0x00, MAX1617_WR_CFG_BYTE); + bbc_i2c_writeb(tp->client, 0x02, MAX1617_WR_CVRATE_BYTE); + + /* Program the hard temperature limits into the chip. */ + bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].high_pwroff, + MAX1617_WR_AMB_HIGHLIM); + bbc_i2c_writeb(tp->client, amb_temp_limits[tp->index].low_pwroff, + MAX1617_WR_AMB_LOWLIM); + bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].high_pwroff, + MAX1617_WR_CPU_HIGHLIM); + bbc_i2c_writeb(tp->client, cpu_temp_limits[tp->index].low_pwroff, + MAX1617_WR_CPU_LOWLIM); + + get_current_temps(tp); + tp->prev_cpu_temp = tp->avg_cpu_temp = tp->curr_cpu_temp; + tp->prev_amb_temp = tp->avg_amb_temp = tp->curr_amb_temp; + + tp->fan_todo[FAN_AMBIENT] = FAN_SAME; + tp->fan_todo[FAN_CPU] = FAN_SAME; +} + +static void attach_one_fan(struct bbc_i2c_bus *bp, struct platform_device *op, + int fan_idx) +{ + struct bbc_fan_control *fp; + + fp = kzalloc(sizeof(*fp), GFP_KERNEL); + if (!fp) + return; + + INIT_LIST_HEAD(&fp->bp_list); + INIT_LIST_HEAD(&fp->glob_list); + + fp->client = bbc_i2c_attach(bp, op); + if (!fp->client) { + kfree(fp); + return; + } + + fp->index = fan_idx; + + list_add(&fp->glob_list, &all_fans); + list_add(&fp->bp_list, &bp->fans); + + /* The i2c device controlling the fans is write-only. + * So the only way to keep track of the current power + * level fed to the fans is via software. Choose half + * power for cpu/system and 'on' fo the powersupply fan + * and set it now. + */ + fp->psupply_fan_on = 1; + fp->cpu_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2; + fp->cpu_fan_speed += FAN_SPEED_MIN; + fp->system_fan_speed = (FAN_SPEED_MAX - FAN_SPEED_MIN) / 2; + fp->system_fan_speed += FAN_SPEED_MIN; + + set_fan_speeds(fp); +} + +static void destroy_one_temp(struct bbc_cpu_temperature *tp) +{ + bbc_i2c_detach(tp->client); + kfree(tp); +} + +static void destroy_all_temps(struct bbc_i2c_bus *bp) +{ + struct bbc_cpu_temperature *tp, *tpos; + + list_for_each_entry_safe(tp, tpos, &bp->temps, bp_list) { + list_del(&tp->bp_list); + list_del(&tp->glob_list); + destroy_one_temp(tp); + } +} + +static void destroy_one_fan(struct bbc_fan_control *fp) +{ + bbc_i2c_detach(fp->client); + kfree(fp); +} + +static void destroy_all_fans(struct bbc_i2c_bus *bp) +{ + struct bbc_fan_control *fp, *fpos; + + list_for_each_entry_safe(fp, fpos, &bp->fans, bp_list) { + list_del(&fp->bp_list); + list_del(&fp->glob_list); + destroy_one_fan(fp); + } +} + +int bbc_envctrl_init(struct bbc_i2c_bus *bp) +{ + struct platform_device *op; + int temp_index = 0; + int fan_index = 0; + int devidx = 0; + + while ((op = bbc_i2c_getdev(bp, devidx++)) != NULL) { + if (of_node_name_eq(op->dev.of_node, "temperature")) + attach_one_temp(bp, op, temp_index++); + if (of_node_name_eq(op->dev.of_node, "fan-control")) + attach_one_fan(bp, op, fan_index++); + } + if (temp_index != 0 && fan_index != 0) { + kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld"); + if (IS_ERR(kenvctrld_task)) { + int err = PTR_ERR(kenvctrld_task); + + kenvctrld_task = NULL; + destroy_all_temps(bp); + destroy_all_fans(bp); + return err; + } + } + + return 0; +} + +void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp) +{ + if (kenvctrld_task) + kthread_stop(kenvctrld_task); + + destroy_all_temps(bp); + destroy_all_fans(bp); +} diff --git a/drivers/sbus/char/bbc_i2c.c b/drivers/sbus/char/bbc_i2c.c new file mode 100644 index 000000000..537e55cd0 --- /dev/null +++ b/drivers/sbus/char/bbc_i2c.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* bbc_i2c.c: I2C low-level driver for BBC device on UltraSPARC-III + * platforms. + * + * Copyright (C) 2001, 2008 David S. Miller (davem@davemloft.net) + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <asm/bbc.h> +#include <asm/io.h> + +#include "bbc_i2c.h" + +/* Convert this driver to use i2c bus layer someday... */ +#define I2C_PCF_PIN 0x80 +#define I2C_PCF_ESO 0x40 +#define I2C_PCF_ES1 0x20 +#define I2C_PCF_ES2 0x10 +#define I2C_PCF_ENI 0x08 +#define I2C_PCF_STA 0x04 +#define I2C_PCF_STO 0x02 +#define I2C_PCF_ACK 0x01 + +#define I2C_PCF_START (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ENI | I2C_PCF_STA | I2C_PCF_ACK) +#define I2C_PCF_STOP (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_STO | I2C_PCF_ACK) +#define I2C_PCF_REPSTART ( I2C_PCF_ESO | I2C_PCF_STA | I2C_PCF_ACK) +#define I2C_PCF_IDLE (I2C_PCF_PIN | I2C_PCF_ESO | I2C_PCF_ACK) + +#define I2C_PCF_INI 0x40 /* 1 if not initialized */ +#define I2C_PCF_STS 0x20 +#define I2C_PCF_BER 0x10 +#define I2C_PCF_AD0 0x08 +#define I2C_PCF_LRB 0x08 +#define I2C_PCF_AAS 0x04 +#define I2C_PCF_LAB 0x02 +#define I2C_PCF_BB 0x01 + +/* The BBC devices have two I2C controllers. The first I2C controller + * connects mainly to configuration proms (NVRAM, cpu configuration, + * dimm types, etc.). Whereas the second I2C controller connects to + * environmental control devices such as fans and temperature sensors. + * The second controller also connects to the smartcard reader, if present. + */ + +static void set_device_claimage(struct bbc_i2c_bus *bp, struct platform_device *op, int val) +{ + int i; + + for (i = 0; i < NUM_CHILDREN; i++) { + if (bp->devs[i].device == op) { + bp->devs[i].client_claimed = val; + return; + } + } +} + +#define claim_device(BP,ECHILD) set_device_claimage(BP,ECHILD,1) +#define release_device(BP,ECHILD) set_device_claimage(BP,ECHILD,0) + +struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *bp, int index) +{ + struct platform_device *op = NULL; + int curidx = 0, i; + + for (i = 0; i < NUM_CHILDREN; i++) { + if (!(op = bp->devs[i].device)) + break; + if (curidx == index) + goto out; + op = NULL; + curidx++; + } + +out: + if (curidx == index) + return op; + return NULL; +} + +struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *op) +{ + struct bbc_i2c_client *client; + const u32 *reg; + + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return NULL; + client->bp = bp; + client->op = op; + + reg = of_get_property(op->dev.of_node, "reg", NULL); + if (!reg) { + kfree(client); + return NULL; + } + + client->bus = reg[0]; + client->address = reg[1]; + + claim_device(bp, op); + + return client; +} + +void bbc_i2c_detach(struct bbc_i2c_client *client) +{ + struct bbc_i2c_bus *bp = client->bp; + struct platform_device *op = client->op; + + release_device(bp, op); + kfree(client); +} + +static int wait_for_pin(struct bbc_i2c_bus *bp, u8 *status) +{ + DECLARE_WAITQUEUE(wait, current); + int limit = 32; + int ret = 1; + + bp->waiting = 1; + add_wait_queue(&bp->wq, &wait); + while (limit-- > 0) { + long val; + + val = wait_event_interruptible_timeout( + bp->wq, + (((*status = readb(bp->i2c_control_regs + 0)) + & I2C_PCF_PIN) == 0), + msecs_to_jiffies(250)); + if (val > 0) { + ret = 0; + break; + } + } + remove_wait_queue(&bp->wq, &wait); + bp->waiting = 0; + + return ret; +} + +int bbc_i2c_writeb(struct bbc_i2c_client *client, unsigned char val, int off) +{ + struct bbc_i2c_bus *bp = client->bp; + int address = client->address; + u8 status; + int ret = -1; + + if (bp->i2c_bussel_reg != NULL) + writeb(client->bus, bp->i2c_bussel_reg); + + writeb(address, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); + if (wait_for_pin(bp, &status)) + goto out; + + writeb(off, bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status) || + (status & I2C_PCF_LRB) != 0) + goto out; + + writeb(val, bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status)) + goto out; + + ret = 0; + +out: + writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); + return ret; +} + +int bbc_i2c_readb(struct bbc_i2c_client *client, unsigned char *byte, int off) +{ + struct bbc_i2c_bus *bp = client->bp; + unsigned char address = client->address, status; + int ret = -1; + + if (bp->i2c_bussel_reg != NULL) + writeb(client->bus, bp->i2c_bussel_reg); + + writeb(address, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); + if (wait_for_pin(bp, &status)) + goto out; + + writeb(off, bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status) || + (status & I2C_PCF_LRB) != 0) + goto out; + + writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); + + address |= 0x1; /* READ */ + + writeb(address, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_START, bp->i2c_control_regs + 0x0); + if (wait_for_pin(bp, &status)) + goto out; + + /* Set PIN back to one so the device sends the first + * byte. + */ + (void) readb(bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status)) + goto out; + + writeb(I2C_PCF_ESO | I2C_PCF_ENI, bp->i2c_control_regs + 0x0); + *byte = readb(bp->i2c_control_regs + 0x1); + if (wait_for_pin(bp, &status)) + goto out; + + ret = 0; + +out: + writeb(I2C_PCF_STOP, bp->i2c_control_regs + 0x0); + (void) readb(bp->i2c_control_regs + 0x1); + + return ret; +} + +int bbc_i2c_write_buf(struct bbc_i2c_client *client, + char *buf, int len, int off) +{ + int ret = 0; + + while (len > 0) { + ret = bbc_i2c_writeb(client, *buf, off); + if (ret < 0) + break; + len--; + buf++; + off++; + } + return ret; +} + +int bbc_i2c_read_buf(struct bbc_i2c_client *client, + char *buf, int len, int off) +{ + int ret = 0; + + while (len > 0) { + ret = bbc_i2c_readb(client, buf, off); + if (ret < 0) + break; + len--; + buf++; + off++; + } + + return ret; +} + +EXPORT_SYMBOL(bbc_i2c_getdev); +EXPORT_SYMBOL(bbc_i2c_attach); +EXPORT_SYMBOL(bbc_i2c_detach); +EXPORT_SYMBOL(bbc_i2c_writeb); +EXPORT_SYMBOL(bbc_i2c_readb); +EXPORT_SYMBOL(bbc_i2c_write_buf); +EXPORT_SYMBOL(bbc_i2c_read_buf); + +static irqreturn_t bbc_i2c_interrupt(int irq, void *dev_id) +{ + struct bbc_i2c_bus *bp = dev_id; + + /* PIN going from set to clear is the only event which + * makes the i2c assert an interrupt. + */ + if (bp->waiting && + !(readb(bp->i2c_control_regs + 0x0) & I2C_PCF_PIN)) + wake_up_interruptible(&bp->wq); + + return IRQ_HANDLED; +} + +static void reset_one_i2c(struct bbc_i2c_bus *bp) +{ + writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); + writeb(bp->own, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); + writeb(bp->clock, bp->i2c_control_regs + 0x1); + writeb(I2C_PCF_IDLE, bp->i2c_control_regs + 0x0); +} + +static struct bbc_i2c_bus * attach_one_i2c(struct platform_device *op, int index) +{ + struct bbc_i2c_bus *bp; + struct device_node *dp; + int entry; + + bp = kzalloc(sizeof(*bp), GFP_KERNEL); + if (!bp) + return NULL; + + INIT_LIST_HEAD(&bp->temps); + INIT_LIST_HEAD(&bp->fans); + + bp->i2c_control_regs = of_ioremap(&op->resource[0], 0, 0x2, "bbc_i2c_regs"); + if (!bp->i2c_control_regs) + goto fail; + + if (op->num_resources == 2) { + bp->i2c_bussel_reg = of_ioremap(&op->resource[1], 0, 0x1, "bbc_i2c_bussel"); + if (!bp->i2c_bussel_reg) + goto fail; + } + + bp->waiting = 0; + init_waitqueue_head(&bp->wq); + if (request_irq(op->archdata.irqs[0], bbc_i2c_interrupt, + IRQF_SHARED, "bbc_i2c", bp)) + goto fail; + + bp->index = index; + bp->op = op; + + spin_lock_init(&bp->lock); + + entry = 0; + for (dp = op->dev.of_node->child; + dp && entry < 8; + dp = dp->sibling, entry++) { + struct platform_device *child_op; + + child_op = of_find_device_by_node(dp); + bp->devs[entry].device = child_op; + bp->devs[entry].client_claimed = 0; + } + + writeb(I2C_PCF_PIN, bp->i2c_control_regs + 0x0); + bp->own = readb(bp->i2c_control_regs + 0x01); + writeb(I2C_PCF_PIN | I2C_PCF_ES1, bp->i2c_control_regs + 0x0); + bp->clock = readb(bp->i2c_control_regs + 0x01); + + printk(KERN_INFO "i2c-%d: Regs at %p, %d devices, own %02x, clock %02x.\n", + bp->index, bp->i2c_control_regs, entry, bp->own, bp->clock); + + reset_one_i2c(bp); + + return bp; + +fail: + if (bp->i2c_bussel_reg) + of_iounmap(&op->resource[1], bp->i2c_bussel_reg, 1); + if (bp->i2c_control_regs) + of_iounmap(&op->resource[0], bp->i2c_control_regs, 2); + kfree(bp); + return NULL; +} + +extern int bbc_envctrl_init(struct bbc_i2c_bus *bp); +extern void bbc_envctrl_cleanup(struct bbc_i2c_bus *bp); + +static int bbc_i2c_probe(struct platform_device *op) +{ + struct bbc_i2c_bus *bp; + int err, index = 0; + + bp = attach_one_i2c(op, index); + if (!bp) + return -EINVAL; + + err = bbc_envctrl_init(bp); + if (err) { + free_irq(op->archdata.irqs[0], bp); + if (bp->i2c_bussel_reg) + of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1); + if (bp->i2c_control_regs) + of_iounmap(&op->resource[1], bp->i2c_control_regs, 2); + kfree(bp); + } else { + dev_set_drvdata(&op->dev, bp); + } + + return err; +} + +static int bbc_i2c_remove(struct platform_device *op) +{ + struct bbc_i2c_bus *bp = dev_get_drvdata(&op->dev); + + bbc_envctrl_cleanup(bp); + + free_irq(op->archdata.irqs[0], bp); + + if (bp->i2c_bussel_reg) + of_iounmap(&op->resource[0], bp->i2c_bussel_reg, 1); + if (bp->i2c_control_regs) + of_iounmap(&op->resource[1], bp->i2c_control_regs, 2); + + kfree(bp); + + return 0; +} + +static const struct of_device_id bbc_i2c_match[] = { + { + .name = "i2c", + .compatible = "SUNW,bbc-i2c", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, bbc_i2c_match); + +static struct platform_driver bbc_i2c_driver = { + .driver = { + .name = "bbc_i2c", + .of_match_table = bbc_i2c_match, + }, + .probe = bbc_i2c_probe, + .remove = bbc_i2c_remove, +}; + +module_platform_driver(bbc_i2c_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/sbus/char/bbc_i2c.h b/drivers/sbus/char/bbc_i2c.h new file mode 100644 index 000000000..c2d066d3f --- /dev/null +++ b/drivers/sbus/char/bbc_i2c.h @@ -0,0 +1,86 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _BBC_I2C_H +#define _BBC_I2C_H + +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/list.h> + +struct bbc_i2c_client { + struct bbc_i2c_bus *bp; + struct platform_device *op; + int bus; + int address; +}; + +enum fan_action { FAN_SLOWER, FAN_SAME, FAN_FASTER, FAN_FULLBLAST, FAN_STATE_MAX }; + +struct bbc_cpu_temperature { + struct list_head bp_list; + struct list_head glob_list; + + struct bbc_i2c_client *client; + int index; + + /* Current readings, and history. */ + s8 curr_cpu_temp; + s8 curr_amb_temp; + s8 prev_cpu_temp; + s8 prev_amb_temp; + s8 avg_cpu_temp; + s8 avg_amb_temp; + + int sample_tick; + + enum fan_action fan_todo[2]; +#define FAN_AMBIENT 0 +#define FAN_CPU 1 +}; + +struct bbc_fan_control { + struct list_head bp_list; + struct list_head glob_list; + + struct bbc_i2c_client *client; + int index; + + int psupply_fan_on; + int cpu_fan_speed; + int system_fan_speed; +}; + +#define NUM_CHILDREN 8 + +struct bbc_i2c_bus { + struct bbc_i2c_bus *next; + int index; + spinlock_t lock; + void __iomem *i2c_bussel_reg; + void __iomem *i2c_control_regs; + unsigned char own, clock; + + wait_queue_head_t wq; + volatile int waiting; + + struct list_head temps; + struct list_head fans; + + struct platform_device *op; + struct { + struct platform_device *device; + int client_claimed; + } devs[NUM_CHILDREN]; +}; + +/* Probing and attachment. */ +extern struct platform_device *bbc_i2c_getdev(struct bbc_i2c_bus *, int); +extern struct bbc_i2c_client *bbc_i2c_attach(struct bbc_i2c_bus *bp, struct platform_device *); +extern void bbc_i2c_detach(struct bbc_i2c_client *); + +/* Register read/write. NOTE: Blocking! */ +extern int bbc_i2c_writeb(struct bbc_i2c_client *, unsigned char val, int off); +extern int bbc_i2c_readb(struct bbc_i2c_client *, unsigned char *byte, int off); +extern int bbc_i2c_write_buf(struct bbc_i2c_client *, char *buf, int len, int off); +extern int bbc_i2c_read_buf(struct bbc_i2c_client *, char *buf, int len, int off); + +#endif /* _BBC_I2C_H */ diff --git a/drivers/sbus/char/display7seg.c b/drivers/sbus/char/display7seg.c new file mode 100644 index 000000000..d93595b39 --- /dev/null +++ b/drivers/sbus/char/display7seg.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* display7seg.c - Driver implementation for the 7-segment display + * present on Sun Microsystems CP1400 and CP1500 + * + * Copyright (c) 2000 Eric Brower (ebrower@usa.net) + */ + +#include <linux/device.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/fs.h> +#include <linux/errno.h> +#include <linux/major.h> +#include <linux/miscdevice.h> +#include <linux/ioport.h> /* request_region */ +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/atomic.h> +#include <linux/uaccess.h> /* put_/get_user */ +#include <asm/io.h> + +#include <asm/display7seg.h> + +#define DRIVER_NAME "d7s" +#define PFX DRIVER_NAME ": " + +static DEFINE_MUTEX(d7s_mutex); +static int sol_compat = 0; /* Solaris compatibility mode */ + +/* Solaris compatibility flag - + * The Solaris implementation omits support for several + * documented driver features (ref Sun doc 806-0180-03). + * By default, this module supports the documented driver + * abilities, rather than the Solaris implementation: + * + * 1) Device ALWAYS reverts to OBP-specified FLIPPED mode + * upon closure of device or module unload. + * 2) Device ioctls D7SIOCRD/D7SIOCWR honor toggling of + * FLIP bit + * + * If you wish the device to operate as under Solaris, + * omitting above features, set this parameter to non-zero. + */ +module_param(sol_compat, int, 0); +MODULE_PARM_DESC(sol_compat, + "Disables documented functionality omitted from Solaris driver"); + +MODULE_AUTHOR("Eric Brower <ebrower@usa.net>"); +MODULE_DESCRIPTION("7-Segment Display driver for Sun Microsystems CP1400/1500"); +MODULE_LICENSE("GPL"); + +struct d7s { + void __iomem *regs; + bool flipped; +}; +struct d7s *d7s_device; + +/* + * Register block address- see header for details + * ----------------------------------------- + * | DP | ALARM | FLIP | 4 | 3 | 2 | 1 | 0 | + * ----------------------------------------- + * + * DP - Toggles decimal point on/off + * ALARM - Toggles "Alarm" LED green/red + * FLIP - Inverts display for upside-down mounted board + * bits 0-4 - 7-segment display contents + */ +static atomic_t d7s_users = ATOMIC_INIT(0); + +static int d7s_open(struct inode *inode, struct file *f) +{ + if (D7S_MINOR != iminor(inode)) + return -ENODEV; + atomic_inc(&d7s_users); + return 0; +} + +static int d7s_release(struct inode *inode, struct file *f) +{ + /* Reset flipped state to OBP default only if + * no other users have the device open and we + * are not operating in solaris-compat mode + */ + if (atomic_dec_and_test(&d7s_users) && !sol_compat) { + struct d7s *p = d7s_device; + u8 regval = 0; + + regval = readb(p->regs); + if (p->flipped) + regval |= D7S_FLIP; + else + regval &= ~D7S_FLIP; + writeb(regval, p->regs); + } + + return 0; +} + +static long d7s_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct d7s *p = d7s_device; + u8 regs = readb(p->regs); + int error = 0; + u8 ireg = 0; + + if (D7S_MINOR != iminor(file_inode(file))) + return -ENODEV; + + mutex_lock(&d7s_mutex); + switch (cmd) { + case D7SIOCWR: + /* assign device register values we mask-out D7S_FLIP + * if in sol_compat mode + */ + if (get_user(ireg, (int __user *) arg)) { + error = -EFAULT; + break; + } + if (sol_compat) { + if (regs & D7S_FLIP) + ireg |= D7S_FLIP; + else + ireg &= ~D7S_FLIP; + } + writeb(ireg, p->regs); + break; + + case D7SIOCRD: + /* retrieve device register values + * NOTE: Solaris implementation returns D7S_FLIP bit + * as toggled by user, even though it does not honor it. + * This driver will not misinform you about the state + * of your hardware while in sol_compat mode + */ + if (put_user(regs, (int __user *) arg)) { + error = -EFAULT; + break; + } + break; + + case D7SIOCTM: + /* toggle device mode-- flip display orientation */ + regs ^= D7S_FLIP; + writeb(regs, p->regs); + break; + } + mutex_unlock(&d7s_mutex); + + return error; +} + +static const struct file_operations d7s_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = d7s_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = d7s_open, + .release = d7s_release, + .llseek = noop_llseek, +}; + +static struct miscdevice d7s_miscdev = { + .minor = D7S_MINOR, + .name = DRIVER_NAME, + .fops = &d7s_fops +}; + +static int d7s_probe(struct platform_device *op) +{ + struct device_node *opts; + int err = -EINVAL; + struct d7s *p; + u8 regs; + + if (d7s_device) + goto out; + + p = devm_kzalloc(&op->dev, sizeof(*p), GFP_KERNEL); + err = -ENOMEM; + if (!p) + goto out; + + p->regs = of_ioremap(&op->resource[0], 0, sizeof(u8), "d7s"); + if (!p->regs) { + printk(KERN_ERR PFX "Cannot map chip registers\n"); + goto out; + } + + err = misc_register(&d7s_miscdev); + if (err) { + printk(KERN_ERR PFX "Unable to acquire miscdevice minor %i\n", + D7S_MINOR); + goto out_iounmap; + } + + /* OBP option "d7s-flipped?" is honored as default for the + * device, and reset default when detached + */ + regs = readb(p->regs); + opts = of_find_node_by_path("/options"); + if (opts && + of_get_property(opts, "d7s-flipped?", NULL)) + p->flipped = true; + + if (p->flipped) + regs |= D7S_FLIP; + else + regs &= ~D7S_FLIP; + + writeb(regs, p->regs); + + printk(KERN_INFO PFX "7-Segment Display%pOF at [%s:0x%llx] %s\n", + op->dev.of_node, + (regs & D7S_FLIP) ? " (FLIPPED)" : "", + op->resource[0].start, + sol_compat ? "in sol_compat mode" : ""); + + dev_set_drvdata(&op->dev, p); + d7s_device = p; + err = 0; + of_node_put(opts); + +out: + return err; + +out_iounmap: + of_iounmap(&op->resource[0], p->regs, sizeof(u8)); + goto out; +} + +static int d7s_remove(struct platform_device *op) +{ + struct d7s *p = dev_get_drvdata(&op->dev); + u8 regs = readb(p->regs); + + /* Honor OBP d7s-flipped? unless operating in solaris-compat mode */ + if (sol_compat) { + if (p->flipped) + regs |= D7S_FLIP; + else + regs &= ~D7S_FLIP; + writeb(regs, p->regs); + } + + misc_deregister(&d7s_miscdev); + of_iounmap(&op->resource[0], p->regs, sizeof(u8)); + + return 0; +} + +static const struct of_device_id d7s_match[] = { + { + .name = "display7seg", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, d7s_match); + +static struct platform_driver d7s_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = d7s_match, + }, + .probe = d7s_probe, + .remove = d7s_remove, +}; + +module_platform_driver(d7s_driver); diff --git a/drivers/sbus/char/envctrl.c b/drivers/sbus/char/envctrl.c new file mode 100644 index 000000000..843e830b5 --- /dev/null +++ b/drivers/sbus/char/envctrl.c @@ -0,0 +1,1135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* envctrl.c: Temperature and Fan monitoring on Machines providing it. + * + * Copyright (C) 1998 Eddie C. Dost (ecd@skynet.be) + * Copyright (C) 2000 Vinh Truong (vinh.truong@eng.sun.com) + * VT - The implementation is to support Sun Microelectronics (SME) platform + * environment monitoring. SME platforms use pcf8584 as the i2c bus + * controller to access pcf8591 (8-bit A/D and D/A converter) and + * pcf8571 (256 x 8-bit static low-voltage RAM with I2C-bus interface). + * At board level, it follows SME Firmware I2C Specification. Reference: + * http://www-eu2.semiconductors.com/pip/PCF8584P + * http://www-eu2.semiconductors.com/pip/PCF8574AP + * http://www-eu2.semiconductors.com/pip/PCF8591P + * + * EB - Added support for CP1500 Global Address and PS/Voltage monitoring. + * Eric Brower <ebrower@usa.net> + * + * DB - Audit every copy_to_user in envctrl_read. + * Daniele Bellucci <bellucda@tiscali.it> + */ + +#include <linux/module.h> +#include <linux/kthread.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/miscdevice.h> +#include <linux/kmod.h> +#include <linux/reboot.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <linux/uaccess.h> +#include <asm/envctrl.h> +#include <asm/io.h> + +#define DRIVER_NAME "envctrl" +#define PFX DRIVER_NAME ": " + +#define PCF8584_ADDRESS 0x55 + +#define CONTROL_PIN 0x80 +#define CONTROL_ES0 0x40 +#define CONTROL_ES1 0x20 +#define CONTROL_ES2 0x10 +#define CONTROL_ENI 0x08 +#define CONTROL_STA 0x04 +#define CONTROL_STO 0x02 +#define CONTROL_ACK 0x01 + +#define STATUS_PIN 0x80 +#define STATUS_STS 0x20 +#define STATUS_BER 0x10 +#define STATUS_LRB 0x08 +#define STATUS_AD0 0x08 +#define STATUS_AAB 0x04 +#define STATUS_LAB 0x02 +#define STATUS_BB 0x01 + +/* + * CLK Mode Register. + */ +#define BUS_CLK_90 0x00 +#define BUS_CLK_45 0x01 +#define BUS_CLK_11 0x02 +#define BUS_CLK_1_5 0x03 + +#define CLK_3 0x00 +#define CLK_4_43 0x10 +#define CLK_6 0x14 +#define CLK_8 0x18 +#define CLK_12 0x1c + +#define OBD_SEND_START 0xc5 /* value to generate I2c_bus START condition */ +#define OBD_SEND_STOP 0xc3 /* value to generate I2c_bus STOP condition */ + +/* Monitor type of i2c child device. + * Firmware definitions. + */ +#define PCF8584_MAX_CHANNELS 8 +#define PCF8584_GLOBALADDR_TYPE 6 /* global address monitor */ +#define PCF8584_FANSTAT_TYPE 3 /* fan status monitor */ +#define PCF8584_VOLTAGE_TYPE 2 /* voltage monitor */ +#define PCF8584_TEMP_TYPE 1 /* temperature monitor*/ + +/* Monitor type of i2c child device. + * Driver definitions. + */ +#define ENVCTRL_NOMON 0 +#define ENVCTRL_CPUTEMP_MON 1 /* cpu temperature monitor */ +#define ENVCTRL_CPUVOLTAGE_MON 2 /* voltage monitor */ +#define ENVCTRL_FANSTAT_MON 3 /* fan status monitor */ +#define ENVCTRL_ETHERTEMP_MON 4 /* ethernet temperature */ + /* monitor */ +#define ENVCTRL_VOLTAGESTAT_MON 5 /* voltage status monitor */ +#define ENVCTRL_MTHRBDTEMP_MON 6 /* motherboard temperature */ +#define ENVCTRL_SCSITEMP_MON 7 /* scsi temperature */ +#define ENVCTRL_GLOBALADDR_MON 8 /* global address */ + +/* Child device type. + * Driver definitions. + */ +#define I2C_ADC 0 /* pcf8591 */ +#define I2C_GPIO 1 /* pcf8571 */ + +/* Data read from child device may need to decode + * through a data table and a scale. + * Translation type as defined by firmware. + */ +#define ENVCTRL_TRANSLATE_NO 0 +#define ENVCTRL_TRANSLATE_PARTIAL 1 +#define ENVCTRL_TRANSLATE_COMBINED 2 +#define ENVCTRL_TRANSLATE_FULL 3 /* table[data] */ +#define ENVCTRL_TRANSLATE_SCALE 4 /* table[data]/scale */ + +/* Driver miscellaneous definitions. */ +#define ENVCTRL_MAX_CPU 4 +#define CHANNEL_DESC_SZ 256 + +/* Mask values for combined GlobalAddress/PowerStatus node */ +#define ENVCTRL_GLOBALADDR_ADDR_MASK 0x1F +#define ENVCTRL_GLOBALADDR_PSTAT_MASK 0x60 + +/* Node 0x70 ignored on CompactPCI CP1400/1500 platforms + * (see envctrl_init_i2c_child) + */ +#define ENVCTRL_CPCI_IGNORED_NODE 0x70 + +#define PCF8584_DATA 0x00 +#define PCF8584_CSR 0x01 + +/* Each child device can be monitored by up to PCF8584_MAX_CHANNELS. + * Property of a port or channel as defined by the firmware. + */ +struct pcf8584_channel { + unsigned char chnl_no; + unsigned char io_direction; + unsigned char type; + unsigned char last; +}; + +/* Each child device may have one or more tables of bytes to help decode + * data. Table property as defined by the firmware. + */ +struct pcf8584_tblprop { + unsigned int type; + unsigned int scale; + unsigned int offset; /* offset from the beginning of the table */ + unsigned int size; +}; + +/* i2c child */ +struct i2c_child_t { + /* Either ADC or GPIO. */ + unsigned char i2ctype; + unsigned long addr; + struct pcf8584_channel chnl_array[PCF8584_MAX_CHANNELS]; + + /* Channel info. */ + unsigned int total_chnls; /* Number of monitor channels. */ + unsigned char fan_mask; /* Byte mask for fan status channels. */ + unsigned char voltage_mask; /* Byte mask for voltage status channels. */ + struct pcf8584_tblprop tblprop_array[PCF8584_MAX_CHANNELS]; + + /* Properties of all monitor channels. */ + unsigned int total_tbls; /* Number of monitor tables. */ + char *tables; /* Pointer to table(s). */ + char chnls_desc[CHANNEL_DESC_SZ]; /* Channel description. */ + char mon_type[PCF8584_MAX_CHANNELS]; +}; + +static void __iomem *i2c; +static struct i2c_child_t i2c_childlist[ENVCTRL_MAX_CPU*2]; +static unsigned char chnls_mask[] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80 }; +static unsigned int warning_temperature = 0; +static unsigned int shutdown_temperature = 0; +static char read_cpu; + +/* Forward declarations. */ +static struct i2c_child_t *envctrl_get_i2c_child(unsigned char); + +/* Function Description: Test the PIN bit (Pending Interrupt Not) + * to test when serial transmission is completed . + * Return : None. + */ +static void envtrl_i2c_test_pin(void) +{ + int limit = 1000000; + + while (--limit > 0) { + if (!(readb(i2c + PCF8584_CSR) & STATUS_PIN)) + break; + udelay(1); + } + + if (limit <= 0) + printk(KERN_INFO PFX "Pin status will not clear.\n"); +} + +/* Function Description: Test busy bit. + * Return : None. + */ +static void envctrl_i2c_test_bb(void) +{ + int limit = 1000000; + + while (--limit > 0) { + /* Busy bit 0 means busy. */ + if (readb(i2c + PCF8584_CSR) & STATUS_BB) + break; + udelay(1); + } + + if (limit <= 0) + printk(KERN_INFO PFX "Busy bit will not clear.\n"); +} + +/* Function Description: Send the address for a read access. + * Return : 0 if not acknowledged, otherwise acknowledged. + */ +static int envctrl_i2c_read_addr(unsigned char addr) +{ + envctrl_i2c_test_bb(); + + /* Load address. */ + writeb(addr + 1, i2c + PCF8584_DATA); + + envctrl_i2c_test_bb(); + + writeb(OBD_SEND_START, i2c + PCF8584_CSR); + + /* Wait for PIN. */ + envtrl_i2c_test_pin(); + + /* CSR 0 means acknowledged. */ + if (!(readb(i2c + PCF8584_CSR) & STATUS_LRB)) { + return readb(i2c + PCF8584_DATA); + } else { + writeb(OBD_SEND_STOP, i2c + PCF8584_CSR); + return 0; + } +} + +/* Function Description: Send the address for write mode. + * Return : None. + */ +static void envctrl_i2c_write_addr(unsigned char addr) +{ + envctrl_i2c_test_bb(); + writeb(addr, i2c + PCF8584_DATA); + + /* Generate Start condition. */ + writeb(OBD_SEND_START, i2c + PCF8584_CSR); +} + +/* Function Description: Read 1 byte of data from addr + * set by envctrl_i2c_read_addr() + * Return : Data from address set by envctrl_i2c_read_addr(). + */ +static unsigned char envctrl_i2c_read_data(void) +{ + envtrl_i2c_test_pin(); + writeb(CONTROL_ES0, i2c + PCF8584_CSR); /* Send neg ack. */ + return readb(i2c + PCF8584_DATA); +} + +/* Function Description: Instruct the device which port to read data from. + * Return : None. + */ +static void envctrl_i2c_write_data(unsigned char port) +{ + envtrl_i2c_test_pin(); + writeb(port, i2c + PCF8584_DATA); +} + +/* Function Description: Generate Stop condition after last byte is sent. + * Return : None. + */ +static void envctrl_i2c_stop(void) +{ + envtrl_i2c_test_pin(); + writeb(OBD_SEND_STOP, i2c + PCF8584_CSR); +} + +/* Function Description: Read adc device. + * Return : Data at address and port. + */ +static unsigned char envctrl_i2c_read_8591(unsigned char addr, unsigned char port) +{ + /* Send address. */ + envctrl_i2c_write_addr(addr); + + /* Setup port to read. */ + envctrl_i2c_write_data(port); + envctrl_i2c_stop(); + + /* Read port. */ + envctrl_i2c_read_addr(addr); + + /* Do a single byte read and send stop. */ + envctrl_i2c_read_data(); + envctrl_i2c_stop(); + + return readb(i2c + PCF8584_DATA); +} + +/* Function Description: Read gpio device. + * Return : Data at address. + */ +static unsigned char envctrl_i2c_read_8574(unsigned char addr) +{ + unsigned char rd; + + envctrl_i2c_read_addr(addr); + + /* Do a single byte read and send stop. */ + rd = envctrl_i2c_read_data(); + envctrl_i2c_stop(); + return rd; +} + +/* Function Description: Decode data read from an adc device using firmware + * table. + * Return: Number of read bytes. Data is stored in bufdata in ascii format. + */ +static int envctrl_i2c_data_translate(unsigned char data, int translate_type, + int scale, char *tbl, char *bufdata) +{ + int len = 0; + + switch (translate_type) { + case ENVCTRL_TRANSLATE_NO: + /* No decode necessary. */ + len = 1; + bufdata[0] = data; + break; + + case ENVCTRL_TRANSLATE_FULL: + /* Decode this way: data = table[data]. */ + len = 1; + bufdata[0] = tbl[data]; + break; + + case ENVCTRL_TRANSLATE_SCALE: + /* Decode this way: data = table[data]/scale */ + sprintf(bufdata,"%d ", (tbl[data] * 10) / (scale)); + len = strlen(bufdata); + bufdata[len - 1] = bufdata[len - 2]; + bufdata[len - 2] = '.'; + break; + + default: + break; + } + + return len; +} + +/* Function Description: Read cpu-related data such as cpu temperature, voltage. + * Return: Number of read bytes. Data is stored in bufdata in ascii format. + */ +static int envctrl_read_cpu_info(int cpu, struct i2c_child_t *pchild, + char mon_type, unsigned char *bufdata) +{ + unsigned char data; + int i; + char *tbl, j = -1; + + /* Find the right monitor type and channel. */ + for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { + if (pchild->mon_type[i] == mon_type) { + if (++j == cpu) { + break; + } + } + } + + if (j != cpu) + return 0; + + /* Read data from address and port. */ + data = envctrl_i2c_read_8591((unsigned char)pchild->addr, + (unsigned char)pchild->chnl_array[i].chnl_no); + + /* Find decoding table. */ + tbl = pchild->tables + pchild->tblprop_array[i].offset; + + return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type, + pchild->tblprop_array[i].scale, + tbl, bufdata); +} + +/* Function Description: Read noncpu-related data such as motherboard + * temperature. + * Return: Number of read bytes. Data is stored in bufdata in ascii format. + */ +static int envctrl_read_noncpu_info(struct i2c_child_t *pchild, + char mon_type, unsigned char *bufdata) +{ + unsigned char data; + int i; + char *tbl = NULL; + + for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { + if (pchild->mon_type[i] == mon_type) + break; + } + + if (i >= PCF8584_MAX_CHANNELS) + return 0; + + /* Read data from address and port. */ + data = envctrl_i2c_read_8591((unsigned char)pchild->addr, + (unsigned char)pchild->chnl_array[i].chnl_no); + + /* Find decoding table. */ + tbl = pchild->tables + pchild->tblprop_array[i].offset; + + return envctrl_i2c_data_translate(data, pchild->tblprop_array[i].type, + pchild->tblprop_array[i].scale, + tbl, bufdata); +} + +/* Function Description: Read fan status. + * Return : Always 1 byte. Status stored in bufdata. + */ +static int envctrl_i2c_fan_status(struct i2c_child_t *pchild, + unsigned char data, + char *bufdata) +{ + unsigned char tmp, ret = 0; + int i, j = 0; + + tmp = data & pchild->fan_mask; + + if (tmp == pchild->fan_mask) { + /* All bits are on. All fans are functioning. */ + ret = ENVCTRL_ALL_FANS_GOOD; + } else if (tmp == 0) { + /* No bits are on. No fans are functioning. */ + ret = ENVCTRL_ALL_FANS_BAD; + } else { + /* Go through all channels, mark 'on' the matched bits. + * Notice that fan_mask may have discontiguous bits but + * return mask are always contiguous. For example if we + * monitor 4 fans at channels 0,1,2,4, the return mask + * should be 00010000 if only fan at channel 4 is working. + */ + for (i = 0; i < PCF8584_MAX_CHANNELS;i++) { + if (pchild->fan_mask & chnls_mask[i]) { + if (!(chnls_mask[i] & tmp)) + ret |= chnls_mask[j]; + + j++; + } + } + } + + bufdata[0] = ret; + return 1; +} + +/* Function Description: Read global addressing line. + * Return : Always 1 byte. Status stored in bufdata. + */ +static int envctrl_i2c_globaladdr(struct i2c_child_t *pchild, + unsigned char data, + char *bufdata) +{ + /* Translatation table is not necessary, as global + * addr is the integer value of the GA# bits. + * + * NOTE: MSB is documented as zero, but I see it as '1' always.... + * + * ----------------------------------------------- + * | 0 | FAL | DEG | GA4 | GA3 | GA2 | GA1 | GA0 | + * ----------------------------------------------- + * GA0 - GA4 integer value of Global Address (backplane slot#) + * DEG 0 = cPCI Power supply output is starting to degrade + * 1 = cPCI Power supply output is OK + * FAL 0 = cPCI Power supply has failed + * 1 = cPCI Power supply output is OK + */ + bufdata[0] = (data & ENVCTRL_GLOBALADDR_ADDR_MASK); + return 1; +} + +/* Function Description: Read standard voltage and power supply status. + * Return : Always 1 byte. Status stored in bufdata. + */ +static unsigned char envctrl_i2c_voltage_status(struct i2c_child_t *pchild, + unsigned char data, + char *bufdata) +{ + unsigned char tmp, ret = 0; + int i, j = 0; + + tmp = data & pchild->voltage_mask; + + /* Two channels are used to monitor voltage and power supply. */ + if (tmp == pchild->voltage_mask) { + /* All bits are on. Voltage and power supply are okay. */ + ret = ENVCTRL_VOLTAGE_POWERSUPPLY_GOOD; + } else if (tmp == 0) { + /* All bits are off. Voltage and power supply are bad */ + ret = ENVCTRL_VOLTAGE_POWERSUPPLY_BAD; + } else { + /* Either voltage or power supply has problem. */ + for (i = 0; i < PCF8584_MAX_CHANNELS; i++) { + if (pchild->voltage_mask & chnls_mask[i]) { + j++; + + /* Break out when there is a mismatch. */ + if (!(chnls_mask[i] & tmp)) + break; + } + } + + /* Make a wish that hardware will always use the + * first channel for voltage and the second for + * power supply. + */ + if (j == 1) + ret = ENVCTRL_VOLTAGE_BAD; + else + ret = ENVCTRL_POWERSUPPLY_BAD; + } + + bufdata[0] = ret; + return 1; +} + +/* Function Description: Read a byte from /dev/envctrl. Mapped to user read(). + * Return: Number of read bytes. 0 for error. + */ +static ssize_t +envctrl_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +{ + struct i2c_child_t *pchild; + unsigned char data[10]; + int ret = 0; + + /* Get the type of read as decided in ioctl() call. + * Find the appropriate i2c child. + * Get the data and put back to the user buffer. + */ + + switch ((int)(long)file->private_data) { + case ENVCTRL_RD_WARNING_TEMPERATURE: + if (warning_temperature == 0) + return 0; + + data[0] = (unsigned char)(warning_temperature); + ret = 1; + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_SHUTDOWN_TEMPERATURE: + if (shutdown_temperature == 0) + return 0; + + data[0] = (unsigned char)(shutdown_temperature); + ret = 1; + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_MTHRBD_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_MTHRBDTEMP_MON))) + return 0; + ret = envctrl_read_noncpu_info(pchild, ENVCTRL_MTHRBDTEMP_MON, data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_CPU_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) + return 0; + ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUTEMP_MON, data); + + /* Reset cpu to the default cpu0. */ + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_CPU_VOLTAGE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_CPUVOLTAGE_MON))) + return 0; + ret = envctrl_read_cpu_info(read_cpu, pchild, ENVCTRL_CPUVOLTAGE_MON, data); + + /* Reset cpu to the default cpu0. */ + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_SCSI_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_SCSITEMP_MON))) + return 0; + ret = envctrl_read_noncpu_info(pchild, ENVCTRL_SCSITEMP_MON, data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_ETHERNET_TEMPERATURE: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_ETHERTEMP_MON))) + return 0; + ret = envctrl_read_noncpu_info(pchild, ENVCTRL_ETHERTEMP_MON, data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_FAN_STATUS: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_FANSTAT_MON))) + return 0; + data[0] = envctrl_i2c_read_8574(pchild->addr); + ret = envctrl_i2c_fan_status(pchild,data[0], data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_GLOBALADDRESS: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON))) + return 0; + data[0] = envctrl_i2c_read_8574(pchild->addr); + ret = envctrl_i2c_globaladdr(pchild, data[0], data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + case ENVCTRL_RD_VOLTAGE_STATUS: + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_VOLTAGESTAT_MON))) + /* If voltage monitor not present, check for CPCI equivalent */ + if (!(pchild = envctrl_get_i2c_child(ENVCTRL_GLOBALADDR_MON))) + return 0; + data[0] = envctrl_i2c_read_8574(pchild->addr); + ret = envctrl_i2c_voltage_status(pchild, data[0], data); + if (copy_to_user(buf, data, ret)) + ret = -EFAULT; + break; + + default: + break; + + } + + return ret; +} + +/* Function Description: Command what to read. Mapped to user ioctl(). + * Return: Gives 0 for implemented commands, -EINVAL otherwise. + */ +static long +envctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + char __user *infobuf; + + switch (cmd) { + case ENVCTRL_RD_WARNING_TEMPERATURE: + case ENVCTRL_RD_SHUTDOWN_TEMPERATURE: + case ENVCTRL_RD_MTHRBD_TEMPERATURE: + case ENVCTRL_RD_FAN_STATUS: + case ENVCTRL_RD_VOLTAGE_STATUS: + case ENVCTRL_RD_ETHERNET_TEMPERATURE: + case ENVCTRL_RD_SCSI_TEMPERATURE: + case ENVCTRL_RD_GLOBALADDRESS: + file->private_data = (void *)(long)cmd; + break; + + case ENVCTRL_RD_CPU_TEMPERATURE: + case ENVCTRL_RD_CPU_VOLTAGE: + /* Check to see if application passes in any cpu number, + * the default is cpu0. + */ + infobuf = (char __user *) arg; + if (infobuf == NULL) { + read_cpu = 0; + }else { + get_user(read_cpu, infobuf); + } + + /* Save the command for use when reading. */ + file->private_data = (void *)(long)cmd; + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* Function Description: open device. Mapped to user open(). + * Return: Always 0. + */ +static int +envctrl_open(struct inode *inode, struct file *file) +{ + file->private_data = NULL; + return 0; +} + +/* Function Description: Open device. Mapped to user close(). + * Return: Always 0. + */ +static int +envctrl_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static const struct file_operations envctrl_fops = { + .owner = THIS_MODULE, + .read = envctrl_read, + .unlocked_ioctl = envctrl_ioctl, + .compat_ioctl = compat_ptr_ioctl, + .open = envctrl_open, + .release = envctrl_release, + .llseek = noop_llseek, +}; + +static struct miscdevice envctrl_dev = { + ENVCTRL_MINOR, + "envctrl", + &envctrl_fops +}; + +/* Function Description: Set monitor type based on firmware description. + * Return: None. + */ +static void envctrl_set_mon(struct i2c_child_t *pchild, + const char *chnl_desc, + int chnl_no) +{ + /* Firmware only has temperature type. It does not distinguish + * different kinds of temperatures. We use channel description + * to disinguish them. + */ + if (!(strcmp(chnl_desc,"temp,cpu")) || + !(strcmp(chnl_desc,"temp,cpu0")) || + !(strcmp(chnl_desc,"temp,cpu1")) || + !(strcmp(chnl_desc,"temp,cpu2")) || + !(strcmp(chnl_desc,"temp,cpu3"))) + pchild->mon_type[chnl_no] = ENVCTRL_CPUTEMP_MON; + + if (!(strcmp(chnl_desc,"vddcore,cpu0")) || + !(strcmp(chnl_desc,"vddcore,cpu1")) || + !(strcmp(chnl_desc,"vddcore,cpu2")) || + !(strcmp(chnl_desc,"vddcore,cpu3"))) + pchild->mon_type[chnl_no] = ENVCTRL_CPUVOLTAGE_MON; + + if (!(strcmp(chnl_desc,"temp,motherboard"))) + pchild->mon_type[chnl_no] = ENVCTRL_MTHRBDTEMP_MON; + + if (!(strcmp(chnl_desc,"temp,scsi"))) + pchild->mon_type[chnl_no] = ENVCTRL_SCSITEMP_MON; + + if (!(strcmp(chnl_desc,"temp,ethernet"))) + pchild->mon_type[chnl_no] = ENVCTRL_ETHERTEMP_MON; +} + +/* Function Description: Initialize monitor channel with channel desc, + * decoding tables, monitor type, optional properties. + * Return: None. + */ +static void envctrl_init_adc(struct i2c_child_t *pchild, struct device_node *dp) +{ + int i = 0, len; + const char *pos; + const unsigned int *pval; + + /* Firmware describe channels into a stream separated by a '\0'. */ + pos = of_get_property(dp, "channels-description", &len); + + while (len > 0) { + int l = strlen(pos) + 1; + envctrl_set_mon(pchild, pos, i++); + len -= l; + pos += l; + } + + /* Get optional properties. */ + pval = of_get_property(dp, "warning-temp", NULL); + if (pval) + warning_temperature = *pval; + + pval = of_get_property(dp, "shutdown-temp", NULL); + if (pval) + shutdown_temperature = *pval; +} + +/* Function Description: Initialize child device monitoring fan status. + * Return: None. + */ +static void envctrl_init_fanstat(struct i2c_child_t *pchild) +{ + int i; + + /* Go through all channels and set up the mask. */ + for (i = 0; i < pchild->total_chnls; i++) + pchild->fan_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no]; + + /* We only need to know if this child has fan status monitored. + * We don't care which channels since we have the mask already. + */ + pchild->mon_type[0] = ENVCTRL_FANSTAT_MON; +} + +/* Function Description: Initialize child device for global addressing line. + * Return: None. + */ +static void envctrl_init_globaladdr(struct i2c_child_t *pchild) +{ + int i; + + /* Voltage/PowerSupply monitoring is piggybacked + * with Global Address on CompactPCI. See comments + * within envctrl_i2c_globaladdr for bit assignments. + * + * The mask is created here by assigning mask bits to each + * bit position that represents PCF8584_VOLTAGE_TYPE data. + * Channel numbers are not consecutive within the globaladdr + * node (why?), so we use the actual counter value as chnls_mask + * index instead of the chnl_array[x].chnl_no value. + * + * NOTE: This loop could be replaced with a constant representing + * a mask of bits 5&6 (ENVCTRL_GLOBALADDR_PSTAT_MASK). + */ + for (i = 0; i < pchild->total_chnls; i++) { + if (PCF8584_VOLTAGE_TYPE == pchild->chnl_array[i].type) { + pchild->voltage_mask |= chnls_mask[i]; + } + } + + /* We only need to know if this child has global addressing + * line monitored. We don't care which channels since we know + * the mask already (ENVCTRL_GLOBALADDR_ADDR_MASK). + */ + pchild->mon_type[0] = ENVCTRL_GLOBALADDR_MON; +} + +/* Initialize child device monitoring voltage status. */ +static void envctrl_init_voltage_status(struct i2c_child_t *pchild) +{ + int i; + + /* Go through all channels and set up the mask. */ + for (i = 0; i < pchild->total_chnls; i++) + pchild->voltage_mask |= chnls_mask[(pchild->chnl_array[i]).chnl_no]; + + /* We only need to know if this child has voltage status monitored. + * We don't care which channels since we have the mask already. + */ + pchild->mon_type[0] = ENVCTRL_VOLTAGESTAT_MON; +} + +/* Function Description: Initialize i2c child device. + * Return: None. + */ +static void envctrl_init_i2c_child(struct device_node *dp, + struct i2c_child_t *pchild) +{ + int len, i, tbls_size = 0; + const void *pval; + + /* Get device address. */ + pval = of_get_property(dp, "reg", &len); + memcpy(&pchild->addr, pval, len); + + /* Get tables property. Read firmware temperature tables. */ + pval = of_get_property(dp, "translation", &len); + if (pval && len > 0) { + memcpy(pchild->tblprop_array, pval, len); + pchild->total_tbls = len / sizeof(struct pcf8584_tblprop); + for (i = 0; i < pchild->total_tbls; i++) { + if ((pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset) > tbls_size) { + tbls_size = pchild->tblprop_array[i].size + pchild->tblprop_array[i].offset; + } + } + + pchild->tables = kmalloc(tbls_size, GFP_KERNEL); + if (pchild->tables == NULL){ + printk(KERN_ERR PFX "Failed to allocate table.\n"); + return; + } + pval = of_get_property(dp, "tables", &len); + if (!pval || len <= 0) { + printk(KERN_ERR PFX "Failed to get table.\n"); + return; + } + memcpy(pchild->tables, pval, len); + } + + /* SPARCengine ASM Reference Manual (ref. SMI doc 805-7581-04) + * sections 2.5, 3.5, 4.5 state node 0x70 for CP1400/1500 is + * "For Factory Use Only." + * + * We ignore the node on these platforms by assigning the + * 'NULL' monitor type. + */ + if (ENVCTRL_CPCI_IGNORED_NODE == pchild->addr) { + struct device_node *root_node; + int len; + + root_node = of_find_node_by_path("/"); + if (of_node_name_eq(root_node, "SUNW,UltraSPARC-IIi-cEngine")) { + for (len = 0; len < PCF8584_MAX_CHANNELS; ++len) { + pchild->mon_type[len] = ENVCTRL_NOMON; + } + of_node_put(root_node); + return; + } + of_node_put(root_node); + } + + /* Get the monitor channels. */ + pval = of_get_property(dp, "channels-in-use", &len); + memcpy(pchild->chnl_array, pval, len); + pchild->total_chnls = len / sizeof(struct pcf8584_channel); + + for (i = 0; i < pchild->total_chnls; i++) { + switch (pchild->chnl_array[i].type) { + case PCF8584_TEMP_TYPE: + envctrl_init_adc(pchild, dp); + break; + + case PCF8584_GLOBALADDR_TYPE: + envctrl_init_globaladdr(pchild); + i = pchild->total_chnls; + break; + + case PCF8584_FANSTAT_TYPE: + envctrl_init_fanstat(pchild); + i = pchild->total_chnls; + break; + + case PCF8584_VOLTAGE_TYPE: + if (pchild->i2ctype == I2C_ADC) { + envctrl_init_adc(pchild,dp); + } else { + envctrl_init_voltage_status(pchild); + } + i = pchild->total_chnls; + break; + + default: + break; + } + } +} + +/* Function Description: Search the child device list for a device. + * Return : The i2c child if found. NULL otherwise. + */ +static struct i2c_child_t *envctrl_get_i2c_child(unsigned char mon_type) +{ + int i, j; + + for (i = 0; i < ENVCTRL_MAX_CPU*2; i++) { + for (j = 0; j < PCF8584_MAX_CHANNELS; j++) { + if (i2c_childlist[i].mon_type[j] == mon_type) { + return (struct i2c_child_t *)(&(i2c_childlist[i])); + } + } + } + return NULL; +} + +static void envctrl_do_shutdown(void) +{ + static int inprog = 0; + + if (inprog != 0) + return; + + inprog = 1; + printk(KERN_CRIT "kenvctrld: WARNING: Shutting down the system now.\n"); + orderly_poweroff(true); +} + +static struct task_struct *kenvctrld_task; + +static int kenvctrld(void *__unused) +{ + int poll_interval; + int whichcpu; + char tempbuf[10]; + struct i2c_child_t *cputemp; + + if (NULL == (cputemp = envctrl_get_i2c_child(ENVCTRL_CPUTEMP_MON))) { + printk(KERN_ERR PFX + "kenvctrld unable to monitor CPU temp-- exiting\n"); + return -ENODEV; + } + + poll_interval = 5000; /* TODO env_mon_interval */ + + printk(KERN_INFO PFX "%s starting...\n", current->comm); + for (;;) { + msleep_interruptible(poll_interval); + + if (kthread_should_stop()) + break; + + for (whichcpu = 0; whichcpu < ENVCTRL_MAX_CPU; ++whichcpu) { + if (0 < envctrl_read_cpu_info(whichcpu, cputemp, + ENVCTRL_CPUTEMP_MON, + tempbuf)) { + if (tempbuf[0] >= shutdown_temperature) { + printk(KERN_CRIT + "%s: WARNING: CPU%i temperature %i C meets or exceeds "\ + "shutdown threshold %i C\n", + current->comm, whichcpu, + tempbuf[0], shutdown_temperature); + envctrl_do_shutdown(); + } + } + } + } + printk(KERN_INFO PFX "%s exiting...\n", current->comm); + return 0; +} + +static int envctrl_probe(struct platform_device *op) +{ + struct device_node *dp; + int index, err; + + if (i2c) + return -EINVAL; + + i2c = of_ioremap(&op->resource[0], 0, 0x2, DRIVER_NAME); + if (!i2c) + return -ENOMEM; + + index = 0; + dp = op->dev.of_node->child; + while (dp) { + if (of_node_name_eq(dp, "gpio")) { + i2c_childlist[index].i2ctype = I2C_GPIO; + envctrl_init_i2c_child(dp, &(i2c_childlist[index++])); + } else if (of_node_name_eq(dp, "adc")) { + i2c_childlist[index].i2ctype = I2C_ADC; + envctrl_init_i2c_child(dp, &(i2c_childlist[index++])); + } + + dp = dp->sibling; + } + + /* Set device address. */ + writeb(CONTROL_PIN, i2c + PCF8584_CSR); + writeb(PCF8584_ADDRESS, i2c + PCF8584_DATA); + + /* Set system clock and SCL frequencies. */ + writeb(CONTROL_PIN | CONTROL_ES1, i2c + PCF8584_CSR); + writeb(CLK_4_43 | BUS_CLK_90, i2c + PCF8584_DATA); + + /* Enable serial interface. */ + writeb(CONTROL_PIN | CONTROL_ES0 | CONTROL_ACK, i2c + PCF8584_CSR); + udelay(200); + + /* Register the device as a minor miscellaneous device. */ + err = misc_register(&envctrl_dev); + if (err) { + printk(KERN_ERR PFX "Unable to get misc minor %d\n", + envctrl_dev.minor); + goto out_iounmap; + } + + /* Note above traversal routine post-incremented 'i' to accommodate + * a next child device, so we decrement before reverse-traversal of + * child devices. + */ + printk(KERN_INFO PFX "Initialized "); + for (--index; index >= 0; --index) { + printk("[%s 0x%lx]%s", + (I2C_ADC == i2c_childlist[index].i2ctype) ? "adc" : + ((I2C_GPIO == i2c_childlist[index].i2ctype) ? "gpio" : "unknown"), + i2c_childlist[index].addr, (0 == index) ? "\n" : " "); + } + + kenvctrld_task = kthread_run(kenvctrld, NULL, "kenvctrld"); + if (IS_ERR(kenvctrld_task)) { + err = PTR_ERR(kenvctrld_task); + goto out_deregister; + } + + return 0; + +out_deregister: + misc_deregister(&envctrl_dev); +out_iounmap: + of_iounmap(&op->resource[0], i2c, 0x2); + for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++) + kfree(i2c_childlist[index].tables); + + return err; +} + +static int envctrl_remove(struct platform_device *op) +{ + int index; + + kthread_stop(kenvctrld_task); + + of_iounmap(&op->resource[0], i2c, 0x2); + misc_deregister(&envctrl_dev); + + for (index = 0; index < ENVCTRL_MAX_CPU * 2; index++) + kfree(i2c_childlist[index].tables); + + return 0; +} + +static const struct of_device_id envctrl_match[] = { + { + .name = "i2c", + .compatible = "i2cpcf,8584", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, envctrl_match); + +static struct platform_driver envctrl_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = envctrl_match, + }, + .probe = envctrl_probe, + .remove = envctrl_remove, +}; + +module_platform_driver(envctrl_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/sbus/char/flash.c b/drivers/sbus/char/flash.c new file mode 100644 index 000000000..3adfef210 --- /dev/null +++ b/drivers/sbus/char/flash.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* flash.c: Allow mmap access to the OBP Flash, for OBP updates. + * + * Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be) + */ + +#include <linux/module.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/fcntl.h> +#include <linux/poll.h> +#include <linux/mutex.h> +#include <linux/spinlock.h> +#include <linux/mm.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <linux/uaccess.h> +#include <asm/io.h> +#include <asm/upa.h> + +static DEFINE_MUTEX(flash_mutex); +static DEFINE_SPINLOCK(flash_lock); +static struct { + unsigned long read_base; /* Physical read address */ + unsigned long write_base; /* Physical write address */ + unsigned long read_size; /* Size of read area */ + unsigned long write_size; /* Size of write area */ + unsigned long busy; /* In use? */ +} flash; + +static int +flash_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned long addr; + unsigned long size; + + spin_lock(&flash_lock); + if (flash.read_base == flash.write_base) { + addr = flash.read_base; + size = flash.read_size; + } else { + if ((vma->vm_flags & VM_READ) && + (vma->vm_flags & VM_WRITE)) { + spin_unlock(&flash_lock); + return -EINVAL; + } + if (vma->vm_flags & VM_READ) { + addr = flash.read_base; + size = flash.read_size; + } else if (vma->vm_flags & VM_WRITE) { + addr = flash.write_base; + size = flash.write_size; + } else { + spin_unlock(&flash_lock); + return -ENXIO; + } + } + spin_unlock(&flash_lock); + + if ((vma->vm_pgoff << PAGE_SHIFT) > size) + return -ENXIO; + addr = vma->vm_pgoff + (addr >> PAGE_SHIFT); + + if (vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)) > size) + size = vma->vm_end - (vma->vm_start + (vma->vm_pgoff << PAGE_SHIFT)); + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (io_remap_pfn_range(vma, vma->vm_start, addr, size, vma->vm_page_prot)) + return -EAGAIN; + + return 0; +} + +static long long +flash_llseek(struct file *file, long long offset, int origin) +{ + mutex_lock(&flash_mutex); + switch (origin) { + case 0: + file->f_pos = offset; + break; + case 1: + file->f_pos += offset; + if (file->f_pos > flash.read_size) + file->f_pos = flash.read_size; + break; + case 2: + file->f_pos = flash.read_size; + break; + default: + mutex_unlock(&flash_mutex); + return -EINVAL; + } + mutex_unlock(&flash_mutex); + return file->f_pos; +} + +static ssize_t +flash_read(struct file * file, char __user * buf, + size_t count, loff_t *ppos) +{ + loff_t p = *ppos; + int i; + + if (count > flash.read_size - p) + count = flash.read_size - p; + + for (i = 0; i < count; i++) { + u8 data = upa_readb(flash.read_base + p + i); + if (put_user(data, buf)) + return -EFAULT; + buf++; + } + + *ppos += count; + return count; +} + +static int +flash_open(struct inode *inode, struct file *file) +{ + mutex_lock(&flash_mutex); + if (test_and_set_bit(0, (void *)&flash.busy) != 0) { + mutex_unlock(&flash_mutex); + return -EBUSY; + } + + mutex_unlock(&flash_mutex); + return 0; +} + +static int +flash_release(struct inode *inode, struct file *file) +{ + spin_lock(&flash_lock); + flash.busy = 0; + spin_unlock(&flash_lock); + + return 0; +} + +static const struct file_operations flash_fops = { + /* no write to the Flash, use mmap + * and play flash dependent tricks. + */ + .owner = THIS_MODULE, + .llseek = flash_llseek, + .read = flash_read, + .mmap = flash_mmap, + .open = flash_open, + .release = flash_release, +}; + +static struct miscdevice flash_dev = { SBUS_FLASH_MINOR, "flash", &flash_fops }; + +static int flash_probe(struct platform_device *op) +{ + struct device_node *dp = op->dev.of_node; + struct device_node *parent; + + parent = dp->parent; + + if (!of_node_name_eq(parent, "sbus") && + !of_node_name_eq(parent, "sbi") && + !of_node_name_eq(parent, "ebus")) + return -ENODEV; + + flash.read_base = op->resource[0].start; + flash.read_size = resource_size(&op->resource[0]); + if (op->resource[1].flags) { + flash.write_base = op->resource[1].start; + flash.write_size = resource_size(&op->resource[1]); + } else { + flash.write_base = op->resource[0].start; + flash.write_size = resource_size(&op->resource[0]); + } + flash.busy = 0; + + printk(KERN_INFO "%pOF: OBP Flash, RD %lx[%lx] WR %lx[%lx]\n", + op->dev.of_node, + flash.read_base, flash.read_size, + flash.write_base, flash.write_size); + + return misc_register(&flash_dev); +} + +static int flash_remove(struct platform_device *op) +{ + misc_deregister(&flash_dev); + + return 0; +} + +static const struct of_device_id flash_match[] = { + { + .name = "flashprom", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, flash_match); + +static struct platform_driver flash_driver = { + .driver = { + .name = "flash", + .of_match_table = flash_match, + }, + .probe = flash_probe, + .remove = flash_remove, +}; + +module_platform_driver(flash_driver); + +MODULE_LICENSE("GPL"); diff --git a/drivers/sbus/char/max1617.h b/drivers/sbus/char/max1617.h new file mode 100644 index 000000000..45c831878 --- /dev/null +++ b/drivers/sbus/char/max1617.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* $Id: max1617.h,v 1.1 2001/04/02 09:59:08 davem Exp $ */ +#ifndef _MAX1617_H +#define _MAX1617_H + +#define MAX1617_AMB_TEMP 0x00 /* Ambient temp in C */ +#define MAX1617_CPU_TEMP 0x01 /* Processor die temp in C */ +#define MAX1617_STATUS 0x02 /* Chip status bits */ + +/* Read-only versions of changeable registers. */ +#define MAX1617_RD_CFG_BYTE 0x03 /* Config register */ +#define MAX1617_RD_CVRATE_BYTE 0x04 /* Temp conversion rate */ +#define MAX1617_RD_AMB_HIGHLIM 0x05 /* Ambient high limit */ +#define MAX1617_RD_AMB_LOWLIM 0x06 /* Ambient low limit */ +#define MAX1617_RD_CPU_HIGHLIM 0x07 /* Processor high limit */ +#define MAX1617_RD_CPU_LOWLIM 0x08 /* Processor low limit */ + +/* Write-only versions of the same. */ +#define MAX1617_WR_CFG_BYTE 0x09 +#define MAX1617_WR_CVRATE_BYTE 0x0a +#define MAX1617_WR_AMB_HIGHLIM 0x0b +#define MAX1617_WR_AMB_LOWLIM 0x0c +#define MAX1617_WR_CPU_HIGHLIM 0x0d +#define MAX1617_WR_CPU_LOWLIM 0x0e + +#define MAX1617_ONESHOT 0x0f + +#endif /* _MAX1617_H */ diff --git a/drivers/sbus/char/openprom.c b/drivers/sbus/char/openprom.c new file mode 100644 index 000000000..30b9751aa --- /dev/null +++ b/drivers/sbus/char/openprom.c @@ -0,0 +1,726 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Linux/SPARC PROM Configuration Driver + * Copyright (C) 1996 Thomas K. Dyas (tdyas@noc.rutgers.edu) + * Copyright (C) 1996 Eddie C. Dost (ecd@skynet.be) + * + * This character device driver allows user programs to access the + * PROM device tree. It is compatible with the SunOS /dev/openprom + * driver and the NetBSD /dev/openprom driver. The SunOS eeprom + * utility works without any modifications. + * + * The driver uses a minor number under the misc device major. The + * file read/write mode determines the type of access to the PROM. + * Interrupts are disabled whenever the driver calls into the PROM for + * sanity's sake. + */ + + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <asm/oplib.h> +#include <asm/prom.h> +#include <linux/uaccess.h> +#include <asm/openpromio.h> +#ifdef CONFIG_PCI +#include <linux/pci.h> +#endif + +MODULE_AUTHOR("Thomas K. Dyas (tdyas@noc.rutgers.edu) and Eddie C. Dost (ecd@skynet.be)"); +MODULE_DESCRIPTION("OPENPROM Configuration Driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("1.0"); +MODULE_ALIAS_MISCDEV(SUN_OPENPROM_MINOR); + +/* Private data kept by the driver for each descriptor. */ +typedef struct openprom_private_data +{ + struct device_node *current_node; /* Current node for SunOS ioctls. */ + struct device_node *lastnode; /* Last valid node used by BSD ioctls. */ +} DATA; + +/* ID of the PROM node containing all of the EEPROM options. */ +static DEFINE_MUTEX(openprom_mutex); +static struct device_node *options_node; + +/* + * Copy an openpromio structure into kernel space from user space. + * This routine does error checking to make sure that all memory + * accesses are within bounds. A pointer to the allocated openpromio + * structure will be placed in "*opp_p". Return value is the length + * of the user supplied buffer. + */ +static int copyin(struct openpromio __user *info, struct openpromio **opp_p) +{ + unsigned int bufsize; + + if (!info || !opp_p) + return -EFAULT; + + if (get_user(bufsize, &info->oprom_size)) + return -EFAULT; + + if (bufsize == 0) + return -EINVAL; + + /* If the bufsize is too large, just limit it. + * Fix from Jason Rappleye. + */ + if (bufsize > OPROMMAXPARAM) + bufsize = OPROMMAXPARAM; + + if (!(*opp_p = kzalloc(sizeof(int) + bufsize + 1, GFP_KERNEL))) + return -ENOMEM; + + if (copy_from_user(&(*opp_p)->oprom_array, + &info->oprom_array, bufsize)) { + kfree(*opp_p); + return -EFAULT; + } + return bufsize; +} + +static int getstrings(struct openpromio __user *info, struct openpromio **opp_p) +{ + int n, bufsize; + char c; + + if (!info || !opp_p) + return -EFAULT; + + if (!(*opp_p = kzalloc(sizeof(int) + OPROMMAXPARAM + 1, GFP_KERNEL))) + return -ENOMEM; + + (*opp_p)->oprom_size = 0; + + n = bufsize = 0; + while ((n < 2) && (bufsize < OPROMMAXPARAM)) { + if (get_user(c, &info->oprom_array[bufsize])) { + kfree(*opp_p); + return -EFAULT; + } + if (c == '\0') + n++; + (*opp_p)->oprom_array[bufsize++] = c; + } + if (!n) { + kfree(*opp_p); + return -EINVAL; + } + return bufsize; +} + +/* + * Copy an openpromio structure in kernel space back to user space. + */ +static int copyout(void __user *info, struct openpromio *opp, int len) +{ + if (copy_to_user(info, opp, len)) + return -EFAULT; + return 0; +} + +static int opromgetprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize) +{ + const void *pval; + int len; + + if (!dp || + !(pval = of_get_property(dp, op->oprom_array, &len)) || + len <= 0 || len > bufsize) + return copyout(argp, op, sizeof(int)); + + memcpy(op->oprom_array, pval, len); + op->oprom_array[len] = '\0'; + op->oprom_size = len; + + return copyout(argp, op, sizeof(int) + bufsize); +} + +static int opromnxtprop(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize) +{ + struct property *prop; + int len; + + if (!dp) + return copyout(argp, op, sizeof(int)); + if (op->oprom_array[0] == '\0') { + prop = dp->properties; + if (!prop) + return copyout(argp, op, sizeof(int)); + len = strlen(prop->name); + } else { + prop = of_find_property(dp, op->oprom_array, NULL); + + if (!prop || + !prop->next || + (len = strlen(prop->next->name)) + 1 > bufsize) + return copyout(argp, op, sizeof(int)); + + prop = prop->next; + } + + memcpy(op->oprom_array, prop->name, len); + op->oprom_array[len] = '\0'; + op->oprom_size = ++len; + + return copyout(argp, op, sizeof(int) + bufsize); +} + +static int opromsetopt(struct device_node *dp, struct openpromio *op, int bufsize) +{ + char *buf = op->oprom_array + strlen(op->oprom_array) + 1; + int len = op->oprom_array + bufsize - buf; + + return of_set_property(options_node, op->oprom_array, buf, len); +} + +static int opromnext(void __user *argp, unsigned int cmd, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +{ + phandle ph; + + BUILD_BUG_ON(sizeof(phandle) != sizeof(int)); + + if (bufsize < sizeof(phandle)) + return -EINVAL; + + ph = *((int *) op->oprom_array); + if (ph) { + dp = of_find_node_by_phandle(ph); + if (!dp) + return -EINVAL; + + switch (cmd) { + case OPROMNEXT: + dp = dp->sibling; + break; + + case OPROMCHILD: + dp = dp->child; + break; + + case OPROMSETCUR: + default: + break; + } + } else { + /* Sibling of node zero is the root node. */ + if (cmd != OPROMNEXT) + return -EINVAL; + + dp = of_find_node_by_path("/"); + } + + ph = 0; + if (dp) + ph = dp->phandle; + + data->current_node = dp; + *((int *) op->oprom_array) = ph; + op->oprom_size = sizeof(phandle); + + return copyout(argp, op, bufsize + sizeof(int)); +} + +static int oprompci2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +{ + int err = -EINVAL; + + if (bufsize >= 2*sizeof(int)) { +#ifdef CONFIG_PCI + struct pci_dev *pdev; + struct device_node *dp; + + pdev = pci_get_domain_bus_and_slot(0, + ((int *) op->oprom_array)[0], + ((int *) op->oprom_array)[1]); + + dp = pci_device_to_OF_node(pdev); + data->current_node = dp; + *((int *)op->oprom_array) = dp->phandle; + op->oprom_size = sizeof(int); + err = copyout(argp, op, bufsize + sizeof(int)); + + pci_dev_put(pdev); +#endif + } + + return err; +} + +static int oprompath2node(void __user *argp, struct device_node *dp, struct openpromio *op, int bufsize, DATA *data) +{ + phandle ph = 0; + + dp = of_find_node_by_path(op->oprom_array); + if (dp) + ph = dp->phandle; + data->current_node = dp; + *((int *)op->oprom_array) = ph; + op->oprom_size = sizeof(int); + + return copyout(argp, op, bufsize + sizeof(int)); +} + +static int opromgetbootargs(void __user *argp, struct openpromio *op, int bufsize) +{ + char *buf = saved_command_line; + int len = strlen(buf); + + if (len > bufsize) + return -EINVAL; + + strcpy(op->oprom_array, buf); + op->oprom_size = len; + + return copyout(argp, op, bufsize + sizeof(int)); +} + +/* + * SunOS and Solaris /dev/openprom ioctl calls. + */ +static long openprom_sunos_ioctl(struct file * file, + unsigned int cmd, unsigned long arg, + struct device_node *dp) +{ + DATA *data = file->private_data; + struct openpromio *opp = NULL; + int bufsize, error = 0; + static int cnt; + void __user *argp = (void __user *)arg; + + if (cmd == OPROMSETOPT) + bufsize = getstrings(argp, &opp); + else + bufsize = copyin(argp, &opp); + + if (bufsize < 0) + return bufsize; + + mutex_lock(&openprom_mutex); + + switch (cmd) { + case OPROMGETOPT: + case OPROMGETPROP: + error = opromgetprop(argp, dp, opp, bufsize); + break; + + case OPROMNXTOPT: + case OPROMNXTPROP: + error = opromnxtprop(argp, dp, opp, bufsize); + break; + + case OPROMSETOPT: + case OPROMSETOPT2: + error = opromsetopt(dp, opp, bufsize); + break; + + case OPROMNEXT: + case OPROMCHILD: + case OPROMSETCUR: + error = opromnext(argp, cmd, dp, opp, bufsize, data); + break; + + case OPROMPCI2NODE: + error = oprompci2node(argp, dp, opp, bufsize, data); + break; + + case OPROMPATH2NODE: + error = oprompath2node(argp, dp, opp, bufsize, data); + break; + + case OPROMGETBOOTARGS: + error = opromgetbootargs(argp, opp, bufsize); + break; + + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + if (cnt++ < 10) + printk(KERN_INFO "openprom_sunos_ioctl: unimplemented ioctl\n"); + error = -EINVAL; + break; + default: + if (cnt++ < 10) + printk(KERN_INFO "openprom_sunos_ioctl: cmd 0x%X, arg 0x%lX\n", cmd, arg); + error = -EINVAL; + break; + } + + kfree(opp); + mutex_unlock(&openprom_mutex); + + return error; +} + +static struct device_node *get_node(phandle n, DATA *data) +{ + struct device_node *dp = of_find_node_by_phandle(n); + + if (dp) + data->lastnode = dp; + + return dp; +} + +/* Copy in a whole string from userspace into kernelspace. */ +static char * copyin_string(char __user *user, size_t len) +{ + if ((ssize_t)len < 0 || (ssize_t)(len + 1) < 0) + return ERR_PTR(-EINVAL); + + return memdup_user_nul(user, len); +} + +/* + * NetBSD /dev/openprom ioctl calls. + */ +static int opiocget(void __user *argp, DATA *data) +{ + struct opiocdesc op; + struct device_node *dp; + char *str; + const void *pval; + int err, len; + + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + dp = get_node(op.op_nodeid, data); + + str = copyin_string(op.op_name, op.op_namelen); + if (IS_ERR(str)) + return PTR_ERR(str); + + pval = of_get_property(dp, str, &len); + err = 0; + if (!pval || len > op.op_buflen) { + err = -EINVAL; + } else { + op.op_buflen = len; + if (copy_to_user(argp, &op, sizeof(op)) || + copy_to_user(op.op_buf, pval, len)) + err = -EFAULT; + } + kfree(str); + + return err; +} + +static int opiocnextprop(void __user *argp, DATA *data) +{ + struct opiocdesc op; + struct device_node *dp; + struct property *prop; + char *str; + int len; + + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + dp = get_node(op.op_nodeid, data); + if (!dp) + return -EINVAL; + + str = copyin_string(op.op_name, op.op_namelen); + if (IS_ERR(str)) + return PTR_ERR(str); + + if (str[0] == '\0') { + prop = dp->properties; + } else { + prop = of_find_property(dp, str, NULL); + if (prop) + prop = prop->next; + } + kfree(str); + + if (!prop) + len = 0; + else + len = prop->length; + + if (len > op.op_buflen) + len = op.op_buflen; + + if (copy_to_user(argp, &op, sizeof(op))) + return -EFAULT; + + if (len && + copy_to_user(op.op_buf, prop->value, len)) + return -EFAULT; + + return 0; +} + +static int opiocset(void __user *argp, DATA *data) +{ + struct opiocdesc op; + struct device_node *dp; + char *str, *tmp; + int err; + + if (copy_from_user(&op, argp, sizeof(op))) + return -EFAULT; + + dp = get_node(op.op_nodeid, data); + if (!dp) + return -EINVAL; + + str = copyin_string(op.op_name, op.op_namelen); + if (IS_ERR(str)) + return PTR_ERR(str); + + tmp = copyin_string(op.op_buf, op.op_buflen); + if (IS_ERR(tmp)) { + kfree(str); + return PTR_ERR(tmp); + } + + err = of_set_property(dp, str, tmp, op.op_buflen); + + kfree(str); + kfree(tmp); + + return err; +} + +static int opiocgetnext(unsigned int cmd, void __user *argp) +{ + struct device_node *dp; + phandle nd; + + BUILD_BUG_ON(sizeof(phandle) != sizeof(int)); + + if (copy_from_user(&nd, argp, sizeof(phandle))) + return -EFAULT; + + if (nd == 0) { + if (cmd != OPIOCGETNEXT) + return -EINVAL; + dp = of_find_node_by_path("/"); + } else { + dp = of_find_node_by_phandle(nd); + nd = 0; + if (dp) { + if (cmd == OPIOCGETNEXT) + dp = dp->sibling; + else + dp = dp->child; + } + } + if (dp) + nd = dp->phandle; + if (copy_to_user(argp, &nd, sizeof(phandle))) + return -EFAULT; + + return 0; +} + +static int openprom_bsd_ioctl(struct file * file, + unsigned int cmd, unsigned long arg) +{ + DATA *data = file->private_data; + void __user *argp = (void __user *)arg; + int err; + + mutex_lock(&openprom_mutex); + switch (cmd) { + case OPIOCGET: + err = opiocget(argp, data); + break; + + case OPIOCNEXTPROP: + err = opiocnextprop(argp, data); + break; + + case OPIOCSET: + err = opiocset(argp, data); + break; + + case OPIOCGETOPTNODE: + BUILD_BUG_ON(sizeof(phandle) != sizeof(int)); + + err = 0; + if (copy_to_user(argp, &options_node->phandle, sizeof(phandle))) + err = -EFAULT; + break; + + case OPIOCGETNEXT: + case OPIOCGETCHILD: + err = opiocgetnext(cmd, argp); + break; + + default: + err = -EINVAL; + break; + } + mutex_unlock(&openprom_mutex); + + return err; +} + + +/* + * Handoff control to the correct ioctl handler. + */ +static long openprom_ioctl(struct file * file, + unsigned int cmd, unsigned long arg) +{ + DATA *data = file->private_data; + + switch (cmd) { + case OPROMGETOPT: + case OPROMNXTOPT: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, + options_node); + + case OPROMSETOPT: + case OPROMSETOPT2: + if ((file->f_mode & FMODE_WRITE) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, + options_node); + + case OPROMNEXT: + case OPROMCHILD: + case OPROMGETPROP: + case OPROMNXTPROP: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, + data->current_node); + + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + case OPROMGETBOOTARGS: + case OPROMSETCUR: + case OPROMPCI2NODE: + case OPROMPATH2NODE: + if ((file->f_mode & FMODE_READ) == 0) + return -EPERM; + return openprom_sunos_ioctl(file, cmd, arg, NULL); + + case OPIOCGET: + case OPIOCNEXTPROP: + case OPIOCGETOPTNODE: + case OPIOCGETNEXT: + case OPIOCGETCHILD: + if ((file->f_mode & FMODE_READ) == 0) + return -EBADF; + return openprom_bsd_ioctl(file,cmd,arg); + + case OPIOCSET: + if ((file->f_mode & FMODE_WRITE) == 0) + return -EBADF; + return openprom_bsd_ioctl(file,cmd,arg); + + default: + return -EINVAL; + }; +} + +static long openprom_compat_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + long rval = -ENOTTY; + + /* + * SunOS/Solaris only, the NetBSD one's have embedded pointers in + * the arg which we'd need to clean up... + */ + switch (cmd) { + case OPROMGETOPT: + case OPROMSETOPT: + case OPROMNXTOPT: + case OPROMSETOPT2: + case OPROMNEXT: + case OPROMCHILD: + case OPROMGETPROP: + case OPROMNXTPROP: + case OPROMU2P: + case OPROMGETCONS: + case OPROMGETFBNAME: + case OPROMGETBOOTARGS: + case OPROMSETCUR: + case OPROMPCI2NODE: + case OPROMPATH2NODE: + rval = openprom_ioctl(file, cmd, arg); + break; + } + + return rval; +} + +static int openprom_open(struct inode * inode, struct file * file) +{ + DATA *data; + + data = kmalloc(sizeof(DATA), GFP_KERNEL); + if (!data) + return -ENOMEM; + + mutex_lock(&openprom_mutex); + data->current_node = of_find_node_by_path("/"); + data->lastnode = data->current_node; + file->private_data = (void *) data; + mutex_unlock(&openprom_mutex); + + return 0; +} + +static int openprom_release(struct inode * inode, struct file * file) +{ + kfree(file->private_data); + return 0; +} + +static const struct file_operations openprom_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = openprom_ioctl, + .compat_ioctl = openprom_compat_ioctl, + .open = openprom_open, + .release = openprom_release, +}; + +static struct miscdevice openprom_dev = { + .minor = SUN_OPENPROM_MINOR, + .name = "openprom", + .fops = &openprom_fops, +}; + +static int __init openprom_init(void) +{ + int err; + + err = misc_register(&openprom_dev); + if (err) + return err; + + options_node = of_get_child_by_name(of_find_node_by_path("/"), "options"); + if (!options_node) { + misc_deregister(&openprom_dev); + return -EIO; + } + + return 0; +} + +static void __exit openprom_cleanup(void) +{ + misc_deregister(&openprom_dev); +} + +module_init(openprom_init); +module_exit(openprom_cleanup); diff --git a/drivers/sbus/char/oradax.c b/drivers/sbus/char/oradax.c new file mode 100644 index 000000000..21b7cb6e7 --- /dev/null +++ b/drivers/sbus/char/oradax.c @@ -0,0 +1,990 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + */ + +/* + * Oracle Data Analytics Accelerator (DAX) + * + * DAX is a coprocessor which resides on the SPARC M7 (DAX1) and M8 + * (DAX2) processor chips, and has direct access to the CPU's L3 + * caches as well as physical memory. It can perform several + * operations on data streams with various input and output formats. + * The driver provides a transport mechanism only and has limited + * knowledge of the various opcodes and data formats. A user space + * library provides high level services and translates these into low + * level commands which are then passed into the driver and + * subsequently the hypervisor and the coprocessor. The library is + * the recommended way for applications to use the coprocessor, and + * the driver interface is not intended for general use. + * + * See Documentation/sparc/oradax/oracle-dax.rst for more details. + */ + +#include <linux/uaccess.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/cdev.h> +#include <linux/slab.h> +#include <linux/mm.h> + +#include <asm/hypervisor.h> +#include <asm/mdesc.h> +#include <asm/oradax.h> + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Driver for Oracle Data Analytics Accelerator"); + +#define DAX_DBG_FLG_BASIC 0x01 +#define DAX_DBG_FLG_STAT 0x02 +#define DAX_DBG_FLG_INFO 0x04 +#define DAX_DBG_FLG_ALL 0xff + +#define dax_err(fmt, ...) pr_err("%s: " fmt "\n", __func__, ##__VA_ARGS__) +#define dax_info(fmt, ...) pr_info("%s: " fmt "\n", __func__, ##__VA_ARGS__) + +#define dax_dbg(fmt, ...) do { \ + if (dax_debug & DAX_DBG_FLG_BASIC)\ + dax_info(fmt, ##__VA_ARGS__); \ + } while (0) +#define dax_stat_dbg(fmt, ...) do { \ + if (dax_debug & DAX_DBG_FLG_STAT) \ + dax_info(fmt, ##__VA_ARGS__); \ + } while (0) +#define dax_info_dbg(fmt, ...) do { \ + if (dax_debug & DAX_DBG_FLG_INFO) \ + dax_info(fmt, ##__VA_ARGS__); \ + } while (0) + +#define DAX1_MINOR 1 +#define DAX1_MAJOR 1 +#define DAX2_MINOR 0 +#define DAX2_MAJOR 2 + +#define DAX1_STR "ORCL,sun4v-dax" +#define DAX2_STR "ORCL,sun4v-dax2" + +#define DAX_CA_ELEMS (DAX_MMAP_LEN / sizeof(struct dax_cca)) + +#define DAX_CCB_USEC 100 +#define DAX_CCB_RETRIES 10000 + +/* stream types */ +enum { + OUT, + PRI, + SEC, + TBL, + NUM_STREAM_TYPES +}; + +/* completion status */ +#define CCA_STAT_NOT_COMPLETED 0 +#define CCA_STAT_COMPLETED 1 +#define CCA_STAT_FAILED 2 +#define CCA_STAT_KILLED 3 +#define CCA_STAT_NOT_RUN 4 +#define CCA_STAT_PIPE_OUT 5 +#define CCA_STAT_PIPE_SRC 6 +#define CCA_STAT_PIPE_DST 7 + +/* completion err */ +#define CCA_ERR_SUCCESS 0x0 /* no error */ +#define CCA_ERR_OVERFLOW 0x1 /* buffer overflow */ +#define CCA_ERR_DECODE 0x2 /* CCB decode error */ +#define CCA_ERR_PAGE_OVERFLOW 0x3 /* page overflow */ +#define CCA_ERR_KILLED 0x7 /* command was killed */ +#define CCA_ERR_TIMEOUT 0x8 /* Timeout */ +#define CCA_ERR_ADI 0x9 /* ADI error */ +#define CCA_ERR_DATA_FMT 0xA /* data format error */ +#define CCA_ERR_OTHER_NO_RETRY 0xE /* Other error, do not retry */ +#define CCA_ERR_OTHER_RETRY 0xF /* Other error, retry */ +#define CCA_ERR_PARTIAL_SYMBOL 0x80 /* QP partial symbol warning */ + +/* CCB address types */ +#define DAX_ADDR_TYPE_NONE 0 +#define DAX_ADDR_TYPE_VA_ALT 1 /* secondary context */ +#define DAX_ADDR_TYPE_RA 2 /* real address */ +#define DAX_ADDR_TYPE_VA 3 /* virtual address */ + +/* dax_header_t opcode */ +#define DAX_OP_SYNC_NOP 0x0 +#define DAX_OP_EXTRACT 0x1 +#define DAX_OP_SCAN_VALUE 0x2 +#define DAX_OP_SCAN_RANGE 0x3 +#define DAX_OP_TRANSLATE 0x4 +#define DAX_OP_SELECT 0x5 +#define DAX_OP_INVERT 0x10 /* OR with translate, scan opcodes */ + +struct dax_header { + u32 ccb_version:4; /* 31:28 CCB Version */ + /* 27:24 Sync Flags */ + u32 pipe:1; /* Pipeline */ + u32 longccb:1; /* Longccb. Set for scan with lu2, lu3, lu4. */ + u32 cond:1; /* Conditional */ + u32 serial:1; /* Serial */ + u32 opcode:8; /* 23:16 Opcode */ + /* 15:0 Address Type. */ + u32 reserved:3; /* 15:13 reserved */ + u32 table_addr_type:2; /* 12:11 Huffman Table Address Type */ + u32 out_addr_type:3; /* 10:8 Destination Address Type */ + u32 sec_addr_type:3; /* 7:5 Secondary Source Address Type */ + u32 pri_addr_type:3; /* 4:2 Primary Source Address Type */ + u32 cca_addr_type:2; /* 1:0 Completion Address Type */ +}; + +struct dax_control { + u32 pri_fmt:4; /* 31:28 Primary Input Format */ + u32 pri_elem_size:5; /* 27:23 Primary Input Element Size(less1) */ + u32 pri_offset:3; /* 22:20 Primary Input Starting Offset */ + u32 sec_encoding:1; /* 19 Secondary Input Encoding */ + /* (must be 0 for Select) */ + u32 sec_offset:3; /* 18:16 Secondary Input Starting Offset */ + u32 sec_elem_size:2; /* 15:14 Secondary Input Element Size */ + /* (must be 0 for Select) */ + u32 out_fmt:2; /* 13:12 Output Format */ + u32 out_elem_size:2; /* 11:10 Output Element Size */ + u32 misc:10; /* 9:0 Opcode specific info */ +}; + +struct dax_data_access { + u64 flow_ctrl:2; /* 63:62 Flow Control Type */ + u64 pipe_target:2; /* 61:60 Pipeline Target */ + u64 out_buf_size:20; /* 59:40 Output Buffer Size */ + /* (cachelines less 1) */ + u64 unused1:8; /* 39:32 Reserved, Set to 0 */ + u64 out_alloc:5; /* 31:27 Output Allocation */ + u64 unused2:1; /* 26 Reserved */ + u64 pri_len_fmt:2; /* 25:24 Input Length Format */ + u64 pri_len:24; /* 23:0 Input Element/Byte/Bit Count */ + /* (less 1) */ +}; + +struct dax_ccb { + struct dax_header hdr; /* CCB Header */ + struct dax_control ctrl;/* Control Word */ + void *ca; /* Completion Address */ + void *pri; /* Primary Input Address */ + struct dax_data_access dac; /* Data Access Control */ + void *sec; /* Secondary Input Address */ + u64 dword5; /* depends on opcode */ + void *out; /* Output Address */ + void *tbl; /* Table Address or bitmap */ +}; + +struct dax_cca { + u8 status; /* user may mwait on this address */ + u8 err; /* user visible error notification */ + u8 rsvd[2]; /* reserved */ + u32 n_remaining; /* for QP partial symbol warning */ + u32 output_sz; /* output in bytes */ + u32 rsvd2; /* reserved */ + u64 run_cycles; /* run time in OCND2 cycles */ + u64 run_stats; /* nothing reported in version 1.0 */ + u32 n_processed; /* number input elements */ + u32 rsvd3[5]; /* reserved */ + u64 retval; /* command return value */ + u64 rsvd4[8]; /* reserved */ +}; + +/* per thread CCB context */ +struct dax_ctx { + struct dax_ccb *ccb_buf; + u64 ccb_buf_ra; /* cached RA of ccb_buf */ + struct dax_cca *ca_buf; + u64 ca_buf_ra; /* cached RA of ca_buf */ + struct page *pages[DAX_CA_ELEMS][NUM_STREAM_TYPES]; + /* array of locked pages */ + struct task_struct *owner; /* thread that owns ctx */ + struct task_struct *client; /* requesting thread */ + union ccb_result result; + u32 ccb_count; + u32 fail_count; +}; + +/* driver public entry points */ +static int dax_open(struct inode *inode, struct file *file); +static ssize_t dax_read(struct file *filp, char __user *buf, + size_t count, loff_t *ppos); +static ssize_t dax_write(struct file *filp, const char __user *buf, + size_t count, loff_t *ppos); +static int dax_devmap(struct file *f, struct vm_area_struct *vma); +static int dax_close(struct inode *i, struct file *f); + +static const struct file_operations dax_fops = { + .owner = THIS_MODULE, + .open = dax_open, + .read = dax_read, + .write = dax_write, + .mmap = dax_devmap, + .release = dax_close, +}; + +static int dax_ccb_exec(struct dax_ctx *ctx, const char __user *buf, + size_t count, loff_t *ppos); +static int dax_ccb_info(u64 ca, struct ccb_info_result *info); +static int dax_ccb_kill(u64 ca, u16 *kill_res); + +static struct cdev c_dev; +static struct class *cl; +static dev_t first; + +static int max_ccb_version; +static int dax_debug; +module_param(dax_debug, int, 0644); +MODULE_PARM_DESC(dax_debug, "Debug flags"); + +static int __init dax_attach(void) +{ + unsigned long dummy, hv_rv, major, minor, minor_requested, max_ccbs; + struct mdesc_handle *hp = mdesc_grab(); + char *prop, *dax_name; + bool found = false; + int len, ret = 0; + u64 pn; + + if (hp == NULL) { + dax_err("Unable to grab mdesc"); + return -ENODEV; + } + + mdesc_for_each_node_by_name(hp, pn, "virtual-device") { + prop = (char *)mdesc_get_property(hp, pn, "name", &len); + if (prop == NULL) + continue; + if (strncmp(prop, "dax", strlen("dax"))) + continue; + dax_dbg("Found node 0x%llx = %s", pn, prop); + + prop = (char *)mdesc_get_property(hp, pn, "compatible", &len); + if (prop == NULL) + continue; + dax_dbg("Found node 0x%llx = %s", pn, prop); + found = true; + break; + } + + if (!found) { + dax_err("No DAX device found"); + ret = -ENODEV; + goto done; + } + + if (strncmp(prop, DAX2_STR, strlen(DAX2_STR)) == 0) { + dax_name = DAX_NAME "2"; + major = DAX2_MAJOR; + minor_requested = DAX2_MINOR; + max_ccb_version = 1; + dax_dbg("MD indicates DAX2 coprocessor"); + } else if (strncmp(prop, DAX1_STR, strlen(DAX1_STR)) == 0) { + dax_name = DAX_NAME "1"; + major = DAX1_MAJOR; + minor_requested = DAX1_MINOR; + max_ccb_version = 0; + dax_dbg("MD indicates DAX1 coprocessor"); + } else { + dax_err("Unknown dax type: %s", prop); + ret = -ENODEV; + goto done; + } + + minor = minor_requested; + dax_dbg("Registering DAX HV api with major %ld minor %ld", major, + minor); + if (sun4v_hvapi_register(HV_GRP_DAX, major, &minor)) { + dax_err("hvapi_register failed"); + ret = -ENODEV; + goto done; + } else { + dax_dbg("Max minor supported by HV = %ld (major %ld)", minor, + major); + minor = min(minor, minor_requested); + dax_dbg("registered DAX major %ld minor %ld", major, minor); + } + + /* submit a zero length ccb array to query coprocessor queue size */ + hv_rv = sun4v_ccb_submit(0, 0, HV_CCB_QUERY_CMD, 0, &max_ccbs, &dummy); + if (hv_rv != 0) { + dax_err("get_hwqueue_size failed with status=%ld and max_ccbs=%ld", + hv_rv, max_ccbs); + ret = -ENODEV; + goto done; + } + + if (max_ccbs != DAX_MAX_CCBS) { + dax_err("HV reports unsupported max_ccbs=%ld", max_ccbs); + ret = -ENODEV; + goto done; + } + + if (alloc_chrdev_region(&first, 0, 1, DAX_NAME) < 0) { + dax_err("alloc_chrdev_region failed"); + ret = -ENXIO; + goto done; + } + + cl = class_create(THIS_MODULE, DAX_NAME); + if (IS_ERR(cl)) { + dax_err("class_create failed"); + ret = PTR_ERR(cl); + goto class_error; + } + + if (device_create(cl, NULL, first, NULL, dax_name) == NULL) { + dax_err("device_create failed"); + ret = -ENXIO; + goto device_error; + } + + cdev_init(&c_dev, &dax_fops); + if (cdev_add(&c_dev, first, 1) == -1) { + dax_err("cdev_add failed"); + ret = -ENXIO; + goto cdev_error; + } + + pr_info("Attached DAX module\n"); + goto done; + +cdev_error: + device_destroy(cl, first); +device_error: + class_destroy(cl); +class_error: + unregister_chrdev_region(first, 1); +done: + mdesc_release(hp); + return ret; +} +module_init(dax_attach); + +static void __exit dax_detach(void) +{ + pr_info("Cleaning up DAX module\n"); + cdev_del(&c_dev); + device_destroy(cl, first); + class_destroy(cl); + unregister_chrdev_region(first, 1); +} +module_exit(dax_detach); + +/* map completion area */ +static int dax_devmap(struct file *f, struct vm_area_struct *vma) +{ + struct dax_ctx *ctx = (struct dax_ctx *)f->private_data; + size_t len = vma->vm_end - vma->vm_start; + + dax_dbg("len=0x%lx, flags=0x%lx", len, vma->vm_flags); + + if (ctx->owner != current) { + dax_dbg("devmap called from wrong thread"); + return -EINVAL; + } + + if (len != DAX_MMAP_LEN) { + dax_dbg("len(%lu) != DAX_MMAP_LEN(%d)", len, DAX_MMAP_LEN); + return -EINVAL; + } + + /* completion area is mapped read-only for user */ + if (vma->vm_flags & VM_WRITE) + return -EPERM; + vma->vm_flags &= ~VM_MAYWRITE; + + if (remap_pfn_range(vma, vma->vm_start, ctx->ca_buf_ra >> PAGE_SHIFT, + len, vma->vm_page_prot)) + return -EAGAIN; + + dax_dbg("mmapped completion area at uva 0x%lx", vma->vm_start); + return 0; +} + +/* Unlock user pages. Called during dequeue or device close */ +static void dax_unlock_pages(struct dax_ctx *ctx, int ccb_index, int nelem) +{ + int i, j; + + for (i = ccb_index; i < ccb_index + nelem; i++) { + for (j = 0; j < NUM_STREAM_TYPES; j++) { + struct page *p = ctx->pages[i][j]; + + if (p) { + dax_dbg("freeing page %p", p); + unpin_user_pages_dirty_lock(&p, 1, j == OUT); + ctx->pages[i][j] = NULL; + } + } + } +} + +static int dax_lock_page(void *va, struct page **p) +{ + int ret; + + dax_dbg("uva %p", va); + + ret = pin_user_pages_fast((unsigned long)va, 1, FOLL_WRITE, p); + if (ret == 1) { + dax_dbg("locked page %p, for VA %p", *p, va); + return 0; + } + + dax_dbg("pin_user_pages failed, va=%p, ret=%d", va, ret); + return -1; +} + +static int dax_lock_pages(struct dax_ctx *ctx, int idx, + int nelem, u64 *err_va) +{ + int i; + + for (i = 0; i < nelem; i++) { + struct dax_ccb *ccbp = &ctx->ccb_buf[i]; + + /* + * For each address in the CCB whose type is virtual, + * lock the page and change the type to virtual alternate + * context. On error, return the offending address in + * err_va. + */ + if (ccbp->hdr.out_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("output"); + if (dax_lock_page(ccbp->out, + &ctx->pages[i + idx][OUT]) != 0) { + *err_va = (u64)ccbp->out; + goto error; + } + ccbp->hdr.out_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + if (ccbp->hdr.pri_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("input"); + if (dax_lock_page(ccbp->pri, + &ctx->pages[i + idx][PRI]) != 0) { + *err_va = (u64)ccbp->pri; + goto error; + } + ccbp->hdr.pri_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + if (ccbp->hdr.sec_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("sec input"); + if (dax_lock_page(ccbp->sec, + &ctx->pages[i + idx][SEC]) != 0) { + *err_va = (u64)ccbp->sec; + goto error; + } + ccbp->hdr.sec_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + if (ccbp->hdr.table_addr_type == DAX_ADDR_TYPE_VA) { + dax_dbg("tbl"); + if (dax_lock_page(ccbp->tbl, + &ctx->pages[i + idx][TBL]) != 0) { + *err_va = (u64)ccbp->tbl; + goto error; + } + ccbp->hdr.table_addr_type = DAX_ADDR_TYPE_VA_ALT; + } + + /* skip over 2nd 64 bytes of long CCB */ + if (ccbp->hdr.longccb) + i++; + } + return DAX_SUBMIT_OK; + +error: + dax_unlock_pages(ctx, idx, nelem); + return DAX_SUBMIT_ERR_NOACCESS; +} + +static void dax_ccb_wait(struct dax_ctx *ctx, int idx) +{ + int ret, nretries; + u16 kill_res; + + dax_dbg("idx=%d", idx); + + for (nretries = 0; nretries < DAX_CCB_RETRIES; nretries++) { + if (ctx->ca_buf[idx].status == CCA_STAT_NOT_COMPLETED) + udelay(DAX_CCB_USEC); + else + return; + } + dax_dbg("ctx (%p): CCB[%d] timed out, wait usec=%d, retries=%d. Killing ccb", + (void *)ctx, idx, DAX_CCB_USEC, DAX_CCB_RETRIES); + + ret = dax_ccb_kill(ctx->ca_buf_ra + idx * sizeof(struct dax_cca), + &kill_res); + dax_dbg("Kill CCB[%d] %s", idx, ret ? "failed" : "succeeded"); +} + +static int dax_close(struct inode *ino, struct file *f) +{ + struct dax_ctx *ctx = (struct dax_ctx *)f->private_data; + int i; + + f->private_data = NULL; + + for (i = 0; i < DAX_CA_ELEMS; i++) { + if (ctx->ca_buf[i].status == CCA_STAT_NOT_COMPLETED) { + dax_dbg("CCB[%d] not completed", i); + dax_ccb_wait(ctx, i); + } + dax_unlock_pages(ctx, i, 1); + } + + kfree(ctx->ccb_buf); + kfree(ctx->ca_buf); + dax_stat_dbg("CCBs: %d good, %d bad", ctx->ccb_count, ctx->fail_count); + kfree(ctx); + + return 0; +} + +static ssize_t dax_read(struct file *f, char __user *buf, + size_t count, loff_t *ppos) +{ + struct dax_ctx *ctx = f->private_data; + + if (ctx->client != current) + return -EUSERS; + + ctx->client = NULL; + + if (count != sizeof(union ccb_result)) + return -EINVAL; + if (copy_to_user(buf, &ctx->result, sizeof(union ccb_result))) + return -EFAULT; + return count; +} + +static ssize_t dax_write(struct file *f, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct dax_ctx *ctx = f->private_data; + struct dax_command hdr; + unsigned long ca; + int i, idx, ret; + + if (ctx->client != NULL) + return -EINVAL; + + if (count == 0 || count > DAX_MAX_CCBS * sizeof(struct dax_ccb)) + return -EINVAL; + + if (count % sizeof(struct dax_ccb) == 0) + return dax_ccb_exec(ctx, buf, count, ppos); /* CCB EXEC */ + + if (count != sizeof(struct dax_command)) + return -EINVAL; + + /* immediate command */ + if (ctx->owner != current) + return -EUSERS; + + if (copy_from_user(&hdr, buf, sizeof(hdr))) + return -EFAULT; + + ca = ctx->ca_buf_ra + hdr.ca_offset; + + switch (hdr.command) { + case CCB_KILL: + if (hdr.ca_offset >= DAX_MMAP_LEN) { + dax_dbg("invalid ca_offset (%d) >= ca_buflen (%d)", + hdr.ca_offset, DAX_MMAP_LEN); + return -EINVAL; + } + + ret = dax_ccb_kill(ca, &ctx->result.kill.action); + if (ret != 0) { + dax_dbg("dax_ccb_kill failed (ret=%d)", ret); + return ret; + } + + dax_info_dbg("killed (ca_offset %d)", hdr.ca_offset); + idx = hdr.ca_offset / sizeof(struct dax_cca); + ctx->ca_buf[idx].status = CCA_STAT_KILLED; + ctx->ca_buf[idx].err = CCA_ERR_KILLED; + ctx->client = current; + return count; + + case CCB_INFO: + if (hdr.ca_offset >= DAX_MMAP_LEN) { + dax_dbg("invalid ca_offset (%d) >= ca_buflen (%d)", + hdr.ca_offset, DAX_MMAP_LEN); + return -EINVAL; + } + + ret = dax_ccb_info(ca, &ctx->result.info); + if (ret != 0) { + dax_dbg("dax_ccb_info failed (ret=%d)", ret); + return ret; + } + + dax_info_dbg("info succeeded on ca_offset %d", hdr.ca_offset); + ctx->client = current; + return count; + + case CCB_DEQUEUE: + for (i = 0; i < DAX_CA_ELEMS; i++) { + if (ctx->ca_buf[i].status != + CCA_STAT_NOT_COMPLETED) + dax_unlock_pages(ctx, i, 1); + } + return count; + + default: + return -EINVAL; + } +} + +static int dax_open(struct inode *inode, struct file *f) +{ + struct dax_ctx *ctx = NULL; + int i; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (ctx == NULL) + goto done; + + ctx->ccb_buf = kcalloc(DAX_MAX_CCBS, sizeof(struct dax_ccb), + GFP_KERNEL); + if (ctx->ccb_buf == NULL) + goto done; + + ctx->ccb_buf_ra = virt_to_phys(ctx->ccb_buf); + dax_dbg("ctx->ccb_buf=0x%p, ccb_buf_ra=0x%llx", + (void *)ctx->ccb_buf, ctx->ccb_buf_ra); + + /* allocate CCB completion area buffer */ + ctx->ca_buf = kzalloc(DAX_MMAP_LEN, GFP_KERNEL); + if (ctx->ca_buf == NULL) + goto alloc_error; + for (i = 0; i < DAX_CA_ELEMS; i++) + ctx->ca_buf[i].status = CCA_STAT_COMPLETED; + + ctx->ca_buf_ra = virt_to_phys(ctx->ca_buf); + dax_dbg("ctx=0x%p, ctx->ca_buf=0x%p, ca_buf_ra=0x%llx", + (void *)ctx, (void *)ctx->ca_buf, ctx->ca_buf_ra); + + ctx->owner = current; + f->private_data = ctx; + return 0; + +alloc_error: + kfree(ctx->ccb_buf); +done: + kfree(ctx); + return -ENOMEM; +} + +static char *dax_hv_errno(unsigned long hv_ret, int *ret) +{ + switch (hv_ret) { + case HV_EBADALIGN: + *ret = -EFAULT; + return "HV_EBADALIGN"; + case HV_ENORADDR: + *ret = -EFAULT; + return "HV_ENORADDR"; + case HV_EINVAL: + *ret = -EINVAL; + return "HV_EINVAL"; + case HV_EWOULDBLOCK: + *ret = -EAGAIN; + return "HV_EWOULDBLOCK"; + case HV_ENOACCESS: + *ret = -EPERM; + return "HV_ENOACCESS"; + default: + break; + } + + *ret = -EIO; + return "UNKNOWN"; +} + +static int dax_ccb_kill(u64 ca, u16 *kill_res) +{ + unsigned long hv_ret; + int count, ret = 0; + char *err_str; + + for (count = 0; count < DAX_CCB_RETRIES; count++) { + dax_dbg("attempting kill on ca_ra 0x%llx", ca); + hv_ret = sun4v_ccb_kill(ca, kill_res); + + if (hv_ret == HV_EOK) { + dax_info_dbg("HV_EOK (ca_ra 0x%llx): %d", ca, + *kill_res); + } else { + err_str = dax_hv_errno(hv_ret, &ret); + dax_dbg("%s (ca_ra 0x%llx)", err_str, ca); + } + + if (ret != -EAGAIN) + return ret; + dax_info_dbg("ccb_kill count = %d", count); + udelay(DAX_CCB_USEC); + } + + return -EAGAIN; +} + +static int dax_ccb_info(u64 ca, struct ccb_info_result *info) +{ + unsigned long hv_ret; + char *err_str; + int ret = 0; + + dax_dbg("attempting info on ca_ra 0x%llx", ca); + hv_ret = sun4v_ccb_info(ca, info); + + if (hv_ret == HV_EOK) { + dax_info_dbg("HV_EOK (ca_ra 0x%llx): %d", ca, info->state); + if (info->state == DAX_CCB_ENQUEUED) { + dax_info_dbg("dax_unit %d, queue_num %d, queue_pos %d", + info->inst_num, info->q_num, info->q_pos); + } + } else { + err_str = dax_hv_errno(hv_ret, &ret); + dax_dbg("%s (ca_ra 0x%llx)", err_str, ca); + } + + return ret; +} + +static void dax_prt_ccbs(struct dax_ccb *ccb, int nelem) +{ + int i, j; + u64 *ccbp; + + dax_dbg("ccb buffer:"); + for (i = 0; i < nelem; i++) { + ccbp = (u64 *)&ccb[i]; + dax_dbg(" %sccb[%d]", ccb[i].hdr.longccb ? "long " : "", i); + for (j = 0; j < 8; j++) + dax_dbg("\tccb[%d].dwords[%d]=0x%llx", + i, j, *(ccbp + j)); + } +} + +/* + * Validates user CCB content. Also sets completion address and address types + * for all addresses contained in CCB. + */ +static int dax_preprocess_usr_ccbs(struct dax_ctx *ctx, int idx, int nelem) +{ + int i; + + /* + * The user is not allowed to specify real address types in + * the CCB header. This must be enforced by the kernel before + * submitting the CCBs to HV. The only allowed values for all + * address fields are VA or IMM + */ + for (i = 0; i < nelem; i++) { + struct dax_ccb *ccbp = &ctx->ccb_buf[i]; + unsigned long ca_offset; + + if (ccbp->hdr.ccb_version > max_ccb_version) + return DAX_SUBMIT_ERR_CCB_INVAL; + + switch (ccbp->hdr.opcode) { + case DAX_OP_SYNC_NOP: + case DAX_OP_EXTRACT: + case DAX_OP_SCAN_VALUE: + case DAX_OP_SCAN_RANGE: + case DAX_OP_TRANSLATE: + case DAX_OP_SCAN_VALUE | DAX_OP_INVERT: + case DAX_OP_SCAN_RANGE | DAX_OP_INVERT: + case DAX_OP_TRANSLATE | DAX_OP_INVERT: + case DAX_OP_SELECT: + break; + default: + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.out_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.out_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid out_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.pri_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.pri_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid pri_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.sec_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.sec_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid sec_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + if (ccbp->hdr.table_addr_type != DAX_ADDR_TYPE_VA && + ccbp->hdr.table_addr_type != DAX_ADDR_TYPE_NONE) { + dax_dbg("invalid table_addr_type in user CCB[%d]", i); + return DAX_SUBMIT_ERR_CCB_INVAL; + } + + /* set completion (real) address and address type */ + ccbp->hdr.cca_addr_type = DAX_ADDR_TYPE_RA; + ca_offset = (idx + i) * sizeof(struct dax_cca); + ccbp->ca = (void *)ctx->ca_buf_ra + ca_offset; + memset(&ctx->ca_buf[idx + i], 0, sizeof(struct dax_cca)); + + dax_dbg("ccb[%d]=%p, ca_offset=0x%lx, compl RA=0x%llx", + i, ccbp, ca_offset, ctx->ca_buf_ra + ca_offset); + + /* skip over 2nd 64 bytes of long CCB */ + if (ccbp->hdr.longccb) + i++; + } + + return DAX_SUBMIT_OK; +} + +static int dax_ccb_exec(struct dax_ctx *ctx, const char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned long accepted_len, hv_rv; + int i, idx, nccbs, naccepted; + + ctx->client = current; + idx = *ppos; + nccbs = count / sizeof(struct dax_ccb); + + if (ctx->owner != current) { + dax_dbg("wrong thread"); + ctx->result.exec.status = DAX_SUBMIT_ERR_THR_INIT; + return 0; + } + dax_dbg("args: ccb_buf_len=%ld, idx=%d", count, idx); + + /* for given index and length, verify ca_buf range exists */ + if (idx < 0 || idx > (DAX_CA_ELEMS - nccbs)) { + ctx->result.exec.status = DAX_SUBMIT_ERR_NO_CA_AVAIL; + return 0; + } + + /* + * Copy CCBs into kernel buffer to prevent modification by the + * user in between validation and submission. + */ + if (copy_from_user(ctx->ccb_buf, buf, count)) { + dax_dbg("copyin of user CCB buffer failed"); + ctx->result.exec.status = DAX_SUBMIT_ERR_CCB_ARR_MMU_MISS; + return 0; + } + + /* check to see if ca_buf[idx] .. ca_buf[idx + nccbs] are available */ + for (i = idx; i < idx + nccbs; i++) { + if (ctx->ca_buf[i].status == CCA_STAT_NOT_COMPLETED) { + dax_dbg("CA range not available, dequeue needed"); + ctx->result.exec.status = DAX_SUBMIT_ERR_NO_CA_AVAIL; + return 0; + } + } + dax_unlock_pages(ctx, idx, nccbs); + + ctx->result.exec.status = dax_preprocess_usr_ccbs(ctx, idx, nccbs); + if (ctx->result.exec.status != DAX_SUBMIT_OK) + return 0; + + ctx->result.exec.status = dax_lock_pages(ctx, idx, nccbs, + &ctx->result.exec.status_data); + if (ctx->result.exec.status != DAX_SUBMIT_OK) + return 0; + + if (dax_debug & DAX_DBG_FLG_BASIC) + dax_prt_ccbs(ctx->ccb_buf, nccbs); + + hv_rv = sun4v_ccb_submit(ctx->ccb_buf_ra, count, + HV_CCB_QUERY_CMD | HV_CCB_VA_SECONDARY, 0, + &accepted_len, &ctx->result.exec.status_data); + + switch (hv_rv) { + case HV_EOK: + /* + * Hcall succeeded with no errors but the accepted + * length may be less than the requested length. The + * only way the driver can resubmit the remainder is + * to wait for completion of the submitted CCBs since + * there is no way to guarantee the ordering semantics + * required by the client applications. Therefore we + * let the user library deal with resubmissions. + */ + ctx->result.exec.status = DAX_SUBMIT_OK; + break; + case HV_EWOULDBLOCK: + /* + * This is a transient HV API error. The user library + * can retry. + */ + dax_dbg("hcall returned HV_EWOULDBLOCK"); + ctx->result.exec.status = DAX_SUBMIT_ERR_WOULDBLOCK; + break; + case HV_ENOMAP: + /* + * HV was unable to translate a VA. The VA it could + * not translate is returned in the status_data param. + */ + dax_dbg("hcall returned HV_ENOMAP"); + ctx->result.exec.status = DAX_SUBMIT_ERR_NOMAP; + break; + case HV_EINVAL: + /* + * This is the result of an invalid user CCB as HV is + * validating some of the user CCB fields. Pass this + * error back to the user. There is no supporting info + * to isolate the invalid field. + */ + dax_dbg("hcall returned HV_EINVAL"); + ctx->result.exec.status = DAX_SUBMIT_ERR_CCB_INVAL; + break; + case HV_ENOACCESS: + /* + * HV found a VA that did not have the appropriate + * permissions (such as the w bit). The VA in question + * is returned in status_data param. + */ + dax_dbg("hcall returned HV_ENOACCESS"); + ctx->result.exec.status = DAX_SUBMIT_ERR_NOACCESS; + break; + case HV_EUNAVAILABLE: + /* + * The requested CCB operation could not be performed + * at this time. Return the specific unavailable code + * in the status_data field. + */ + dax_dbg("hcall returned HV_EUNAVAILABLE"); + ctx->result.exec.status = DAX_SUBMIT_ERR_UNAVAIL; + break; + default: + ctx->result.exec.status = DAX_SUBMIT_ERR_INTERNAL; + dax_dbg("unknown hcall return value (%ld)", hv_rv); + break; + } + + /* unlock pages associated with the unaccepted CCBs */ + naccepted = accepted_len / sizeof(struct dax_ccb); + dax_unlock_pages(ctx, idx + naccepted, nccbs - naccepted); + + /* mark unaccepted CCBs as not completed */ + for (i = idx + naccepted; i < idx + nccbs; i++) + ctx->ca_buf[i].status = CCA_STAT_COMPLETED; + + ctx->ccb_count += naccepted; + ctx->fail_count += nccbs - naccepted; + + dax_dbg("hcall rv=%ld, accepted_len=%ld, status_data=0x%llx, ret status=%d", + hv_rv, accepted_len, ctx->result.exec.status_data, + ctx->result.exec.status); + + if (count == accepted_len) + ctx->client = NULL; /* no read needed to complete protocol */ + return accepted_len; +} diff --git a/drivers/sbus/char/uctrl.c b/drivers/sbus/char/uctrl.c new file mode 100644 index 000000000..05de0ce79 --- /dev/null +++ b/drivers/sbus/char/uctrl.c @@ -0,0 +1,435 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* uctrl.c: TS102 Microcontroller interface on Tadpole Sparcbook 3 + * + * Copyright 1999 Derrick J Brashear (shadow@dementia.org) + * Copyright 2008 David S. Miller (davem@davemloft.net) + */ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/ioport.h> +#include <linux/miscdevice.h> +#include <linux/mm.h> +#include <linux/of.h> +#include <linux/of_device.h> + +#include <asm/openprom.h> +#include <asm/oplib.h> +#include <asm/irq.h> +#include <asm/io.h> + +#define DEBUG 1 +#ifdef DEBUG +#define dprintk(x) printk x +#else +#define dprintk(x) +#endif + +struct uctrl_regs { + u32 uctrl_intr; + u32 uctrl_data; + u32 uctrl_stat; + u32 uctrl_xxx[5]; +}; + +struct ts102_regs { + u32 card_a_intr; + u32 card_a_stat; + u32 card_a_ctrl; + u32 card_a_xxx; + u32 card_b_intr; + u32 card_b_stat; + u32 card_b_ctrl; + u32 card_b_xxx; + u32 uctrl_intr; + u32 uctrl_data; + u32 uctrl_stat; + u32 uctrl_xxx; + u32 ts102_xxx[4]; +}; + +/* Bits for uctrl_intr register */ +#define UCTRL_INTR_TXE_REQ 0x01 /* transmit FIFO empty int req */ +#define UCTRL_INTR_TXNF_REQ 0x02 /* transmit FIFO not full int req */ +#define UCTRL_INTR_RXNE_REQ 0x04 /* receive FIFO not empty int req */ +#define UCTRL_INTR_RXO_REQ 0x08 /* receive FIFO overflow int req */ +#define UCTRL_INTR_TXE_MSK 0x10 /* transmit FIFO empty mask */ +#define UCTRL_INTR_TXNF_MSK 0x20 /* transmit FIFO not full mask */ +#define UCTRL_INTR_RXNE_MSK 0x40 /* receive FIFO not empty mask */ +#define UCTRL_INTR_RXO_MSK 0x80 /* receive FIFO overflow mask */ + +/* Bits for uctrl_stat register */ +#define UCTRL_STAT_TXE_STA 0x01 /* transmit FIFO empty status */ +#define UCTRL_STAT_TXNF_STA 0x02 /* transmit FIFO not full status */ +#define UCTRL_STAT_RXNE_STA 0x04 /* receive FIFO not empty status */ +#define UCTRL_STAT_RXO_STA 0x08 /* receive FIFO overflow status */ + +static DEFINE_MUTEX(uctrl_mutex); +static const char *uctrl_extstatus[16] = { + "main power available", + "internal battery attached", + "external battery attached", + "external VGA attached", + "external keyboard attached", + "external mouse attached", + "lid down", + "internal battery currently charging", + "external battery currently charging", + "internal battery currently discharging", + "external battery currently discharging", +}; + +/* Everything required for one transaction with the uctrl */ +struct uctrl_txn { + u8 opcode; + u8 inbits; + u8 outbits; + u8 *inbuf; + u8 *outbuf; +}; + +struct uctrl_status { + u8 current_temp; /* 0x07 */ + u8 reset_status; /* 0x0b */ + u16 event_status; /* 0x0c */ + u16 error_status; /* 0x10 */ + u16 external_status; /* 0x11, 0x1b */ + u8 internal_charge; /* 0x18 */ + u8 external_charge; /* 0x19 */ + u16 control_lcd; /* 0x20 */ + u8 control_bitport; /* 0x21 */ + u8 speaker_volume; /* 0x23 */ + u8 control_tft_brightness; /* 0x24 */ + u8 control_kbd_repeat_delay; /* 0x28 */ + u8 control_kbd_repeat_period; /* 0x29 */ + u8 control_screen_contrast; /* 0x2F */ +}; + +enum uctrl_opcode { + READ_SERIAL_NUMBER=0x1, + READ_ETHERNET_ADDRESS=0x2, + READ_HARDWARE_VERSION=0x3, + READ_MICROCONTROLLER_VERSION=0x4, + READ_MAX_TEMPERATURE=0x5, + READ_MIN_TEMPERATURE=0x6, + READ_CURRENT_TEMPERATURE=0x7, + READ_SYSTEM_VARIANT=0x8, + READ_POWERON_CYCLES=0x9, + READ_POWERON_SECONDS=0xA, + READ_RESET_STATUS=0xB, + READ_EVENT_STATUS=0xC, + READ_REAL_TIME_CLOCK=0xD, + READ_EXTERNAL_VGA_PORT=0xE, + READ_MICROCONTROLLER_ROM_CHECKSUM=0xF, + READ_ERROR_STATUS=0x10, + READ_EXTERNAL_STATUS=0x11, + READ_USER_CONFIGURATION_AREA=0x12, + READ_MICROCONTROLLER_VOLTAGE=0x13, + READ_INTERNAL_BATTERY_VOLTAGE=0x14, + READ_DCIN_VOLTAGE=0x15, + READ_HORIZONTAL_POINTER_VOLTAGE=0x16, + READ_VERTICAL_POINTER_VOLTAGE=0x17, + READ_INTERNAL_BATTERY_CHARGE_LEVEL=0x18, + READ_EXTERNAL_BATTERY_CHARGE_LEVEL=0x19, + READ_REAL_TIME_CLOCK_ALARM=0x1A, + READ_EVENT_STATUS_NO_RESET=0x1B, + READ_INTERNAL_KEYBOARD_LAYOUT=0x1C, + READ_EXTERNAL_KEYBOARD_LAYOUT=0x1D, + READ_EEPROM_STATUS=0x1E, + CONTROL_LCD=0x20, + CONTROL_BITPORT=0x21, + SPEAKER_VOLUME=0x23, + CONTROL_TFT_BRIGHTNESS=0x24, + CONTROL_WATCHDOG=0x25, + CONTROL_FACTORY_EEPROM_AREA=0x26, + CONTROL_KBD_TIME_UNTIL_REPEAT=0x28, + CONTROL_KBD_TIME_BETWEEN_REPEATS=0x29, + CONTROL_TIMEZONE=0x2A, + CONTROL_MARK_SPACE_RATIO=0x2B, + CONTROL_DIAGNOSTIC_MODE=0x2E, + CONTROL_SCREEN_CONTRAST=0x2F, + RING_BELL=0x30, + SET_DIAGNOSTIC_STATUS=0x32, + CLEAR_KEY_COMBINATION_TABLE=0x33, + PERFORM_SOFTWARE_RESET=0x34, + SET_REAL_TIME_CLOCK=0x35, + RECALIBRATE_POINTING_STICK=0x36, + SET_BELL_FREQUENCY=0x37, + SET_INTERNAL_BATTERY_CHARGE_RATE=0x39, + SET_EXTERNAL_BATTERY_CHARGE_RATE=0x3A, + SET_REAL_TIME_CLOCK_ALARM=0x3B, + READ_EEPROM=0x40, + WRITE_EEPROM=0x41, + WRITE_TO_STATUS_DISPLAY=0x42, + DEFINE_SPECIAL_CHARACTER=0x43, + DEFINE_KEY_COMBINATION_ENTRY=0x50, + DEFINE_STRING_TABLE_ENTRY=0x51, + DEFINE_STATUS_SCREEN_DISPLAY=0x52, + PERFORM_EMU_COMMANDS=0x64, + READ_EMU_REGISTER=0x65, + WRITE_EMU_REGISTER=0x66, + READ_EMU_RAM=0x67, + WRITE_EMU_RAM=0x68, + READ_BQ_REGISTER=0x69, + WRITE_BQ_REGISTER=0x6A, + SET_USER_PASSWORD=0x70, + VERIFY_USER_PASSWORD=0x71, + GET_SYSTEM_PASSWORD_KEY=0x72, + VERIFY_SYSTEM_PASSWORD=0x73, + POWER_OFF=0x82, + POWER_RESTART=0x83, +}; + +static struct uctrl_driver { + struct uctrl_regs __iomem *regs; + int irq; + int pending; + struct uctrl_status status; +} *global_driver; + +static void uctrl_get_event_status(struct uctrl_driver *); +static void uctrl_get_external_status(struct uctrl_driver *); + +static long +uctrl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + default: + return -EINVAL; + } + return 0; +} + +static int +uctrl_open(struct inode *inode, struct file *file) +{ + mutex_lock(&uctrl_mutex); + uctrl_get_event_status(global_driver); + uctrl_get_external_status(global_driver); + mutex_unlock(&uctrl_mutex); + return 0; +} + +static irqreturn_t uctrl_interrupt(int irq, void *dev_id) +{ + return IRQ_HANDLED; +} + +static const struct file_operations uctrl_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .unlocked_ioctl = uctrl_ioctl, + .open = uctrl_open, +}; + +static struct miscdevice uctrl_dev = { + UCTRL_MINOR, + "uctrl", + &uctrl_fops +}; + +/* Wait for space to write, then write to it */ +#define WRITEUCTLDATA(value) \ +{ \ + unsigned int i; \ + for (i = 0; i < 10000; i++) { \ + if (UCTRL_STAT_TXNF_STA & sbus_readl(&driver->regs->uctrl_stat)) \ + break; \ + } \ + dprintk(("write data 0x%02x\n", value)); \ + sbus_writel(value, &driver->regs->uctrl_data); \ +} + +/* Wait for something to read, read it, then clear the bit */ +#define READUCTLDATA(value) \ +{ \ + unsigned int i; \ + value = 0; \ + for (i = 0; i < 10000; i++) { \ + if ((UCTRL_STAT_RXNE_STA & sbus_readl(&driver->regs->uctrl_stat)) == 0) \ + break; \ + udelay(1); \ + } \ + value = sbus_readl(&driver->regs->uctrl_data); \ + dprintk(("read data 0x%02x\n", value)); \ + sbus_writel(UCTRL_STAT_RXNE_STA, &driver->regs->uctrl_stat); \ +} + +static void uctrl_do_txn(struct uctrl_driver *driver, struct uctrl_txn *txn) +{ + int stat, incnt, outcnt, bytecnt, intr; + u32 byte; + + stat = sbus_readl(&driver->regs->uctrl_stat); + intr = sbus_readl(&driver->regs->uctrl_intr); + sbus_writel(stat, &driver->regs->uctrl_stat); + + dprintk(("interrupt stat 0x%x int 0x%x\n", stat, intr)); + + incnt = txn->inbits; + outcnt = txn->outbits; + byte = (txn->opcode << 8); + WRITEUCTLDATA(byte); + + bytecnt = 0; + while (incnt > 0) { + byte = (txn->inbuf[bytecnt] << 8); + WRITEUCTLDATA(byte); + incnt--; + bytecnt++; + } + + /* Get the ack */ + READUCTLDATA(byte); + dprintk(("ack was %x\n", (byte >> 8))); + + bytecnt = 0; + while (outcnt > 0) { + READUCTLDATA(byte); + txn->outbuf[bytecnt] = (byte >> 8); + dprintk(("set byte to %02x\n", byte)); + outcnt--; + bytecnt++; + } +} + +static void uctrl_get_event_status(struct uctrl_driver *driver) +{ + struct uctrl_txn txn; + u8 outbits[2]; + + txn.opcode = READ_EVENT_STATUS; + txn.inbits = 0; + txn.outbits = 2; + txn.inbuf = NULL; + txn.outbuf = outbits; + + uctrl_do_txn(driver, &txn); + + dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); + driver->status.event_status = + ((outbits[0] & 0xff) << 8) | (outbits[1] & 0xff); + dprintk(("ev is %x\n", driver->status.event_status)); +} + +static void uctrl_get_external_status(struct uctrl_driver *driver) +{ + struct uctrl_txn txn; + u8 outbits[2]; + int i, v; + + txn.opcode = READ_EXTERNAL_STATUS; + txn.inbits = 0; + txn.outbits = 2; + txn.inbuf = NULL; + txn.outbuf = outbits; + + uctrl_do_txn(driver, &txn); + + dprintk(("bytes %x %x\n", (outbits[0] & 0xff), (outbits[1] & 0xff))); + driver->status.external_status = + ((outbits[0] * 256) + (outbits[1])); + dprintk(("ex is %x\n", driver->status.external_status)); + v = driver->status.external_status; + for (i = 0; v != 0; i++, v >>= 1) { + if (v & 1) { + dprintk(("%s%s", " ", uctrl_extstatus[i])); + } + } + dprintk(("\n")); + +} + +static int uctrl_probe(struct platform_device *op) +{ + struct uctrl_driver *p; + int err = -ENOMEM; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + printk(KERN_ERR "uctrl: Unable to allocate device struct.\n"); + goto out; + } + + p->regs = of_ioremap(&op->resource[0], 0, + resource_size(&op->resource[0]), + "uctrl"); + if (!p->regs) { + printk(KERN_ERR "uctrl: Unable to map registers.\n"); + goto out_free; + } + + p->irq = op->archdata.irqs[0]; + err = request_irq(p->irq, uctrl_interrupt, 0, "uctrl", p); + if (err) { + printk(KERN_ERR "uctrl: Unable to register irq.\n"); + goto out_iounmap; + } + + err = misc_register(&uctrl_dev); + if (err) { + printk(KERN_ERR "uctrl: Unable to register misc device.\n"); + goto out_free_irq; + } + + sbus_writel(UCTRL_INTR_RXNE_REQ|UCTRL_INTR_RXNE_MSK, &p->regs->uctrl_intr); + printk(KERN_INFO "%pOF: uctrl regs[0x%p] (irq %d)\n", + op->dev.of_node, p->regs, p->irq); + uctrl_get_event_status(p); + uctrl_get_external_status(p); + + dev_set_drvdata(&op->dev, p); + global_driver = p; + +out: + return err; + +out_free_irq: + free_irq(p->irq, p); + +out_iounmap: + of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); + +out_free: + kfree(p); + goto out; +} + +static int uctrl_remove(struct platform_device *op) +{ + struct uctrl_driver *p = dev_get_drvdata(&op->dev); + + if (p) { + misc_deregister(&uctrl_dev); + free_irq(p->irq, p); + of_iounmap(&op->resource[0], p->regs, resource_size(&op->resource[0])); + kfree(p); + } + return 0; +} + +static const struct of_device_id uctrl_match[] = { + { + .name = "uctrl", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, uctrl_match); + +static struct platform_driver uctrl_driver = { + .driver = { + .name = "uctrl", + .of_match_table = uctrl_match, + }, + .probe = uctrl_probe, + .remove = uctrl_remove, +}; + + +module_platform_driver(uctrl_driver); + +MODULE_LICENSE("GPL"); |