mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-27 06:04:23 +08:00
usb: gadget: midi2: More flexible MIDI 1.0 configuration
This patch allows users to set up MIDI 1.0 ports more flexibly. Namely, instead of the fixed mapping only from FB 0, now multiple block definitions are applied to build up the MIDI 1.0 mapping. The each block config has midi1_first_group and midi1_num_groups attributes, and those specify which Groups are used for MIDI 1.0. Those fields must be within the UMP Groups defined in the block itself. Signed-off-by: Takashi Iwai <tiwai@suse.de> Link: https://lore.kernel.org/r/20230725062206.9674-8-tiwai@suse.de Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
1b437d2fb3
commit
a85ff0db48
@ -39,14 +39,16 @@ Description:
|
||||
|
||||
The attributes:
|
||||
|
||||
=============== ===============================================
|
||||
name Function Block name string
|
||||
direction 1: input, 2: output, 3: bidirectional
|
||||
first_group The first UMP Group number (0-15)
|
||||
num_groups The number of groups in this FB (1-16)
|
||||
ui_hint 0: unknown, 1: receiver, 2: sender, 3: both
|
||||
midi_ci_verison Supported MIDI-CI version number (8 bit)
|
||||
is_midi1 Legacy MIDI 1.0 device (0, 1 or 2)
|
||||
sysex8_streams Max number of SysEx8 streams (8 bit)
|
||||
active Active FB flag (0 or 1)
|
||||
=============== ===============================================
|
||||
================= ==============================================
|
||||
name Function Block name string
|
||||
direction 1: input, 2: output, 3: bidirectional
|
||||
first_group The first UMP Group number (0-15)
|
||||
num_groups The number of groups in this FB (1-16)
|
||||
midi1_first_group The first UMP Group number for MIDI 1.0 (0-15)
|
||||
midi1_num_groups The number of groups for MIDI 1.0 (0-16)
|
||||
ui_hint 0: unknown, 1: receiver, 2: sender, 3: both
|
||||
midi_ci_verison Supported MIDI-CI version number (8 bit)
|
||||
is_midi1 Legacy MIDI 1.0 device (0, 1 or 2)
|
||||
sysex8_streams Max number of SysEx8 streams (8 bit)
|
||||
active Active FB flag (0 or 1)
|
||||
================= ==============================================
|
||||
|
@ -1009,22 +1009,24 @@ Each Endpoint subdirectory contains a subdirectory "block.0", which
|
||||
represents the Function Block for Block 0 information.
|
||||
Its attributes are:
|
||||
|
||||
=============== ===============================================
|
||||
name Function Block name string
|
||||
direction Direction of this FB
|
||||
1: input, 2: output, or 3: bidirectional
|
||||
first_group The first UMP Group number (0-15)
|
||||
num_groups The number of groups in this FB (1-16)
|
||||
ui_hint UI-hint of this FB
|
||||
0: unknown, 1: receiver, 2: sender, 3: both
|
||||
midi_ci_verison Supported MIDI-CI version number (8 bit)
|
||||
is_midi1 Legacy MIDI 1.0 device (0-2)
|
||||
0: MIDI 2.0 device,
|
||||
1: MIDI 1.0 without restriction, or
|
||||
2: MIDI 1.0 with low speed
|
||||
sysex8_streams Max number of SysEx8 streams (8 bit)
|
||||
active Bool flag for FB activity (0 or 1)
|
||||
=============== ===============================================
|
||||
================= ===============================================
|
||||
name Function Block name string
|
||||
direction Direction of this FB
|
||||
1: input, 2: output, or 3: bidirectional
|
||||
first_group The first UMP Group number (0-15)
|
||||
num_groups The number of groups in this FB (1-16)
|
||||
midi1_first_group The first UMP Group number for MIDI 1.0 (0-15)
|
||||
midi1_num_groups The number of groups for MIDI 1.0 (0-16)
|
||||
ui_hint UI-hint of this FB
|
||||
0: unknown, 1: receiver, 2: sender, 3: both
|
||||
midi_ci_verison Supported MIDI-CI version number (8 bit)
|
||||
is_midi1 Legacy MIDI 1.0 device (0-2)
|
||||
0: MIDI 2.0 device,
|
||||
1: MIDI 1.0 without restriction, or
|
||||
2: MIDI 1.0 with low speed
|
||||
sysex8_streams Max number of SysEx8 streams (8 bit)
|
||||
active Bool flag for FB activity (0 or 1)
|
||||
================= ===============================================
|
||||
|
||||
If multiple Function Blocks are required, you can add more Function
|
||||
Blocks by creating subdirectories "block.<num>" with the corresponding
|
||||
|
@ -84,6 +84,8 @@ struct f_midi2_ep {
|
||||
|
||||
struct f_midi2_usb_ep ep_in; /* USB MIDI EP-in */
|
||||
struct f_midi2_usb_ep ep_out; /* USB MIDI EP-out */
|
||||
|
||||
u8 in_group_to_cable[SNDRV_UMP_MAX_GROUPS]; /* map to cable; 1-based! */
|
||||
};
|
||||
|
||||
/* indices for USB strings */
|
||||
@ -95,6 +97,13 @@ enum {
|
||||
/* 1-based GTB id to string id */
|
||||
#define gtb_to_str_id(id) (STR_GTB1 + (id) - 1)
|
||||
|
||||
/* mapping from MIDI 1.0 cable to UMP group */
|
||||
struct midi1_cable_mapping {
|
||||
struct f_midi2_ep *ep;
|
||||
unsigned char block;
|
||||
unsigned char group;
|
||||
};
|
||||
|
||||
/* operation mode */
|
||||
enum {
|
||||
MIDI_OP_MODE_UNSET, /* no altset set yet */
|
||||
@ -112,10 +121,17 @@ struct f_midi2 {
|
||||
struct f_midi2_usb_ep midi1_ep_in;
|
||||
struct f_midi2_usb_ep midi1_ep_out;
|
||||
|
||||
/* number of MIDI 1.0 I/O cables */
|
||||
unsigned int num_midi1_in;
|
||||
unsigned int num_midi1_out;
|
||||
|
||||
/* conversion for MIDI 1.0 EP-in */
|
||||
struct f_midi2_midi1_port midi1_port[MAX_CABLES];
|
||||
/* conversion for MIDI 1.0 EP-out */
|
||||
struct ump_cvt_to_ump midi1_ump_cvt;
|
||||
/* mapping between cables and UMP groups */
|
||||
struct midi1_cable_mapping in_cable_mapping[MAX_CABLES];
|
||||
struct midi1_cable_mapping out_cable_mapping[MAX_CABLES];
|
||||
|
||||
int midi_if; /* USB MIDI interface number */
|
||||
int operation_mode; /* current operation mode */
|
||||
@ -917,8 +933,7 @@ static bool process_midi1_pending_buf(struct f_midi2 *midi2,
|
||||
{
|
||||
unsigned int cable, c;
|
||||
|
||||
for (cable = 0; cable < midi2->midi2_eps[0].blks[0].info.num_groups;
|
||||
cable++) {
|
||||
for (cable = 0; cable < midi2->num_midi1_in; cable++) {
|
||||
struct f_midi2_midi1_port *port = &midi2->midi1_port[cable];
|
||||
|
||||
if (!port->pending)
|
||||
@ -960,8 +975,8 @@ static void process_midi1_transmit(struct f_midi2 *midi2)
|
||||
struct usb_request *req = NULL;
|
||||
/* 12 is the largest outcome (4 MIDI1 cmds) for a single UMP packet */
|
||||
unsigned char outbuf[12];
|
||||
unsigned char group;
|
||||
int len, size, cable;
|
||||
unsigned char group, cable;
|
||||
int len, size;
|
||||
u32 ump;
|
||||
|
||||
if (!usb_ep->usb_ep || !usb_ep->usb_ep->enabled)
|
||||
@ -986,9 +1001,10 @@ static void process_midi1_transmit(struct f_midi2 *midi2)
|
||||
&group);
|
||||
if (size <= 0)
|
||||
continue;
|
||||
cable = group - ep->blks[0].info.first_group;
|
||||
if (cable < 0 || cable >= ep->blks[0].info.num_groups)
|
||||
cable = ep->in_group_to_cable[group];
|
||||
if (!cable)
|
||||
continue;
|
||||
cable--; /* to 0-base */
|
||||
fill_midi1_pending_buf(midi2, cable, outbuf, size);
|
||||
}
|
||||
|
||||
@ -1025,12 +1041,12 @@ static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep,
|
||||
{
|
||||
struct f_midi2_req_ctx *ctx = req->context;
|
||||
struct f_midi2 *midi2 = ctx->usb_ep->card;
|
||||
struct f_midi2_ep *ep = &midi2->midi2_eps[0];
|
||||
struct f_midi2_ep *ep;
|
||||
struct ump_cvt_to_ump *cvt = &midi2->midi1_ump_cvt;
|
||||
static const u8 midi1_packet_bytes[16] = {
|
||||
0, 0, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1
|
||||
};
|
||||
unsigned int group, bytes, c, len;
|
||||
unsigned int group, cable, bytes, c, len;
|
||||
int status = req->status;
|
||||
const u8 *buf = req->buf;
|
||||
|
||||
@ -1042,10 +1058,11 @@ static void f_midi2_midi1_ep_out_complete(struct usb_ep *usb_ep,
|
||||
|
||||
len = req->actual >> 2;
|
||||
for (; len; len--, buf += 4) {
|
||||
group = *buf >> 4;
|
||||
if (group >= ep->blks[0].info.num_groups)
|
||||
cable = *buf >> 4;
|
||||
ep = midi2->out_cable_mapping[cable].ep;
|
||||
if (!ep)
|
||||
continue;
|
||||
group += ep->blks[0].info.first_group;
|
||||
group = midi2->out_cable_mapping[cable].group;
|
||||
bytes = midi1_packet_bytes[*buf & 0x0f];
|
||||
for (c = 0; c < bytes; c++) {
|
||||
snd_ump_convert_to_ump(cvt, group, ep->info.protocol,
|
||||
@ -1641,6 +1658,7 @@ static int append_configs(struct f_midi2_usb_config *config, void **d)
|
||||
|
||||
static int append_midi1_in_jack(struct f_midi2 *midi2,
|
||||
struct f_midi2_usb_config *config,
|
||||
struct midi1_cable_mapping *map,
|
||||
unsigned int type)
|
||||
{
|
||||
struct usb_midi_in_jack_descriptor *jack =
|
||||
@ -1653,7 +1671,9 @@ static int append_midi1_in_jack(struct f_midi2 *midi2,
|
||||
jack->bDescriptorSubtype = USB_MS_MIDI_IN_JACK;
|
||||
jack->bJackType = type;
|
||||
jack->bJackID = id;
|
||||
jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names?
|
||||
/* use the corresponding block name as jack name */
|
||||
if (map->ep)
|
||||
jack->iJack = map->ep->blks[map->block].string_id;
|
||||
|
||||
err = append_config(config, jack);
|
||||
if (err < 0)
|
||||
@ -1663,6 +1683,7 @@ static int append_midi1_in_jack(struct f_midi2 *midi2,
|
||||
|
||||
static int append_midi1_out_jack(struct f_midi2 *midi2,
|
||||
struct f_midi2_usb_config *config,
|
||||
struct midi1_cable_mapping *map,
|
||||
unsigned int type, unsigned int source)
|
||||
{
|
||||
struct usb_midi_out_jack_descriptor_1 *jack =
|
||||
@ -1678,7 +1699,9 @@ static int append_midi1_out_jack(struct f_midi2 *midi2,
|
||||
jack->bNrInputPins = 1;
|
||||
jack->pins[0].baSourceID = source;
|
||||
jack->pins[0].baSourcePin = 0x01;
|
||||
jack->iJack = midi2->strings[STR_GTB1].id; // TODO: better names?
|
||||
/* use the corresponding block name as jack name */
|
||||
if (map->ep)
|
||||
jack->iJack = map->ep->blks[map->block].string_id;
|
||||
|
||||
err = append_config(config, jack);
|
||||
if (err < 0)
|
||||
@ -1690,7 +1713,6 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
|
||||
struct f_midi2_usb_config *config,
|
||||
int speed)
|
||||
{
|
||||
struct f_midi2_block *blk = &midi2->midi2_eps[0].blks[0];
|
||||
void **midi1_in_eps, **midi1_out_eps;
|
||||
int i, jack, total;
|
||||
int err;
|
||||
@ -1724,56 +1746,55 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
switch (blk->info.direction) {
|
||||
case SNDRV_UMP_DIR_INPUT:
|
||||
case SNDRV_UMP_DIR_OUTPUT:
|
||||
midi2_midi1_if_desc.bNumEndpoints = 1;
|
||||
break;
|
||||
default:
|
||||
if (midi2->num_midi1_in && midi2->num_midi1_out)
|
||||
midi2_midi1_if_desc.bNumEndpoints = 2;
|
||||
break;
|
||||
}
|
||||
else
|
||||
midi2_midi1_if_desc.bNumEndpoints = 1;
|
||||
|
||||
err = append_configs(config, midi2_midi1_descs);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
total = USB_DT_MS_HEADER_SIZE;
|
||||
if (blk->info.direction != SNDRV_UMP_DIR_INPUT) {
|
||||
if (midi2->num_midi1_out) {
|
||||
midi2_midi1_ep_out_class_desc.bLength =
|
||||
USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups);
|
||||
USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_out);
|
||||
total += midi2_midi1_ep_out_class_desc.bLength;
|
||||
midi2_midi1_ep_out_class_desc.bNumEmbMIDIJack =
|
||||
blk->info.num_groups;
|
||||
total += blk->info.num_groups *
|
||||
midi2->num_midi1_out;
|
||||
total += midi2->num_midi1_out *
|
||||
(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
|
||||
for (i = 0; i < blk->info.num_groups; i++) {
|
||||
for (i = 0; i < midi2->num_midi1_out; i++) {
|
||||
jack = append_midi1_in_jack(midi2, config,
|
||||
&midi2->in_cable_mapping[i],
|
||||
USB_MS_EMBEDDED);
|
||||
if (jack < 0)
|
||||
return jack;
|
||||
midi2_midi1_ep_out_class_desc.baAssocJackID[i] = jack;
|
||||
jack = append_midi1_out_jack(midi2, config,
|
||||
&midi2->in_cable_mapping[i],
|
||||
USB_MS_EXTERNAL, jack);
|
||||
if (jack < 0)
|
||||
return jack;
|
||||
}
|
||||
}
|
||||
|
||||
if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) {
|
||||
if (midi2->num_midi1_in) {
|
||||
midi2_midi1_ep_in_class_desc.bLength =
|
||||
USB_DT_MS_ENDPOINT_SIZE(blk->info.num_groups);
|
||||
USB_DT_MS_ENDPOINT_SIZE(midi2->num_midi1_in);
|
||||
total += midi2_midi1_ep_in_class_desc.bLength;
|
||||
midi2_midi1_ep_in_class_desc.bNumEmbMIDIJack =
|
||||
blk->info.num_groups;
|
||||
total += blk->info.num_groups *
|
||||
midi2->num_midi1_in;
|
||||
total += midi2->num_midi1_in *
|
||||
(USB_DT_MIDI_IN_SIZE + USB_DT_MIDI_OUT_SIZE(1));
|
||||
for (i = 0; i < blk->info.num_groups; i++) {
|
||||
for (i = 0; i < midi2->num_midi1_in; i++) {
|
||||
jack = append_midi1_in_jack(midi2, config,
|
||||
&midi2->out_cable_mapping[i],
|
||||
USB_MS_EXTERNAL);
|
||||
if (jack < 0)
|
||||
return jack;
|
||||
jack = append_midi1_out_jack(midi2, config,
|
||||
&midi2->out_cable_mapping[i],
|
||||
USB_MS_EMBEDDED, jack);
|
||||
if (jack < 0)
|
||||
return jack;
|
||||
@ -1783,12 +1804,12 @@ static int f_midi2_create_usb_configs(struct f_midi2 *midi2,
|
||||
|
||||
midi2_midi1_class_desc.wTotalLength = cpu_to_le16(total);
|
||||
|
||||
if (blk->info.direction != SNDRV_UMP_DIR_INPUT) {
|
||||
if (midi2->num_midi1_out) {
|
||||
err = append_configs(config, midi1_out_eps);
|
||||
if (err < 0)
|
||||
return err;
|
||||
}
|
||||
if (blk->info.direction != SNDRV_UMP_DIR_OUTPUT) {
|
||||
if (midi2->num_midi1_in) {
|
||||
err = append_configs(config, midi1_in_eps);
|
||||
if (err < 0)
|
||||
return err;
|
||||
@ -2236,6 +2257,8 @@ CONFIGFS_ATTR(f_midi2_block_opts_, name)
|
||||
F_MIDI2_BLOCK_OPT(direction, "0x%x", 1, 3);
|
||||
F_MIDI2_BLOCK_OPT(first_group, "0x%x", 0, 15);
|
||||
F_MIDI2_BLOCK_OPT(num_groups, "0x%x", 1, 16);
|
||||
F_MIDI2_BLOCK_OPT(midi1_first_group, "0x%x", 0, 15);
|
||||
F_MIDI2_BLOCK_OPT(midi1_num_groups, "0x%x", 0, 16);
|
||||
F_MIDI2_BLOCK_OPT(ui_hint, "0x%x", 0, 3);
|
||||
F_MIDI2_BLOCK_OPT(midi_ci_version, "%u", 0, 1);
|
||||
F_MIDI2_BLOCK_OPT(sysex8_streams, "%u", 0, 255);
|
||||
@ -2265,6 +2288,8 @@ static struct configfs_attribute *f_midi2_block_attrs[] = {
|
||||
&f_midi2_block_opts_attr_direction,
|
||||
&f_midi2_block_opts_attr_first_group,
|
||||
&f_midi2_block_opts_attr_num_groups,
|
||||
&f_midi2_block_opts_attr_midi1_first_group,
|
||||
&f_midi2_block_opts_attr_midi1_num_groups,
|
||||
&f_midi2_block_opts_attr_ui_hint,
|
||||
&f_midi2_block_opts_attr_midi_ci_version,
|
||||
&f_midi2_block_opts_attr_sysex8_streams,
|
||||
@ -2644,6 +2669,9 @@ static struct usb_function_instance *f_midi2_alloc_inst(void)
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/* set up the default MIDI1 (that is mandatory) */
|
||||
block_opts->info.midi1_num_groups = 1;
|
||||
|
||||
config_group_init_type_name(&opts->func_inst.group, "",
|
||||
&f_midi2_func_type);
|
||||
|
||||
@ -2707,6 +2735,16 @@ static int verify_parameters(struct f_midi2_opts *opts)
|
||||
i, j);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (bp->midi1_num_groups) {
|
||||
if (bp->midi1_first_group < bp->first_group ||
|
||||
bp->midi1_first_group + bp->midi1_num_groups >
|
||||
bp->first_group + bp->num_groups) {
|
||||
pr_err("f_midi2: Invalid MIDI1 group definitions for block %d:%d\n",
|
||||
i, j);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!num_blks) {
|
||||
@ -2717,6 +2755,46 @@ static int verify_parameters(struct f_midi2_opts *opts)
|
||||
return num_eps;
|
||||
}
|
||||
|
||||
/* fill mapping between MIDI 1.0 cable and UMP EP/group */
|
||||
static void fill_midi1_cable_mapping(struct f_midi2 *midi2,
|
||||
struct f_midi2_ep *ep,
|
||||
int blk)
|
||||
{
|
||||
const struct f_midi2_block_info *binfo = &ep->blks[blk].info;
|
||||
struct midi1_cable_mapping *map;
|
||||
int i, group;
|
||||
|
||||
if (!binfo->midi1_num_groups)
|
||||
return;
|
||||
if (binfo->direction != SNDRV_UMP_DIR_OUTPUT) {
|
||||
group = binfo->midi1_first_group;
|
||||
map = midi2->in_cable_mapping + midi2->num_midi1_in;
|
||||
for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) {
|
||||
if (midi2->num_midi1_in >= MAX_CABLES)
|
||||
break;
|
||||
map->ep = ep;
|
||||
map->block = blk;
|
||||
map->group = group;
|
||||
midi2->num_midi1_in++;
|
||||
/* store 1-based cable number */
|
||||
ep->in_group_to_cable[group] = midi2->num_midi1_in;
|
||||
}
|
||||
}
|
||||
|
||||
if (binfo->direction != SNDRV_UMP_DIR_INPUT) {
|
||||
group = binfo->midi1_first_group;
|
||||
map = midi2->out_cable_mapping + midi2->num_midi1_out;
|
||||
for (i = 0; i < binfo->midi1_num_groups; i++, group++, map++) {
|
||||
if (midi2->num_midi1_out >= MAX_CABLES)
|
||||
break;
|
||||
map->ep = ep;
|
||||
map->block = blk;
|
||||
map->group = group;
|
||||
midi2->num_midi1_out++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* gadget alloc callback */
|
||||
static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
|
||||
{
|
||||
@ -2786,9 +2864,17 @@ static struct usb_function *f_midi2_alloc(struct usb_function_instance *fi)
|
||||
bp = &ep->blks[blk];
|
||||
midi2->string_defs[gtb_to_str_id(bp->gtb_id)].s =
|
||||
ump_fb_name(&bp->info);
|
||||
|
||||
fill_midi1_cable_mapping(midi2, ep, blk);
|
||||
}
|
||||
}
|
||||
|
||||
if (!midi2->num_midi1_in && !midi2->num_midi1_out) {
|
||||
pr_err("f_midi2: MIDI1 definition is missing\n");
|
||||
do_f_midi2_free(midi2, opts);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
return &midi2->func;
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,8 @@ struct f_midi2_block_info {
|
||||
unsigned int direction; /* FB direction: 1-3 */
|
||||
unsigned int first_group; /* first UMP group: 0-15 */
|
||||
unsigned int num_groups; /* number of UMP groups: 1-16 */
|
||||
unsigned int midi1_first_group; /* first UMP group for MIDI 1.0 */
|
||||
unsigned int midi1_num_groups; /* number of UMP groups for MIDI 1.0 */
|
||||
unsigned int ui_hint; /* UI-hint: 0-3 */
|
||||
unsigned int midi_ci_version; /* MIDI-CI version: 0-255 */
|
||||
unsigned int sysex8_streams; /* number of sysex8 streams: 0-255 */
|
||||
|
Loading…
Reference in New Issue
Block a user