18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2015-2016 Red Hat
48c2ecf20Sopenharmony_ci * Copyright (C) 2015 Lyude Paul <thatslyude@gmail.com>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/kernel.h>
88c2ecf20Sopenharmony_ci#include <linux/slab.h>
98c2ecf20Sopenharmony_ci#include <linux/serio.h>
108c2ecf20Sopenharmony_ci#include <linux/notifier.h>
118c2ecf20Sopenharmony_ci#include "rmi_driver.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define RMI_F03_RX_DATA_OFB		0x01
148c2ecf20Sopenharmony_ci#define RMI_F03_OB_SIZE			2
158c2ecf20Sopenharmony_ci
168c2ecf20Sopenharmony_ci#define RMI_F03_OB_OFFSET		2
178c2ecf20Sopenharmony_ci#define RMI_F03_OB_DATA_OFFSET		1
188c2ecf20Sopenharmony_ci#define RMI_F03_OB_FLAG_TIMEOUT		BIT(6)
198c2ecf20Sopenharmony_ci#define RMI_F03_OB_FLAG_PARITY		BIT(7)
208c2ecf20Sopenharmony_ci
218c2ecf20Sopenharmony_ci#define RMI_F03_DEVICE_COUNT		0x07
228c2ecf20Sopenharmony_ci#define RMI_F03_BYTES_PER_DEVICE	0x07
238c2ecf20Sopenharmony_ci#define RMI_F03_BYTES_PER_DEVICE_SHIFT	4
248c2ecf20Sopenharmony_ci#define RMI_F03_QUEUE_LENGTH		0x0F
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_ci#define PSMOUSE_OOB_EXTRA_BTNS		0x01
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistruct f03_data {
298c2ecf20Sopenharmony_ci	struct rmi_function *fn;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	struct serio *serio;
328c2ecf20Sopenharmony_ci	bool serio_registered;
338c2ecf20Sopenharmony_ci
348c2ecf20Sopenharmony_ci	unsigned int overwrite_buttons;
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_ci	u8 device_count;
378c2ecf20Sopenharmony_ci	u8 rx_queue_length;
388c2ecf20Sopenharmony_ci};
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ciint rmi_f03_overwrite_button(struct rmi_function *fn, unsigned int button,
418c2ecf20Sopenharmony_ci			     int value)
428c2ecf20Sopenharmony_ci{
438c2ecf20Sopenharmony_ci	struct f03_data *f03 = dev_get_drvdata(&fn->dev);
448c2ecf20Sopenharmony_ci	unsigned int bit;
458c2ecf20Sopenharmony_ci
468c2ecf20Sopenharmony_ci	if (button < BTN_LEFT || button > BTN_MIDDLE)
478c2ecf20Sopenharmony_ci		return -EINVAL;
488c2ecf20Sopenharmony_ci
498c2ecf20Sopenharmony_ci	bit = BIT(button - BTN_LEFT);
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	if (value)
528c2ecf20Sopenharmony_ci		f03->overwrite_buttons |= bit;
538c2ecf20Sopenharmony_ci	else
548c2ecf20Sopenharmony_ci		f03->overwrite_buttons &= ~bit;
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	return 0;
578c2ecf20Sopenharmony_ci}
588c2ecf20Sopenharmony_ci
598c2ecf20Sopenharmony_civoid rmi_f03_commit_buttons(struct rmi_function *fn)
608c2ecf20Sopenharmony_ci{
618c2ecf20Sopenharmony_ci	struct f03_data *f03 = dev_get_drvdata(&fn->dev);
628c2ecf20Sopenharmony_ci	struct serio *serio = f03->serio;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	serio_pause_rx(serio);
658c2ecf20Sopenharmony_ci	if (serio->drv) {
668c2ecf20Sopenharmony_ci		serio->drv->interrupt(serio, PSMOUSE_OOB_EXTRA_BTNS,
678c2ecf20Sopenharmony_ci				      SERIO_OOB_DATA);
688c2ecf20Sopenharmony_ci		serio->drv->interrupt(serio, f03->overwrite_buttons,
698c2ecf20Sopenharmony_ci				      SERIO_OOB_DATA);
708c2ecf20Sopenharmony_ci	}
718c2ecf20Sopenharmony_ci	serio_continue_rx(serio);
728c2ecf20Sopenharmony_ci}
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_cistatic int rmi_f03_pt_write(struct serio *id, unsigned char val)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	struct f03_data *f03 = id->port_data;
778c2ecf20Sopenharmony_ci	int error;
788c2ecf20Sopenharmony_ci
798c2ecf20Sopenharmony_ci	rmi_dbg(RMI_DEBUG_FN, &f03->fn->dev,
808c2ecf20Sopenharmony_ci		"%s: Wrote %.2hhx to PS/2 passthrough address",
818c2ecf20Sopenharmony_ci		__func__, val);
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	error = rmi_write(f03->fn->rmi_dev, f03->fn->fd.data_base_addr, val);
848c2ecf20Sopenharmony_ci	if (error) {
858c2ecf20Sopenharmony_ci		dev_err(&f03->fn->dev,
868c2ecf20Sopenharmony_ci			"%s: Failed to write to F03 TX register (%d).\n",
878c2ecf20Sopenharmony_ci			__func__, error);
888c2ecf20Sopenharmony_ci		return error;
898c2ecf20Sopenharmony_ci	}
908c2ecf20Sopenharmony_ci
918c2ecf20Sopenharmony_ci	return 0;
928c2ecf20Sopenharmony_ci}
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic int rmi_f03_initialize(struct f03_data *f03)
958c2ecf20Sopenharmony_ci{
968c2ecf20Sopenharmony_ci	struct rmi_function *fn = f03->fn;
978c2ecf20Sopenharmony_ci	struct device *dev = &fn->dev;
988c2ecf20Sopenharmony_ci	int error;
998c2ecf20Sopenharmony_ci	u8 bytes_per_device;
1008c2ecf20Sopenharmony_ci	u8 query1;
1018c2ecf20Sopenharmony_ci	u8 query2[RMI_F03_DEVICE_COUNT * RMI_F03_BYTES_PER_DEVICE];
1028c2ecf20Sopenharmony_ci	size_t query2_len;
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	error = rmi_read(fn->rmi_dev, fn->fd.query_base_addr, &query1);
1058c2ecf20Sopenharmony_ci	if (error) {
1068c2ecf20Sopenharmony_ci		dev_err(dev, "Failed to read query register (%d).\n", error);
1078c2ecf20Sopenharmony_ci		return error;
1088c2ecf20Sopenharmony_ci	}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci	f03->device_count = query1 & RMI_F03_DEVICE_COUNT;
1118c2ecf20Sopenharmony_ci	bytes_per_device = (query1 >> RMI_F03_BYTES_PER_DEVICE_SHIFT) &
1128c2ecf20Sopenharmony_ci				RMI_F03_BYTES_PER_DEVICE;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	query2_len = f03->device_count * bytes_per_device;
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_ci	/*
1178c2ecf20Sopenharmony_ci	 * The first generation of image sensors don't have a second part to
1188c2ecf20Sopenharmony_ci	 * their f03 query, as such we have to set some of these values manually
1198c2ecf20Sopenharmony_ci	 */
1208c2ecf20Sopenharmony_ci	if (query2_len < 1) {
1218c2ecf20Sopenharmony_ci		f03->device_count = 1;
1228c2ecf20Sopenharmony_ci		f03->rx_queue_length = 7;
1238c2ecf20Sopenharmony_ci	} else {
1248c2ecf20Sopenharmony_ci		error = rmi_read_block(fn->rmi_dev, fn->fd.query_base_addr + 1,
1258c2ecf20Sopenharmony_ci				       query2, query2_len);
1268c2ecf20Sopenharmony_ci		if (error) {
1278c2ecf20Sopenharmony_ci			dev_err(dev,
1288c2ecf20Sopenharmony_ci				"Failed to read second set of query registers (%d).\n",
1298c2ecf20Sopenharmony_ci				error);
1308c2ecf20Sopenharmony_ci			return error;
1318c2ecf20Sopenharmony_ci		}
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci		f03->rx_queue_length = query2[0] & RMI_F03_QUEUE_LENGTH;
1348c2ecf20Sopenharmony_ci	}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci	return 0;
1378c2ecf20Sopenharmony_ci}
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_cistatic int rmi_f03_pt_open(struct serio *serio)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	struct f03_data *f03 = serio->port_data;
1428c2ecf20Sopenharmony_ci	struct rmi_function *fn = f03->fn;
1438c2ecf20Sopenharmony_ci	const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE;
1448c2ecf20Sopenharmony_ci	const u16 data_addr = fn->fd.data_base_addr + RMI_F03_OB_OFFSET;
1458c2ecf20Sopenharmony_ci	u8 obs[RMI_F03_QUEUE_LENGTH * RMI_F03_OB_SIZE];
1468c2ecf20Sopenharmony_ci	int error;
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	/*
1498c2ecf20Sopenharmony_ci	 * Consume any pending data. Some devices like to spam with
1508c2ecf20Sopenharmony_ci	 * 0xaa 0x00 announcements which may confuse us as we try to
1518c2ecf20Sopenharmony_ci	 * probe the device.
1528c2ecf20Sopenharmony_ci	 */
1538c2ecf20Sopenharmony_ci	error = rmi_read_block(fn->rmi_dev, data_addr, &obs, ob_len);
1548c2ecf20Sopenharmony_ci	if (!error)
1558c2ecf20Sopenharmony_ci		rmi_dbg(RMI_DEBUG_FN, &fn->dev,
1568c2ecf20Sopenharmony_ci			"%s: Consumed %*ph (%d) from PS2 guest\n",
1578c2ecf20Sopenharmony_ci			__func__, ob_len, obs, ob_len);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	return fn->rmi_dev->driver->set_irq_bits(fn->rmi_dev, fn->irq_mask);
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic void rmi_f03_pt_close(struct serio *serio)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	struct f03_data *f03 = serio->port_data;
1658c2ecf20Sopenharmony_ci	struct rmi_function *fn = f03->fn;
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	fn->rmi_dev->driver->clear_irq_bits(fn->rmi_dev, fn->irq_mask);
1688c2ecf20Sopenharmony_ci}
1698c2ecf20Sopenharmony_ci
1708c2ecf20Sopenharmony_cistatic int rmi_f03_register_pt(struct f03_data *f03)
1718c2ecf20Sopenharmony_ci{
1728c2ecf20Sopenharmony_ci	struct serio *serio;
1738c2ecf20Sopenharmony_ci
1748c2ecf20Sopenharmony_ci	serio = kzalloc(sizeof(struct serio), GFP_KERNEL);
1758c2ecf20Sopenharmony_ci	if (!serio)
1768c2ecf20Sopenharmony_ci		return -ENOMEM;
1778c2ecf20Sopenharmony_ci
1788c2ecf20Sopenharmony_ci	serio->id.type = SERIO_PS_PSTHRU;
1798c2ecf20Sopenharmony_ci	serio->write = rmi_f03_pt_write;
1808c2ecf20Sopenharmony_ci	serio->open = rmi_f03_pt_open;
1818c2ecf20Sopenharmony_ci	serio->close = rmi_f03_pt_close;
1828c2ecf20Sopenharmony_ci	serio->port_data = f03;
1838c2ecf20Sopenharmony_ci
1848c2ecf20Sopenharmony_ci	strlcpy(serio->name, "RMI4 PS/2 pass-through", sizeof(serio->name));
1858c2ecf20Sopenharmony_ci	snprintf(serio->phys, sizeof(serio->phys), "%s/serio0",
1868c2ecf20Sopenharmony_ci		 dev_name(&f03->fn->dev));
1878c2ecf20Sopenharmony_ci	serio->dev.parent = &f03->fn->dev;
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	f03->serio = serio;
1908c2ecf20Sopenharmony_ci
1918c2ecf20Sopenharmony_ci	printk(KERN_INFO "serio: %s port at %s\n",
1928c2ecf20Sopenharmony_ci		serio->name, dev_name(&f03->fn->dev));
1938c2ecf20Sopenharmony_ci	serio_register_port(serio);
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_ci	return 0;
1968c2ecf20Sopenharmony_ci}
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_cistatic int rmi_f03_probe(struct rmi_function *fn)
1998c2ecf20Sopenharmony_ci{
2008c2ecf20Sopenharmony_ci	struct device *dev = &fn->dev;
2018c2ecf20Sopenharmony_ci	struct f03_data *f03;
2028c2ecf20Sopenharmony_ci	int error;
2038c2ecf20Sopenharmony_ci
2048c2ecf20Sopenharmony_ci	f03 = devm_kzalloc(dev, sizeof(struct f03_data), GFP_KERNEL);
2058c2ecf20Sopenharmony_ci	if (!f03)
2068c2ecf20Sopenharmony_ci		return -ENOMEM;
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ci	f03->fn = fn;
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_ci	error = rmi_f03_initialize(f03);
2118c2ecf20Sopenharmony_ci	if (error < 0)
2128c2ecf20Sopenharmony_ci		return error;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	if (f03->device_count != 1)
2158c2ecf20Sopenharmony_ci		dev_warn(dev, "found %d devices on PS/2 passthrough",
2168c2ecf20Sopenharmony_ci			 f03->device_count);
2178c2ecf20Sopenharmony_ci
2188c2ecf20Sopenharmony_ci	dev_set_drvdata(dev, f03);
2198c2ecf20Sopenharmony_ci	return 0;
2208c2ecf20Sopenharmony_ci}
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_cistatic int rmi_f03_config(struct rmi_function *fn)
2238c2ecf20Sopenharmony_ci{
2248c2ecf20Sopenharmony_ci	struct f03_data *f03 = dev_get_drvdata(&fn->dev);
2258c2ecf20Sopenharmony_ci	int error;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	if (!f03->serio_registered) {
2288c2ecf20Sopenharmony_ci		error = rmi_f03_register_pt(f03);
2298c2ecf20Sopenharmony_ci		if (error)
2308c2ecf20Sopenharmony_ci			return error;
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci		f03->serio_registered = true;
2338c2ecf20Sopenharmony_ci	} else {
2348c2ecf20Sopenharmony_ci		/*
2358c2ecf20Sopenharmony_ci		 * We must be re-configuring the sensor, just enable
2368c2ecf20Sopenharmony_ci		 * interrupts for this function.
2378c2ecf20Sopenharmony_ci		 */
2388c2ecf20Sopenharmony_ci		fn->rmi_dev->driver->set_irq_bits(fn->rmi_dev, fn->irq_mask);
2398c2ecf20Sopenharmony_ci	}
2408c2ecf20Sopenharmony_ci
2418c2ecf20Sopenharmony_ci	return 0;
2428c2ecf20Sopenharmony_ci}
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_cistatic irqreturn_t rmi_f03_attention(int irq, void *ctx)
2458c2ecf20Sopenharmony_ci{
2468c2ecf20Sopenharmony_ci	struct rmi_function *fn = ctx;
2478c2ecf20Sopenharmony_ci	struct rmi_device *rmi_dev = fn->rmi_dev;
2488c2ecf20Sopenharmony_ci	struct rmi_driver_data *drvdata = dev_get_drvdata(&rmi_dev->dev);
2498c2ecf20Sopenharmony_ci	struct f03_data *f03 = dev_get_drvdata(&fn->dev);
2508c2ecf20Sopenharmony_ci	const u16 data_addr = fn->fd.data_base_addr + RMI_F03_OB_OFFSET;
2518c2ecf20Sopenharmony_ci	const u8 ob_len = f03->rx_queue_length * RMI_F03_OB_SIZE;
2528c2ecf20Sopenharmony_ci	u8 obs[RMI_F03_QUEUE_LENGTH * RMI_F03_OB_SIZE];
2538c2ecf20Sopenharmony_ci	u8 ob_status;
2548c2ecf20Sopenharmony_ci	u8 ob_data;
2558c2ecf20Sopenharmony_ci	unsigned int serio_flags;
2568c2ecf20Sopenharmony_ci	int i;
2578c2ecf20Sopenharmony_ci	int error;
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (drvdata->attn_data.data) {
2608c2ecf20Sopenharmony_ci		/* First grab the data passed by the transport device */
2618c2ecf20Sopenharmony_ci		if (drvdata->attn_data.size < ob_len) {
2628c2ecf20Sopenharmony_ci			dev_warn(&fn->dev, "F03 interrupted, but data is missing!\n");
2638c2ecf20Sopenharmony_ci			return IRQ_HANDLED;
2648c2ecf20Sopenharmony_ci		}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci		memcpy(obs, drvdata->attn_data.data, ob_len);
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci		drvdata->attn_data.data += ob_len;
2698c2ecf20Sopenharmony_ci		drvdata->attn_data.size -= ob_len;
2708c2ecf20Sopenharmony_ci	} else {
2718c2ecf20Sopenharmony_ci		/* Grab all of the data registers, and check them for data */
2728c2ecf20Sopenharmony_ci		error = rmi_read_block(fn->rmi_dev, data_addr, &obs, ob_len);
2738c2ecf20Sopenharmony_ci		if (error) {
2748c2ecf20Sopenharmony_ci			dev_err(&fn->dev,
2758c2ecf20Sopenharmony_ci				"%s: Failed to read F03 output buffers: %d\n",
2768c2ecf20Sopenharmony_ci				__func__, error);
2778c2ecf20Sopenharmony_ci			serio_interrupt(f03->serio, 0, SERIO_TIMEOUT);
2788c2ecf20Sopenharmony_ci			return IRQ_RETVAL(error);
2798c2ecf20Sopenharmony_ci		}
2808c2ecf20Sopenharmony_ci	}
2818c2ecf20Sopenharmony_ci
2828c2ecf20Sopenharmony_ci	for (i = 0; i < ob_len; i += RMI_F03_OB_SIZE) {
2838c2ecf20Sopenharmony_ci		ob_status = obs[i];
2848c2ecf20Sopenharmony_ci		ob_data = obs[i + RMI_F03_OB_DATA_OFFSET];
2858c2ecf20Sopenharmony_ci		serio_flags = 0;
2868c2ecf20Sopenharmony_ci
2878c2ecf20Sopenharmony_ci		if (!(ob_status & RMI_F03_RX_DATA_OFB))
2888c2ecf20Sopenharmony_ci			continue;
2898c2ecf20Sopenharmony_ci
2908c2ecf20Sopenharmony_ci		if (ob_status & RMI_F03_OB_FLAG_TIMEOUT)
2918c2ecf20Sopenharmony_ci			serio_flags |= SERIO_TIMEOUT;
2928c2ecf20Sopenharmony_ci		if (ob_status & RMI_F03_OB_FLAG_PARITY)
2938c2ecf20Sopenharmony_ci			serio_flags |= SERIO_PARITY;
2948c2ecf20Sopenharmony_ci
2958c2ecf20Sopenharmony_ci		rmi_dbg(RMI_DEBUG_FN, &fn->dev,
2968c2ecf20Sopenharmony_ci			"%s: Received %.2hhx from PS2 guest T: %c P: %c\n",
2978c2ecf20Sopenharmony_ci			__func__, ob_data,
2988c2ecf20Sopenharmony_ci			serio_flags & SERIO_TIMEOUT ?  'Y' : 'N',
2998c2ecf20Sopenharmony_ci			serio_flags & SERIO_PARITY ? 'Y' : 'N');
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci		serio_interrupt(f03->serio, ob_data, serio_flags);
3028c2ecf20Sopenharmony_ci	}
3038c2ecf20Sopenharmony_ci
3048c2ecf20Sopenharmony_ci	return IRQ_HANDLED;
3058c2ecf20Sopenharmony_ci}
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_cistatic void rmi_f03_remove(struct rmi_function *fn)
3088c2ecf20Sopenharmony_ci{
3098c2ecf20Sopenharmony_ci	struct f03_data *f03 = dev_get_drvdata(&fn->dev);
3108c2ecf20Sopenharmony_ci
3118c2ecf20Sopenharmony_ci	if (f03->serio_registered)
3128c2ecf20Sopenharmony_ci		serio_unregister_port(f03->serio);
3138c2ecf20Sopenharmony_ci}
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_cistruct rmi_function_handler rmi_f03_handler = {
3168c2ecf20Sopenharmony_ci	.driver = {
3178c2ecf20Sopenharmony_ci		.name = "rmi4_f03",
3188c2ecf20Sopenharmony_ci	},
3198c2ecf20Sopenharmony_ci	.func = 0x03,
3208c2ecf20Sopenharmony_ci	.probe = rmi_f03_probe,
3218c2ecf20Sopenharmony_ci	.config = rmi_f03_config,
3228c2ecf20Sopenharmony_ci	.attention = rmi_f03_attention,
3238c2ecf20Sopenharmony_ci	.remove = rmi_f03_remove,
3248c2ecf20Sopenharmony_ci};
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ciMODULE_AUTHOR("Lyude Paul <thatslyude@gmail.com>");
3278c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("RMI F03 module");
3288c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
329