/* $OpenBSD: smu.c,v 1.27 2015/06/04 18:01:44 kettenis Exp $ */ /* * Copyright (c) 2005 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include int smu_match(struct device *, void *, void *); void smu_attach(struct device *, struct device *, void *); #define SMU_MAXFANS 8 struct smu_fan { u_int8_t reg; u_int16_t min_rpm; u_int16_t max_rpm; u_int16_t unmanaged_rpm; struct ksensor sensor; }; #define SMU_MAXSENSORS 4 struct smu_sensor { u_int8_t reg; struct ksensor sensor; }; struct smu_softc { struct device sc_dev; /* SMU command buffer. */ bus_dma_tag_t sc_dmat; bus_dmamap_t sc_cmdmap; bus_dma_segment_t sc_cmdseg[1]; caddr_t sc_cmd; struct rwlock sc_lock; /* Doorbell and mailbox. */ struct ppc_bus_space sc_mem_bus_space; bus_space_tag_t sc_memt; bus_space_handle_t sc_gpioh; bus_space_handle_t sc_buffh; struct smu_fan sc_fans[SMU_MAXFANS]; int sc_num_fans; struct smu_sensor sc_sensors[SMU_MAXSENSORS]; int sc_num_sensors; struct ksensordev sc_sensordev; u_int16_t sc_cpu_diode_scale; int16_t sc_cpu_diode_offset; u_int16_t sc_cpu_volt_scale; int16_t sc_cpu_volt_offset; u_int16_t sc_cpu_curr_scale; int16_t sc_cpu_curr_offset; u_int16_t sc_slots_pow_scale; int16_t sc_slots_pow_offset; struct i2c_controller sc_i2c_tag; }; struct cfattach smu_ca = { sizeof(struct smu_softc), smu_match, smu_attach }; struct cfdriver smu_cd = { NULL, "smu", DV_DULL, }; /* SMU command */ struct smu_cmd { u_int8_t cmd; u_int8_t len; u_int8_t data[254]; }; #define SMU_CMDSZ sizeof(struct smu_cmd) /* RTC */ #define SMU_RTC 0x8e #define SMU_RTC_SET_DATETIME 0x80 #define SMU_RTC_GET_DATETIME 0x81 /* ADC */ #define SMU_ADC 0xd8 /* Fan control */ #define SMU_FAN 0x4a /* Data partitions */ #define SMU_PARTITION 0x3e #define SMU_PARTITION_LATEST 0x01 #define SMU_PARTITION_BASE 0x02 #define SMU_PARTITION_UPDATE 0x03 /* I2C */ #define SMU_I2C 0x9a #define SMU_I2C_SIMPLE 0x00 #define SMU_I2C_NORMAL 0x01 #define SMU_I2C_COMBINED 0x02 /* Power Management */ #define SMU_POWER 0xaa /* Miscellaneous */ #define SMU_MISC 0xee #define SMU_MISC_GET_DATA 0x02 int smu_intr(void *); int smu_do_cmd(struct smu_softc *, int); int smu_time_read(time_t *); int smu_time_write(time_t); int smu_get_datablock(struct smu_softc *sc, u_int8_t, u_int8_t *, size_t); int smu_fan_set_rpm(struct smu_softc *, struct smu_fan *, u_int16_t); int smu_fan_refresh(struct smu_softc *, struct smu_fan *); int smu_sensor_refresh(struct smu_softc *, struct smu_sensor *); void smu_refresh_sensors(void *); int smu_i2c_acquire_bus(void *, int); void smu_i2c_release_bus(void *, int); int smu_i2c_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, void *buf, size_t, int); void smu_slew_voltage(u_int); int smu_match(struct device *parent, void *cf, void *aux) { struct confargs *ca = aux; if (strcmp(ca->ca_name, "smu") == 0) return (1); return (0); } /* XXX */ extern struct powerpc_bus_dma_tag pci_bus_dma_tag; void smu_attach(struct device *parent, struct device *self, void *aux) { struct smu_softc *sc = (struct smu_softc *)self; struct confargs *ca = aux; struct i2cbus_attach_args iba; struct smu_fan *fan; struct smu_sensor *sensor; int nseg, node; char type[32], loc[32]; u_int32_t reg, intr, gpio, val; #ifndef SMALL_KERNEL u_int8_t data[12]; #endif /* XXX */ sc->sc_mem_bus_space.bus_base = 0x80000000; sc->sc_mem_bus_space.bus_size = 0; sc->sc_mem_bus_space.bus_io = 0; sc->sc_memt = &sc->sc_mem_bus_space; /* Map smu-doorbell gpio. */ if (OF_getprop(ca->ca_node, "platform-doorbell-ack", &node, sizeof node) <= 0 || OF_getprop(node, "reg", ®, sizeof reg) <= 0 || OF_getprop(node, "interrupts", &intr, sizeof intr) <= 0 || OF_getprop(OF_parent(node), "reg", &gpio, sizeof gpio) <= 0) { printf(": cannot find smu-doorbell gpio\n"); return; } if (bus_space_map(sc->sc_memt, gpio + reg, 1, 0, &sc->sc_gpioh)) { printf(": cannot map smu-doorbell gpio\n"); return; } /* XXX Should get this from OF. */ if (bus_space_map(sc->sc_memt, 0x860c, 4, 0, &sc->sc_buffh)) { printf(": cannot map smu-doorbell buffer\n"); return; } /* XXX */ sc->sc_dmat = &pci_bus_dma_tag; /* Allocate and map SMU command buffer. */ if (bus_dmamem_alloc(sc->sc_dmat, SMU_CMDSZ, 0, 0, sc->sc_cmdseg, 1, &nseg, BUS_DMA_NOWAIT)) { printf(": cannot allocate cmd buffer\n"); return; } if (bus_dmamem_map(sc->sc_dmat, sc->sc_cmdseg, nseg, SMU_CMDSZ, &sc->sc_cmd, BUS_DMA_NOWAIT)) { printf(": cannot map cmd buffer\n"); bus_dmamem_free(sc->sc_dmat, sc->sc_cmdseg, 1); return; } if (bus_dmamap_create(sc->sc_dmat, SMU_CMDSZ, 1, SMU_CMDSZ, 0, BUS_DMA_NOWAIT | BUS_DMA_ALLOCNOW, &sc->sc_cmdmap)) { printf(": cannot create cmd dmamap\n"); bus_dmamem_unmap(sc->sc_dmat, sc->sc_cmd, SMU_CMDSZ); bus_dmamem_free(sc->sc_dmat, sc->sc_cmdseg, 1); return; } if (bus_dmamap_load(sc->sc_dmat, sc->sc_cmdmap, sc->sc_cmd, SMU_CMDSZ, NULL, BUS_DMA_NOWAIT)) { printf(": cannot load cmd dmamap\n"); bus_dmamap_destroy(sc->sc_dmat, sc->sc_cmdmap); bus_dmamem_unmap(sc->sc_dmat, sc->sc_cmd, SMU_CMDSZ); bus_dmamem_free(sc->sc_dmat, sc->sc_cmdseg, nseg); return; } rw_init(&sc->sc_lock, sc->sc_dev.dv_xname); /* Establish smu-doorbell interrupt. */ mac_intr_establish(parent, intr, IST_EDGE, IPL_BIO, smu_intr, sc, sc->sc_dev.dv_xname); /* Initialize global variables that control RTC functionality. */ time_read = smu_time_read; time_write = smu_time_write; /* Fans */ node = OF_getnodebyname(ca->ca_node, "rpm-fans"); if (node == 0) node = OF_getnodebyname(ca->ca_node, "fans"); for (node = OF_child(node); node; node = OF_peer(node)) { if (OF_getprop(node, "reg", ®, sizeof reg) <= 0 || OF_getprop(node, "device_type", type, sizeof type) <= 0) continue; if (strcmp(type, "fan-rpm-control") != 0) { printf(": unsupported fan type: %s\n", type); return; } if (sc->sc_num_fans >= SMU_MAXFANS) { printf(": too many fans\n"); return; } fan = &sc->sc_fans[sc->sc_num_fans++]; fan->sensor.type = SENSOR_FANRPM; fan->sensor.flags = SENSOR_FINVALID; fan->reg = reg; if (OF_getprop(node, "min-value", &val, sizeof val) <= 0) val = 0; fan->min_rpm = val; if (OF_getprop(node, "max-value", &val, sizeof val) <= 0) val = 0xffff; fan->max_rpm = val; if (OF_getprop(node, "unmanage-value", &val, sizeof val) <= 0) val = fan->max_rpm; fan->unmanaged_rpm = val; if (OF_getprop(node, "location", loc, sizeof loc) <= 0) strlcpy(loc, "Unknown", sizeof loc); strlcpy(fan->sensor.desc, loc, sizeof sensor->sensor.desc); /* Start running fans at their "unmanaged" speed. */ smu_fan_set_rpm(sc, fan, fan->unmanaged_rpm); #ifndef SMALL_KERNEL sensor_attach(&sc->sc_sensordev, &fan->sensor); #endif } /* * Bail out if we didn't find any fans. If we don't set the * fans to a safe speed, but tickle the SMU periodically by * reading sensors, the fans will never spin up and the * machine might overheat. */ if (sc->sc_num_fans == 0) { printf(": no fans\n"); return; } #ifndef SMALL_KERNEL /* Sensors */ node = OF_getnodebyname(ca->ca_node, "sensors"); for (node = OF_child(node); node; node = OF_peer(node)) { if (OF_getprop(node, "reg", &val, sizeof val) <= 0 || OF_getprop(node, "device_type", type, sizeof type) <= 0) continue; if (sc->sc_num_sensors >= SMU_MAXSENSORS) { printf(": too many sensors\n"); return; } sensor = &sc->sc_sensors[sc->sc_num_sensors++]; sensor->sensor.flags = SENSOR_FINVALID; sensor->reg = val; if (strcmp(type, "current-sensor") == 0) { sensor->sensor.type = SENSOR_AMPS; } else if (strcmp(type, "temp-sensor") == 0) { sensor->sensor.type = SENSOR_TEMP; } else if (strcmp(type, "voltage-sensor") == 0) { sensor->sensor.type = SENSOR_VOLTS_DC; } else if (strcmp(type, "power-sensor") == 0) { sensor->sensor.type = SENSOR_WATTS; } else { sensor->sensor.type = SENSOR_INTEGER; } if (OF_getprop(node, "location", loc, sizeof loc) <= 0) strlcpy(loc, "Unknown", sizeof loc); strlcpy(sensor->sensor.desc, loc, sizeof sensor->sensor.desc); sensor_attach(&sc->sc_sensordev, &sensor->sensor); } /* Register sensor device with sysctl */ strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, sizeof(sc->sc_sensordev.xname)); sensordev_install(&sc->sc_sensordev); /* CPU temperature diode calibration */ smu_get_datablock(sc, 0x18, data, sizeof data); sc->sc_cpu_diode_scale = (data[4] << 8) + data[5]; sc->sc_cpu_diode_offset = (data[6] << 8) + data[7]; /* CPU power (voltage and current) calibration */ smu_get_datablock(sc, 0x21, data, sizeof data); sc->sc_cpu_volt_scale = (data[4] << 8) + data[5]; sc->sc_cpu_volt_offset = (data[6] << 8) + data[7]; sc->sc_cpu_curr_scale = (data[8] << 8) + data[9]; sc->sc_cpu_curr_offset = (data[10] << 8) + data[11]; /* Slots power calibration */ smu_get_datablock(sc, 0x78, data, sizeof data); sc->sc_slots_pow_scale = (data[4] << 8) + data[5]; sc->sc_slots_pow_offset = (data[6] << 8) + data[7]; sensor_task_register(sc, smu_refresh_sensors, 5); #endif /* !SMALL_KERNEL */ printf("\n"); ppc64_slew_voltage = smu_slew_voltage; sc->sc_i2c_tag.ic_cookie = sc; sc->sc_i2c_tag.ic_acquire_bus = smu_i2c_acquire_bus; sc->sc_i2c_tag.ic_release_bus = smu_i2c_release_bus; sc->sc_i2c_tag.ic_exec = smu_i2c_exec; /* * Early versions of the SMU have the i2c bus node directly * below the "smu" node, while later models have an * intermediate "smu-i2c-control" node. */ node = OF_getnodebyname(ca->ca_node, "smu-i2c-control"); if (node) node = OF_child(node); else node = OF_getnodebyname(ca->ca_node, "i2c"); bzero(&iba, sizeof iba); iba.iba_name = "iic"; iba.iba_tag = &sc->sc_i2c_tag; iba.iba_bus_scan = maciic_scan; iba.iba_bus_scan_arg = &node; config_found(&sc->sc_dev, &iba, NULL); } int smu_intr(void *arg) { wakeup(arg); return 1; } int smu_do_cmd(struct smu_softc *sc, int timo) { struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; u_int8_t gpio, ack = ~cmd->cmd; int error; /* Write to mailbox. */ bus_space_write_4(sc->sc_memt, sc->sc_buffh, 0, sc->sc_cmdmap->dm_segs->ds_addr); /* Flush to RAM. */ asm volatile ("dcbst 0,%0; sync" :: "r"(sc->sc_cmd): "memory"); /* Ring doorbell. */ bus_space_write_1(sc->sc_memt, sc->sc_gpioh, 0, GPIO_DDR_OUTPUT); do { error = tsleep(sc, PWAIT, "smu", (timo * hz) / 1000); if (error) return (error); gpio = bus_space_read_1(sc->sc_memt, sc->sc_gpioh, 0); } while (!(gpio & (GPIO_DATA))); /* CPU might have brought back the cache line. */ asm volatile ("dcbf 0,%0; sync" :: "r"(sc->sc_cmd) : "memory"); if (cmd->cmd != ack) return (EIO); return (0); } int smu_time_read(time_t *secs) { struct smu_softc *sc = smu_cd.cd_devs[0]; struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; struct clock_ymdhms dt; int error; rw_enter_write(&sc->sc_lock); cmd->cmd = SMU_RTC; cmd->len = 1; cmd->data[0] = SMU_RTC_GET_DATETIME; error = smu_do_cmd(sc, 800); if (error) { rw_exit_write(&sc->sc_lock); *secs = 0; return (error); } dt.dt_year = 2000 + FROMBCD(cmd->data[6]); dt.dt_mon = FROMBCD(cmd->data[5]); dt.dt_day = FROMBCD(cmd->data[4]); dt.dt_hour = FROMBCD(cmd->data[2]); dt.dt_min = FROMBCD(cmd->data[1]); dt.dt_sec = FROMBCD(cmd->data[0]); rw_exit_write(&sc->sc_lock); *secs = clock_ymdhms_to_secs(&dt); return (0); } int smu_time_write(time_t secs) { struct smu_softc *sc = smu_cd.cd_devs[0]; struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; struct clock_ymdhms dt; int error; clock_secs_to_ymdhms(secs, &dt); rw_enter_write(&sc->sc_lock); cmd->cmd = SMU_RTC; cmd->len = 8; cmd->data[0] = SMU_RTC_SET_DATETIME; cmd->data[1] = TOBCD(dt.dt_sec); cmd->data[2] = TOBCD(dt.dt_min); cmd->data[3] = TOBCD(dt.dt_hour); cmd->data[4] = TOBCD(dt.dt_wday); cmd->data[5] = TOBCD(dt.dt_day); cmd->data[6] = TOBCD(dt.dt_mon); cmd->data[7] = TOBCD(dt.dt_year - 2000); error = smu_do_cmd(sc, 800); rw_exit_write(&sc->sc_lock); return (error); } int smu_get_datablock(struct smu_softc *sc, u_int8_t id, u_int8_t *buf, size_t len) { struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; u_int8_t addr[4]; int error; cmd->cmd = SMU_PARTITION; cmd->len = 2; cmd->data[0] = SMU_PARTITION_LATEST; cmd->data[1] = id; error = smu_do_cmd(sc, 800); if (error) return (error); addr[0] = 0x00; addr[1] = 0x00; addr[2] = cmd->data[0]; addr[3] = cmd->data[1]; cmd->cmd = SMU_MISC; cmd->len = 7; cmd->data[0] = SMU_MISC_GET_DATA; cmd->data[1] = sizeof(u_int32_t); cmd->data[2] = addr[0]; cmd->data[3] = addr[1]; cmd->data[4] = addr[2]; cmd->data[5] = addr[3]; cmd->data[6] = len; error = smu_do_cmd(sc, 800); if (error) return (error); memcpy(buf, cmd->data, len); return (0); } int smu_fan_set_rpm(struct smu_softc *sc, struct smu_fan *fan, u_int16_t rpm) { struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; /* * On the PowerMac8,2 this command expects the requested fan * speed at a different location in the command block than on * the PowerMac8,1. We simply store the value at both * locations. */ cmd->cmd = SMU_FAN; cmd->len = 14; cmd->data[0] = 0x00; /* fan-rpm-control */ cmd->data[1] = 0x01 << fan->reg; cmd->data[2] = cmd->data[2 + fan->reg * 2] = (rpm >> 8) & 0xff; cmd->data[3] = cmd->data[3 + fan->reg * 2] = (rpm & 0xff); return smu_do_cmd(sc, 800); } int smu_fan_refresh(struct smu_softc *sc, struct smu_fan *fan) { struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; int error; cmd->cmd = SMU_FAN; cmd->len = 2; cmd->data[0] = 0x01; /* fan-rpm-control */ cmd->data[1] = 0x01 << fan->reg; error = smu_do_cmd(sc, 800); if (error) { fan->sensor.flags = SENSOR_FINVALID; return (error); } fan->sensor.value = (cmd->data[1] << 8) + cmd->data[2]; fan->sensor.flags = 0; return (0); } int smu_sensor_refresh(struct smu_softc *sc, struct smu_sensor *sensor) { struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; int64_t value; int error; cmd->cmd = SMU_ADC; cmd->len = 1; cmd->data[0] = sensor->reg; error = smu_do_cmd(sc, 800); if (error) { sensor->sensor.flags = SENSOR_FINVALID; return (error); } value = (cmd->data[0] << 8) + cmd->data[1]; switch (sensor->sensor.type) { case SENSOR_TEMP: value *= sc->sc_cpu_diode_scale; value >>= 3; value += ((int64_t)sc->sc_cpu_diode_offset) << 9; value <<= 1; /* Convert from 16.16 fixed point degC into muK. */ value *= 15625; value /= 1024; value += 273150000; break; case SENSOR_VOLTS_DC: value *= sc->sc_cpu_volt_scale; value += sc->sc_cpu_volt_offset; value <<= 4; /* Convert from 16.16 fixed point V into muV. */ value *= 15625; value /= 1024; break; case SENSOR_AMPS: value *= sc->sc_cpu_curr_scale; value += sc->sc_cpu_curr_offset; value <<= 4; /* Convert from 16.16 fixed point A into muA. */ value *= 15625; value /= 1024; break; case SENSOR_WATTS: value *= sc->sc_slots_pow_scale; value += sc->sc_slots_pow_offset; value <<= 4; /* Convert from 16.16 fixed point W into muW. */ value *= 15625; value /= 1024; break; default: break; } sensor->sensor.value = value; sensor->sensor.flags = 0; return (0); } void smu_refresh_sensors(void *arg) { struct smu_softc *sc = arg; int i; rw_enter_write(&sc->sc_lock); for (i = 0; i < sc->sc_num_sensors; i++) smu_sensor_refresh(sc, &sc->sc_sensors[i]); for (i = 0; i < sc->sc_num_fans; i++) smu_fan_refresh(sc, &sc->sc_fans[i]); rw_exit_write(&sc->sc_lock); } int smu_i2c_acquire_bus(void *cookie, int flags) { struct smu_softc *sc = cookie; if (flags & I2C_F_POLL) return (0); return (rw_enter(&sc->sc_lock, RW_WRITE)); } void smu_i2c_release_bus(void *cookie, int flags) { struct smu_softc *sc = cookie; if (flags & I2C_F_POLL) return; rw_exit(&sc->sc_lock); } int smu_i2c_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmdbuf, size_t cmdlen, void *buf, size_t len, int flags) { struct smu_softc *sc = cookie; struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; u_int8_t smu_op = SMU_I2C_NORMAL; int error, retries = 10; if (!I2C_OP_STOP_P(op) || cmdlen > 3 || len > 5) return (EINVAL); if(cmdlen == 0) smu_op = SMU_I2C_SIMPLE; else if (I2C_OP_READ_P(op)) smu_op = SMU_I2C_COMBINED; cmd->cmd = SMU_I2C; cmd->len = 9 + len; cmd->data[0] = 0xb; cmd->data[1] = smu_op; cmd->data[2] = addr << 1; cmd->data[3] = cmdlen; memcpy (&cmd->data[4], cmdbuf, cmdlen); cmd->data[7] = addr << 1 | I2C_OP_READ_P(op); cmd->data[8] = len; memcpy(&cmd->data[9], buf, len); error = smu_do_cmd(sc, 250); if (error) return error; while (retries--) { cmd->cmd = SMU_I2C; cmd->len = 1; cmd->data[0] = 0; memset(&cmd->data[1], 0xff, len); error = smu_do_cmd(sc, 250); if (error) return error; if ((cmd->data[0] & 0x80) == 0) break; if (cmd->data[0] == 0xfd) break; DELAY(15 * 1000); } if (cmd->data[0] & 0x80) return (EIO); if (I2C_OP_READ_P(op)) memcpy(buf, &cmd->data[1], len); return (0); } void smu_slew_voltage(u_int freq_scale) { struct smu_softc *sc = smu_cd.cd_devs[0]; struct smu_cmd *cmd = (struct smu_cmd *)sc->sc_cmd; rw_enter_write(&sc->sc_lock); cmd->cmd = SMU_POWER; cmd->len = 8; memcpy(cmd->data, "VSLEW", 5); cmd->data[5] = 0xff; cmd->data[6] = 1; cmd->data[7] = freq_scale; smu_do_cmd(sc, 250); rw_exit_write(&sc->sc_lock); }