#include <sys/param.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/conf.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/sysctl.h>
#include <sys/taskqueue.h>
#include <sys/fcntl.h>
#include <sys/poll.h>
#include <sys/selinfo.h>
#include <sys/uio.h>
#include <sys/mbuf.h>
#include <dev/firewire/firewire.h>
#include <dev/firewire/firewirereg.h>
#include <dev/firewire/iec13213.h>
#include <dev/firewire/fwcam.h>
#include <dev/firewire/fw_helpers.h>
static MALLOC_DEFINE(M_FWCAM, "fwcam", "IIDC FireWire Camera");
static int debug = 0;
static int iso_channel = 0;
SYSCTL_DECL(_hw_firewire);
static SYSCTL_NODE(_hw_firewire, OID_AUTO, fwcam, CTLFLAG_RD | CTLFLAG_MPSAFE,
0, "IIDC Camera");
SYSCTL_INT(_hw_firewire_fwcam, OID_AUTO, debug, CTLFLAG_RWTUN, &debug, 0,
"fwcam debug level");
SYSCTL_INT(_hw_firewire_fwcam, OID_AUTO, iso_channel, CTLFLAG_RWTUN,
&iso_channel, 0, "ISO channel for isochronous receive (default 0)");
#define FWCAM_DEBUG(lev, fmt, ...) \
do { \
if (debug >= (lev)) \
printf("fwcam: " fmt, ## __VA_ARGS__); \
} while (0)
static int fwcam_probe(device_t);
static int fwcam_attach(device_t);
static int fwcam_detach(device_t);
static void fwcam_probe_task(void *, int);
static int fwcam_iso_start(struct fwcam_softc *);
static void fwcam_iso_stop(struct fwcam_softc *);
static void fwcam_iso_input(struct fw_xferq *);
static d_open_t fwcam_cdev_open;
static d_close_t fwcam_cdev_close;
static d_read_t fwcam_cdev_read;
static d_poll_t fwcam_cdev_poll;
static d_ioctl_t fwcam_cdev_ioctl;
static struct cdevsw fwcam_cdevsw = {
.d_version = D_VERSION,
.d_flags = D_TRACKCLOSE,
.d_open = fwcam_cdev_open,
.d_close = fwcam_cdev_close,
.d_read = fwcam_cdev_read,
.d_poll = fwcam_cdev_poll,
.d_ioctl = fwcam_cdev_ioctl,
.d_name = "fwcam",
};
static uint32_t
fwcam_search_dir_cmd_base(uint32_t *csrrom, uint32_t dir_qoff,
uint32_t rom_quads, int max_depth)
{
struct csrdirectory *dir;
struct csrreg *reg;
uint32_t dir_len, sub_qoff, val;
int i;
if (dir_qoff >= rom_quads || max_depth <= 0)
return (0);
dir = (struct csrdirectory *)&csrrom[dir_qoff];
dir_len = dir->crc_len;
if (dir_qoff + 1 + dir_len > rom_quads)
return (0);
if (!crom_crc_valid((uint32_t *)&dir->entry[0], dir_len, dir->crc)) {
if (firewire_debug)
printf("fwcam: bad CRC in directory at 0x%x\n",
dir_qoff);
}
for (i = 0; i < (int)dir_len; i++) {
if (dir_qoff + 1 + i >= rom_quads)
break;
reg = &dir->entry[i];
if (reg->key == IIDC_CROM_CMD_BASE && reg->val != 0)
return (reg->val);
if ((reg->key & CSRTYPE_MASK) == CSRTYPE_D) {
sub_qoff = dir_qoff + 1 + i + reg->val;
val = fwcam_search_dir_cmd_base(csrrom, sub_qoff,
rom_quads, max_depth - 1);
if (val != 0)
return (val);
}
}
return (0);
}
static uint32_t
fwcam_find_iidc_cmd_base(struct fw_unit *unit)
{
return (fwcam_search_dir_cmd_base(unit->fwdev->csrrom,
unit->dir_offset / 4, CSRROMSIZE / 4, 2));
}
static int
fwcam_read_quadlet(struct fwcam_softc *sc, uint32_t offset, uint32_t *val)
{
uint16_t dst;
uint8_t spd;
int err;
FWCAM_LOCK(sc);
if (sc->fwdev == NULL) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
dst = FWLOCALBUS | sc->fwdev->dst;
spd = min(sc->fwdev->speed, FWSPD_S400);
FWCAM_UNLOCK(sc);
err = fw_read_quadlet(sc->fd.fc, M_FWCAM, dst, spd,
sc->cmd_hi, sc->cmd_lo + offset, val);
if (err)
FWCAM_DEBUG(1, "read_quadlet: offset=0x%x err=%d\n",
offset, err);
return (err);
}
static int
fwcam_write_quadlet(struct fwcam_softc *sc, uint32_t offset, uint32_t val)
{
uint16_t dst;
uint8_t spd;
int err;
FWCAM_LOCK(sc);
if (sc->fwdev == NULL) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
dst = FWLOCALBUS | sc->fwdev->dst;
spd = min(sc->fwdev->speed, FWSPD_S400);
FWCAM_UNLOCK(sc);
err = fw_write_quadlet(sc->fd.fc, M_FWCAM, dst, spd,
sc->cmd_hi, sc->cmd_lo + offset, val);
if (err)
FWCAM_DEBUG(1, "write_quadlet: offset=0x%x err=%d\n",
offset, err);
return (err);
}
static int
fwcam_read_capabilities(struct fwcam_softc *sc)
{
int err, f, m;
err = fwcam_read_quadlet(sc, IIDC_V_FORMAT_INQ, &sc->formats);
if (err) {
device_printf(sc->fd.dev,
"failed to read V_FORMAT_INQ: %d\n", err);
return (err);
}
err = fwcam_read_quadlet(sc, IIDC_BASIC_FUNC_INQ, &sc->basic_func);
if (err) {
device_printf(sc->fd.dev,
"failed to read BASIC_FUNC_INQ: %d\n", err);
return (err);
}
for (f = 0; f < 8; f++) {
if (!(sc->formats & (1 << (31 - f)))) {
sc->modes[f] = 0;
continue;
}
err = fwcam_read_quadlet(sc, IIDC_V_MODE_INQ(f),
&sc->modes[f]);
if (err) {
sc->modes[f] = 0;
continue;
}
for (m = 0; m < 8; m++) {
if (!(sc->modes[f] & (1 << (31 - m)))) {
sc->rates[f][m] = 0;
continue;
}
err = fwcam_read_quadlet(sc, IIDC_V_RATE_INQ(f, m),
&sc->rates[f][m]);
if (err)
sc->rates[f][m] = 0;
}
}
err = fwcam_read_quadlet(sc, IIDC_FEATURE_HI_INQ, &sc->features_hi);
if (err)
sc->features_hi = 0;
err = fwcam_read_quadlet(sc, IIDC_FEATURE_LO_INQ, &sc->features_lo);
if (err)
sc->features_lo = 0;
return (0);
}
static int
fwcam_power_on(struct fwcam_softc *sc)
{
uint32_t val;
int err, retries;
err = fwcam_read_quadlet(sc, IIDC_BASIC_FUNC_INQ, &val);
if (err) {
device_printf(sc->fd.dev,
"cannot read BASIC_FUNC_INQ: %d\n", err);
return (err);
}
if ((val & IIDC_CAM_POWER_CTRL) == 0) {
FWCAM_DEBUG(1, "no power control, assuming powered\n");
return (0);
}
err = fwcam_read_quadlet(sc, IIDC_CAMERA_POWER, &val);
if (err == 0 && (val & IIDC_POWER_ON)) {
FWCAM_DEBUG(1, "camera already powered on\n");
return (0);
}
err = fwcam_write_quadlet(sc, IIDC_CAMERA_POWER, IIDC_POWER_ON);
if (err) {
device_printf(sc->fd.dev,
"failed to write CAMERA_POWER: %d\n", err);
return (err);
}
for (retries = 0; retries < 50; retries++) {
pause("fwcampw", hz / 10);
if (sc->state == FWCAM_STATE_DETACHING)
return (ENXIO);
err = fwcam_read_quadlet(sc, IIDC_CAMERA_POWER, &val);
if (err)
continue;
if (val & IIDC_POWER_ON)
return (0);
}
device_printf(sc->fd.dev, "camera power-on timeout (5s)\n");
return (ETIMEDOUT);
}
static void
fwcam_probe_task(void *arg, int pending __unused)
{
struct fwcam_softc *sc = (struct fwcam_softc *)arg;
int err;
if (sc->state == FWCAM_STATE_DETACHING || sc->fwdev == NULL)
return;
err = fwcam_power_on(sc);
if (err) {
device_printf(sc->fd.dev,
"power-on failed (%d), trying to read anyway\n", err);
}
if (sc->state == FWCAM_STATE_DETACHING)
return;
if (fwcam_read_capabilities(sc) == 0) {
uint32_t val;
if (fwcam_read_quadlet(sc, IIDC_CUR_V_FORMAT, &val) == 0)
sc->cur_format = (val >> 29) & 0x7;
if (fwcam_read_quadlet(sc, IIDC_CUR_V_MODE, &val) == 0)
sc->cur_mode = (val >> 29) & 0x7;
if (fwcam_read_quadlet(sc, IIDC_CUR_V_FRM_RATE, &val) == 0)
sc->cur_framerate = (val >> 29) & 0x7;
FWCAM_LOCK(sc);
if (sc->state == FWCAM_STATE_DETACHING) {
FWCAM_UNLOCK(sc);
return;
}
sc->state = FWCAM_STATE_PROBED;
wakeup(sc);
FWCAM_UNLOCK(sc);
if (sc->dma_ch >= 0 &&
sc->state != FWCAM_STATE_DETACHING)
fwcam_iso_start(sc);
} else {
FWCAM_LOCK(sc);
if (sc->state == FWCAM_STATE_PROBING)
sc->state = FWCAM_STATE_IDLE;
wakeup(sc);
FWCAM_UNLOCK(sc);
}
}
static uint32_t
fwcam_frame_size(struct fwcam_softc *sc)
{
static const uint32_t fmt0_sizes[] = {
160 * 120 * 3,
320 * 240 * 2,
640 * 480 * 3 / 2,
640 * 480 * 2,
640 * 480 * 3,
640 * 480,
};
if (sc->cur_format == IIDC_FMT_VGA && sc->cur_mode < nitems(fmt0_sizes))
return (fmt0_sizes[sc->cur_mode]);
return (FWCAM_MAX_FRAME_SIZE);
}
static int
fwcam_iso_start(struct fwcam_softc *sc)
{
struct firewire_comm *fc = sc->fd.fc;
struct fw_xferq *xferq;
uint32_t val;
int dma_ch, err;
mtx_assert(&sc->mtx, MA_NOTOWNED);
FWCAM_LOCK(sc);
if (sc->dma_ch >= 0 || sc->state == FWCAM_STATE_STREAMING) {
FWCAM_UNLOCK(sc);
return (0);
}
if (sc->state == FWCAM_STATE_DETACHING) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
FWCAM_UNLOCK(sc);
dma_ch = fw_open_isodma(fc, 0);
if (dma_ch < 0) {
device_printf(sc->fd.dev, "no IR DMA channel available\n");
return (EBUSY);
}
FWCAM_LOCK(sc);
if (sc->dma_ch >= 0) {
FWCAM_UNLOCK(sc);
fc->ir[dma_ch]->flag &= ~FWXFERQ_OPEN;
return (0);
}
FWCAM_UNLOCK(sc);
xferq = fc->ir[dma_ch];
xferq->flag |= FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_STREAM;
sc->iso_channel = (uint8_t)(iso_channel & FWXFERQ_CHTAGMASK);
xferq->flag &= ~FWXFERQ_CHTAGMASK;
xferq->flag |= sc->iso_channel & FWXFERQ_CHTAGMASK;
xferq->sc = (caddr_t)sc;
xferq->hand = fwcam_iso_input;
xferq->bnchunk = FWCAM_ISO_NCHUNK;
xferq->bnpacket = 1;
xferq->psize = FWCAM_ISO_PKTSIZE;
xferq->queued = 0;
xferq->buf = NULL;
xferq->bulkxfer = malloc(sizeof(struct fw_bulkxfer) * xferq->bnchunk,
M_FWCAM, M_WAITOK | M_ZERO);
fw_iso_init_chunks(xferq);
sc->frame_size = fwcam_frame_size(sc);
sc->frame_buf = malloc(sc->frame_size, M_FWCAM, M_WAITOK | M_ZERO);
sc->read_buf = malloc(sc->frame_size, M_FWCAM, M_WAITOK | M_ZERO);
sc->frame_offset = 0;
sc->frame_ready = 0;
sc->frame_dropped = 0;
err = fwcam_write_quadlet(sc, IIDC_CUR_V_FORMAT,
(uint32_t)sc->cur_format << IIDC_CUR_V_SHIFT);
if (err) {
device_printf(sc->fd.dev,
"failed to set CUR_V_FORMAT: %d\n", err);
goto fail;
}
err = fwcam_write_quadlet(sc, IIDC_CUR_V_MODE,
(uint32_t)sc->cur_mode << IIDC_CUR_V_SHIFT);
if (err) {
device_printf(sc->fd.dev,
"failed to set CUR_V_MODE: %d\n", err);
goto fail;
}
err = fwcam_write_quadlet(sc, IIDC_CUR_V_FRM_RATE,
(uint32_t)sc->cur_framerate << IIDC_CUR_V_SHIFT);
if (err) {
device_printf(sc->fd.dev,
"failed to set CUR_V_FRM_RATE: %d\n", err);
goto fail;
}
err = fwcam_read_quadlet(sc, IIDC_VMODE_ERR_STATUS, &val);
if (err == 0 && (val & IIDC_VMODE_ERROR)) {
device_printf(sc->fd.dev,
"Vmode_Error_Status set: format=%d mode=%d rate=%d "
"speed=%d\n", sc->cur_format, sc->cur_mode,
sc->cur_framerate, sc->iso_speed);
err = EINVAL;
goto fail;
}
val = ((uint32_t)sc->iso_channel << IIDC_ISO_CH_SHIFT) |
((uint32_t)sc->iso_speed << IIDC_ISO_SPEED_SHIFT);
err = fwcam_write_quadlet(sc, IIDC_ISO_CHANNEL, val);
if (err) {
device_printf(sc->fd.dev,
"failed to set ISO_CHANNEL: %d\n", err);
goto fail;
}
err = fwcam_write_quadlet(sc, IIDC_ISO_EN, IIDC_ISO_EN_ON);
if (err == EIO) {
err = fwcam_power_on(sc);
if (err == 0)
err = fwcam_write_quadlet(sc, IIDC_ISO_EN,
IIDC_ISO_EN_ON);
if (err) {
device_printf(sc->fd.dev,
"ISO enable refused (lens cover closed?)\n");
goto fail;
}
} else if (err) {
device_printf(sc->fd.dev,
"failed to enable ISO: %d\n", err);
goto fail;
}
err = fc->irx_enable(fc, dma_ch);
if (err) {
device_printf(sc->fd.dev,
"failed to start IR DMA: %d\n", err);
fwcam_write_quadlet(sc, IIDC_ISO_EN, 0);
goto fail;
}
FWCAM_LOCK(sc);
if (sc->state == FWCAM_STATE_DETACHING) {
FWCAM_UNLOCK(sc);
fc->irx_disable(fc, dma_ch);
fwcam_write_quadlet(sc, IIDC_ISO_EN, 0);
err = ENXIO;
goto fail;
}
sc->dma_ch = dma_ch;
sc->state = FWCAM_STATE_STREAMING;
FWCAM_UNLOCK(sc);
return (0);
fail:
fw_iso_free_chunks(xferq, M_FWCAM);
xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM |
FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
xferq->hand = NULL;
free(sc->frame_buf, M_FWCAM);
free(sc->read_buf, M_FWCAM);
sc->frame_buf = NULL;
sc->read_buf = NULL;
sc->dma_ch = -1;
return (err);
}
static void
fwcam_iso_stop(struct fwcam_softc *sc)
{
struct firewire_comm *fc = sc->fd.fc;
struct fw_xferq *xferq;
int dma_ch;
FWCAM_LOCK(sc);
dma_ch = sc->dma_ch;
if (dma_ch < 0) {
FWCAM_UNLOCK(sc);
return;
}
sc->dma_ch = -1;
FWCAM_UNLOCK(sc);
xferq = fc->ir[dma_ch];
if (xferq->flag & FWXFERQ_RUNNING)
fc->irx_disable(fc, dma_ch);
if (sc->fwdev != NULL)
fwcam_write_quadlet(sc, IIDC_ISO_EN, 0);
FWCAM_LOCK(sc);
fw_iso_wait_inactive_locked(&sc->mtx, &sc->iso_active, "fwcamis");
sc->frame_ready = 0;
while (sc->read_in_progress)
msleep(&sc->read_in_progress, &sc->mtx, PWAIT, "fwcamst", hz);
FWCAM_UNLOCK(sc);
xferq->flag &= ~(FWXFERQ_MODEMASK | FWXFERQ_OPEN | FWXFERQ_STREAM |
FWXFERQ_EXTBUF | FWXFERQ_HANDLER | FWXFERQ_CHTAGMASK);
xferq->hand = NULL;
fw_iso_free_chunks(xferq, M_FWCAM);
free(sc->frame_buf, M_FWCAM);
free(sc->read_buf, M_FWCAM);
sc->frame_buf = NULL;
sc->read_buf = NULL;
}
static void
fwcam_iso_input(struct fw_xferq *xferq)
{
struct fwcam_softc *sc = (struct fwcam_softc *)xferq->sc;
struct fw_bulkxfer *sxfer;
struct fw_pkt *fp;
struct mbuf *m;
uint8_t *payload;
uint32_t plen;
uint8_t *tmp;
int dma_ch;
FWCAM_LOCK(sc);
dma_ch = sc->dma_ch;
if (dma_ch < 0 || sc->frame_buf == NULL) {
FWCAM_UNLOCK(sc);
return;
}
sc->iso_active = 1;
FWCAM_UNLOCK(sc);
while ((sxfer = STAILQ_FIRST(&xferq->stvalid)) != NULL) {
STAILQ_REMOVE_HEAD(&xferq->stvalid, link);
m = fw_iso_dequeue(xferq, sxfer, sc->fd.fc);
if (m == NULL)
continue;
fp = mtod(m, struct fw_pkt *);
plen = fp->mode.stream.len;
if (plen == 0) {
m_freem(m);
continue;
}
if (fp->mode.stream.sy == 1) {
if (sc->frame_offset > 0) {
if (sc->frame_offset == sc->frame_size) {
FWCAM_LOCK(sc);
if (sc->read_in_progress) {
sc->frame_dropped++;
} else {
if (sc->frame_ready)
sc->frame_dropped++;
tmp = sc->read_buf;
sc->read_buf = sc->frame_buf;
sc->frame_buf = tmp;
sc->frame_ready = 1;
wakeup(sc);
selwakeup(&sc->rsel);
}
FWCAM_UNLOCK(sc);
} else {
sc->frame_dropped++;
}
}
sc->frame_offset = 0;
}
if (m->m_len < 4) {
m_freem(m);
continue;
}
payload = mtod(m, uint8_t *) + 4;
if (plen > (uint32_t)(m->m_len - 4))
plen = m->m_len - 4;
if (sc->frame_offset + plen <= sc->frame_size) {
memcpy(sc->frame_buf + sc->frame_offset, payload, plen);
sc->frame_offset += plen;
} else {
sc->frame_dropped++;
sc->frame_offset = 0;
}
m_freem(m);
}
fw_iso_rearm_done(xferq, sc->fd.fc, &sc->mtx, &sc->iso_active,
&sc->dma_ch, dma_ch);
}
static int
fwcam_cdev_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
struct fwcam_softc *sc = dev->si_drv1;
int err;
FWCAM_LOCK(sc);
if (sc->state == FWCAM_STATE_DETACHING) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
if (sc->state == FWCAM_STATE_IDLE) {
sc->state = FWCAM_STATE_PROBING;
FWCAM_UNLOCK(sc);
taskqueue_enqueue(taskqueue_thread, &sc->probe_task);
FWCAM_LOCK(sc);
}
while (sc->state == FWCAM_STATE_PROBING) {
err = msleep(sc, &sc->mtx, PCATCH, "fwcampr", 10 * hz);
if (err) {
FWCAM_UNLOCK(sc);
return (err == EWOULDBLOCK ? ETIMEDOUT : err);
}
}
if (sc->state != FWCAM_STATE_PROBED &&
sc->state != FWCAM_STATE_STREAMING) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
sc->open_count++;
FWCAM_UNLOCK(sc);
return (0);
}
static int
fwcam_cdev_close(struct cdev *dev, int fflag, int devtype, struct thread *td)
{
struct fwcam_softc *sc = dev->si_drv1;
FWCAM_LOCK(sc);
sc->open_count--;
if (sc->open_count <= 0) {
sc->open_count = 0;
if (sc->state == FWCAM_STATE_STREAMING) {
FWCAM_UNLOCK(sc);
fwcam_iso_stop(sc);
FWCAM_LOCK(sc);
if (sc->state != FWCAM_STATE_DETACHING)
sc->state = FWCAM_STATE_PROBED;
}
}
FWCAM_UNLOCK(sc);
return (0);
}
static int
fwcam_cdev_read(struct cdev *dev, struct uio *uio, int ioflag)
{
struct fwcam_softc *sc = dev->si_drv1;
int err;
FWCAM_LOCK(sc);
if (sc->state == FWCAM_STATE_PROBED) {
FWCAM_UNLOCK(sc);
err = fwcam_iso_start(sc);
if (err)
return (err);
FWCAM_LOCK(sc);
}
while (!sc->frame_ready) {
if (sc->state != FWCAM_STATE_STREAMING) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
if (ioflag & FNONBLOCK) {
FWCAM_UNLOCK(sc);
return (EAGAIN);
}
err = msleep(sc, &sc->mtx, PCATCH, "fwcamrd", 5 * hz);
if (err) {
FWCAM_UNLOCK(sc);
return (err);
}
}
sc->frame_ready = 0;
if (sc->read_buf == NULL) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
sc->read_in_progress = 1;
FWCAM_UNLOCK(sc);
err = uiomove(sc->read_buf,
MIN(uio->uio_resid, sc->frame_size), uio);
FWCAM_LOCK(sc);
sc->read_in_progress = 0;
wakeup(&sc->read_in_progress);
FWCAM_UNLOCK(sc);
return (err);
}
static int
fwcam_cdev_poll(struct cdev *dev, int events, struct thread *td)
{
struct fwcam_softc *sc = dev->si_drv1;
int revents = 0;
FWCAM_LOCK(sc);
if (events & (POLLIN | POLLRDNORM)) {
if (sc->frame_ready)
revents |= events & (POLLIN | POLLRDNORM);
else
selrecord(td, &sc->rsel);
}
FWCAM_UNLOCK(sc);
return (revents);
}
static const uint32_t fwcam_feat_inq[] = {
[FWCAM_FEAT_BRIGHTNESS] = IIDC_BRIGHTNESS_INQ,
[FWCAM_FEAT_AUTO_EXPOSURE] = IIDC_AUTO_EXPOSURE_INQ,
[FWCAM_FEAT_SHARPNESS] = IIDC_SHARPNESS_INQ,
[FWCAM_FEAT_WHITE_BALANCE] = IIDC_WHITE_BAL_INQ,
[FWCAM_FEAT_HUE] = IIDC_HUE_INQ,
[FWCAM_FEAT_SATURATION] = IIDC_SATURATION_INQ,
[FWCAM_FEAT_GAMMA] = IIDC_GAMMA_INQ,
[FWCAM_FEAT_SHUTTER] = IIDC_SHUTTER_INQ,
[FWCAM_FEAT_GAIN] = IIDC_GAIN_INQ,
[FWCAM_FEAT_IRIS] = IIDC_IRIS_INQ,
[FWCAM_FEAT_FOCUS] = IIDC_FOCUS_INQ,
[FWCAM_FEAT_TEMPERATURE] = IIDC_TEMPERATURE_INQ,
[FWCAM_FEAT_TRIGGER] = IIDC_TRIGGER_INQ,
};
static const uint32_t fwcam_feat_ctrl[] = {
[FWCAM_FEAT_BRIGHTNESS] = IIDC_BRIGHTNESS,
[FWCAM_FEAT_AUTO_EXPOSURE] = IIDC_AUTO_EXPOSURE,
[FWCAM_FEAT_SHARPNESS] = IIDC_SHARPNESS,
[FWCAM_FEAT_WHITE_BALANCE] = IIDC_WHITE_BALANCE,
[FWCAM_FEAT_HUE] = IIDC_HUE,
[FWCAM_FEAT_SATURATION] = IIDC_SATURATION,
[FWCAM_FEAT_GAMMA] = IIDC_GAMMA,
[FWCAM_FEAT_SHUTTER] = IIDC_SHUTTER,
[FWCAM_FEAT_GAIN] = IIDC_GAIN,
[FWCAM_FEAT_IRIS] = IIDC_IRIS,
[FWCAM_FEAT_FOCUS] = IIDC_FOCUS,
[FWCAM_FEAT_TEMPERATURE] = IIDC_TEMPERATURE,
[FWCAM_FEAT_TRIGGER] = IIDC_TRIGGER_MODE,
[FWCAM_FEAT_ZOOM] = IIDC_ZOOM,
[FWCAM_FEAT_PAN] = IIDC_PAN,
[FWCAM_FEAT_TILT] = IIDC_TILT,
};
static int
fwcam_get_feature(struct fwcam_softc *sc, struct fwcam_feature *feat)
{
uint32_t inq, ctrl;
int err;
if (feat->id >= FWCAM_FEAT_MAX)
return (EINVAL);
feat->flags = 0;
feat->min = 0;
feat->max = 0;
if (feat->id < nitems(fwcam_feat_inq) &&
fwcam_feat_inq[feat->id] != 0) {
err = fwcam_read_quadlet(sc, fwcam_feat_inq[feat->id], &inq);
if (err)
return (err);
if (inq & (1 << 31))
feat->flags |= FWCAM_FEATF_PRESENT;
if (inq & (1 << 28))
feat->flags |= FWCAM_FEATF_ONOFF;
if (inq & (1 << 27))
feat->flags |= FWCAM_FEATF_AUTO;
if (inq & (1 << 26))
feat->flags |= FWCAM_FEATF_MANUAL;
feat->min = (inq >> 12) & 0xfff;
feat->max = inq & 0xfff;
}
if (feat->id >= nitems(fwcam_feat_ctrl) ||
fwcam_feat_ctrl[feat->id] == 0)
return (EINVAL);
err = fwcam_read_quadlet(sc, fwcam_feat_ctrl[feat->id], &ctrl);
if (err)
return (err);
feat->value = ctrl & 0xfff;
if (feat->id == FWCAM_FEAT_WHITE_BALANCE)
feat->value2 = (ctrl >> 12) & 0xfff;
else
feat->value2 = 0;
return (0);
}
static int
fwcam_set_feature(struct fwcam_softc *sc, struct fwcam_feature *feat)
{
uint32_t ctrl, val;
int err;
if (feat->id >= FWCAM_FEAT_MAX)
return (EINVAL);
if (feat->id >= nitems(fwcam_feat_ctrl) ||
fwcam_feat_ctrl[feat->id] == 0)
return (EINVAL);
err = fwcam_read_quadlet(sc, fwcam_feat_ctrl[feat->id], &ctrl);
if (err)
return (err);
if (feat->id == FWCAM_FEAT_WHITE_BALANCE) {
val = (ctrl & 0xff000000) |
((feat->value2 & 0xfff) << 12) |
(feat->value & 0xfff);
} else {
val = (ctrl & 0xfffff000) | (feat->value & 0xfff);
}
return (fwcam_write_quadlet(sc, fwcam_feat_ctrl[feat->id], val));
}
static int
fwcam_cdev_ioctl(struct cdev *dev, u_long cmd, caddr_t data,
int fflag, struct thread *td)
{
struct fwcam_softc *sc = dev->si_drv1;
struct fwcam_mode *mode;
struct fwcam_feature *feat;
struct fwcam_info *info;
int err;
if (sc->fwdev == NULL)
return (ENXIO);
switch (cmd) {
case FWCAM_GMODE:
mode = (struct fwcam_mode *)data;
mode->format = sc->cur_format;
mode->mode = sc->cur_mode;
mode->framerate = sc->cur_framerate;
mode->frame_size = sc->frame_size ?
sc->frame_size : fwcam_frame_size(sc);
return (0);
case FWCAM_SMODE:
{
int was_streaming = 0;
mode = (struct fwcam_mode *)data;
if (mode->format > 7 || mode->mode > 7 || mode->framerate > 7)
return (EINVAL);
if (!(sc->formats & (1 << (31 - mode->format))))
return (EINVAL);
if (!(sc->modes[mode->format] & (1 << (31 - mode->mode))))
return (EINVAL);
if (!(sc->rates[mode->format][mode->mode] &
(1 << (31 - mode->framerate))))
return (EINVAL);
FWCAM_LOCK(sc);
if (sc->state == FWCAM_STATE_DETACHING) {
FWCAM_UNLOCK(sc);
return (ENXIO);
}
if (sc->state == FWCAM_STATE_STREAMING) {
was_streaming = 1;
FWCAM_UNLOCK(sc);
fwcam_iso_stop(sc);
FWCAM_LOCK(sc);
if (sc->state != FWCAM_STATE_DETACHING)
sc->state = FWCAM_STATE_PROBED;
}
FWCAM_UNLOCK(sc);
err = fwcam_write_quadlet(sc, IIDC_CUR_V_FORMAT,
(uint32_t)mode->format << IIDC_CUR_V_SHIFT);
if (err == 0)
err = fwcam_write_quadlet(sc, IIDC_CUR_V_MODE,
(uint32_t)mode->mode << IIDC_CUR_V_SHIFT);
if (err == 0)
err = fwcam_write_quadlet(sc, IIDC_CUR_V_FRM_RATE,
(uint32_t)mode->framerate << IIDC_CUR_V_SHIFT);
if (err == 0) {
sc->cur_format = mode->format;
sc->cur_mode = mode->mode;
sc->cur_framerate = mode->framerate;
mode->frame_size = fwcam_frame_size(sc);
}
if (was_streaming)
fwcam_iso_start(sc);
return (err);
}
case FWCAM_GFEAT:
feat = (struct fwcam_feature *)data;
return (fwcam_get_feature(sc, feat));
case FWCAM_SFEAT:
feat = (struct fwcam_feature *)data;
return (fwcam_set_feature(sc, feat));
case FWCAM_GINFO:
info = (struct fwcam_info *)data;
info->formats = sc->formats;
info->basic_func = sc->basic_func;
info->features_hi = sc->features_hi;
info->features_lo = sc->features_lo;
info->cur_format = sc->cur_format;
info->cur_mode = sc->cur_mode;
info->cur_framerate = sc->cur_framerate;
info->state = sc->state;
info->frame_size = sc->frame_size ?
sc->frame_size : fwcam_frame_size(sc);
info->frame_dropped = sc->frame_dropped;
info->iso_channel = sc->iso_channel;
info->_pad[0] = info->_pad[1] = info->_pad[2] = 0;
return (0);
default:
return (ENOTTY);
}
}
static int
fwcam_probe(device_t dev)
{
struct fw_unit *unit;
unit = fw_get_unit(dev);
if (unit == NULL)
return (ENXIO);
if (unit->spec_id != CSRVAL_1394TA)
return (ENXIO);
if (unit->sw_version != CSR_PROTCAM130 &&
unit->sw_version != CSR_PROTCAM120 &&
unit->sw_version != CSR_PROTCAM104)
return (ENXIO);
device_set_desc(dev, "IIDC Digital Camera over FireWire");
return (BUS_PROBE_DEFAULT);
}
static int
fwcam_attach(device_t dev)
{
struct fwcam_softc *sc;
struct fw_unit *unit;
uint32_t cmd_base;
unit = fw_get_unit(dev);
if (unit == NULL || unit->fwdev == NULL)
return (ENXIO);
cmd_base = fwcam_find_iidc_cmd_base(unit);
if (cmd_base == 0) {
device_printf(dev, "no IIDC command base in unit directory\n");
return (ENXIO);
}
sc = device_get_softc(dev);
sc->fd.dev = dev;
sc->fd.fc = fw_get_comm(dev);
mtx_init(&sc->mtx, "fwcam", NULL, MTX_DEF);
sc->fwdev = unit->fwdev;
sc->cmd_hi = 0xffff;
sc->cmd_lo = 0xf0000000 | (cmd_base << 2);
sc->iso_speed = min(unit->fwdev->speed, FWSPD_S400);
sc->state = FWCAM_STATE_IDLE;
sc->dma_ch = -1;
sc->iso_active = 0;
sc->open_count = 0;
knlist_init_mtx(&sc->rsel.si_note, &sc->mtx);
TASK_INIT(&sc->probe_task, 0, fwcam_probe_task, sc);
sc->cdev = make_dev(&fwcam_cdevsw, device_get_unit(dev),
UID_ROOT, GID_OPERATOR, 0660, "fwcam%d", device_get_unit(dev));
sc->cdev->si_drv1 = sc;
return (0);
}
static int
fwcam_detach(device_t dev)
{
struct fwcam_softc *sc;
sc = device_get_softc(dev);
FWCAM_LOCK(sc);
sc->state = FWCAM_STATE_DETACHING;
wakeup(sc);
FWCAM_UNLOCK(sc);
taskqueue_drain(taskqueue_thread, &sc->probe_task);
fwcam_iso_stop(sc);
if (sc->cdev != NULL)
destroy_dev(sc->cdev);
seldrain(&sc->rsel);
knlist_destroy(&sc->rsel.si_note);
FWCAM_LOCK(sc);
sc->fwdev = NULL;
FWCAM_UNLOCK(sc);
mtx_destroy(&sc->mtx);
return (0);
}
static device_method_t fwcam_methods[] = {
DEVMETHOD(device_probe, fwcam_probe),
DEVMETHOD(device_attach, fwcam_attach),
DEVMETHOD(device_detach, fwcam_detach),
DEVMETHOD_END
};
static driver_t fwcam_driver = {
"fwcam",
fwcam_methods,
sizeof(struct fwcam_softc),
};
DRIVER_MODULE(fwcam, firewire, fwcam_driver, 0, 0);
MODULE_VERSION(fwcam, 1);
MODULE_DEPEND(fwcam, firewire, 1, 1, 1);