Index: sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c =================================================================== --- /dev/null +++ sys/arm/broadcom/bcm2835/bcm2835_cpufreq.c @@ -0,0 +1,1836 @@ +/*- + * Copyright (C) 2013-2014 Daisuke Aoyama + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include "cpufreq_if.h" +#include "mbox_if.h" + +#ifdef DEBUG +#define DPRINTF(fmt, ...) do { \ + printf("%s:%u: ", __func__, __LINE__); \ + printf(fmt, ##__VA_ARGS__); \ +} while (0) +#else +#define DPRINTF(fmt, ...) +#endif + +#define HZ2MHZ(freq) ((freq) / (1000 * 1000)) +#define MHZ2HZ(freq) ((freq) * (1000 * 1000)) +#define OFFSET2MVOLT(val) (1200 + ((val) * 25)) +#define MVOLT2OFFSET(val) (((val) - 1200) / 25) +#define RAW2K(temp) (((temp) + 273150) / 1000) +#define K2RAW(temp) (((temp) * 1000) - 273150) + +#define DEFAULT_ARM_FREQUENCY 700 +#define DEFAULT_CORE_FREQUENCY 250 +#define DEFAULT_SDRAM_FREQUENCY 400 +#define DEFAULT_LOWEST_FREQ 300 +#define TRANSITION_LATENCY 1000 +#define MIN_OVER_VOLTAGE -16 +#define MAX_OVER_VOLTAGE 6 +#define MSG_ERROR -999999999 +#define MHZSTEP 100 +#define HZSTEP (MHZ2HZ(MHZSTEP)) + +#define VC_LOCK(sc) do { \ + sema_wait(&vc_sema); \ + } while (0) +#define VC_UNLOCK(sc) do { \ + sema_post(&vc_sema); \ + } while (0) + +/* ARM->VC mailbox property semaphore */ +static struct sema vc_sema; + +static struct sysctl_ctx_list bcm2835_sysctl_ctx; + +struct bcm2835_cpufreq_softc { + device_t dev; + int arm_max_freq; + int arm_min_freq; + int core_max_freq; + int core_min_freq; + int sdram_max_freq; + int sdram_min_freq; + int max_voltage_core; + int min_voltage_core; + + /* the values written in mbox */ + int voltage_core; + int voltage_sdram; + int voltage_sdram_c; + int voltage_sdram_i; + int voltage_sdram_p; + int turbo_mode; + + /* mbox buffer (physical address) */ + bus_dma_tag_t dma_tag; + bus_dmamap_t dma_map; + bus_size_t dma_size; + void *dma_buf; + bus_addr_t dma_phys; + + /* initial hook for waiting mbox intr */ + struct intr_config_hook init_hook; +}; + +static int cpufreq_verbose = 0; +TUNABLE_INT("hw.bcm2835.cpufreq.verbose", &cpufreq_verbose); +static int cpufreq_lowest_freq = DEFAULT_LOWEST_FREQ; +TUNABLE_INT("hw.bcm2835.cpufreq.lowest_freq", &cpufreq_lowest_freq); + +#ifdef DEBUG +static void +bcm2835_dump(const void *data, int len) +{ + const uint8_t *p = (const uint8_t*)data; + int i; + + printf("dump @ %p:\n", data); + for (i = 0; i < len; i++) { + printf("%2.2x ", p[i]); + if ((i % 4) == 3) + printf(" "); + if ((i % 16) == 15) + printf("\n"); + } + printf("\n"); +} +#endif + +static int +bcm2835_mbox_call_prop(struct bcm2835_cpufreq_softc *sc) +{ + struct bcm2835_mbox_hdr *msg = (struct bcm2835_mbox_hdr *)sc->dma_buf; + struct bcm2835_mbox_tag_hdr *tag, *last; + uint8_t *up; + device_t mbox; + size_t hdr_size; + int idx; + int err; + + /* + * For multiple calls, locking is not here. The caller must have + * VC semaphore. + */ + + /* get mbox device */ + mbox = devclass_get_device(devclass_find("mbox"), 0); + if (mbox == NULL) { + device_printf(sc->dev, "can't find mbox\n"); + return (-1); + } + + /* go mailbox property */ +#ifdef PROP_DEBUG + bcm2835_dump(msg, 64); +#endif + bus_dmamap_sync(sc->dma_tag, sc->dma_map, + BUS_DMASYNC_PREWRITE | BUS_DMASYNC_PREREAD); + MBOX_WRITE(mbox, BCM2835_MBOX_CHAN_PROP, (uint32_t)sc->dma_phys); + MBOX_READ(mbox, BCM2835_MBOX_CHAN_PROP, &err); + bus_dmamap_sync(sc->dma_tag, sc->dma_map, BUS_DMASYNC_POSTREAD); +#ifdef PROP_DEBUG + bcm2835_dump(msg, 64); +#endif + + /* check response code */ + if (msg->code != BCM2835_MBOX_CODE_RESP_SUCCESS) { + device_printf(sc->dev, "mbox response error\n"); + return (-1); + } + + /* tag = first tag */ + up = (uint8_t *)msg; + hdr_size = sizeof(struct bcm2835_mbox_hdr); + tag = (struct bcm2835_mbox_tag_hdr *)(up + hdr_size); + /* last = end of buffer specified by header */ + last = (struct bcm2835_mbox_tag_hdr *)(up + msg->buf_size); + + /* loop unitl end tag (=0x0) */ + hdr_size = sizeof(struct bcm2835_mbox_tag_hdr); + for (idx = 0; tag->tag != 0; idx++) { + if ((tag->val_len & BCM2835_MBOX_TAG_VAL_LEN_RESPONSE) == 0) { + device_printf(sc->dev, "tag%d response error\n", idx); + return (-1); + } + /* clear response bit */ + tag->val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + + /* get next tag */ + up = (uint8_t *)tag; + tag = (struct bcm2835_mbox_tag_hdr *)(up + hdr_size + + tag->val_buf_size); + + /* check buffer size of header */ + if (tag > last) { + device_printf(sc->dev, "mbox buffer size error\n"); + return (-1); + } + } + + return (0); +} + +static int +bcm2835_cpufreq_get_clock_rate(struct bcm2835_cpufreq_softc *sc, + uint32_t clock_id) +{ + struct msg_get_clock_rate *msg; + int rate; + int err; + + /* + * Get clock rate + * Tag: 0x00030002 + * Request: + * Length: 4 + * Value: + * u32: clock id + * Response: + * Length: 8 + * Value: + * u32: clock id + * u32: rate (in Hz) + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_clock_rate *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_CLOCK_RATE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.clock_id = clock_id; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get clock rate (id=%u)\n", + clock_id); + return (MSG_ERROR); + } + + /* result (Hz) */ + rate = (int)msg->body.resp.rate_hz; + DPRINTF("clock = %d(Hz)\n", rate); + return (rate); +} + +static int +bcm2835_cpufreq_get_max_clock_rate(struct bcm2835_cpufreq_softc *sc, + uint32_t clock_id) +{ + struct msg_get_max_clock_rate *msg; + int rate; + int err; + + /* + * Get max clock rate + * Tag: 0x00030004 + * Request: + * Length: 4 + * Value: + * u32: clock id + * Response: + * Length: 8 + * Value: + * u32: clock id + * u32: rate (in Hz) + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_max_clock_rate *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_MAX_CLOCK_RATE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.clock_id = clock_id; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get max clock rate (id=%u)\n", + clock_id); + return (MSG_ERROR); + } + + /* result (Hz) */ + rate = (int)msg->body.resp.rate_hz; + DPRINTF("clock = %d(Hz)\n", rate); + return (rate); +} + +static int +bcm2835_cpufreq_get_min_clock_rate(struct bcm2835_cpufreq_softc *sc, + uint32_t clock_id) +{ + struct msg_get_min_clock_rate *msg; + int rate; + int err; + + /* + * Get min clock rate + * Tag: 0x00030007 + * Request: + * Length: 4 + * Value: + * u32: clock id + * Response: + * Length: 8 + * Value: + * u32: clock id + * u32: rate (in Hz) + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_min_clock_rate *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_MIN_CLOCK_RATE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.clock_id = clock_id; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get min clock rate (id=%u)\n", + clock_id); + return (MSG_ERROR); + } + + /* result (Hz) */ + rate = (int)msg->body.resp.rate_hz; + DPRINTF("clock = %d(Hz)\n", rate); + return (rate); +} + +static int +bcm2835_cpufreq_set_clock_rate(struct bcm2835_cpufreq_softc *sc, + uint32_t clock_id, uint32_t rate_hz) +{ + struct msg_set_clock_rate *msg; + int rate; + int err; + + /* + * Set clock rate + * Tag: 0x00038002 + * Request: + * Length: 8 + * Value: + * u32: clock id + * u32: rate (in Hz) + * Response: + * Length: 8 + * Value: + * u32: clock id + * u32: rate (in Hz) + */ + + /* using DMA buffer for VC */ + msg = (struct msg_set_clock_rate *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_SET_CLOCK_RATE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.clock_id = clock_id; + msg->body.req.rate_hz = rate_hz; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't set clock rate (id=%u)\n", + clock_id); + return (MSG_ERROR); + } + + /* result (Hz) */ + rate = (int)msg->body.resp.rate_hz; + DPRINTF("clock = %d(Hz)\n", rate); + return (rate); +} + +static int +bcm2835_cpufreq_get_turbo(struct bcm2835_cpufreq_softc *sc) +{ + struct msg_get_turbo *msg; + int level; + int err; + + /* + * Get turbo + * Tag: 0x00030009 + * Request: + * Length: 4 + * Value: + * u32: id + * Response: + * Length: 8 + * Value: + * u32: id + * u32: level + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_turbo *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_TURBO; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.id = 0; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get turbo\n"); + return (MSG_ERROR); + } + + /* result 0=non-turbo, 1=turbo */ + level = (int)msg->body.resp.level; + DPRINTF("level = %d\n", level); + return (level); +} + +static int +bcm2835_cpufreq_set_turbo(struct bcm2835_cpufreq_softc *sc, uint32_t level) +{ + struct msg_set_turbo *msg; + int value; + int err; + + /* + * Set turbo + * Tag: 0x00038009 + * Request: + * Length: 8 + * Value: + * u32: id + * u32: level + * Response: + * Length: 8 + * Value: + * u32: id + * u32: level + */ + + /* using DMA buffer for VC */ + msg = (struct msg_set_turbo *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* replace unknown value to OFF */ + if (level != BCM2835_MBOX_TURBO_ON && level != BCM2835_MBOX_TURBO_OFF) + level = BCM2835_MBOX_TURBO_OFF; + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_SET_TURBO; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.id = 0; + msg->body.req.level = level; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't set turbo\n"); + return (MSG_ERROR); + } + + /* result 0=non-turbo, 1=turbo */ + value = (int)msg->body.resp.level; + DPRINTF("level = %d\n", value); + return (value); +} + +static int +bcm2835_cpufreq_get_voltage(struct bcm2835_cpufreq_softc *sc, + uint32_t voltage_id) +{ + struct msg_get_voltage *msg; + int value; + int err; + + /* + * Get voltage + * Tag: 0x00030003 + * Request: + * Length: 4 + * Value: + * u32: voltage id + * Response: + * Length: 8 + * Value: + * u32: voltage id + * u32: value (offset from 1.2V in units of 0.025V) + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_voltage *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_VOLTAGE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.voltage_id = voltage_id; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get voltage\n"); + return (MSG_ERROR); + } + + /* result (offset from 1.2V) */ + value = (int)msg->body.resp.value; + DPRINTF("value = %d\n", value); + return (value); +} + +static int +bcm2835_cpufreq_get_max_voltage(struct bcm2835_cpufreq_softc *sc, + uint32_t voltage_id) +{ + struct msg_get_max_voltage *msg; + int value; + int err; + + /* + * Get voltage + * Tag: 0x00030005 + * Request: + * Length: 4 + * Value: + * u32: voltage id + * Response: + * Length: 8 + * Value: + * u32: voltage id + * u32: value (offset from 1.2V in units of 0.025V) + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_max_voltage *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_MAX_VOLTAGE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.voltage_id = voltage_id; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get max voltage\n"); + return (MSG_ERROR); + } + + /* result (offset from 1.2V) */ + value = (int)msg->body.resp.value; + DPRINTF("value = %d\n", value); + return (value); +} +static int +bcm2835_cpufreq_get_min_voltage(struct bcm2835_cpufreq_softc *sc, + uint32_t voltage_id) +{ + struct msg_get_min_voltage *msg; + int value; + int err; + + /* + * Get voltage + * Tag: 0x00030008 + * Request: + * Length: 4 + * Value: + * u32: voltage id + * Response: + * Length: 8 + * Value: + * u32: voltage id + * u32: value (offset from 1.2V in units of 0.025V) + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_min_voltage *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_MIN_VOLTAGE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.voltage_id = voltage_id; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get min voltage\n"); + return (MSG_ERROR); + } + + /* result (offset from 1.2V) */ + value = (int)msg->body.resp.value; + DPRINTF("value = %d\n", value); + return (value); +} + +static int +bcm2835_cpufreq_set_voltage(struct bcm2835_cpufreq_softc *sc, + uint32_t voltage_id, int32_t value) +{ + struct msg_set_voltage *msg; + int err; + + /* + * Set voltage + * Tag: 0x00038003 + * Request: + * Length: 4 + * Value: + * u32: voltage id + * u32: value (offset from 1.2V in units of 0.025V) + * Response: + * Length: 8 + * Value: + * u32: voltage id + * u32: value (offset from 1.2V in units of 0.025V) + */ + + /* + * over_voltage: + * 0 (1.2 V). Values above 6 are only allowed when force_turbo or + * current_limit_override are specified (which set the warranty bit). + */ + if (value > MAX_OVER_VOLTAGE || value < MIN_OVER_VOLTAGE) { + /* currently not supported */ + device_printf(sc->dev, "not supported voltage: %d\n", value); + return (MSG_ERROR); + } + + /* using DMA buffer for VC */ + msg = (struct msg_set_voltage *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_SET_VOLTAGE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.voltage_id = voltage_id; + msg->body.req.value = (uint32_t)value; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't set voltage\n"); + return (MSG_ERROR); + } + + /* result (offset from 1.2V) */ + value = (int)msg->body.resp.value; + DPRINTF("value = %d\n", value); + return (value); +} + +static int +bcm2835_cpufreq_get_temperature(struct bcm2835_cpufreq_softc *sc) +{ + struct msg_get_temperature *msg; + int value; + int err; + + /* + * Get temperature + * Tag: 0x00030006 + * Request: + * Length: 4 + * Value: + * u32: temperature id + * Response: + * Length: 8 + * Value: + * u32: temperature id + * u32: value + */ + + /* using DMA buffer for VC */ + msg = (struct msg_get_temperature *)sc->dma_buf; + if (sizeof(*msg) > sc->dma_size) { + device_printf(sc->dev, "DMA size overflow (%zu>%lu)\n", + sizeof(*msg), sc->dma_size); + return (MSG_ERROR); + } + + /* setup single tag buffer */ + memset(msg, 0, sizeof(*msg)); + msg->hdr.buf_size = sizeof(*msg); + msg->hdr.code = BCM2835_MBOX_CODE_REQ; + msg->tag_hdr.tag = BCM2835_MBOX_TAG_GET_TEMPERATURE; + msg->tag_hdr.val_buf_size = sizeof(msg->body); + msg->tag_hdr.val_len = sizeof(msg->body.req); + msg->tag_hdr.val_len &= ~BCM2835_MBOX_TAG_VAL_LEN_RESPONSE; + msg->body.req.temperature_id = 0; + msg->end_tag = 0; + + /* call mailbox property */ + err = bcm2835_mbox_call_prop(sc); + if (err) { + device_printf(sc->dev, "can't get temperature\n"); + return (MSG_ERROR); + } + + /* result (temperature of degree C) */ + value = (int)msg->body.resp.value; + DPRINTF("value = %d\n", value); + return (value); +} + + + +static int +sysctl_bcm2835_cpufreq_arm_freq(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + VC_LOCK(sc); + err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM, + val); + VC_UNLOCK(sc); + if (err == MSG_ERROR) { + device_printf(sc->dev, "set clock arm_freq error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_core_freq(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + VC_LOCK(sc); + err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, + val); + if (err == MSG_ERROR) { + VC_UNLOCK(sc); + device_printf(sc->dev, "set clock core_freq error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + /* XXX write the core clock twice */ + err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, + val); + if (err == MSG_ERROR) { + VC_UNLOCK(sc); + device_printf(sc->dev, "set clock core_freq error\n"); + return (EIO); + } + VC_UNLOCK(sc); + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_sdram_freq(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + VC_LOCK(sc); + err = bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_SDRAM, + val); + VC_UNLOCK(sc); + if (err == MSG_ERROR) { + device_printf(sc->dev, "set clock sdram_freq error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_turbo(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_turbo(sc); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + if (val > 0) + sc->turbo_mode = BCM2835_MBOX_TURBO_ON; + else + sc->turbo_mode = BCM2835_MBOX_TURBO_OFF; + + VC_LOCK(sc); + err = bcm2835_cpufreq_set_turbo(sc, sc->turbo_mode); + VC_UNLOCK(sc); + if (err == MSG_ERROR) { + device_printf(sc->dev, "set turbo error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_voltage_core(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) + return (EINVAL); + sc->voltage_core = val; + + VC_LOCK(sc); + err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_CORE, + sc->voltage_core); + VC_UNLOCK(sc); + if (err == MSG_ERROR) { + device_printf(sc->dev, "set voltage core error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_voltage_sdram_c(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) + return (EINVAL); + sc->voltage_sdram_c = val; + + VC_LOCK(sc); + err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C, + sc->voltage_sdram_c); + VC_UNLOCK(sc); + if (err == MSG_ERROR) { + device_printf(sc->dev, "set voltage sdram_c error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_voltage_sdram_i(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) + return (EINVAL); + sc->voltage_sdram_i = val; + + VC_LOCK(sc); + err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I, + sc->voltage_sdram_i); + VC_UNLOCK(sc); + if (err == MSG_ERROR) { + device_printf(sc->dev, "set voltage sdram_i error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_voltage_sdram_p(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) + return (EINVAL); + sc->voltage_sdram_p = val; + + VC_LOCK(sc); + err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P, + sc->voltage_sdram_p); + VC_UNLOCK(sc); + if (err == MSG_ERROR) { + device_printf(sc->dev, "set voltage sdram_p error\n"); + return (EIO); + } + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_voltage_sdram(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* multiple write only */ + if (!req->newptr) + return (EINVAL); + val = 0; + err = sysctl_handle_int(oidp, &val, 0, req); + if (err) + return (err); + + /* write request */ + if (val > MAX_OVER_VOLTAGE || val < MIN_OVER_VOLTAGE) + return (EINVAL); + sc->voltage_sdram = val; + + VC_LOCK(sc); + err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_C, + val); + if (err == MSG_ERROR) { + VC_UNLOCK(sc); + device_printf(sc->dev, "set voltage sdram_c error\n"); + return (EIO); + } + err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_I, + val); + if (err == MSG_ERROR) { + VC_UNLOCK(sc); + device_printf(sc->dev, "set voltage sdram_i error\n"); + return (EIO); + } + err = bcm2835_cpufreq_set_voltage(sc, BCM2835_MBOX_VOLTAGE_ID_SDRAM_P, + val); + if (err == MSG_ERROR) { + VC_UNLOCK(sc); + device_printf(sc->dev, "set voltage sdram_p error\n"); + return (EIO); + } + VC_UNLOCK(sc); + DELAY(TRANSITION_LATENCY); + + return (0); +} + +static int +sysctl_bcm2835_cpufreq_temperature(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_temperature(sc); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + return (EINVAL); +} + +static int +sysctl_bcm2835_devcpu_temperature(SYSCTL_HANDLER_ARGS) +{ + struct bcm2835_cpufreq_softc *sc = arg1; + int val; + int err; + + /* get realtime value */ + VC_LOCK(sc); + val = bcm2835_cpufreq_get_temperature(sc); + VC_UNLOCK(sc); + if (val == MSG_ERROR) + return (EIO); + + /* 1/1000 celsius (raw) to 1/10 kelvin */ + val = RAW2K(val) * 10; + + err = sysctl_handle_int(oidp, &val, 0, req); + if (err || !req->newptr) /* error || read request */ + return (err); + + /* write request */ + return (EINVAL); +} + + +static void +bcm2835_cpufreq_init(void *arg) +{ + struct bcm2835_cpufreq_softc *sc = arg; + struct sysctl_ctx_list *ctx; + device_t cpu; + int arm_freq, core_freq, sdram_freq; + int arm_max_freq, arm_min_freq, core_max_freq, core_min_freq; + int sdram_max_freq, sdram_min_freq; + int voltage_core, voltage_sdram_c, voltage_sdram_i, voltage_sdram_p; + int max_voltage_core, min_voltage_core; + int max_voltage_sdram_c, min_voltage_sdram_c; + int max_voltage_sdram_i, min_voltage_sdram_i; + int max_voltage_sdram_p, min_voltage_sdram_p; + int turbo, temperature; + + VC_LOCK(sc); + + /* current clock */ + arm_freq = bcm2835_cpufreq_get_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_ARM); + core_freq = bcm2835_cpufreq_get_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE); + sdram_freq = bcm2835_cpufreq_get_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_SDRAM); + + /* max/min clock */ + arm_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_ARM); + arm_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_ARM); + core_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE); + core_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE); + sdram_max_freq = bcm2835_cpufreq_get_max_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_SDRAM); + sdram_min_freq = bcm2835_cpufreq_get_min_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_SDRAM); + + /* turbo mode */ + turbo = bcm2835_cpufreq_get_turbo(sc); + if (turbo > 0) + sc->turbo_mode = BCM2835_MBOX_TURBO_ON; + else + sc->turbo_mode = BCM2835_MBOX_TURBO_OFF; + + /* voltage */ + voltage_core = bcm2835_cpufreq_get_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_CORE); + voltage_sdram_c = bcm2835_cpufreq_get_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); + voltage_sdram_i = bcm2835_cpufreq_get_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); + voltage_sdram_p = bcm2835_cpufreq_get_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); + + /* current values (offset from 1.2V) */ + sc->voltage_core = voltage_core; + sc->voltage_sdram = voltage_sdram_c; + sc->voltage_sdram_c = voltage_sdram_c; + sc->voltage_sdram_i = voltage_sdram_i; + sc->voltage_sdram_p = voltage_sdram_p; + + /* max/min voltage */ + max_voltage_core = bcm2835_cpufreq_get_max_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_CORE); + min_voltage_core = bcm2835_cpufreq_get_min_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_CORE); + max_voltage_sdram_c = bcm2835_cpufreq_get_max_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); + max_voltage_sdram_i = bcm2835_cpufreq_get_max_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); + max_voltage_sdram_p = bcm2835_cpufreq_get_max_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); + min_voltage_sdram_c = bcm2835_cpufreq_get_min_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_C); + min_voltage_sdram_i = bcm2835_cpufreq_get_min_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_I); + min_voltage_sdram_p = bcm2835_cpufreq_get_min_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_SDRAM_P); + + /* temperature */ + temperature = bcm2835_cpufreq_get_temperature(sc); + + /* show result */ + if (cpufreq_verbose || bootverbose) { + device_printf(sc->dev, "Boot settings:\n"); + device_printf(sc->dev, + "current ARM %dMHz, Core %dMHz, SDRAM %dMHz, Turbo %s\n", + HZ2MHZ(arm_freq), HZ2MHZ(core_freq), HZ2MHZ(sdram_freq), + (sc->turbo_mode == BCM2835_MBOX_TURBO_ON) ? "ON" : "OFF"); + + device_printf(sc->dev, + "max/min ARM %d/%dMHz, Core %d/%dMHz, SDRAM %d/%dMHz\n", + HZ2MHZ(arm_max_freq), HZ2MHZ(arm_min_freq), + HZ2MHZ(core_max_freq), HZ2MHZ(core_min_freq), + HZ2MHZ(sdram_max_freq), HZ2MHZ(sdram_min_freq)); + + device_printf(sc->dev, + "current Core %dmV, SDRAM_C %dmV, SDRAM_I %dmV, " + "SDRAM_P %dmV\n", + OFFSET2MVOLT(voltage_core), OFFSET2MVOLT(voltage_sdram_c), + OFFSET2MVOLT(voltage_sdram_i), + OFFSET2MVOLT(voltage_sdram_p)); + + device_printf(sc->dev, + "max/min Core %d/%dmV, SDRAM_C %d/%dmV, SDRAM_I %d/%dmV, " + "SDRAM_P %d/%dmV\n", + OFFSET2MVOLT(max_voltage_core), + OFFSET2MVOLT(min_voltage_core), + OFFSET2MVOLT(max_voltage_sdram_c), + OFFSET2MVOLT(min_voltage_sdram_c), + OFFSET2MVOLT(max_voltage_sdram_i), + OFFSET2MVOLT(min_voltage_sdram_i), + OFFSET2MVOLT(max_voltage_sdram_p), + OFFSET2MVOLT(min_voltage_sdram_p)); + + device_printf(sc->dev, + "Temperature %d.%dC\n", (temperature / 1000), + (temperature % 1000) / 100); + } else { /* !cpufreq_verbose && !bootverbose */ + device_printf(sc->dev, + "ARM %dMHz, Core %dMHz, SDRAM %dMHz, Turbo %s\n", + HZ2MHZ(arm_freq), HZ2MHZ(core_freq), HZ2MHZ(sdram_freq), + (sc->turbo_mode == BCM2835_MBOX_TURBO_ON) ? "ON" : "OFF"); + } + + /* keep in softc (MHz/mV) */ + sc->arm_max_freq = HZ2MHZ(arm_max_freq); + sc->arm_min_freq = HZ2MHZ(arm_min_freq); + sc->core_max_freq = HZ2MHZ(core_max_freq); + sc->core_min_freq = HZ2MHZ(core_min_freq); + sc->sdram_max_freq = HZ2MHZ(sdram_max_freq); + sc->sdram_min_freq = HZ2MHZ(sdram_min_freq); + sc->max_voltage_core = OFFSET2MVOLT(max_voltage_core); + sc->min_voltage_core = OFFSET2MVOLT(min_voltage_core); + + /* if turbo is on, set to max values */ + if (sc->turbo_mode == BCM2835_MBOX_TURBO_ON) { + bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM, + arm_max_freq); + DELAY(TRANSITION_LATENCY); + bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, + core_max_freq); + DELAY(TRANSITION_LATENCY); + /* + * XXX: the core clock is unable to change at once, + * to change certainly, write it twice now. + */ + bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, + core_max_freq); + DELAY(TRANSITION_LATENCY); + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_SDRAM, sdram_max_freq); + DELAY(TRANSITION_LATENCY); + } else { + bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_ARM, + arm_min_freq); + DELAY(TRANSITION_LATENCY); + bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, + core_min_freq); + DELAY(TRANSITION_LATENCY); + /* XXX write the core clock twice */ + bcm2835_cpufreq_set_clock_rate(sc, BCM2835_MBOX_CLOCK_ID_CORE, + core_min_freq); + DELAY(TRANSITION_LATENCY); + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_SDRAM, sdram_min_freq); + DELAY(TRANSITION_LATENCY); + } + + VC_UNLOCK(sc); + + /* add human readable temperature to dev.cpu node */ + cpu = device_get_parent(sc->dev); + if (cpu != NULL) { + ctx = device_get_sysctl_ctx(cpu); + SYSCTL_ADD_PROC(ctx, + SYSCTL_CHILDREN(device_get_sysctl_tree(cpu)), OID_AUTO, + "temperature", CTLTYPE_INT | CTLFLAG_RD, sc, 0, + sysctl_bcm2835_devcpu_temperature, "IK", + "Current SoC temperature"); + } + + /* release this hook (continue boot) */ + config_intrhook_disestablish(&sc->init_hook); +} + +static void +bcm2835_cpufreq_identify(driver_t *driver, device_t parent) +{ + + DPRINTF("driver=%p, parent=%p\n", driver, parent); + if (device_find_child(parent, "bcm2835_cpufreq", -1) != NULL) + return; + if (BUS_ADD_CHILD(parent, 0, "bcm2835_cpufreq", -1) == NULL) + device_printf(parent, "add child failed\n"); +} + +static int +bcm2835_cpufreq_probe(device_t dev) +{ + + device_set_desc(dev, "CPU Frequency Control"); + return (0); +} + +static void +bcm2835_cpufreq_cb(void *arg, bus_dma_segment_t *segs, int nseg, int err) +{ + bus_addr_t *addr; + + if (err) + return; + addr = (bus_addr_t *)arg; + *addr = PHYS_TO_VCBUS(segs[0].ds_addr); +} + +static int +bcm2835_cpufreq_attach(device_t dev) +{ + struct bcm2835_cpufreq_softc *sc; + struct sysctl_oid *oid; + int err; + + /* clear softc and set self dev */ + sc = device_get_softc(dev); + memset(sc, 0, sizeof(*sc)); + sc->dev = dev; + + /* initial values */ + sc->arm_max_freq = -1; + sc->arm_min_freq = -1; + sc->core_max_freq = -1; + sc->core_min_freq = -1; + sc->sdram_max_freq = -1; + sc->sdram_min_freq = -1; + sc->max_voltage_core = 0; + sc->min_voltage_core = 0; + + /* create VC mbox buffer */ + sc->dma_size = PAGE_SIZE; + err = bus_dma_tag_create( + bus_get_dma_tag(sc->dev), + PAGE_SIZE, 0, /* alignment, boundary */ + BUS_SPACE_MAXADDR_32BIT, /* lowaddr */ + BUS_SPACE_MAXADDR, /* highaddr */ + NULL, NULL, /* filter, filterarg */ + sc->dma_size, 1, /* maxsize, nsegments */ + sc->dma_size, 0, /* maxsegsize, flags */ + NULL, NULL, /* lockfunc, lockarg */ + &sc->dma_tag); + if (err) { + device_printf(dev, "can't create DMA tag\n"); + return (ENXIO); + } + + err = bus_dmamem_alloc(sc->dma_tag, (void **)&sc->dma_buf, 0, + &sc->dma_map); + if (err) { + bus_dma_tag_destroy(sc->dma_tag); + device_printf(dev, "can't allocate dmamem\n"); + return (ENXIO); + } + + err = bus_dmamap_load(sc->dma_tag, sc->dma_map, sc->dma_buf, + sc->dma_size, bcm2835_cpufreq_cb, &sc->dma_phys, BUS_DMA_NOWAIT); + if (err) { + bus_dmamem_free(sc->dma_tag, sc->dma_buf, sc->dma_map); + bus_dma_tag_destroy(sc->dma_tag); + device_printf(dev, "can't load DMA map\n"); + return (ENXIO); + } + /* OK, ready to use VC buffer */ + + /* setup sysctl at first device */ + if (device_get_unit(dev) == 0) { + sysctl_ctx_init(&bcm2835_sysctl_ctx); + /* create node for hw.cpufreq */ + oid = SYSCTL_ADD_NODE(&bcm2835_sysctl_ctx, + SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "cpufreq", + CTLFLAG_RD, NULL, ""); + + /* Frequency (Hz) */ + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "arm_freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + sysctl_bcm2835_cpufreq_arm_freq, "IU", + "ARM frequency (Hz)"); + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "core_freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + sysctl_bcm2835_cpufreq_core_freq, "IU", + "Core frequency (Hz)"); + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "sdram_freq", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + sysctl_bcm2835_cpufreq_sdram_freq, "IU", + "SDRAM frequency (Hz)"); + + /* Turbo state */ + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "turbo", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + sysctl_bcm2835_cpufreq_turbo, "IU", + "Disables dynamic clocking"); + + /* Voltage (offset from 1.2V in units of 0.025V) */ + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "voltage_core", CTLTYPE_INT | CTLFLAG_RW, sc, 0, + sysctl_bcm2835_cpufreq_voltage_core, "I", + "ARM/GPU core voltage" + "(offset from 1.2V in units of 0.025V)"); + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "voltage_sdram", CTLTYPE_INT | CTLFLAG_WR, sc, + 0, sysctl_bcm2835_cpufreq_voltage_sdram, "I", + "SDRAM voltage (offset from 1.2V in units of 0.025V)"); + + /* Voltage individual SDRAM */ + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "voltage_sdram_c", CTLTYPE_INT | CTLFLAG_RW, sc, + 0, sysctl_bcm2835_cpufreq_voltage_sdram_c, "I", + "SDRAM controller voltage" + "(offset from 1.2V in units of 0.025V)"); + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "voltage_sdram_i", CTLTYPE_INT | CTLFLAG_RW, sc, + 0, sysctl_bcm2835_cpufreq_voltage_sdram_i, "I", + "SDRAM I/O voltage (offset from 1.2V in units of 0.025V)"); + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "voltage_sdram_p", CTLTYPE_INT | CTLFLAG_RW, sc, + 0, sysctl_bcm2835_cpufreq_voltage_sdram_p, "I", + "SDRAM phy voltage (offset from 1.2V in units of 0.025V)"); + + /* Temperature */ + SYSCTL_ADD_PROC(&bcm2835_sysctl_ctx, SYSCTL_CHILDREN(oid), + OID_AUTO, "temperature", CTLTYPE_INT | CTLFLAG_RD, sc, 0, + sysctl_bcm2835_cpufreq_temperature, "I", + "SoC temperature (thousandths of a degree C)"); + } + + /* ARM->VC lock */ + sema_init(&vc_sema, 1, "vcsema"); + + /* register callback for using mbox when interrupts are enabled */ + sc->init_hook.ich_func = bcm2835_cpufreq_init; + sc->init_hook.ich_arg = sc; + + if (config_intrhook_establish(&sc->init_hook) != 0) { + bus_dmamap_unload(sc->dma_tag, sc->dma_map); + bus_dmamem_free(sc->dma_tag, sc->dma_buf, sc->dma_map); + bus_dma_tag_destroy(sc->dma_tag); + device_printf(dev, "config_intrhook_establish failed\n"); + return (ENOMEM); + } + + /* this device is controlled by cpufreq(4) */ + cpufreq_register(dev); + + return (0); +} + +static int +bcm2835_cpufreq_detach(device_t dev) +{ + struct bcm2835_cpufreq_softc *sc; + + sc = device_get_softc(dev); + + sema_destroy(&vc_sema); + + if (sc->dma_phys != 0) + bus_dmamap_unload(sc->dma_tag, sc->dma_map); + if (sc->dma_buf != NULL) + bus_dmamem_free(sc->dma_tag, sc->dma_buf, sc->dma_map); + if (sc->dma_tag != NULL) + bus_dma_tag_destroy(sc->dma_tag); + + return (cpufreq_unregister(dev)); +} + +static int +bcm2835_cpufreq_set(device_t dev, const struct cf_setting *cf) +{ + struct bcm2835_cpufreq_softc *sc; + uint32_t rate_hz, rem; + int cur_freq, resp_freq, arm_freq, min_freq, core_freq; + + if (cf == NULL || cf->freq < 0) + return (EINVAL); + + sc = device_get_softc(dev); + + /* setting clock (Hz) */ + rate_hz = (uint32_t)MHZ2HZ(cf->freq); + rem = rate_hz % HZSTEP; + rate_hz -= rem; + if (rate_hz == 0) + return (EINVAL); + + /* adjust min freq */ + min_freq = sc->arm_min_freq; + if (sc->turbo_mode != BCM2835_MBOX_TURBO_ON) + if (min_freq > cpufreq_lowest_freq) + min_freq = cpufreq_lowest_freq; + + if (rate_hz < MHZ2HZ(min_freq) || rate_hz > MHZ2HZ(sc->arm_max_freq)) + return (EINVAL); + + /* set new value and verify it */ + VC_LOCK(sc); + cur_freq = bcm2835_cpufreq_get_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_ARM); + resp_freq = bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_ARM, rate_hz); + DELAY(TRANSITION_LATENCY); + arm_freq = bcm2835_cpufreq_get_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_ARM); + + /* + * if non-turbo and lower than or equal min_freq, + * clock down core and sdram to default first. + */ + if (sc->turbo_mode != BCM2835_MBOX_TURBO_ON) { + core_freq = bcm2835_cpufreq_get_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE); + if (rate_hz > MHZ2HZ(sc->arm_min_freq)) { + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE, + MHZ2HZ(sc->core_max_freq)); + DELAY(TRANSITION_LATENCY); + /* XXX write the core clock twice */ + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE, + MHZ2HZ(sc->core_max_freq)); + DELAY(TRANSITION_LATENCY); + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_SDRAM, + MHZ2HZ(sc->sdram_max_freq)); + DELAY(TRANSITION_LATENCY); + } else { + if (sc->core_min_freq < DEFAULT_CORE_FREQUENCY && + core_freq > DEFAULT_CORE_FREQUENCY) { + /* first, down to 250, then down to min */ + DELAY(TRANSITION_LATENCY); + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE, + MHZ2HZ(DEFAULT_CORE_FREQUENCY)); + DELAY(TRANSITION_LATENCY); + /* XXX write the core clock twice */ + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE, + MHZ2HZ(DEFAULT_CORE_FREQUENCY)); + DELAY(TRANSITION_LATENCY); + /* reset core voltage */ + bcm2835_cpufreq_set_voltage(sc, + BCM2835_MBOX_VOLTAGE_ID_CORE, 0); + DELAY(TRANSITION_LATENCY); + } + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE, + MHZ2HZ(sc->core_min_freq)); + DELAY(TRANSITION_LATENCY); + /* XXX write the core clock twice */ + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_CORE, + MHZ2HZ(sc->core_min_freq)); + DELAY(TRANSITION_LATENCY); + bcm2835_cpufreq_set_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_SDRAM, + MHZ2HZ(sc->sdram_min_freq)); + DELAY(TRANSITION_LATENCY); + } + } + + VC_UNLOCK(sc); + + if (resp_freq < 0 || arm_freq < 0 || resp_freq != arm_freq) { + device_printf(dev, "wrong freq\n"); + return (EIO); + } + DPRINTF("cpufreq: %d -> %d\n", cur_freq, arm_freq); + + return (0); +} + +static int +bcm2835_cpufreq_get(device_t dev, struct cf_setting *cf) +{ + struct bcm2835_cpufreq_softc *sc; + int arm_freq; + + if (cf == NULL) + return (EINVAL); + + sc = device_get_softc(dev); + memset(cf, CPUFREQ_VAL_UNKNOWN, sizeof(*cf)); + cf->dev = NULL; + + /* get cuurent value */ + VC_LOCK(sc); + arm_freq = bcm2835_cpufreq_get_clock_rate(sc, + BCM2835_MBOX_CLOCK_ID_ARM); + VC_UNLOCK(sc); + if (arm_freq < 0) { + device_printf(dev, "can't get clock\n"); + return (EINVAL); + } + + /* CPU clock in MHz or 100ths of a percent. */ + cf->freq = HZ2MHZ(arm_freq); + /* Voltage in mV. */ + cf->volts = CPUFREQ_VAL_UNKNOWN; + /* Power consumed in mW. */ + cf->power = CPUFREQ_VAL_UNKNOWN; + /* Transition latency in us. */ + cf->lat = TRANSITION_LATENCY; + /* Driver providing this setting. */ + cf->dev = dev; + + return (0); +} + +static int +bcm2835_cpufreq_make_freq_list(device_t dev, struct cf_setting *sets, + int *count) +{ + struct bcm2835_cpufreq_softc *sc; + int freq, min_freq, volts, rem; + int idx; + + sc = device_get_softc(dev); + freq = sc->arm_max_freq; + min_freq = sc->arm_min_freq; + + /* adjust head freq to STEP */ + rem = freq % MHZSTEP; + freq -= rem; + if (freq < min_freq) + freq = min_freq; + + /* if non-turbo, add extra low freq */ + if (sc->turbo_mode != BCM2835_MBOX_TURBO_ON) + if (min_freq > cpufreq_lowest_freq) + min_freq = cpufreq_lowest_freq; + + /* from freq to min_freq */ + for (idx = 0; idx < *count && freq >= min_freq; idx++) { + if (freq > sc->arm_min_freq) + volts = sc->max_voltage_core; + else + volts = sc->min_voltage_core; + sets[idx].freq = freq; + sets[idx].volts = volts; + sets[idx].lat = TRANSITION_LATENCY; + sets[idx].dev = dev; + freq -= MHZSTEP; + } + *count = ++idx; + + return (0); +} + +static int +bcm2835_cpufreq_settings(device_t dev, struct cf_setting *sets, int *count) +{ + struct bcm2835_cpufreq_softc *sc; + + if (sets == NULL || count == NULL) + return (EINVAL); + + sc = device_get_softc(dev); + if (sc->arm_min_freq < 0 || sc->arm_max_freq < 0) { + printf("device is not configured\n"); + return (EINVAL); + } + + /* fill data with unknown value */ + memset(sets, CPUFREQ_VAL_UNKNOWN, sizeof(*sets) * (*count)); + /* create new array up to count */ + bcm2835_cpufreq_make_freq_list(dev, sets, count); + + return (0); +} + +static int +bcm2835_cpufreq_type(device_t dev, int *type) +{ + + if (type == NULL) + return (EINVAL); + *type = CPUFREQ_TYPE_ABSOLUTE; + + return (0); +} + +static device_method_t bcm2835_cpufreq_methods[] = { + /* Device interface */ + DEVMETHOD(device_identify, bcm2835_cpufreq_identify), + DEVMETHOD(device_probe, bcm2835_cpufreq_probe), + DEVMETHOD(device_attach, bcm2835_cpufreq_attach), + DEVMETHOD(device_detach, bcm2835_cpufreq_detach), + + /* cpufreq interface */ + DEVMETHOD(cpufreq_drv_set, bcm2835_cpufreq_set), + DEVMETHOD(cpufreq_drv_get, bcm2835_cpufreq_get), + DEVMETHOD(cpufreq_drv_settings, bcm2835_cpufreq_settings), + DEVMETHOD(cpufreq_drv_type, bcm2835_cpufreq_type), + + DEVMETHOD_END +}; + +static devclass_t bcm2835_cpufreq_devclass; +static driver_t bcm2835_cpufreq_driver = { + "bcm2835_cpufreq", + bcm2835_cpufreq_methods, + sizeof(struct bcm2835_cpufreq_softc), +}; + +DRIVER_MODULE(bcm2835_cpufreq, cpu, bcm2835_cpufreq_driver, + bcm2835_cpufreq_devclass, 0, 0); Index: sys/arm/broadcom/bcm2835/bcm2835_mbox.h =================================================================== --- sys/arm/broadcom/bcm2835/bcm2835_mbox.h +++ sys/arm/broadcom/bcm2835/bcm2835_mbox.h @@ -36,6 +36,7 @@ #define BCM2835_MBOX_CHAN_LEDS 4 #define BCM2835_MBOX_CHAN_BUTTONS 5 #define BCM2835_MBOX_CHAN_TS 6 -#define BCM2835_MBOX_CHANS 7 +#define BCM2835_MBOX_CHAN_PROP 8 +#define BCM2835_MBOX_CHANS 9 #endif /* _BCM2835_MBOX_H_ */ Index: sys/arm/broadcom/bcm2835/bcm2835_mbox.c =================================================================== --- sys/arm/broadcom/bcm2835/bcm2835_mbox.c +++ sys/arm/broadcom/bcm2835/bcm2835_mbox.c @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -88,8 +89,8 @@ void* intr_hl; bus_space_tag_t bst; bus_space_handle_t bsh; - int valid[BCM2835_MBOX_CHANS]; int msg[BCM2835_MBOX_CHANS]; + struct sema sema[BCM2835_MBOX_CHANS]; }; #define mbox_read_4(sc, reg) \ @@ -105,23 +106,19 @@ uint32_t data; uint32_t msg; - MBOX_LOCK(sc); while (!(mbox_read_4(sc, REG_STATUS) & STATUS_EMPTY)) { msg = mbox_read_4(sc, REG_READ); dprintf("bcm_mbox_intr: raw data %08x\n", msg); chan = MBOX_CHAN(msg); data = MBOX_DATA(msg); - if (sc->valid[chan]) { + if (sc->msg[chan]) { printf("bcm_mbox_intr: channel %d oveflow\n", chan); continue; } dprintf("bcm_mbox_intr: chan %d, data %08x\n", chan, data); - sc->msg[chan] = data; - sc->valid[chan] = 1; - wakeup(&sc->msg[chan]); - + sc->msg[chan] = MBOX_MSG(data, 0xf); + sema_post(&sc->sema[chan]); } - MBOX_UNLOCK(sc); } static int @@ -172,8 +169,8 @@ mtx_init(&sc->lock, "vcio mbox", NULL, MTX_DEF); for (i = 0; i < BCM2835_MBOX_CHANS; i++) { - sc->valid[0] = 0; - sc->msg[0] = 0; + sc->msg[i] = 0; + sema_init(&sc->sema[i], 0, "mbox"); } /* Read all pending messages */ @@ -197,7 +194,7 @@ MBOX_LOCK(sc); while ((mbox_read_4(sc, REG_STATUS) & STATUS_FULL) && limit--) { - DELAY(2); + DELAY(5); } if (limit == 0) { @@ -219,10 +216,18 @@ dprintf("bcm_mbox_read: chan %d\n", chan); MBOX_LOCK(sc); - while (!sc->valid[chan]) - msleep(&sc->msg[chan], &sc->lock, PZERO, "vcio mbox read", 0); - *data = sc->msg[chan]; - sc->valid[chan] = 0; + while (sema_trywait(&sc->sema[chan]) == 0) { + /* do not unlock sc while waiting for the mbox */ + if (sema_timedwait(&sc->sema[chan], 10*hz) == 0) + break; + printf("timeout sema for chan %d\n", chan); + } + /* + * get data from intr handler, the same channel is never coming + * because of holding sc lock. + */ + *data = MBOX_DATA(sc->msg[chan]); + sc->msg[chan] = 0; MBOX_UNLOCK(sc); dprintf("bcm_mbox_read: chan %d, data %08x\n", chan, *data); Index: sys/arm/broadcom/bcm2835/bcm2835_mbox_prop.h =================================================================== --- /dev/null +++ sys/arm/broadcom/bcm2835/bcm2835_mbox_prop.h @@ -0,0 +1,273 @@ +/*- + * Copyright (C) 2013-2014 Daisuke Aoyama + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _BCM2835_MBOX_PROP_H_ +#define _BCM2835_MBOX_PROP_H_ + +#include +#include + +/* + * Mailbox property interface: + * https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface + */ +#define BCM2835_MBOX_CODE_REQ 0 +#define BCM2835_MBOX_CODE_RESP_SUCCESS 0x80000000 +#define BCM2835_MBOX_CODE_RESP_ERROR 0x80000001 +#define BCM2835_MBOX_TAG_VAL_LEN_RESPONSE 0x80000000 + +struct bcm2835_mbox_hdr { + uint32_t buf_size; + uint32_t code; +}; + +struct bcm2835_mbox_tag_hdr { + uint32_t tag; + uint32_t val_buf_size; + uint32_t val_len; +}; + +#define BCM2835_MBOX_CLOCK_ID_EMMC 0x00000001 +#define BCM2835_MBOX_CLOCK_ID_UART 0x00000002 +#define BCM2835_MBOX_CLOCK_ID_ARM 0x00000003 +#define BCM2835_MBOX_CLOCK_ID_CORE 0x00000004 +#define BCM2835_MBOX_CLOCK_ID_V3D 0x00000005 +#define BCM2835_MBOX_CLOCK_ID_H264 0x00000006 +#define BCM2835_MBOX_CLOCK_ID_ISP 0x00000007 +#define BCM2835_MBOX_CLOCK_ID_SDRAM 0x00000008 +#define BCM2835_MBOX_CLOCK_ID_PIXEL 0x00000009 +#define BCM2835_MBOX_CLOCK_ID_PWM 0x0000000a + +#define BCM2835_MBOX_TAG_GET_CLOCK_RATE 0x00030002 +#define BCM2835_MBOX_TAG_SET_CLOCK_RATE 0x00038002 +#define BCM2835_MBOX_TAG_GET_MAX_CLOCK_RATE 0x00030004 +#define BCM2835_MBOX_TAG_GET_MIN_CLOCK_RATE 0x00030007 + +struct msg_get_clock_rate { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t clock_id; + } req; + struct { + uint32_t clock_id; + uint32_t rate_hz; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_set_clock_rate { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t clock_id; + uint32_t rate_hz; + } req; + struct { + uint32_t clock_id; + uint32_t rate_hz; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_get_max_clock_rate { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t clock_id; + } req; + struct { + uint32_t clock_id; + uint32_t rate_hz; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_get_min_clock_rate { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t clock_id; + } req; + struct { + uint32_t clock_id; + uint32_t rate_hz; + } resp; + } body; + uint32_t end_tag; +}; + +#define BCM2835_MBOX_TURBO_ON 1 +#define BCM2835_MBOX_TURBO_OFF 0 + +#define BCM2835_MBOX_TAG_GET_TURBO 0x00030009 +#define BCM2835_MBOX_TAG_SET_TURBO 0x00038009 + +struct msg_get_turbo { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t id; + } req; + struct { + uint32_t id; + uint32_t level; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_set_turbo { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t id; + uint32_t level; + } req; + struct { + uint32_t id; + uint32_t level; + } resp; + } body; + uint32_t end_tag; +}; + +#define BCM2835_MBOX_VOLTAGE_ID_CORE 0x00000001 +#define BCM2835_MBOX_VOLTAGE_ID_SDRAM_C 0x00000002 +#define BCM2835_MBOX_VOLTAGE_ID_SDRAM_P 0x00000003 +#define BCM2835_MBOX_VOLTAGE_ID_SDRAM_I 0x00000004 + +#define BCM2835_MBOX_TAG_GET_VOLTAGE 0x00030003 +#define BCM2835_MBOX_TAG_SET_VOLTAGE 0x00038003 +#define BCM2835_MBOX_TAG_GET_MAX_VOLTAGE 0x00030005 +#define BCM2835_MBOX_TAG_GET_MIN_VOLTAGE 0x00030008 + +struct msg_get_voltage { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t voltage_id; + } req; + struct { + uint32_t voltage_id; + uint32_t value; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_set_voltage { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t voltage_id; + uint32_t value; + } req; + struct { + uint32_t voltage_id; + uint32_t value; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_get_max_voltage { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t voltage_id; + } req; + struct { + uint32_t voltage_id; + uint32_t value; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_get_min_voltage { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t voltage_id; + } req; + struct { + uint32_t voltage_id; + uint32_t value; + } resp; + } body; + uint32_t end_tag; +}; + +#define BCM2835_MBOX_TAG_GET_TEMPERATURE 0x00030006 +#define BCM2835_MBOX_TAG_SET_TEMPERATURE 0x0003000a + +struct msg_get_temperature { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t temperature_id; + } req; + struct { + uint32_t temperature_id; + uint32_t value; + } resp; + } body; + uint32_t end_tag; +}; + +struct msg_get_max_temperature { + struct bcm2835_mbox_hdr hdr; + struct bcm2835_mbox_tag_hdr tag_hdr; + union { + struct { + uint32_t temperature_id; + } req; + struct { + uint32_t temperature_id; + uint32_t value; + } resp; + } body; + uint32_t end_tag; +}; + +#endif /* _BCM2835_MBOX_PROP_H_ */ Index: sys/arm/broadcom/bcm2835/bcm2835_ofwcpu.c =================================================================== --- /dev/null +++ sys/arm/broadcom/bcm2835/bcm2835_ofwcpu.c @@ -0,0 +1,229 @@ +/*- + * Copyright (C) 2009 Nathan Whitehorn + * Copyright (C) 2013-2014 Daisuke Aoyama + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define DEFAULT_FREQUENCY 700 + +/* OFW CPU list (from powerpc/ofw/ofw_cpu.c) */ +static int +ofw_cpulist_probe(device_t dev) +{ + const char *name; + + name = ofw_bus_get_name(dev); + + if (name == NULL || strcmp(name, "cpus") != 0) + return (ENXIO); + + device_set_desc(dev, "Open Firmware CPU Group"); + + return (BUS_PROBE_DEFAULT); +} + +static int +ofw_cpulist_attach(device_t dev) +{ + phandle_t root, child; + device_t cdev; + struct ofw_bus_devinfo *dinfo; + + root = ofw_bus_get_node(dev); + + for (child = OF_child(root); child != 0; child = OF_peer(child)) { + dinfo = malloc(sizeof(*dinfo), M_DEVBUF, M_NOWAIT | M_ZERO); + + if (ofw_bus_gen_setup_devinfo(dinfo, child) != 0) { + free(dinfo, M_DEVBUF); + continue; + } + cdev = device_add_child(dev, NULL, -1); + if (cdev == NULL) { + device_printf(dev, "%s: device_add_child failed\n", + dinfo->obd_name); + ofw_bus_gen_destroy_devinfo(dinfo); + free(dinfo, M_DEVBUF); + continue; + } + device_set_ivars(cdev, dinfo); + } + + return (bus_generic_attach(dev)); +} + +static const struct ofw_bus_devinfo * +ofw_cpulist_get_devinfo(device_t dev, device_t child) +{ + + return (device_get_ivars(child)); +} + +static device_method_t ofw_cpulist_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ofw_cpulist_probe), + DEVMETHOD(device_attach, ofw_cpulist_attach), + + /* Bus interface */ + DEVMETHOD(bus_add_child, bus_generic_add_child), + DEVMETHOD(bus_child_pnpinfo_str, ofw_bus_gen_child_pnpinfo_str), + + /* ofw_bus interface */ + DEVMETHOD(ofw_bus_get_devinfo, ofw_cpulist_get_devinfo), + DEVMETHOD(ofw_bus_get_compat, ofw_bus_gen_get_compat), + DEVMETHOD(ofw_bus_get_model, ofw_bus_gen_get_model), + DEVMETHOD(ofw_bus_get_name, ofw_bus_gen_get_name), + DEVMETHOD(ofw_bus_get_node, ofw_bus_gen_get_node), + DEVMETHOD(ofw_bus_get_type, ofw_bus_gen_get_type), + + DEVMETHOD_END +}; + +static devclass_t ofw_cpulist_devclass; +static driver_t ofw_cpulist_driver = { + "cpulist", + ofw_cpulist_methods, + 0 +}; + +DRIVER_MODULE(ofw_cpulist, ofwbus, ofw_cpulist_driver, ofw_cpulist_devclass, + 0, 0); + + +/* OFW CPU (from powerpc/ofw/ofw_cpu.c) */ +struct ofw_cpu_softc { + struct pcpu *sc_cpu_pcpu; + uint32_t sc_nominal_mhz; +}; + +static int +ofw_cpu_probe(device_t dev) +{ + const char *type = ofw_bus_get_type(dev); + const char *name = ofw_bus_get_name(dev); + const char *compat = ofw_bus_get_compat(dev); + + if (type != NULL) { + if (strcmp(type, "cpu") != 0) + return (ENXIO); + } else { + /* XXX rpi.dts/bcm2835.dtsi have no device_type = "cpu" */ + /* rpi have compat only for cpu@0 */ + if (name == NULL || compat == NULL) + return (ENXIO); + if (bootverbose) + printf("cpu probe: name=%s, compat=%s\n", name, + compat); + if (!ofw_bus_is_compatible(dev, "arm,1176jzf-s")) + return (ENXIO); + } + + device_set_desc(dev, "Open Firmware CPU"); + return (BUS_PROBE_DEFAULT); +} + +static int +ofw_cpu_attach(device_t dev) +{ + struct ofw_cpu_softc *sc; + struct pcpu *pc; + int idx; + + sc = device_get_softc(dev); + idx = device_get_unit(dev); + + pc = pcpu_find(idx); + if (pc == NULL) + panic("can't find pcpu for idx=%d\n", idx); + sc->sc_cpu_pcpu = pc; + sc->sc_nominal_mhz = DEFAULT_FREQUENCY; + + bus_generic_probe(dev); + return (bus_generic_attach(dev)); +} + +static int +ofw_cpu_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) +{ + struct ofw_cpu_softc *sc; + + sc = device_get_softc(dev); + + switch (index) { + case CPU_IVAR_PCPU: + *result = (uintptr_t)sc->sc_cpu_pcpu; + return (0); + case CPU_IVAR_NOMINAL_MHZ: + *result = (uintptr_t)sc->sc_nominal_mhz; + return (0); + } + return (ENOENT); +} + +static device_method_t ofw_cpu_methods[] = { + /* Device interface */ + DEVMETHOD(device_probe, ofw_cpu_probe), + DEVMETHOD(device_attach, ofw_cpu_attach), + + /* Bus interface */ + DEVMETHOD(bus_add_child, bus_generic_add_child), + DEVMETHOD(bus_read_ivar, ofw_cpu_read_ivar), + DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), + DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), + DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), + DEVMETHOD(bus_release_resource, bus_generic_release_resource), + DEVMETHOD(bus_activate_resource,bus_generic_activate_resource), + + DEVMETHOD_END +}; + +static devclass_t ofw_cpu_devclass; +static driver_t ofw_cpu_driver = { + "cpu", + ofw_cpu_methods, + sizeof(struct ofw_cpu_softc) +}; + +DRIVER_MODULE(ofw_cpu, cpulist, ofw_cpu_driver, ofw_cpu_devclass, 0, 0); Index: sys/arm/broadcom/bcm2835/files.bcm2835 =================================================================== --- sys/arm/broadcom/bcm2835/files.bcm2835 +++ sys/arm/broadcom/bcm2835/files.bcm2835 @@ -2,6 +2,7 @@ arm/broadcom/bcm2835/bcm2835_bsc.c optional bcm2835_bsc arm/broadcom/bcm2835/bcm2835_common.c optional fdt +arm/broadcom/bcm2835/bcm2835_cpufreq.c standard arm/broadcom/bcm2835/bcm2835_dma.c standard arm/broadcom/bcm2835/bcm2835_fb.c optional sc arm/broadcom/bcm2835/bcm2835_fbd.c optional vt @@ -9,6 +10,7 @@ arm/broadcom/bcm2835/bcm2835_intr.c standard arm/broadcom/bcm2835/bcm2835_machdep.c standard arm/broadcom/bcm2835/bcm2835_mbox.c standard +arm/broadcom/bcm2835/bcm2835_ofwcpu.c standard arm/broadcom/bcm2835/bcm2835_sdhci.c optional sdhci arm/broadcom/bcm2835/bcm2835_spi.c optional bcm2835_spi arm/broadcom/bcm2835/bcm2835_systimer.c standard