552 lines
14 KiB
C
552 lines
14 KiB
C
/* $OpenBSD: kb3310.c,v 1.16 2010/10/14 21:23:04 pirofti Exp $ */
|
|
/*
|
|
* Copyright (c) 2010 Otto Moerbeek <otto@drijf.net>
|
|
*
|
|
* 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 <sys/param.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/device.h>
|
|
#include <sys/sensors.h>
|
|
#include <sys/timeout.h>
|
|
|
|
#include <mips64/archtype.h>
|
|
#include <machine/apmvar.h>
|
|
#include <evbmips/loongson/autoconf.h>
|
|
#include <machine/bus.h>
|
|
#include <dev/isa/isavar.h>
|
|
|
|
#include <dev/pci/glxreg.h>
|
|
|
|
#include <loongson/dev/bonitoreg.h>
|
|
#include <loongson/dev/kb3310var.h>
|
|
|
|
#include "apm.h"
|
|
#include "pckbd.h"
|
|
#include "hidkbd.h"
|
|
|
|
#if NPCKBD > 0 || NHIDKBD > 0
|
|
#include <dev/ic/pckbcvar.h>
|
|
#include <dev/pckbc/pckbdvar.h>
|
|
#include <dev/usb/hidkbdvar.h>
|
|
#endif
|
|
|
|
struct cfdriver ykbec_cd = {
|
|
NULL, "ykbec", DV_DULL,
|
|
};
|
|
|
|
#ifdef KB3310_DEBUG
|
|
#define DPRINTF(x) printf x
|
|
#else
|
|
#define DPRINTF(x)
|
|
#endif
|
|
|
|
#define IO_YKBEC 0x381
|
|
#define IO_YKBECSIZE 0x3
|
|
|
|
static const struct {
|
|
const char *desc;
|
|
int type;
|
|
} ykbec_table[] = {
|
|
#define YKBEC_FAN 0
|
|
{ NULL, SENSOR_FANRPM },
|
|
#define YKBEC_ITEMP 1
|
|
{ "Internal temperature", SENSOR_TEMP },
|
|
#define YKBEC_FCAP 2
|
|
{ "Battery full charge capacity", SENSOR_AMPHOUR },
|
|
#define YKBEC_BCURRENT 3
|
|
{ "Battery current", SENSOR_AMPS },
|
|
#define YKBEC_BVOLT 4
|
|
{ "Battery voltage", SENSOR_VOLTS_DC },
|
|
#define YKBEC_BTEMP 5
|
|
{ "Battery temperature", SENSOR_TEMP },
|
|
#define YKBEC_CAP 6
|
|
{ "Battery capacity", SENSOR_PERCENT },
|
|
#define YKBEC_CHARGING 7
|
|
{ "Battery charging", SENSOR_INDICATOR },
|
|
#define YKBEC_AC 8
|
|
{ "AC-Power", SENSOR_INDICATOR }
|
|
#define YKBEC_NSENSORS 9
|
|
};
|
|
|
|
struct ykbec_softc {
|
|
bus_space_tag_t sc_iot;
|
|
bus_space_handle_t sc_ioh;
|
|
struct ksensor sc_sensor[YKBEC_NSENSORS];
|
|
struct ksensordev sc_sensordev;
|
|
#if NPCKBD > 0 || NHIDKBD > 0
|
|
struct timeout sc_bell_tmo;
|
|
#endif
|
|
};
|
|
|
|
static struct ykbec_softc *ykbec_sc;
|
|
static int ykbec_chip_config;
|
|
|
|
extern void loongson_set_isa_imr(uint);
|
|
|
|
int ykbec_match(device_t, cfdata_t, void *);
|
|
void ykbec_attach(device_t, device_t, void *);
|
|
|
|
const struct cfattach ykbec_ca = {
|
|
sizeof(struct ykbec_softc), ykbec_match, ykbec_attach
|
|
};
|
|
|
|
int ykbec_apminfo(struct apm_power_info *);
|
|
void ykbec_bell(void *, u_int, u_int, u_int, int);
|
|
void ykbec_bell_stop(void *);
|
|
void ykbec_print_bat_info(struct ykbec_softc *);
|
|
u_int ykbec_read(struct ykbec_softc *, u_int);
|
|
u_int ykbec_read16(struct ykbec_softc *, u_int);
|
|
void ykbec_refresh(void *arg);
|
|
void ykbec_write(struct ykbec_softc *, u_int, u_int);
|
|
|
|
#if NAPM > 0
|
|
struct apm_power_info ykbec_apmdata;
|
|
const char *ykbec_batstate[] = {
|
|
"high",
|
|
"low",
|
|
"critical",
|
|
"charging",
|
|
"unknown"
|
|
};
|
|
#define BATTERY_STRING(x) ((x) < nitems(ykbec_batstate) ? \
|
|
ykbec_batstate[x] : ykbec_batstate[4])
|
|
#endif
|
|
|
|
int
|
|
ykbec_match(device_t parent, cfdata_t match, void *aux)
|
|
{
|
|
struct isa_attach_args *ia = aux;
|
|
bus_space_handle_t ioh;
|
|
|
|
if (sys_platform->system_type != LOONGSON_YEELOONG)
|
|
return (0);
|
|
|
|
if ((ia->ia_iobase != IOBASEUNK && ia->ia_iobase != IO_YKBEC) ||
|
|
/* (ia->ia_iosize != 0 && ia->ia_iosize != IO_YKBECSIZE) || XXX isa.c */
|
|
ia->ia_maddr != MADDRUNK || ia->ia_msize != 0 ||
|
|
ia->ia_irq != IRQUNK || ia->ia_drq != DRQUNK)
|
|
return (0);
|
|
|
|
if (bus_space_map(ia->ia_iot, IO_YKBEC, IO_YKBECSIZE, 0, &ioh))
|
|
return (0);
|
|
|
|
bus_space_unmap(ia->ia_iot, ioh, IO_YKBECSIZE);
|
|
|
|
ia->ia_iobase = IO_YKBEC;
|
|
ia->ia_iosize = IO_YKBECSIZE;
|
|
|
|
return (1);
|
|
}
|
|
|
|
void
|
|
ykbec_attach(device_t parent, device_t self, void *aux)
|
|
{
|
|
struct isa_attach_args *ia = aux;
|
|
struct ykbec_softc *sc = device_private(self);
|
|
int i;
|
|
|
|
sc->sc_iot = ia->ia_iot;
|
|
if (bus_space_map(sc->sc_iot, ia->ia_iobase, ia->ia_iosize, 0,
|
|
&sc->sc_ioh)) {
|
|
aprint_error(": couldn't map I/O space");
|
|
return;
|
|
}
|
|
|
|
/* Initialize sensor data. */
|
|
strlcpy(sc->sc_sensordev.xname, device_xname(self),
|
|
sizeof(sc->sc_sensordev.xname));
|
|
if (sensor_task_register(sc, ykbec_refresh, 5) == NULL) {
|
|
aprint_error(", unable to register update task\n");
|
|
return;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
ykbec_print_bat_info(sc);
|
|
#endif
|
|
aprint_normal("\n");
|
|
|
|
for (i = 0; i < YKBEC_NSENSORS; i++) {
|
|
sc->sc_sensor[i].type = ykbec_table[i].type;
|
|
if (ykbec_table[i].desc)
|
|
strlcpy(sc->sc_sensor[i].desc, ykbec_table[i].desc,
|
|
sizeof(sc->sc_sensor[i].desc));
|
|
sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]);
|
|
}
|
|
|
|
sensordev_install(&sc->sc_sensordev);
|
|
|
|
#if NAPM > 0
|
|
/* make sure we have the apm state initialized before apm attaches */
|
|
ykbec_refresh(sc);
|
|
apm_setinfohook(ykbec_apminfo);
|
|
#endif
|
|
#if NPCKBD > 0 || NHIDKBD > 0
|
|
timeout_set(&sc->sc_bell_tmo, ykbec_bell_stop, sc);
|
|
#if NPCKBD > 0
|
|
pckbd_hookup_bell(ykbec_bell, sc);
|
|
#endif
|
|
#if NHIDKBD > 0
|
|
hidkbd_hookup_bell(ykbec_bell, sc);
|
|
#endif
|
|
#endif
|
|
ykbec_sc = sc;
|
|
}
|
|
|
|
void
|
|
ykbec_write(struct ykbec_softc *mcsc, u_int reg, u_int datum)
|
|
{
|
|
struct ykbec_softc *sc = (struct ykbec_softc *)mcsc;
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
|
|
bus_space_write_1(iot, ioh, 0, (reg >> 8) & 0xff);
|
|
bus_space_write_1(iot, ioh, 1, (reg >> 0) & 0xff);
|
|
bus_space_write_1(iot, ioh, 2, datum);
|
|
}
|
|
|
|
u_int
|
|
ykbec_read(struct ykbec_softc *mcsc, u_int reg)
|
|
{
|
|
struct ykbec_softc *sc = (struct ykbec_softc *)mcsc;
|
|
bus_space_tag_t iot = sc->sc_iot;
|
|
bus_space_handle_t ioh = sc->sc_ioh;
|
|
|
|
bus_space_write_1(iot, ioh, 0, (reg >> 8) & 0xff);
|
|
bus_space_write_1(iot, ioh, 1, (reg >> 0) & 0xff);
|
|
return bus_space_read_1(iot, ioh, 2);
|
|
}
|
|
|
|
u_int
|
|
ykbec_read16(struct ykbec_softc *mcsc, u_int reg)
|
|
{
|
|
u_int val;
|
|
|
|
val = ykbec_read(mcsc, reg);
|
|
return (val << 8) | ykbec_read(mcsc, reg + 1);
|
|
}
|
|
|
|
#define KB3310_FAN_SPEED_DIVIDER 480000
|
|
|
|
#define ECTEMP_CURRENT_REG 0xf458
|
|
#define REG_FAN_SPEED_HIGH 0xfe22
|
|
#define REG_FAN_SPEED_LOW 0xfe23
|
|
|
|
#define REG_DESIGN_CAP_HIGH 0xf77d
|
|
#define REG_DESIGN_CAP_LOW 0xf77e
|
|
#define REG_FULLCHG_CAP_HIGH 0xf780
|
|
#define REG_FULLCHG_CAP_LOW 0xf781
|
|
|
|
#define REG_DESIGN_VOL_HIGH 0xf782
|
|
#define REG_DESIGN_VOL_LOW 0xf783
|
|
#define REG_CURRENT_HIGH 0xf784
|
|
#define REG_CURRENT_LOW 0xf785
|
|
#define REG_VOLTAGE_HIGH 0xf786
|
|
#define REG_VOLTAGE_LOW 0xf787
|
|
#define REG_TEMPERATURE_HIGH 0xf788
|
|
#define REG_TEMPERATURE_LOW 0xf789
|
|
#define REG_RELATIVE_CAT_HIGH 0xf492
|
|
#define REG_RELATIVE_CAT_LOW 0xf493
|
|
#define REG_BAT_VENDOR 0xf4c4
|
|
#define REG_BAT_CELL_COUNT 0xf4c6
|
|
|
|
#define REG_BAT_CHARGE 0xf4a2
|
|
#define BAT_CHARGE_AC 0x00
|
|
#define BAT_CHARGE_DISCHARGE 0x01
|
|
#define BAT_CHARGE_CHARGE 0x02
|
|
|
|
#define REG_POWER_FLAG 0xf440
|
|
#define POWER_FLAG_ADAPTER_IN (1<<0)
|
|
#define POWER_FLAG_POWER_ON (1<<1)
|
|
#define POWER_FLAG_ENTER_SUS (1<<2)
|
|
|
|
#define REG_BAT_STATUS 0xf4b0
|
|
#define BAT_STATUS_BAT_EXISTS (1<<0)
|
|
#define BAT_STATUS_BAT_FULL (1<<1)
|
|
#define BAT_STATUS_BAT_DESTROY (1<<2)
|
|
#define BAT_STATUS_BAT_LOW (1<<5)
|
|
|
|
#define REG_CHARGE_STATUS 0xf4b1
|
|
#define CHARGE_STATUS_PRECHARGE (1<<1)
|
|
#define CHARGE_STATUS_OVERHEAT (1<<2)
|
|
|
|
#define REG_BAT_STATE 0xf482
|
|
#define BAT_STATE_DISCHARGING (1<<0)
|
|
#define BAT_STATE_CHARGING (1<<1)
|
|
|
|
#define REG_BEEP_CONTROL 0xf4d0
|
|
#define BEEP_ENABLE (1<<0)
|
|
|
|
#define REG_PMUCFG 0xff0c
|
|
#define PMUCFG_STOP_MODE (1<<7)
|
|
#define PMUCFG_IDLE_MODE (1<<6)
|
|
#define PMUCFG_LPC_WAKEUP (1<<5)
|
|
#define PMUCFG_RESET_8051 (1<<4)
|
|
#define PMUCFG_SCI_WAKEUP (1<<3)
|
|
#define PMUCFG_WDT_WAKEUP (1<<2)
|
|
#define PMUCFG_GPWU_WAKEUP (1<<1)
|
|
#define PMUCFG_IRQ_IDLE (1<<0)
|
|
|
|
#define REG_USB0 0xf461
|
|
#define REG_USB1 0xf462
|
|
#define REG_USB2 0xf463
|
|
#define USB_FLAG_ON 1
|
|
#define USB_FLAG_OFF 0
|
|
|
|
#define REG_FAN_CONTROL 0xf4d2
|
|
#define REG_FAN_ON 1
|
|
#define REG_FAN_OFF 0
|
|
|
|
#define YKBEC_SCI_IRQ 0xa
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
ykbec_print_bat_info(struct ykbec_softc *sc)
|
|
{
|
|
uint bat_status, count, dvolt, dcap;
|
|
|
|
printf(": battery ");
|
|
bat_status = ykbec_read(sc, REG_BAT_STATUS);
|
|
if (!ISSET(bat_status, BAT_STATUS_BAT_EXISTS)) {
|
|
printf("absent");
|
|
return;
|
|
}
|
|
|
|
count = ykbec_read(sc, REG_BAT_CELL_COUNT);
|
|
dvolt = ykbec_read16(sc, REG_DESIGN_VOL_HIGH);
|
|
dcap = ykbec_read16(sc, REG_DESIGN_CAP_HIGH);
|
|
printf("%d cells, design capacity %dmV %dmAh", count, dvolt, dcap);
|
|
}
|
|
#endif
|
|
|
|
void
|
|
ykbec_refresh(void *arg)
|
|
{
|
|
struct ykbec_softc *sc = (struct ykbec_softc *)arg;
|
|
u_int val, bat_charge, bat_status, charge_status, bat_state, power_flag;
|
|
u_int cap_pct, fullcap;
|
|
int current;
|
|
#if NAPM > 0
|
|
struct apm_power_info old;
|
|
#endif
|
|
|
|
val = ykbec_read16(sc, REG_FAN_SPEED_HIGH) & 0xfffff;
|
|
if (val != 0) {
|
|
val = KB3310_FAN_SPEED_DIVIDER / val;
|
|
sc->sc_sensor[YKBEC_FAN].value = val;
|
|
CLR(sc->sc_sensor[YKBEC_FAN].flags, SENSOR_FINVALID);
|
|
} else
|
|
SET(sc->sc_sensor[YKBEC_FAN].flags, SENSOR_FINVALID);
|
|
|
|
val = ykbec_read(sc, ECTEMP_CURRENT_REG);
|
|
sc->sc_sensor[YKBEC_ITEMP].value = val * 1000000 + 273150000;
|
|
|
|
fullcap = ykbec_read16(sc, REG_FULLCHG_CAP_HIGH);
|
|
sc->sc_sensor[YKBEC_FCAP].value = fullcap * 1000;
|
|
|
|
current = ykbec_read16(sc, REG_CURRENT_HIGH);
|
|
/* sign extend short -> int, int -> int64 will be done next statement */
|
|
current |= -(current & 0x8000);
|
|
sc->sc_sensor[YKBEC_BCURRENT].value = -1000 * current;
|
|
|
|
sc->sc_sensor[YKBEC_BVOLT].value = ykbec_read16(sc, REG_VOLTAGE_HIGH) *
|
|
1000;
|
|
|
|
val = ykbec_read16(sc, REG_TEMPERATURE_HIGH);
|
|
sc->sc_sensor[YKBEC_BTEMP].value = val * 1000000 + 273150000;
|
|
|
|
cap_pct = ykbec_read16(sc, REG_RELATIVE_CAT_HIGH);
|
|
sc->sc_sensor[YKBEC_CAP].value = cap_pct * 1000;
|
|
|
|
bat_charge = ykbec_read(sc, REG_BAT_CHARGE);
|
|
bat_status = ykbec_read(sc, REG_BAT_STATUS);
|
|
charge_status = ykbec_read(sc, REG_CHARGE_STATUS);
|
|
bat_state = ykbec_read(sc, REG_BAT_STATE);
|
|
power_flag = ykbec_read(sc, REG_POWER_FLAG);
|
|
|
|
sc->sc_sensor[YKBEC_CHARGING].value = !!ISSET(bat_state,
|
|
BAT_STATE_CHARGING);
|
|
sc->sc_sensor[YKBEC_AC].value = !!ISSET(power_flag,
|
|
POWER_FLAG_ADAPTER_IN);
|
|
|
|
sc->sc_sensor[YKBEC_CAP].status = ISSET(bat_status, BAT_STATUS_BAT_LOW) ?
|
|
SENSOR_S_CRIT : SENSOR_S_OK;
|
|
|
|
#if NAPM > 0
|
|
bcopy(&ykbec_apmdata, &old, sizeof(old));
|
|
ykbec_apmdata.battery_life = cap_pct;
|
|
ykbec_apmdata.ac_state = ISSET(power_flag, POWER_FLAG_ADAPTER_IN) ?
|
|
APM_AC_ON : APM_AC_OFF;
|
|
if (!ISSET(bat_status, BAT_STATUS_BAT_EXISTS)) {
|
|
ykbec_apmdata.battery_state = APM_BATTERY_ABSENT;
|
|
ykbec_apmdata.minutes_left = 0;
|
|
ykbec_apmdata.battery_life = 0;
|
|
} else {
|
|
if (ISSET(bat_state, BAT_STATE_CHARGING))
|
|
ykbec_apmdata.battery_state = APM_BATT_CHARGING;
|
|
else if (ISSET(bat_status, BAT_STATUS_BAT_LOW))
|
|
ykbec_apmdata.battery_state = APM_BATT_CRITICAL;
|
|
/* XXX arbitrary */
|
|
else if (cap_pct > 60)
|
|
ykbec_apmdata.battery_state = APM_BATT_HIGH;
|
|
else
|
|
ykbec_apmdata.battery_state = APM_BATT_LOW;
|
|
|
|
/* if charging, current is positive */
|
|
if (ISSET(bat_state, BAT_STATE_CHARGING))
|
|
current = 0;
|
|
else
|
|
current = -current;
|
|
/* XXX Yeeloong draw is about 1A */
|
|
if (current <= 0)
|
|
current = 1000;
|
|
/* XXX at 5?%, the Yeeloong shuts down */
|
|
if (cap_pct <= 5)
|
|
cap_pct = 0;
|
|
else
|
|
cap_pct -= 5;
|
|
fullcap = cap_pct * 60 * fullcap / 100;
|
|
ykbec_apmdata.minutes_left = fullcap / current;
|
|
|
|
}
|
|
if (old.ac_state != ykbec_apmdata.ac_state)
|
|
apm_record_event(APM_POWER_CHANGE, "AC power",
|
|
ykbec_apmdata.ac_state ? "restored" : "lost");
|
|
if (old.battery_state != ykbec_apmdata.battery_state)
|
|
apm_record_event(APM_POWER_CHANGE, "battery",
|
|
BATTERY_STRING(ykbec_apmdata.battery_state));
|
|
#endif
|
|
}
|
|
|
|
|
|
#if NAPM > 0
|
|
int
|
|
ykbec_apminfo(struct apm_power_info *info)
|
|
{
|
|
bcopy(&ykbec_apmdata, info, sizeof(struct apm_power_info));
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ykbec_suspend()
|
|
{
|
|
struct ykbec_softc *sc = ykbec_sc;
|
|
int ctrl;
|
|
|
|
/*
|
|
* Set up wakeup sources: currently only the internal keyboard.
|
|
*/
|
|
loongson_set_isa_imr(1 << 1);
|
|
|
|
/* USB */
|
|
DPRINTF(("USB\n"));
|
|
ykbec_write(sc, REG_USB0, USB_FLAG_OFF);
|
|
ykbec_write(sc, REG_USB1, USB_FLAG_OFF);
|
|
ykbec_write(sc, REG_USB2, USB_FLAG_OFF);
|
|
|
|
/* EC */
|
|
DPRINTF(("REG_PMUCFG\n"));
|
|
ctrl = PMUCFG_SCI_WAKEUP | PMUCFG_WDT_WAKEUP | PMUCFG_GPWU_WAKEUP |
|
|
PMUCFG_LPC_WAKEUP | PMUCFG_STOP_MODE | PMUCFG_RESET_8051;
|
|
ykbec_write(sc, REG_PMUCFG, ctrl);
|
|
|
|
/* FAN */
|
|
DPRINTF(("FAN\n"));
|
|
ykbec_write(sc, REG_FAN_CONTROL, REG_FAN_OFF);
|
|
|
|
/* CPU */
|
|
DPRINTF(("CPU\n"));
|
|
ykbec_chip_config = REGVAL(LOONGSON_CHIP_CONFIG0);
|
|
enableintr();
|
|
REGVAL(LOONGSON_CHIP_CONFIG0) = ykbec_chip_config & ~0x7;
|
|
(void)REGVAL(LOONGSON_CHIP_CONFIG0);
|
|
|
|
/*
|
|
* When a resume interrupt fires, we will enter the interrupt
|
|
* dispatcher, which will do nothing because we are at splhigh,
|
|
* and execution flow will return here and continue.
|
|
*/
|
|
(void)disableintr();
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
ykbec_resume()
|
|
{
|
|
struct ykbec_softc *sc = ykbec_sc;
|
|
|
|
/* CPU */
|
|
DPRINTF(("CPU\n"));
|
|
REGVAL(LOONGSON_CHIP_CONFIG0) = ykbec_chip_config;
|
|
(void)REGVAL(LOONGSON_CHIP_CONFIG0);
|
|
|
|
/* FAN */
|
|
DPRINTF(("FAN\n"));
|
|
ykbec_write(sc, REG_FAN_CONTROL, REG_FAN_ON);
|
|
|
|
/* USB */
|
|
DPRINTF(("USB\n"));
|
|
ykbec_write(sc, REG_USB0, USB_FLAG_ON);
|
|
ykbec_write(sc, REG_USB1, USB_FLAG_ON);
|
|
ykbec_write(sc, REG_USB2, USB_FLAG_ON);
|
|
|
|
ykbec_refresh(sc);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if NPCKBD > 0 || NHIDKBD > 0
|
|
void
|
|
ykbec_bell(void *arg, u_int pitch, u_int period, u_int volume, int poll)
|
|
{
|
|
struct ykbec_softc *sc = (struct ykbec_softc *)arg;
|
|
int bctrl;
|
|
int s;
|
|
|
|
s = spltty();
|
|
bctrl = ykbec_read(sc, REG_BEEP_CONTROL);
|
|
if (volume == 0 || timeout_pending(&sc->sc_bell_tmo)) {
|
|
timeout_del(&sc->sc_bell_tmo);
|
|
/* inline ykbec_bell_stop(arg); */
|
|
ykbec_write(sc, REG_BEEP_CONTROL, bctrl & ~BEEP_ENABLE);
|
|
}
|
|
|
|
if (volume != 0) {
|
|
ykbec_write(sc, REG_BEEP_CONTROL, bctrl | BEEP_ENABLE);
|
|
if (poll) {
|
|
delay(period * 1000);
|
|
ykbec_write(sc, REG_BEEP_CONTROL, bctrl & ~BEEP_ENABLE);
|
|
} else {
|
|
timeout_add_msec(&sc->sc_bell_tmo, period);
|
|
}
|
|
}
|
|
splx(s);
|
|
}
|
|
|
|
void
|
|
ykbec_bell_stop(void *arg)
|
|
{
|
|
struct ykbec_softc *sc = (struct ykbec_softc *)arg;
|
|
int s;
|
|
|
|
s = spltty();
|
|
ykbec_write(sc, REG_BEEP_CONTROL,
|
|
ykbec_read(sc, REG_BEEP_CONTROL) & ~BEEP_ENABLE);
|
|
splx(s);
|
|
}
|
|
#endif
|