162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * gspca ViCam subdriver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com>
662306a36Sopenharmony_ci *
762306a36Sopenharmony_ci * Based on the usbvideo vicam driver, which is:
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * Copyright (c) 2002 Joe Burks (jburks@wavicle.org),
1062306a36Sopenharmony_ci *                    Chris Cheney (chris.cheney@gmail.com),
1162306a36Sopenharmony_ci *                    Pavel Machek (pavel@ucw.cz),
1262306a36Sopenharmony_ci *                    John Tyner (jtyner@cs.ucr.edu),
1362306a36Sopenharmony_ci *                    Monroe Williams (monroe@pobox.com)
1462306a36Sopenharmony_ci */
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci#define MODULE_NAME "vicam"
1962306a36Sopenharmony_ci#define HEADER_SIZE 64
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci#include <linux/workqueue.h>
2262306a36Sopenharmony_ci#include <linux/slab.h>
2362306a36Sopenharmony_ci#include <linux/firmware.h>
2462306a36Sopenharmony_ci#include <linux/ihex.h>
2562306a36Sopenharmony_ci#include "gspca.h"
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci#define VICAM_FIRMWARE "vicam/firmware.fw"
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ciMODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
3062306a36Sopenharmony_ciMODULE_DESCRIPTION("GSPCA ViCam USB Camera Driver");
3162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
3262306a36Sopenharmony_ciMODULE_FIRMWARE(VICAM_FIRMWARE);
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_cistruct sd {
3562306a36Sopenharmony_ci	struct gspca_dev gspca_dev;	/* !! must be the first item */
3662306a36Sopenharmony_ci	struct work_struct work_struct;
3762306a36Sopenharmony_ci};
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci/* The vicam sensor has a resolution of 512 x 244, with I believe square
4062306a36Sopenharmony_ci   pixels, but this is forced to a 4:3 ratio by optics. So it has
4162306a36Sopenharmony_ci   non square pixels :( */
4262306a36Sopenharmony_cistatic struct v4l2_pix_format vicam_mode[] = {
4362306a36Sopenharmony_ci	{ 256, 122, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
4462306a36Sopenharmony_ci		.bytesperline = 256,
4562306a36Sopenharmony_ci		.sizeimage = 256 * 122,
4662306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB,},
4762306a36Sopenharmony_ci	/* 2 modes with somewhat more square pixels */
4862306a36Sopenharmony_ci	{ 256, 200, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
4962306a36Sopenharmony_ci		.bytesperline = 256,
5062306a36Sopenharmony_ci		.sizeimage = 256 * 200,
5162306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB,},
5262306a36Sopenharmony_ci	{ 256, 240, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
5362306a36Sopenharmony_ci		.bytesperline = 256,
5462306a36Sopenharmony_ci		.sizeimage = 256 * 240,
5562306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB,},
5662306a36Sopenharmony_ci#if 0   /* This mode has extremely non square pixels, testing use only */
5762306a36Sopenharmony_ci	{ 512, 122, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
5862306a36Sopenharmony_ci		.bytesperline = 512,
5962306a36Sopenharmony_ci		.sizeimage = 512 * 122,
6062306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB,},
6162306a36Sopenharmony_ci#endif
6262306a36Sopenharmony_ci	{ 512, 244, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
6362306a36Sopenharmony_ci		.bytesperline = 512,
6462306a36Sopenharmony_ci		.sizeimage = 512 * 244,
6562306a36Sopenharmony_ci		.colorspace = V4L2_COLORSPACE_SRGB,},
6662306a36Sopenharmony_ci};
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_cistatic int vicam_control_msg(struct gspca_dev *gspca_dev, u8 request,
6962306a36Sopenharmony_ci	u16 value, u16 index, u8 *data, u16 len)
7062306a36Sopenharmony_ci{
7162306a36Sopenharmony_ci	int ret;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	ret = usb_control_msg(gspca_dev->dev,
7462306a36Sopenharmony_ci			      usb_sndctrlpipe(gspca_dev->dev, 0),
7562306a36Sopenharmony_ci			      request,
7662306a36Sopenharmony_ci			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
7762306a36Sopenharmony_ci			      value, index, data, len, 1000);
7862306a36Sopenharmony_ci	if (ret < 0)
7962306a36Sopenharmony_ci		pr_err("control msg req %02X error %d\n", request, ret);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci	return ret;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic int vicam_set_camera_power(struct gspca_dev *gspca_dev, int state)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	int ret;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	ret = vicam_control_msg(gspca_dev, 0x50, state, 0, NULL, 0);
8962306a36Sopenharmony_ci	if (ret < 0)
9062306a36Sopenharmony_ci		return ret;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	if (state)
9362306a36Sopenharmony_ci		ret = vicam_control_msg(gspca_dev, 0x55, 1, 0, NULL, 0);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	return ret;
9662306a36Sopenharmony_ci}
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci/*
9962306a36Sopenharmony_ci *  request and read a block of data
10062306a36Sopenharmony_ci */
10162306a36Sopenharmony_cistatic int vicam_read_frame(struct gspca_dev *gspca_dev, u8 *data, int size)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	int ret, unscaled_height, act_len = 0;
10462306a36Sopenharmony_ci	u8 *req_data = gspca_dev->usb_buf;
10562306a36Sopenharmony_ci	s32 expo = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
10662306a36Sopenharmony_ci	s32 gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	memset(req_data, 0, 16);
10962306a36Sopenharmony_ci	req_data[0] = gain;
11062306a36Sopenharmony_ci	if (gspca_dev->pixfmt.width == 256)
11162306a36Sopenharmony_ci		req_data[1] |= 0x01; /* low nibble x-scale */
11262306a36Sopenharmony_ci	if (gspca_dev->pixfmt.height <= 122) {
11362306a36Sopenharmony_ci		req_data[1] |= 0x10; /* high nibble y-scale */
11462306a36Sopenharmony_ci		unscaled_height = gspca_dev->pixfmt.height * 2;
11562306a36Sopenharmony_ci	} else
11662306a36Sopenharmony_ci		unscaled_height = gspca_dev->pixfmt.height;
11762306a36Sopenharmony_ci	req_data[2] = 0x90; /* unknown, does not seem to do anything */
11862306a36Sopenharmony_ci	if (unscaled_height <= 200)
11962306a36Sopenharmony_ci		req_data[3] = 0x06; /* vend? */
12062306a36Sopenharmony_ci	else if (unscaled_height <= 242) /* Yes 242 not 240 */
12162306a36Sopenharmony_ci		req_data[3] = 0x07; /* vend? */
12262306a36Sopenharmony_ci	else /* Up to 244 lines with req_data[3] == 0x08 */
12362306a36Sopenharmony_ci		req_data[3] = 0x08; /* vend? */
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	if (expo < 256) {
12662306a36Sopenharmony_ci		/* Frame rate maxed out, use partial frame expo time */
12762306a36Sopenharmony_ci		req_data[4] = 255 - expo;
12862306a36Sopenharmony_ci		req_data[5] = 0x00;
12962306a36Sopenharmony_ci		req_data[6] = 0x00;
13062306a36Sopenharmony_ci		req_data[7] = 0x01;
13162306a36Sopenharmony_ci	} else {
13262306a36Sopenharmony_ci		/* Modify frame rate */
13362306a36Sopenharmony_ci		req_data[4] = 0x00;
13462306a36Sopenharmony_ci		req_data[5] = 0x00;
13562306a36Sopenharmony_ci		req_data[6] = expo & 0xFF;
13662306a36Sopenharmony_ci		req_data[7] = expo >> 8;
13762306a36Sopenharmony_ci	}
13862306a36Sopenharmony_ci	req_data[8] = ((244 - unscaled_height) / 2) & ~0x01; /* vstart */
13962306a36Sopenharmony_ci	/* bytes 9-15 do not seem to affect exposure or image quality */
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	mutex_lock(&gspca_dev->usb_lock);
14262306a36Sopenharmony_ci	ret = vicam_control_msg(gspca_dev, 0x51, 0x80, 0, req_data, 16);
14362306a36Sopenharmony_ci	mutex_unlock(&gspca_dev->usb_lock);
14462306a36Sopenharmony_ci	if (ret < 0)
14562306a36Sopenharmony_ci		return ret;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	ret = usb_bulk_msg(gspca_dev->dev,
14862306a36Sopenharmony_ci			   usb_rcvbulkpipe(gspca_dev->dev, 0x81),
14962306a36Sopenharmony_ci			   data, size, &act_len, 10000);
15062306a36Sopenharmony_ci	/* successful, it returns 0, otherwise  negative */
15162306a36Sopenharmony_ci	if (ret < 0 || act_len != size) {
15262306a36Sopenharmony_ci		pr_err("bulk read fail (%d) len %d/%d\n",
15362306a36Sopenharmony_ci		       ret, act_len, size);
15462306a36Sopenharmony_ci		return -EIO;
15562306a36Sopenharmony_ci	}
15662306a36Sopenharmony_ci	return 0;
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci/*
16062306a36Sopenharmony_ci * This function is called as a workqueue function and runs whenever the camera
16162306a36Sopenharmony_ci * is streaming data. Because it is a workqueue function it is allowed to sleep
16262306a36Sopenharmony_ci * so we can use synchronous USB calls. To avoid possible collisions with other
16362306a36Sopenharmony_ci * threads attempting to use gspca_dev->usb_buf we take the usb_lock when
16462306a36Sopenharmony_ci * performing USB operations using it. In practice we don't really need this
16562306a36Sopenharmony_ci * as the cameras controls are only written from the workqueue.
16662306a36Sopenharmony_ci */
16762306a36Sopenharmony_cistatic void vicam_dostream(struct work_struct *work)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	struct sd *sd = container_of(work, struct sd, work_struct);
17062306a36Sopenharmony_ci	struct gspca_dev *gspca_dev = &sd->gspca_dev;
17162306a36Sopenharmony_ci	int ret, frame_sz;
17262306a36Sopenharmony_ci	u8 *buffer;
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	frame_sz = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].sizeimage +
17562306a36Sopenharmony_ci		   HEADER_SIZE;
17662306a36Sopenharmony_ci	buffer = kmalloc(frame_sz, GFP_KERNEL);
17762306a36Sopenharmony_ci	if (!buffer) {
17862306a36Sopenharmony_ci		pr_err("Couldn't allocate USB buffer\n");
17962306a36Sopenharmony_ci		goto exit;
18062306a36Sopenharmony_ci	}
18162306a36Sopenharmony_ci
18262306a36Sopenharmony_ci	while (gspca_dev->present && gspca_dev->streaming) {
18362306a36Sopenharmony_ci#ifdef CONFIG_PM
18462306a36Sopenharmony_ci		if (gspca_dev->frozen)
18562306a36Sopenharmony_ci			break;
18662306a36Sopenharmony_ci#endif
18762306a36Sopenharmony_ci		ret = vicam_read_frame(gspca_dev, buffer, frame_sz);
18862306a36Sopenharmony_ci		if (ret < 0)
18962306a36Sopenharmony_ci			break;
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_ci		/* Note the frame header contents seem to be completely
19262306a36Sopenharmony_ci		   constant, they do not change with either image, or
19362306a36Sopenharmony_ci		   settings. So we simply discard it. The frames have
19462306a36Sopenharmony_ci		   a very similar 64 byte footer, which we don't even
19562306a36Sopenharmony_ci		   bother reading from the cam */
19662306a36Sopenharmony_ci		gspca_frame_add(gspca_dev, FIRST_PACKET,
19762306a36Sopenharmony_ci				buffer + HEADER_SIZE,
19862306a36Sopenharmony_ci				frame_sz - HEADER_SIZE);
19962306a36Sopenharmony_ci		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
20062306a36Sopenharmony_ci	}
20162306a36Sopenharmony_ciexit:
20262306a36Sopenharmony_ci	kfree(buffer);
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci/* This function is called at probe time just before sd_init */
20662306a36Sopenharmony_cistatic int sd_config(struct gspca_dev *gspca_dev,
20762306a36Sopenharmony_ci		const struct usb_device_id *id)
20862306a36Sopenharmony_ci{
20962306a36Sopenharmony_ci	struct cam *cam = &gspca_dev->cam;
21062306a36Sopenharmony_ci	struct sd *sd = (struct sd *)gspca_dev;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	/* We don't use the buffer gspca allocates so make it small. */
21362306a36Sopenharmony_ci	cam->bulk = 1;
21462306a36Sopenharmony_ci	cam->bulk_size = 64;
21562306a36Sopenharmony_ci	cam->cam_mode = vicam_mode;
21662306a36Sopenharmony_ci	cam->nmodes = ARRAY_SIZE(vicam_mode);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	INIT_WORK(&sd->work_struct, vicam_dostream);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	return 0;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci/* this function is called at probe and resume time */
22462306a36Sopenharmony_cistatic int sd_init(struct gspca_dev *gspca_dev)
22562306a36Sopenharmony_ci{
22662306a36Sopenharmony_ci	int ret;
22762306a36Sopenharmony_ci	const struct ihex_binrec *rec;
22862306a36Sopenharmony_ci	const struct firmware *fw;
22962306a36Sopenharmony_ci	u8 *firmware_buf;
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_ci	ret = request_ihex_firmware(&fw, VICAM_FIRMWARE,
23262306a36Sopenharmony_ci				    &gspca_dev->dev->dev);
23362306a36Sopenharmony_ci	if (ret) {
23462306a36Sopenharmony_ci		pr_err("Failed to load \"vicam/firmware.fw\": %d\n", ret);
23562306a36Sopenharmony_ci		return ret;
23662306a36Sopenharmony_ci	}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	firmware_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
23962306a36Sopenharmony_ci	if (!firmware_buf) {
24062306a36Sopenharmony_ci		ret = -ENOMEM;
24162306a36Sopenharmony_ci		goto exit;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci	for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) {
24462306a36Sopenharmony_ci		memcpy(firmware_buf, rec->data, be16_to_cpu(rec->len));
24562306a36Sopenharmony_ci		ret = vicam_control_msg(gspca_dev, 0xff, 0, 0, firmware_buf,
24662306a36Sopenharmony_ci					be16_to_cpu(rec->len));
24762306a36Sopenharmony_ci		if (ret < 0)
24862306a36Sopenharmony_ci			break;
24962306a36Sopenharmony_ci	}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_ci	kfree(firmware_buf);
25262306a36Sopenharmony_ciexit:
25362306a36Sopenharmony_ci	release_firmware(fw);
25462306a36Sopenharmony_ci	return ret;
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci/* Set up for getting frames. */
25862306a36Sopenharmony_cistatic int sd_start(struct gspca_dev *gspca_dev)
25962306a36Sopenharmony_ci{
26062306a36Sopenharmony_ci	struct sd *sd = (struct sd *)gspca_dev;
26162306a36Sopenharmony_ci	int ret;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci	ret = vicam_set_camera_power(gspca_dev, 1);
26462306a36Sopenharmony_ci	if (ret < 0)
26562306a36Sopenharmony_ci		return ret;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	schedule_work(&sd->work_struct);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	return 0;
27062306a36Sopenharmony_ci}
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci/* called on streamoff with alt==0 and on disconnect */
27362306a36Sopenharmony_ci/* the usb_lock is held at entry - restore on exit */
27462306a36Sopenharmony_cistatic void sd_stop0(struct gspca_dev *gspca_dev)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct sd *dev = (struct sd *)gspca_dev;
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	/* wait for the work queue to terminate */
27962306a36Sopenharmony_ci	mutex_unlock(&gspca_dev->usb_lock);
28062306a36Sopenharmony_ci	/* This waits for vicam_dostream to finish */
28162306a36Sopenharmony_ci	flush_work(&dev->work_struct);
28262306a36Sopenharmony_ci	mutex_lock(&gspca_dev->usb_lock);
28362306a36Sopenharmony_ci
28462306a36Sopenharmony_ci	if (gspca_dev->present)
28562306a36Sopenharmony_ci		vicam_set_camera_power(gspca_dev, 0);
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic int sd_init_controls(struct gspca_dev *gspca_dev)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
29162306a36Sopenharmony_ci
29262306a36Sopenharmony_ci	gspca_dev->vdev.ctrl_handler = hdl;
29362306a36Sopenharmony_ci	v4l2_ctrl_handler_init(hdl, 2);
29462306a36Sopenharmony_ci	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, NULL,
29562306a36Sopenharmony_ci			V4L2_CID_EXPOSURE, 0, 2047, 1, 256);
29662306a36Sopenharmony_ci	gspca_dev->gain = v4l2_ctrl_new_std(hdl, NULL,
29762306a36Sopenharmony_ci			V4L2_CID_GAIN, 0, 255, 1, 200);
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	if (hdl->error) {
30062306a36Sopenharmony_ci		pr_err("Could not initialize controls\n");
30162306a36Sopenharmony_ci		return hdl->error;
30262306a36Sopenharmony_ci	}
30362306a36Sopenharmony_ci	return 0;
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci/* Table of supported USB devices */
30762306a36Sopenharmony_cistatic const struct usb_device_id device_table[] = {
30862306a36Sopenharmony_ci	{USB_DEVICE(0x04c1, 0x009d)},
30962306a36Sopenharmony_ci	{USB_DEVICE(0x0602, 0x1001)},
31062306a36Sopenharmony_ci	{}
31162306a36Sopenharmony_ci};
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, device_table);
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci/* sub-driver description */
31662306a36Sopenharmony_cistatic const struct sd_desc sd_desc = {
31762306a36Sopenharmony_ci	.name   = MODULE_NAME,
31862306a36Sopenharmony_ci	.config = sd_config,
31962306a36Sopenharmony_ci	.init   = sd_init,
32062306a36Sopenharmony_ci	.init_controls = sd_init_controls,
32162306a36Sopenharmony_ci	.start  = sd_start,
32262306a36Sopenharmony_ci	.stop0  = sd_stop0,
32362306a36Sopenharmony_ci};
32462306a36Sopenharmony_ci
32562306a36Sopenharmony_ci/* -- device connect -- */
32662306a36Sopenharmony_cistatic int sd_probe(struct usb_interface *intf,
32762306a36Sopenharmony_ci		const struct usb_device_id *id)
32862306a36Sopenharmony_ci{
32962306a36Sopenharmony_ci	return gspca_dev_probe(intf, id,
33062306a36Sopenharmony_ci			&sd_desc,
33162306a36Sopenharmony_ci			sizeof(struct sd),
33262306a36Sopenharmony_ci			THIS_MODULE);
33362306a36Sopenharmony_ci}
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_cistatic struct usb_driver sd_driver = {
33662306a36Sopenharmony_ci	.name       = MODULE_NAME,
33762306a36Sopenharmony_ci	.id_table   = device_table,
33862306a36Sopenharmony_ci	.probe      = sd_probe,
33962306a36Sopenharmony_ci	.disconnect = gspca_disconnect,
34062306a36Sopenharmony_ci#ifdef CONFIG_PM
34162306a36Sopenharmony_ci	.suspend = gspca_suspend,
34262306a36Sopenharmony_ci	.resume  = gspca_resume,
34362306a36Sopenharmony_ci	.reset_resume = gspca_resume,
34462306a36Sopenharmony_ci#endif
34562306a36Sopenharmony_ci};
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cimodule_usb_driver(sd_driver);
348