162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2005-2006 Micronas USA Inc. 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci 662306a36Sopenharmony_ci#include <linux/module.h> 762306a36Sopenharmony_ci#include <linux/delay.h> 862306a36Sopenharmony_ci#include <linux/sched.h> 962306a36Sopenharmony_ci#include <linux/spinlock.h> 1062306a36Sopenharmony_ci#include <linux/unistd.h> 1162306a36Sopenharmony_ci#include <linux/time.h> 1262306a36Sopenharmony_ci#include <linux/mm.h> 1362306a36Sopenharmony_ci#include <linux/vmalloc.h> 1462306a36Sopenharmony_ci#include <linux/device.h> 1562306a36Sopenharmony_ci#include <linux/i2c.h> 1662306a36Sopenharmony_ci#include <linux/firmware.h> 1762306a36Sopenharmony_ci#include <linux/mutex.h> 1862306a36Sopenharmony_ci#include <linux/uaccess.h> 1962306a36Sopenharmony_ci#include <linux/slab.h> 2062306a36Sopenharmony_ci#include <linux/videodev2.h> 2162306a36Sopenharmony_ci#include <media/tuner.h> 2262306a36Sopenharmony_ci#include <media/v4l2-common.h> 2362306a36Sopenharmony_ci#include <media/v4l2-event.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include "go7007-priv.h" 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_ci/* 2862306a36Sopenharmony_ci * Wait for an interrupt to be delivered from the GO7007SB and return 2962306a36Sopenharmony_ci * the associated value and data. 3062306a36Sopenharmony_ci * 3162306a36Sopenharmony_ci * Must be called with the hw_lock held. 3262306a36Sopenharmony_ci */ 3362306a36Sopenharmony_ciint go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data) 3462306a36Sopenharmony_ci{ 3562306a36Sopenharmony_ci go->interrupt_available = 0; 3662306a36Sopenharmony_ci go->hpi_ops->read_interrupt(go); 3762306a36Sopenharmony_ci if (wait_event_timeout(go->interrupt_waitq, 3862306a36Sopenharmony_ci go->interrupt_available, 5*HZ) < 0) { 3962306a36Sopenharmony_ci v4l2_err(&go->v4l2_dev, "timeout waiting for read interrupt\n"); 4062306a36Sopenharmony_ci return -1; 4162306a36Sopenharmony_ci } 4262306a36Sopenharmony_ci if (!go->interrupt_available) 4362306a36Sopenharmony_ci return -1; 4462306a36Sopenharmony_ci go->interrupt_available = 0; 4562306a36Sopenharmony_ci *value = go->interrupt_value & 0xfffe; 4662306a36Sopenharmony_ci *data = go->interrupt_data; 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci} 4962306a36Sopenharmony_ciEXPORT_SYMBOL(go7007_read_interrupt); 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci/* 5262306a36Sopenharmony_ci * Read a register/address on the GO7007SB. 5362306a36Sopenharmony_ci * 5462306a36Sopenharmony_ci * Must be called with the hw_lock held. 5562306a36Sopenharmony_ci */ 5662306a36Sopenharmony_ciint go7007_read_addr(struct go7007 *go, u16 addr, u16 *data) 5762306a36Sopenharmony_ci{ 5862306a36Sopenharmony_ci int count = 100; 5962306a36Sopenharmony_ci u16 value; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (go7007_write_interrupt(go, 0x0010, addr) < 0) 6262306a36Sopenharmony_ci return -EIO; 6362306a36Sopenharmony_ci while (count-- > 0) { 6462306a36Sopenharmony_ci if (go7007_read_interrupt(go, &value, data) == 0 && 6562306a36Sopenharmony_ci value == 0xa000) 6662306a36Sopenharmony_ci return 0; 6762306a36Sopenharmony_ci } 6862306a36Sopenharmony_ci return -EIO; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ciEXPORT_SYMBOL(go7007_read_addr); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci/* 7362306a36Sopenharmony_ci * Send the boot firmware to the encoder, which just wakes it up and lets 7462306a36Sopenharmony_ci * us talk to the GPIO pins and on-board I2C adapter. 7562306a36Sopenharmony_ci * 7662306a36Sopenharmony_ci * Must be called with the hw_lock held. 7762306a36Sopenharmony_ci */ 7862306a36Sopenharmony_cistatic int go7007_load_encoder(struct go7007 *go) 7962306a36Sopenharmony_ci{ 8062306a36Sopenharmony_ci const struct firmware *fw_entry; 8162306a36Sopenharmony_ci char fw_name[] = "go7007/go7007fw.bin"; 8262306a36Sopenharmony_ci void *bounce; 8362306a36Sopenharmony_ci int fw_len; 8462306a36Sopenharmony_ci u16 intr_val, intr_data; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci if (go->boot_fw == NULL) { 8762306a36Sopenharmony_ci if (request_firmware(&fw_entry, fw_name, go->dev)) { 8862306a36Sopenharmony_ci v4l2_err(go, "unable to load firmware from file \"%s\"\n", fw_name); 8962306a36Sopenharmony_ci return -1; 9062306a36Sopenharmony_ci } 9162306a36Sopenharmony_ci if (fw_entry->size < 16 || memcmp(fw_entry->data, "WISGO7007FW", 11)) { 9262306a36Sopenharmony_ci v4l2_err(go, "file \"%s\" does not appear to be go7007 firmware\n", fw_name); 9362306a36Sopenharmony_ci release_firmware(fw_entry); 9462306a36Sopenharmony_ci return -1; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci fw_len = fw_entry->size - 16; 9762306a36Sopenharmony_ci bounce = kmemdup(fw_entry->data + 16, fw_len, GFP_KERNEL); 9862306a36Sopenharmony_ci if (bounce == NULL) { 9962306a36Sopenharmony_ci v4l2_err(go, "unable to allocate %d bytes for firmware transfer\n", fw_len); 10062306a36Sopenharmony_ci release_firmware(fw_entry); 10162306a36Sopenharmony_ci return -1; 10262306a36Sopenharmony_ci } 10362306a36Sopenharmony_ci release_firmware(fw_entry); 10462306a36Sopenharmony_ci go->boot_fw_len = fw_len; 10562306a36Sopenharmony_ci go->boot_fw = bounce; 10662306a36Sopenharmony_ci } 10762306a36Sopenharmony_ci if (go7007_interface_reset(go) < 0 || 10862306a36Sopenharmony_ci go7007_send_firmware(go, go->boot_fw, go->boot_fw_len) < 0 || 10962306a36Sopenharmony_ci go7007_read_interrupt(go, &intr_val, &intr_data) < 0 || 11062306a36Sopenharmony_ci (intr_val & ~0x1) != 0x5a5a) { 11162306a36Sopenharmony_ci v4l2_err(go, "error transferring firmware\n"); 11262306a36Sopenharmony_ci kfree(go->boot_fw); 11362306a36Sopenharmony_ci go->boot_fw = NULL; 11462306a36Sopenharmony_ci return -1; 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci return 0; 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ciMODULE_FIRMWARE("go7007/go7007fw.bin"); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci/* 12262306a36Sopenharmony_ci * Boot the encoder and register the I2C adapter if requested. Do the 12362306a36Sopenharmony_ci * minimum initialization necessary, since the board-specific code may 12462306a36Sopenharmony_ci * still need to probe the board ID. 12562306a36Sopenharmony_ci * 12662306a36Sopenharmony_ci * Must NOT be called with the hw_lock held. 12762306a36Sopenharmony_ci */ 12862306a36Sopenharmony_ciint go7007_boot_encoder(struct go7007 *go, int init_i2c) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci int ret; 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci mutex_lock(&go->hw_lock); 13362306a36Sopenharmony_ci ret = go7007_load_encoder(go); 13462306a36Sopenharmony_ci mutex_unlock(&go->hw_lock); 13562306a36Sopenharmony_ci if (ret < 0) 13662306a36Sopenharmony_ci return -1; 13762306a36Sopenharmony_ci if (!init_i2c) 13862306a36Sopenharmony_ci return 0; 13962306a36Sopenharmony_ci if (go7007_i2c_init(go) < 0) 14062306a36Sopenharmony_ci return -1; 14162306a36Sopenharmony_ci go->i2c_adapter_online = 1; 14262306a36Sopenharmony_ci return 0; 14362306a36Sopenharmony_ci} 14462306a36Sopenharmony_ciEXPORT_SYMBOL(go7007_boot_encoder); 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_ci/* 14762306a36Sopenharmony_ci * Configure any hardware-related registers in the GO7007, such as GPIO 14862306a36Sopenharmony_ci * pins and bus parameters, which are board-specific. This assumes 14962306a36Sopenharmony_ci * the boot firmware has already been downloaded. 15062306a36Sopenharmony_ci * 15162306a36Sopenharmony_ci * Must be called with the hw_lock held. 15262306a36Sopenharmony_ci */ 15362306a36Sopenharmony_cistatic int go7007_init_encoder(struct go7007 *go) 15462306a36Sopenharmony_ci{ 15562306a36Sopenharmony_ci if (go->board_info->audio_flags & GO7007_AUDIO_I2S_MASTER) { 15662306a36Sopenharmony_ci go7007_write_addr(go, 0x1000, 0x0811); 15762306a36Sopenharmony_ci go7007_write_addr(go, 0x1000, 0x0c11); 15862306a36Sopenharmony_ci } 15962306a36Sopenharmony_ci switch (go->board_id) { 16062306a36Sopenharmony_ci case GO7007_BOARDID_MATRIX_REV: 16162306a36Sopenharmony_ci /* Set GPIO pin 0 to be an output (audio clock control) */ 16262306a36Sopenharmony_ci go7007_write_addr(go, 0x3c82, 0x0001); 16362306a36Sopenharmony_ci go7007_write_addr(go, 0x3c80, 0x00fe); 16462306a36Sopenharmony_ci break; 16562306a36Sopenharmony_ci case GO7007_BOARDID_ADLINK_MPG24: 16662306a36Sopenharmony_ci /* set GPIO5 to be an output, currently low */ 16762306a36Sopenharmony_ci go7007_write_addr(go, 0x3c82, 0x0000); 16862306a36Sopenharmony_ci go7007_write_addr(go, 0x3c80, 0x00df); 16962306a36Sopenharmony_ci break; 17062306a36Sopenharmony_ci case GO7007_BOARDID_ADS_USBAV_709: 17162306a36Sopenharmony_ci /* GPIO pin 0: audio clock control */ 17262306a36Sopenharmony_ci /* pin 2: TW9906 reset */ 17362306a36Sopenharmony_ci /* pin 3: capture LED */ 17462306a36Sopenharmony_ci go7007_write_addr(go, 0x3c82, 0x000d); 17562306a36Sopenharmony_ci go7007_write_addr(go, 0x3c80, 0x00f2); 17662306a36Sopenharmony_ci break; 17762306a36Sopenharmony_ci } 17862306a36Sopenharmony_ci return 0; 17962306a36Sopenharmony_ci} 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci/* 18262306a36Sopenharmony_ci * Send the boot firmware to the GO7007 and configure the registers. This 18362306a36Sopenharmony_ci * is the only way to stop the encoder once it has started streaming video. 18462306a36Sopenharmony_ci * 18562306a36Sopenharmony_ci * Must be called with the hw_lock held. 18662306a36Sopenharmony_ci */ 18762306a36Sopenharmony_ciint go7007_reset_encoder(struct go7007 *go) 18862306a36Sopenharmony_ci{ 18962306a36Sopenharmony_ci if (go7007_load_encoder(go) < 0) 19062306a36Sopenharmony_ci return -1; 19162306a36Sopenharmony_ci return go7007_init_encoder(go); 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci/* 19562306a36Sopenharmony_ci * Attempt to instantiate an I2C client by ID, probably loading a module. 19662306a36Sopenharmony_ci */ 19762306a36Sopenharmony_cistatic int init_i2c_module(struct i2c_adapter *adapter, const struct go_i2c *const i2c) 19862306a36Sopenharmony_ci{ 19962306a36Sopenharmony_ci struct go7007 *go = i2c_get_adapdata(adapter); 20062306a36Sopenharmony_ci struct v4l2_device *v4l2_dev = &go->v4l2_dev; 20162306a36Sopenharmony_ci struct v4l2_subdev *sd; 20262306a36Sopenharmony_ci struct i2c_board_info info; 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci memset(&info, 0, sizeof(info)); 20562306a36Sopenharmony_ci strscpy(info.type, i2c->type, sizeof(info.type)); 20662306a36Sopenharmony_ci info.addr = i2c->addr; 20762306a36Sopenharmony_ci info.flags = i2c->flags; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci sd = v4l2_i2c_new_subdev_board(v4l2_dev, adapter, &info, NULL); 21062306a36Sopenharmony_ci if (sd) { 21162306a36Sopenharmony_ci if (i2c->is_video) 21262306a36Sopenharmony_ci go->sd_video = sd; 21362306a36Sopenharmony_ci if (i2c->is_audio) 21462306a36Sopenharmony_ci go->sd_audio = sd; 21562306a36Sopenharmony_ci return 0; 21662306a36Sopenharmony_ci } 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci pr_info("go7007: probing for module i2c:%s failed\n", i2c->type); 21962306a36Sopenharmony_ci return -EINVAL; 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci/* 22362306a36Sopenharmony_ci * Detach and unregister the encoder. The go7007 struct won't be freed 22462306a36Sopenharmony_ci * until v4l2 finishes releasing its resources and all associated fds are 22562306a36Sopenharmony_ci * closed by applications. 22662306a36Sopenharmony_ci */ 22762306a36Sopenharmony_cistatic void go7007_remove(struct v4l2_device *v4l2_dev) 22862306a36Sopenharmony_ci{ 22962306a36Sopenharmony_ci struct go7007 *go = container_of(v4l2_dev, struct go7007, v4l2_dev); 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci v4l2_device_unregister(v4l2_dev); 23262306a36Sopenharmony_ci if (go->hpi_ops->release) 23362306a36Sopenharmony_ci go->hpi_ops->release(go); 23462306a36Sopenharmony_ci if (go->i2c_adapter_online) { 23562306a36Sopenharmony_ci i2c_del_adapter(&go->i2c_adapter); 23662306a36Sopenharmony_ci go->i2c_adapter_online = 0; 23762306a36Sopenharmony_ci } 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci kfree(go->boot_fw); 24062306a36Sopenharmony_ci go7007_v4l2_remove(go); 24162306a36Sopenharmony_ci kfree(go); 24262306a36Sopenharmony_ci} 24362306a36Sopenharmony_ci 24462306a36Sopenharmony_ci/* 24562306a36Sopenharmony_ci * Finalize the GO7007 hardware setup, register the on-board I2C adapter 24662306a36Sopenharmony_ci * (if used on this board), load the I2C client driver for the sensor 24762306a36Sopenharmony_ci * (SAA7115 or whatever) and other devices, and register the ALSA and V4L2 24862306a36Sopenharmony_ci * interfaces. 24962306a36Sopenharmony_ci * 25062306a36Sopenharmony_ci * Must NOT be called with the hw_lock held. 25162306a36Sopenharmony_ci */ 25262306a36Sopenharmony_ciint go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci int i, ret; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci dev_info(go->dev, "go7007: registering new %s\n", go->name); 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci go->v4l2_dev.release = go7007_remove; 25962306a36Sopenharmony_ci ret = v4l2_device_register(go->dev, &go->v4l2_dev); 26062306a36Sopenharmony_ci if (ret < 0) 26162306a36Sopenharmony_ci return ret; 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_ci mutex_lock(&go->hw_lock); 26462306a36Sopenharmony_ci ret = go7007_init_encoder(go); 26562306a36Sopenharmony_ci mutex_unlock(&go->hw_lock); 26662306a36Sopenharmony_ci if (ret < 0) 26762306a36Sopenharmony_ci return ret; 26862306a36Sopenharmony_ci 26962306a36Sopenharmony_ci ret = go7007_v4l2_ctrl_init(go); 27062306a36Sopenharmony_ci if (ret < 0) 27162306a36Sopenharmony_ci return ret; 27262306a36Sopenharmony_ci 27362306a36Sopenharmony_ci if (!go->i2c_adapter_online && 27462306a36Sopenharmony_ci go->board_info->flags & GO7007_BOARD_USE_ONBOARD_I2C) { 27562306a36Sopenharmony_ci ret = go7007_i2c_init(go); 27662306a36Sopenharmony_ci if (ret < 0) 27762306a36Sopenharmony_ci return ret; 27862306a36Sopenharmony_ci go->i2c_adapter_online = 1; 27962306a36Sopenharmony_ci } 28062306a36Sopenharmony_ci if (go->i2c_adapter_online) { 28162306a36Sopenharmony_ci if (go->board_id == GO7007_BOARDID_ADS_USBAV_709) { 28262306a36Sopenharmony_ci /* Reset the TW9906 */ 28362306a36Sopenharmony_ci go7007_write_addr(go, 0x3c82, 0x0009); 28462306a36Sopenharmony_ci msleep(50); 28562306a36Sopenharmony_ci go7007_write_addr(go, 0x3c82, 0x000d); 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci for (i = 0; i < num_i2c_devs; ++i) 28862306a36Sopenharmony_ci init_i2c_module(&go->i2c_adapter, &go->board_info->i2c_devs[i]); 28962306a36Sopenharmony_ci 29062306a36Sopenharmony_ci if (go->tuner_type >= 0) { 29162306a36Sopenharmony_ci struct tuner_setup setup = { 29262306a36Sopenharmony_ci .addr = ADDR_UNSET, 29362306a36Sopenharmony_ci .type = go->tuner_type, 29462306a36Sopenharmony_ci .mode_mask = T_ANALOG_TV, 29562306a36Sopenharmony_ci }; 29662306a36Sopenharmony_ci 29762306a36Sopenharmony_ci v4l2_device_call_all(&go->v4l2_dev, 0, tuner, 29862306a36Sopenharmony_ci s_type_addr, &setup); 29962306a36Sopenharmony_ci } 30062306a36Sopenharmony_ci if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) 30162306a36Sopenharmony_ci v4l2_subdev_call(go->sd_video, video, s_routing, 30262306a36Sopenharmony_ci 0, 0, go->channel_number + 1); 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci ret = go7007_v4l2_init(go); 30662306a36Sopenharmony_ci if (ret < 0) 30762306a36Sopenharmony_ci return ret; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci if (go->board_info->flags & GO7007_BOARD_HAS_AUDIO) { 31062306a36Sopenharmony_ci go->audio_enabled = 1; 31162306a36Sopenharmony_ci go7007_snd_init(go); 31262306a36Sopenharmony_ci } 31362306a36Sopenharmony_ci return 0; 31462306a36Sopenharmony_ci} 31562306a36Sopenharmony_ciEXPORT_SYMBOL(go7007_register_encoder); 31662306a36Sopenharmony_ci 31762306a36Sopenharmony_ci/* 31862306a36Sopenharmony_ci * Send the encode firmware to the encoder, which will cause it 31962306a36Sopenharmony_ci * to immediately start delivering the video and audio streams. 32062306a36Sopenharmony_ci * 32162306a36Sopenharmony_ci * Must be called with the hw_lock held. 32262306a36Sopenharmony_ci */ 32362306a36Sopenharmony_ciint go7007_start_encoder(struct go7007 *go) 32462306a36Sopenharmony_ci{ 32562306a36Sopenharmony_ci u8 *fw; 32662306a36Sopenharmony_ci int fw_len, rv = 0, i, x, y; 32762306a36Sopenharmony_ci u16 intr_val, intr_data; 32862306a36Sopenharmony_ci 32962306a36Sopenharmony_ci go->modet_enable = 0; 33062306a36Sopenharmony_ci for (i = 0; i < 4; i++) 33162306a36Sopenharmony_ci go->modet[i].enable = 0; 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_ci switch (v4l2_ctrl_g_ctrl(go->modet_mode)) { 33462306a36Sopenharmony_ci case V4L2_DETECT_MD_MODE_GLOBAL: 33562306a36Sopenharmony_ci memset(go->modet_map, 0, sizeof(go->modet_map)); 33662306a36Sopenharmony_ci go->modet[0].enable = 1; 33762306a36Sopenharmony_ci go->modet_enable = 1; 33862306a36Sopenharmony_ci break; 33962306a36Sopenharmony_ci case V4L2_DETECT_MD_MODE_REGION_GRID: 34062306a36Sopenharmony_ci for (y = 0; y < go->height / 16; y++) { 34162306a36Sopenharmony_ci for (x = 0; x < go->width / 16; x++) { 34262306a36Sopenharmony_ci int idx = y * go->width / 16 + x; 34362306a36Sopenharmony_ci 34462306a36Sopenharmony_ci go->modet[go->modet_map[idx]].enable = 1; 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci } 34762306a36Sopenharmony_ci go->modet_enable = 1; 34862306a36Sopenharmony_ci break; 34962306a36Sopenharmony_ci } 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci if (go->dvd_mode) 35262306a36Sopenharmony_ci go->modet_enable = 0; 35362306a36Sopenharmony_ci 35462306a36Sopenharmony_ci if (go7007_construct_fw_image(go, &fw, &fw_len) < 0) 35562306a36Sopenharmony_ci return -1; 35662306a36Sopenharmony_ci 35762306a36Sopenharmony_ci if (go7007_send_firmware(go, fw, fw_len) < 0 || 35862306a36Sopenharmony_ci go7007_read_interrupt(go, &intr_val, &intr_data) < 0) { 35962306a36Sopenharmony_ci v4l2_err(&go->v4l2_dev, "error transferring firmware\n"); 36062306a36Sopenharmony_ci rv = -1; 36162306a36Sopenharmony_ci goto start_error; 36262306a36Sopenharmony_ci } 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci go->state = STATE_DATA; 36562306a36Sopenharmony_ci go->parse_length = 0; 36662306a36Sopenharmony_ci go->seen_frame = 0; 36762306a36Sopenharmony_ci if (go7007_stream_start(go) < 0) { 36862306a36Sopenharmony_ci v4l2_err(&go->v4l2_dev, "error starting stream transfer\n"); 36962306a36Sopenharmony_ci rv = -1; 37062306a36Sopenharmony_ci goto start_error; 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_cistart_error: 37462306a36Sopenharmony_ci kfree(fw); 37562306a36Sopenharmony_ci return rv; 37662306a36Sopenharmony_ci} 37762306a36Sopenharmony_ci 37862306a36Sopenharmony_ci/* 37962306a36Sopenharmony_ci * Store a byte in the current video buffer, if there is one. 38062306a36Sopenharmony_ci */ 38162306a36Sopenharmony_cistatic inline void store_byte(struct go7007_buffer *vb, u8 byte) 38262306a36Sopenharmony_ci{ 38362306a36Sopenharmony_ci if (vb && vb->vb.vb2_buf.planes[0].bytesused < GO7007_BUF_SIZE) { 38462306a36Sopenharmony_ci u8 *ptr = vb2_plane_vaddr(&vb->vb.vb2_buf, 0); 38562306a36Sopenharmony_ci 38662306a36Sopenharmony_ci ptr[vb->vb.vb2_buf.planes[0].bytesused++] = byte; 38762306a36Sopenharmony_ci } 38862306a36Sopenharmony_ci} 38962306a36Sopenharmony_ci 39062306a36Sopenharmony_cistatic void go7007_set_motion_regions(struct go7007 *go, struct go7007_buffer *vb, 39162306a36Sopenharmony_ci u32 motion_regions) 39262306a36Sopenharmony_ci{ 39362306a36Sopenharmony_ci if (motion_regions != go->modet_event_status) { 39462306a36Sopenharmony_ci struct v4l2_event ev = { 39562306a36Sopenharmony_ci .type = V4L2_EVENT_MOTION_DET, 39662306a36Sopenharmony_ci .u.motion_det = { 39762306a36Sopenharmony_ci .flags = V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ, 39862306a36Sopenharmony_ci .frame_sequence = vb->vb.sequence, 39962306a36Sopenharmony_ci .region_mask = motion_regions, 40062306a36Sopenharmony_ci }, 40162306a36Sopenharmony_ci }; 40262306a36Sopenharmony_ci 40362306a36Sopenharmony_ci v4l2_event_queue(&go->vdev, &ev); 40462306a36Sopenharmony_ci go->modet_event_status = motion_regions; 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci} 40762306a36Sopenharmony_ci 40862306a36Sopenharmony_ci/* 40962306a36Sopenharmony_ci * Determine regions with motion and send a motion detection event 41062306a36Sopenharmony_ci * in case of changes. 41162306a36Sopenharmony_ci */ 41262306a36Sopenharmony_cistatic void go7007_motion_regions(struct go7007 *go, struct go7007_buffer *vb) 41362306a36Sopenharmony_ci{ 41462306a36Sopenharmony_ci u32 *bytesused = &vb->vb.vb2_buf.planes[0].bytesused; 41562306a36Sopenharmony_ci unsigned motion[4] = { 0, 0, 0, 0 }; 41662306a36Sopenharmony_ci u32 motion_regions = 0; 41762306a36Sopenharmony_ci unsigned stride = (go->width + 7) >> 3; 41862306a36Sopenharmony_ci unsigned x, y; 41962306a36Sopenharmony_ci int i; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci for (i = 0; i < 216; ++i) 42262306a36Sopenharmony_ci store_byte(vb, go->active_map[i]); 42362306a36Sopenharmony_ci for (y = 0; y < go->height / 16; y++) { 42462306a36Sopenharmony_ci for (x = 0; x < go->width / 16; x++) { 42562306a36Sopenharmony_ci if (!(go->active_map[y * stride + (x >> 3)] & (1 << (x & 7)))) 42662306a36Sopenharmony_ci continue; 42762306a36Sopenharmony_ci motion[go->modet_map[y * (go->width / 16) + x]]++; 42862306a36Sopenharmony_ci } 42962306a36Sopenharmony_ci } 43062306a36Sopenharmony_ci motion_regions = ((motion[0] > 0) << 0) | 43162306a36Sopenharmony_ci ((motion[1] > 0) << 1) | 43262306a36Sopenharmony_ci ((motion[2] > 0) << 2) | 43362306a36Sopenharmony_ci ((motion[3] > 0) << 3); 43462306a36Sopenharmony_ci *bytesused -= 216; 43562306a36Sopenharmony_ci go7007_set_motion_regions(go, vb, motion_regions); 43662306a36Sopenharmony_ci} 43762306a36Sopenharmony_ci 43862306a36Sopenharmony_ci/* 43962306a36Sopenharmony_ci * Deliver the last video buffer and get a new one to start writing to. 44062306a36Sopenharmony_ci */ 44162306a36Sopenharmony_cistatic struct go7007_buffer *frame_boundary(struct go7007 *go, struct go7007_buffer *vb) 44262306a36Sopenharmony_ci{ 44362306a36Sopenharmony_ci u32 *bytesused; 44462306a36Sopenharmony_ci struct go7007_buffer *vb_tmp = NULL; 44562306a36Sopenharmony_ci unsigned long flags; 44662306a36Sopenharmony_ci 44762306a36Sopenharmony_ci if (vb == NULL) { 44862306a36Sopenharmony_ci spin_lock_irqsave(&go->spinlock, flags); 44962306a36Sopenharmony_ci if (!list_empty(&go->vidq_active)) 45062306a36Sopenharmony_ci vb = go->active_buf = 45162306a36Sopenharmony_ci list_first_entry(&go->vidq_active, struct go7007_buffer, list); 45262306a36Sopenharmony_ci spin_unlock_irqrestore(&go->spinlock, flags); 45362306a36Sopenharmony_ci go->next_seq++; 45462306a36Sopenharmony_ci return vb; 45562306a36Sopenharmony_ci } 45662306a36Sopenharmony_ci bytesused = &vb->vb.vb2_buf.planes[0].bytesused; 45762306a36Sopenharmony_ci 45862306a36Sopenharmony_ci vb->vb.sequence = go->next_seq++; 45962306a36Sopenharmony_ci if (vb->modet_active && *bytesused + 216 < GO7007_BUF_SIZE) 46062306a36Sopenharmony_ci go7007_motion_regions(go, vb); 46162306a36Sopenharmony_ci else 46262306a36Sopenharmony_ci go7007_set_motion_regions(go, vb, 0); 46362306a36Sopenharmony_ci 46462306a36Sopenharmony_ci vb->vb.vb2_buf.timestamp = ktime_get_ns(); 46562306a36Sopenharmony_ci vb_tmp = vb; 46662306a36Sopenharmony_ci spin_lock_irqsave(&go->spinlock, flags); 46762306a36Sopenharmony_ci list_del(&vb->list); 46862306a36Sopenharmony_ci if (list_empty(&go->vidq_active)) 46962306a36Sopenharmony_ci vb = NULL; 47062306a36Sopenharmony_ci else 47162306a36Sopenharmony_ci vb = list_first_entry(&go->vidq_active, 47262306a36Sopenharmony_ci struct go7007_buffer, list); 47362306a36Sopenharmony_ci go->active_buf = vb; 47462306a36Sopenharmony_ci spin_unlock_irqrestore(&go->spinlock, flags); 47562306a36Sopenharmony_ci vb2_buffer_done(&vb_tmp->vb.vb2_buf, VB2_BUF_STATE_DONE); 47662306a36Sopenharmony_ci return vb; 47762306a36Sopenharmony_ci} 47862306a36Sopenharmony_ci 47962306a36Sopenharmony_cistatic void write_bitmap_word(struct go7007 *go) 48062306a36Sopenharmony_ci{ 48162306a36Sopenharmony_ci int x, y, i, stride = ((go->width >> 4) + 7) >> 3; 48262306a36Sopenharmony_ci 48362306a36Sopenharmony_ci for (i = 0; i < 16; ++i) { 48462306a36Sopenharmony_ci y = (((go->parse_length - 1) << 3) + i) / (go->width >> 4); 48562306a36Sopenharmony_ci x = (((go->parse_length - 1) << 3) + i) % (go->width >> 4); 48662306a36Sopenharmony_ci if (stride * y + (x >> 3) < sizeof(go->active_map)) 48762306a36Sopenharmony_ci go->active_map[stride * y + (x >> 3)] |= 48862306a36Sopenharmony_ci (go->modet_word & 1) << (x & 0x7); 48962306a36Sopenharmony_ci go->modet_word >>= 1; 49062306a36Sopenharmony_ci } 49162306a36Sopenharmony_ci} 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci/* 49462306a36Sopenharmony_ci * Parse a chunk of the video stream into frames. The frames are not 49562306a36Sopenharmony_ci * delimited by the hardware, so we have to parse the frame boundaries 49662306a36Sopenharmony_ci * based on the type of video stream we're receiving. 49762306a36Sopenharmony_ci */ 49862306a36Sopenharmony_civoid go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length) 49962306a36Sopenharmony_ci{ 50062306a36Sopenharmony_ci struct go7007_buffer *vb = go->active_buf; 50162306a36Sopenharmony_ci int i, seq_start_code = -1, gop_start_code = -1, frame_start_code = -1; 50262306a36Sopenharmony_ci 50362306a36Sopenharmony_ci switch (go->format) { 50462306a36Sopenharmony_ci case V4L2_PIX_FMT_MPEG4: 50562306a36Sopenharmony_ci seq_start_code = 0xB0; 50662306a36Sopenharmony_ci gop_start_code = 0xB3; 50762306a36Sopenharmony_ci frame_start_code = 0xB6; 50862306a36Sopenharmony_ci break; 50962306a36Sopenharmony_ci case V4L2_PIX_FMT_MPEG1: 51062306a36Sopenharmony_ci case V4L2_PIX_FMT_MPEG2: 51162306a36Sopenharmony_ci seq_start_code = 0xB3; 51262306a36Sopenharmony_ci gop_start_code = 0xB8; 51362306a36Sopenharmony_ci frame_start_code = 0x00; 51462306a36Sopenharmony_ci break; 51562306a36Sopenharmony_ci } 51662306a36Sopenharmony_ci 51762306a36Sopenharmony_ci for (i = 0; i < length; ++i) { 51862306a36Sopenharmony_ci if (vb && vb->vb.vb2_buf.planes[0].bytesused >= 51962306a36Sopenharmony_ci GO7007_BUF_SIZE - 3) { 52062306a36Sopenharmony_ci v4l2_info(&go->v4l2_dev, "dropping oversized frame\n"); 52162306a36Sopenharmony_ci vb2_set_plane_payload(&vb->vb.vb2_buf, 0, 0); 52262306a36Sopenharmony_ci vb->frame_offset = 0; 52362306a36Sopenharmony_ci vb->modet_active = 0; 52462306a36Sopenharmony_ci vb = go->active_buf = NULL; 52562306a36Sopenharmony_ci } 52662306a36Sopenharmony_ci 52762306a36Sopenharmony_ci switch (go->state) { 52862306a36Sopenharmony_ci case STATE_DATA: 52962306a36Sopenharmony_ci switch (buf[i]) { 53062306a36Sopenharmony_ci case 0x00: 53162306a36Sopenharmony_ci go->state = STATE_00; 53262306a36Sopenharmony_ci break; 53362306a36Sopenharmony_ci case 0xFF: 53462306a36Sopenharmony_ci go->state = STATE_FF; 53562306a36Sopenharmony_ci break; 53662306a36Sopenharmony_ci default: 53762306a36Sopenharmony_ci store_byte(vb, buf[i]); 53862306a36Sopenharmony_ci break; 53962306a36Sopenharmony_ci } 54062306a36Sopenharmony_ci break; 54162306a36Sopenharmony_ci case STATE_00: 54262306a36Sopenharmony_ci switch (buf[i]) { 54362306a36Sopenharmony_ci case 0x00: 54462306a36Sopenharmony_ci go->state = STATE_00_00; 54562306a36Sopenharmony_ci break; 54662306a36Sopenharmony_ci case 0xFF: 54762306a36Sopenharmony_ci store_byte(vb, 0x00); 54862306a36Sopenharmony_ci go->state = STATE_FF; 54962306a36Sopenharmony_ci break; 55062306a36Sopenharmony_ci default: 55162306a36Sopenharmony_ci store_byte(vb, 0x00); 55262306a36Sopenharmony_ci store_byte(vb, buf[i]); 55362306a36Sopenharmony_ci go->state = STATE_DATA; 55462306a36Sopenharmony_ci break; 55562306a36Sopenharmony_ci } 55662306a36Sopenharmony_ci break; 55762306a36Sopenharmony_ci case STATE_00_00: 55862306a36Sopenharmony_ci switch (buf[i]) { 55962306a36Sopenharmony_ci case 0x00: 56062306a36Sopenharmony_ci store_byte(vb, 0x00); 56162306a36Sopenharmony_ci /* go->state remains STATE_00_00 */ 56262306a36Sopenharmony_ci break; 56362306a36Sopenharmony_ci case 0x01: 56462306a36Sopenharmony_ci go->state = STATE_00_00_01; 56562306a36Sopenharmony_ci break; 56662306a36Sopenharmony_ci case 0xFF: 56762306a36Sopenharmony_ci store_byte(vb, 0x00); 56862306a36Sopenharmony_ci store_byte(vb, 0x00); 56962306a36Sopenharmony_ci go->state = STATE_FF; 57062306a36Sopenharmony_ci break; 57162306a36Sopenharmony_ci default: 57262306a36Sopenharmony_ci store_byte(vb, 0x00); 57362306a36Sopenharmony_ci store_byte(vb, 0x00); 57462306a36Sopenharmony_ci store_byte(vb, buf[i]); 57562306a36Sopenharmony_ci go->state = STATE_DATA; 57662306a36Sopenharmony_ci break; 57762306a36Sopenharmony_ci } 57862306a36Sopenharmony_ci break; 57962306a36Sopenharmony_ci case STATE_00_00_01: 58062306a36Sopenharmony_ci if (buf[i] == 0xF8 && go->modet_enable == 0) { 58162306a36Sopenharmony_ci /* MODET start code, but MODET not enabled */ 58262306a36Sopenharmony_ci store_byte(vb, 0x00); 58362306a36Sopenharmony_ci store_byte(vb, 0x00); 58462306a36Sopenharmony_ci store_byte(vb, 0x01); 58562306a36Sopenharmony_ci store_byte(vb, 0xF8); 58662306a36Sopenharmony_ci go->state = STATE_DATA; 58762306a36Sopenharmony_ci break; 58862306a36Sopenharmony_ci } 58962306a36Sopenharmony_ci /* If this is the start of a new MPEG frame, 59062306a36Sopenharmony_ci * get a new buffer */ 59162306a36Sopenharmony_ci if ((go->format == V4L2_PIX_FMT_MPEG1 || 59262306a36Sopenharmony_ci go->format == V4L2_PIX_FMT_MPEG2 || 59362306a36Sopenharmony_ci go->format == V4L2_PIX_FMT_MPEG4) && 59462306a36Sopenharmony_ci (buf[i] == seq_start_code || 59562306a36Sopenharmony_ci buf[i] == gop_start_code || 59662306a36Sopenharmony_ci buf[i] == frame_start_code)) { 59762306a36Sopenharmony_ci if (vb == NULL || go->seen_frame) 59862306a36Sopenharmony_ci vb = frame_boundary(go, vb); 59962306a36Sopenharmony_ci go->seen_frame = buf[i] == frame_start_code; 60062306a36Sopenharmony_ci if (vb && go->seen_frame) 60162306a36Sopenharmony_ci vb->frame_offset = 60262306a36Sopenharmony_ci vb->vb.vb2_buf.planes[0].bytesused; 60362306a36Sopenharmony_ci } 60462306a36Sopenharmony_ci /* Handle any special chunk types, or just write the 60562306a36Sopenharmony_ci * start code to the (potentially new) buffer */ 60662306a36Sopenharmony_ci switch (buf[i]) { 60762306a36Sopenharmony_ci case 0xF5: /* timestamp */ 60862306a36Sopenharmony_ci go->parse_length = 12; 60962306a36Sopenharmony_ci go->state = STATE_UNPARSED; 61062306a36Sopenharmony_ci break; 61162306a36Sopenharmony_ci case 0xF6: /* vbi */ 61262306a36Sopenharmony_ci go->state = STATE_VBI_LEN_A; 61362306a36Sopenharmony_ci break; 61462306a36Sopenharmony_ci case 0xF8: /* MD map */ 61562306a36Sopenharmony_ci go->parse_length = 0; 61662306a36Sopenharmony_ci memset(go->active_map, 0, 61762306a36Sopenharmony_ci sizeof(go->active_map)); 61862306a36Sopenharmony_ci go->state = STATE_MODET_MAP; 61962306a36Sopenharmony_ci break; 62062306a36Sopenharmony_ci case 0xFF: /* Potential JPEG start code */ 62162306a36Sopenharmony_ci store_byte(vb, 0x00); 62262306a36Sopenharmony_ci store_byte(vb, 0x00); 62362306a36Sopenharmony_ci store_byte(vb, 0x01); 62462306a36Sopenharmony_ci go->state = STATE_FF; 62562306a36Sopenharmony_ci break; 62662306a36Sopenharmony_ci default: 62762306a36Sopenharmony_ci store_byte(vb, 0x00); 62862306a36Sopenharmony_ci store_byte(vb, 0x00); 62962306a36Sopenharmony_ci store_byte(vb, 0x01); 63062306a36Sopenharmony_ci store_byte(vb, buf[i]); 63162306a36Sopenharmony_ci go->state = STATE_DATA; 63262306a36Sopenharmony_ci break; 63362306a36Sopenharmony_ci } 63462306a36Sopenharmony_ci break; 63562306a36Sopenharmony_ci case STATE_FF: 63662306a36Sopenharmony_ci switch (buf[i]) { 63762306a36Sopenharmony_ci case 0x00: 63862306a36Sopenharmony_ci store_byte(vb, 0xFF); 63962306a36Sopenharmony_ci go->state = STATE_00; 64062306a36Sopenharmony_ci break; 64162306a36Sopenharmony_ci case 0xFF: 64262306a36Sopenharmony_ci store_byte(vb, 0xFF); 64362306a36Sopenharmony_ci /* go->state remains STATE_FF */ 64462306a36Sopenharmony_ci break; 64562306a36Sopenharmony_ci case 0xD8: 64662306a36Sopenharmony_ci if (go->format == V4L2_PIX_FMT_MJPEG) 64762306a36Sopenharmony_ci vb = frame_boundary(go, vb); 64862306a36Sopenharmony_ci fallthrough; 64962306a36Sopenharmony_ci default: 65062306a36Sopenharmony_ci store_byte(vb, 0xFF); 65162306a36Sopenharmony_ci store_byte(vb, buf[i]); 65262306a36Sopenharmony_ci go->state = STATE_DATA; 65362306a36Sopenharmony_ci break; 65462306a36Sopenharmony_ci } 65562306a36Sopenharmony_ci break; 65662306a36Sopenharmony_ci case STATE_VBI_LEN_A: 65762306a36Sopenharmony_ci go->parse_length = buf[i] << 8; 65862306a36Sopenharmony_ci go->state = STATE_VBI_LEN_B; 65962306a36Sopenharmony_ci break; 66062306a36Sopenharmony_ci case STATE_VBI_LEN_B: 66162306a36Sopenharmony_ci go->parse_length |= buf[i]; 66262306a36Sopenharmony_ci if (go->parse_length > 0) 66362306a36Sopenharmony_ci go->state = STATE_UNPARSED; 66462306a36Sopenharmony_ci else 66562306a36Sopenharmony_ci go->state = STATE_DATA; 66662306a36Sopenharmony_ci break; 66762306a36Sopenharmony_ci case STATE_MODET_MAP: 66862306a36Sopenharmony_ci if (go->parse_length < 204) { 66962306a36Sopenharmony_ci if (go->parse_length & 1) { 67062306a36Sopenharmony_ci go->modet_word |= buf[i]; 67162306a36Sopenharmony_ci write_bitmap_word(go); 67262306a36Sopenharmony_ci } else 67362306a36Sopenharmony_ci go->modet_word = buf[i] << 8; 67462306a36Sopenharmony_ci } else if (go->parse_length == 207 && vb) { 67562306a36Sopenharmony_ci vb->modet_active = buf[i]; 67662306a36Sopenharmony_ci } 67762306a36Sopenharmony_ci if (++go->parse_length == 208) 67862306a36Sopenharmony_ci go->state = STATE_DATA; 67962306a36Sopenharmony_ci break; 68062306a36Sopenharmony_ci case STATE_UNPARSED: 68162306a36Sopenharmony_ci if (--go->parse_length == 0) 68262306a36Sopenharmony_ci go->state = STATE_DATA; 68362306a36Sopenharmony_ci break; 68462306a36Sopenharmony_ci } 68562306a36Sopenharmony_ci } 68662306a36Sopenharmony_ci} 68762306a36Sopenharmony_ciEXPORT_SYMBOL(go7007_parse_video_stream); 68862306a36Sopenharmony_ci 68962306a36Sopenharmony_ci/* 69062306a36Sopenharmony_ci * Allocate a new go7007 struct. Used by the hardware-specific probe. 69162306a36Sopenharmony_ci */ 69262306a36Sopenharmony_cistruct go7007 *go7007_alloc(const struct go7007_board_info *board, 69362306a36Sopenharmony_ci struct device *dev) 69462306a36Sopenharmony_ci{ 69562306a36Sopenharmony_ci struct go7007 *go; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci go = kzalloc(sizeof(struct go7007), GFP_KERNEL); 69862306a36Sopenharmony_ci if (go == NULL) 69962306a36Sopenharmony_ci return NULL; 70062306a36Sopenharmony_ci go->dev = dev; 70162306a36Sopenharmony_ci go->board_info = board; 70262306a36Sopenharmony_ci go->tuner_type = -1; 70362306a36Sopenharmony_ci mutex_init(&go->hw_lock); 70462306a36Sopenharmony_ci init_waitqueue_head(&go->frame_waitq); 70562306a36Sopenharmony_ci spin_lock_init(&go->spinlock); 70662306a36Sopenharmony_ci go->status = STATUS_INIT; 70762306a36Sopenharmony_ci init_waitqueue_head(&go->interrupt_waitq); 70862306a36Sopenharmony_ci go7007_update_board(go); 70962306a36Sopenharmony_ci go->format = V4L2_PIX_FMT_MJPEG; 71062306a36Sopenharmony_ci go->bitrate = 1500000; 71162306a36Sopenharmony_ci go->fps_scale = 1; 71262306a36Sopenharmony_ci go->aspect_ratio = GO7007_RATIO_1_1; 71362306a36Sopenharmony_ci 71462306a36Sopenharmony_ci return go; 71562306a36Sopenharmony_ci} 71662306a36Sopenharmony_ciEXPORT_SYMBOL(go7007_alloc); 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_civoid go7007_update_board(struct go7007 *go) 71962306a36Sopenharmony_ci{ 72062306a36Sopenharmony_ci const struct go7007_board_info *board = go->board_info; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci if (board->sensor_flags & GO7007_SENSOR_TV) { 72362306a36Sopenharmony_ci go->standard = GO7007_STD_NTSC; 72462306a36Sopenharmony_ci go->std = V4L2_STD_NTSC_M; 72562306a36Sopenharmony_ci go->width = 720; 72662306a36Sopenharmony_ci go->height = 480; 72762306a36Sopenharmony_ci go->sensor_framerate = 30000; 72862306a36Sopenharmony_ci } else { 72962306a36Sopenharmony_ci go->standard = GO7007_STD_OTHER; 73062306a36Sopenharmony_ci go->width = board->sensor_width; 73162306a36Sopenharmony_ci go->height = board->sensor_height; 73262306a36Sopenharmony_ci go->sensor_framerate = board->sensor_framerate; 73362306a36Sopenharmony_ci } 73462306a36Sopenharmony_ci go->encoder_v_offset = board->sensor_v_offset; 73562306a36Sopenharmony_ci go->encoder_h_offset = board->sensor_h_offset; 73662306a36Sopenharmony_ci} 73762306a36Sopenharmony_ciEXPORT_SYMBOL(go7007_update_board); 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 740