root/sound/firewire/bebob/bebob_command.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * bebob_command.c - driver for BeBoB based devices
 *
 * Copyright (c) 2013-2014 Takashi Sakamoto
 */

#include "./bebob.h"

int avc_audio_set_selector(struct fw_unit *unit, unsigned int subunit_id,
                           unsigned int fb_id, unsigned int num)
{
        u8 *buf;
        int err;

        buf = kzalloc(12, GFP_KERNEL);
        if (buf == NULL)
                return -ENOMEM;

        buf[0]  = 0x00;         /* AV/C CONTROL */
        buf[1]  = 0x08 | (0x07 & subunit_id);   /* AUDIO SUBUNIT ID */
        buf[2]  = 0xb8;         /* FUNCTION BLOCK  */
        buf[3]  = 0x80;         /* type is 'selector'*/
        buf[4]  = 0xff & fb_id; /* function block id */
        buf[5]  = 0x10;         /* control attribute is CURRENT */
        buf[6]  = 0x02;         /* selector length is 2 */
        buf[7]  = 0xff & num;   /* input function block plug number */
        buf[8]  = 0x01;         /* control selector is SELECTOR_CONTROL */

        err = fcp_avc_transaction(unit, buf, 12, buf, 12,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
                                  BIT(6) | BIT(7) | BIT(8));
        if (err < 0)
                ;
        else if (err < 9)
                err = -EIO;
        else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
                err = -ENOSYS;
        else if (buf[0] == 0x0a) /* REJECTED */
                err = -EINVAL;
        else
                err = 0;

        kfree(buf);
        return err;
}

int avc_audio_get_selector(struct fw_unit *unit, unsigned int subunit_id,
                           unsigned int fb_id, unsigned int *num)
{
        u8 *buf;
        int err;

        buf = kzalloc(12, GFP_KERNEL);
        if (buf == NULL)
                return -ENOMEM;

        buf[0]  = 0x01;         /* AV/C STATUS */
        buf[1]  = 0x08 | (0x07 & subunit_id);   /* AUDIO SUBUNIT ID */
        buf[2]  = 0xb8;         /* FUNCTION BLOCK */
        buf[3]  = 0x80;         /* type is 'selector'*/
        buf[4]  = 0xff & fb_id; /* function block id */
        buf[5]  = 0x10;         /* control attribute is CURRENT */
        buf[6]  = 0x02;         /* selector length is 2 */
        buf[7]  = 0xff;         /* input function block plug number */
        buf[8]  = 0x01;         /* control selector is SELECTOR_CONTROL */

        err = fcp_avc_transaction(unit, buf, 12, buf, 12,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
                                  BIT(6) | BIT(8));
        if (err < 0)
                ;
        else if (err < 9)
                err = -EIO;
        else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
                err = -ENOSYS;
        else if (buf[0] == 0x0a) /* REJECTED */
                err = -EINVAL;
        else if (buf[0] == 0x0b) /* IN TRANSITION */
                err = -EAGAIN;
        if (err < 0)
                goto end;

        *num = buf[7];
        err = 0;
end:
        kfree(buf);
        return err;
}

static inline void
avc_bridgeco_fill_extension_addr(u8 *buf, u8 *addr)
{
        buf[1] = addr[0];
        memcpy(buf + 4, addr + 1, 5);
}

static inline void
avc_bridgeco_fill_plug_info_extension_command(u8 *buf, u8 *addr,
                                              unsigned int itype)
{
        buf[0] = 0x01;  /* AV/C STATUS */
        buf[2] = 0x02;  /* AV/C GENERAL PLUG INFO */
        buf[3] = 0xc0;  /* BridgeCo extension */
        avc_bridgeco_fill_extension_addr(buf, addr);
        buf[9] = itype; /* info type */
}

int avc_bridgeco_get_plug_type(struct fw_unit *unit,
                               u8 addr[AVC_BRIDGECO_ADDR_BYTES],
                               enum avc_bridgeco_plug_type *type)
{
        u8 *buf;
        int err;

        buf = kzalloc(12, GFP_KERNEL);
        if (buf == NULL)
                return -ENOMEM;

        /* Info type is 'plug type'. */
        avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x00);

        err = fcp_avc_transaction(unit, buf, 12, buf, 12,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
                                  BIT(6) | BIT(7) | BIT(9));
        if (err < 0)
                ;
        else if (err < 11)
                err = -EIO;
        else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
                err = -ENOSYS;
        else if (buf[0] == 0x0a) /* REJECTED */
                err = -EINVAL;
        else if (buf[0] == 0x0b) /* IN TRANSITION */
                err = -EAGAIN;
        if (err < 0)
                goto end;

        *type = buf[10];
        err = 0;
end:
        kfree(buf);
        return err;
}

int avc_bridgeco_get_plug_ch_count(struct fw_unit *unit, u8 addr[AVC_BRIDGECO_ADDR_BYTES],
                                   unsigned int *ch_count)
{
        u8 *buf;
        int err;

        buf = kzalloc(12, GFP_KERNEL);
        if (buf == NULL)
                return -ENOMEM;

        // Info type is 'plug type'.
        avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x02);

        err = fcp_avc_transaction(unit, buf, 12, buf, 12,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
                                  BIT(6) | BIT(7) | BIT(9));
        if (err < 0)
                ;
        else if (err < 11)
                err = -EIO;
        else if (buf[0] == 0x08) // NOT IMPLEMENTED
                err = -ENOSYS;
        else if (buf[0] == 0x0a) // REJECTED
                err = -EINVAL;
        else if (buf[0] == 0x0b) // IN TRANSITION
                err = -EAGAIN;
        if (err < 0)
                goto end;

        *ch_count = buf[10];
        err = 0;
end:
        kfree(buf);
        return err;
}

int avc_bridgeco_get_plug_ch_pos(struct fw_unit *unit,
                                 u8 addr[AVC_BRIDGECO_ADDR_BYTES],
                                 u8 *buf, unsigned int len)
{
        int err;

        /* Info type is 'channel position'. */
        avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x03);

        err = fcp_avc_transaction(unit, buf, 12, buf, 256,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) |
                                  BIT(5) | BIT(6) | BIT(7) | BIT(9));
        if (err < 0)
                ;
        else if (err < 11)
                err = -EIO;
        else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
                err = -ENOSYS;
        else if (buf[0] == 0x0a) /* REJECTED */
                err = -EINVAL;
        else if (buf[0] == 0x0b) /* IN TRANSITION */
                err = -EAGAIN;
        if (err < 0)
                goto end;

        /* Pick up specific data. */
        memmove(buf, buf + 10, err - 10);
        err = 0;
end:
        return err;
}

int avc_bridgeco_get_plug_section_type(struct fw_unit *unit,
                                       u8 addr[AVC_BRIDGECO_ADDR_BYTES],
                                       unsigned int id, u8 *type)
{
        u8 *buf;
        int err;

        /* section info includes charactors but this module don't need it */
        buf = kzalloc(12, GFP_KERNEL);
        if (buf == NULL)
                return -ENOMEM;

        /* Info type is 'section info'. */
        avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x07);
        buf[10] = 0xff & ++id;  /* section id */

        err = fcp_avc_transaction(unit, buf, 12, buf, 12,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
                                  BIT(6) | BIT(7) | BIT(9) | BIT(10));
        if (err < 0)
                ;
        else if (err < 12)
                err = -EIO;
        else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
                err = -ENOSYS;
        else if (buf[0] == 0x0a) /* REJECTED */
                err = -EINVAL;
        else if (buf[0] == 0x0b) /* IN TRANSITION */
                err = -EAGAIN;
        if (err < 0)
                goto end;

        *type = buf[11];
        err = 0;
end:
        kfree(buf);
        return err;
}

int avc_bridgeco_get_plug_input(struct fw_unit *unit,
                                u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 input[7])
{
        int err;
        u8 *buf;

        buf = kzalloc(18, GFP_KERNEL);
        if (buf == NULL)
                return -ENOMEM;

        /* Info type is 'plug input'. */
        avc_bridgeco_fill_plug_info_extension_command(buf, addr, 0x05);

        err = fcp_avc_transaction(unit, buf, 16, buf, 16,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
                                  BIT(6) | BIT(7));
        if (err < 0)
                ;
        else if (err < 16)
                err = -EIO;
        else if (buf[0] == 0x08) /* NOT IMPLEMENTED */
                err = -ENOSYS;
        else if (buf[0] == 0x0a) /* REJECTED */
                err = -EINVAL;
        else if (buf[0] == 0x0b) /* IN TRANSITION */
                err = -EAGAIN;
        if (err < 0)
                goto end;

        memcpy(input, buf + 10, 5);
        err = 0;
end:
        kfree(buf);
        return err;
}

int avc_bridgeco_get_plug_strm_fmt(struct fw_unit *unit,
                                   u8 addr[AVC_BRIDGECO_ADDR_BYTES], u8 *buf,
                                   unsigned int *len, unsigned int eid)
{
        int err;

        /* check given buffer */
        if ((buf == NULL) || (*len < 12)) {
                err = -EINVAL;
                goto end;
        }

        buf[0] = 0x01;  /* AV/C STATUS */
        buf[2] = 0x2f;  /* AV/C STREAM FORMAT SUPPORT */
        buf[3] = 0xc1;  /* Bridgeco extension - List Request */
        avc_bridgeco_fill_extension_addr(buf, addr);
        buf[10] = 0xff & eid;   /* Entry ID */

        err = fcp_avc_transaction(unit, buf, 12, buf, *len,
                                  BIT(1) | BIT(2) | BIT(3) | BIT(4) | BIT(5) |
                                  BIT(6) | BIT(7) | BIT(10));
        if (err < 0)
                ;
        else if (err < 12)
                err = -EIO;
        else if (buf[0] == 0x08)        /* NOT IMPLEMENTED */
                err = -ENOSYS;
        else if (buf[0] == 0x0a)        /* REJECTED */
                err = -EINVAL;
        else if (buf[0] == 0x0b)        /* IN TRANSITION */
                err = -EAGAIN;
        else if (buf[10] != eid)
                err = -EIO;
        if (err < 0)
                goto end;

        /* Pick up 'stream format info'. */
        memmove(buf, buf + 11, err - 11);
        *len = err - 11;
        err = 0;
end:
        return err;
}