diff options
Diffstat (limited to 'sys/arch/amd64/isa')
-rw-r--r-- | sys/arch/amd64/isa/i8042.c | 438 | ||||
-rw-r--r-- | sys/arch/amd64/isa/mc1468.c | 281 | ||||
-rw-r--r-- | sys/arch/amd64/isa/spkr.c | 122 |
3 files changed, 841 insertions, 0 deletions
diff --git a/sys/arch/amd64/isa/i8042.c b/sys/arch/amd64/isa/i8042.c new file mode 100644 index 0000000..eb8960c --- /dev/null +++ b/sys/arch/amd64/isa/i8042.c @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * 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. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 <sys/types.h> +#include <sys/driver.h> +#include <sys/errno.h> +#include <sys/syslog.h> +#include <sys/spinlock.h> +#include <sys/param.h> +#include <sys/proc.h> +#include <sys/reboot.h> +#include <sys/queue.h> +#include <dev/acpi/acpi.h> +#include <dev/timer.h> +#include <dev/cons/cons.h> +#include <dev/dmi/dmi.h> +#include <machine/cpu.h> +#include <machine/pio.h> +#include <machine/isa/i8042var.h> +#include <machine/isa/spkr.h> +#include <machine/idt.h> +#include <machine/ioapic.h> +#include <machine/intr.h> +#include <machine/lapic.h> +#include <machine/cdefs.h> +#include <string.h> +#include <assert.h> + +#define KEY_REP_MAX 2 + +#define pr_trace(fmt, ...) kprintf("i8042: " fmt, ##__VA_ARGS__) +#define pr_error(...) pr_trace(__VA_ARGS__) + +#define IO_NOP() inb(0x80) + +static struct spinlock data_lock; +static struct spinlock isr_lock; +static bool shift_key = false; +static bool capslock = false; +static bool capslock_released = true; +static uint16_t quirks = 0; +static struct proc polltd; +static struct timer tmr; +static bool is_init = false; + +static void i8042_ibuf_wait(void); +static int dev_send(bool aux, uint8_t data); +static int i8042_kb_getc(uint8_t sc, char *chr); +static void i8042_drain(void); + +static char keytab[] = { + '\0', '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '-', '=', '\b', '\t', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', + 'o', 'p', '[', ']', '\n', '\0', 'a', 's', 'd', 'f', 'g', 'h', + 'j', 'k', 'l', ';', '\'', '`', '\0', '\\', 'z', 'x', 'c', 'v', + 'b', 'n', 'm', ',', '.', '/', '\0', '\0', '\0', ' ' +}; + +static char keytab_shift[] = { + '\0', '\0', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', + '_', '+', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', + 'O', 'P', '{', '}', '\n', '\0', 'A', 'S', 'D', 'F', 'G', 'H', + 'J', 'K', 'L', ':', '\"', '~', '\0', '|', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', '<', '>', '?', '\0', '\0', '\0', ' ' +}; + +static char keytab_caps[] = { + '\0', '\0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', + '-','=', '\b', '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', + 'O', 'P', '[', ']', '\n', '\0', 'A', 'S', 'D', 'F', 'G', 'H', + 'J', 'K', 'L', ';', '\'', '`', '\0', '\\', 'Z', 'X', 'C', 'V', + 'B', 'N', 'M', ',', '.', '/', '\0', '\0', '\0', ' ' +}; + +static void +kbd_set_leds(uint8_t mask) +{ + dev_send(false, 0xED); + dev_send(false, mask); +} + +static void +i8042_obuf_wait(void) +{ + uint8_t status; + + for (;;) { + status = inb(I8042_STATUS); + if (ISSET(status, I8042_OBUFF)) { + return; + } + } +} + +static void +i8042_ibuf_wait(void) +{ + uint8_t status; + + for (;;) { + status = inb(I8042_STATUS); + if (!ISSET(status, I8042_IBUFF)) { + return; + } + } +} + +/* + * Drain i8042 internal data registers. + */ +static void +i8042_drain(void) +{ + spinlock_acquire(&data_lock); + while (ISSET(inb(I8042_STATUS), I8042_OBUFF)) { + inb(I8042_DATA); + } + spinlock_release(&data_lock); +} + +/* + * Write to an i8042 register. + * + * @port: I/O port + * @val: Value to write + */ +static void +i8042_write(uint16_t port, uint8_t val) +{ + i8042_ibuf_wait(); + outb(port, val); +} + +/* + * Read from an i8042 register. + * + * @port: I/O port + */ +static uint8_t +i8042_read(uint16_t port) +{ + i8042_obuf_wait(); + return inb(port); +} + +/* + * Read the i8042 controller configuration + * byte. + */ +static uint8_t +i8042_read_conf(void) +{ + uint8_t conf; + + i8042_write(I8042_CMD, I8042_GET_CONFB); + i8042_obuf_wait(); + conf = i8042_read(I8042_DATA); + return conf; +} + +/* + * Write a new value to the i8042 controller + * configuration byte. + */ +static void +i8042_write_conf(uint8_t conf) +{ + i8042_write(I8042_CMD, I8042_SET_CONFB); + i8042_ibuf_wait(); + i8042_write(I8042_DATA, conf); +} + +/* + * Send a data to a device + * + * @aux: If true, send to aux device (mouse) + * @data: Data to send. + */ +static int +dev_send(bool aux, uint8_t data) +{ + if (aux) { + i8042_write(I8042_CMD, I8042_PORT1_SEND); + } + + i8042_write(I8042_DATA, data); + i8042_obuf_wait(); + return inb(I8042_DATA); +} + +static int +i8042_kb_event(void *sp) +{ + struct cpu_info *ci; + struct cons_input input; + uint8_t data; + char c; + + spinlock_acquire(&isr_lock); + ci = this_cpu(); + ci->irq_mask |= CPU_IRQ(1); + data = inb(I8042_DATA); + + if (i8042_kb_getc(data, &c) != 0) { + /* No data useful */ + goto done; + } + input.scancode = data; + input.chr = c; + cons_ibuf_push(&g_root_scr, input); +done: + ci->irq_mask &= ~CPU_IRQ(1); + spinlock_release(&isr_lock); + return 1; /* handled */ +} + +static void +i8042_en_intr(void) +{ + struct intr_hand ih; + uint8_t conf; + + ih.func = i8042_kb_event; + ih.priority = IPL_BIO; + ih.irq = KB_IRQ; + intr_register("i8042-kb", &ih); + + /* + * Enable the clock of PS/2 port 0 and tell + * the controller that we are accepting + * interrupts. + */ + conf = i8042_read_conf(); + conf &= ~I8042_PORT0_CLK; + conf |= I8042_PORT0_INTR; + i8042_write_conf(conf); +} + +/* + * Convert scancode to character + * + * @sc: Scancode + * @chr: Character output + * + * Returns 0 when a char is given back. + */ +static int +i8042_kb_getc(uint8_t sc, char *chr) +{ + bool release = ISSET(sc, BIT(7)); + + switch (sc) { + /* Caps lock [press] */ + case 0x3A: + /* + * In case we are holding the caps lock button down, + * we don't want it to be spam toggled as that would + * be pretty strange looking and probably annoying. + */ + if (!capslock_released) { + return -EAGAIN; + } + + capslock_released = false; + capslock = !capslock; + + if (!capslock) { + kbd_set_leds(0); + } else { + kbd_set_leds(I8042_LED_CAPS); + } + return -EAGAIN; + /* Caps lock [release] */ + case 0xBA: + capslock_released = true; + return -EAGAIN; + /* Shift */ + case 0x36: + case 0xAA: + case 0x2A: + case 0xB6: + if (!release) { + shift_key = true; + } else { + shift_key = false; + } + return -EAGAIN; + } + + if (release) { + return -EAGAIN; + } + + if (capslock) { + *chr = keytab_caps[sc]; + return 0; + } + + if (shift_key) { + *chr = keytab_shift[sc]; + return 0; + } + + *chr = keytab[sc]; + return 0; +} + +/* + * Grabs a key from the keyboard, used typically + * for syncing the machine however can be used + * to bypass IRQs to prevent lost bytes. + */ +void +i8042_sync(void) +{ + static struct spinlock lock; + struct cons_input input; + uint8_t data, status; + char c; + + if (spinlock_try_acquire(&lock)) { + return; + } + + if (is_init) { + status = inb(I8042_STATUS); + if (!ISSET(status, I8042_OBUFF)) { + goto done; + } + + data = inb(I8042_DATA); + if (i8042_kb_getc(data, &c) == 0) { + input.scancode = data; + input.chr = c; + cons_ibuf_push(&g_root_scr, input); + } + } +done: + spinlock_release(&lock); +} + +void +i8042_quirk(int mask) +{ + quirks |= mask; +} + +static void +i8042_sync_loop(void) +{ + for (;;) { + i8042_obuf_wait(); + i8042_sync(); + } +} + +static int +i8042_init(void) +{ + const char *prodver = NULL; + + /* Try to request a general purpose timer */ + if (req_timer(TIMER_GP, &tmr) != TMRR_SUCCESS) { + pr_error("failed to fetch general purpose timer\n"); + return -ENODEV; + } + + /* Ensure it has get_time_usec() */ + if (tmr.get_time_usec == NULL) { + pr_error("general purpose timer has no get_time_usec()\n"); + return -ENODEV; + } + + /* We also need msleep() */ + if (tmr.msleep == NULL) { + pr_error("general purpose timer has no msleep()\n"); + return -ENODEV; + } + + i8042_write(I8042_CMD, I8042_DISABLE_PORT0); + i8042_write(I8042_CMD, I8042_DISABLE_PORT1); + + /* + * On some thinkpads, e.g., the T420s, the EC implementing + * the i8042 logic likes to play cop and throw NMIs at us + * for anything we do e.g., config register r/w, IRQs, + * etc... As of now, treat the i8042 like a fucking bomb + * if this bit is set. + */ + if ((prodver = dmi_prodver()) == NULL) { + prodver = "None"; + } + if (strcmp(prodver, "ThinkPad T420s") == 0) { + quirks |= I8042_HOSTILE; + pr_trace("ThinkPad T420s detected, assuming hostile\n"); + pr_trace("disabling irq 1, polling as fallback\n"); + spawn(&polltd, i8042_sync_loop, NULL, 0, NULL); + } + + if (!ISSET(quirks, I8042_HOSTILE)) { + /* Enable interrupts */ + i8042_drain(); + i8042_en_intr(); + } + + i8042_write(I8042_CMD, I8042_ENABLE_PORT0); + i8042_drain(); + is_init = true; + return 0; +} + +DRIVER_EXPORT(i8042_init); diff --git a/sys/arch/amd64/isa/mc1468.c b/sys/arch/amd64/isa/mc1468.c new file mode 100644 index 0000000..bbaa3d1 --- /dev/null +++ b/sys/arch/amd64/isa/mc1468.c @@ -0,0 +1,281 @@ +/* + * Copyright (c) 2023-2025 Ian Marco Moffett and the Osmora Team. + * 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. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 <sys/types.h> +#include <sys/param.h> +#include <sys/time.h> +#include <sys/driver.h> +#include <sys/device.h> +#include <sys/syslog.h> +#include <fs/devfs.h> +#include <machine/pio.h> +#include <machine/cdefs.h> +#include <string.h> + +#define MC1468_REGSEL 0x70 +#define MC1468_DATA 0x71 + +/* Register A flags */ +#define MC1468_UPDATING BIT(7) + +/* Register B flags */ +#define MC1468_DAYSAVE BIT(1) +#define MC1468_CLOCK24 BIT(2) + +static struct cdevsw mc1468_cdevsw; + +static uint8_t +bin_dabble(uint8_t bin) +{ + uint8_t retval = 0; + uint8_t nibble; + + for (int i = 7; i >= 0; --i) { + retval <<= 1; + if (bin & (1 << i)) { + retval |= 1; + } + + for (int j = 0; j < 2; ++j) { + nibble = retval & (retval >> (4 * nibble)) & 0x0F; + if (nibble >= 5) { + retval += 0x03 << (4 * nibble); + } + } + } + + return retval; +} + +/* + * Read a byte from an MC1468XX register. + */ +static uint8_t +mc1468_read(uint8_t reg) +{ + outb(MC1468_REGSEL, reg); + return inb(MC1468_DATA); +} + +/* + * Write a byte to the MC1468XX register. + */ +static void +mc1468_write(uint8_t reg, uint8_t val) +{ + outb(MC1468_REGSEL, reg); + outb(MC1468_DATA, val); +} + +/* + * Returns true if the MC1468XX is updating + * its time registers. + */ +static bool +mc1468_updating(void) +{ + uint8_t reg_b; + + reg_b = mc1468_read(0xB); + return ISSET(reg_b, MC1468_UPDATING) != 0; +} + +/* + * Check if date `a' and date `b' are synced. + * Used to make sure a bogus date caused by a + * read right before an MC1468XX register + * update doesn't occur. + */ +static bool +mc1468_date_synced(struct date *a, struct date *b) +{ + if (a->year != b->year) + return false; + if (a->month != b->month) + return false; + if (a->day != b->day) + return false; + if (a->sec != b->sec) + return false; + if (a->min != b->min) + return false; + if (a->hour != b->hour) + return false; + + return true; +} + +/* + * Sometimes the clock chip may encode the + * date in binary-coded-decimal. This function + * converts a date in BCD format to plain binary. + */ +static void +mc1468_bcd_conv(struct date *dp) +{ + dp->year = (dp->year & 0x0F) + ((dp->year / 16) * 10); + dp->month = (dp->month & 0x0F) + ((dp->month / 16) * 10); + dp->day = (dp->day & 0x0F) + ((dp->day / 16) * 10); + dp->sec = (dp->sec & 0x0F) + ((dp->sec / 16) * 10); + dp->min = (dp->min & 0x0F) + ((dp->min / 16) * 10); + dp->hour = (dp->hour & 0x0F) + (((dp->hour & 0x70) / 16) * 10); + dp->hour |= dp->hour & 0x80; +} + +/* + * Read the time for the clock without syncing + * it up. + * + * XXX: Please use mc1468_get_date() instead as + * this function may return inconsistent + * values if not used correctly. + */ +static void +__mc1468_get_time(struct date *dp) +{ + dp->year = mc1468_read(0x09); + dp->month = mc1468_read(0x08); + dp->day = mc1468_read(0x07); + dp->sec = mc1468_read(0x00); + dp->min = mc1468_read(0x02); + dp->hour = mc1468_read(0x04); +} + +/* + * Write a new time/date to the chip. + */ +static void +mc1468_set_date(const struct date *dp) +{ + while (mc1468_updating()) { + md_pause(); + } + + mc1468_write(0x08, bin_dabble(dp->month)); + mc1468_write(0x07, bin_dabble(dp->day)); + mc1468_write(0x04, bin_dabble(dp->hour)); + mc1468_write(0x02, bin_dabble(dp->min)); + mc1468_write(0x00, bin_dabble(dp->sec)); +} + +static int +mc1468_get_date(struct date *dp) +{ + struct date date_cur, date_last; + uint8_t reg_b = mc1468_read(0x0B); + + while (mc1468_updating()) { + __mc1468_get_time(&date_last); + } + + /* + * Get the current date and time. + * + * XXX: The date and time returned by __mc1468_get_time() + * may at times be out of sync, read it twice to + * make sure everything is synced up. + */ + do { + while (mc1468_updating()) { + md_pause(); + } + __mc1468_get_time(&date_last); + date_cur.year = date_last.year; + date_cur.month = date_last.month; + date_cur.day = date_last.day; + date_cur.sec = date_last.sec; + date_cur.min = date_last.min; + date_cur.hour = date_last.hour; + } while (!mc1468_date_synced(&date_cur, &date_last)); + + /* Is this in BCD? */ + if (!ISSET(reg_b, 0x04)) { + mc1468_bcd_conv(&date_cur); + } + + /* 24-hour mode? */ + if (ISSET(reg_b, MC1468_CLOCK24)) { + date_cur.hour = ((date_cur.hour & 0x7F) + 12) % 24; + } + + date_cur.year += 2000; + *dp = date_cur; + return 0; +} + +static int +mc1468_dev_read(dev_t dev, struct sio_txn *sio, int flags) +{ + struct date d; + size_t len = sizeof(d); + + if (sio->len > len) { + sio->len = len; + } + + mc1468_get_date(&d); + memcpy(sio->buf, &d, sio->len); + return sio->len; +} + +static int +mc1468_dev_write(dev_t dev, struct sio_txn *sio, int flags) +{ + struct date d; + size_t len = sizeof(d); + + if (sio->len > len) { + sio->len = len; + } + + memcpy(&d, sio->buf, sio->len); + mc1468_set_date(&d); + return sio->len; +} + +static int +mc1468_init(void) +{ + char devname[] = "rtc"; + devmajor_t major; + dev_t dev; + + major = dev_alloc_major(); + dev = dev_alloc(major); + dev_register(major, dev, &mc1468_cdevsw); + devfs_create_entry(devname, major, dev, 0444); + return 0; +} + +static struct cdevsw mc1468_cdevsw = { + .read = mc1468_dev_read, + .write = mc1468_dev_write, +}; + +DRIVER_EXPORT(mc1468_init); diff --git a/sys/arch/amd64/isa/spkr.c b/sys/arch/amd64/isa/spkr.c new file mode 100644 index 0000000..b2f63b0 --- /dev/null +++ b/sys/arch/amd64/isa/spkr.c @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023-2024 Ian Marco Moffett and the Osmora Team. + * 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. + * 3. Neither the name of Hyra nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 <sys/cdefs.h> +#include <sys/errno.h> +#include <sys/param.h> +#include <sys/device.h> +#include <sys/driver.h> +#include <fs/devfs.h> +#include <dev/timer.h> +#include <machine/isa/spkr.h> +#include <machine/isa/i8254.h> +#include <machine/pio.h> +#include <string.h> + +#define DIVIDEND 1193180 +#define CTRL_PORT 0x61 + +static struct cdevsw beep_cdevsw; + +/* + * Write to the pcspkr + * + * Bits 15:0 - frequency (hz) + * Bits 31:16 - duration (msec) + */ +static int +dev_write(dev_t dev, struct sio_txn *sio, int flags) +{ + uint32_t payload = 0; + uint16_t hz; + uint16_t duration; + size_t len = sizeof(payload); + + if (sio->len < len) { + return -EINVAL; + } + + memcpy(&payload, sio->buf, len); + hz = payload & 0xFFFF; + duration = (payload >> 16) & 0xFFFF; + pcspkr_tone(hz, duration); + return sio->len; +} + +static int +beep_init(void) +{ + char devname[] = "beep"; + devmajor_t major; + dev_t dev; + + /* Register the device here */ + major = dev_alloc_major(); + dev = dev_alloc(major); + dev_register(major, dev, &beep_cdevsw); + devfs_create_entry(devname, major, dev, 0666); + return 0; +} + +int +pcspkr_tone(uint16_t freq, uint32_t msec) +{ + uint32_t divisor; + uint8_t tmp; + struct timer tmr; + + if (req_timer(TIMER_GP, &tmr) != TMRR_SUCCESS) + return -ENOTSUP; + if (__unlikely(tmr.msleep == NULL)) + return -ENOTSUP; + + divisor = DIVIDEND / freq; + outb(I8254_COMMAND, 0xB6); + outb(I8254_CHANNEL_2, divisor & 0xFF); + outb(I8254_CHANNEL_2, (divisor >> 8) & 0xFF); + + /* Oscillate the speaker */ + tmp = inb(CTRL_PORT); + if (!ISSET(tmp, 3)) { + tmp |= 3; + outb(CTRL_PORT, tmp); + } + + /* Sleep then turn off the speaker */ + tmr.msleep(msec); + outb(CTRL_PORT, tmp & ~3); + return 0; +} + +static struct cdevsw beep_cdevsw = { + .read = noread, + .write = dev_write +}; + +DRIVER_EXPORT(beep_init); |