Merge branch 'typec' into 4.14.98
diff --git a/MAINTAINERS b/MAINTAINERS
index 6cb70b8..75fecef 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -5717,6 +5717,13 @@
F: scripts/Makefile.gcc-plugins
F: Documentation/gcc-plugins.txt
+GASKET DRIVER FRAMEWORK
+M: Rob Springer <rspringer@google.com>
+M: John Joseph <jnjoseph@google.com>
+M: Ben Chan <benchan@chromium.org>
+S: Maintained
+F: drivers/staging/gasket/
+
GCOV BASED KERNEL PROFILING
M: Peter Oberparleiter <oberpar@linux.vnet.ibm.com>
S: Maintained
diff --git a/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c b/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c
index abeb382..89a0ce8 100644
--- a/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c
+++ b/drivers/media/platform/imx8/mxc-mipi-csi2_yav.c
@@ -316,6 +316,16 @@ static int mipi_csi2_s_power(struct v4l2_subdev *sd, int on)
return 0;
}
+static int mipi_csi2_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct device *dev = &csi2dev->pdev->dev;
+ struct v4l2_subdev *sensor_sd = csi2dev->sensor_sd;
+
+ return v4l2_subdev_call(sensor_sd, core, queryctrl, qc);
+
+}
+
static int mipi_csi2_s_stream(struct v4l2_subdev *sd, int enable)
{
struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
@@ -326,6 +336,10 @@ static int mipi_csi2_s_stream(struct v4l2_subdev *sd, int enable)
dev_dbg(&csi2dev->pdev->dev, "%s: %d, csi2dev: 0x%x\n",
__func__, enable, csi2dev->flags);
+ // TOOO: The below code only works for one CSI camera attached. The old
+ // code was broken for OpenCV, and not according to V4L2 spec, both for the
+ // one and two camera cases and this is a temp fix that takes care of the
+ // one camera case See b/128424247
if (enable) {
if (!csi2dev->running) {
pm_runtime_get_sync(dev);
@@ -335,16 +349,13 @@ static int mipi_csi2_s_stream(struct v4l2_subdev *sd, int enable)
mxc_mipi_csi2_reg_dump(csi2dev);
}
v4l2_subdev_call(sensor_sd, video, s_stream, true);
- csi2dev->running++;
+ csi2dev->running = 1;
- } else {
-
+ } else if (csi2dev->running){
v4l2_subdev_call(sensor_sd, video, s_stream, false);
- csi2dev->running--;
- if (!csi2dev->running) {
- pm_runtime_put(dev);
- mxc_mipi_csi2_disable(csi2dev);
- }
+ csi2dev->running = 0;
+ pm_runtime_put(dev);
+ mxc_mipi_csi2_disable(csi2dev);
}
return ret;
@@ -444,6 +455,7 @@ static struct v4l2_subdev_pad_ops mipi_csi2_pad_ops = {
static struct v4l2_subdev_core_ops mipi_csi2_core_ops = {
.s_power = mipi_csi2_s_power,
+ .queryctrl = mipi_csi2_queryctrl,
};
static struct v4l2_subdev_video_ops mipi_csi2_video_ops = {
diff --git a/drivers/media/platform/mxc/capture/Kconfig b/drivers/media/platform/mxc/capture/Kconfig
index 040b6a0..43f7935 100644
--- a/drivers/media/platform/mxc/capture/Kconfig
+++ b/drivers/media/platform/mxc/capture/Kconfig
@@ -49,6 +49,12 @@
---help---
If you plan to use the ov5642 Camera with your MXC system, say Y here.
+config MXC_CAMERA_OV5645_MIPI_V2
+ tristate "OmniVision ov5645 camera support using mipi"
+ depends on MXC_MIPI_CSI && I2C
+ ---help---
+ If you plan to use the ov5645 Camera with mipi interface in your MXC system, say Y here.
+
config MXC_CAMERA_OV5640_MIPI
tristate "OmniVision ov5640 camera support using mipi"
depends on !VIDEO_MXC_EMMA_CAMERA && I2C && MXC_MIPI_CSI2
diff --git a/drivers/media/platform/mxc/capture/Makefile b/drivers/media/platform/mxc/capture/Makefile
index e6e4b99..20e6a15 100644
--- a/drivers/media/platform/mxc/capture/Makefile
+++ b/drivers/media/platform/mxc/capture/Makefile
@@ -32,6 +32,9 @@
ov5647_camera_mipi-objs := ov5647_mipi.o
obj-$(CONFIG_MXC_CAMERA_OV5647_MIPI) += ov5647_camera_mipi.o
+ov5645_camera_mipi_v2-objs := ov5645_mipi_v2.o
+obj-$(CONFIG_MXC_CAMERA_OV5645_MIPI_V2) += ov5645_camera_mipi_v2.o
+
adv7180_tvin-objs := adv7180.o
obj-$(CONFIG_MXC_TVIN_ADV7180) += adv7180_tvin.o
diff --git a/drivers/media/platform/mxc/capture/mx6s_capture.c b/drivers/media/platform/mxc/capture/mx6s_capture.c
index a1f885d..8ca0242 100644
--- a/drivers/media/platform/mxc/capture/mx6s_capture.c
+++ b/drivers/media/platform/mxc/capture/mx6s_capture.c
@@ -1528,6 +1528,14 @@ static int mx6s_vidioc_expbuf(struct file *file, void *priv,
return -EINVAL;
}
+static int mx6s_vidioc_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *qc)
+{
+ struct mx6s_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+
+ return v4l2_subdev_call(sd, core, queryctrl, qc);
+}
+
static int mx6s_vidioc_streamon(struct file *file, void *priv,
enum v4l2_buf_type i)
{
@@ -1717,6 +1725,7 @@ static const struct v4l2_ioctl_ops mx6s_csi_ioctl_ops = {
.vidioc_s_parm = mx6s_vidioc_s_parm,
.vidioc_enum_framesizes = mx6s_vidioc_enum_framesizes,
.vidioc_enum_frameintervals = mx6s_vidioc_enum_frameintervals,
+ .vidioc_queryctrl = mx6s_vidioc_queryctrl,
};
static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
diff --git a/drivers/media/platform/mxc/capture/ov5645_mipi_v2.c b/drivers/media/platform/mxc/capture/ov5645_mipi_v2.c
new file mode 100644
index 0000000..6ae6aa3
--- /dev/null
+++ b/drivers/media/platform/mxc/capture/ov5645_mipi_v2.c
@@ -0,0 +1,3713 @@
+/*
+ * Copyright (C) 2011-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ */
+
+/*
+ * The code contained herein is licensed under the GNU General Public
+ * License. You may obtain a copy of the GNU General Public License
+ * Version 2 or later at the following locations:
+ *
+ * http://www.opensource.org/licenses/gpl-license.html
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/of_device.h>
+#include <linux/i2c.h>
+#include <linux/of_gpio.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <linux/v4l2-mediabus.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+
+#define OV5645_VOLTAGE_ANALOG 2800000
+#define OV5645_VOLTAGE_DIGITAL_CORE 1500000
+#define OV5645_VOLTAGE_DIGITAL_IO 2800000
+
+#define MIN_FPS 15
+#define MAX_FPS 30
+#define DEFAULT_FPS 30
+
+#define OV5645_XCLK_MIN 6000000
+#define OV5645_XCLK_MAX 24000000
+#define OV5645_XCLK_20MHZ 20000000
+
+#define OV5645_CHIP_ID_HIGH_BYTE 0x300A
+#define OV5645_CHIP_ID_LOW_BYTE 0x300B
+
+enum ov5645_mode {
+ ov5645_mode_MIN = 0,
+ ov5645_mode_VGA_640_480 = 0,
+ ov5645_mode_NTSC_720_480 = 1,
+ ov5645_mode_720P_1280_720 = 2,
+ ov5645_mode_1080P_1920_1080 = 3,
+ ov5645_mode_QSXGA_2592_1944 = 4,
+ ov5645_mode_MAX = 5,
+ ov5645_mode_INIT = 0xff, /*only for sensor init*/
+};
+
+enum ov5645_frame_rate {
+ ov5645_15_fps,
+ ov5645_30_fps
+};
+
+static int ov5645_framerates[] = {
+ [ov5645_15_fps] = 15,
+ [ov5645_30_fps] = 30,
+};
+
+struct ov5645_datafmt {
+ u32 code;
+ enum v4l2_colorspace colorspace;
+};
+
+/* image size under 1280 * 960 are SUBSAMPLING
+ * image size upper 1280 * 960 are SCALING
+ */
+enum ov5645_downsize_mode {
+ SUBSAMPLING,
+ SCALING,
+};
+
+enum ov5645_af_mode {
+ ov5645_af_release = 0,
+ ov5645_af_lock = 1,
+ ov5645_af_cont = 2,
+};
+
+struct reg_value {
+ u16 u16RegAddr;
+ u8 u8Val;
+ u8 u8Mask;
+ u32 u32Delay_ms;
+};
+
+struct ov5645_mode_info {
+ enum ov5645_mode mode;
+ enum ov5645_downsize_mode dn_mode;
+ u32 width;
+ u32 height;
+ struct reg_value *init_data_ptr;
+ u32 init_data_size;
+};
+
+struct ov5645 {
+ struct v4l2_subdev subdev;
+ struct i2c_client *i2c_client;
+ struct v4l2_pix_format pix;
+ const struct ov5645_datafmt *fmt;
+ struct v4l2_captureparm streamcap;
+ bool on;
+
+ /* control settings */
+ int brightness;
+ int hue;
+ int contrast;
+ int saturation;
+ int red;
+ int green;
+ int blue;
+ int ae_mode;
+
+ u32 mclk;
+ u8 mclk_source;
+ struct clk *sensor_clk;
+ int csi;
+
+ void (*io_init)(void);
+
+ /* Fields to keep track of loaded settings */
+ enum ov5645_frame_rate loaded_fps;
+ enum ov5645_mode loaded_mode;
+ bool initialized;
+};
+
+struct ov5645_res {
+ int width;
+ int height;
+};
+
+/*!
+ * Maintains the information on the current state of the sesor.
+ */
+static struct ov5645 ov5645_data;
+static int pwn_gpio, rst_gpio;
+
+struct ov5645_res ov5645_valid_res[] = {
+ [0] = {640, 480},
+ [1] = {720, 480},
+ [2] = {1280, 720},
+ [3] = {1920, 1080},
+ [4] = {2592, 1944},
+};
+
+static struct reg_value ov5645_af_setting[] = {
+ {0x3000, 0x20, 0, 0}, {0x8000, 0x02, 0, 0}, {0x8001, 0x0f, 0, 0},
+ {0x8002, 0xe0, 0, 0}, {0x8003, 0x02, 0, 0}, {0x8004, 0x09, 0, 0},
+ {0x8005, 0x28, 0, 0}, {0x8006, 0xc2, 0, 0}, {0x8007, 0x01, 0, 0},
+ {0x8008, 0x22, 0, 0}, {0x8009, 0x22, 0, 0}, {0x800a, 0x00, 0, 0},
+ {0x800b, 0x02, 0, 0}, {0x800c, 0x0d, 0, 0}, {0x800d, 0xea, 0, 0},
+ {0x800e, 0x30, 0, 0}, {0x800f, 0x01, 0, 0}, {0x8010, 0x03, 0, 0},
+ {0x8011, 0x02, 0, 0}, {0x8012, 0x02, 0, 0}, {0x8013, 0xa6, 0, 0},
+ {0x8014, 0x30, 0, 0}, {0x8015, 0x02, 0, 0}, {0x8016, 0x03, 0, 0},
+ {0x8017, 0x02, 0, 0}, {0x8018, 0x02, 0, 0}, {0x8019, 0xa6, 0, 0},
+ {0x801a, 0x90, 0, 0}, {0x801b, 0x51, 0, 0}, {0x801c, 0xa5, 0, 0},
+ {0x801d, 0xe0, 0, 0}, {0x801e, 0x78, 0, 0}, {0x801f, 0x93, 0, 0},
+ {0x8020, 0xf6, 0, 0}, {0x8021, 0xa3, 0, 0}, {0x8022, 0xe0, 0, 0},
+ {0x8023, 0x08, 0, 0}, {0x8024, 0xf6, 0, 0}, {0x8025, 0xa3, 0, 0},
+ {0x8026, 0xe0, 0, 0}, {0x8027, 0x08, 0, 0}, {0x8028, 0xf6, 0, 0},
+ {0x8029, 0xe5, 0, 0}, {0x802a, 0x1f, 0, 0}, {0x802b, 0x70, 0, 0},
+ {0x802c, 0x4f, 0, 0}, {0x802d, 0x75, 0, 0}, {0x802e, 0x1e, 0, 0},
+ {0x802f, 0x20, 0, 0}, {0x8030, 0xd2, 0, 0}, {0x8031, 0x35, 0, 0},
+ {0x8032, 0xd3, 0, 0}, {0x8033, 0x78, 0, 0}, {0x8034, 0x4f, 0, 0},
+ {0x8035, 0xe6, 0, 0}, {0x8036, 0x94, 0, 0}, {0x8037, 0x00, 0, 0},
+ {0x8038, 0x18, 0, 0}, {0x8039, 0xe6, 0, 0}, {0x803a, 0x94, 0, 0},
+ {0x803b, 0x00, 0, 0}, {0x803c, 0x40, 0, 0}, {0x803d, 0x07, 0, 0},
+ {0x803e, 0xe6, 0, 0}, {0x803f, 0xfe, 0, 0}, {0x8040, 0x08, 0, 0},
+ {0x8041, 0xe6, 0, 0}, {0x8042, 0xff, 0, 0}, {0x8043, 0x80, 0, 0},
+ {0x8044, 0x03, 0, 0}, {0x8045, 0x12, 0, 0}, {0x8046, 0x0c, 0, 0},
+ {0x8047, 0x67, 0, 0}, {0x8048, 0x78, 0, 0}, {0x8049, 0x7e, 0, 0},
+ {0x804a, 0xa6, 0, 0}, {0x804b, 0x06, 0, 0}, {0x804c, 0x08, 0, 0},
+ {0x804d, 0xa6, 0, 0}, {0x804e, 0x07, 0, 0}, {0x804f, 0x78, 0, 0},
+ {0x8050, 0x8b, 0, 0}, {0x8051, 0xa6, 0, 0}, {0x8052, 0x09, 0, 0},
+ {0x8053, 0x18, 0, 0}, {0x8054, 0x76, 0, 0}, {0x8055, 0x01, 0, 0},
+ {0x8056, 0x12, 0, 0}, {0x8057, 0x0c, 0, 0}, {0x8058, 0x67, 0, 0},
+ {0x8059, 0x78, 0, 0}, {0x805a, 0x4e, 0, 0}, {0x805b, 0xa6, 0, 0},
+ {0x805c, 0x06, 0, 0}, {0x805d, 0x08, 0, 0}, {0x805e, 0xa6, 0, 0},
+ {0x805f, 0x07, 0, 0}, {0x8060, 0x78, 0, 0}, {0x8061, 0x8b, 0, 0},
+ {0x8062, 0xe6, 0, 0}, {0x8063, 0x78, 0, 0}, {0x8064, 0x6e, 0, 0},
+ {0x8065, 0xf6, 0, 0}, {0x8066, 0x75, 0, 0}, {0x8067, 0x1f, 0, 0},
+ {0x8068, 0x01, 0, 0}, {0x8069, 0x78, 0, 0}, {0x806a, 0x93, 0, 0},
+ {0x806b, 0xe6, 0, 0}, {0x806c, 0x78, 0, 0}, {0x806d, 0x90, 0, 0},
+ {0x806e, 0xf6, 0, 0}, {0x806f, 0x78, 0, 0}, {0x8070, 0x94, 0, 0},
+ {0x8071, 0xe6, 0, 0}, {0x8072, 0x78, 0, 0}, {0x8073, 0x91, 0, 0},
+ {0x8074, 0xf6, 0, 0}, {0x8075, 0x78, 0, 0}, {0x8076, 0x95, 0, 0},
+ {0x8077, 0xe6, 0, 0}, {0x8078, 0x78, 0, 0}, {0x8079, 0x92, 0, 0},
+ {0x807a, 0xf6, 0, 0}, {0x807b, 0x22, 0, 0}, {0x807c, 0x79, 0, 0},
+ {0x807d, 0x90, 0, 0}, {0x807e, 0xe7, 0, 0}, {0x807f, 0xd3, 0, 0},
+ {0x8080, 0x78, 0, 0}, {0x8081, 0x93, 0, 0}, {0x8082, 0x96, 0, 0},
+ {0x8083, 0x40, 0, 0}, {0x8084, 0x05, 0, 0}, {0x8085, 0xe7, 0, 0},
+ {0x8086, 0x96, 0, 0}, {0x8087, 0xff, 0, 0}, {0x8088, 0x80, 0, 0},
+ {0x8089, 0x08, 0, 0}, {0x808a, 0xc3, 0, 0}, {0x808b, 0x79, 0, 0},
+ {0x808c, 0x93, 0, 0}, {0x808d, 0xe7, 0, 0}, {0x808e, 0x78, 0, 0},
+ {0x808f, 0x90, 0, 0}, {0x8090, 0x96, 0, 0}, {0x8091, 0xff, 0, 0},
+ {0x8092, 0x78, 0, 0}, {0x8093, 0x88, 0, 0}, {0x8094, 0x76, 0, 0},
+ {0x8095, 0x00, 0, 0}, {0x8096, 0x08, 0, 0}, {0x8097, 0xa6, 0, 0},
+ {0x8098, 0x07, 0, 0}, {0x8099, 0x79, 0, 0}, {0x809a, 0x91, 0, 0},
+ {0x809b, 0xe7, 0, 0}, {0x809c, 0xd3, 0, 0}, {0x809d, 0x78, 0, 0},
+ {0x809e, 0x94, 0, 0}, {0x809f, 0x96, 0, 0}, {0x80a0, 0x40, 0, 0},
+ {0x80a1, 0x05, 0, 0}, {0x80a2, 0xe7, 0, 0}, {0x80a3, 0x96, 0, 0},
+ {0x80a4, 0xff, 0, 0}, {0x80a5, 0x80, 0, 0}, {0x80a6, 0x08, 0, 0},
+ {0x80a7, 0xc3, 0, 0}, {0x80a8, 0x79, 0, 0}, {0x80a9, 0x94, 0, 0},
+ {0x80aa, 0xe7, 0, 0}, {0x80ab, 0x78, 0, 0}, {0x80ac, 0x91, 0, 0},
+ {0x80ad, 0x96, 0, 0}, {0x80ae, 0xff, 0, 0}, {0x80af, 0x12, 0, 0},
+ {0x80b0, 0x0c, 0, 0}, {0x80b1, 0xb0, 0, 0}, {0x80b2, 0x79, 0, 0},
+ {0x80b3, 0x92, 0, 0}, {0x80b4, 0xe7, 0, 0}, {0x80b5, 0xd3, 0, 0},
+ {0x80b6, 0x78, 0, 0}, {0x80b7, 0x95, 0, 0}, {0x80b8, 0x96, 0, 0},
+ {0x80b9, 0x40, 0, 0}, {0x80ba, 0x05, 0, 0}, {0x80bb, 0xe7, 0, 0},
+ {0x80bc, 0x96, 0, 0}, {0x80bd, 0xff, 0, 0}, {0x80be, 0x80, 0, 0},
+ {0x80bf, 0x08, 0, 0}, {0x80c0, 0xc3, 0, 0}, {0x80c1, 0x79, 0, 0},
+ {0x80c2, 0x95, 0, 0}, {0x80c3, 0xe7, 0, 0}, {0x80c4, 0x78, 0, 0},
+ {0x80c5, 0x92, 0, 0}, {0x80c6, 0x96, 0, 0}, {0x80c7, 0xff, 0, 0},
+ {0x80c8, 0x12, 0, 0}, {0x80c9, 0x0c, 0, 0}, {0x80ca, 0xb0, 0, 0},
+ {0x80cb, 0x12, 0, 0}, {0x80cc, 0x0c, 0, 0}, {0x80cd, 0x67, 0, 0},
+ {0x80ce, 0x78, 0, 0}, {0x80cf, 0x8a, 0, 0}, {0x80d0, 0xe6, 0, 0},
+ {0x80d1, 0x25, 0, 0}, {0x80d2, 0xe0, 0, 0}, {0x80d3, 0x24, 0, 0},
+ {0x80d4, 0x4e, 0, 0}, {0x80d5, 0xf8, 0, 0}, {0x80d6, 0xa6, 0, 0},
+ {0x80d7, 0x06, 0, 0}, {0x80d8, 0x08, 0, 0}, {0x80d9, 0xa6, 0, 0},
+ {0x80da, 0x07, 0, 0}, {0x80db, 0x78, 0, 0}, {0x80dc, 0x8a, 0, 0},
+ {0x80dd, 0xe6, 0, 0}, {0x80de, 0x24, 0, 0}, {0x80df, 0x6e, 0, 0},
+ {0x80e0, 0xf8, 0, 0}, {0x80e1, 0xa6, 0, 0}, {0x80e2, 0x09, 0, 0},
+ {0x80e3, 0x90, 0, 0}, {0x80e4, 0x0e, 0, 0}, {0x80e5, 0x93, 0, 0},
+ {0x80e6, 0xe4, 0, 0}, {0x80e7, 0x93, 0, 0}, {0x80e8, 0x24, 0, 0},
+ {0x80e9, 0xff, 0, 0}, {0x80ea, 0xff, 0, 0}, {0x80eb, 0xe4, 0, 0},
+ {0x80ec, 0x34, 0, 0}, {0x80ed, 0xff, 0, 0}, {0x80ee, 0xfe, 0, 0},
+ {0x80ef, 0x78, 0, 0}, {0x80f0, 0x8a, 0, 0}, {0x80f1, 0xe6, 0, 0},
+ {0x80f2, 0x24, 0, 0}, {0x80f3, 0x01, 0, 0}, {0x80f4, 0xfd, 0, 0},
+ {0x80f5, 0xe4, 0, 0}, {0x80f6, 0x33, 0, 0}, {0x80f7, 0xfc, 0, 0},
+ {0x80f8, 0xd3, 0, 0}, {0x80f9, 0xed, 0, 0}, {0x80fa, 0x9f, 0, 0},
+ {0x80fb, 0xee, 0, 0}, {0x80fc, 0x64, 0, 0}, {0x80fd, 0x80, 0, 0},
+ {0x80fe, 0xf8, 0, 0}, {0x80ff, 0xec, 0, 0}, {0x8100, 0x64, 0, 0},
+ {0x8101, 0x80, 0, 0}, {0x8102, 0x98, 0, 0}, {0x8103, 0x40, 0, 0},
+ {0x8104, 0x04, 0, 0}, {0x8105, 0x7f, 0, 0}, {0x8106, 0x00, 0, 0},
+ {0x8107, 0x80, 0, 0}, {0x8108, 0x05, 0, 0}, {0x8109, 0x78, 0, 0},
+ {0x810a, 0x8a, 0, 0}, {0x810b, 0xe6, 0, 0}, {0x810c, 0x04, 0, 0},
+ {0x810d, 0xff, 0, 0}, {0x810e, 0x78, 0, 0}, {0x810f, 0x8a, 0, 0},
+ {0x8110, 0xa6, 0, 0}, {0x8111, 0x07, 0, 0}, {0x8112, 0xe5, 0, 0},
+ {0x8113, 0x1f, 0, 0}, {0x8114, 0xb4, 0, 0}, {0x8115, 0x01, 0, 0},
+ {0x8116, 0x0a, 0, 0}, {0x8117, 0xe6, 0, 0}, {0x8118, 0x60, 0, 0},
+ {0x8119, 0x03, 0, 0}, {0x811a, 0x02, 0, 0}, {0x811b, 0x02, 0, 0},
+ {0x811c, 0xa6, 0, 0}, {0x811d, 0x75, 0, 0}, {0x811e, 0x1f, 0, 0},
+ {0x811f, 0x02, 0, 0}, {0x8120, 0x22, 0, 0}, {0x8121, 0x78, 0, 0},
+ {0x8122, 0x4e, 0, 0}, {0x8123, 0xe6, 0, 0}, {0x8124, 0xfe, 0, 0},
+ {0x8125, 0x08, 0, 0}, {0x8126, 0xe6, 0, 0}, {0x8127, 0xff, 0, 0},
+ {0x8128, 0x78, 0, 0}, {0x8129, 0x80, 0, 0}, {0x812a, 0xa6, 0, 0},
+ {0x812b, 0x06, 0, 0}, {0x812c, 0x08, 0, 0}, {0x812d, 0xa6, 0, 0},
+ {0x812e, 0x07, 0, 0}, {0x812f, 0x78, 0, 0}, {0x8130, 0x4e, 0, 0},
+ {0x8131, 0xe6, 0, 0}, {0x8132, 0xfe, 0, 0}, {0x8133, 0x08, 0, 0},
+ {0x8134, 0xe6, 0, 0}, {0x8135, 0xff, 0, 0}, {0x8136, 0x78, 0, 0},
+ {0x8137, 0x82, 0, 0}, {0x8138, 0xa6, 0, 0}, {0x8139, 0x06, 0, 0},
+ {0x813a, 0x08, 0, 0}, {0x813b, 0xa6, 0, 0}, {0x813c, 0x07, 0, 0},
+ {0x813d, 0x78, 0, 0}, {0x813e, 0x6e, 0, 0}, {0x813f, 0xe6, 0, 0},
+ {0x8140, 0x78, 0, 0}, {0x8141, 0x8c, 0, 0}, {0x8142, 0xf6, 0, 0},
+ {0x8143, 0x78, 0, 0}, {0x8144, 0x6e, 0, 0}, {0x8145, 0xe6, 0, 0},
+ {0x8146, 0x78, 0, 0}, {0x8147, 0x8d, 0, 0}, {0x8148, 0xf6, 0, 0},
+ {0x8149, 0x7f, 0, 0}, {0x814a, 0x01, 0, 0}, {0x814b, 0x90, 0, 0},
+ {0x814c, 0x0e, 0, 0}, {0x814d, 0x93, 0, 0}, {0x814e, 0xe4, 0, 0},
+ {0x814f, 0x93, 0, 0}, {0x8150, 0xfe, 0, 0}, {0x8151, 0xef, 0, 0},
+ {0x8152, 0xc3, 0, 0}, {0x8153, 0x9e, 0, 0}, {0x8154, 0x50, 0, 0},
+ {0x8155, 0x5f, 0, 0}, {0x8156, 0xef, 0, 0}, {0x8157, 0x25, 0, 0},
+ {0x8158, 0xe0, 0, 0}, {0x8159, 0x24, 0, 0}, {0x815a, 0x4f, 0, 0},
+ {0x815b, 0xf9, 0, 0}, {0x815c, 0xc3, 0, 0}, {0x815d, 0x78, 0, 0},
+ {0x815e, 0x81, 0, 0}, {0x815f, 0xe6, 0, 0}, {0x8160, 0x97, 0, 0},
+ {0x8161, 0x18, 0, 0}, {0x8162, 0xe6, 0, 0}, {0x8163, 0x19, 0, 0},
+ {0x8164, 0x97, 0, 0}, {0x8165, 0x50, 0, 0}, {0x8166, 0x0a, 0, 0},
+ {0x8167, 0x12, 0, 0}, {0x8168, 0x0c, 0, 0}, {0x8169, 0x98, 0, 0},
+ {0x816a, 0x78, 0, 0}, {0x816b, 0x80, 0, 0}, {0x816c, 0xa6, 0, 0},
+ {0x816d, 0x04, 0, 0}, {0x816e, 0x08, 0, 0}, {0x816f, 0xa6, 0, 0},
+ {0x8170, 0x05, 0, 0}, {0x8171, 0x74, 0, 0}, {0x8172, 0x6e, 0, 0},
+ {0x8173, 0x2f, 0, 0}, {0x8174, 0xf9, 0, 0}, {0x8175, 0x78, 0, 0},
+ {0x8176, 0x8c, 0, 0}, {0x8177, 0xe6, 0, 0}, {0x8178, 0xc3, 0, 0},
+ {0x8179, 0x97, 0, 0}, {0x817a, 0x50, 0, 0}, {0x817b, 0x08, 0, 0},
+ {0x817c, 0x74, 0, 0}, {0x817d, 0x6e, 0, 0}, {0x817e, 0x2f, 0, 0},
+ {0x817f, 0xf8, 0, 0}, {0x8180, 0xe6, 0, 0}, {0x8181, 0x78, 0, 0},
+ {0x8182, 0x8c, 0, 0}, {0x8183, 0xf6, 0, 0}, {0x8184, 0xef, 0, 0},
+ {0x8185, 0x25, 0, 0}, {0x8186, 0xe0, 0, 0}, {0x8187, 0x24, 0, 0},
+ {0x8188, 0x4f, 0, 0}, {0x8189, 0xf9, 0, 0}, {0x818a, 0xd3, 0, 0},
+ {0x818b, 0x78, 0, 0}, {0x818c, 0x83, 0, 0}, {0x818d, 0xe6, 0, 0},
+ {0x818e, 0x97, 0, 0}, {0x818f, 0x18, 0, 0}, {0x8190, 0xe6, 0, 0},
+ {0x8191, 0x19, 0, 0}, {0x8192, 0x97, 0, 0}, {0x8193, 0x40, 0, 0},
+ {0x8194, 0x0a, 0, 0}, {0x8195, 0x12, 0, 0}, {0x8196, 0x0c, 0, 0},
+ {0x8197, 0x98, 0, 0}, {0x8198, 0x78, 0, 0}, {0x8199, 0x82, 0, 0},
+ {0x819a, 0xa6, 0, 0}, {0x819b, 0x04, 0, 0}, {0x819c, 0x08, 0, 0},
+ {0x819d, 0xa6, 0, 0}, {0x819e, 0x05, 0, 0}, {0x819f, 0x74, 0, 0},
+ {0x81a0, 0x6e, 0, 0}, {0x81a1, 0x2f, 0, 0}, {0x81a2, 0xf9, 0, 0},
+ {0x81a3, 0x78, 0, 0}, {0x81a4, 0x8d, 0, 0}, {0x81a5, 0xe6, 0, 0},
+ {0x81a6, 0xd3, 0, 0}, {0x81a7, 0x97, 0, 0}, {0x81a8, 0x40, 0, 0},
+ {0x81a9, 0x08, 0, 0}, {0x81aa, 0x74, 0, 0}, {0x81ab, 0x6e, 0, 0},
+ {0x81ac, 0x2f, 0, 0}, {0x81ad, 0xf8, 0, 0}, {0x81ae, 0xe6, 0, 0},
+ {0x81af, 0x78, 0, 0}, {0x81b0, 0x8d, 0, 0}, {0x81b1, 0xf6, 0, 0},
+ {0x81b2, 0x0f, 0, 0}, {0x81b3, 0x80, 0, 0}, {0x81b4, 0x96, 0, 0},
+ {0x81b5, 0xc3, 0, 0}, {0x81b6, 0x79, 0, 0}, {0x81b7, 0x81, 0, 0},
+ {0x81b8, 0xe7, 0, 0}, {0x81b9, 0x78, 0, 0}, {0x81ba, 0x83, 0, 0},
+ {0x81bb, 0x96, 0, 0}, {0x81bc, 0xff, 0, 0}, {0x81bd, 0x19, 0, 0},
+ {0x81be, 0xe7, 0, 0}, {0x81bf, 0x18, 0, 0}, {0x81c0, 0x96, 0, 0},
+ {0x81c1, 0x78, 0, 0}, {0x81c2, 0x84, 0, 0}, {0x81c3, 0xf6, 0, 0},
+ {0x81c4, 0x08, 0, 0}, {0x81c5, 0xa6, 0, 0}, {0x81c6, 0x07, 0, 0},
+ {0x81c7, 0xc3, 0, 0}, {0x81c8, 0x79, 0, 0}, {0x81c9, 0x8c, 0, 0},
+ {0x81ca, 0xe7, 0, 0}, {0x81cb, 0x78, 0, 0}, {0x81cc, 0x8d, 0, 0},
+ {0x81cd, 0x96, 0, 0}, {0x81ce, 0x08, 0, 0}, {0x81cf, 0xf6, 0, 0},
+ {0x81d0, 0x12, 0, 0}, {0x81d1, 0x0c, 0, 0}, {0x81d2, 0xa4, 0, 0},
+ {0x81d3, 0x40, 0, 0}, {0x81d4, 0x05, 0, 0}, {0x81d5, 0x09, 0, 0},
+ {0x81d6, 0xe7, 0, 0}, {0x81d7, 0x08, 0, 0}, {0x81d8, 0x80, 0, 0},
+ {0x81d9, 0x06, 0, 0}, {0x81da, 0xc3, 0, 0}, {0x81db, 0x79, 0, 0},
+ {0x81dc, 0x7f, 0, 0}, {0x81dd, 0xe7, 0, 0}, {0x81de, 0x78, 0, 0},
+ {0x81df, 0x81, 0, 0}, {0x81e0, 0x96, 0, 0}, {0x81e1, 0xff, 0, 0},
+ {0x81e2, 0x19, 0, 0}, {0x81e3, 0xe7, 0, 0}, {0x81e4, 0x18, 0, 0},
+ {0x81e5, 0x96, 0, 0}, {0x81e6, 0xfe, 0, 0}, {0x81e7, 0x78, 0, 0},
+ {0x81e8, 0x86, 0, 0}, {0x81e9, 0xa6, 0, 0}, {0x81ea, 0x06, 0, 0},
+ {0x81eb, 0x08, 0, 0}, {0x81ec, 0xa6, 0, 0}, {0x81ed, 0x07, 0, 0},
+ {0x81ee, 0x79, 0, 0}, {0x81ef, 0x8c, 0, 0}, {0x81f0, 0xe7, 0, 0},
+ {0x81f1, 0xd3, 0, 0}, {0x81f2, 0x78, 0, 0}, {0x81f3, 0x8b, 0, 0},
+ {0x81f4, 0x96, 0, 0}, {0x81f5, 0x40, 0, 0}, {0x81f6, 0x05, 0, 0},
+ {0x81f7, 0xe7, 0, 0}, {0x81f8, 0x96, 0, 0}, {0x81f9, 0xff, 0, 0},
+ {0x81fa, 0x80, 0, 0}, {0x81fb, 0x08, 0, 0}, {0x81fc, 0xc3, 0, 0},
+ {0x81fd, 0x79, 0, 0}, {0x81fe, 0x8b, 0, 0}, {0x81ff, 0xe7, 0, 0},
+ {0x8200, 0x78, 0, 0}, {0x8201, 0x8c, 0, 0}, {0x8202, 0x96, 0, 0},
+ {0x8203, 0xff, 0, 0}, {0x8204, 0x78, 0, 0}, {0x8205, 0x8f, 0, 0},
+ {0x8206, 0xa6, 0, 0}, {0x8207, 0x07, 0, 0}, {0x8208, 0xe5, 0, 0},
+ {0x8209, 0x1f, 0, 0}, {0x820a, 0x64, 0, 0}, {0x820b, 0x02, 0, 0},
+ {0x820c, 0x60, 0, 0}, {0x820d, 0x03, 0, 0}, {0x820e, 0x02, 0, 0},
+ {0x820f, 0x02, 0, 0}, {0x8210, 0x92, 0, 0}, {0x8211, 0x90, 0, 0},
+ {0x8212, 0x0e, 0, 0}, {0x8213, 0x91, 0, 0}, {0x8214, 0x93, 0, 0},
+ {0x8215, 0xff, 0, 0}, {0x8216, 0x18, 0, 0}, {0x8217, 0xe6, 0, 0},
+ {0x8218, 0xc3, 0, 0}, {0x8219, 0x9f, 0, 0}, {0x821a, 0x40, 0, 0},
+ {0x821b, 0x03, 0, 0}, {0x821c, 0x02, 0, 0}, {0x821d, 0x02, 0, 0},
+ {0x821e, 0xa6, 0, 0}, {0x821f, 0x78, 0, 0}, {0x8220, 0x84, 0, 0},
+ {0x8221, 0x12, 0, 0}, {0x8222, 0x0c, 0, 0}, {0x8223, 0x89, 0, 0},
+ {0x8224, 0x12, 0, 0}, {0x8225, 0x0c, 0, 0}, {0x8226, 0x5e, 0, 0},
+ {0x8227, 0x90, 0, 0}, {0x8228, 0x0e, 0, 0}, {0x8229, 0x8e, 0, 0},
+ {0x822a, 0x12, 0, 0}, {0x822b, 0x0c, 0, 0}, {0x822c, 0x77, 0, 0},
+ {0x822d, 0x78, 0, 0}, {0x822e, 0x80, 0, 0}, {0x822f, 0xe6, 0, 0},
+ {0x8230, 0xfe, 0, 0}, {0x8231, 0x08, 0, 0}, {0x8232, 0xe6, 0, 0},
+ {0x8233, 0xff, 0, 0}, {0x8234, 0x12, 0, 0}, {0x8235, 0x0c, 0, 0},
+ {0x8236, 0xba, 0, 0}, {0x8237, 0x7b, 0, 0}, {0x8238, 0x04, 0, 0},
+ {0x8239, 0x12, 0, 0}, {0x823a, 0x0c, 0, 0}, {0x823b, 0x4c, 0, 0},
+ {0x823c, 0xc3, 0, 0}, {0x823d, 0x12, 0, 0}, {0x823e, 0x06, 0, 0},
+ {0x823f, 0xa6, 0, 0}, {0x8240, 0x50, 0, 0}, {0x8241, 0x64, 0, 0},
+ {0x8242, 0x90, 0, 0}, {0x8243, 0x0e, 0, 0}, {0x8244, 0x92, 0, 0},
+ {0x8245, 0xe4, 0, 0}, {0x8246, 0x93, 0, 0}, {0x8247, 0xff, 0, 0},
+ {0x8248, 0x78, 0, 0}, {0x8249, 0x8f, 0, 0}, {0x824a, 0xe6, 0, 0},
+ {0x824b, 0x9f, 0, 0}, {0x824c, 0x40, 0, 0}, {0x824d, 0x02, 0, 0},
+ {0x824e, 0x80, 0, 0}, {0x824f, 0x11, 0, 0}, {0x8250, 0x90, 0, 0},
+ {0x8251, 0x0e, 0, 0}, {0x8252, 0x90, 0, 0}, {0x8253, 0xe4, 0, 0},
+ {0x8254, 0x93, 0, 0}, {0x8255, 0xff, 0, 0}, {0x8256, 0xd3, 0, 0},
+ {0x8257, 0x78, 0, 0}, {0x8258, 0x89, 0, 0}, {0x8259, 0xe6, 0, 0},
+ {0x825a, 0x9f, 0, 0}, {0x825b, 0x18, 0, 0}, {0x825c, 0xe6, 0, 0},
+ {0x825d, 0x94, 0, 0}, {0x825e, 0x00, 0, 0}, {0x825f, 0x40, 0, 0},
+ {0x8260, 0x03, 0, 0}, {0x8261, 0x75, 0, 0}, {0x8262, 0x1f, 0, 0},
+ {0x8263, 0x05, 0, 0}, {0x8264, 0x78, 0, 0}, {0x8265, 0x86, 0, 0},
+ {0x8266, 0x12, 0, 0}, {0x8267, 0x0c, 0, 0}, {0x8268, 0x89, 0, 0},
+ {0x8269, 0x12, 0, 0}, {0x826a, 0x0c, 0, 0}, {0x826b, 0x5e, 0, 0},
+ {0x826c, 0x90, 0, 0}, {0x826d, 0x0e, 0, 0}, {0x826e, 0x8f, 0, 0},
+ {0x826f, 0x12, 0, 0}, {0x8270, 0x0c, 0, 0}, {0x8271, 0x77, 0, 0},
+ {0x8272, 0x12, 0, 0}, {0x8273, 0x0c, 0, 0}, {0x8274, 0xa4, 0, 0},
+ {0x8275, 0x40, 0, 0}, {0x8276, 0x02, 0, 0}, {0x8277, 0x80, 0, 0},
+ {0x8278, 0x02, 0, 0}, {0x8279, 0x78, 0, 0}, {0x827a, 0x80, 0, 0},
+ {0x827b, 0xe6, 0, 0}, {0x827c, 0xfe, 0, 0}, {0x827d, 0x08, 0, 0},
+ {0x827e, 0xe6, 0, 0}, {0x827f, 0xff, 0, 0}, {0x8280, 0x12, 0, 0},
+ {0x8281, 0x0c, 0, 0}, {0x8282, 0xba, 0, 0}, {0x8283, 0x7b, 0, 0},
+ {0x8284, 0x10, 0, 0}, {0x8285, 0x12, 0, 0}, {0x8286, 0x0c, 0, 0},
+ {0x8287, 0x4c, 0, 0}, {0x8288, 0xd3, 0, 0}, {0x8289, 0x12, 0, 0},
+ {0x828a, 0x06, 0, 0}, {0x828b, 0xa6, 0, 0}, {0x828c, 0x40, 0, 0},
+ {0x828d, 0x18, 0, 0}, {0x828e, 0x75, 0, 0}, {0x828f, 0x1f, 0, 0},
+ {0x8290, 0x05, 0, 0}, {0x8291, 0x22, 0, 0}, {0x8292, 0xe5, 0, 0},
+ {0x8293, 0x1f, 0, 0}, {0x8294, 0xb4, 0, 0}, {0x8295, 0x05, 0, 0},
+ {0x8296, 0x0f, 0, 0}, {0x8297, 0xd2, 0, 0}, {0x8298, 0x01, 0, 0},
+ {0x8299, 0xc2, 0, 0}, {0x829a, 0x02, 0, 0}, {0x829b, 0xe4, 0, 0},
+ {0x829c, 0xf5, 0, 0}, {0x829d, 0x1f, 0, 0}, {0x829e, 0xf5, 0, 0},
+ {0x829f, 0x1e, 0, 0}, {0x82a0, 0xd2, 0, 0}, {0x82a1, 0x35, 0, 0},
+ {0x82a2, 0xd2, 0, 0}, {0x82a3, 0x33, 0, 0}, {0x82a4, 0xd2, 0, 0},
+ {0x82a5, 0x36, 0, 0}, {0x82a6, 0x22, 0, 0}, {0x82a7, 0xe5, 0, 0},
+ {0x82a8, 0x1f, 0, 0}, {0x82a9, 0x70, 0, 0}, {0x82aa, 0x72, 0, 0},
+ {0x82ab, 0xf5, 0, 0}, {0x82ac, 0x1e, 0, 0}, {0x82ad, 0xd2, 0, 0},
+ {0x82ae, 0x35, 0, 0}, {0x82af, 0xff, 0, 0}, {0x82b0, 0xef, 0, 0},
+ {0x82b1, 0x25, 0, 0}, {0x82b2, 0xe0, 0, 0}, {0x82b3, 0x24, 0, 0},
+ {0x82b4, 0x4e, 0, 0}, {0x82b5, 0xf8, 0, 0}, {0x82b6, 0xe4, 0, 0},
+ {0x82b7, 0xf6, 0, 0}, {0x82b8, 0x08, 0, 0}, {0x82b9, 0xf6, 0, 0},
+ {0x82ba, 0x0f, 0, 0}, {0x82bb, 0xbf, 0, 0}, {0x82bc, 0x34, 0, 0},
+ {0x82bd, 0xf2, 0, 0}, {0x82be, 0x90, 0, 0}, {0x82bf, 0x0e, 0, 0},
+ {0x82c0, 0x94, 0, 0}, {0x82c1, 0xe4, 0, 0}, {0x82c2, 0x93, 0, 0},
+ {0x82c3, 0xff, 0, 0}, {0x82c4, 0xe5, 0, 0}, {0x82c5, 0x4b, 0, 0},
+ {0x82c6, 0xc3, 0, 0}, {0x82c7, 0x9f, 0, 0}, {0x82c8, 0x50, 0, 0},
+ {0x82c9, 0x04, 0, 0}, {0x82ca, 0x7f, 0, 0}, {0x82cb, 0x05, 0, 0},
+ {0x82cc, 0x80, 0, 0}, {0x82cd, 0x02, 0, 0}, {0x82ce, 0x7f, 0, 0},
+ {0x82cf, 0xfb, 0, 0}, {0x82d0, 0x78, 0, 0}, {0x82d1, 0xbd, 0, 0},
+ {0x82d2, 0xa6, 0, 0}, {0x82d3, 0x07, 0, 0}, {0x82d4, 0x12, 0, 0},
+ {0x82d5, 0x0e, 0, 0}, {0x82d6, 0xb1, 0, 0}, {0x82d7, 0x40, 0, 0},
+ {0x82d8, 0x04, 0, 0}, {0x82d9, 0x7f, 0, 0}, {0x82da, 0x03, 0, 0},
+ {0x82db, 0x80, 0, 0}, {0x82dc, 0x02, 0, 0}, {0x82dd, 0x7f, 0, 0},
+ {0x82de, 0x30, 0, 0}, {0x82df, 0x78, 0, 0}, {0x82e0, 0xbc, 0, 0},
+ {0x82e1, 0xa6, 0, 0}, {0x82e2, 0x07, 0, 0}, {0x82e3, 0xe6, 0, 0},
+ {0x82e4, 0x18, 0, 0}, {0x82e5, 0xf6, 0, 0}, {0x82e6, 0x08, 0, 0},
+ {0x82e7, 0xe6, 0, 0}, {0x82e8, 0x78, 0, 0}, {0x82e9, 0xb9, 0, 0},
+ {0x82ea, 0xf6, 0, 0}, {0x82eb, 0x78, 0, 0}, {0x82ec, 0xbc, 0, 0},
+ {0x82ed, 0xe6, 0, 0}, {0x82ee, 0x78, 0, 0}, {0x82ef, 0xba, 0, 0},
+ {0x82f0, 0xf6, 0, 0}, {0x82f1, 0x78, 0, 0}, {0x82f2, 0xbf, 0, 0},
+ {0x82f3, 0x76, 0, 0}, {0x82f4, 0x33, 0, 0}, {0x82f5, 0xe4, 0, 0},
+ {0x82f6, 0x08, 0, 0}, {0x82f7, 0xf6, 0, 0}, {0x82f8, 0x78, 0, 0},
+ {0x82f9, 0xb8, 0, 0}, {0x82fa, 0x76, 0, 0}, {0x82fb, 0x01, 0, 0},
+ {0x82fc, 0x75, 0, 0}, {0x82fd, 0x4a, 0, 0}, {0x82fe, 0x02, 0, 0},
+ {0x82ff, 0x78, 0, 0}, {0x8300, 0xb6, 0, 0}, {0x8301, 0xf6, 0, 0},
+ {0x8302, 0x08, 0, 0}, {0x8303, 0xf6, 0, 0}, {0x8304, 0x74, 0, 0},
+ {0x8305, 0xff, 0, 0}, {0x8306, 0x78, 0, 0}, {0x8307, 0xc1, 0, 0},
+ {0x8308, 0xf6, 0, 0}, {0x8309, 0x08, 0, 0}, {0x830a, 0xf6, 0, 0},
+ {0x830b, 0x75, 0, 0}, {0x830c, 0x1f, 0, 0}, {0x830d, 0x01, 0, 0},
+ {0x830e, 0x78, 0, 0}, {0x830f, 0xbc, 0, 0}, {0x8310, 0xe6, 0, 0},
+ {0x8311, 0x75, 0, 0}, {0x8312, 0xf0, 0, 0}, {0x8313, 0x05, 0, 0},
+ {0x8314, 0xa4, 0, 0}, {0x8315, 0xf5, 0, 0}, {0x8316, 0x4b, 0, 0},
+ {0x8317, 0x12, 0, 0}, {0x8318, 0x0b, 0, 0}, {0x8319, 0x39, 0, 0},
+ {0x831a, 0xc2, 0, 0}, {0x831b, 0x37, 0, 0}, {0x831c, 0x22, 0, 0},
+ {0x831d, 0x78, 0, 0}, {0x831e, 0xb8, 0, 0}, {0x831f, 0xe6, 0, 0},
+ {0x8320, 0xd3, 0, 0}, {0x8321, 0x94, 0, 0}, {0x8322, 0x00, 0, 0},
+ {0x8323, 0x40, 0, 0}, {0x8324, 0x02, 0, 0}, {0x8325, 0x16, 0, 0},
+ {0x8326, 0x22, 0, 0}, {0x8327, 0xe5, 0, 0}, {0x8328, 0x1f, 0, 0},
+ {0x8329, 0xb4, 0, 0}, {0x832a, 0x05, 0, 0}, {0x832b, 0x23, 0, 0},
+ {0x832c, 0xe4, 0, 0}, {0x832d, 0xf5, 0, 0}, {0x832e, 0x1f, 0, 0},
+ {0x832f, 0xc2, 0, 0}, {0x8330, 0x01, 0, 0}, {0x8331, 0x78, 0, 0},
+ {0x8332, 0xb6, 0, 0}, {0x8333, 0xe6, 0, 0}, {0x8334, 0xfe, 0, 0},
+ {0x8335, 0x08, 0, 0}, {0x8336, 0xe6, 0, 0}, {0x8337, 0xff, 0, 0},
+ {0x8338, 0x78, 0, 0}, {0x8339, 0x4e, 0, 0}, {0x833a, 0xa6, 0, 0},
+ {0x833b, 0x06, 0, 0}, {0x833c, 0x08, 0, 0}, {0x833d, 0xa6, 0, 0},
+ {0x833e, 0x07, 0, 0}, {0x833f, 0xa2, 0, 0}, {0x8340, 0x37, 0, 0},
+ {0x8341, 0xe4, 0, 0}, {0x8342, 0x33, 0, 0}, {0x8343, 0xf5, 0, 0},
+ {0x8344, 0x3c, 0, 0}, {0x8345, 0x90, 0, 0}, {0x8346, 0x30, 0, 0},
+ {0x8347, 0x28, 0, 0}, {0x8348, 0xf0, 0, 0}, {0x8349, 0x75, 0, 0},
+ {0x834a, 0x1e, 0, 0}, {0x834b, 0x10, 0, 0}, {0x834c, 0xd2, 0, 0},
+ {0x834d, 0x35, 0, 0}, {0x834e, 0x22, 0, 0}, {0x834f, 0xe5, 0, 0},
+ {0x8350, 0x4b, 0, 0}, {0x8351, 0x75, 0, 0}, {0x8352, 0xf0, 0, 0},
+ {0x8353, 0x05, 0, 0}, {0x8354, 0x84, 0, 0}, {0x8355, 0x78, 0, 0},
+ {0x8356, 0xbc, 0, 0}, {0x8357, 0xf6, 0, 0}, {0x8358, 0x90, 0, 0},
+ {0x8359, 0x0e, 0, 0}, {0x835a, 0x8c, 0, 0}, {0x835b, 0xe4, 0, 0},
+ {0x835c, 0x93, 0, 0}, {0x835d, 0xff, 0, 0}, {0x835e, 0x25, 0, 0},
+ {0x835f, 0xe0, 0, 0}, {0x8360, 0x24, 0, 0}, {0x8361, 0x0a, 0, 0},
+ {0x8362, 0xf8, 0, 0}, {0x8363, 0xe6, 0, 0}, {0x8364, 0xfc, 0, 0},
+ {0x8365, 0x08, 0, 0}, {0x8366, 0xe6, 0, 0}, {0x8367, 0xfd, 0, 0},
+ {0x8368, 0x78, 0, 0}, {0x8369, 0xbc, 0, 0}, {0x836a, 0xe6, 0, 0},
+ {0x836b, 0x25, 0, 0}, {0x836c, 0xe0, 0, 0}, {0x836d, 0x24, 0, 0},
+ {0x836e, 0x4e, 0, 0}, {0x836f, 0xf8, 0, 0}, {0x8370, 0xa6, 0, 0},
+ {0x8371, 0x04, 0, 0}, {0x8372, 0x08, 0, 0}, {0x8373, 0xa6, 0, 0},
+ {0x8374, 0x05, 0, 0}, {0x8375, 0xef, 0, 0}, {0x8376, 0x12, 0, 0},
+ {0x8377, 0x0e, 0, 0}, {0x8378, 0xf5, 0, 0}, {0x8379, 0xd3, 0, 0},
+ {0x837a, 0x78, 0, 0}, {0x837b, 0xb7, 0, 0}, {0x837c, 0x96, 0, 0},
+ {0x837d, 0xee, 0, 0}, {0x837e, 0x18, 0, 0}, {0x837f, 0x96, 0, 0},
+ {0x8380, 0x40, 0, 0}, {0x8381, 0x0d, 0, 0}, {0x8382, 0x78, 0, 0},
+ {0x8383, 0xbc, 0, 0}, {0x8384, 0xe6, 0, 0}, {0x8385, 0x78, 0, 0},
+ {0x8386, 0xb9, 0, 0}, {0x8387, 0xf6, 0, 0}, {0x8388, 0x78, 0, 0},
+ {0x8389, 0xb6, 0, 0}, {0x838a, 0xa6, 0, 0}, {0x838b, 0x06, 0, 0},
+ {0x838c, 0x08, 0, 0}, {0x838d, 0xa6, 0, 0}, {0x838e, 0x07, 0, 0},
+ {0x838f, 0x90, 0, 0}, {0x8390, 0x0e, 0, 0}, {0x8391, 0x8c, 0, 0},
+ {0x8392, 0xe4, 0, 0}, {0x8393, 0x93, 0, 0}, {0x8394, 0x12, 0, 0},
+ {0x8395, 0x0e, 0, 0}, {0x8396, 0xf5, 0, 0}, {0x8397, 0xc3, 0, 0},
+ {0x8398, 0x78, 0, 0}, {0x8399, 0xc2, 0, 0}, {0x839a, 0x96, 0, 0},
+ {0x839b, 0xee, 0, 0}, {0x839c, 0x18, 0, 0}, {0x839d, 0x96, 0, 0},
+ {0x839e, 0x50, 0, 0}, {0x839f, 0x0d, 0, 0}, {0x83a0, 0x78, 0, 0},
+ {0x83a1, 0xbc, 0, 0}, {0x83a2, 0xe6, 0, 0}, {0x83a3, 0x78, 0, 0},
+ {0x83a4, 0xba, 0, 0}, {0x83a5, 0xf6, 0, 0}, {0x83a6, 0x78, 0, 0},
+ {0x83a7, 0xc1, 0, 0}, {0x83a8, 0xa6, 0, 0}, {0x83a9, 0x06, 0, 0},
+ {0x83aa, 0x08, 0, 0}, {0x83ab, 0xa6, 0, 0}, {0x83ac, 0x07, 0, 0},
+ {0x83ad, 0x78, 0, 0}, {0x83ae, 0xb6, 0, 0}, {0x83af, 0xe6, 0, 0},
+ {0x83b0, 0xfe, 0, 0}, {0x83b1, 0x08, 0, 0}, {0x83b2, 0xe6, 0, 0},
+ {0x83b3, 0xc3, 0, 0}, {0x83b4, 0x78, 0, 0}, {0x83b5, 0xc2, 0, 0},
+ {0x83b6, 0x96, 0, 0}, {0x83b7, 0xff, 0, 0}, {0x83b8, 0xee, 0, 0},
+ {0x83b9, 0x18, 0, 0}, {0x83ba, 0x96, 0, 0}, {0x83bb, 0x78, 0, 0},
+ {0x83bc, 0xc3, 0, 0}, {0x83bd, 0xf6, 0, 0}, {0x83be, 0x08, 0, 0},
+ {0x83bf, 0xa6, 0, 0}, {0x83c0, 0x07, 0, 0}, {0x83c1, 0x90, 0, 0},
+ {0x83c2, 0x0e, 0, 0}, {0x83c3, 0x96, 0, 0}, {0x83c4, 0xe4, 0, 0},
+ {0x83c5, 0x18, 0, 0}, {0x83c6, 0x12, 0, 0}, {0x83c7, 0x0e, 0, 0},
+ {0x83c8, 0xb8, 0, 0}, {0x83c9, 0x40, 0, 0}, {0x83ca, 0x02, 0, 0},
+ {0x83cb, 0xd2, 0, 0}, {0x83cc, 0x37, 0, 0}, {0x83cd, 0x78, 0, 0},
+ {0x83ce, 0xbc, 0, 0}, {0x83cf, 0xe6, 0, 0}, {0x83d0, 0x08, 0, 0},
+ {0x83d1, 0x26, 0, 0}, {0x83d2, 0x08, 0, 0}, {0x83d3, 0xf6, 0, 0},
+ {0x83d4, 0xe5, 0, 0}, {0x83d5, 0x1f, 0, 0}, {0x83d6, 0x64, 0, 0},
+ {0x83d7, 0x01, 0, 0}, {0x83d8, 0x70, 0, 0}, {0x83d9, 0x7a, 0, 0},
+ {0x83da, 0xe6, 0, 0}, {0x83db, 0xc3, 0, 0}, {0x83dc, 0x78, 0, 0},
+ {0x83dd, 0xc0, 0, 0}, {0x83de, 0x12, 0, 0}, {0x83df, 0x0e, 0, 0},
+ {0x83e0, 0xa1, 0, 0}, {0x83e1, 0x40, 0, 0}, {0x83e2, 0x08, 0, 0},
+ {0x83e3, 0x12, 0, 0}, {0x83e4, 0x0e, 0, 0}, {0x83e5, 0x9c, 0, 0},
+ {0x83e6, 0x50, 0, 0}, {0x83e7, 0x03, 0, 0}, {0x83e8, 0x02, 0, 0},
+ {0x83e9, 0x04, 0, 0}, {0x83ea, 0xf1, 0, 0}, {0x83eb, 0x12, 0, 0},
+ {0x83ec, 0x0e, 0, 0}, {0x83ed, 0xaf, 0, 0}, {0x83ee, 0x40, 0, 0},
+ {0x83ef, 0x04, 0, 0}, {0x83f0, 0x7f, 0, 0}, {0x83f1, 0xfe, 0, 0},
+ {0x83f2, 0x80, 0, 0}, {0x83f3, 0x02, 0, 0}, {0x83f4, 0x7f, 0, 0},
+ {0x83f5, 0x02, 0, 0}, {0x83f6, 0x78, 0, 0}, {0x83f7, 0xbd, 0, 0},
+ {0x83f8, 0xa6, 0, 0}, {0x83f9, 0x07, 0, 0}, {0x83fa, 0x78, 0, 0},
+ {0x83fb, 0xb9, 0, 0}, {0x83fc, 0xe6, 0, 0}, {0x83fd, 0x24, 0, 0},
+ {0x83fe, 0x03, 0, 0}, {0x83ff, 0x78, 0, 0}, {0x8400, 0xbf, 0, 0},
+ {0x8401, 0xf6, 0, 0}, {0x8402, 0x78, 0, 0}, {0x8403, 0xb9, 0, 0},
+ {0x8404, 0xe6, 0, 0}, {0x8405, 0x24, 0, 0}, {0x8406, 0xfd, 0, 0},
+ {0x8407, 0x78, 0, 0}, {0x8408, 0xc0, 0, 0}, {0x8409, 0xf6, 0, 0},
+ {0x840a, 0x18, 0, 0}, {0x840b, 0x12, 0, 0}, {0x840c, 0x0e, 0, 0},
+ {0x840d, 0xb1, 0, 0}, {0x840e, 0x40, 0, 0}, {0x840f, 0x04, 0, 0},
+ {0x8410, 0xe6, 0, 0}, {0x8411, 0xff, 0, 0}, {0x8412, 0x80, 0, 0},
+ {0x8413, 0x02, 0, 0}, {0x8414, 0x7f, 0, 0}, {0x8415, 0x00, 0, 0},
+ {0x8416, 0x12, 0, 0}, {0x8417, 0x0e, 0, 0}, {0x8418, 0xd1, 0, 0},
+ {0x8419, 0x40, 0, 0}, {0x841a, 0x04, 0, 0}, {0x841b, 0xe6, 0, 0},
+ {0x841c, 0xff, 0, 0}, {0x841d, 0x80, 0, 0}, {0x841e, 0x02, 0, 0},
+ {0x841f, 0x7f, 0, 0}, {0x8420, 0x00, 0, 0}, {0x8421, 0x12, 0, 0},
+ {0x8422, 0x0e, 0, 0}, {0x8423, 0xdd, 0, 0}, {0x8424, 0x50, 0, 0},
+ {0x8425, 0x04, 0, 0}, {0x8426, 0xe6, 0, 0}, {0x8427, 0xff, 0, 0},
+ {0x8428, 0x80, 0, 0}, {0x8429, 0x02, 0, 0}, {0x842a, 0x7f, 0, 0},
+ {0x842b, 0x33, 0, 0}, {0x842c, 0x12, 0, 0}, {0x842d, 0x0e, 0, 0},
+ {0x842e, 0xe9, 0, 0}, {0x842f, 0x50, 0, 0}, {0x8430, 0x04, 0, 0},
+ {0x8431, 0xe6, 0, 0}, {0x8432, 0xff, 0, 0}, {0x8433, 0x80, 0, 0},
+ {0x8434, 0x02, 0, 0}, {0x8435, 0x7f, 0, 0}, {0x8436, 0x33, 0, 0},
+ {0x8437, 0x12, 0, 0}, {0x8438, 0x0e, 0, 0}, {0x8439, 0xab, 0, 0},
+ {0x843a, 0x40, 0, 0}, {0x843b, 0x06, 0, 0}, {0x843c, 0x78, 0, 0},
+ {0x843d, 0xc0, 0, 0}, {0x843e, 0xe6, 0, 0}, {0x843f, 0xff, 0, 0},
+ {0x8440, 0x80, 0, 0}, {0x8441, 0x04, 0, 0}, {0x8442, 0x78, 0, 0},
+ {0x8443, 0xbf, 0, 0}, {0x8444, 0xe6, 0, 0}, {0x8445, 0xff, 0, 0},
+ {0x8446, 0x78, 0, 0}, {0x8447, 0xbe, 0, 0}, {0x8448, 0xa6, 0, 0},
+ {0x8449, 0x07, 0, 0}, {0x844a, 0x75, 0, 0}, {0x844b, 0x1f, 0, 0},
+ {0x844c, 0x02, 0, 0}, {0x844d, 0x78, 0, 0}, {0x844e, 0xb8, 0, 0},
+ {0x844f, 0x76, 0, 0}, {0x8450, 0x01, 0, 0}, {0x8451, 0x02, 0, 0},
+ {0x8452, 0x04, 0, 0}, {0x8453, 0xf1, 0, 0}, {0x8454, 0xe5, 0, 0},
+ {0x8455, 0x1f, 0, 0}, {0x8456, 0x64, 0, 0}, {0x8457, 0x02, 0, 0},
+ {0x8458, 0x70, 0, 0}, {0x8459, 0x77, 0, 0}, {0x845a, 0x78, 0, 0},
+ {0x845b, 0xbe, 0, 0}, {0x845c, 0xe6, 0, 0}, {0x845d, 0xff, 0, 0},
+ {0x845e, 0xc3, 0, 0}, {0x845f, 0x78, 0, 0}, {0x8460, 0xc0, 0, 0},
+ {0x8461, 0x12, 0, 0}, {0x8462, 0x0e, 0, 0}, {0x8463, 0xa2, 0, 0},
+ {0x8464, 0x40, 0, 0}, {0x8465, 0x05, 0, 0}, {0x8466, 0x12, 0, 0},
+ {0x8467, 0x0e, 0, 0}, {0x8468, 0x9c, 0, 0}, {0x8469, 0x40, 0, 0},
+ {0x846a, 0x64, 0, 0}, {0x846b, 0x12, 0, 0}, {0x846c, 0x0e, 0, 0},
+ {0x846d, 0xaf, 0, 0}, {0x846e, 0x40, 0, 0}, {0x846f, 0x04, 0, 0},
+ {0x8470, 0x7f, 0, 0}, {0x8471, 0xff, 0, 0}, {0x8472, 0x80, 0, 0},
+ {0x8473, 0x02, 0, 0}, {0x8474, 0x7f, 0, 0}, {0x8475, 0x01, 0, 0},
+ {0x8476, 0x78, 0, 0}, {0x8477, 0xbd, 0, 0}, {0x8478, 0xa6, 0, 0},
+ {0x8479, 0x07, 0, 0}, {0x847a, 0x78, 0, 0}, {0x847b, 0xb9, 0, 0},
+ {0x847c, 0xe6, 0, 0}, {0x847d, 0x04, 0, 0}, {0x847e, 0x78, 0, 0},
+ {0x847f, 0xbf, 0, 0}, {0x8480, 0xf6, 0, 0}, {0x8481, 0x78, 0, 0},
+ {0x8482, 0xb9, 0, 0}, {0x8483, 0xe6, 0, 0}, {0x8484, 0x14, 0, 0},
+ {0x8485, 0x78, 0, 0}, {0x8486, 0xc0, 0, 0}, {0x8487, 0xf6, 0, 0},
+ {0x8488, 0x18, 0, 0}, {0x8489, 0x12, 0, 0}, {0x848a, 0x0e, 0, 0},
+ {0x848b, 0xd6, 0, 0}, {0x848c, 0x40, 0, 0}, {0x848d, 0x04, 0, 0},
+ {0x848e, 0xe6, 0, 0}, {0x848f, 0xff, 0, 0}, {0x8490, 0x80, 0, 0},
+ {0x8491, 0x02, 0, 0}, {0x8492, 0x7f, 0, 0}, {0x8493, 0x00, 0, 0},
+ {0x8494, 0x12, 0, 0}, {0x8495, 0x0e, 0, 0}, {0x8496, 0xd1, 0, 0},
+ {0x8497, 0x40, 0, 0}, {0x8498, 0x04, 0, 0}, {0x8499, 0xe6, 0, 0},
+ {0x849a, 0xff, 0, 0}, {0x849b, 0x80, 0, 0}, {0x849c, 0x02, 0, 0},
+ {0x849d, 0x7f, 0, 0}, {0x849e, 0x00, 0, 0}, {0x849f, 0x12, 0, 0},
+ {0x84a0, 0x0e, 0, 0}, {0x84a1, 0xdd, 0, 0}, {0x84a2, 0x50, 0, 0},
+ {0x84a3, 0x04, 0, 0}, {0x84a4, 0xe6, 0, 0}, {0x84a5, 0xff, 0, 0},
+ {0x84a6, 0x80, 0, 0}, {0x84a7, 0x02, 0, 0}, {0x84a8, 0x7f, 0, 0},
+ {0x84a9, 0x33, 0, 0}, {0x84aa, 0x12, 0, 0}, {0x84ab, 0x0e, 0, 0},
+ {0x84ac, 0xe9, 0, 0}, {0x84ad, 0x50, 0, 0}, {0x84ae, 0x04, 0, 0},
+ {0x84af, 0xe6, 0, 0}, {0x84b0, 0xff, 0, 0}, {0x84b1, 0x80, 0, 0},
+ {0x84b2, 0x02, 0, 0}, {0x84b3, 0x7f, 0, 0}, {0x84b4, 0x33, 0, 0},
+ {0x84b5, 0x12, 0, 0}, {0x84b6, 0x0e, 0, 0}, {0x84b7, 0xab, 0, 0},
+ {0x84b8, 0x40, 0, 0}, {0x84b9, 0x06, 0, 0}, {0x84ba, 0x78, 0, 0},
+ {0x84bb, 0xc0, 0, 0}, {0x84bc, 0xe6, 0, 0}, {0x84bd, 0xff, 0, 0},
+ {0x84be, 0x80, 0, 0}, {0x84bf, 0x04, 0, 0}, {0x84c0, 0x78, 0, 0},
+ {0x84c1, 0xbf, 0, 0}, {0x84c2, 0xe6, 0, 0}, {0x84c3, 0xff, 0, 0},
+ {0x84c4, 0x78, 0, 0}, {0x84c5, 0xbe, 0, 0}, {0x84c6, 0xa6, 0, 0},
+ {0x84c7, 0x07, 0, 0}, {0x84c8, 0x75, 0, 0}, {0x84c9, 0x1f, 0, 0},
+ {0x84ca, 0x03, 0, 0}, {0x84cb, 0x78, 0, 0}, {0x84cc, 0xb8, 0, 0},
+ {0x84cd, 0x76, 0, 0}, {0x84ce, 0x01, 0, 0}, {0x84cf, 0x80, 0, 0},
+ {0x84d0, 0x20, 0, 0}, {0x84d1, 0xe5, 0, 0}, {0x84d2, 0x1f, 0, 0},
+ {0x84d3, 0x64, 0, 0}, {0x84d4, 0x03, 0, 0}, {0x84d5, 0x70, 0, 0},
+ {0x84d6, 0x26, 0, 0}, {0x84d7, 0x78, 0, 0}, {0x84d8, 0xbe, 0, 0},
+ {0x84d9, 0xe6, 0, 0}, {0x84da, 0xff, 0, 0}, {0x84db, 0xc3, 0, 0},
+ {0x84dc, 0x78, 0, 0}, {0x84dd, 0xc0, 0, 0}, {0x84de, 0x12, 0, 0},
+ {0x84df, 0x0e, 0, 0}, {0x84e0, 0xa2, 0, 0}, {0x84e1, 0x40, 0, 0},
+ {0x84e2, 0x05, 0, 0}, {0x84e3, 0x12, 0, 0}, {0x84e4, 0x0e, 0, 0},
+ {0x84e5, 0x9c, 0, 0}, {0x84e6, 0x40, 0, 0}, {0x84e7, 0x09, 0, 0},
+ {0x84e8, 0x78, 0, 0}, {0x84e9, 0xb9, 0, 0}, {0x84ea, 0xe6, 0, 0},
+ {0x84eb, 0x78, 0, 0}, {0x84ec, 0xbe, 0, 0}, {0x84ed, 0xf6, 0, 0},
+ {0x84ee, 0x75, 0, 0}, {0x84ef, 0x1f, 0, 0}, {0x84f0, 0x04, 0, 0},
+ {0x84f1, 0x78, 0, 0}, {0x84f2, 0xbe, 0, 0}, {0x84f3, 0xe6, 0, 0},
+ {0x84f4, 0x75, 0, 0}, {0x84f5, 0xf0, 0, 0}, {0x84f6, 0x05, 0, 0},
+ {0x84f7, 0xa4, 0, 0}, {0x84f8, 0xf5, 0, 0}, {0x84f9, 0x4b, 0, 0},
+ {0x84fa, 0x02, 0, 0}, {0x84fb, 0x0b, 0, 0}, {0x84fc, 0x39, 0, 0},
+ {0x84fd, 0xe5, 0, 0}, {0x84fe, 0x1f, 0, 0}, {0x84ff, 0x64, 0, 0},
+ {0x8500, 0x04, 0, 0}, {0x8501, 0x70, 0, 0}, {0x8502, 0x1e, 0, 0},
+ {0x8503, 0x90, 0, 0}, {0x8504, 0x0e, 0, 0}, {0x8505, 0x95, 0, 0},
+ {0x8506, 0x78, 0, 0}, {0x8507, 0xc3, 0, 0}, {0x8508, 0x12, 0, 0},
+ {0x8509, 0x0e, 0, 0}, {0x850a, 0xb8, 0, 0}, {0x850b, 0x40, 0, 0},
+ {0x850c, 0x04, 0, 0}, {0x850d, 0xd2, 0, 0}, {0x850e, 0x37, 0, 0},
+ {0x850f, 0x80, 0, 0}, {0x8510, 0x0d, 0, 0}, {0x8511, 0x90, 0, 0},
+ {0x8512, 0x0e, 0, 0}, {0x8513, 0x97, 0, 0}, {0x8514, 0xe4, 0, 0},
+ {0x8515, 0x93, 0, 0}, {0x8516, 0xff, 0, 0}, {0x8517, 0x60, 0, 0},
+ {0x8518, 0x05, 0, 0}, {0x8519, 0xf5, 0, 0}, {0x851a, 0x4b, 0, 0},
+ {0x851b, 0x12, 0, 0}, {0x851c, 0x0b, 0, 0}, {0x851d, 0x39, 0, 0},
+ {0x851e, 0x75, 0, 0}, {0x851f, 0x1f, 0, 0}, {0x8520, 0x05, 0, 0},
+ {0x8521, 0x22, 0, 0}, {0x8522, 0xef, 0, 0}, {0x8523, 0x8d, 0, 0},
+ {0x8524, 0xf0, 0, 0}, {0x8525, 0xa4, 0, 0}, {0x8526, 0xa8, 0, 0},
+ {0x8527, 0xf0, 0, 0}, {0x8528, 0xcf, 0, 0}, {0x8529, 0x8c, 0, 0},
+ {0x852a, 0xf0, 0, 0}, {0x852b, 0xa4, 0, 0}, {0x852c, 0x28, 0, 0},
+ {0x852d, 0xce, 0, 0}, {0x852e, 0x8d, 0, 0}, {0x852f, 0xf0, 0, 0},
+ {0x8530, 0xa4, 0, 0}, {0x8531, 0x2e, 0, 0}, {0x8532, 0xfe, 0, 0},
+ {0x8533, 0x22, 0, 0}, {0x8534, 0xbc, 0, 0}, {0x8535, 0x00, 0, 0},
+ {0x8536, 0x0b, 0, 0}, {0x8537, 0xbe, 0, 0}, {0x8538, 0x00, 0, 0},
+ {0x8539, 0x29, 0, 0}, {0x853a, 0xef, 0, 0}, {0x853b, 0x8d, 0, 0},
+ {0x853c, 0xf0, 0, 0}, {0x853d, 0x84, 0, 0}, {0x853e, 0xff, 0, 0},
+ {0x853f, 0xad, 0, 0}, {0x8540, 0xf0, 0, 0}, {0x8541, 0x22, 0, 0},
+ {0x8542, 0xe4, 0, 0}, {0x8543, 0xcc, 0, 0}, {0x8544, 0xf8, 0, 0},
+ {0x8545, 0x75, 0, 0}, {0x8546, 0xf0, 0, 0}, {0x8547, 0x08, 0, 0},
+ {0x8548, 0xef, 0, 0}, {0x8549, 0x2f, 0, 0}, {0x854a, 0xff, 0, 0},
+ {0x854b, 0xee, 0, 0}, {0x854c, 0x33, 0, 0}, {0x854d, 0xfe, 0, 0},
+ {0x854e, 0xec, 0, 0}, {0x854f, 0x33, 0, 0}, {0x8550, 0xfc, 0, 0},
+ {0x8551, 0xee, 0, 0}, {0x8552, 0x9d, 0, 0}, {0x8553, 0xec, 0, 0},
+ {0x8554, 0x98, 0, 0}, {0x8555, 0x40, 0, 0}, {0x8556, 0x05, 0, 0},
+ {0x8557, 0xfc, 0, 0}, {0x8558, 0xee, 0, 0}, {0x8559, 0x9d, 0, 0},
+ {0x855a, 0xfe, 0, 0}, {0x855b, 0x0f, 0, 0}, {0x855c, 0xd5, 0, 0},
+ {0x855d, 0xf0, 0, 0}, {0x855e, 0xe9, 0, 0}, {0x855f, 0xe4, 0, 0},
+ {0x8560, 0xce, 0, 0}, {0x8561, 0xfd, 0, 0}, {0x8562, 0x22, 0, 0},
+ {0x8563, 0xed, 0, 0}, {0x8564, 0xf8, 0, 0}, {0x8565, 0xf5, 0, 0},
+ {0x8566, 0xf0, 0, 0}, {0x8567, 0xee, 0, 0}, {0x8568, 0x84, 0, 0},
+ {0x8569, 0x20, 0, 0}, {0x856a, 0xd2, 0, 0}, {0x856b, 0x1c, 0, 0},
+ {0x856c, 0xfe, 0, 0}, {0x856d, 0xad, 0, 0}, {0x856e, 0xf0, 0, 0},
+ {0x856f, 0x75, 0, 0}, {0x8570, 0xf0, 0, 0}, {0x8571, 0x08, 0, 0},
+ {0x8572, 0xef, 0, 0}, {0x8573, 0x2f, 0, 0}, {0x8574, 0xff, 0, 0},
+ {0x8575, 0xed, 0, 0}, {0x8576, 0x33, 0, 0}, {0x8577, 0xfd, 0, 0},
+ {0x8578, 0x40, 0, 0}, {0x8579, 0x07, 0, 0}, {0x857a, 0x98, 0, 0},
+ {0x857b, 0x50, 0, 0}, {0x857c, 0x06, 0, 0}, {0x857d, 0xd5, 0, 0},
+ {0x857e, 0xf0, 0, 0}, {0x857f, 0xf2, 0, 0}, {0x8580, 0x22, 0, 0},
+ {0x8581, 0xc3, 0, 0}, {0x8582, 0x98, 0, 0}, {0x8583, 0xfd, 0, 0},
+ {0x8584, 0x0f, 0, 0}, {0x8585, 0xd5, 0, 0}, {0x8586, 0xf0, 0, 0},
+ {0x8587, 0xea, 0, 0}, {0x8588, 0x22, 0, 0}, {0x8589, 0xe8, 0, 0},
+ {0x858a, 0x8f, 0, 0}, {0x858b, 0xf0, 0, 0}, {0x858c, 0xa4, 0, 0},
+ {0x858d, 0xcc, 0, 0}, {0x858e, 0x8b, 0, 0}, {0x858f, 0xf0, 0, 0},
+ {0x8590, 0xa4, 0, 0}, {0x8591, 0x2c, 0, 0}, {0x8592, 0xfc, 0, 0},
+ {0x8593, 0xe9, 0, 0}, {0x8594, 0x8e, 0, 0}, {0x8595, 0xf0, 0, 0},
+ {0x8596, 0xa4, 0, 0}, {0x8597, 0x2c, 0, 0}, {0x8598, 0xfc, 0, 0},
+ {0x8599, 0x8a, 0, 0}, {0x859a, 0xf0, 0, 0}, {0x859b, 0xed, 0, 0},
+ {0x859c, 0xa4, 0, 0}, {0x859d, 0x2c, 0, 0}, {0x859e, 0xfc, 0, 0},
+ {0x859f, 0xea, 0, 0}, {0x85a0, 0x8e, 0, 0}, {0x85a1, 0xf0, 0, 0},
+ {0x85a2, 0xa4, 0, 0}, {0x85a3, 0xcd, 0, 0}, {0x85a4, 0xa8, 0, 0},
+ {0x85a5, 0xf0, 0, 0}, {0x85a6, 0x8b, 0, 0}, {0x85a7, 0xf0, 0, 0},
+ {0x85a8, 0xa4, 0, 0}, {0x85a9, 0x2d, 0, 0}, {0x85aa, 0xcc, 0, 0},
+ {0x85ab, 0x38, 0, 0}, {0x85ac, 0x25, 0, 0}, {0x85ad, 0xf0, 0, 0},
+ {0x85ae, 0xfd, 0, 0}, {0x85af, 0xe9, 0, 0}, {0x85b0, 0x8f, 0, 0},
+ {0x85b1, 0xf0, 0, 0}, {0x85b2, 0xa4, 0, 0}, {0x85b3, 0x2c, 0, 0},
+ {0x85b4, 0xcd, 0, 0}, {0x85b5, 0x35, 0, 0}, {0x85b6, 0xf0, 0, 0},
+ {0x85b7, 0xfc, 0, 0}, {0x85b8, 0xeb, 0, 0}, {0x85b9, 0x8e, 0, 0},
+ {0x85ba, 0xf0, 0, 0}, {0x85bb, 0xa4, 0, 0}, {0x85bc, 0xfe, 0, 0},
+ {0x85bd, 0xa9, 0, 0}, {0x85be, 0xf0, 0, 0}, {0x85bf, 0xeb, 0, 0},
+ {0x85c0, 0x8f, 0, 0}, {0x85c1, 0xf0, 0, 0}, {0x85c2, 0xa4, 0, 0},
+ {0x85c3, 0xcf, 0, 0}, {0x85c4, 0xc5, 0, 0}, {0x85c5, 0xf0, 0, 0},
+ {0x85c6, 0x2e, 0, 0}, {0x85c7, 0xcd, 0, 0}, {0x85c8, 0x39, 0, 0},
+ {0x85c9, 0xfe, 0, 0}, {0x85ca, 0xe4, 0, 0}, {0x85cb, 0x3c, 0, 0},
+ {0x85cc, 0xfc, 0, 0}, {0x85cd, 0xea, 0, 0}, {0x85ce, 0xa4, 0, 0},
+ {0x85cf, 0x2d, 0, 0}, {0x85d0, 0xce, 0, 0}, {0x85d1, 0x35, 0, 0},
+ {0x85d2, 0xf0, 0, 0}, {0x85d3, 0xfd, 0, 0}, {0x85d4, 0xe4, 0, 0},
+ {0x85d5, 0x3c, 0, 0}, {0x85d6, 0xfc, 0, 0}, {0x85d7, 0x22, 0, 0},
+ {0x85d8, 0x75, 0, 0}, {0x85d9, 0xf0, 0, 0}, {0x85da, 0x08, 0, 0},
+ {0x85db, 0x75, 0, 0}, {0x85dc, 0x82, 0, 0}, {0x85dd, 0x00, 0, 0},
+ {0x85de, 0xef, 0, 0}, {0x85df, 0x2f, 0, 0}, {0x85e0, 0xff, 0, 0},
+ {0x85e1, 0xee, 0, 0}, {0x85e2, 0x33, 0, 0}, {0x85e3, 0xfe, 0, 0},
+ {0x85e4, 0xcd, 0, 0}, {0x85e5, 0x33, 0, 0}, {0x85e6, 0xcd, 0, 0},
+ {0x85e7, 0xcc, 0, 0}, {0x85e8, 0x33, 0, 0}, {0x85e9, 0xcc, 0, 0},
+ {0x85ea, 0xc5, 0, 0}, {0x85eb, 0x82, 0, 0}, {0x85ec, 0x33, 0, 0},
+ {0x85ed, 0xc5, 0, 0}, {0x85ee, 0x82, 0, 0}, {0x85ef, 0x9b, 0, 0},
+ {0x85f0, 0xed, 0, 0}, {0x85f1, 0x9a, 0, 0}, {0x85f2, 0xec, 0, 0},
+ {0x85f3, 0x99, 0, 0}, {0x85f4, 0xe5, 0, 0}, {0x85f5, 0x82, 0, 0},
+ {0x85f6, 0x98, 0, 0}, {0x85f7, 0x40, 0, 0}, {0x85f8, 0x0c, 0, 0},
+ {0x85f9, 0xf5, 0, 0}, {0x85fa, 0x82, 0, 0}, {0x85fb, 0xee, 0, 0},
+ {0x85fc, 0x9b, 0, 0}, {0x85fd, 0xfe, 0, 0}, {0x85fe, 0xed, 0, 0},
+ {0x85ff, 0x9a, 0, 0}, {0x8600, 0xfd, 0, 0}, {0x8601, 0xec, 0, 0},
+ {0x8602, 0x99, 0, 0}, {0x8603, 0xfc, 0, 0}, {0x8604, 0x0f, 0, 0},
+ {0x8605, 0xd5, 0, 0}, {0x8606, 0xf0, 0, 0}, {0x8607, 0xd6, 0, 0},
+ {0x8608, 0xe4, 0, 0}, {0x8609, 0xce, 0, 0}, {0x860a, 0xfb, 0, 0},
+ {0x860b, 0xe4, 0, 0}, {0x860c, 0xcd, 0, 0}, {0x860d, 0xfa, 0, 0},
+ {0x860e, 0xe4, 0, 0}, {0x860f, 0xcc, 0, 0}, {0x8610, 0xf9, 0, 0},
+ {0x8611, 0xa8, 0, 0}, {0x8612, 0x82, 0, 0}, {0x8613, 0x22, 0, 0},
+ {0x8614, 0xb8, 0, 0}, {0x8615, 0x00, 0, 0}, {0x8616, 0xc1, 0, 0},
+ {0x8617, 0xb9, 0, 0}, {0x8618, 0x00, 0, 0}, {0x8619, 0x59, 0, 0},
+ {0x861a, 0xba, 0, 0}, {0x861b, 0x00, 0, 0}, {0x861c, 0x2d, 0, 0},
+ {0x861d, 0xec, 0, 0}, {0x861e, 0x8b, 0, 0}, {0x861f, 0xf0, 0, 0},
+ {0x8620, 0x84, 0, 0}, {0x8621, 0xcf, 0, 0}, {0x8622, 0xce, 0, 0},
+ {0x8623, 0xcd, 0, 0}, {0x8624, 0xfc, 0, 0}, {0x8625, 0xe5, 0, 0},
+ {0x8626, 0xf0, 0, 0}, {0x8627, 0xcb, 0, 0}, {0x8628, 0xf9, 0, 0},
+ {0x8629, 0x78, 0, 0}, {0x862a, 0x18, 0, 0}, {0x862b, 0xef, 0, 0},
+ {0x862c, 0x2f, 0, 0}, {0x862d, 0xff, 0, 0}, {0x862e, 0xee, 0, 0},
+ {0x862f, 0x33, 0, 0}, {0x8630, 0xfe, 0, 0}, {0x8631, 0xed, 0, 0},
+ {0x8632, 0x33, 0, 0}, {0x8633, 0xfd, 0, 0}, {0x8634, 0xec, 0, 0},
+ {0x8635, 0x33, 0, 0}, {0x8636, 0xfc, 0, 0}, {0x8637, 0xeb, 0, 0},
+ {0x8638, 0x33, 0, 0}, {0x8639, 0xfb, 0, 0}, {0x863a, 0x10, 0, 0},
+ {0x863b, 0xd7, 0, 0}, {0x863c, 0x03, 0, 0}, {0x863d, 0x99, 0, 0},
+ {0x863e, 0x40, 0, 0}, {0x863f, 0x04, 0, 0}, {0x8640, 0xeb, 0, 0},
+ {0x8641, 0x99, 0, 0}, {0x8642, 0xfb, 0, 0}, {0x8643, 0x0f, 0, 0},
+ {0x8644, 0xd8, 0, 0}, {0x8645, 0xe5, 0, 0}, {0x8646, 0xe4, 0, 0},
+ {0x8647, 0xf9, 0, 0}, {0x8648, 0xfa, 0, 0}, {0x8649, 0x22, 0, 0},
+ {0x864a, 0x78, 0, 0}, {0x864b, 0x18, 0, 0}, {0x864c, 0xef, 0, 0},
+ {0x864d, 0x2f, 0, 0}, {0x864e, 0xff, 0, 0}, {0x864f, 0xee, 0, 0},
+ {0x8650, 0x33, 0, 0}, {0x8651, 0xfe, 0, 0}, {0x8652, 0xed, 0, 0},
+ {0x8653, 0x33, 0, 0}, {0x8654, 0xfd, 0, 0}, {0x8655, 0xec, 0, 0},
+ {0x8656, 0x33, 0, 0}, {0x8657, 0xfc, 0, 0}, {0x8658, 0xc9, 0, 0},
+ {0x8659, 0x33, 0, 0}, {0x865a, 0xc9, 0, 0}, {0x865b, 0x10, 0, 0},
+ {0x865c, 0xd7, 0, 0}, {0x865d, 0x05, 0, 0}, {0x865e, 0x9b, 0, 0},
+ {0x865f, 0xe9, 0, 0}, {0x8660, 0x9a, 0, 0}, {0x8661, 0x40, 0, 0},
+ {0x8662, 0x07, 0, 0}, {0x8663, 0xec, 0, 0}, {0x8664, 0x9b, 0, 0},
+ {0x8665, 0xfc, 0, 0}, {0x8666, 0xe9, 0, 0}, {0x8667, 0x9a, 0, 0},
+ {0x8668, 0xf9, 0, 0}, {0x8669, 0x0f, 0, 0}, {0x866a, 0xd8, 0, 0},
+ {0x866b, 0xe0, 0, 0}, {0x866c, 0xe4, 0, 0}, {0x866d, 0xc9, 0, 0},
+ {0x866e, 0xfa, 0, 0}, {0x866f, 0xe4, 0, 0}, {0x8670, 0xcc, 0, 0},
+ {0x8671, 0xfb, 0, 0}, {0x8672, 0x22, 0, 0}, {0x8673, 0x75, 0, 0},
+ {0x8674, 0xf0, 0, 0}, {0x8675, 0x10, 0, 0}, {0x8676, 0xef, 0, 0},
+ {0x8677, 0x2f, 0, 0}, {0x8678, 0xff, 0, 0}, {0x8679, 0xee, 0, 0},
+ {0x867a, 0x33, 0, 0}, {0x867b, 0xfe, 0, 0}, {0x867c, 0xed, 0, 0},
+ {0x867d, 0x33, 0, 0}, {0x867e, 0xfd, 0, 0}, {0x867f, 0xcc, 0, 0},
+ {0x8680, 0x33, 0, 0}, {0x8681, 0xcc, 0, 0}, {0x8682, 0xc8, 0, 0},
+ {0x8683, 0x33, 0, 0}, {0x8684, 0xc8, 0, 0}, {0x8685, 0x10, 0, 0},
+ {0x8686, 0xd7, 0, 0}, {0x8687, 0x07, 0, 0}, {0x8688, 0x9b, 0, 0},
+ {0x8689, 0xec, 0, 0}, {0x868a, 0x9a, 0, 0}, {0x868b, 0xe8, 0, 0},
+ {0x868c, 0x99, 0, 0}, {0x868d, 0x40, 0, 0}, {0x868e, 0x0a, 0, 0},
+ {0x868f, 0xed, 0, 0}, {0x8690, 0x9b, 0, 0}, {0x8691, 0xfd, 0, 0},
+ {0x8692, 0xec, 0, 0}, {0x8693, 0x9a, 0, 0}, {0x8694, 0xfc, 0, 0},
+ {0x8695, 0xe8, 0, 0}, {0x8696, 0x99, 0, 0}, {0x8697, 0xf8, 0, 0},
+ {0x8698, 0x0f, 0, 0}, {0x8699, 0xd5, 0, 0}, {0x869a, 0xf0, 0, 0},
+ {0x869b, 0xda, 0, 0}, {0x869c, 0xe4, 0, 0}, {0x869d, 0xcd, 0, 0},
+ {0x869e, 0xfb, 0, 0}, {0x869f, 0xe4, 0, 0}, {0x86a0, 0xcc, 0, 0},
+ {0x86a1, 0xfa, 0, 0}, {0x86a2, 0xe4, 0, 0}, {0x86a3, 0xc8, 0, 0},
+ {0x86a4, 0xf9, 0, 0}, {0x86a5, 0x22, 0, 0}, {0x86a6, 0xeb, 0, 0},
+ {0x86a7, 0x9f, 0, 0}, {0x86a8, 0xf5, 0, 0}, {0x86a9, 0xf0, 0, 0},
+ {0x86aa, 0xea, 0, 0}, {0x86ab, 0x9e, 0, 0}, {0x86ac, 0x42, 0, 0},
+ {0x86ad, 0xf0, 0, 0}, {0x86ae, 0xe9, 0, 0}, {0x86af, 0x9d, 0, 0},
+ {0x86b0, 0x42, 0, 0}, {0x86b1, 0xf0, 0, 0}, {0x86b2, 0xe8, 0, 0},
+ {0x86b3, 0x9c, 0, 0}, {0x86b4, 0x45, 0, 0}, {0x86b5, 0xf0, 0, 0},
+ {0x86b6, 0x22, 0, 0}, {0x86b7, 0xe8, 0, 0}, {0x86b8, 0x60, 0, 0},
+ {0x86b9, 0x0f, 0, 0}, {0x86ba, 0xec, 0, 0}, {0x86bb, 0xc3, 0, 0},
+ {0x86bc, 0x13, 0, 0}, {0x86bd, 0xfc, 0, 0}, {0x86be, 0xed, 0, 0},
+ {0x86bf, 0x13, 0, 0}, {0x86c0, 0xfd, 0, 0}, {0x86c1, 0xee, 0, 0},
+ {0x86c2, 0x13, 0, 0}, {0x86c3, 0xfe, 0, 0}, {0x86c4, 0xef, 0, 0},
+ {0x86c5, 0x13, 0, 0}, {0x86c6, 0xff, 0, 0}, {0x86c7, 0xd8, 0, 0},
+ {0x86c8, 0xf1, 0, 0}, {0x86c9, 0x22, 0, 0}, {0x86ca, 0xe8, 0, 0},
+ {0x86cb, 0x60, 0, 0}, {0x86cc, 0x0f, 0, 0}, {0x86cd, 0xef, 0, 0},
+ {0x86ce, 0xc3, 0, 0}, {0x86cf, 0x33, 0, 0}, {0x86d0, 0xff, 0, 0},
+ {0x86d1, 0xee, 0, 0}, {0x86d2, 0x33, 0, 0}, {0x86d3, 0xfe, 0, 0},
+ {0x86d4, 0xed, 0, 0}, {0x86d5, 0x33, 0, 0}, {0x86d6, 0xfd, 0, 0},
+ {0x86d7, 0xec, 0, 0}, {0x86d8, 0x33, 0, 0}, {0x86d9, 0xfc, 0, 0},
+ {0x86da, 0xd8, 0, 0}, {0x86db, 0xf1, 0, 0}, {0x86dc, 0x22, 0, 0},
+ {0x86dd, 0xe4, 0, 0}, {0x86de, 0x93, 0, 0}, {0x86df, 0xfc, 0, 0},
+ {0x86e0, 0x74, 0, 0}, {0x86e1, 0x01, 0, 0}, {0x86e2, 0x93, 0, 0},
+ {0x86e3, 0xfd, 0, 0}, {0x86e4, 0x74, 0, 0}, {0x86e5, 0x02, 0, 0},
+ {0x86e6, 0x93, 0, 0}, {0x86e7, 0xfe, 0, 0}, {0x86e8, 0x74, 0, 0},
+ {0x86e9, 0x03, 0, 0}, {0x86ea, 0x93, 0, 0}, {0x86eb, 0xff, 0, 0},
+ {0x86ec, 0x22, 0, 0}, {0x86ed, 0xe6, 0, 0}, {0x86ee, 0xfb, 0, 0},
+ {0x86ef, 0x08, 0, 0}, {0x86f0, 0xe6, 0, 0}, {0x86f1, 0xf9, 0, 0},
+ {0x86f2, 0x08, 0, 0}, {0x86f3, 0xe6, 0, 0}, {0x86f4, 0xfa, 0, 0},
+ {0x86f5, 0x08, 0, 0}, {0x86f6, 0xe6, 0, 0}, {0x86f7, 0xcb, 0, 0},
+ {0x86f8, 0xf8, 0, 0}, {0x86f9, 0x22, 0, 0}, {0x86fa, 0xec, 0, 0},
+ {0x86fb, 0xf6, 0, 0}, {0x86fc, 0x08, 0, 0}, {0x86fd, 0xed, 0, 0},
+ {0x86fe, 0xf6, 0, 0}, {0x86ff, 0x08, 0, 0}, {0x8700, 0xee, 0, 0},
+ {0x8701, 0xf6, 0, 0}, {0x8702, 0x08, 0, 0}, {0x8703, 0xef, 0, 0},
+ {0x8704, 0xf6, 0, 0}, {0x8705, 0x22, 0, 0}, {0x8706, 0xa4, 0, 0},
+ {0x8707, 0x25, 0, 0}, {0x8708, 0x82, 0, 0}, {0x8709, 0xf5, 0, 0},
+ {0x870a, 0x82, 0, 0}, {0x870b, 0xe5, 0, 0}, {0x870c, 0xf0, 0, 0},
+ {0x870d, 0x35, 0, 0}, {0x870e, 0x83, 0, 0}, {0x870f, 0xf5, 0, 0},
+ {0x8710, 0x83, 0, 0}, {0x8711, 0x22, 0, 0}, {0x8712, 0xd0, 0, 0},
+ {0x8713, 0x83, 0, 0}, {0x8714, 0xd0, 0, 0}, {0x8715, 0x82, 0, 0},
+ {0x8716, 0xf8, 0, 0}, {0x8717, 0xe4, 0, 0}, {0x8718, 0x93, 0, 0},
+ {0x8719, 0x70, 0, 0}, {0x871a, 0x12, 0, 0}, {0x871b, 0x74, 0, 0},
+ {0x871c, 0x01, 0, 0}, {0x871d, 0x93, 0, 0}, {0x871e, 0x70, 0, 0},
+ {0x871f, 0x0d, 0, 0}, {0x8720, 0xa3, 0, 0}, {0x8721, 0xa3, 0, 0},
+ {0x8722, 0x93, 0, 0}, {0x8723, 0xf8, 0, 0}, {0x8724, 0x74, 0, 0},
+ {0x8725, 0x01, 0, 0}, {0x8726, 0x93, 0, 0}, {0x8727, 0xf5, 0, 0},
+ {0x8728, 0x82, 0, 0}, {0x8729, 0x88, 0, 0}, {0x872a, 0x83, 0, 0},
+ {0x872b, 0xe4, 0, 0}, {0x872c, 0x73, 0, 0}, {0x872d, 0x74, 0, 0},
+ {0x872e, 0x02, 0, 0}, {0x872f, 0x93, 0, 0}, {0x8730, 0x68, 0, 0},
+ {0x8731, 0x60, 0, 0}, {0x8732, 0xef, 0, 0}, {0x8733, 0xa3, 0, 0},
+ {0x8734, 0xa3, 0, 0}, {0x8735, 0xa3, 0, 0}, {0x8736, 0x80, 0, 0},
+ {0x8737, 0xdf, 0, 0}, {0x8738, 0x90, 0, 0}, {0x8739, 0x38, 0, 0},
+ {0x873a, 0x04, 0, 0}, {0x873b, 0x78, 0, 0}, {0x873c, 0x52, 0, 0},
+ {0x873d, 0x12, 0, 0}, {0x873e, 0x0b, 0, 0}, {0x873f, 0x0f, 0, 0},
+ {0x8740, 0x90, 0, 0}, {0x8741, 0x38, 0, 0}, {0x8742, 0x00, 0, 0},
+ {0x8743, 0xe0, 0, 0}, {0x8744, 0xfe, 0, 0}, {0x8745, 0xa3, 0, 0},
+ {0x8746, 0xe0, 0, 0}, {0x8747, 0xfd, 0, 0}, {0x8748, 0xed, 0, 0},
+ {0x8749, 0xff, 0, 0}, {0x874a, 0xc3, 0, 0}, {0x874b, 0x12, 0, 0},
+ {0x874c, 0x0a, 0, 0}, {0x874d, 0xb0, 0, 0}, {0x874e, 0x90, 0, 0},
+ {0x874f, 0x38, 0, 0}, {0x8750, 0x10, 0, 0}, {0x8751, 0x12, 0, 0},
+ {0x8752, 0x0a, 0, 0}, {0x8753, 0xa4, 0, 0}, {0x8754, 0x90, 0, 0},
+ {0x8755, 0x38, 0, 0}, {0x8756, 0x06, 0, 0}, {0x8757, 0x78, 0, 0},
+ {0x8758, 0x54, 0, 0}, {0x8759, 0x12, 0, 0}, {0x875a, 0x0b, 0, 0},
+ {0x875b, 0x0f, 0, 0}, {0x875c, 0x90, 0, 0}, {0x875d, 0x38, 0, 0},
+ {0x875e, 0x02, 0, 0}, {0x875f, 0xe0, 0, 0}, {0x8760, 0xfe, 0, 0},
+ {0x8761, 0xa3, 0, 0}, {0x8762, 0xe0, 0, 0}, {0x8763, 0xfd, 0, 0},
+ {0x8764, 0xed, 0, 0}, {0x8765, 0xff, 0, 0}, {0x8766, 0xc3, 0, 0},
+ {0x8767, 0x12, 0, 0}, {0x8768, 0x0a, 0, 0}, {0x8769, 0xb0, 0, 0},
+ {0x876a, 0x90, 0, 0}, {0x876b, 0x38, 0, 0}, {0x876c, 0x12, 0, 0},
+ {0x876d, 0x12, 0, 0}, {0x876e, 0x0a, 0, 0}, {0x876f, 0xa4, 0, 0},
+ {0x8770, 0xa3, 0, 0}, {0x8771, 0xe0, 0, 0}, {0x8772, 0xb4, 0, 0},
+ {0x8773, 0x31, 0, 0}, {0x8774, 0x07, 0, 0}, {0x8775, 0x78, 0, 0},
+ {0x8776, 0x52, 0, 0}, {0x8777, 0x79, 0, 0}, {0x8778, 0x52, 0, 0},
+ {0x8779, 0x12, 0, 0}, {0x877a, 0x0b, 0, 0}, {0x877b, 0x2f, 0, 0},
+ {0x877c, 0x90, 0, 0}, {0x877d, 0x38, 0, 0}, {0x877e, 0x14, 0, 0},
+ {0x877f, 0xe0, 0, 0}, {0x8780, 0xb4, 0, 0}, {0x8781, 0x71, 0, 0},
+ {0x8782, 0x15, 0, 0}, {0x8783, 0x78, 0, 0}, {0x8784, 0x52, 0, 0},
+ {0x8785, 0xe6, 0, 0}, {0x8786, 0xfe, 0, 0}, {0x8787, 0x08, 0, 0},
+ {0x8788, 0xe6, 0, 0}, {0x8789, 0x78, 0, 0}, {0x878a, 0x02, 0, 0},
+ {0x878b, 0xce, 0, 0}, {0x878c, 0xc3, 0, 0}, {0x878d, 0x13, 0, 0},
+ {0x878e, 0xce, 0, 0}, {0x878f, 0x13, 0, 0}, {0x8790, 0xd8, 0, 0},
+ {0x8791, 0xf9, 0, 0}, {0x8792, 0x79, 0, 0}, {0x8793, 0x53, 0, 0},
+ {0x8794, 0xf7, 0, 0}, {0x8795, 0xee, 0, 0}, {0x8796, 0x19, 0, 0},
+ {0x8797, 0xf7, 0, 0}, {0x8798, 0x90, 0, 0}, {0x8799, 0x38, 0, 0},
+ {0x879a, 0x15, 0, 0}, {0x879b, 0xe0, 0, 0}, {0x879c, 0xb4, 0, 0},
+ {0x879d, 0x31, 0, 0}, {0x879e, 0x07, 0, 0}, {0x879f, 0x78, 0, 0},
+ {0x87a0, 0x54, 0, 0}, {0x87a1, 0x79, 0, 0}, {0x87a2, 0x54, 0, 0},
+ {0x87a3, 0x12, 0, 0}, {0x87a4, 0x0b, 0, 0}, {0x87a5, 0x2f, 0, 0},
+ {0x87a6, 0x90, 0, 0}, {0x87a7, 0x38, 0, 0}, {0x87a8, 0x15, 0, 0},
+ {0x87a9, 0xe0, 0, 0}, {0x87aa, 0xb4, 0, 0}, {0x87ab, 0x71, 0, 0},
+ {0x87ac, 0x15, 0, 0}, {0x87ad, 0x78, 0, 0}, {0x87ae, 0x54, 0, 0},
+ {0x87af, 0xe6, 0, 0}, {0x87b0, 0xfe, 0, 0}, {0x87b1, 0x08, 0, 0},
+ {0x87b2, 0xe6, 0, 0}, {0x87b3, 0x78, 0, 0}, {0x87b4, 0x02, 0, 0},
+ {0x87b5, 0xce, 0, 0}, {0x87b6, 0xc3, 0, 0}, {0x87b7, 0x13, 0, 0},
+ {0x87b8, 0xce, 0, 0}, {0x87b9, 0x13, 0, 0}, {0x87ba, 0xd8, 0, 0},
+ {0x87bb, 0xf9, 0, 0}, {0x87bc, 0x79, 0, 0}, {0x87bd, 0x55, 0, 0},
+ {0x87be, 0xf7, 0, 0}, {0x87bf, 0xee, 0, 0}, {0x87c0, 0x19, 0, 0},
+ {0x87c1, 0xf7, 0, 0}, {0x87c2, 0x79, 0, 0}, {0x87c3, 0x52, 0, 0},
+ {0x87c4, 0x12, 0, 0}, {0x87c5, 0x0a, 0, 0}, {0x87c6, 0xeb, 0, 0},
+ {0x87c7, 0x09, 0, 0}, {0x87c8, 0x12, 0, 0}, {0x87c9, 0x0a, 0, 0},
+ {0x87ca, 0xeb, 0, 0}, {0x87cb, 0xaf, 0, 0}, {0x87cc, 0x47, 0, 0},
+ {0x87cd, 0x12, 0, 0}, {0x87ce, 0x0a, 0, 0}, {0x87cf, 0xc4, 0, 0},
+ {0x87d0, 0xe5, 0, 0}, {0x87d1, 0x44, 0, 0}, {0x87d2, 0xfb, 0, 0},
+ {0x87d3, 0x7a, 0, 0}, {0x87d4, 0x00, 0, 0}, {0x87d5, 0xfd, 0, 0},
+ {0x87d6, 0x7c, 0, 0}, {0x87d7, 0x00, 0, 0}, {0x87d8, 0x12, 0, 0},
+ {0x87d9, 0x05, 0, 0}, {0x87da, 0x34, 0, 0}, {0x87db, 0x78, 0, 0},
+ {0x87dc, 0x5a, 0, 0}, {0x87dd, 0xa6, 0, 0}, {0x87de, 0x06, 0, 0},
+ {0x87df, 0x08, 0, 0}, {0x87e0, 0xa6, 0, 0}, {0x87e1, 0x07, 0, 0},
+ {0x87e2, 0xaf, 0, 0}, {0x87e3, 0x45, 0, 0}, {0x87e4, 0x12, 0, 0},
+ {0x87e5, 0x0a, 0, 0}, {0x87e6, 0xc4, 0, 0}, {0x87e7, 0xad, 0, 0},
+ {0x87e8, 0x03, 0, 0}, {0x87e9, 0x7c, 0, 0}, {0x87ea, 0x00, 0, 0},
+ {0x87eb, 0x12, 0, 0}, {0x87ec, 0x05, 0, 0}, {0x87ed, 0x34, 0, 0},
+ {0x87ee, 0x78, 0, 0}, {0x87ef, 0x56, 0, 0}, {0x87f0, 0xa6, 0, 0},
+ {0x87f1, 0x06, 0, 0}, {0x87f2, 0x08, 0, 0}, {0x87f3, 0xa6, 0, 0},
+ {0x87f4, 0x07, 0, 0}, {0x87f5, 0xaf, 0, 0}, {0x87f6, 0x48, 0, 0},
+ {0x87f7, 0x78, 0, 0}, {0x87f8, 0x54, 0, 0}, {0x87f9, 0x12, 0, 0},
+ {0x87fa, 0x0a, 0, 0}, {0x87fb, 0xc6, 0, 0}, {0x87fc, 0xe5, 0, 0},
+ {0x87fd, 0x43, 0, 0}, {0x87fe, 0xfb, 0, 0}, {0x87ff, 0xfd, 0, 0},
+ {0x8800, 0x7c, 0, 0}, {0x8801, 0x00, 0, 0}, {0x8802, 0x12, 0, 0},
+ {0x8803, 0x05, 0, 0}, {0x8804, 0x34, 0, 0}, {0x8805, 0x78, 0, 0},
+ {0x8806, 0x5c, 0, 0}, {0x8807, 0xa6, 0, 0}, {0x8808, 0x06, 0, 0},
+ {0x8809, 0x08, 0, 0}, {0x880a, 0xa6, 0, 0}, {0x880b, 0x07, 0, 0},
+ {0x880c, 0xaf, 0, 0}, {0x880d, 0x46, 0, 0}, {0x880e, 0x7e, 0, 0},
+ {0x880f, 0x00, 0, 0}, {0x8810, 0x78, 0, 0}, {0x8811, 0x54, 0, 0},
+ {0x8812, 0x12, 0, 0}, {0x8813, 0x0a, 0, 0}, {0x8814, 0xc8, 0, 0},
+ {0x8815, 0xad, 0, 0}, {0x8816, 0x03, 0, 0}, {0x8817, 0x7c, 0, 0},
+ {0x8818, 0x00, 0, 0}, {0x8819, 0x12, 0, 0}, {0x881a, 0x05, 0, 0},
+ {0x881b, 0x34, 0, 0}, {0x881c, 0x78, 0, 0}, {0x881d, 0x58, 0, 0},
+ {0x881e, 0xa6, 0, 0}, {0x881f, 0x06, 0, 0}, {0x8820, 0x08, 0, 0},
+ {0x8821, 0xa6, 0, 0}, {0x8822, 0x07, 0, 0}, {0x8823, 0xc3, 0, 0},
+ {0x8824, 0x78, 0, 0}, {0x8825, 0x5b, 0, 0}, {0x8826, 0xe6, 0, 0},
+ {0x8827, 0x94, 0, 0}, {0x8828, 0x08, 0, 0}, {0x8829, 0x18, 0, 0},
+ {0x882a, 0xe6, 0, 0}, {0x882b, 0x94, 0, 0}, {0x882c, 0x00, 0, 0},
+ {0x882d, 0x50, 0, 0}, {0x882e, 0x05, 0, 0}, {0x882f, 0x76, 0, 0},
+ {0x8830, 0x00, 0, 0}, {0x8831, 0x08, 0, 0}, {0x8832, 0x76, 0, 0},
+ {0x8833, 0x08, 0, 0}, {0x8834, 0xc3, 0, 0}, {0x8835, 0x78, 0, 0},
+ {0x8836, 0x5d, 0, 0}, {0x8837, 0xe6, 0, 0}, {0x8838, 0x94, 0, 0},
+ {0x8839, 0x08, 0, 0}, {0x883a, 0x18, 0, 0}, {0x883b, 0xe6, 0, 0},
+ {0x883c, 0x94, 0, 0}, {0x883d, 0x00, 0, 0}, {0x883e, 0x50, 0, 0},
+ {0x883f, 0x05, 0, 0}, {0x8840, 0x76, 0, 0}, {0x8841, 0x00, 0, 0},
+ {0x8842, 0x08, 0, 0}, {0x8843, 0x76, 0, 0}, {0x8844, 0x08, 0, 0},
+ {0x8845, 0x78, 0, 0}, {0x8846, 0x5a, 0, 0}, {0x8847, 0x12, 0, 0},
+ {0x8848, 0x0a, 0, 0}, {0x8849, 0xd8, 0, 0}, {0x884a, 0xff, 0, 0},
+ {0x884b, 0xd3, 0, 0}, {0x884c, 0x78, 0, 0}, {0x884d, 0x57, 0, 0},
+ {0x884e, 0xe6, 0, 0}, {0x884f, 0x9f, 0, 0}, {0x8850, 0x18, 0, 0},
+ {0x8851, 0xe6, 0, 0}, {0x8852, 0x9e, 0, 0}, {0x8853, 0x40, 0, 0},
+ {0x8854, 0x0e, 0, 0}, {0x8855, 0x78, 0, 0}, {0x8856, 0x5a, 0, 0},
+ {0x8857, 0xe6, 0, 0}, {0x8858, 0x13, 0, 0}, {0x8859, 0xfe, 0, 0},
+ {0x885a, 0x08, 0, 0}, {0x885b, 0xe6, 0, 0}, {0x885c, 0x78, 0, 0},
+ {0x885d, 0x57, 0, 0}, {0x885e, 0x12, 0, 0}, {0x885f, 0x0b, 0, 0},
+ {0x8860, 0x1a, 0, 0}, {0x8861, 0x80, 0, 0}, {0x8862, 0x04, 0, 0},
+ {0x8863, 0x7e, 0, 0}, {0x8864, 0x00, 0, 0}, {0x8865, 0x7f, 0, 0},
+ {0x8866, 0x00, 0, 0}, {0x8867, 0x78, 0, 0}, {0x8868, 0x5e, 0, 0},
+ {0x8869, 0x12, 0, 0}, {0x886a, 0x0a, 0, 0}, {0x886b, 0xd0, 0, 0},
+ {0x886c, 0xff, 0, 0}, {0x886d, 0xd3, 0, 0}, {0x886e, 0x78, 0, 0},
+ {0x886f, 0x59, 0, 0}, {0x8870, 0xe6, 0, 0}, {0x8871, 0x9f, 0, 0},
+ {0x8872, 0x18, 0, 0}, {0x8873, 0xe6, 0, 0}, {0x8874, 0x9e, 0, 0},
+ {0x8875, 0x40, 0, 0}, {0x8876, 0x0e, 0, 0}, {0x8877, 0x78, 0, 0},
+ {0x8878, 0x5c, 0, 0}, {0x8879, 0xe6, 0, 0}, {0x887a, 0x13, 0, 0},
+ {0x887b, 0xfe, 0, 0}, {0x887c, 0x08, 0, 0}, {0x887d, 0xe6, 0, 0},
+ {0x887e, 0x78, 0, 0}, {0x887f, 0x59, 0, 0}, {0x8880, 0x12, 0, 0},
+ {0x8881, 0x0b, 0, 0}, {0x8882, 0x1a, 0, 0}, {0x8883, 0x80, 0, 0},
+ {0x8884, 0x04, 0, 0}, {0x8885, 0x7e, 0, 0}, {0x8886, 0x00, 0, 0},
+ {0x8887, 0x7f, 0, 0}, {0x8888, 0x00, 0, 0}, {0x8889, 0xe4, 0, 0},
+ {0x888a, 0xfc, 0, 0}, {0x888b, 0xfd, 0, 0}, {0x888c, 0x78, 0, 0},
+ {0x888d, 0x62, 0, 0}, {0x888e, 0x12, 0, 0}, {0x888f, 0x06, 0, 0},
+ {0x8890, 0xfa, 0, 0}, {0x8891, 0x78, 0, 0}, {0x8892, 0x5a, 0, 0},
+ {0x8893, 0x12, 0, 0}, {0x8894, 0x0a, 0, 0}, {0x8895, 0xd8, 0, 0},
+ {0x8896, 0x78, 0, 0}, {0x8897, 0x57, 0, 0}, {0x8898, 0x26, 0, 0},
+ {0x8899, 0xff, 0, 0}, {0x889a, 0xee, 0, 0}, {0x889b, 0x18, 0, 0},
+ {0x889c, 0x36, 0, 0}, {0x889d, 0xfe, 0, 0}, {0x889e, 0x78, 0, 0},
+ {0x889f, 0x66, 0, 0}, {0x88a0, 0x12, 0, 0}, {0x88a1, 0x0a, 0, 0},
+ {0x88a2, 0xd0, 0, 0}, {0x88a3, 0x78, 0, 0}, {0x88a4, 0x59, 0, 0},
+ {0x88a5, 0x26, 0, 0}, {0x88a6, 0xff, 0, 0}, {0x88a7, 0xee, 0, 0},
+ {0x88a8, 0x18, 0, 0}, {0x88a9, 0x36, 0, 0}, {0x88aa, 0xfe, 0, 0},
+ {0x88ab, 0xe4, 0, 0}, {0x88ac, 0xfc, 0, 0}, {0x88ad, 0xfd, 0, 0},
+ {0x88ae, 0x78, 0, 0}, {0x88af, 0x6a, 0, 0}, {0x88b0, 0x12, 0, 0},
+ {0x88b1, 0x06, 0, 0}, {0x88b2, 0xfa, 0, 0}, {0x88b3, 0x12, 0, 0},
+ {0x88b4, 0x0a, 0, 0}, {0x88b5, 0xe0, 0, 0}, {0x88b6, 0x78, 0, 0},
+ {0x88b7, 0x66, 0, 0}, {0x88b8, 0x12, 0, 0}, {0x88b9, 0x06, 0, 0},
+ {0x88ba, 0xed, 0, 0}, {0x88bb, 0xd3, 0, 0}, {0x88bc, 0x12, 0, 0},
+ {0x88bd, 0x06, 0, 0}, {0x88be, 0xa6, 0, 0}, {0x88bf, 0x40, 0, 0},
+ {0x88c0, 0x08, 0, 0}, {0x88c1, 0x12, 0, 0}, {0x88c2, 0x0a, 0, 0},
+ {0x88c3, 0xe0, 0, 0}, {0x88c4, 0x78, 0, 0}, {0x88c5, 0x66, 0, 0},
+ {0x88c6, 0x12, 0, 0}, {0x88c7, 0x06, 0, 0}, {0x88c8, 0xfa, 0, 0},
+ {0x88c9, 0x78, 0, 0}, {0x88ca, 0x54, 0, 0}, {0x88cb, 0x12, 0, 0},
+ {0x88cc, 0x0a, 0, 0}, {0x88cd, 0xe2, 0, 0}, {0x88ce, 0x78, 0, 0},
+ {0x88cf, 0x6a, 0, 0}, {0x88d0, 0x12, 0, 0}, {0x88d1, 0x06, 0, 0},
+ {0x88d2, 0xed, 0, 0}, {0x88d3, 0xd3, 0, 0}, {0x88d4, 0x12, 0, 0},
+ {0x88d5, 0x06, 0, 0}, {0x88d6, 0xa6, 0, 0}, {0x88d7, 0x40, 0, 0},
+ {0x88d8, 0x0a, 0, 0}, {0x88d9, 0x78, 0, 0}, {0x88da, 0x54, 0, 0},
+ {0x88db, 0x12, 0, 0}, {0x88dc, 0x0a, 0, 0}, {0x88dd, 0xe2, 0, 0},
+ {0x88de, 0x78, 0, 0}, {0x88df, 0x6a, 0, 0}, {0x88e0, 0x12, 0, 0},
+ {0x88e1, 0x06, 0, 0}, {0x88e2, 0xfa, 0, 0}, {0x88e3, 0x78, 0, 0},
+ {0x88e4, 0x61, 0, 0}, {0x88e5, 0xe6, 0, 0}, {0x88e6, 0x90, 0, 0},
+ {0x88e7, 0x60, 0, 0}, {0x88e8, 0x01, 0, 0}, {0x88e9, 0xf0, 0, 0},
+ {0x88ea, 0x78, 0, 0}, {0x88eb, 0x65, 0, 0}, {0x88ec, 0xe6, 0, 0},
+ {0x88ed, 0xa3, 0, 0}, {0x88ee, 0xf0, 0, 0}, {0x88ef, 0x78, 0, 0},
+ {0x88f0, 0x69, 0, 0}, {0x88f1, 0xe6, 0, 0}, {0x88f2, 0xa3, 0, 0},
+ {0x88f3, 0xf0, 0, 0}, {0x88f4, 0x78, 0, 0}, {0x88f5, 0x55, 0, 0},
+ {0x88f6, 0xe6, 0, 0}, {0x88f7, 0xa3, 0, 0}, {0x88f8, 0xf0, 0, 0},
+ {0x88f9, 0x7d, 0, 0}, {0x88fa, 0x01, 0, 0}, {0x88fb, 0x78, 0, 0},
+ {0x88fc, 0x61, 0, 0}, {0x88fd, 0x12, 0, 0}, {0x88fe, 0x0a, 0, 0},
+ {0x88ff, 0xfb, 0, 0}, {0x8900, 0x24, 0, 0}, {0x8901, 0x01, 0, 0},
+ {0x8902, 0x12, 0, 0}, {0x8903, 0x0a, 0, 0}, {0x8904, 0xb8, 0, 0},
+ {0x8905, 0x78, 0, 0}, {0x8906, 0x65, 0, 0}, {0x8907, 0x12, 0, 0},
+ {0x8908, 0x0a, 0, 0}, {0x8909, 0xfb, 0, 0}, {0x890a, 0x24, 0, 0},
+ {0x890b, 0x02, 0, 0}, {0x890c, 0x12, 0, 0}, {0x890d, 0x0a, 0, 0},
+ {0x890e, 0xb8, 0, 0}, {0x890f, 0x78, 0, 0}, {0x8910, 0x69, 0, 0},
+ {0x8911, 0x12, 0, 0}, {0x8912, 0x0a, 0, 0}, {0x8913, 0xfb, 0, 0},
+ {0x8914, 0x24, 0, 0}, {0x8915, 0x03, 0, 0}, {0x8916, 0x12, 0, 0},
+ {0x8917, 0x0a, 0, 0}, {0x8918, 0xb8, 0, 0}, {0x8919, 0x78, 0, 0},
+ {0x891a, 0x6d, 0, 0}, {0x891b, 0x12, 0, 0}, {0x891c, 0x0a, 0, 0},
+ {0x891d, 0xfb, 0, 0}, {0x891e, 0x24, 0, 0}, {0x891f, 0x04, 0, 0},
+ {0x8920, 0x12, 0, 0}, {0x8921, 0x0a, 0, 0}, {0x8922, 0xb8, 0, 0},
+ {0x8923, 0x0d, 0, 0}, {0x8924, 0xbd, 0, 0}, {0x8925, 0x05, 0, 0},
+ {0x8926, 0xd4, 0, 0}, {0x8927, 0x22, 0, 0}, {0x8928, 0xc0, 0, 0},
+ {0x8929, 0xe0, 0, 0}, {0x892a, 0xc0, 0, 0}, {0x892b, 0x83, 0, 0},
+ {0x892c, 0xc0, 0, 0}, {0x892d, 0x82, 0, 0}, {0x892e, 0xc0, 0, 0},
+ {0x892f, 0xd0, 0, 0}, {0x8930, 0x90, 0, 0}, {0x8931, 0x3f, 0, 0},
+ {0x8932, 0x0c, 0, 0}, {0x8933, 0xe0, 0, 0}, {0x8934, 0xf5, 0, 0},
+ {0x8935, 0x32, 0, 0}, {0x8936, 0xe5, 0, 0}, {0x8937, 0x32, 0, 0},
+ {0x8938, 0x30, 0, 0}, {0x8939, 0xe3, 0, 0}, {0x893a, 0x74, 0, 0},
+ {0x893b, 0x30, 0, 0}, {0x893c, 0x36, 0, 0}, {0x893d, 0x66, 0, 0},
+ {0x893e, 0x90, 0, 0}, {0x893f, 0x60, 0, 0}, {0x8940, 0x19, 0, 0},
+ {0x8941, 0xe0, 0, 0}, {0x8942, 0xf5, 0, 0}, {0x8943, 0x0a, 0, 0},
+ {0x8944, 0xa3, 0, 0}, {0x8945, 0xe0, 0, 0}, {0x8946, 0xf5, 0, 0},
+ {0x8947, 0x0b, 0, 0}, {0x8948, 0x90, 0, 0}, {0x8949, 0x60, 0, 0},
+ {0x894a, 0x1d, 0, 0}, {0x894b, 0xe0, 0, 0}, {0x894c, 0xf5, 0, 0},
+ {0x894d, 0x14, 0, 0}, {0x894e, 0xa3, 0, 0}, {0x894f, 0xe0, 0, 0},
+ {0x8950, 0xf5, 0, 0}, {0x8951, 0x15, 0, 0}, {0x8952, 0x90, 0, 0},
+ {0x8953, 0x60, 0, 0}, {0x8954, 0x21, 0, 0}, {0x8955, 0xe0, 0, 0},
+ {0x8956, 0xf5, 0, 0}, {0x8957, 0x0c, 0, 0}, {0x8958, 0xa3, 0, 0},
+ {0x8959, 0xe0, 0, 0}, {0x895a, 0xf5, 0, 0}, {0x895b, 0x0d, 0, 0},
+ {0x895c, 0x90, 0, 0}, {0x895d, 0x60, 0, 0}, {0x895e, 0x29, 0, 0},
+ {0x895f, 0xe0, 0, 0}, {0x8960, 0xf5, 0, 0}, {0x8961, 0x0e, 0, 0},
+ {0x8962, 0xa3, 0, 0}, {0x8963, 0xe0, 0, 0}, {0x8964, 0xf5, 0, 0},
+ {0x8965, 0x0f, 0, 0}, {0x8966, 0x90, 0, 0}, {0x8967, 0x60, 0, 0},
+ {0x8968, 0x31, 0, 0}, {0x8969, 0xe0, 0, 0}, {0x896a, 0xf5, 0, 0},
+ {0x896b, 0x10, 0, 0}, {0x896c, 0xa3, 0, 0}, {0x896d, 0xe0, 0, 0},
+ {0x896e, 0xf5, 0, 0}, {0x896f, 0x11, 0, 0}, {0x8970, 0x90, 0, 0},
+ {0x8971, 0x60, 0, 0}, {0x8972, 0x39, 0, 0}, {0x8973, 0xe0, 0, 0},
+ {0x8974, 0xf5, 0, 0}, {0x8975, 0x12, 0, 0}, {0x8976, 0xa3, 0, 0},
+ {0x8977, 0xe0, 0, 0}, {0x8978, 0xf5, 0, 0}, {0x8979, 0x13, 0, 0},
+ {0x897a, 0x30, 0, 0}, {0x897b, 0x01, 0, 0}, {0x897c, 0x06, 0, 0},
+ {0x897d, 0x30, 0, 0}, {0x897e, 0x33, 0, 0}, {0x897f, 0x03, 0, 0},
+ {0x8980, 0xd3, 0, 0}, {0x8981, 0x80, 0, 0}, {0x8982, 0x01, 0, 0},
+ {0x8983, 0xc3, 0, 0}, {0x8984, 0x92, 0, 0}, {0x8985, 0x09, 0, 0},
+ {0x8986, 0x30, 0, 0}, {0x8987, 0x02, 0, 0}, {0x8988, 0x06, 0, 0},
+ {0x8989, 0x30, 0, 0}, {0x898a, 0x33, 0, 0}, {0x898b, 0x03, 0, 0},
+ {0x898c, 0xd3, 0, 0}, {0x898d, 0x80, 0, 0}, {0x898e, 0x01, 0, 0},
+ {0x898f, 0xc3, 0, 0}, {0x8990, 0x92, 0, 0}, {0x8991, 0x0a, 0, 0},
+ {0x8992, 0x30, 0, 0}, {0x8993, 0x33, 0, 0}, {0x8994, 0x0c, 0, 0},
+ {0x8995, 0x30, 0, 0}, {0x8996, 0x03, 0, 0}, {0x8997, 0x09, 0, 0},
+ {0x8998, 0x20, 0, 0}, {0x8999, 0x02, 0, 0}, {0x899a, 0x06, 0, 0},
+ {0x899b, 0x20, 0, 0}, {0x899c, 0x01, 0, 0}, {0x899d, 0x03, 0, 0},
+ {0x899e, 0xd3, 0, 0}, {0x899f, 0x80, 0, 0}, {0x89a0, 0x01, 0, 0},
+ {0x89a1, 0xc3, 0, 0}, {0x89a2, 0x92, 0, 0}, {0x89a3, 0x0b, 0, 0},
+ {0x89a4, 0x90, 0, 0}, {0x89a5, 0x30, 0, 0}, {0x89a6, 0x01, 0, 0},
+ {0x89a7, 0xe0, 0, 0}, {0x89a8, 0x44, 0, 0}, {0x89a9, 0x40, 0, 0},
+ {0x89aa, 0xf0, 0, 0}, {0x89ab, 0xe0, 0, 0}, {0x89ac, 0x54, 0, 0},
+ {0x89ad, 0xbf, 0, 0}, {0x89ae, 0xf0, 0, 0}, {0x89af, 0xe5, 0, 0},
+ {0x89b0, 0x32, 0, 0}, {0x89b1, 0x30, 0, 0}, {0x89b2, 0xe1, 0, 0},
+ {0x89b3, 0x14, 0, 0}, {0x89b4, 0x30, 0, 0}, {0x89b5, 0x34, 0, 0},
+ {0x89b6, 0x11, 0, 0}, {0x89b7, 0x90, 0, 0}, {0x89b8, 0x30, 0, 0},
+ {0x89b9, 0x22, 0, 0}, {0x89ba, 0xe0, 0, 0}, {0x89bb, 0xf5, 0, 0},
+ {0x89bc, 0x08, 0, 0}, {0x89bd, 0xe4, 0, 0}, {0x89be, 0xf0, 0, 0},
+ {0x89bf, 0x30, 0, 0}, {0x89c0, 0x00, 0, 0}, {0x89c1, 0x03, 0, 0},
+ {0x89c2, 0xd3, 0, 0}, {0x89c3, 0x80, 0, 0}, {0x89c4, 0x01, 0, 0},
+ {0x89c5, 0xc3, 0, 0}, {0x89c6, 0x92, 0, 0}, {0x89c7, 0x08, 0, 0},
+ {0x89c8, 0xe5, 0, 0}, {0x89c9, 0x32, 0, 0}, {0x89ca, 0x30, 0, 0},
+ {0x89cb, 0xe5, 0, 0}, {0x89cc, 0x12, 0, 0}, {0x89cd, 0x90, 0, 0},
+ {0x89ce, 0x56, 0, 0}, {0x89cf, 0xa1, 0, 0}, {0x89d0, 0xe0, 0, 0},
+ {0x89d1, 0xf5, 0, 0}, {0x89d2, 0x09, 0, 0}, {0x89d3, 0x30, 0, 0},
+ {0x89d4, 0x31, 0, 0}, {0x89d5, 0x09, 0, 0}, {0x89d6, 0x30, 0, 0},
+ {0x89d7, 0x05, 0, 0}, {0x89d8, 0x03, 0, 0}, {0x89d9, 0xd3, 0, 0},
+ {0x89da, 0x80, 0, 0}, {0x89db, 0x01, 0, 0}, {0x89dc, 0xc3, 0, 0},
+ {0x89dd, 0x92, 0, 0}, {0x89de, 0x0d, 0, 0}, {0x89df, 0x90, 0, 0},
+ {0x89e0, 0x3f, 0, 0}, {0x89e1, 0x0c, 0, 0}, {0x89e2, 0xe5, 0, 0},
+ {0x89e3, 0x32, 0, 0}, {0x89e4, 0xf0, 0, 0}, {0x89e5, 0xd0, 0, 0},
+ {0x89e6, 0xd0, 0, 0}, {0x89e7, 0xd0, 0, 0}, {0x89e8, 0x82, 0, 0},
+ {0x89e9, 0xd0, 0, 0}, {0x89ea, 0x83, 0, 0}, {0x89eb, 0xd0, 0, 0},
+ {0x89ec, 0xe0, 0, 0}, {0x89ed, 0x32, 0, 0}, {0x89ee, 0x85, 0, 0},
+ {0x89ef, 0x08, 0, 0}, {0x89f0, 0x41, 0, 0}, {0x89f1, 0x90, 0, 0},
+ {0x89f2, 0x30, 0, 0}, {0x89f3, 0x24, 0, 0}, {0x89f4, 0xe0, 0, 0},
+ {0x89f5, 0xf5, 0, 0}, {0x89f6, 0x3d, 0, 0}, {0x89f7, 0xa3, 0, 0},
+ {0x89f8, 0xe0, 0, 0}, {0x89f9, 0xf5, 0, 0}, {0x89fa, 0x3e, 0, 0},
+ {0x89fb, 0xa3, 0, 0}, {0x89fc, 0xe0, 0, 0}, {0x89fd, 0xf5, 0, 0},
+ {0x89fe, 0x3f, 0, 0}, {0x89ff, 0xa3, 0, 0}, {0x8a00, 0xe0, 0, 0},
+ {0x8a01, 0xf5, 0, 0}, {0x8a02, 0x40, 0, 0}, {0x8a03, 0xa3, 0, 0},
+ {0x8a04, 0xe0, 0, 0}, {0x8a05, 0xf5, 0, 0}, {0x8a06, 0x3c, 0, 0},
+ {0x8a07, 0xd2, 0, 0}, {0x8a08, 0x34, 0, 0}, {0x8a09, 0xe5, 0, 0},
+ {0x8a0a, 0x41, 0, 0}, {0x8a0b, 0x12, 0, 0}, {0x8a0c, 0x07, 0, 0},
+ {0x8a0d, 0x12, 0, 0}, {0x8a0e, 0x0a, 0, 0}, {0x8a0f, 0x33, 0, 0},
+ {0x8a10, 0x03, 0, 0}, {0x8a11, 0x0a, 0, 0}, {0x8a12, 0x40, 0, 0},
+ {0x8a13, 0x04, 0, 0}, {0x8a14, 0x0a, 0, 0}, {0x8a15, 0x51, 0, 0},
+ {0x8a16, 0x05, 0, 0}, {0x8a17, 0x0a, 0, 0}, {0x8a18, 0x54, 0, 0},
+ {0x8a19, 0x06, 0, 0}, {0x8a1a, 0x0a, 0, 0}, {0x8a1b, 0x5d, 0, 0},
+ {0x8a1c, 0x08, 0, 0}, {0x8a1d, 0x0a, 0, 0}, {0x8a1e, 0x6d, 0, 0},
+ {0x8a1f, 0x12, 0, 0}, {0x8a20, 0x0a, 0, 0}, {0x8a21, 0x72, 0, 0},
+ {0x8a22, 0x1a, 0, 0}, {0x8a23, 0x0a, 0, 0}, {0x8a24, 0x7d, 0, 0},
+ {0x8a25, 0x1b, 0, 0}, {0x8a26, 0x0a, 0, 0}, {0x8a27, 0x6d, 0, 0},
+ {0x8a28, 0x80, 0, 0}, {0x8a29, 0x0a, 0, 0}, {0x8a2a, 0x6d, 0, 0},
+ {0x8a2b, 0x81, 0, 0}, {0x8a2c, 0x0a, 0, 0}, {0x8a2d, 0x85, 0, 0},
+ {0x8a2e, 0xec, 0, 0}, {0x8a2f, 0x00, 0, 0}, {0x8a30, 0x00, 0, 0},
+ {0x8a31, 0x0a, 0, 0}, {0x8a32, 0xa3, 0, 0}, {0x8a33, 0x12, 0, 0},
+ {0x8a34, 0x0f, 0, 0}, {0x8a35, 0xd2, 0, 0}, {0x8a36, 0xd2, 0, 0},
+ {0x8a37, 0x36, 0, 0}, {0x8a38, 0xd2, 0, 0}, {0x8a39, 0x01, 0, 0},
+ {0x8a3a, 0xc2, 0, 0}, {0x8a3b, 0x02, 0, 0}, {0x8a3c, 0x12, 0, 0},
+ {0x8a3d, 0x0f, 0, 0}, {0x8a3e, 0xd7, 0, 0}, {0x8a3f, 0x22, 0, 0},
+ {0x8a40, 0xd2, 0, 0}, {0x8a41, 0x33, 0, 0}, {0x8a42, 0xd2, 0, 0},
+ {0x8a43, 0x36, 0, 0}, {0x8a44, 0xe5, 0, 0}, {0x8a45, 0x3d, 0, 0},
+ {0x8a46, 0xd3, 0, 0}, {0x8a47, 0x94, 0, 0}, {0x8a48, 0x00, 0, 0},
+ {0x8a49, 0x40, 0, 0}, {0x8a4a, 0x03, 0, 0}, {0x8a4b, 0x12, 0, 0},
+ {0x8a4c, 0x0f, 0, 0}, {0x8a4d, 0xd2, 0, 0}, {0x8a4e, 0xd2, 0, 0},
+ {0x8a4f, 0x03, 0, 0}, {0x8a50, 0x22, 0, 0}, {0x8a51, 0xd2, 0, 0},
+ {0x8a52, 0x03, 0, 0}, {0x8a53, 0x22, 0, 0}, {0x8a54, 0xc2, 0, 0},
+ {0x8a55, 0x03, 0, 0}, {0x8a56, 0x20, 0, 0}, {0x8a57, 0x01, 0, 0},
+ {0x8a58, 0x4a, 0, 0}, {0x8a59, 0x30, 0, 0}, {0x8a5a, 0x02, 0, 0},
+ {0x8a5b, 0x2c, 0, 0}, {0x8a5c, 0x22, 0, 0}, {0x8a5d, 0xc2, 0, 0},
+ {0x8a5e, 0x01, 0, 0}, {0x8a5f, 0xc2, 0, 0}, {0x8a60, 0x02, 0, 0},
+ {0x8a61, 0xc2, 0, 0}, {0x8a62, 0x03, 0, 0}, {0x8a63, 0x12, 0, 0},
+ {0x8a64, 0x0d, 0, 0}, {0x8a65, 0x39, 0, 0}, {0x8a66, 0x75, 0, 0},
+ {0x8a67, 0x1e, 0, 0}, {0x8a68, 0x70, 0, 0}, {0x8a69, 0xd2, 0, 0},
+ {0x8a6a, 0x35, 0, 0}, {0x8a6b, 0x80, 0, 0}, {0x8a6c, 0x1b, 0, 0},
+ {0x8a6d, 0x12, 0, 0}, {0x8a6e, 0x0b, 0, 0}, {0x8a6f, 0xcc, 0, 0},
+ {0x8a70, 0x80, 0, 0}, {0x8a71, 0x16, 0, 0}, {0x8a72, 0x85, 0, 0},
+ {0x8a73, 0x40, 0, 0}, {0x8a74, 0x4a, 0, 0}, {0x8a75, 0x85, 0, 0},
+ {0x8a76, 0x3c, 0, 0}, {0x8a77, 0x4b, 0, 0}, {0x8a78, 0x12, 0, 0},
+ {0x8a79, 0x0b, 0, 0}, {0x8a7a, 0x39, 0, 0}, {0x8a7b, 0x80, 0, 0},
+ {0x8a7c, 0x0b, 0, 0}, {0x8a7d, 0x85, 0, 0}, {0x8a7e, 0x4a, 0, 0},
+ {0x8a7f, 0x40, 0, 0}, {0x8a80, 0x85, 0, 0}, {0x8a81, 0x4b, 0, 0},
+ {0x8a82, 0x3c, 0, 0}, {0x8a83, 0x80, 0, 0}, {0x8a84, 0x03, 0, 0},
+ {0x8a85, 0x12, 0, 0}, {0x8a86, 0x0f, 0, 0}, {0x8a87, 0x00, 0, 0},
+ {0x8a88, 0x90, 0, 0}, {0x8a89, 0x30, 0, 0}, {0x8a8a, 0x24, 0, 0},
+ {0x8a8b, 0xe5, 0, 0}, {0x8a8c, 0x3d, 0, 0}, {0x8a8d, 0xf0, 0, 0},
+ {0x8a8e, 0xa3, 0, 0}, {0x8a8f, 0xe5, 0, 0}, {0x8a90, 0x3e, 0, 0},
+ {0x8a91, 0xf0, 0, 0}, {0x8a92, 0xa3, 0, 0}, {0x8a93, 0xe5, 0, 0},
+ {0x8a94, 0x3f, 0, 0}, {0x8a95, 0xf0, 0, 0}, {0x8a96, 0xa3, 0, 0},
+ {0x8a97, 0xe5, 0, 0}, {0x8a98, 0x40, 0, 0}, {0x8a99, 0xf0, 0, 0},
+ {0x8a9a, 0xa3, 0, 0}, {0x8a9b, 0xe5, 0, 0}, {0x8a9c, 0x3c, 0, 0},
+ {0x8a9d, 0xf0, 0, 0}, {0x8a9e, 0x90, 0, 0}, {0x8a9f, 0x30, 0, 0},
+ {0x8aa0, 0x23, 0, 0}, {0x8aa1, 0xe4, 0, 0}, {0x8aa2, 0xf0, 0, 0},
+ {0x8aa3, 0x22, 0, 0}, {0x8aa4, 0xe0, 0, 0}, {0x8aa5, 0xa3, 0, 0},
+ {0x8aa6, 0xe0, 0, 0}, {0x8aa7, 0x75, 0, 0}, {0x8aa8, 0xf0, 0, 0},
+ {0x8aa9, 0x02, 0, 0}, {0x8aaa, 0xa4, 0, 0}, {0x8aab, 0xff, 0, 0},
+ {0x8aac, 0xae, 0, 0}, {0x8aad, 0xf0, 0, 0}, {0x8aae, 0xc3, 0, 0},
+ {0x8aaf, 0x08, 0, 0}, {0x8ab0, 0xe6, 0, 0}, {0x8ab1, 0x9f, 0, 0},
+ {0x8ab2, 0xf6, 0, 0}, {0x8ab3, 0x18, 0, 0}, {0x8ab4, 0xe6, 0, 0},
+ {0x8ab5, 0x9e, 0, 0}, {0x8ab6, 0xf6, 0, 0}, {0x8ab7, 0x22, 0, 0},
+ {0x8ab8, 0xff, 0, 0}, {0x8ab9, 0xe5, 0, 0}, {0x8aba, 0xf0, 0, 0},
+ {0x8abb, 0x34, 0, 0}, {0x8abc, 0x60, 0, 0}, {0x8abd, 0x8f, 0, 0},
+ {0x8abe, 0x82, 0, 0}, {0x8abf, 0xf5, 0, 0}, {0x8ac0, 0x83, 0, 0},
+ {0x8ac1, 0xec, 0, 0}, {0x8ac2, 0xf0, 0, 0}, {0x8ac3, 0x22, 0, 0},
+ {0x8ac4, 0x78, 0, 0}, {0x8ac5, 0x52, 0, 0}, {0x8ac6, 0x7e, 0, 0},
+ {0x8ac7, 0x00, 0, 0}, {0x8ac8, 0xe6, 0, 0}, {0x8ac9, 0xfc, 0, 0},
+ {0x8aca, 0x08, 0, 0}, {0x8acb, 0xe6, 0, 0}, {0x8acc, 0xfd, 0, 0},
+ {0x8acd, 0x02, 0, 0}, {0x8ace, 0x05, 0, 0}, {0x8acf, 0x22, 0, 0},
+ {0x8ad0, 0xe4, 0, 0}, {0x8ad1, 0xfc, 0, 0}, {0x8ad2, 0xfd, 0, 0},
+ {0x8ad3, 0x12, 0, 0}, {0x8ad4, 0x06, 0, 0}, {0x8ad5, 0xfa, 0, 0},
+ {0x8ad6, 0x78, 0, 0}, {0x8ad7, 0x5c, 0, 0}, {0x8ad8, 0xe6, 0, 0},
+ {0x8ad9, 0xc3, 0, 0}, {0x8ada, 0x13, 0, 0}, {0x8adb, 0xfe, 0, 0},
+ {0x8adc, 0x08, 0, 0}, {0x8add, 0xe6, 0, 0}, {0x8ade, 0x13, 0, 0},
+ {0x8adf, 0x22, 0, 0}, {0x8ae0, 0x78, 0, 0}, {0x8ae1, 0x52, 0, 0},
+ {0x8ae2, 0xe6, 0, 0}, {0x8ae3, 0xfe, 0, 0}, {0x8ae4, 0x08, 0, 0},
+ {0x8ae5, 0xe6, 0, 0}, {0x8ae6, 0xff, 0, 0}, {0x8ae7, 0xe4, 0, 0},
+ {0x8ae8, 0xfc, 0, 0}, {0x8ae9, 0xfd, 0, 0}, {0x8aea, 0x22, 0, 0},
+ {0x8aeb, 0xe7, 0, 0}, {0x8aec, 0xc4, 0, 0}, {0x8aed, 0xf8, 0, 0},
+ {0x8aee, 0x54, 0, 0}, {0x8aef, 0xf0, 0, 0}, {0x8af0, 0xc8, 0, 0},
+ {0x8af1, 0x68, 0, 0}, {0x8af2, 0xf7, 0, 0}, {0x8af3, 0x09, 0, 0},
+ {0x8af4, 0xe7, 0, 0}, {0x8af5, 0xc4, 0, 0}, {0x8af6, 0x54, 0, 0},
+ {0x8af7, 0x0f, 0, 0}, {0x8af8, 0x48, 0, 0}, {0x8af9, 0xf7, 0, 0},
+ {0x8afa, 0x22, 0, 0}, {0x8afb, 0xe6, 0, 0}, {0x8afc, 0xfc, 0, 0},
+ {0x8afd, 0xed, 0, 0}, {0x8afe, 0x75, 0, 0}, {0x8aff, 0xf0, 0, 0},
+ {0x8b00, 0x04, 0, 0}, {0x8b01, 0xa4, 0, 0}, {0x8b02, 0x22, 0, 0},
+ {0x8b03, 0x12, 0, 0}, {0x8b04, 0x06, 0, 0}, {0x8b05, 0xdd, 0, 0},
+ {0x8b06, 0x8f, 0, 0}, {0x8b07, 0x48, 0, 0}, {0x8b08, 0x8e, 0, 0},
+ {0x8b09, 0x47, 0, 0}, {0x8b0a, 0x8d, 0, 0}, {0x8b0b, 0x46, 0, 0},
+ {0x8b0c, 0x8c, 0, 0}, {0x8b0d, 0x45, 0, 0}, {0x8b0e, 0x22, 0, 0},
+ {0x8b0f, 0xe0, 0, 0}, {0x8b10, 0xfe, 0, 0}, {0x8b11, 0xa3, 0, 0},
+ {0x8b12, 0xe0, 0, 0}, {0x8b13, 0xfd, 0, 0}, {0x8b14, 0xee, 0, 0},
+ {0x8b15, 0xf6, 0, 0}, {0x8b16, 0xed, 0, 0}, {0x8b17, 0x08, 0, 0},
+ {0x8b18, 0xf6, 0, 0}, {0x8b19, 0x22, 0, 0}, {0x8b1a, 0x13, 0, 0},
+ {0x8b1b, 0xff, 0, 0}, {0x8b1c, 0xc3, 0, 0}, {0x8b1d, 0xe6, 0, 0},
+ {0x8b1e, 0x9f, 0, 0}, {0x8b1f, 0xff, 0, 0}, {0x8b20, 0x18, 0, 0},
+ {0x8b21, 0xe6, 0, 0}, {0x8b22, 0x9e, 0, 0}, {0x8b23, 0xfe, 0, 0},
+ {0x8b24, 0x22, 0, 0}, {0x8b25, 0xfb, 0, 0}, {0x8b26, 0xd3, 0, 0},
+ {0x8b27, 0xed, 0, 0}, {0x8b28, 0x9b, 0, 0}, {0x8b29, 0x74, 0, 0},
+ {0x8b2a, 0x80, 0, 0}, {0x8b2b, 0xf8, 0, 0}, {0x8b2c, 0x6c, 0, 0},
+ {0x8b2d, 0x98, 0, 0}, {0x8b2e, 0x22, 0, 0}, {0x8b2f, 0xe6, 0, 0},
+ {0x8b30, 0xc3, 0, 0}, {0x8b31, 0x13, 0, 0}, {0x8b32, 0xf7, 0, 0},
+ {0x8b33, 0x08, 0, 0}, {0x8b34, 0xe6, 0, 0}, {0x8b35, 0x13, 0, 0},
+ {0x8b36, 0x09, 0, 0}, {0x8b37, 0xf7, 0, 0}, {0x8b38, 0x22, 0, 0},
+ {0x8b39, 0x90, 0, 0}, {0x8b3a, 0x0e, 0, 0}, {0x8b3b, 0x7e, 0, 0},
+ {0x8b3c, 0xe4, 0, 0}, {0x8b3d, 0x93, 0, 0}, {0x8b3e, 0xfe, 0, 0},
+ {0x8b3f, 0x74, 0, 0}, {0x8b40, 0x01, 0, 0}, {0x8b41, 0x93, 0, 0},
+ {0x8b42, 0xff, 0, 0}, {0x8b43, 0xc3, 0, 0}, {0x8b44, 0x90, 0, 0},
+ {0x8b45, 0x0e, 0, 0}, {0x8b46, 0x7c, 0, 0}, {0x8b47, 0x74, 0, 0},
+ {0x8b48, 0x01, 0, 0}, {0x8b49, 0x93, 0, 0}, {0x8b4a, 0x9f, 0, 0},
+ {0x8b4b, 0xff, 0, 0}, {0x8b4c, 0xe4, 0, 0}, {0x8b4d, 0x93, 0, 0},
+ {0x8b4e, 0x9e, 0, 0}, {0x8b4f, 0xfe, 0, 0}, {0x8b50, 0xe4, 0, 0},
+ {0x8b51, 0x8f, 0, 0}, {0x8b52, 0x3b, 0, 0}, {0x8b53, 0x8e, 0, 0},
+ {0x8b54, 0x3a, 0, 0}, {0x8b55, 0xf5, 0, 0}, {0x8b56, 0x39, 0, 0},
+ {0x8b57, 0xf5, 0, 0}, {0x8b58, 0x38, 0, 0}, {0x8b59, 0xab, 0, 0},
+ {0x8b5a, 0x3b, 0, 0}, {0x8b5b, 0xaa, 0, 0}, {0x8b5c, 0x3a, 0, 0},
+ {0x8b5d, 0xa9, 0, 0}, {0x8b5e, 0x39, 0, 0}, {0x8b5f, 0xa8, 0, 0},
+ {0x8b60, 0x38, 0, 0}, {0x8b61, 0xaf, 0, 0}, {0x8b62, 0x4b, 0, 0},
+ {0x8b63, 0xfc, 0, 0}, {0x8b64, 0xfd, 0, 0}, {0x8b65, 0xfe, 0, 0},
+ {0x8b66, 0x12, 0, 0}, {0x8b67, 0x05, 0, 0}, {0x8b68, 0x89, 0, 0},
+ {0x8b69, 0x12, 0, 0}, {0x8b6a, 0x0f, 0, 0}, {0x8b6b, 0x91, 0, 0},
+ {0x8b6c, 0xe4, 0, 0}, {0x8b6d, 0x7b, 0, 0}, {0x8b6e, 0xff, 0, 0},
+ {0x8b6f, 0xfa, 0, 0}, {0x8b70, 0xf9, 0, 0}, {0x8b71, 0xf8, 0, 0},
+ {0x8b72, 0x12, 0, 0}, {0x8b73, 0x06, 0, 0}, {0x8b74, 0x14, 0, 0},
+ {0x8b75, 0x12, 0, 0}, {0x8b76, 0x0f, 0, 0}, {0x8b77, 0x91, 0, 0},
+ {0x8b78, 0x90, 0, 0}, {0x8b79, 0x0e, 0, 0}, {0x8b7a, 0x69, 0, 0},
+ {0x8b7b, 0xe4, 0, 0}, {0x8b7c, 0x12, 0, 0}, {0x8b7d, 0x0f, 0, 0},
+ {0x8b7e, 0xa6, 0, 0}, {0x8b7f, 0x12, 0, 0}, {0x8b80, 0x0f, 0, 0},
+ {0x8b81, 0x91, 0, 0}, {0x8b82, 0xe4, 0, 0}, {0x8b83, 0x85, 0, 0},
+ {0x8b84, 0x4a, 0, 0}, {0x8b85, 0x37, 0, 0}, {0x8b86, 0xf5, 0, 0},
+ {0x8b87, 0x36, 0, 0}, {0x8b88, 0xf5, 0, 0}, {0x8b89, 0x35, 0, 0},
+ {0x8b8a, 0xf5, 0, 0}, {0x8b8b, 0x34, 0, 0}, {0x8b8c, 0xaf, 0, 0},
+ {0x8b8d, 0x37, 0, 0}, {0x8b8e, 0xae, 0, 0}, {0x8b8f, 0x36, 0, 0},
+ {0x8b90, 0xad, 0, 0}, {0x8b91, 0x35, 0, 0}, {0x8b92, 0xac, 0, 0},
+ {0x8b93, 0x34, 0, 0}, {0x8b94, 0xa3, 0, 0}, {0x8b95, 0x12, 0, 0},
+ {0x8b96, 0x0f, 0, 0}, {0x8b97, 0xa6, 0, 0}, {0x8b98, 0x8f, 0, 0},
+ {0x8b99, 0x37, 0, 0}, {0x8b9a, 0x8e, 0, 0}, {0x8b9b, 0x36, 0, 0},
+ {0x8b9c, 0x8d, 0, 0}, {0x8b9d, 0x35, 0, 0}, {0x8b9e, 0x8c, 0, 0},
+ {0x8b9f, 0x34, 0, 0}, {0x8ba0, 0xe5, 0, 0}, {0x8ba1, 0x3b, 0, 0},
+ {0x8ba2, 0x45, 0, 0}, {0x8ba3, 0x37, 0, 0}, {0x8ba4, 0xf5, 0, 0},
+ {0x8ba5, 0x3b, 0, 0}, {0x8ba6, 0xe5, 0, 0}, {0x8ba7, 0x3a, 0, 0},
+ {0x8ba8, 0x45, 0, 0}, {0x8ba9, 0x36, 0, 0}, {0x8baa, 0xf5, 0, 0},
+ {0x8bab, 0x3a, 0, 0}, {0x8bac, 0xe5, 0, 0}, {0x8bad, 0x39, 0, 0},
+ {0x8bae, 0x45, 0, 0}, {0x8baf, 0x35, 0, 0}, {0x8bb0, 0xf5, 0, 0},
+ {0x8bb1, 0x39, 0, 0}, {0x8bb2, 0xe5, 0, 0}, {0x8bb3, 0x38, 0, 0},
+ {0x8bb4, 0x45, 0, 0}, {0x8bb5, 0x34, 0, 0}, {0x8bb6, 0xf5, 0, 0},
+ {0x8bb7, 0x38, 0, 0}, {0x8bb8, 0xe4, 0, 0}, {0x8bb9, 0xf5, 0, 0},
+ {0x8bba, 0x22, 0, 0}, {0x8bbb, 0xf5, 0, 0}, {0x8bbc, 0x23, 0, 0},
+ {0x8bbd, 0x85, 0, 0}, {0x8bbe, 0x3b, 0, 0}, {0x8bbf, 0x31, 0, 0},
+ {0x8bc0, 0x85, 0, 0}, {0x8bc1, 0x3a, 0, 0}, {0x8bc2, 0x30, 0, 0},
+ {0x8bc3, 0x85, 0, 0}, {0x8bc4, 0x39, 0, 0}, {0x8bc5, 0x2f, 0, 0},
+ {0x8bc6, 0x85, 0, 0}, {0x8bc7, 0x38, 0, 0}, {0x8bc8, 0x2e, 0, 0},
+ {0x8bc9, 0x02, 0, 0}, {0x8bca, 0x0f, 0, 0}, {0x8bcb, 0x63, 0, 0},
+ {0x8bcc, 0xe5, 0, 0}, {0x8bcd, 0x3c, 0, 0}, {0x8bce, 0xd3, 0, 0},
+ {0x8bcf, 0x94, 0, 0}, {0x8bd0, 0x01, 0, 0}, {0x8bd1, 0x40, 0, 0},
+ {0x8bd2, 0x0b, 0, 0}, {0x8bd3, 0x90, 0, 0}, {0x8bd4, 0x0e, 0, 0},
+ {0x8bd5, 0x88, 0, 0}, {0x8bd6, 0x12, 0, 0}, {0x8bd7, 0x0b, 0, 0},
+ {0x8bd8, 0x03, 0, 0}, {0x8bd9, 0x90, 0, 0}, {0x8bda, 0x0e, 0, 0},
+ {0x8bdb, 0x86, 0, 0}, {0x8bdc, 0x80, 0, 0}, {0x8bdd, 0x09, 0, 0},
+ {0x8bde, 0x90, 0, 0}, {0x8bdf, 0x0e, 0, 0}, {0x8be0, 0x82, 0, 0},
+ {0x8be1, 0x12, 0, 0}, {0x8be2, 0x0b, 0, 0}, {0x8be3, 0x03, 0, 0},
+ {0x8be4, 0x90, 0, 0}, {0x8be5, 0x0e, 0, 0}, {0x8be6, 0x80, 0, 0},
+ {0x8be7, 0xe4, 0, 0}, {0x8be8, 0x93, 0, 0}, {0x8be9, 0xf5, 0, 0},
+ {0x8bea, 0x44, 0, 0}, {0x8beb, 0xa3, 0, 0}, {0x8bec, 0xe4, 0, 0},
+ {0x8bed, 0x93, 0, 0}, {0x8bee, 0xf5, 0, 0}, {0x8bef, 0x43, 0, 0},
+ {0x8bf0, 0xe5, 0, 0}, {0x8bf1, 0x3c, 0, 0}, {0x8bf2, 0xd3, 0, 0},
+ {0x8bf3, 0x94, 0, 0}, {0x8bf4, 0x00, 0, 0}, {0x8bf5, 0x40, 0, 0},
+ {0x8bf6, 0x06, 0, 0}, {0x8bf7, 0x85, 0, 0}, {0x8bf8, 0x3d, 0, 0},
+ {0x8bf9, 0x45, 0, 0}, {0x8bfa, 0x85, 0, 0}, {0x8bfb, 0x3e, 0, 0},
+ {0x8bfc, 0x46, 0, 0}, {0x8bfd, 0xe5, 0, 0}, {0x8bfe, 0x47, 0, 0},
+ {0x8bff, 0xc3, 0, 0}, {0x8c00, 0x13, 0, 0}, {0x8c01, 0xff, 0, 0},
+ {0x8c02, 0xe5, 0, 0}, {0x8c03, 0x45, 0, 0}, {0x8c04, 0xc3, 0, 0},
+ {0x8c05, 0x9f, 0, 0}, {0x8c06, 0x50, 0, 0}, {0x8c07, 0x02, 0, 0},
+ {0x8c08, 0x8f, 0, 0}, {0x8c09, 0x45, 0, 0}, {0x8c0a, 0xe5, 0, 0},
+ {0x8c0b, 0x48, 0, 0}, {0x8c0c, 0xc3, 0, 0}, {0x8c0d, 0x13, 0, 0},
+ {0x8c0e, 0xff, 0, 0}, {0x8c0f, 0xe5, 0, 0}, {0x8c10, 0x46, 0, 0},
+ {0x8c11, 0xc3, 0, 0}, {0x8c12, 0x9f, 0, 0}, {0x8c13, 0x50, 0, 0},
+ {0x8c14, 0x02, 0, 0}, {0x8c15, 0x8f, 0, 0}, {0x8c16, 0x46, 0, 0},
+ {0x8c17, 0xe5, 0, 0}, {0x8c18, 0x47, 0, 0}, {0x8c19, 0xc3, 0, 0},
+ {0x8c1a, 0x13, 0, 0}, {0x8c1b, 0xff, 0, 0}, {0x8c1c, 0xfd, 0, 0},
+ {0x8c1d, 0xe5, 0, 0}, {0x8c1e, 0x45, 0, 0}, {0x8c1f, 0x2d, 0, 0},
+ {0x8c20, 0xfd, 0, 0}, {0x8c21, 0xe4, 0, 0}, {0x8c22, 0x33, 0, 0},
+ {0x8c23, 0xfc, 0, 0}, {0x8c24, 0xe5, 0, 0}, {0x8c25, 0x44, 0, 0},
+ {0x8c26, 0x12, 0, 0}, {0x8c27, 0x0b, 0, 0}, {0x8c28, 0x25, 0, 0},
+ {0x8c29, 0x40, 0, 0}, {0x8c2a, 0x05, 0, 0}, {0x8c2b, 0xe5, 0, 0},
+ {0x8c2c, 0x44, 0, 0}, {0x8c2d, 0x9f, 0, 0}, {0x8c2e, 0xf5, 0, 0},
+ {0x8c2f, 0x45, 0, 0}, {0x8c30, 0xe5, 0, 0}, {0x8c31, 0x48, 0, 0},
+ {0x8c32, 0xc3, 0, 0}, {0x8c33, 0x13, 0, 0}, {0x8c34, 0xff, 0, 0},
+ {0x8c35, 0xfd, 0, 0}, {0x8c36, 0xe5, 0, 0}, {0x8c37, 0x46, 0, 0},
+ {0x8c38, 0x2d, 0, 0}, {0x8c39, 0xfd, 0, 0}, {0x8c3a, 0xe4, 0, 0},
+ {0x8c3b, 0x33, 0, 0}, {0x8c3c, 0xfc, 0, 0}, {0x8c3d, 0xe5, 0, 0},
+ {0x8c3e, 0x43, 0, 0}, {0x8c3f, 0x12, 0, 0}, {0x8c40, 0x0b, 0, 0},
+ {0x8c41, 0x25, 0, 0}, {0x8c42, 0x40, 0, 0}, {0x8c43, 0x05, 0, 0},
+ {0x8c44, 0xe5, 0, 0}, {0x8c45, 0x43, 0, 0}, {0x8c46, 0x9f, 0, 0},
+ {0x8c47, 0xf5, 0, 0}, {0x8c48, 0x46, 0, 0}, {0x8c49, 0x02, 0, 0},
+ {0x8c4a, 0x07, 0, 0}, {0x8c4b, 0x38, 0, 0}, {0x8c4c, 0xad, 0, 0},
+ {0x8c4d, 0x39, 0, 0}, {0x8c4e, 0xac, 0, 0}, {0x8c4f, 0x38, 0, 0},
+ {0x8c50, 0xfa, 0, 0}, {0x8c51, 0xf9, 0, 0}, {0x8c52, 0xf8, 0, 0},
+ {0x8c53, 0x12, 0, 0}, {0x8c54, 0x05, 0, 0}, {0x8c55, 0x89, 0, 0},
+ {0x8c56, 0x8f, 0, 0}, {0x8c57, 0x3b, 0, 0}, {0x8c58, 0x8e, 0, 0},
+ {0x8c59, 0x3a, 0, 0}, {0x8c5a, 0x8d, 0, 0}, {0x8c5b, 0x39, 0, 0},
+ {0x8c5c, 0x8c, 0, 0}, {0x8c5d, 0x38, 0, 0}, {0x8c5e, 0xab, 0, 0},
+ {0x8c5f, 0x37, 0, 0}, {0x8c60, 0xaa, 0, 0}, {0x8c61, 0x36, 0, 0},
+ {0x8c62, 0xa9, 0, 0}, {0x8c63, 0x35, 0, 0}, {0x8c64, 0xa8, 0, 0},
+ {0x8c65, 0x34, 0, 0}, {0x8c66, 0x22, 0, 0}, {0x8c67, 0x90, 0, 0},
+ {0x8c68, 0x0e, 0, 0}, {0x8c69, 0x8c, 0, 0}, {0x8c6a, 0xe4, 0, 0},
+ {0x8c6b, 0x93, 0, 0}, {0x8c6c, 0x25, 0, 0}, {0x8c6d, 0xe0, 0, 0},
+ {0x8c6e, 0x24, 0, 0}, {0x8c6f, 0x0a, 0, 0}, {0x8c70, 0xf8, 0, 0},
+ {0x8c71, 0xe6, 0, 0}, {0x8c72, 0xfe, 0, 0}, {0x8c73, 0x08, 0, 0},
+ {0x8c74, 0xe6, 0, 0}, {0x8c75, 0xff, 0, 0}, {0x8c76, 0x22, 0, 0},
+ {0x8c77, 0x93, 0, 0}, {0x8c78, 0xff, 0, 0}, {0x8c79, 0xe4, 0, 0},
+ {0x8c7a, 0xfc, 0, 0}, {0x8c7b, 0xfd, 0, 0}, {0x8c7c, 0xfe, 0, 0},
+ {0x8c7d, 0x12, 0, 0}, {0x8c7e, 0x05, 0, 0}, {0x8c7f, 0x89, 0, 0},
+ {0x8c80, 0x8f, 0, 0}, {0x8c81, 0x37, 0, 0}, {0x8c82, 0x8e, 0, 0},
+ {0x8c83, 0x36, 0, 0}, {0x8c84, 0x8d, 0, 0}, {0x8c85, 0x35, 0, 0},
+ {0x8c86, 0x8c, 0, 0}, {0x8c87, 0x34, 0, 0}, {0x8c88, 0x22, 0, 0},
+ {0x8c89, 0xe6, 0, 0}, {0x8c8a, 0xfe, 0, 0}, {0x8c8b, 0x08, 0, 0},
+ {0x8c8c, 0xe6, 0, 0}, {0x8c8d, 0xff, 0, 0}, {0x8c8e, 0xe4, 0, 0},
+ {0x8c8f, 0x8f, 0, 0}, {0x8c90, 0x37, 0, 0}, {0x8c91, 0x8e, 0, 0},
+ {0x8c92, 0x36, 0, 0}, {0x8c93, 0xf5, 0, 0}, {0x8c94, 0x35, 0, 0},
+ {0x8c95, 0xf5, 0, 0}, {0x8c96, 0x34, 0, 0}, {0x8c97, 0x22, 0, 0},
+ {0x8c98, 0xef, 0, 0}, {0x8c99, 0x25, 0, 0}, {0x8c9a, 0xe0, 0, 0},
+ {0x8c9b, 0x24, 0, 0}, {0x8c9c, 0x4e, 0, 0}, {0x8c9d, 0xf8, 0, 0},
+ {0x8c9e, 0xe6, 0, 0}, {0x8c9f, 0xfc, 0, 0}, {0x8ca0, 0x08, 0, 0},
+ {0x8ca1, 0xe6, 0, 0}, {0x8ca2, 0xfd, 0, 0}, {0x8ca3, 0x22, 0, 0},
+ {0x8ca4, 0xd3, 0, 0}, {0x8ca5, 0x79, 0, 0}, {0x8ca6, 0x81, 0, 0},
+ {0x8ca7, 0xe7, 0, 0}, {0x8ca8, 0x78, 0, 0}, {0x8ca9, 0x7f, 0, 0},
+ {0x8caa, 0x96, 0, 0}, {0x8cab, 0x19, 0, 0}, {0x8cac, 0xe7, 0, 0},
+ {0x8cad, 0x18, 0, 0}, {0x8cae, 0x96, 0, 0}, {0x8caf, 0x22, 0, 0},
+ {0x8cb0, 0x78, 0, 0}, {0x8cb1, 0x89, 0, 0}, {0x8cb2, 0xef, 0, 0},
+ {0x8cb3, 0x26, 0, 0}, {0x8cb4, 0xf6, 0, 0}, {0x8cb5, 0x18, 0, 0},
+ {0x8cb6, 0xe4, 0, 0}, {0x8cb7, 0x36, 0, 0}, {0x8cb8, 0xf6, 0, 0},
+ {0x8cb9, 0x22, 0, 0}, {0x8cba, 0xe4, 0, 0}, {0x8cbb, 0x8f, 0, 0},
+ {0x8cbc, 0x3b, 0, 0}, {0x8cbd, 0x8e, 0, 0}, {0x8cbe, 0x3a, 0, 0},
+ {0x8cbf, 0xf5, 0, 0}, {0x8cc0, 0x39, 0, 0}, {0x8cc1, 0xf5, 0, 0},
+ {0x8cc2, 0x38, 0, 0}, {0x8cc3, 0x22, 0, 0}, {0x8cc4, 0x75, 0, 0},
+ {0x8cc5, 0x89, 0, 0}, {0x8cc6, 0x03, 0, 0}, {0x8cc7, 0x75, 0, 0},
+ {0x8cc8, 0xa8, 0, 0}, {0x8cc9, 0x01, 0, 0}, {0x8cca, 0x75, 0, 0},
+ {0x8ccb, 0xb8, 0, 0}, {0x8ccc, 0x04, 0, 0}, {0x8ccd, 0x75, 0, 0},
+ {0x8cce, 0x34, 0, 0}, {0x8ccf, 0xff, 0, 0}, {0x8cd0, 0x75, 0, 0},
+ {0x8cd1, 0x35, 0, 0}, {0x8cd2, 0x0e, 0, 0}, {0x8cd3, 0x75, 0, 0},
+ {0x8cd4, 0x36, 0, 0}, {0x8cd5, 0x15, 0, 0}, {0x8cd6, 0x75, 0, 0},
+ {0x8cd7, 0x37, 0, 0}, {0x8cd8, 0x0d, 0, 0}, {0x8cd9, 0x12, 0, 0},
+ {0x8cda, 0x0d, 0, 0}, {0x8cdb, 0xaa, 0, 0}, {0x8cdc, 0x12, 0, 0},
+ {0x8cdd, 0x00, 0, 0}, {0x8cde, 0x09, 0, 0}, {0x8cdf, 0x12, 0, 0},
+ {0x8ce0, 0x0b, 0, 0}, {0x8ce1, 0xcc, 0, 0}, {0x8ce2, 0x12, 0, 0},
+ {0x8ce3, 0x00, 0, 0}, {0x8ce4, 0x06, 0, 0}, {0x8ce5, 0xd2, 0, 0},
+ {0x8ce6, 0x00, 0, 0}, {0x8ce7, 0xd2, 0, 0}, {0x8ce8, 0x34, 0, 0},
+ {0x8ce9, 0xd2, 0, 0}, {0x8cea, 0xaf, 0, 0}, {0x8ceb, 0x75, 0, 0},
+ {0x8cec, 0x34, 0, 0}, {0x8ced, 0xff, 0, 0}, {0x8cee, 0x75, 0, 0},
+ {0x8cef, 0x35, 0, 0}, {0x8cf0, 0x0e, 0, 0}, {0x8cf1, 0x75, 0, 0},
+ {0x8cf2, 0x36, 0, 0}, {0x8cf3, 0x49, 0, 0}, {0x8cf4, 0x75, 0, 0},
+ {0x8cf5, 0x37, 0, 0}, {0x8cf6, 0x03, 0, 0}, {0x8cf7, 0x12, 0, 0},
+ {0x8cf8, 0x0d, 0, 0}, {0x8cf9, 0xaa, 0, 0}, {0x8cfa, 0x30, 0, 0},
+ {0x8cfb, 0x08, 0, 0}, {0x8cfc, 0x09, 0, 0}, {0x8cfd, 0xc2, 0, 0},
+ {0x8cfe, 0x34, 0, 0}, {0x8cff, 0x12, 0, 0}, {0x8d00, 0x09, 0, 0},
+ {0x8d01, 0xee, 0, 0}, {0x8d02, 0xc2, 0, 0}, {0x8d03, 0x08, 0, 0},
+ {0x8d04, 0xd2, 0, 0}, {0x8d05, 0x34, 0, 0}, {0x8d06, 0x30, 0, 0},
+ {0x8d07, 0x0b, 0, 0}, {0x8d08, 0x09, 0, 0}, {0x8d09, 0xc2, 0, 0},
+ {0x8d0a, 0x36, 0, 0}, {0x8d0b, 0x12, 0, 0}, {0x8d0c, 0x00, 0, 0},
+ {0x8d0d, 0x0e, 0, 0}, {0x8d0e, 0xc2, 0, 0}, {0x8d0f, 0x0b, 0, 0},
+ {0x8d10, 0xd2, 0, 0}, {0x8d11, 0x36, 0, 0}, {0x8d12, 0x30, 0, 0},
+ {0x8d13, 0x09, 0, 0}, {0x8d14, 0x09, 0, 0}, {0x8d15, 0xc2, 0, 0},
+ {0x8d16, 0x36, 0, 0}, {0x8d17, 0x12, 0, 0}, {0x8d18, 0x02, 0, 0},
+ {0x8d19, 0xa7, 0, 0}, {0x8d1a, 0xc2, 0, 0}, {0x8d1b, 0x09, 0, 0},
+ {0x8d1c, 0xd2, 0, 0}, {0x8d1d, 0x36, 0, 0}, {0x8d1e, 0x30, 0, 0},
+ {0x8d1f, 0x0e, 0, 0}, {0x8d20, 0x03, 0, 0}, {0x8d21, 0x12, 0, 0},
+ {0x8d22, 0x07, 0, 0}, {0x8d23, 0x38, 0, 0}, {0x8d24, 0x30, 0, 0},
+ {0x8d25, 0x35, 0, 0}, {0x8d26, 0xd3, 0, 0}, {0x8d27, 0x90, 0, 0},
+ {0x8d28, 0x30, 0, 0}, {0x8d29, 0x29, 0, 0}, {0x8d2a, 0xe5, 0, 0},
+ {0x8d2b, 0x1e, 0, 0}, {0x8d2c, 0xf0, 0, 0}, {0x8d2d, 0xb4, 0, 0},
+ {0x8d2e, 0x10, 0, 0}, {0x8d2f, 0x05, 0, 0}, {0x8d30, 0x90, 0, 0},
+ {0x8d31, 0x30, 0, 0}, {0x8d32, 0x23, 0, 0}, {0x8d33, 0xe4, 0, 0},
+ {0x8d34, 0xf0, 0, 0}, {0x8d35, 0xc2, 0, 0}, {0x8d36, 0x35, 0, 0},
+ {0x8d37, 0x80, 0, 0}, {0x8d38, 0xc1, 0, 0}, {0x8d39, 0xe4, 0, 0},
+ {0x8d3a, 0xf5, 0, 0}, {0x8d3b, 0x4b, 0, 0}, {0x8d3c, 0x90, 0, 0},
+ {0x8d3d, 0x0e, 0, 0}, {0x8d3e, 0x7a, 0, 0}, {0x8d3f, 0x93, 0, 0},
+ {0x8d40, 0xff, 0, 0}, {0x8d41, 0xe4, 0, 0}, {0x8d42, 0x8f, 0, 0},
+ {0x8d43, 0x37, 0, 0}, {0x8d44, 0xf5, 0, 0}, {0x8d45, 0x36, 0, 0},
+ {0x8d46, 0xf5, 0, 0}, {0x8d47, 0x35, 0, 0}, {0x8d48, 0xf5, 0, 0},
+ {0x8d49, 0x34, 0, 0}, {0x8d4a, 0xaf, 0, 0}, {0x8d4b, 0x37, 0, 0},
+ {0x8d4c, 0xae, 0, 0}, {0x8d4d, 0x36, 0, 0}, {0x8d4e, 0xad, 0, 0},
+ {0x8d4f, 0x35, 0, 0}, {0x8d50, 0xac, 0, 0}, {0x8d51, 0x34, 0, 0},
+ {0x8d52, 0x90, 0, 0}, {0x8d53, 0x0e, 0, 0}, {0x8d54, 0x6a, 0, 0},
+ {0x8d55, 0x12, 0, 0}, {0x8d56, 0x0f, 0, 0}, {0x8d57, 0xa6, 0, 0},
+ {0x8d58, 0x8f, 0, 0}, {0x8d59, 0x37, 0, 0}, {0x8d5a, 0x8e, 0, 0},
+ {0x8d5b, 0x36, 0, 0}, {0x8d5c, 0x8d, 0, 0}, {0x8d5d, 0x35, 0, 0},
+ {0x8d5e, 0x8c, 0, 0}, {0x8d5f, 0x34, 0, 0}, {0x8d60, 0x90, 0, 0},
+ {0x8d61, 0x0e, 0, 0}, {0x8d62, 0x72, 0, 0}, {0x8d63, 0x12, 0, 0},
+ {0x8d64, 0x06, 0, 0}, {0x8d65, 0xdd, 0, 0}, {0x8d66, 0xef, 0, 0},
+ {0x8d67, 0x45, 0, 0}, {0x8d68, 0x37, 0, 0}, {0x8d69, 0xf5, 0, 0},
+ {0x8d6a, 0x37, 0, 0}, {0x8d6b, 0xee, 0, 0}, {0x8d6c, 0x45, 0, 0},
+ {0x8d6d, 0x36, 0, 0}, {0x8d6e, 0xf5, 0, 0}, {0x8d6f, 0x36, 0, 0},
+ {0x8d70, 0xed, 0, 0}, {0x8d71, 0x45, 0, 0}, {0x8d72, 0x35, 0, 0},
+ {0x8d73, 0xf5, 0, 0}, {0x8d74, 0x35, 0, 0}, {0x8d75, 0xec, 0, 0},
+ {0x8d76, 0x45, 0, 0}, {0x8d77, 0x34, 0, 0}, {0x8d78, 0xf5, 0, 0},
+ {0x8d79, 0x34, 0, 0}, {0x8d7a, 0xe4, 0, 0}, {0x8d7b, 0xf5, 0, 0},
+ {0x8d7c, 0x22, 0, 0}, {0x8d7d, 0xf5, 0, 0}, {0x8d7e, 0x23, 0, 0},
+ {0x8d7f, 0x85, 0, 0}, {0x8d80, 0x37, 0, 0}, {0x8d81, 0x31, 0, 0},
+ {0x8d82, 0x85, 0, 0}, {0x8d83, 0x36, 0, 0}, {0x8d84, 0x30, 0, 0},
+ {0x8d85, 0x85, 0, 0}, {0x8d86, 0x35, 0, 0}, {0x8d87, 0x2f, 0, 0},
+ {0x8d88, 0x85, 0, 0}, {0x8d89, 0x34, 0, 0}, {0x8d8a, 0x2e, 0, 0},
+ {0x8d8b, 0x12, 0, 0}, {0x8d8c, 0x0f, 0, 0}, {0x8d8d, 0x63, 0, 0},
+ {0x8d8e, 0xe4, 0, 0}, {0x8d8f, 0xf5, 0, 0}, {0x8d90, 0x22, 0, 0},
+ {0x8d91, 0xf5, 0, 0}, {0x8d92, 0x23, 0, 0}, {0x8d93, 0x90, 0, 0},
+ {0x8d94, 0x0e, 0, 0}, {0x8d95, 0x72, 0, 0}, {0x8d96, 0x12, 0, 0},
+ {0x8d97, 0x0f, 0, 0}, {0x8d98, 0x9a, 0, 0}, {0x8d99, 0x12, 0, 0},
+ {0x8d9a, 0x0f, 0, 0}, {0x8d9b, 0x63, 0, 0}, {0x8d9c, 0xe4, 0, 0},
+ {0x8d9d, 0xf5, 0, 0}, {0x8d9e, 0x22, 0, 0}, {0x8d9f, 0xf5, 0, 0},
+ {0x8da0, 0x23, 0, 0}, {0x8da1, 0x90, 0, 0}, {0x8da2, 0x0e, 0, 0},
+ {0x8da3, 0x6e, 0, 0}, {0x8da4, 0x12, 0, 0}, {0x8da5, 0x0f, 0, 0},
+ {0x8da6, 0x9a, 0, 0}, {0x8da7, 0x02, 0, 0}, {0x8da8, 0x0f, 0, 0},
+ {0x8da9, 0x63, 0, 0}, {0x8daa, 0xae, 0, 0}, {0x8dab, 0x35, 0, 0},
+ {0x8dac, 0xaf, 0, 0}, {0x8dad, 0x36, 0, 0}, {0x8dae, 0xe4, 0, 0},
+ {0x8daf, 0xfd, 0, 0}, {0x8db0, 0xed, 0, 0}, {0x8db1, 0xc3, 0, 0},
+ {0x8db2, 0x95, 0, 0}, {0x8db3, 0x37, 0, 0}, {0x8db4, 0x50, 0, 0},
+ {0x8db5, 0x33, 0, 0}, {0x8db6, 0x12, 0, 0}, {0x8db7, 0x0f, 0, 0},
+ {0x8db8, 0xec, 0, 0}, {0x8db9, 0xe4, 0, 0}, {0x8dba, 0x93, 0, 0},
+ {0x8dbb, 0xf5, 0, 0}, {0x8dbc, 0x38, 0, 0}, {0x8dbd, 0x74, 0, 0},
+ {0x8dbe, 0x01, 0, 0}, {0x8dbf, 0x93, 0, 0}, {0x8dc0, 0xf5, 0, 0},
+ {0x8dc1, 0x39, 0, 0}, {0x8dc2, 0x45, 0, 0}, {0x8dc3, 0x38, 0, 0},
+ {0x8dc4, 0x60, 0, 0}, {0x8dc5, 0x23, 0, 0}, {0x8dc6, 0x85, 0, 0},
+ {0x8dc7, 0x39, 0, 0}, {0x8dc8, 0x82, 0, 0}, {0x8dc9, 0x85, 0, 0},
+ {0x8dca, 0x38, 0, 0}, {0x8dcb, 0x83, 0, 0}, {0x8dcc, 0xe0, 0, 0},
+ {0x8dcd, 0xfc, 0, 0}, {0x8dce, 0x12, 0, 0}, {0x8dcf, 0x0f, 0, 0},
+ {0x8dd0, 0xec, 0, 0}, {0x8dd1, 0x74, 0, 0}, {0x8dd2, 0x03, 0, 0},
+ {0x8dd3, 0x93, 0, 0}, {0x8dd4, 0x52, 0, 0}, {0x8dd5, 0x04, 0, 0},
+ {0x8dd6, 0x12, 0, 0}, {0x8dd7, 0x0f, 0, 0}, {0x8dd8, 0xec, 0, 0},
+ {0x8dd9, 0x74, 0, 0}, {0x8dda, 0x02, 0, 0}, {0x8ddb, 0x93, 0, 0},
+ {0x8ddc, 0x42, 0, 0}, {0x8ddd, 0x04, 0, 0}, {0x8dde, 0x85, 0, 0},
+ {0x8ddf, 0x39, 0, 0}, {0x8de0, 0x82, 0, 0}, {0x8de1, 0x85, 0, 0},
+ {0x8de2, 0x38, 0, 0}, {0x8de3, 0x83, 0, 0}, {0x8de4, 0xec, 0, 0},
+ {0x8de5, 0xf0, 0, 0}, {0x8de6, 0x0d, 0, 0}, {0x8de7, 0x80, 0, 0},
+ {0x8de8, 0xc7, 0, 0}, {0x8de9, 0x22, 0, 0}, {0x8dea, 0xc0, 0, 0},
+ {0x8deb, 0xe0, 0, 0}, {0x8dec, 0xc0, 0, 0}, {0x8ded, 0x83, 0, 0},
+ {0x8dee, 0xc0, 0, 0}, {0x8def, 0x82, 0, 0}, {0x8df0, 0x90, 0, 0},
+ {0x8df1, 0x3f, 0, 0}, {0x8df2, 0x0d, 0, 0}, {0x8df3, 0xe0, 0, 0},
+ {0x8df4, 0xf5, 0, 0}, {0x8df5, 0x33, 0, 0}, {0x8df6, 0xe5, 0, 0},
+ {0x8df7, 0x33, 0, 0}, {0x8df8, 0xf0, 0, 0}, {0x8df9, 0xd0, 0, 0},
+ {0x8dfa, 0x82, 0, 0}, {0x8dfb, 0xd0, 0, 0}, {0x8dfc, 0x83, 0, 0},
+ {0x8dfd, 0xd0, 0, 0}, {0x8dfe, 0xe0, 0, 0}, {0x8dff, 0x32, 0, 0},
+ {0x8e00, 0x12, 0, 0}, {0x8e01, 0x04, 0, 0}, {0x8e02, 0x13, 0, 0},
+ {0x8e03, 0x10, 0, 0}, {0x8e04, 0x01, 0, 0}, {0x8e05, 0x03, 0, 0},
+ {0x8e06, 0x4f, 0, 0}, {0x8e07, 0x56, 0, 0}, {0x8e08, 0x54, 0, 0},
+ {0x8e09, 0x20, 0, 0}, {0x8e0a, 0x20, 0, 0}, {0x8e0b, 0x20, 0, 0},
+ {0x8e0c, 0x20, 0, 0}, {0x8e0d, 0x20, 0, 0}, {0x8e0e, 0x13, 0, 0},
+ {0x8e0f, 0x01, 0, 0}, {0x8e10, 0x10, 0, 0}, {0x8e11, 0x01, 0, 0},
+ {0x8e12, 0x56, 0, 0}, {0x8e13, 0x40, 0, 0}, {0x8e14, 0x1a, 0, 0},
+ {0x8e15, 0x30, 0, 0}, {0x8e16, 0x29, 0, 0}, {0x8e17, 0x7e, 0, 0},
+ {0x8e18, 0x00, 0, 0}, {0x8e19, 0x30, 0, 0}, {0x8e1a, 0x04, 0, 0},
+ {0x8e1b, 0x20, 0, 0}, {0x8e1c, 0xdf, 0, 0}, {0x8e1d, 0x30, 0, 0},
+ {0x8e1e, 0x05, 0, 0}, {0x8e1f, 0x40, 0, 0}, {0x8e20, 0xbf, 0, 0},
+ {0x8e21, 0x50, 0, 0}, {0x8e22, 0x03, 0, 0}, {0x8e23, 0x00, 0, 0},
+ {0x8e24, 0xfd, 0, 0}, {0x8e25, 0x50, 0, 0}, {0x8e26, 0x27, 0, 0},
+ {0x8e27, 0x01, 0, 0}, {0x8e28, 0xfe, 0, 0}, {0x8e29, 0x60, 0, 0},
+ {0x8e2a, 0x00, 0, 0}, {0x8e2b, 0x11, 0, 0}, {0x8e2c, 0x00, 0, 0},
+ {0x8e2d, 0x3f, 0, 0}, {0x8e2e, 0x05, 0, 0}, {0x8e2f, 0x30, 0, 0},
+ {0x8e30, 0x00, 0, 0}, {0x8e31, 0x3f, 0, 0}, {0x8e32, 0x06, 0, 0},
+ {0x8e33, 0x22, 0, 0}, {0x8e34, 0x00, 0, 0}, {0x8e35, 0x3f, 0, 0},
+ {0x8e36, 0x01, 0, 0}, {0x8e37, 0x2a, 0, 0}, {0x8e38, 0x00, 0, 0},
+ {0x8e39, 0x3f, 0, 0}, {0x8e3a, 0x02, 0, 0}, {0x8e3b, 0x00, 0, 0},
+ {0x8e3c, 0x00, 0, 0}, {0x8e3d, 0x36, 0, 0}, {0x8e3e, 0x06, 0, 0},
+ {0x8e3f, 0x07, 0, 0}, {0x8e40, 0x00, 0, 0}, {0x8e41, 0x3f, 0, 0},
+ {0x8e42, 0x0b, 0, 0}, {0x8e43, 0x0f, 0, 0}, {0x8e44, 0xf0, 0, 0},
+ {0x8e45, 0x00, 0, 0}, {0x8e46, 0x00, 0, 0}, {0x8e47, 0x00, 0, 0},
+ {0x8e48, 0x00, 0, 0}, {0x8e49, 0x30, 0, 0}, {0x8e4a, 0x01, 0, 0},
+ {0x8e4b, 0x40, 0, 0}, {0x8e4c, 0xbf, 0, 0}, {0x8e4d, 0x30, 0, 0},
+ {0x8e4e, 0x01, 0, 0}, {0x8e4f, 0x00, 0, 0}, {0x8e50, 0xbf, 0, 0},
+ {0x8e51, 0x30, 0, 0}, {0x8e52, 0x29, 0, 0}, {0x8e53, 0x70, 0, 0},
+ {0x8e54, 0x00, 0, 0}, {0x8e55, 0x3a, 0, 0}, {0x8e56, 0x00, 0, 0},
+ {0x8e57, 0x00, 0, 0}, {0x8e58, 0xff, 0, 0}, {0x8e59, 0x3a, 0, 0},
+ {0x8e5a, 0x00, 0, 0}, {0x8e5b, 0x00, 0, 0}, {0x8e5c, 0xff, 0, 0},
+ {0x8e5d, 0x36, 0, 0}, {0x8e5e, 0x03, 0, 0}, {0x8e5f, 0x36, 0, 0},
+ {0x8e60, 0x02, 0, 0}, {0x8e61, 0x41, 0, 0}, {0x8e62, 0x44, 0, 0},
+ {0x8e63, 0x58, 0, 0}, {0x8e64, 0x20, 0, 0}, {0x8e65, 0x18, 0, 0},
+ {0x8e66, 0x10, 0, 0}, {0x8e67, 0x0a, 0, 0}, {0x8e68, 0x04, 0, 0},
+ {0x8e69, 0x04, 0, 0}, {0x8e6a, 0x00, 0, 0}, {0x8e6b, 0x03, 0, 0},
+ {0x8e6c, 0xff, 0, 0}, {0x8e6d, 0x64, 0, 0}, {0x8e6e, 0x00, 0, 0},
+ {0x8e6f, 0x00, 0, 0}, {0x8e70, 0x80, 0, 0}, {0x8e71, 0x00, 0, 0},
+ {0x8e72, 0x00, 0, 0}, {0x8e73, 0x00, 0, 0}, {0x8e74, 0x00, 0, 0},
+ {0x8e75, 0x00, 0, 0}, {0x8e76, 0x00, 0, 0}, {0x8e77, 0x02, 0, 0},
+ {0x8e78, 0x04, 0, 0}, {0x8e79, 0x06, 0, 0}, {0x8e7a, 0x06, 0, 0},
+ {0x8e7b, 0x00, 0, 0}, {0x8e7c, 0x02, 0, 0}, {0x8e7d, 0x65, 0, 0},
+ {0x8e7e, 0x00, 0, 0}, {0x8e7f, 0x7a, 0, 0}, {0x8e80, 0x50, 0, 0},
+ {0x8e81, 0x3c, 0, 0}, {0x8e82, 0x28, 0, 0}, {0x8e83, 0x1e, 0, 0},
+ {0x8e84, 0x20, 0, 0}, {0x8e85, 0x20, 0, 0}, {0x8e86, 0x50, 0, 0},
+ {0x8e87, 0x2d, 0, 0}, {0x8e88, 0x28, 0, 0}, {0x8e89, 0x16, 0, 0},
+ {0x8e8a, 0x20, 0, 0}, {0x8e8b, 0x20, 0, 0}, {0x8e8c, 0x02, 0, 0},
+ {0x8e8d, 0x00, 0, 0}, {0x8e8e, 0x10, 0, 0}, {0x8e8f, 0x30, 0, 0},
+ {0x8e90, 0x0a, 0, 0}, {0x8e91, 0x04, 0, 0}, {0x8e92, 0x05, 0, 0},
+ {0x8e93, 0x08, 0, 0}, {0x8e94, 0x64, 0, 0}, {0x8e95, 0x0a, 0, 0},
+ {0x8e96, 0x05, 0, 0}, {0x8e97, 0x00, 0, 0}, {0x8e98, 0x00, 0, 0},
+ {0x8e99, 0xa5, 0, 0}, {0x8e9a, 0x5a, 0, 0}, {0x8e9b, 0x00, 0, 0},
+ {0x8e9c, 0x78, 0, 0}, {0x8e9d, 0xbe, 0, 0}, {0x8e9e, 0xe6, 0, 0},
+ {0x8e9f, 0xd3, 0, 0}, {0x8ea0, 0x08, 0, 0}, {0x8ea1, 0xff, 0, 0},
+ {0x8ea2, 0xe6, 0, 0}, {0x8ea3, 0x64, 0, 0}, {0x8ea4, 0x80, 0, 0},
+ {0x8ea5, 0xf8, 0, 0}, {0x8ea6, 0xef, 0, 0}, {0x8ea7, 0x64, 0, 0},
+ {0x8ea8, 0x80, 0, 0}, {0x8ea9, 0x98, 0, 0}, {0x8eaa, 0x22, 0, 0},
+ {0x8eab, 0x78, 0, 0}, {0x8eac, 0xc0, 0, 0}, {0x8ead, 0xa6, 0, 0},
+ {0x8eae, 0x07, 0, 0}, {0x8eaf, 0x78, 0, 0}, {0x8eb0, 0xbd, 0, 0},
+ {0x8eb1, 0xd3, 0, 0}, {0x8eb2, 0xe6, 0, 0}, {0x8eb3, 0x64, 0, 0},
+ {0x8eb4, 0x80, 0, 0}, {0x8eb5, 0x94, 0, 0}, {0x8eb6, 0x80, 0, 0},
+ {0x8eb7, 0x22, 0, 0}, {0x8eb8, 0x93, 0, 0}, {0x8eb9, 0xff, 0, 0},
+ {0x8eba, 0x7e, 0, 0}, {0x8ebb, 0x00, 0, 0}, {0x8ebc, 0xe6, 0, 0},
+ {0x8ebd, 0xfc, 0, 0}, {0x8ebe, 0x08, 0, 0}, {0x8ebf, 0xe6, 0, 0},
+ {0x8ec0, 0xfd, 0, 0}, {0x8ec1, 0x12, 0, 0}, {0x8ec2, 0x05, 0, 0},
+ {0x8ec3, 0x22, 0, 0}, {0x8ec4, 0x78, 0, 0}, {0x8ec5, 0xc1, 0, 0},
+ {0x8ec6, 0xe6, 0, 0}, {0x8ec7, 0xfc, 0, 0}, {0x8ec8, 0x08, 0, 0},
+ {0x8ec9, 0xe6, 0, 0}, {0x8eca, 0xfd, 0, 0}, {0x8ecb, 0xd3, 0, 0},
+ {0x8ecc, 0xef, 0, 0}, {0x8ecd, 0x9d, 0, 0}, {0x8ece, 0xee, 0, 0},
+ {0x8ecf, 0x9c, 0, 0}, {0x8ed0, 0x22, 0, 0}, {0x8ed1, 0x78, 0, 0},
+ {0x8ed2, 0xbf, 0, 0}, {0x8ed3, 0xa6, 0, 0}, {0x8ed4, 0x07, 0, 0},
+ {0x8ed5, 0x08, 0, 0}, {0x8ed6, 0xd3, 0, 0}, {0x8ed7, 0xe6, 0, 0},
+ {0x8ed8, 0x64, 0, 0}, {0x8ed9, 0x80, 0, 0}, {0x8eda, 0x94, 0, 0},
+ {0x8edb, 0x80, 0, 0}, {0x8edc, 0x22, 0, 0}, {0x8edd, 0x78, 0, 0},
+ {0x8ede, 0xc0, 0, 0}, {0x8edf, 0xa6, 0, 0}, {0x8ee0, 0x07, 0, 0},
+ {0x8ee1, 0xc3, 0, 0}, {0x8ee2, 0x18, 0, 0}, {0x8ee3, 0xe6, 0, 0},
+ {0x8ee4, 0x64, 0, 0}, {0x8ee5, 0x80, 0, 0}, {0x8ee6, 0x94, 0, 0},
+ {0x8ee7, 0xb3, 0, 0}, {0x8ee8, 0x22, 0, 0}, {0x8ee9, 0x78, 0, 0},
+ {0x8eea, 0xbf, 0, 0}, {0x8eeb, 0xa6, 0, 0}, {0x8eec, 0x07, 0, 0},
+ {0x8eed, 0xc3, 0, 0}, {0x8eee, 0x08, 0, 0}, {0x8eef, 0xe6, 0, 0},
+ {0x8ef0, 0x64, 0, 0}, {0x8ef1, 0x80, 0, 0}, {0x8ef2, 0x94, 0, 0},
+ {0x8ef3, 0xb3, 0, 0}, {0x8ef4, 0x22, 0, 0}, {0x8ef5, 0x25, 0, 0},
+ {0x8ef6, 0xe0, 0, 0}, {0x8ef7, 0x24, 0, 0}, {0x8ef8, 0x0a, 0, 0},
+ {0x8ef9, 0xf8, 0, 0}, {0x8efa, 0xe6, 0, 0}, {0x8efb, 0xfe, 0, 0},
+ {0x8efc, 0x08, 0, 0}, {0x8efd, 0xe6, 0, 0}, {0x8efe, 0xff, 0, 0},
+ {0x8eff, 0x22, 0, 0}, {0x8f00, 0xe5, 0, 0}, {0x8f01, 0x40, 0, 0},
+ {0x8f02, 0x24, 0, 0}, {0x8f03, 0xf2, 0, 0}, {0x8f04, 0xf5, 0, 0},
+ {0x8f05, 0x37, 0, 0}, {0x8f06, 0xe5, 0, 0}, {0x8f07, 0x3f, 0, 0},
+ {0x8f08, 0x34, 0, 0}, {0x8f09, 0x43, 0, 0}, {0x8f0a, 0xf5, 0, 0},
+ {0x8f0b, 0x36, 0, 0}, {0x8f0c, 0xe5, 0, 0}, {0x8f0d, 0x3e, 0, 0},
+ {0x8f0e, 0x34, 0, 0}, {0x8f0f, 0xa2, 0, 0}, {0x8f10, 0xf5, 0, 0},
+ {0x8f11, 0x35, 0, 0}, {0x8f12, 0xe5, 0, 0}, {0x8f13, 0x3d, 0, 0},
+ {0x8f14, 0x34, 0, 0}, {0x8f15, 0x28, 0, 0}, {0x8f16, 0xf5, 0, 0},
+ {0x8f17, 0x34, 0, 0}, {0x8f18, 0xe5, 0, 0}, {0x8f19, 0x37, 0, 0},
+ {0x8f1a, 0xff, 0, 0}, {0x8f1b, 0xe4, 0, 0}, {0x8f1c, 0xfe, 0, 0},
+ {0x8f1d, 0xfd, 0, 0}, {0x8f1e, 0xfc, 0, 0}, {0x8f1f, 0x78, 0, 0},
+ {0x8f20, 0x18, 0, 0}, {0x8f21, 0x12, 0, 0}, {0x8f22, 0x06, 0, 0},
+ {0x8f23, 0xca, 0, 0}, {0x8f24, 0x8f, 0, 0}, {0x8f25, 0x40, 0, 0},
+ {0x8f26, 0x8e, 0, 0}, {0x8f27, 0x3f, 0, 0}, {0x8f28, 0x8d, 0, 0},
+ {0x8f29, 0x3e, 0, 0}, {0x8f2a, 0x8c, 0, 0}, {0x8f2b, 0x3d, 0, 0},
+ {0x8f2c, 0xe5, 0, 0}, {0x8f2d, 0x37, 0, 0}, {0x8f2e, 0x54, 0, 0},
+ {0x8f2f, 0xa0, 0, 0}, {0x8f30, 0xff, 0, 0}, {0x8f31, 0xe5, 0, 0},
+ {0x8f32, 0x36, 0, 0}, {0x8f33, 0xfe, 0, 0}, {0x8f34, 0xe4, 0, 0},
+ {0x8f35, 0xfd, 0, 0}, {0x8f36, 0xfc, 0, 0}, {0x8f37, 0x78, 0, 0},
+ {0x8f38, 0x07, 0, 0}, {0x8f39, 0x12, 0, 0}, {0x8f3a, 0x06, 0, 0},
+ {0x8f3b, 0xb7, 0, 0}, {0x8f3c, 0x78, 0, 0}, {0x8f3d, 0x10, 0, 0},
+ {0x8f3e, 0x12, 0, 0}, {0x8f3f, 0x0f, 0, 0}, {0x8f40, 0xac, 0, 0},
+ {0x8f41, 0xe4, 0, 0}, {0x8f42, 0xff, 0, 0}, {0x8f43, 0xfe, 0, 0},
+ {0x8f44, 0xe5, 0, 0}, {0x8f45, 0x35, 0, 0}, {0x8f46, 0xfd, 0, 0},
+ {0x8f47, 0xe4, 0, 0}, {0x8f48, 0xfc, 0, 0}, {0x8f49, 0x78, 0, 0},
+ {0x8f4a, 0x0e, 0, 0}, {0x8f4b, 0x12, 0, 0}, {0x8f4c, 0x06, 0, 0},
+ {0x8f4d, 0xb7, 0, 0}, {0x8f4e, 0x12, 0, 0}, {0x8f4f, 0x0f, 0, 0},
+ {0x8f50, 0xaf, 0, 0}, {0x8f51, 0xe4, 0, 0}, {0x8f52, 0xff, 0, 0},
+ {0x8f53, 0xfe, 0, 0}, {0x8f54, 0xfd, 0, 0}, {0x8f55, 0xe5, 0, 0},
+ {0x8f56, 0x34, 0, 0}, {0x8f57, 0xfc, 0, 0}, {0x8f58, 0x78, 0, 0},
+ {0x8f59, 0x18, 0, 0}, {0x8f5a, 0x12, 0, 0}, {0x8f5b, 0x06, 0, 0},
+ {0x8f5c, 0xb7, 0, 0}, {0x8f5d, 0x78, 0, 0}, {0x8f5e, 0x08, 0, 0},
+ {0x8f5f, 0x12, 0, 0}, {0x8f60, 0x0f, 0, 0}, {0x8f61, 0xac, 0, 0},
+ {0x8f62, 0x22, 0, 0}, {0x8f63, 0xa2, 0, 0}, {0x8f64, 0xaf, 0, 0},
+ {0x8f65, 0x92, 0, 0}, {0x8f66, 0x32, 0, 0}, {0x8f67, 0xc2, 0, 0},
+ {0x8f68, 0xaf, 0, 0}, {0x8f69, 0xe5, 0, 0}, {0x8f6a, 0x23, 0, 0},
+ {0x8f6b, 0x45, 0, 0}, {0x8f6c, 0x22, 0, 0}, {0x8f6d, 0x90, 0, 0},
+ {0x8f6e, 0x0e, 0, 0}, {0x8f6f, 0x5d, 0, 0}, {0x8f70, 0x60, 0, 0},
+ {0x8f71, 0x0e, 0, 0}, {0x8f72, 0x12, 0, 0}, {0x8f73, 0x0f, 0, 0},
+ {0x8f74, 0xc7, 0, 0}, {0x8f75, 0xe0, 0, 0}, {0x8f76, 0xf5, 0, 0},
+ {0x8f77, 0x2c, 0, 0}, {0x8f78, 0x12, 0, 0}, {0x8f79, 0x0f, 0, 0},
+ {0x8f7a, 0xc4, 0, 0}, {0x8f7b, 0xe0, 0, 0}, {0x8f7c, 0xf5, 0, 0},
+ {0x8f7d, 0x2d, 0, 0}, {0x8f7e, 0x80, 0, 0}, {0x8f7f, 0x0c, 0, 0},
+ {0x8f80, 0x12, 0, 0}, {0x8f81, 0x0f, 0, 0}, {0x8f82, 0xc7, 0, 0},
+ {0x8f83, 0xe5, 0, 0}, {0x8f84, 0x30, 0, 0}, {0x8f85, 0xf0, 0, 0},
+ {0x8f86, 0x12, 0, 0}, {0x8f87, 0x0f, 0, 0}, {0x8f88, 0xc4, 0, 0},
+ {0x8f89, 0xe5, 0, 0}, {0x8f8a, 0x31, 0, 0}, {0x8f8b, 0xf0, 0, 0},
+ {0x8f8c, 0xa2, 0, 0}, {0x8f8d, 0x32, 0, 0}, {0x8f8e, 0x92, 0, 0},
+ {0x8f8f, 0xaf, 0, 0}, {0x8f90, 0x22, 0, 0}, {0x8f91, 0x8f, 0, 0},
+ {0x8f92, 0x3b, 0, 0}, {0x8f93, 0x8e, 0, 0}, {0x8f94, 0x3a, 0, 0},
+ {0x8f95, 0x8d, 0, 0}, {0x8f96, 0x39, 0, 0}, {0x8f97, 0x8c, 0, 0},
+ {0x8f98, 0x38, 0, 0}, {0x8f99, 0x22, 0, 0}, {0x8f9a, 0x12, 0, 0},
+ {0x8f9b, 0x06, 0, 0}, {0x8f9c, 0xdd, 0, 0}, {0x8f9d, 0x8f, 0, 0},
+ {0x8f9e, 0x31, 0, 0}, {0x8f9f, 0x8e, 0, 0}, {0x8fa0, 0x30, 0, 0},
+ {0x8fa1, 0x8d, 0, 0}, {0x8fa2, 0x2f, 0, 0}, {0x8fa3, 0x8c, 0, 0},
+ {0x8fa4, 0x2e, 0, 0}, {0x8fa5, 0x22, 0, 0}, {0x8fa6, 0x93, 0, 0},
+ {0x8fa7, 0xf9, 0, 0}, {0x8fa8, 0xf8, 0, 0}, {0x8fa9, 0x02, 0, 0},
+ {0x8faa, 0x06, 0, 0}, {0x8fab, 0xca, 0, 0}, {0x8fac, 0x12, 0, 0},
+ {0x8fad, 0x06, 0, 0}, {0x8fae, 0xca, 0, 0}, {0x8faf, 0xe5, 0, 0},
+ {0x8fb0, 0x40, 0, 0}, {0x8fb1, 0x2f, 0, 0}, {0x8fb2, 0xf5, 0, 0},
+ {0x8fb3, 0x40, 0, 0}, {0x8fb4, 0xe5, 0, 0}, {0x8fb5, 0x3f, 0, 0},
+ {0x8fb6, 0x3e, 0, 0}, {0x8fb7, 0xf5, 0, 0}, {0x8fb8, 0x3f, 0, 0},
+ {0x8fb9, 0xe5, 0, 0}, {0x8fba, 0x3e, 0, 0}, {0x8fbb, 0x3d, 0, 0},
+ {0x8fbc, 0xf5, 0, 0}, {0x8fbd, 0x3e, 0, 0}, {0x8fbe, 0xe5, 0, 0},
+ {0x8fbf, 0x3d, 0, 0}, {0x8fc0, 0x3c, 0, 0}, {0x8fc1, 0xf5, 0, 0},
+ {0x8fc2, 0x3d, 0, 0}, {0x8fc3, 0x22, 0, 0}, {0x8fc4, 0x90, 0, 0},
+ {0x8fc5, 0x0e, 0, 0}, {0x8fc6, 0x5f, 0, 0}, {0x8fc7, 0xe4, 0, 0},
+ {0x8fc8, 0x93, 0, 0}, {0x8fc9, 0xfe, 0, 0}, {0x8fca, 0x74, 0, 0},
+ {0x8fcb, 0x01, 0, 0}, {0x8fcc, 0x93, 0, 0}, {0x8fcd, 0xf5, 0, 0},
+ {0x8fce, 0x82, 0, 0}, {0x8fcf, 0x8e, 0, 0}, {0x8fd0, 0x83, 0, 0},
+ {0x8fd1, 0x22, 0, 0}, {0x8fd2, 0xd2, 0, 0}, {0x8fd3, 0x01, 0, 0},
+ {0x8fd4, 0xc2, 0, 0}, {0x8fd5, 0x02, 0, 0}, {0x8fd6, 0xe4, 0, 0},
+ {0x8fd7, 0xf5, 0, 0}, {0x8fd8, 0x1f, 0, 0}, {0x8fd9, 0xf5, 0, 0},
+ {0x8fda, 0x1e, 0, 0}, {0x8fdb, 0xd2, 0, 0}, {0x8fdc, 0x35, 0, 0},
+ {0x8fdd, 0xd2, 0, 0}, {0x8fde, 0x33, 0, 0}, {0x8fdf, 0x22, 0, 0},
+ {0x8fe0, 0x78, 0, 0}, {0x8fe1, 0x7f, 0, 0}, {0x8fe2, 0xe4, 0, 0},
+ {0x8fe3, 0xf6, 0, 0}, {0x8fe4, 0xd8, 0, 0}, {0x8fe5, 0xfd, 0, 0},
+ {0x8fe6, 0x75, 0, 0}, {0x8fe7, 0x81, 0, 0}, {0x8fe8, 0xcd, 0, 0},
+ {0x8fe9, 0x02, 0, 0}, {0x8fea, 0x0c, 0, 0}, {0x8feb, 0xc4, 0, 0},
+ {0x8fec, 0x8f, 0, 0}, {0x8fed, 0x82, 0, 0}, {0x8fee, 0x8e, 0, 0},
+ {0x8fef, 0x83, 0, 0}, {0x8ff0, 0x75, 0, 0}, {0x8ff1, 0xf0, 0, 0},
+ {0x8ff2, 0x04, 0, 0}, {0x8ff3, 0xed, 0, 0}, {0x8ff4, 0x02, 0, 0},
+ {0x8ff5, 0x07, 0, 0}, {0x8ff6, 0x06, 0, 0}, {0x3022, 0x00, 0, 0},
+ {0x3023, 0x00, 0, 0}, {0x3024, 0x00, 0, 0}, {0x3025, 0x00, 0, 0},
+ {0x3026, 0x00, 0, 0}, {0x3027, 0x00, 0, 0}, {0x3028, 0x00, 0, 0},
+ {0x3029, 0x7F, 0, 0}, {0x3000, 0x00, 0, 0},
+};
+
+static struct reg_value ov5645_init_setting_30fps_VGA[] = {
+ {0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0},
+ // These registers need to be set for AF to work
+ {0x3000, 0x00, 0, 0}, {0x3004, 0xff, 0, 0},
+ {0x3008, 0x42, 0, 0}, {0x3c00, 0x04, 0, 300},
+};
+
+static struct reg_value ov5645_setting_30fps_VGA_640_480[] = {
+ {0x3103, 0x11, 0, 0},
+// Comment this out in order for AF to work
+// {0x3008, 0x82, 0, 0},
+ {0x3008, 0x42, 0, 0}, {0x3103, 0x03, 0, 0}, {0x3503, 0x07, 0, 0},
+ {0x3002, 0x1c, 0, 0}, {0x3006, 0xc3, 0, 0}, {0x300e, 0x45, 0, 0},
+ {0x3017, 0x40, 0, 0}, {0x3018, 0x00, 0, 0}, {0x302e, 0x0b, 0, 0},
+ {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3611, 0x06, 0, 0},
+ {0x3612, 0xab, 0, 0}, {0x3614, 0x50, 0, 0}, {0x3618, 0x04, 0, 0},
+ {0x3034, 0x18, 0, 0}, {0x3035, 0x22, 0, 0}, {0x3036, 0x70, 0, 0},
+ {0x3500, 0x00, 0, 0}, {0x3501, 0x01, 0, 0}, {0x3502, 0x00, 0, 0},
+ {0x350a, 0x00, 0, 0}, {0x350b, 0x3f, 0, 0}, {0x3600, 0x09, 0, 0},
+ {0x3601, 0x43, 0, 0}, {0x3620, 0x33, 0, 0}, {0x3621, 0xe0, 0, 0},
+ {0x3622, 0x01, 0, 0}, {0x3630, 0x2d, 0, 0}, {0x3631, 0x00, 0, 0},
+ {0x3632, 0x32, 0, 0}, {0x3633, 0x52, 0, 0}, {0x3634, 0x70, 0, 0},
+ {0x3635, 0x13, 0, 0}, {0x3636, 0x03, 0, 0}, {0x3702, 0x6e, 0, 0},
+ {0x3703, 0x52, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3705, 0x33, 0, 0},
+ {0x3708, 0x66, 0, 0}, {0x3709, 0x12, 0, 0}, {0x370b, 0x61, 0, 0},
+ {0x370c, 0xc3, 0, 0}, {0x370f, 0x10, 0, 0}, {0x3715, 0x08, 0, 0},
+ {0x3717, 0x01, 0, 0}, {0x371b, 0x20, 0, 0}, {0x3731, 0x22, 0, 0},
+ {0x3739, 0x70, 0, 0}, {0x3901, 0x0a, 0, 0}, {0x3905, 0x02, 0, 0},
+ {0x3906, 0x10, 0, 0}, {0x3719, 0x86, 0, 0}, {0x3800, 0x00, 0, 0},
+ {0x3801, 0x00, 0, 0}, {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0},
+ {0x3804, 0x0a, 0, 0}, {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0},
+ {0x3807, 0x9b, 0, 0}, {0x3808, 0x02, 0, 0}, {0x3809, 0x80, 0, 0},
+ {0x380a, 0x01, 0, 0}, {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0},
+ {0x380d, 0x68, 0, 0}, {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0},
+ {0x3810, 0x00, 0, 0}, {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0},
+ {0x3813, 0x06, 0, 0}, {0x3814, 0x31, 0, 0}, {0x3815, 0x31, 0, 0},
+ {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3824, 0x01, 0, 0},
+ {0x3826, 0x03, 0, 0}, {0x3828, 0x08, 0, 0}, {0x3a02, 0x03, 0, 0},
+ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xf8, 0, 0},
+ {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0xa4, 0, 0}, {0x3a0e, 0x02, 0, 0},
+ {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
+ {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3c01, 0x34, 0, 0},
+ {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, {0x3c07, 0x07, 0, 0},
+ {0x3c09, 0xc2, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
+ {0x3c01, 0x34, 0, 0}, {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
+ {0x4005, 0x18, 0, 0}, {0x4050, 0x6e, 0, 0}, {0x4051, 0x8f, 0, 0},
+ {0x4300, 0x30, 0, 0}, {0x4514, 0xbb, 0, 0}, {0x4520, 0xb0, 0, 0},
+ {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x4818, 0x01, 0, 0},
+ {0x481d, 0xf0, 0, 0}, {0x481f, 0x50, 0, 0}, {0x4823, 0x70, 0, 0},
+ {0x4831, 0x14, 0, 0}, {0x4837, 0x20, 0, 0}, {0x5000, 0xa7, 0, 0},
+ {0x5001, 0xa3, 0, 0}, {0x501d, 0x00, 0, 0}, {0x501f, 0x00, 0, 0},
+ {0x503d, 0x00, 0, 0}, {0x505c, 0x30, 0, 0}, {0x5181, 0x59, 0, 0},
+ {0x5183, 0x00, 0, 0}, {0x5191, 0xf0, 0, 0}, {0x5192, 0x03, 0, 0},
+ {0x5684, 0x10, 0, 0}, {0x5685, 0xa0, 0, 0}, {0x5686, 0x0c, 0, 0},
+ {0x5687, 0x78, 0, 0}, {0x5a00, 0x08, 0, 0}, {0x5a21, 0x00, 0, 0},
+ {0x5a24, 0x00, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0x00, 0, 0},
+ {0x5180, 0xff, 0, 0}, {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0},
+ {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0},
+ {0x5186, 0x13, 0, 0}, {0x5187, 0x13, 0, 0}, {0x5188, 0x12, 0, 0},
+ {0x5189, 0x8c, 0, 0}, {0x518a, 0x5b, 0, 0}, {0x518b, 0x89, 0, 0},
+ {0x518c, 0x94, 0, 0}, {0x518d, 0x40, 0, 0}, {0x518e, 0x2c, 0, 0},
+ {0x518f, 0x77, 0, 0}, {0x5190, 0x45, 0, 0}, {0x5191, 0xf8, 0, 0},
+ {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0},
+ {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0},
+ {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0},
+ {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0},
+ {0x519e, 0x38, 0, 0}, {0x5381, 0x32, 0, 0}, {0x5382, 0x6b, 0, 0},
+ {0x5383, 0x1d, 0, 0}, {0x5384, 0x2a, 0, 0}, {0x5385, 0x7f, 0, 0},
+ {0x5386, 0xa9, 0, 0}, {0x5387, 0xb2, 0, 0}, {0x5388, 0xb5, 0, 0},
+ {0x5389, 0x03, 0, 0}, {0x538a, 0x00, 0, 0}, {0x538b, 0x9c, 0, 0},
+ {0x5300, 0x08, 0, 0}, {0x5301, 0x1e, 0, 0}, {0x5302, 0x10, 0, 0},
+ {0x5303, 0x00, 0, 0}, {0x5304, 0x08, 0, 0}, {0x5305, 0x1e, 0, 0},
+ {0x5306, 0x08, 0, 0}, {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0},
+ {0x530a, 0x1e, 0, 0}, {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0},
+ {0x5480, 0x01, 0, 0}, {0x5481, 0x0e, 0, 0}, {0x5482, 0x18, 0, 0},
+ {0x5483, 0x2b, 0, 0}, {0x5484, 0x52, 0, 0}, {0x5485, 0x65, 0, 0},
+ {0x5486, 0x71, 0, 0}, {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0},
+ {0x5489, 0x91, 0, 0}, {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0},
+ {0x548c, 0xb8, 0, 0}, {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0},
+ {0x548f, 0xea, 0, 0}, {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0},
+ {0x5583, 0x40, 0, 0}, {0x5584, 0x30, 0, 0}, {0x5589, 0x10, 0, 0},
+ {0x558a, 0x00, 0, 0}, {0x558b, 0xf8, 0, 0}, {0x5780, 0xfc, 0, 0},
+ {0x5781, 0x13, 0, 0}, {0x5782, 0x03, 0, 0}, {0x5786, 0x20, 0, 0},
+ {0x5787, 0x40, 0, 0}, {0x5788, 0x08, 0, 0}, {0x5789, 0x08, 0, 0},
+ {0x578a, 0x02, 0, 0}, {0x578b, 0x01, 0, 0}, {0x578c, 0x01, 0, 0},
+ {0x578d, 0x0c, 0, 0}, {0x578e, 0x02, 0, 0}, {0x578f, 0x01, 0, 0},
+ {0x5790, 0x01, 0, 0}, {0x5800, 0x3f, 0, 0}, {0x5801, 0x17, 0, 0},
+ {0x5802, 0x12, 0, 0}, {0x5803, 0x11, 0, 0}, {0x5804, 0x16, 0, 0},
+ {0x5805, 0x31, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0},
+ {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x07, 0, 0},
+ {0x580b, 0x0b, 0, 0}, {0x580c, 0x0a, 0, 0}, {0x580d, 0x03, 0, 0},
+ {0x580e, 0x01, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x02, 0, 0},
+ {0x5811, 0x07, 0, 0}, {0x5812, 0x08, 0, 0}, {0x5813, 0x03, 0, 0},
+ {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x02, 0, 0},
+ {0x5817, 0x06, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x07, 0, 0},
+ {0x581a, 0x04, 0, 0}, {0x581b, 0x04, 0, 0}, {0x581c, 0x06, 0, 0},
+ {0x581d, 0x0b, 0, 0}, {0x581e, 0x2e, 0, 0}, {0x581f, 0x12, 0, 0},
+ {0x5820, 0x0e, 0, 0}, {0x5821, 0x0d, 0, 0}, {0x5822, 0x10, 0, 0},
+ {0x5823, 0x1d, 0, 0}, {0x5824, 0x48, 0, 0}, {0x5825, 0x4c, 0, 0},
+ {0x5826, 0x4e, 0, 0}, {0x5827, 0x2c, 0, 0}, {0x5828, 0x68, 0, 0},
+ {0x5829, 0x4c, 0, 0}, {0x582a, 0x48, 0, 0}, {0x582b, 0x26, 0, 0},
+ {0x582c, 0x46, 0, 0}, {0x582d, 0x2c, 0, 0}, {0x582e, 0x4a, 0, 0},
+ {0x582f, 0x22, 0, 0}, {0x5830, 0x20, 0, 0}, {0x5831, 0x22, 0, 0},
+ {0x5832, 0x48, 0, 0}, {0x5833, 0x4c, 0, 0}, {0x5834, 0x26, 0, 0},
+ {0x5835, 0x24, 0, 0}, {0x5836, 0x26, 0, 0}, {0x5837, 0x0c, 0, 0},
+ {0x5838, 0x48, 0, 0}, {0x5839, 0x2e, 0, 0}, {0x583a, 0x2e, 0, 0},
+ {0x583b, 0x2e, 0, 0}, {0x583c, 0x4a, 0, 0}, {0x583d, 0xee, 0, 0},
+ {0x5025, 0x00, 0, 0}, {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0},
+ {0x3a11, 0x70, 0, 0}, {0x3a1b, 0x38, 0, 0}, {0x3a1e, 0x30, 0, 0},
+ {0x3a1f, 0x18, 0, 0}, {0x3008, 0x02, 0, 0},
+};
+
+static struct reg_value ov5645_setting_30fps_NTSC_720_480[] = {
+ {0x3103, 0x11, 0, 0},
+// Comment this out in order for AF to work
+// {0x3008, 0x82, 0, 0},
+ {0x3008, 0x42, 0, 0}, {0x3103, 0x03, 0, 0}, {0x3503, 0x07, 0, 0},
+ {0x3002, 0x1c, 0, 0}, {0x3006, 0xc3, 0, 0}, {0x300e, 0x45, 0, 0},
+ {0x3017, 0x40, 0, 0}, {0x3018, 0x00, 0, 0}, {0x302e, 0x0b, 0, 0},
+ {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3611, 0x06, 0, 0},
+ {0x3612, 0xab, 0, 0}, {0x3614, 0x50, 0, 0}, {0x3618, 0x04, 0, 0},
+ {0x3034, 0x18, 0, 0}, {0x3035, 0x22, 0, 0}, {0x3036, 0x70, 0, 0},
+ {0x3500, 0x00, 0, 0}, {0x3501, 0x01, 0, 0}, {0x3502, 0x00, 0, 0},
+ {0x350a, 0x00, 0, 0}, {0x350b, 0x3f, 0, 0}, {0x3600, 0x09, 0, 0},
+ {0x3601, 0x43, 0, 0}, {0x3620, 0x33, 0, 0}, {0x3621, 0xe0, 0, 0},
+ {0x3622, 0x01, 0, 0}, {0x3630, 0x2d, 0, 0}, {0x3631, 0x00, 0, 0},
+ {0x3632, 0x32, 0, 0}, {0x3633, 0x52, 0, 0}, {0x3634, 0x70, 0, 0},
+ {0x3635, 0x13, 0, 0}, {0x3636, 0x03, 0, 0}, {0x3702, 0x6e, 0, 0},
+ {0x3703, 0x52, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3705, 0x33, 0, 0},
+ {0x3708, 0x66, 0, 0}, {0x3709, 0x12, 0, 0}, {0x370b, 0x61, 0, 0},
+ {0x370c, 0xc3, 0, 0}, {0x370f, 0x10, 0, 0}, {0x3715, 0x08, 0, 0},
+ {0x3717, 0x01, 0, 0}, {0x371b, 0x20, 0, 0}, {0x3731, 0x22, 0, 0},
+ {0x3739, 0x70, 0, 0}, {0x3901, 0x0a, 0, 0}, {0x3905, 0x02, 0, 0},
+ {0x3906, 0x10, 0, 0}, {0x3719, 0x86, 0, 0}, {0x3800, 0x00, 0, 0},
+ {0x3801, 0x00, 0, 0}, {0x3802, 0x00, 0, 0}, {0x3803, 0x6c, 0, 0},
+ {0x3804, 0x0a, 0, 0}, {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0},
+ {0x3807, 0x33, 0, 0}, {0x3808, 0x02, 0, 0}, {0x3809, 0xd0, 0, 0},
+ {0x380a, 0x01, 0, 0}, {0x380b, 0xe0, 0, 0}, {0x380c, 0x07, 0, 0},
+ {0x380d, 0x68, 0, 0}, {0x380e, 0x03, 0, 0}, {0x380f, 0xd8, 0, 0},
+ {0x3810, 0x00, 0, 0}, {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0},
+ {0x3813, 0x06, 0, 0}, {0x3814, 0x31, 0, 0}, {0x3815, 0x31, 0, 0},
+ {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3824, 0x01, 0, 0},
+ {0x3826, 0x03, 0, 0}, {0x3828, 0x08, 0, 0}, {0x3a02, 0x03, 0, 0},
+ {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xf8, 0, 0},
+ {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0xa4, 0, 0}, {0x3a0e, 0x02, 0, 0},
+ {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
+ {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3c01, 0x34, 0, 0},
+ {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, {0x3c07, 0x07, 0, 0},
+ {0x3c09, 0xc2, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
+ {0x3c01, 0x34, 0, 0}, {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
+ {0x4005, 0x18, 0, 0}, {0x4050, 0x6e, 0, 0}, {0x4051, 0x8f, 0, 0},
+ {0x4300, 0x30, 0, 0}, {0x4514, 0xbb, 0, 0}, {0x4520, 0xb0, 0, 0},
+ {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x4818, 0x01, 0, 0},
+ {0x481d, 0xf0, 0, 0}, {0x481f, 0x50, 0, 0}, {0x4823, 0x70, 0, 0},
+ {0x4831, 0x14, 0, 0}, {0x4837, 0x20, 0, 0}, {0x5000, 0xa7, 0, 0},
+ {0x5001, 0xa3, 0, 0}, {0x501d, 0x00, 0, 0}, {0x501f, 0x00, 0, 0},
+ {0x503d, 0x00, 0, 0}, {0x505c, 0x30, 0, 0}, {0x5181, 0x59, 0, 0},
+ {0x5183, 0x94, 0, 0}, {0x5191, 0xff, 0, 0}, {0x5192, 0x00, 0, 0},
+ {0x5684, 0x10, 0, 0}, {0x5685, 0xa0, 0, 0}, {0x5686, 0x0c, 0, 0},
+ {0x5687, 0x78, 0, 0}, {0x5a00, 0x08, 0, 0}, {0x5a21, 0x00, 0, 0},
+ {0x5a24, 0x00, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0x00, 0, 0},
+ {0x5180, 0xff, 0, 0}, {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0},
+ {0x5183, 0x94, 0, 0}, {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0},
+ {0x5186, 0x13, 0, 0}, {0x5187, 0x13, 0, 0}, {0x5188, 0x12, 0, 0},
+ {0x5189, 0x8c, 0, 0}, {0x518a, 0x5b, 0, 0}, {0x518b, 0x89, 0, 0},
+ {0x518c, 0x94, 0, 0}, {0x518d, 0x40, 0, 0}, {0x518e, 0x2c, 0, 0},
+ {0x518f, 0x77, 0, 0}, {0x5190, 0x45, 0, 0}, {0x5191, 0xff, 0, 0},
+ {0x5192, 0x00, 0, 0}, {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0},
+ {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0},
+ {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0},
+ {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0},
+ {0x519e, 0x38, 0, 0}, {0x5381, 0x32, 0, 0}, {0x5382, 0x6b, 0, 0},
+ {0x5383, 0x1d, 0, 0}, {0x5384, 0x2a, 0, 0}, {0x5385, 0x7f, 0, 0},
+ {0x5386, 0xa9, 0, 0}, {0x5387, 0xb2, 0, 0}, {0x5388, 0xb5, 0, 0},
+ {0x5389, 0x03, 0, 0}, {0x538a, 0x00, 0, 0}, {0x538b, 0x9c, 0, 0},
+ {0x5300, 0x08, 0, 0}, {0x5301, 0x1e, 0, 0}, {0x5302, 0x10, 0, 0},
+ {0x5303, 0x00, 0, 0}, {0x5304, 0x08, 0, 0}, {0x5305, 0x1e, 0, 0},
+ {0x5306, 0x08, 0, 0}, {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0},
+ {0x530a, 0x1e, 0, 0}, {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0},
+ {0x5480, 0x01, 0, 0}, {0x5481, 0x0e, 0, 0}, {0x5482, 0x18, 0, 0},
+ {0x5483, 0x2b, 0, 0}, {0x5484, 0x52, 0, 0}, {0x5485, 0x65, 0, 0},
+ {0x5486, 0x71, 0, 0}, {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0},
+ {0x5489, 0x91, 0, 0}, {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0},
+ {0x548c, 0xb8, 0, 0}, {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0},
+ {0x548f, 0xea, 0, 0}, {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0},
+ {0x5583, 0x40, 0, 0}, {0x5584, 0x30, 0, 0}, {0x5589, 0x10, 0, 0},
+ {0x558a, 0x00, 0, 0}, {0x558b, 0xf8, 0, 0}, {0x5780, 0xfc, 0, 0},
+ {0x5781, 0x13, 0, 0}, {0x5782, 0x03, 0, 0}, {0x5786, 0x20, 0, 0},
+ {0x5787, 0x40, 0, 0}, {0x5788, 0x08, 0, 0}, {0x5789, 0x08, 0, 0},
+ {0x578a, 0x02, 0, 0}, {0x578b, 0x01, 0, 0}, {0x578c, 0x01, 0, 0},
+ {0x578d, 0x0c, 0, 0}, {0x578e, 0x02, 0, 0}, {0x578f, 0x01, 0, 0},
+ {0x5790, 0x01, 0, 0}, {0x5800, 0x3f, 0, 0}, {0x5801, 0x17, 0, 0},
+ {0x5802, 0x12, 0, 0}, {0x5803, 0x11, 0, 0}, {0x5804, 0x16, 0, 0},
+ {0x5805, 0x31, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0},
+ {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x07, 0, 0},
+ {0x580b, 0x0b, 0, 0}, {0x580c, 0x0a, 0, 0}, {0x580d, 0x03, 0, 0},
+ {0x580e, 0x01, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x02, 0, 0},
+ {0x5811, 0x07, 0, 0}, {0x5812, 0x08, 0, 0}, {0x5813, 0x03, 0, 0},
+ {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x02, 0, 0},
+ {0x5817, 0x06, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x07, 0, 0},
+ {0x581a, 0x04, 0, 0}, {0x581b, 0x04, 0, 0}, {0x581c, 0x06, 0, 0},
+ {0x581d, 0x0b, 0, 0}, {0x581e, 0x2e, 0, 0}, {0x581f, 0x12, 0, 0},
+ {0x5820, 0x0e, 0, 0}, {0x5821, 0x0d, 0, 0}, {0x5822, 0x10, 0, 0},
+ {0x5823, 0x1d, 0, 0}, {0x5824, 0x48, 0, 0}, {0x5825, 0x4c, 0, 0},
+ {0x5826, 0x4e, 0, 0}, {0x5827, 0x2c, 0, 0}, {0x5828, 0x68, 0, 0},
+ {0x5829, 0x4c, 0, 0}, {0x582a, 0x48, 0, 0}, {0x582b, 0x26, 0, 0},
+ {0x582c, 0x46, 0, 0}, {0x582d, 0x2c, 0, 0}, {0x582e, 0x4a, 0, 0},
+ {0x582f, 0x22, 0, 0}, {0x5830, 0x20, 0, 0}, {0x5831, 0x22, 0, 0},
+ {0x5832, 0x48, 0, 0}, {0x5833, 0x4c, 0, 0}, {0x5834, 0x26, 0, 0},
+ {0x5835, 0x24, 0, 0}, {0x5836, 0x26, 0, 0}, {0x5837, 0x0c, 0, 0},
+ {0x5838, 0x48, 0, 0}, {0x5839, 0x2e, 0, 0}, {0x583a, 0x2e, 0, 0},
+ {0x583b, 0x2e, 0, 0}, {0x583c, 0x4a, 0, 0}, {0x583d, 0xee, 0, 0},
+ {0x5025, 0x00, 0, 0}, {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0},
+ {0x3a11, 0x70, 0, 0}, {0x3a1b, 0x38, 0, 0}, {0x3a1e, 0x30, 0, 0},
+ {0x3a1f, 0x18, 0, 0}, {0x3008, 0x02, 0, 0},
+};
+
+static struct reg_value ov5645_setting_30fps_720P_1280_720[] = {
+ {0x3103, 0x11, 0, 0},
+// Comment this out in order for AF to work
+// {0x3008, 0x82, 0, 0},
+ {0x3008, 0x42, 0, 0}, {0x3103, 0x03, 0, 0}, {0x3503, 0x07, 0, 0},
+ {0x3002, 0x1c, 0, 0}, {0x3006, 0xc3, 0, 0}, {0x300e, 0x45, 0, 0},
+ {0x3017, 0x40, 0, 0}, {0x3018, 0x00, 0, 0}, {0x302e, 0x0b, 0, 0},
+ {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3611, 0x06, 0, 0},
+ {0x3612, 0xa9, 0, 0}, {0x3614, 0x50, 0, 0}, {0x3618, 0x00, 0, 0},
+ {0x3034, 0x18, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x54, 0, 0},
+ {0x3500, 0x00, 0, 0}, {0x3501, 0x01, 0, 0}, {0x3502, 0x00, 0, 0},
+ {0x350a, 0x00, 0, 0}, {0x350b, 0x3f, 0, 0}, {0x3600, 0x0a, 0, 0},
+ {0x3601, 0x75, 0, 0}, {0x3620, 0x33, 0, 0}, {0x3621, 0xe0, 0, 0},
+ {0x3622, 0x01, 0, 0}, {0x3630, 0x2d, 0, 0}, {0x3631, 0x00, 0, 0},
+ {0x3632, 0x32, 0, 0}, {0x3633, 0x52, 0, 0}, {0x3634, 0x70, 0, 0},
+ {0x3635, 0x13, 0, 0}, {0x3636, 0x03, 0, 0}, {0x3702, 0x6e, 0, 0},
+ {0x3703, 0x52, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3705, 0x33, 0, 0},
+ {0x3708, 0x66, 0, 0}, {0x3709, 0x12, 0, 0}, {0x370b, 0x61, 0, 0},
+ {0x370c, 0xc3, 0, 0}, {0x370f, 0x10, 0, 0}, {0x3715, 0x08, 0, 0},
+ {0x3717, 0x01, 0, 0}, {0x371b, 0x20, 0, 0}, {0x3731, 0x22, 0, 0},
+ {0x3739, 0x70, 0, 0}, {0x3901, 0x0a, 0, 0}, {0x3905, 0x02, 0, 0},
+ {0x3906, 0x10, 0, 0}, {0x3719, 0x86, 0, 0}, {0x3800, 0x00, 0, 0},
+ {0x3801, 0x00, 0, 0}, {0x3802, 0x00, 0, 0}, {0x3803, 0xfa, 0, 0},
+ {0x3804, 0x0a, 0, 0}, {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0},
+ {0x3807, 0xa9, 0, 0}, {0x3808, 0x05, 0, 0}, {0x3809, 0x00, 0, 0},
+ {0x380a, 0x02, 0, 0}, {0x380b, 0xd0, 0, 0}, {0x380c, 0x07, 0, 0},
+ {0x380d, 0x64, 0, 0}, {0x380e, 0x05, 0, 0}, {0x380f, 0xc8, 0, 0},
+ {0x3810, 0x00, 0, 0}, {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0},
+ {0x3813, 0x04, 0, 0}, {0x3814, 0x31, 0, 0}, {0x3815, 0x31, 0, 0},
+ {0x3820, 0x41, 0, 0}, {0x3821, 0x07, 0, 0}, {0x3824, 0x01, 0, 0},
+ {0x3826, 0x03, 0, 0}, {0x3828, 0x08, 0, 0}, {0x3a02, 0x02, 0, 0},
+ {0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0},
+ {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0},
+ {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0},
+ {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3c01, 0x34, 0, 0},
+ {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, {0x3c07, 0x07, 0, 0},
+ {0x3c09, 0xc2, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
+ {0x3c01, 0x34, 0, 0}, {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
+ {0x4005, 0x18, 0, 0}, {0x4050, 0x6e, 0, 0}, {0x4051, 0x8f, 0, 0},
+ {0x4300, 0x30, 0, 0}, {0x4514, 0x00, 0, 0}, {0x4520, 0xb0, 0, 0},
+ {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x4818, 0x01, 0, 0},
+ {0x481d, 0xf0, 0, 0}, {0x481f, 0x50, 0, 0}, {0x4823, 0x70, 0, 0},
+ {0x4831, 0x14, 0, 0}, {0x4837, 0x0b, 0, 0}, {0x5000, 0xa7, 0, 0},
+ {0x5001, 0x83, 0, 0}, {0x501d, 0x00, 0, 0}, {0x501f, 0x00, 0, 0},
+ {0x503d, 0x00, 0, 0}, {0x505c, 0x30, 0, 0}, {0x5181, 0x59, 0, 0},
+ {0x5183, 0x00, 0, 0}, {0x5191, 0xf0, 0, 0}, {0x5192, 0x03, 0, 0},
+ {0x5684, 0x10, 0, 0}, {0x5685, 0xa0, 0, 0}, {0x5686, 0x0c, 0, 0},
+ {0x5687, 0x78, 0, 0}, {0x5a00, 0x08, 0, 0}, {0x5a21, 0x00, 0, 0},
+ {0x5a24, 0x00, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0x00, 0, 0},
+ {0x5180, 0xff, 0, 0}, {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0},
+ {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0},
+ {0x5186, 0x13, 0, 0}, {0x5187, 0x13, 0, 0}, {0x5188, 0x12, 0, 0},
+ {0x5189, 0x8c, 0, 0}, {0x518a, 0x5b, 0, 0}, {0x518b, 0x89, 0, 0},
+ {0x518c, 0x94, 0, 0}, {0x518d, 0x40, 0, 0}, {0x518e, 0x2c, 0, 0},
+ {0x518f, 0x77, 0, 0}, {0x5190, 0x45, 0, 0}, {0x5191, 0xf8, 0, 0},
+ {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0},
+ {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0},
+ {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0},
+ {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0},
+ {0x519e, 0x38, 0, 0}, {0x5381, 0x32, 0, 0}, {0x5382, 0x6b, 0, 0},
+ {0x5383, 0x1d, 0, 0}, {0x5384, 0x2a, 0, 0}, {0x5385, 0x7f, 0, 0},
+ {0x5386, 0xa9, 0, 0}, {0x5387, 0xb2, 0, 0}, {0x5388, 0xb5, 0, 0},
+ {0x5389, 0x03, 0, 0}, {0x538a, 0x00, 0, 0}, {0x538b, 0x9c, 0, 0},
+ {0x5300, 0x08, 0, 0}, {0x5301, 0x1e, 0, 0}, {0x5302, 0x10, 0, 0},
+ {0x5303, 0x00, 0, 0}, {0x5304, 0x08, 0, 0}, {0x5305, 0x1e, 0, 0},
+ {0x5306, 0x08, 0, 0}, {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0},
+ {0x530a, 0x1e, 0, 0}, {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0},
+ {0x5480, 0x01, 0, 0}, {0x5481, 0x0e, 0, 0}, {0x5482, 0x18, 0, 0},
+ {0x5483, 0x2b, 0, 0}, {0x5484, 0x52, 0, 0}, {0x5485, 0x65, 0, 0},
+ {0x5486, 0x71, 0, 0}, {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0},
+ {0x5489, 0x91, 0, 0}, {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0},
+ {0x548c, 0xb8, 0, 0}, {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0},
+ {0x548f, 0xea, 0, 0}, {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0},
+ {0x5583, 0x40, 0, 0}, {0x5584, 0x30, 0, 0}, {0x5589, 0x10, 0, 0},
+ {0x558a, 0x00, 0, 0}, {0x558b, 0xf8, 0, 0}, {0x5780, 0xfc, 0, 0},
+ {0x5781, 0x13, 0, 0}, {0x5782, 0x03, 0, 0}, {0x5786, 0x20, 0, 0},
+ {0x5787, 0x40, 0, 0}, {0x5788, 0x08, 0, 0}, {0x5789, 0x08, 0, 0},
+ {0x578a, 0x02, 0, 0}, {0x578b, 0x01, 0, 0}, {0x578c, 0x01, 0, 0},
+ {0x578d, 0x0c, 0, 0}, {0x578e, 0x02, 0, 0}, {0x578f, 0x01, 0, 0},
+ {0x5790, 0x01, 0, 0}, {0x5800, 0x3f, 0, 0}, {0x5801, 0x17, 0, 0},
+ {0x5802, 0x12, 0, 0}, {0x5803, 0x11, 0, 0}, {0x5804, 0x16, 0, 0},
+ {0x5805, 0x31, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0},
+ {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x07, 0, 0},
+ {0x580b, 0x0b, 0, 0}, {0x580c, 0x0a, 0, 0}, {0x580d, 0x03, 0, 0},
+ {0x580e, 0x01, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x02, 0, 0},
+ {0x5811, 0x07, 0, 0}, {0x5812, 0x08, 0, 0}, {0x5813, 0x03, 0, 0},
+ {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x02, 0, 0},
+ {0x5817, 0x06, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x07, 0, 0},
+ {0x581a, 0x04, 0, 0}, {0x581b, 0x04, 0, 0}, {0x581c, 0x06, 0, 0},
+ {0x581d, 0x0b, 0, 0}, {0x581e, 0x2e, 0, 0}, {0x581f, 0x12, 0, 0},
+ {0x5820, 0x0e, 0, 0}, {0x5821, 0x0d, 0, 0}, {0x5822, 0x10, 0, 0},
+ {0x5823, 0x1d, 0, 0}, {0x5824, 0x48, 0, 0}, {0x5825, 0x4c, 0, 0},
+ {0x5826, 0x4e, 0, 0}, {0x5827, 0x2c, 0, 0}, {0x5828, 0x68, 0, 0},
+ {0x5829, 0x4c, 0, 0}, {0x582a, 0x48, 0, 0}, {0x582b, 0x26, 0, 0},
+ {0x582c, 0x46, 0, 0}, {0x582d, 0x2c, 0, 0}, {0x582e, 0x4a, 0, 0},
+ {0x582f, 0x22, 0, 0}, {0x5830, 0x20, 0, 0}, {0x5831, 0x22, 0, 0},
+ {0x5832, 0x48, 0, 0}, {0x5833, 0x4c, 0, 0}, {0x5834, 0x26, 0, 0},
+ {0x5835, 0x24, 0, 0}, {0x5836, 0x26, 0, 0}, {0x5837, 0x0c, 0, 0},
+ {0x5838, 0x48, 0, 0}, {0x5839, 0x2e, 0, 0}, {0x583a, 0x2e, 0, 0},
+ {0x583b, 0x2e, 0, 0}, {0x583c, 0x4a, 0, 0}, {0x583d, 0xee, 0, 0},
+ {0x5025, 0x00, 0, 0}, {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0},
+ {0x3a11, 0x70, 0, 0}, {0x3a1b, 0x38, 0, 0}, {0x3a1e, 0x30, 0, 0},
+ {0x3a1f, 0x18, 0, 0}, {0x3008, 0x02, 0, 0},
+};
+
+static struct reg_value ov5645_setting_30fps_1080P_1920_1080[] = {
+ {0x3103, 0x11, 0, 0},
+// Comment this out in order for AF to work
+// {0x3008, 0x82, 0, 0},
+ {0x3008, 0x42, 0, 0}, {0x3103, 0x03, 0, 0}, {0x3503, 0x07, 0, 0},
+ {0x3002, 0x1c, 0, 0}, {0x3006, 0xc3, 0, 0}, {0x300e, 0x45, 0, 0},
+ {0x3017, 0x40, 0, 0}, {0x3018, 0x00, 0, 0}, {0x302e, 0x0b, 0, 0},
+ {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3611, 0x06, 0, 0},
+ {0x3612, 0xab, 0, 0}, {0x3614, 0x50, 0, 0}, {0x3618, 0x04, 0, 0},
+ {0x3034, 0x18, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x54, 0, 0},
+ {0x3500, 0x00, 0, 0}, {0x3501, 0x01, 0, 0}, {0x3502, 0x00, 0, 0},
+ {0x350a, 0x00, 0, 0}, {0x350b, 0x3f, 0, 0}, {0x3600, 0x08, 0, 0},
+ {0x3601, 0x33, 0, 0}, {0x3620, 0x33, 0, 0}, {0x3621, 0xe0, 0, 0},
+ {0x3622, 0x01, 0, 0}, {0x3630, 0x2d, 0, 0}, {0x3631, 0x00, 0, 0},
+ {0x3632, 0x32, 0, 0}, {0x3633, 0x52, 0, 0}, {0x3634, 0x70, 0, 0},
+ {0x3635, 0x13, 0, 0}, {0x3636, 0x03, 0, 0}, {0x3702, 0x6e, 0, 0},
+ {0x3703, 0x52, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3705, 0x33, 0, 0},
+ {0x3708, 0x63, 0, 0}, {0x3709, 0x12, 0, 0}, {0x370b, 0x61, 0, 0},
+ {0x370c, 0xc0, 0, 0}, {0x370f, 0x10, 0, 0}, {0x3715, 0x08, 0, 0},
+ {0x3717, 0x01, 0, 0}, {0x371b, 0x20, 0, 0}, {0x3731, 0x22, 0, 0},
+ {0x3739, 0x70, 0, 0}, {0x3901, 0x0a, 0, 0}, {0x3905, 0x02, 0, 0},
+ {0x3906, 0x10, 0, 0}, {0x3719, 0x86, 0, 0}, {0x3800, 0x01, 0, 0},
+ {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0}, {0x3803, 0xb2, 0, 0},
+ {0x3804, 0x08, 0, 0}, {0x3805, 0xef, 0, 0}, {0x3806, 0x05, 0, 0},
+ {0x3807, 0xf1, 0, 0}, {0x3808, 0x07, 0, 0}, {0x3809, 0x80, 0, 0},
+ {0x380a, 0x04, 0, 0}, {0x380b, 0x38, 0, 0}, {0x380c, 0x09, 0, 0},
+ {0x380d, 0xc4, 0, 0}, {0x380e, 0x04, 0, 0}, {0x380f, 0x60, 0, 0},
+ {0x3810, 0x00, 0, 0}, {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0},
+ {0x3813, 0x04, 0, 0}, {0x3814, 0x11, 0, 0}, {0x3815, 0x11, 0, 0},
+ {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, {0x3824, 0x01, 0, 0},
+ {0x3826, 0x03, 0, 0}, {0x3828, 0x08, 0, 0}, {0x3a02, 0x04, 0, 0},
+ {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x50, 0, 0},
+ {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0}, {0x3a0e, 0x03, 0, 0},
+ {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0}, {0x3a15, 0x60, 0, 0},
+ {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3c01, 0x34, 0, 0},
+ {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, {0x3c07, 0x07, 0, 0},
+ {0x3c09, 0xc2, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
+ {0x3c01, 0x34, 0, 0}, {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0},
+ {0x4005, 0x18, 0, 0}, {0x4050, 0x6e, 0, 0}, {0x4051, 0x8f, 0, 0},
+ {0x4300, 0x30, 0, 0}, {0x4514, 0x00, 0, 0}, {0x4520, 0xb0, 0, 0},
+ {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x4818, 0x01, 0, 0},
+ {0x481d, 0xf0, 0, 0}, {0x481f, 0x50, 0, 0}, {0x4823, 0x70, 0, 0},
+ {0x4831, 0x14, 0, 0}, {0x4837, 0x0b, 0, 0}, {0x5000, 0xa7, 0, 0},
+ {0x5001, 0x83, 0, 0}, {0x501d, 0x00, 0, 0}, {0x501f, 0x00, 0, 0},
+ {0x503d, 0x00, 0, 0}, {0x505c, 0x30, 0, 0}, {0x5181, 0x59, 0, 0},
+ {0x5183, 0x00, 0, 0}, {0x5191, 0xf0, 0, 0}, {0x5192, 0x03, 0, 0},
+ {0x5684, 0x10, 0, 0}, {0x5685, 0xa0, 0, 0}, {0x5686, 0x0c, 0, 0},
+ {0x5687, 0x78, 0, 0}, {0x5a00, 0x08, 0, 0}, {0x5a21, 0x00, 0, 0},
+ {0x5a24, 0x00, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0x00, 0, 0},
+ {0x5180, 0xff, 0, 0}, {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0},
+ {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0},
+ {0x5186, 0x13, 0, 0}, {0x5187, 0x13, 0, 0}, {0x5188, 0x12, 0, 0},
+ {0x5189, 0x8c, 0, 0}, {0x518a, 0x5b, 0, 0}, {0x518b, 0x89, 0, 0},
+ {0x518c, 0x94, 0, 0}, {0x518d, 0x40, 0, 0}, {0x518e, 0x2c, 0, 0},
+ {0x518f, 0x77, 0, 0}, {0x5190, 0x45, 0, 0}, {0x5191, 0xf8, 0, 0},
+ {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0},
+ {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0},
+ {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0},
+ {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0},
+ {0x519e, 0x38, 0, 0}, {0x5381, 0x32, 0, 0}, {0x5382, 0x6b, 0, 0},
+ {0x5383, 0x1d, 0, 0}, {0x5384, 0x2a, 0, 0}, {0x5385, 0x7f, 0, 0},
+ {0x5386, 0xa9, 0, 0}, {0x5387, 0xb2, 0, 0}, {0x5388, 0xb5, 0, 0},
+ {0x5389, 0x03, 0, 0}, {0x538a, 0x00, 0, 0}, {0x538b, 0x9c, 0, 0},
+ {0x5300, 0x08, 0, 0}, {0x5301, 0x1e, 0, 0}, {0x5302, 0x10, 0, 0},
+ {0x5303, 0x00, 0, 0}, {0x5304, 0x08, 0, 0}, {0x5305, 0x1e, 0, 0},
+ {0x5306, 0x08, 0, 0}, {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0},
+ {0x530a, 0x1e, 0, 0}, {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0},
+ {0x5480, 0x01, 0, 0}, {0x5481, 0x0e, 0, 0}, {0x5482, 0x18, 0, 0},
+ {0x5483, 0x2b, 0, 0}, {0x5484, 0x52, 0, 0}, {0x5485, 0x65, 0, 0},
+ {0x5486, 0x71, 0, 0}, {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0},
+ {0x5489, 0x91, 0, 0}, {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0},
+ {0x548c, 0xb8, 0, 0}, {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0},
+ {0x548f, 0xea, 0, 0}, {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0},
+ {0x5583, 0x40, 0, 0}, {0x5584, 0x30, 0, 0}, {0x5589, 0x10, 0, 0},
+ {0x558a, 0x00, 0, 0}, {0x558b, 0xf8, 0, 0}, {0x5780, 0xfc, 0, 0},
+ {0x5781, 0x13, 0, 0}, {0x5782, 0x03, 0, 0}, {0x5786, 0x20, 0, 0},
+ {0x5787, 0x40, 0, 0}, {0x5788, 0x08, 0, 0}, {0x5789, 0x08, 0, 0},
+ {0x578a, 0x02, 0, 0}, {0x578b, 0x01, 0, 0}, {0x578c, 0x01, 0, 0},
+ {0x578d, 0x0c, 0, 0}, {0x578e, 0x02, 0, 0}, {0x578f, 0x01, 0, 0},
+ {0x5790, 0x01, 0, 0}, {0x5800, 0x3f, 0, 0}, {0x5801, 0x17, 0, 0},
+ {0x5802, 0x12, 0, 0}, {0x5803, 0x11, 0, 0}, {0x5804, 0x16, 0, 0},
+ {0x5805, 0x31, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0},
+ {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x07, 0, 0},
+ {0x580b, 0x0b, 0, 0}, {0x580c, 0x0a, 0, 0}, {0x580d, 0x03, 0, 0},
+ {0x580e, 0x01, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x02, 0, 0},
+ {0x5811, 0x07, 0, 0}, {0x5812, 0x08, 0, 0}, {0x5813, 0x03, 0, 0},
+ {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x02, 0, 0},
+ {0x5817, 0x06, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x07, 0, 0},
+ {0x581a, 0x04, 0, 0}, {0x581b, 0x04, 0, 0}, {0x581c, 0x06, 0, 0},
+ {0x581d, 0x0b, 0, 0}, {0x581e, 0x2e, 0, 0}, {0x581f, 0x12, 0, 0},
+ {0x5820, 0x0e, 0, 0}, {0x5821, 0x0d, 0, 0}, {0x5822, 0x10, 0, 0},
+ {0x5823, 0x1d, 0, 0}, {0x5824, 0x48, 0, 0}, {0x5825, 0x4c, 0, 0},
+ {0x5826, 0x4e, 0, 0}, {0x5827, 0x2c, 0, 0}, {0x5828, 0x68, 0, 0},
+ {0x5829, 0x4c, 0, 0}, {0x582a, 0x48, 0, 0}, {0x582b, 0x26, 0, 0},
+ {0x582c, 0x46, 0, 0}, {0x582d, 0x2c, 0, 0}, {0x582e, 0x4a, 0, 0},
+ {0x582f, 0x22, 0, 0}, {0x5830, 0x20, 0, 0}, {0x5831, 0x22, 0, 0},
+ {0x5832, 0x48, 0, 0}, {0x5833, 0x4c, 0, 0}, {0x5834, 0x26, 0, 0},
+ {0x5835, 0x24, 0, 0}, {0x5836, 0x26, 0, 0}, {0x5837, 0x0c, 0, 0},
+ {0x5838, 0x48, 0, 0}, {0x5839, 0x2e, 0, 0}, {0x583a, 0x2e, 0, 0},
+ {0x583b, 0x2e, 0, 0}, {0x583c, 0x4a, 0, 0}, {0x583d, 0xee, 0, 0},
+ {0x5025, 0x00, 0, 0}, {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0},
+ {0x3a11, 0x70, 0, 0}, {0x3a1b, 0x38, 0, 0}, {0x3a1e, 0x30, 0, 0},
+ {0x3a1f, 0x18, 0, 0}, {0x3008, 0x02, 0, 0},
+};
+
+static struct reg_value ov5645_setting_15fps_1080P_1920_1080[] = {
+ {0x3103, 0x11, 0, 0},
+// Comment this out in order for AF to work
+// {0x3008, 0x82, 0, 0},
+ {0x3008, 0x42, 0, 0}, {0x3103, 0x03, 0, 0}, {0x3503, 0x07, 0, 0},
+ {0x3002, 0x1c, 0, 0}, {0x3006, 0xc3, 0, 0}, {0x300e, 0x45, 0, 0},
+ {0x3017, 0x40, 0, 0}, {0x3018, 0x00, 0, 0}, {0x302e, 0x0b, 0, 0},
+ {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3611, 0x06, 0, 0},
+ {0x3612, 0xab, 0, 0}, {0x3614, 0x50, 0, 0}, {0x3618, 0x04, 0, 0},
+ {0x3034, 0x18, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x54, 0, 0},
+ {0x3500, 0x00, 0, 0}, {0x3501, 0x01, 0, 0}, {0x3502, 0x00, 0, 0},
+ {0x350a, 0x00, 0, 0}, {0x350b, 0x3f, 0, 0}, {0x3600, 0x08, 0, 0},
+ {0x3601, 0x33, 0, 0}, {0x3620, 0x33, 0, 0}, {0x3621, 0xe0, 0, 0},
+ {0x3622, 0x01, 0, 0}, {0x3630, 0x2d, 0, 0}, {0x3631, 0x00, 0, 0},
+ {0x3632, 0x32, 0, 0}, {0x3633, 0x52, 0, 0}, {0x3634, 0x70, 0, 0},
+ {0x3635, 0x13, 0, 0}, {0x3636, 0x03, 0, 0}, {0x3702, 0x6e, 0, 0},
+ {0x3703, 0x52, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3705, 0x33, 0, 0},
+ {0x3708, 0x63, 0, 0}, {0x3709, 0x12, 0, 0}, {0x370b, 0x61, 0, 0},
+ {0x370c, 0xc0, 0, 0}, {0x370f, 0x10, 0, 0}, {0x3715, 0x08, 0, 0},
+ {0x3717, 0x01, 0, 0}, {0x371b, 0x20, 0, 0}, {0x3731, 0x22, 0, 0},
+ {0x3739, 0x70, 0, 0}, {0x3901, 0x0a, 0, 0}, {0x3905, 0x02, 0, 0},
+ {0x3906, 0x10, 0, 0}, {0x3719, 0x86, 0, 0}, {0x3800, 0x00, 0, 0},
+ {0x3801, 0x00, 0, 0}, {0x3802, 0x00, 0, 0}, {0x3803, 0xf4, 0, 0},
+ {0x3804, 0x0a, 0, 0}, {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0},
+ {0x3807, 0xa7, 0, 0}, {0x3808, 0x07, 0, 0}, {0x3809, 0x80, 0, 0},
+ {0x380a, 0x04, 0, 0}, {0x380b, 0x38, 0, 0}, {0x380c, 0x0b, 0, 0},
+ {0x380d, 0x1c, 0, 0}, {0x380e, 0x07, 0, 0}, {0x380f, 0xb0, 0, 0},
+ {0x3810, 0x00, 0, 0}, {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0},
+ {0x3813, 0x06, 0, 0}, {0x3814, 0x11, 0, 0}, {0x3815, 0x11, 0, 0},
+ {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, {0x3824, 0x01, 0, 0},
+ {0x3826, 0x03, 0, 0}, {0x3828, 0x08, 0, 0}, {0x3a02, 0x07, 0, 0},
+ {0x3a03, 0xb0, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
+ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x06, 0, 0},
+ {0x3a0d, 0x08, 0, 0}, {0x3a14, 0x07, 0, 0}, {0x3a15, 0xb0, 0, 0},
+ {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3c01, 0x34, 0, 0},
+ {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, {0x3c07, 0x07, 0, 0},
+ {0x3c09, 0xc2, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
+ {0x3c01, 0x34, 0, 0}, {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0},
+ {0x4005, 0x18, 0, 0}, {0x4050, 0x6e, 0, 0}, {0x4051, 0x8f, 0, 0},
+ {0x4300, 0x30, 0, 0}, {0x4514, 0x00, 0, 0}, {0x4520, 0xb0, 0, 0},
+ {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x4818, 0x01, 0, 0},
+ {0x481d, 0xf0, 0, 0}, {0x481f, 0x50, 0, 0}, {0x4823, 0x70, 0, 0},
+ {0x4831, 0x14, 0, 0}, {0x4837, 0x0b, 0, 0}, {0x5000, 0xa7, 0, 0},
+ {0x5001, 0xa3, 0, 0}, {0x501d, 0x00, 0, 0}, {0x501f, 0x00, 0, 0},
+ {0x503d, 0x00, 0, 0}, {0x505c, 0x30, 0, 0}, {0x5181, 0x59, 0, 0},
+ {0x5183, 0x00, 0, 0}, {0x5191, 0xf0, 0, 0}, {0x5192, 0x03, 0, 0},
+ {0x5684, 0x10, 0, 0}, {0x5685, 0xa0, 0, 0}, {0x5686, 0x0c, 0, 0},
+ {0x5687, 0x78, 0, 0}, {0x5a00, 0x08, 0, 0}, {0x5a21, 0x00, 0, 0},
+ {0x5a24, 0x00, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0x00, 0, 0},
+ {0x5180, 0xff, 0, 0}, {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0},
+ {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0},
+ {0x5186, 0x09, 0, 0}, {0x5187, 0x09, 0, 0}, {0x5188, 0x0a, 0, 0},
+ {0x5189, 0x75, 0, 0}, {0x518a, 0x52, 0, 0}, {0x518b, 0xea, 0, 0},
+ {0x518c, 0xa8, 0, 0}, {0x518d, 0x42, 0, 0}, {0x518e, 0x38, 0, 0},
+ {0x518f, 0x56, 0, 0}, {0x5190, 0x42, 0, 0}, {0x5191, 0xf8, 0, 0},
+ {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0},
+ {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0},
+ {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0},
+ {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0},
+ {0x519e, 0x38, 0, 0}, {0x5381, 0x1e, 0, 0}, {0x5382, 0x5b, 0, 0},
+ {0x5383, 0x08, 0, 0}, {0x5384, 0x0b, 0, 0}, {0x5385, 0x84, 0, 0},
+ {0x5386, 0x8f, 0, 0}, {0x5387, 0x82, 0, 0}, {0x5388, 0x71, 0, 0},
+ {0x5389, 0x11, 0, 0}, {0x538a, 0x01, 0, 0}, {0x538b, 0x98, 0, 0},
+ {0x5300, 0x08, 0, 0}, {0x5301, 0x1e, 0, 0}, {0x5302, 0x10, 0, 0},
+ {0x5303, 0x00, 0, 0}, {0x5304, 0x08, 0, 0}, {0x5305, 0x1e, 0, 0},
+ {0x5306, 0x08, 0, 0}, {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0},
+ {0x530a, 0x1e, 0, 0}, {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0},
+ {0x5480, 0x01, 0, 0}, {0x5481, 0x0e, 0, 0}, {0x5482, 0x18, 0, 0},
+ {0x5483, 0x2b, 0, 0}, {0x5484, 0x52, 0, 0}, {0x5485, 0x65, 0, 0},
+ {0x5486, 0x71, 0, 0}, {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0},
+ {0x5489, 0x91, 0, 0}, {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0},
+ {0x548c, 0xb8, 0, 0}, {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0},
+ {0x548f, 0xea, 0, 0}, {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0},
+ {0x5583, 0x40, 0, 0}, {0x5584, 0x30, 0, 0}, {0x5589, 0x10, 0, 0},
+ {0x558a, 0x00, 0, 0}, {0x558b, 0xf8, 0, 0}, {0x5780, 0xfc, 0, 0},
+ {0x5781, 0x13, 0, 0}, {0x5782, 0x03, 0, 0}, {0x5786, 0x20, 0, 0},
+ {0x5787, 0x40, 0, 0}, {0x5788, 0x08, 0, 0}, {0x5789, 0x08, 0, 0},
+ {0x578a, 0x02, 0, 0}, {0x578b, 0x01, 0, 0}, {0x578c, 0x01, 0, 0},
+ {0x578d, 0x0c, 0, 0}, {0x578e, 0x02, 0, 0}, {0x578f, 0x01, 0, 0},
+ {0x5790, 0x01, 0, 0}, {0x5800, 0x3f, 0, 0}, {0x5801, 0x16, 0, 0},
+ {0x5802, 0x0e, 0, 0}, {0x5803, 0x0d, 0, 0}, {0x5804, 0x17, 0, 0},
+ {0x5805, 0x3f, 0, 0}, {0x5806, 0x0b, 0, 0}, {0x5807, 0x06, 0, 0},
+ {0x5808, 0x04, 0, 0}, {0x5809, 0x04, 0, 0}, {0x580a, 0x06, 0, 0},
+ {0x580b, 0x0b, 0, 0}, {0x580c, 0x09, 0, 0}, {0x580d, 0x03, 0, 0},
+ {0x580e, 0x00, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x03, 0, 0},
+ {0x5811, 0x08, 0, 0}, {0x5812, 0x0a, 0, 0}, {0x5813, 0x03, 0, 0},
+ {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x04, 0, 0},
+ {0x5817, 0x09, 0, 0}, {0x5818, 0x0f, 0, 0}, {0x5819, 0x08, 0, 0},
+ {0x581a, 0x06, 0, 0}, {0x581b, 0x06, 0, 0}, {0x581c, 0x08, 0, 0},
+ {0x581d, 0x0c, 0, 0}, {0x581e, 0x3f, 0, 0}, {0x581f, 0x1e, 0, 0},
+ {0x5820, 0x12, 0, 0}, {0x5821, 0x13, 0, 0}, {0x5822, 0x21, 0, 0},
+ {0x5823, 0x3f, 0, 0}, {0x5824, 0x68, 0, 0}, {0x5825, 0x28, 0, 0},
+ {0x5826, 0x2c, 0, 0}, {0x5827, 0x28, 0, 0}, {0x5828, 0x08, 0, 0},
+ {0x5829, 0x48, 0, 0}, {0x582a, 0x64, 0, 0}, {0x582b, 0x62, 0, 0},
+ {0x582c, 0x64, 0, 0}, {0x582d, 0x28, 0, 0}, {0x582e, 0x46, 0, 0},
+ {0x582f, 0x62, 0, 0}, {0x5830, 0x60, 0, 0}, {0x5831, 0x62, 0, 0},
+ {0x5832, 0x26, 0, 0}, {0x5833, 0x48, 0, 0}, {0x5834, 0x66, 0, 0},
+ {0x5835, 0x44, 0, 0}, {0x5836, 0x64, 0, 0}, {0x5837, 0x28, 0, 0},
+ {0x5838, 0x66, 0, 0}, {0x5839, 0x48, 0, 0}, {0x583a, 0x2c, 0, 0},
+ {0x583b, 0x28, 0, 0}, {0x583c, 0x26, 0, 0}, {0x583d, 0xae, 0, 0},
+ {0x5025, 0x00, 0, 0}, {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0},
+ {0x3a11, 0x70, 0, 0}, {0x3a1b, 0x38, 0, 0}, {0x3a1e, 0x30, 0, 0},
+ {0x3a1f, 0x18, 0, 0}, {0x3008, 0x02, 0, 0},
+};
+
+static struct reg_value ov5645_setting_15fps_QSXGA_2592_1944[] = {
+ {0x3103, 0x11, 0, 0},
+// Comment this out in order for AF to work
+// {0x3008, 0x82, 0, 0},
+ {0x3008, 0x42, 0, 0}, {0x3103, 0x03, 0, 0}, {0x3503, 0x07, 0, 0},
+ {0x3002, 0x1c, 0, 0}, {0x3006, 0xc3, 0, 0}, {0x300e, 0x45, 0, 0},
+ {0x3017, 0x40, 0, 0}, {0x3018, 0x00, 0, 0}, {0x302e, 0x0b, 0, 0},
+ {0x3037, 0x13, 0, 0}, {0x3108, 0x01, 0, 0}, {0x3611, 0x06, 0, 0},
+ {0x3612, 0xab, 0, 0}, {0x3614, 0x50, 0, 0}, {0x3618, 0x04, 0, 0},
+ {0x3034, 0x18, 0, 0}, {0x3035, 0x11, 0, 0}, {0x3036, 0x54, 0, 0},
+ {0x3500, 0x00, 0, 0}, {0x3501, 0x01, 0, 0}, {0x3502, 0x00, 0, 0},
+ {0x350a, 0x00, 0, 0}, {0x350b, 0x3f, 0, 0}, {0x3600, 0x08, 0, 0},
+ {0x3601, 0x33, 0, 0}, {0x3620, 0x33, 0, 0}, {0x3621, 0xe0, 0, 0},
+ {0x3622, 0x01, 0, 0}, {0x3630, 0x2d, 0, 0}, {0x3631, 0x00, 0, 0},
+ {0x3632, 0x32, 0, 0}, {0x3633, 0x52, 0, 0}, {0x3634, 0x70, 0, 0},
+ {0x3635, 0x13, 0, 0}, {0x3636, 0x03, 0, 0}, {0x3702, 0x6e, 0, 0},
+ {0x3703, 0x52, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3705, 0x33, 0, 0},
+ {0x3708, 0x63, 0, 0}, {0x3709, 0x12, 0, 0}, {0x370b, 0x61, 0, 0},
+ {0x370c, 0xc0, 0, 0}, {0x370f, 0x10, 0, 0}, {0x3715, 0x08, 0, 0},
+ {0x3717, 0x01, 0, 0}, {0x371b, 0x20, 0, 0}, {0x3731, 0x22, 0, 0},
+ {0x3739, 0x70, 0, 0}, {0x3901, 0x0a, 0, 0}, {0x3905, 0x02, 0, 0},
+ {0x3906, 0x10, 0, 0}, {0x3719, 0x86, 0, 0}, {0x3800, 0x00, 0, 0},
+ {0x3801, 0x00, 0, 0}, {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0},
+ {0x3804, 0x0a, 0, 0}, {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0},
+ {0x3807, 0x9f, 0, 0}, {0x3808, 0x0a, 0, 0}, {0x3809, 0x20, 0, 0},
+ {0x380a, 0x07, 0, 0}, {0x380b, 0x98, 0, 0}, {0x380c, 0x0b, 0, 0},
+ {0x380d, 0x1c, 0, 0}, {0x380e, 0x07, 0, 0}, {0x380f, 0xb0, 0, 0},
+ {0x3810, 0x00, 0, 0}, {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0},
+ {0x3813, 0x06, 0, 0}, {0x3814, 0x11, 0, 0}, {0x3815, 0x11, 0, 0},
+ {0x3820, 0x40, 0, 0}, {0x3821, 0x06, 0, 0}, {0x3824, 0x01, 0, 0},
+ {0x3826, 0x03, 0, 0}, {0x3828, 0x08, 0, 0}, {0x3a02, 0x07, 0, 0},
+ {0x3a03, 0xb0, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
+ {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x06, 0, 0},
+ {0x3a0d, 0x08, 0, 0}, {0x3a14, 0x07, 0, 0}, {0x3a15, 0xb0, 0, 0},
+ {0x3a18, 0x00, 0, 0}, {0x3a19, 0xf8, 0, 0}, {0x3c01, 0x34, 0, 0},
+ {0x3c04, 0x28, 0, 0}, {0x3c05, 0x98, 0, 0}, {0x3c07, 0x07, 0, 0},
+ {0x3c09, 0xc2, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
+ {0x3c01, 0x34, 0, 0}, {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0},
+ {0x4005, 0x18, 0, 0}, {0x4050, 0x6e, 0, 0}, {0x4051, 0x8f, 0, 0},
+ {0x4300, 0x30, 0, 0}, {0x4514, 0x00, 0, 0}, {0x4520, 0xb0, 0, 0},
+ {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x4818, 0x01, 0, 0},
+ {0x481d, 0xf0, 0, 0}, {0x481f, 0x50, 0, 0}, {0x4823, 0x70, 0, 0},
+ {0x4831, 0x14, 0, 0}, {0x4837, 0x0b, 0, 0}, {0x5000, 0xa7, 0, 0},
+ {0x5001, 0x83, 0, 0}, {0x501d, 0x00, 0, 0}, {0x501f, 0x00, 0, 0},
+ {0x503d, 0x00, 0, 0}, {0x505c, 0x30, 0, 0}, {0x5181, 0x59, 0, 0},
+ {0x5183, 0x00, 0, 0}, {0x5191, 0xf0, 0, 0}, {0x5192, 0x03, 0, 0},
+ {0x5684, 0x10, 0, 0}, {0x5685, 0xa0, 0, 0}, {0x5686, 0x0c, 0, 0},
+ {0x5687, 0x78, 0, 0}, {0x5a00, 0x08, 0, 0}, {0x5a21, 0x00, 0, 0},
+ {0x5a24, 0x00, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0x00, 0, 0},
+ {0x5180, 0xff, 0, 0}, {0x5181, 0xf2, 0, 0}, {0x5182, 0x00, 0, 0},
+ {0x5183, 0x14, 0, 0}, {0x5184, 0x25, 0, 0}, {0x5185, 0x24, 0, 0},
+ {0x5186, 0x13, 0, 0}, {0x5187, 0x13, 0, 0}, {0x5188, 0x12, 0, 0},
+ {0x5189, 0x8c, 0, 0}, {0x518a, 0x5b, 0, 0}, {0x518b, 0x89, 0, 0},
+ {0x518c, 0x94, 0, 0}, {0x518d, 0x40, 0, 0}, {0x518e, 0x2c, 0, 0},
+ {0x518f, 0x77, 0, 0}, {0x5190, 0x45, 0, 0}, {0x5191, 0xf8, 0, 0},
+ {0x5192, 0x04, 0, 0}, {0x5193, 0x70, 0, 0}, {0x5194, 0xf0, 0, 0},
+ {0x5195, 0xf0, 0, 0}, {0x5196, 0x03, 0, 0}, {0x5197, 0x01, 0, 0},
+ {0x5198, 0x04, 0, 0}, {0x5199, 0x12, 0, 0}, {0x519a, 0x04, 0, 0},
+ {0x519b, 0x00, 0, 0}, {0x519c, 0x06, 0, 0}, {0x519d, 0x82, 0, 0},
+ {0x519e, 0x38, 0, 0}, {0x5381, 0x32, 0, 0}, {0x5382, 0x6b, 0, 0},
+ {0x5383, 0x1d, 0, 0}, {0x5384, 0x2a, 0, 0}, {0x5385, 0x7f, 0, 0},
+ {0x5386, 0xa9, 0, 0}, {0x5387, 0xb2, 0, 0}, {0x5388, 0xb5, 0, 0},
+ {0x5389, 0x03, 0, 0}, {0x538a, 0x00, 0, 0}, {0x538b, 0x9c, 0, 0},
+ {0x5300, 0x08, 0, 0}, {0x5301, 0x1e, 0, 0}, {0x5302, 0x10, 0, 0},
+ {0x5303, 0x00, 0, 0}, {0x5304, 0x08, 0, 0}, {0x5305, 0x1e, 0, 0},
+ {0x5306, 0x08, 0, 0}, {0x5307, 0x16, 0, 0}, {0x5309, 0x08, 0, 0},
+ {0x530a, 0x1e, 0, 0}, {0x530b, 0x04, 0, 0}, {0x530c, 0x06, 0, 0},
+ {0x5480, 0x01, 0, 0}, {0x5481, 0x0e, 0, 0}, {0x5482, 0x18, 0, 0},
+ {0x5483, 0x2b, 0, 0}, {0x5484, 0x52, 0, 0}, {0x5485, 0x65, 0, 0},
+ {0x5486, 0x71, 0, 0}, {0x5487, 0x7d, 0, 0}, {0x5488, 0x87, 0, 0},
+ {0x5489, 0x91, 0, 0}, {0x548a, 0x9a, 0, 0}, {0x548b, 0xaa, 0, 0},
+ {0x548c, 0xb8, 0, 0}, {0x548d, 0xcd, 0, 0}, {0x548e, 0xdd, 0, 0},
+ {0x548f, 0xea, 0, 0}, {0x5490, 0x1d, 0, 0}, {0x5580, 0x02, 0, 0},
+ {0x5583, 0x40, 0, 0}, {0x5584, 0x30, 0, 0}, {0x5589, 0x10, 0, 0},
+ {0x558a, 0x00, 0, 0}, {0x558b, 0xf8, 0, 0}, {0x5780, 0xfc, 0, 0},
+ {0x5781, 0x13, 0, 0}, {0x5782, 0x03, 0, 0}, {0x5786, 0x20, 0, 0},
+ {0x5787, 0x40, 0, 0}, {0x5788, 0x08, 0, 0}, {0x5789, 0x08, 0, 0},
+ {0x578a, 0x02, 0, 0}, {0x578b, 0x01, 0, 0}, {0x578c, 0x01, 0, 0},
+ {0x578d, 0x0c, 0, 0}, {0x578e, 0x02, 0, 0}, {0x578f, 0x01, 0, 0},
+ {0x5790, 0x01, 0, 0}, {0x5800, 0x3f, 0, 0}, {0x5801, 0x17, 0, 0},
+ {0x5802, 0x12, 0, 0}, {0x5803, 0x11, 0, 0}, {0x5804, 0x16, 0, 0},
+ {0x5805, 0x31, 0, 0}, {0x5806, 0x0c, 0, 0}, {0x5807, 0x08, 0, 0},
+ {0x5808, 0x05, 0, 0}, {0x5809, 0x05, 0, 0}, {0x580a, 0x07, 0, 0},
+ {0x580b, 0x0b, 0, 0}, {0x580c, 0x0a, 0, 0}, {0x580d, 0x03, 0, 0},
+ {0x580e, 0x01, 0, 0}, {0x580f, 0x00, 0, 0}, {0x5810, 0x02, 0, 0},
+ {0x5811, 0x07, 0, 0}, {0x5812, 0x08, 0, 0}, {0x5813, 0x03, 0, 0},
+ {0x5814, 0x00, 0, 0}, {0x5815, 0x00, 0, 0}, {0x5816, 0x02, 0, 0},
+ {0x5817, 0x06, 0, 0}, {0x5818, 0x0d, 0, 0}, {0x5819, 0x07, 0, 0},
+ {0x581a, 0x04, 0, 0}, {0x581b, 0x04, 0, 0}, {0x581c, 0x06, 0, 0},
+ {0x581d, 0x0b, 0, 0}, {0x581e, 0x2e, 0, 0}, {0x581f, 0x12, 0, 0},
+ {0x5820, 0x0e, 0, 0}, {0x5821, 0x0d, 0, 0}, {0x5822, 0x10, 0, 0},
+ {0x5823, 0x1d, 0, 0}, {0x5824, 0x48, 0, 0}, {0x5825, 0x4c, 0, 0},
+ {0x5826, 0x4e, 0, 0}, {0x5827, 0x2c, 0, 0}, {0x5828, 0x68, 0, 0},
+ {0x5829, 0x4c, 0, 0}, {0x582a, 0x48, 0, 0}, {0x582b, 0x26, 0, 0},
+ {0x582c, 0x46, 0, 0}, {0x582d, 0x2c, 0, 0}, {0x582e, 0x4a, 0, 0},
+ {0x582f, 0x22, 0, 0}, {0x5830, 0x20, 0, 0}, {0x5831, 0x22, 0, 0},
+ {0x5832, 0x48, 0, 0}, {0x5833, 0x4c, 0, 0}, {0x5834, 0x26, 0, 0},
+ {0x5835, 0x24, 0, 0}, {0x5836, 0x26, 0, 0}, {0x5837, 0x0c, 0, 0},
+ {0x5838, 0x48, 0, 0}, {0x5839, 0x2e, 0, 0}, {0x583a, 0x2e, 0, 0},
+ {0x583b, 0x2e, 0, 0}, {0x583c, 0x4a, 0, 0}, {0x583d, 0xee, 0, 0},
+ {0x5025, 0x00, 0, 0}, {0x3a0f, 0x38, 0, 0}, {0x3a10, 0x30, 0, 0},
+ {0x3a11, 0x70, 0, 0}, {0x3a1b, 0x38, 0, 0}, {0x3a1e, 0x30, 0, 0},
+ {0x3a1f, 0x18, 0, 0}, {0x3008, 0x02, 0, 0},
+};
+
+static struct ov5645_mode_info ov5645_mode_info_data[2][ov5645_mode_MAX + 1] = {
+ {
+ {ov5645_mode_VGA_640_480, -1, 0, 0, NULL, 0},
+ {ov5645_mode_NTSC_720_480, -1, 0, 0, NULL, 0},
+ {ov5645_mode_720P_1280_720, -1, 0, 0, NULL, 0},
+ {ov5645_mode_1080P_1920_1080, SUBSAMPLING, 1920, 1080,
+ ov5645_setting_15fps_1080P_1920_1080,
+ ARRAY_SIZE(ov5645_setting_15fps_1080P_1920_1080)},
+ {ov5645_mode_QSXGA_2592_1944, SCALING, 2592, 1944,
+ ov5645_setting_15fps_QSXGA_2592_1944,
+ ARRAY_SIZE(ov5645_setting_15fps_QSXGA_2592_1944)},
+ },
+ {
+ {ov5645_mode_VGA_640_480, SUBSAMPLING, 640, 480,
+ ov5645_setting_30fps_VGA_640_480,
+ ARRAY_SIZE(ov5645_setting_30fps_VGA_640_480)},
+ {ov5645_mode_NTSC_720_480, SUBSAMPLING, 720, 480,
+ ov5645_setting_30fps_NTSC_720_480,
+ ARRAY_SIZE(ov5645_setting_30fps_NTSC_720_480)},
+ {ov5645_mode_720P_1280_720, SUBSAMPLING, 1280, 720,
+ ov5645_setting_30fps_720P_1280_720,
+ ARRAY_SIZE(ov5645_setting_30fps_720P_1280_720)},
+ {ov5645_mode_1080P_1920_1080, SCALING, 1920, 1080,
+ ov5645_setting_30fps_1080P_1920_1080,
+ ARRAY_SIZE(ov5645_setting_30fps_1080P_1920_1080)},
+ {ov5645_mode_QSXGA_2592_1944, -1, 0, 0, NULL, 0},
+ },
+};
+
+static struct regulator *io_regulator;
+static struct regulator *core_regulator;
+static struct regulator *analog_regulator;
+static struct regulator *gpo_regulator;
+
+static int ov5645_probe(struct i2c_client *adapter,
+ const struct i2c_device_id *device_id);
+static int ov5645_remove(struct i2c_client *client);
+
+static s32 ov5645_read_reg(u16 reg, u8 *val);
+static s32 ov5645_write_reg(u16 reg, u8 val);
+
+static const struct i2c_device_id ov5645_id[] = {
+ {"ov5645_mipi", 0},
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, ov5645_id);
+
+static struct i2c_driver ov5645_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "ov5645_mipi",
+ },
+ .probe = ov5645_probe,
+ .remove = ov5645_remove,
+ .id_table = ov5645_id,
+};
+
+static const struct ov5645_datafmt ov5645_colour_fmts[] = {
+ {MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_JPEG},
+};
+
+static int get_capturemode(int width, int height)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ov5645_valid_res); i++) {
+ if ((ov5645_valid_res[i].width == width) &&
+ (ov5645_valid_res[i].height == height))
+ return i;
+ }
+
+ return -1;
+}
+
+static struct ov5645 *to_ov5645(const struct i2c_client *client)
+{
+ return container_of(i2c_get_clientdata(client), struct ov5645, subdev);
+}
+
+/* Find a data format by a pixel code in an array */
+static const struct ov5645_datafmt
+ *ov5645_find_datafmt(u32 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(ov5645_colour_fmts); i++)
+ if (ov5645_colour_fmts[i].code == code)
+ return ov5645_colour_fmts + i;
+
+ return NULL;
+}
+
+static inline void ov5645_power_down(int enable)
+{
+ if (pwn_gpio < 0)
+ return;
+
+ if (enable)
+ gpio_set_value_cansleep(pwn_gpio, 0);
+ else
+ gpio_set_value_cansleep(pwn_gpio, 1);
+
+ msleep(2);
+}
+
+static void ov5645_reset(void)
+{
+ if (rst_gpio < 0 || pwn_gpio < 0)
+ return;
+
+ /* camera reset */
+ gpio_set_value(rst_gpio, 1);
+
+ /* camera power dowmn */
+ gpio_set_value(pwn_gpio, 0);
+ msleep(5);
+
+ gpio_set_value(pwn_gpio, 1);
+ msleep(5);
+
+ gpio_set_value(rst_gpio, 0);
+ msleep(1);
+
+ gpio_set_value(rst_gpio, 1);
+ msleep(5);
+}
+
+static int ov5645_regulator_enable(struct device *dev)
+{
+ int ret = 0;
+
+ io_regulator = devm_regulator_get(dev, "DOVDD");
+ if (!IS_ERR(io_regulator)) {
+ regulator_set_voltage(io_regulator,
+ OV5645_VOLTAGE_DIGITAL_IO,
+ OV5645_VOLTAGE_DIGITAL_IO);
+ ret = regulator_enable(io_regulator);
+ if (ret) {
+ pr_err("%s:io set voltage error\n", __func__);
+ return ret;
+ } else {
+ dev_dbg(dev,
+ "%s:io set voltage ok\n", __func__);
+ }
+ } else {
+ pr_err("%s: cannot get io voltage error\n", __func__);
+ io_regulator = NULL;
+ }
+
+ core_regulator = devm_regulator_get(dev, "DVDD");
+ if (!IS_ERR(core_regulator)) {
+ regulator_set_voltage(core_regulator,
+ OV5645_VOLTAGE_DIGITAL_CORE,
+ OV5645_VOLTAGE_DIGITAL_CORE);
+ ret = regulator_enable(core_regulator);
+ if (ret) {
+ pr_err("%s:core set voltage error\n", __func__);
+ return ret;
+ } else {
+ dev_dbg(dev,
+ "%s:core set voltage ok\n", __func__);
+ }
+ } else {
+ core_regulator = NULL;
+ pr_err("%s: cannot get core voltage error\n", __func__);
+ }
+
+ analog_regulator = devm_regulator_get(dev, "AVDD");
+ if (!IS_ERR(analog_regulator)) {
+ regulator_set_voltage(analog_regulator,
+ OV5645_VOLTAGE_ANALOG,
+ OV5645_VOLTAGE_ANALOG);
+ ret = regulator_enable(analog_regulator);
+ if (ret) {
+ pr_err("%s:analog set voltage error\n",
+ __func__);
+ return ret;
+ } else {
+ dev_dbg(dev,
+ "%s:analog set voltage ok\n", __func__);
+ }
+ } else {
+ analog_regulator = NULL;
+ pr_err("%s: cannot get analog voltage error\n", __func__);
+ }
+
+ return ret;
+}
+
+static s32 ov5645_write_reg(u16 reg, u8 val)
+{
+ u8 au8Buf[3] = {0};
+
+ au8Buf[0] = reg >> 8;
+ au8Buf[1] = reg & 0xff;
+ au8Buf[2] = val;
+
+ if (i2c_master_send(ov5645_data.i2c_client, au8Buf, 3) < 0) {
+ pr_err("%s:write reg error:reg=%x,val=%x, retrying once\n",
+ __func__, reg, val);
+
+ msleep(1);
+
+ if (i2c_master_send(ov5645_data.i2c_client, au8Buf, 3) < 0) {
+ pr_err("%s:write reg error:reg=%x,val=%x, failed again, returning\n",
+ __func__, reg, val);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static s32 ov5645_read_reg(u16 reg, u8 *val)
+{
+ u8 au8RegBuf[2] = {0};
+ u8 u8RdVal = 0;
+
+ au8RegBuf[0] = reg >> 8;
+ au8RegBuf[1] = reg & 0xff;
+
+ if (2 != i2c_master_send(ov5645_data.i2c_client, au8RegBuf, 2)) {
+ pr_err("%s:write reg error:reg=%x\n",
+ __func__, reg);
+ return -1;
+ }
+
+ if (1 != i2c_master_recv(ov5645_data.i2c_client, &u8RdVal, 1)) {
+ pr_err("%s:read reg error:reg=%x,val=%x\n",
+ __func__, reg, u8RdVal);
+ return -1;
+ }
+
+ *val = u8RdVal;
+
+ return u8RdVal;
+}
+
+static void ov5645_release_af(void)
+{
+ ov5645_write_reg(0x3022, 0x08);
+}
+
+static void ov5645_enable_cont_af(void)
+{
+ ov5645_release_af();
+ ov5645_write_reg(0x3022, 0x12);
+ ov5645_write_reg(0x3022, 0x04);
+}
+
+static void ov5645_lock_af(void)
+{
+ u8 temp;
+ int cnt = 40;
+ /* At full 5M resolution it typically takes 1600ms for AF to stabilize.
+ * Setting the retry count to 20 gives us enough margin to always succeed */
+ ov5645_release_af();
+ ov5645_write_reg(0x3022, 0x12);
+ ov5645_write_reg(0x3022, 0x03);
+ do {
+ ov5645_read_reg(0x3029, &temp);
+ msleep(50);
+ pr_debug("AF reg 0x3029: 0x%02x\n", temp);
+ // If the status reg 0x3029 reads 0x10 we have stabilized
+ } while (temp != 0x10 && cnt-- > 0);
+
+ if (temp != 0x10)
+ pr_warning("%s: Failed to enable AF\n", __func__);
+}
+
+static int prev_sysclk, prev_HTS;
+static int AE_low, AE_high, AE_Target = 52;
+
+static void OV5645_stream_on(void)
+{
+ ov5645_write_reg(0x3008, 0x02);
+ ov5645_write_reg(0x4202, 0x00);
+ ov5645_enable_cont_af();
+}
+
+static void OV5645_stream_off(void)
+{
+ ov5645_release_af();
+ ov5645_write_reg(0x4202, 0x0f);
+ ov5645_write_reg(0x3008, 0x42);
+}
+
+static int OV5645_get_sysclk(void)
+{
+ /* calculate sysclk */
+ int xvclk = ov5645_data.mclk / 10000;
+ int temp1, temp2;
+ int Multiplier, PreDiv, VCO, SysDiv, Pll_rdiv;
+ int Bit_div2x = 1, sclk_rdiv, sysclk;
+ u8 temp;
+
+ int sclk_rdiv_map[] = {1, 2, 4, 8};
+
+ temp1 = ov5645_read_reg(0x3034, &temp);
+ temp2 = temp1 & 0x0f;
+ if (temp2 == 8 || temp2 == 10)
+ Bit_div2x = temp2 / 2;
+
+ temp1 = ov5645_read_reg(0x3035, &temp);
+ SysDiv = temp1>>4;
+ if (SysDiv == 0)
+ SysDiv = 16;
+
+ temp1 = ov5645_read_reg(0x3036, &temp);
+ Multiplier = temp1;
+
+ temp1 = ov5645_read_reg(0x3037, &temp);
+ PreDiv = temp1 & 0x0f;
+ Pll_rdiv = ((temp1 >> 4) & 0x01) + 1;
+
+ temp1 = ov5645_read_reg(0x3108, &temp);
+ temp2 = temp1 & 0x03;
+ sclk_rdiv = sclk_rdiv_map[temp2];
+
+ VCO = xvclk * Multiplier / PreDiv;
+
+ sysclk = VCO / SysDiv / Pll_rdiv * 2 / Bit_div2x / sclk_rdiv;
+
+ return sysclk;
+}
+
+static void OV5645_set_night_mode(void)
+{
+ /* read HTS from register settings */
+ u8 mode;
+
+ ov5645_read_reg(0x3a00, &mode);
+ mode &= 0xfb;
+ ov5645_write_reg(0x3a00, mode);
+}
+
+static int OV5645_get_HTS(void)
+{
+ /* read HTS from register settings */
+ int HTS;
+ u8 temp;
+
+ HTS = ov5645_read_reg(0x380c, &temp);
+ HTS = (HTS<<8) + ov5645_read_reg(0x380d, &temp);
+
+ return HTS;
+}
+
+static int OV5645_get_VTS(void)
+{
+ /* read VTS from register settings */
+ int VTS;
+ u8 temp;
+
+ /* total vertical size[15:8] high byte */
+ VTS = ov5645_read_reg(0x380e, &temp);
+
+ VTS = (VTS<<8) + ov5645_read_reg(0x380f, &temp);
+
+ return VTS;
+}
+
+static int OV5645_set_VTS(int VTS)
+{
+ /* write VTS to registers */
+ int temp;
+
+ temp = VTS & 0xff;
+ ov5645_write_reg(0x380f, temp);
+
+ temp = VTS>>8;
+ ov5645_write_reg(0x380e, temp);
+
+ return 0;
+}
+
+static int OV5645_get_shutter(void)
+{
+ /* read shutter, in number of line period */
+ int shutter;
+ u8 temp;
+
+ shutter = (ov5645_read_reg(0x03500, &temp) & 0x0f);
+ shutter = (shutter<<8) + ov5645_read_reg(0x3501, &temp);
+ shutter = (shutter<<4) + (ov5645_read_reg(0x3502, &temp)>>4);
+
+ return shutter;
+}
+
+static int OV5645_set_shutter(int shutter)
+{
+ /* write shutter, in number of line period */
+ int temp;
+
+ shutter = shutter & 0xffff;
+
+ temp = shutter & 0x0f;
+ temp = temp<<4;
+ ov5645_write_reg(0x3502, temp);
+
+ temp = shutter & 0xfff;
+ temp = temp>>4;
+ ov5645_write_reg(0x3501, temp);
+
+ temp = shutter>>12;
+ ov5645_write_reg(0x3500, temp);
+
+ return 0;
+}
+
+static int OV5645_get_gain16(void)
+{
+ /* read gain, 16 = 1x */
+ int gain16;
+ u8 temp;
+
+ gain16 = ov5645_read_reg(0x350a, &temp) & 0x03;
+ gain16 = (gain16<<8) + ov5645_read_reg(0x350b, &temp);
+
+ return gain16;
+}
+
+static int OV5645_set_gain16(int gain16)
+{
+ /* write gain, 16 = 1x */
+ u8 temp;
+ gain16 = gain16 & 0x3ff;
+
+ temp = gain16 & 0xff;
+ ov5645_write_reg(0x350b, temp);
+
+ temp = gain16>>8;
+ ov5645_write_reg(0x350a, temp);
+
+ return 0;
+}
+
+static int OV5645_get_light_freq(void)
+{
+ /* get banding filter value */
+ int temp, temp1, light_freq = 0;
+ u8 tmp;
+
+ temp = ov5645_read_reg(0x3c01, &tmp);
+
+ if (temp & 0x80) {
+ /* manual */
+ temp1 = ov5645_read_reg(0x3c00, &tmp);
+ if (temp1 & 0x04) {
+ /* 50Hz */
+ light_freq = 50;
+ } else {
+ /* 60Hz */
+ light_freq = 60;
+ }
+ } else {
+ /* auto */
+ temp1 = ov5645_read_reg(0x3c0c, &tmp);
+ if (temp1 & 0x01) {
+ /* 50Hz */
+ light_freq = 50;
+ } else {
+ /* 60Hz */
+ }
+ }
+ return light_freq;
+}
+
+static void OV5645_set_bandingfilter(void)
+{
+ int prev_VTS;
+ int band_step60, max_band60, band_step50, max_band50;
+
+ /* read preview PCLK */
+ prev_sysclk = OV5645_get_sysclk();
+ /* read preview HTS */
+ prev_HTS = OV5645_get_HTS();
+
+ /* read preview VTS */
+ prev_VTS = OV5645_get_VTS();
+
+ /* calculate banding filter */
+ /* 60Hz */
+ band_step60 = prev_sysclk * 100/prev_HTS * 100/120;
+ ov5645_write_reg(0x3a0a, (band_step60 >> 8));
+ ov5645_write_reg(0x3a0b, (band_step60 & 0xff));
+
+ max_band60 = (int)((prev_VTS-4)/band_step60);
+ ov5645_write_reg(0x3a0d, max_band60);
+
+ /* 50Hz */
+ band_step50 = prev_sysclk * 100/prev_HTS;
+ ov5645_write_reg(0x3a08, (band_step50 >> 8));
+ ov5645_write_reg(0x3a09, (band_step50 & 0xff));
+
+ max_band50 = (int)((prev_VTS-4)/band_step50);
+ ov5645_write_reg(0x3a0e, max_band50);
+}
+
+static int OV5645_set_AE_target(int target)
+{
+ /* stable in high */
+ int fast_high, fast_low;
+ AE_low = target * 23 / 25; /* 0.92 */
+ AE_high = target * 27 / 25; /* 1.08 */
+
+ fast_high = AE_high<<1;
+ if (fast_high > 255)
+ fast_high = 255;
+
+ fast_low = AE_low >> 1;
+
+ ov5645_write_reg(0x3a0f, AE_high);
+ ov5645_write_reg(0x3a10, AE_low);
+ ov5645_write_reg(0x3a1b, AE_high);
+ ov5645_write_reg(0x3a1e, AE_low);
+ ov5645_write_reg(0x3a11, fast_high);
+ ov5645_write_reg(0x3a1f, fast_low);
+
+ return 0;
+}
+
+static void OV5645_turn_on_AE_AG(int enable)
+{
+ u8 ae_ag_ctrl;
+
+ ov5645_read_reg(0x3503, &ae_ag_ctrl);
+ if (enable) {
+ /* turn on auto AE/AG */
+ ae_ag_ctrl = ae_ag_ctrl & ~(0x03);
+ } else {
+ /* turn off AE/AG */
+ ae_ag_ctrl = ae_ag_ctrl | 0x03;
+ }
+ ov5645_write_reg(0x3503, ae_ag_ctrl);
+}
+
+static bool binning_on(void)
+{
+ u8 temp;
+ ov5645_read_reg(0x3821, &temp);
+ temp &= 0xfe;
+ if (temp)
+ return true;
+ else
+ return false;
+}
+
+static void ov5645_set_virtual_channel(int channel)
+{
+ u8 channel_id;
+
+ ov5645_read_reg(0x4814, &channel_id);
+ channel_id &= ~(3 << 6);
+ ov5645_write_reg(0x4814, channel_id | (channel << 6));
+}
+
+/* download ov5645 settings to sensor through i2c */
+static int ov5645_download_firmware(struct reg_value *pModeSetting, s32 ArySize)
+{
+ register u32 Delay_ms = 0;
+ register u16 RegAddr = 0;
+ register u8 Mask = 0;
+ register u8 Val = 0;
+ u8 RegVal = 0;
+ int i, retval = 0;
+
+ for (i = 0; i < ArySize; ++i, ++pModeSetting) {
+ Delay_ms = pModeSetting->u32Delay_ms;
+ RegAddr = pModeSetting->u16RegAddr;
+ Val = pModeSetting->u8Val;
+ Mask = pModeSetting->u8Mask;
+
+ if (Mask) {
+ retval = ov5645_read_reg(RegAddr, &RegVal);
+ if (retval < 0)
+ goto err;
+
+ RegVal &= ~(u8)Mask;
+ Val &= Mask;
+ Val |= RegVal;
+ }
+
+ retval = ov5645_write_reg(RegAddr, Val);
+ if (retval < 0)
+ goto err;
+
+ if (Delay_ms)
+ msleep(Delay_ms);
+ }
+err:
+ return retval;
+}
+
+static void ov5645_dnld_af_fw(void)
+{
+ struct reg_value *pModeSetting = NULL;
+ s32 ArySize = 0;
+ int retval = 0;
+ u8 temp;
+ int cnt = 40;
+
+ pModeSetting = ov5645_af_setting;
+ ArySize = ARRAY_SIZE(ov5645_af_setting);
+ retval = ov5645_download_firmware(pModeSetting, ArySize);
+
+ do {
+ ov5645_read_reg(0x3029, &temp);
+ msleep(5);
+ } while (temp != 0x70 && --cnt > 0);
+
+ if (temp != 0x70)
+ pr_warning("%s: Failed to download AF firmware\n", __func__);
+}
+
+/* sensor changes between scaling and subsampling
+ * go through exposure calcualtion
+ */
+static int ov5645_change_mode_exposure_calc(enum ov5645_frame_rate frame_rate,
+ enum ov5645_mode mode)
+{
+ struct reg_value *pModeSetting = NULL;
+ s32 ArySize = 0;
+ u8 average;
+ int prev_shutter, prev_gain16;
+ int cap_shutter, cap_gain16;
+ int cap_sysclk, cap_HTS, cap_VTS;
+ int light_freq, cap_bandfilt, cap_maxband;
+ long cap_gain16_shutter;
+ int retval = 0;
+
+ /* check if the input mode and frame rate is valid */
+ pModeSetting =
+ ov5645_mode_info_data[frame_rate][mode].init_data_ptr;
+ ArySize =
+ ov5645_mode_info_data[frame_rate][mode].init_data_size;
+
+ ov5645_data.pix.width =
+ ov5645_mode_info_data[frame_rate][mode].width;
+ ov5645_data.pix.height =
+ ov5645_mode_info_data[frame_rate][mode].height;
+
+ if (ov5645_data.pix.width == 0 || ov5645_data.pix.height == 0 ||
+ pModeSetting == NULL || ArySize == 0)
+ return -EINVAL;
+
+ /* auto focus */
+ /* OV5645_auto_focus();//if no af function, just skip it */
+
+ /* turn off AE/AG */
+ OV5645_turn_on_AE_AG(0);
+
+ /* read preview shutter */
+ prev_shutter = OV5645_get_shutter();
+ if ((binning_on()) && (mode != ov5645_mode_720P_1280_720)
+ && (mode != ov5645_mode_1080P_1920_1080))
+ prev_shutter *= 2;
+
+ /* read preview gain */
+ prev_gain16 = OV5645_get_gain16();
+
+ /* get average */
+ ov5645_read_reg(0x56a1, &average);
+
+ /* turn off night mode for capture */
+ OV5645_set_night_mode();
+
+ /* turn off overlay */
+ /* ov5645_write_reg(0x3022, 0x06);//if no af function, just skip it */
+
+ OV5645_stream_off();
+
+ /* Write capture setting */
+ retval = ov5645_download_firmware(pModeSetting, ArySize);
+ if (retval < 0)
+ goto err;
+
+ /* read capture VTS */
+ cap_VTS = OV5645_get_VTS();
+ cap_HTS = OV5645_get_HTS();
+ cap_sysclk = OV5645_get_sysclk();
+
+ /* calculate capture banding filter */
+ light_freq = OV5645_get_light_freq();
+ if (light_freq == 60) {
+ /* 60Hz */
+ cap_bandfilt = cap_sysclk * 100 / cap_HTS * 100 / 120;
+ } else {
+ /* 50Hz */
+ cap_bandfilt = cap_sysclk * 100 / cap_HTS;
+ }
+ cap_maxband = (int)((cap_VTS - 4)/cap_bandfilt);
+
+ /* calculate capture shutter/gain16 */
+ if (average > AE_low && average < AE_high) {
+ /* in stable range */
+ cap_gain16_shutter =
+ prev_gain16 * prev_shutter * cap_sysclk/prev_sysclk
+ * prev_HTS/cap_HTS * AE_Target / average;
+ } else {
+ cap_gain16_shutter =
+ prev_gain16 * prev_shutter * cap_sysclk/prev_sysclk
+ * prev_HTS/cap_HTS;
+ }
+
+ /* gain to shutter */
+ if (cap_gain16_shutter < (cap_bandfilt * 16)) {
+ /* shutter < 1/100 */
+ cap_shutter = cap_gain16_shutter/16;
+ if (cap_shutter < 1)
+ cap_shutter = 1;
+
+ cap_gain16 = cap_gain16_shutter/cap_shutter;
+ if (cap_gain16 < 16)
+ cap_gain16 = 16;
+ } else {
+ if (cap_gain16_shutter >
+ (cap_bandfilt * cap_maxband * 16)) {
+ /* exposure reach max */
+ cap_shutter = cap_bandfilt * cap_maxband;
+ cap_gain16 = cap_gain16_shutter / cap_shutter;
+ } else {
+ /* 1/100 < (cap_shutter = n/100) =< max */
+ cap_shutter =
+ ((int) (cap_gain16_shutter/16 / cap_bandfilt))
+ *cap_bandfilt;
+ cap_gain16 = cap_gain16_shutter / cap_shutter;
+ }
+ }
+
+ /* write capture gain */
+ OV5645_set_gain16(cap_gain16);
+
+ /* write capture shutter */
+ if (cap_shutter > (cap_VTS - 4)) {
+ cap_VTS = cap_shutter + 4;
+ OV5645_set_VTS(cap_VTS);
+ }
+ OV5645_set_shutter(cap_shutter);
+
+err:
+ return retval;
+}
+
+/* if sensor changes inside scaling or subsampling
+ * change mode directly
+ * */
+static int ov5645_change_mode_direct(enum ov5645_frame_rate frame_rate,
+ enum ov5645_mode mode)
+{
+ struct reg_value *pModeSetting = NULL;
+ s32 ArySize = 0;
+ int retval = 0;
+
+ /* check if the input mode and frame rate is valid */
+ pModeSetting =
+ ov5645_mode_info_data[frame_rate][mode].init_data_ptr;
+ ArySize =
+ ov5645_mode_info_data[frame_rate][mode].init_data_size;
+
+ ov5645_data.pix.width =
+ ov5645_mode_info_data[frame_rate][mode].width;
+ ov5645_data.pix.height =
+ ov5645_mode_info_data[frame_rate][mode].height;
+
+ if (ov5645_data.pix.width == 0 || ov5645_data.pix.height == 0 ||
+ pModeSetting == NULL || ArySize == 0)
+ return -EINVAL;
+
+ /* turn off AE/AG */
+ OV5645_turn_on_AE_AG(0);
+
+ OV5645_stream_off();
+
+ /* Write capture setting */
+ retval = ov5645_download_firmware(pModeSetting, ArySize);
+ if (retval < 0)
+ goto err;
+
+ OV5645_turn_on_AE_AG(1);
+
+err:
+ return retval;
+}
+
+static int ov5645_init_mode(enum ov5645_frame_rate frame_rate,
+ enum ov5645_mode mode, enum ov5645_mode orig_mode)
+{
+ struct reg_value *pModeSetting = NULL;
+ s32 ArySize = 0;
+ int retval = 0;
+ u32 msec_wait4stable = 0;
+ enum ov5645_downsize_mode dn_mode, orig_dn_mode;
+
+ if ((mode > ov5645_mode_MAX || mode < ov5645_mode_MIN)
+ && (mode != ov5645_mode_INIT)) {
+ pr_err("Wrong ov5645 mode detected!\n");
+ return -1;
+ }
+
+ dn_mode = ov5645_mode_info_data[frame_rate][mode].dn_mode;
+ orig_dn_mode = ov5645_mode_info_data[frame_rate][orig_mode].dn_mode;
+ if (mode == ov5645_mode_INIT) {
+ pModeSetting = ov5645_init_setting_30fps_VGA;
+ ArySize = ARRAY_SIZE(ov5645_init_setting_30fps_VGA);
+
+ ov5645_data.pix.width = 2592;
+ ov5645_data.pix.height = 1944;
+ ov5645_data.loaded_fps = ov5645_15_fps;
+ ov5645_data.loaded_mode = ov5645_mode_QSXGA_2592_1944;
+
+ retval = ov5645_download_firmware(pModeSetting, ArySize);
+ if (retval < 0)
+ goto err;
+
+ pModeSetting = ov5645_setting_15fps_QSXGA_2592_1944;
+ ArySize = ARRAY_SIZE(ov5645_setting_15fps_QSXGA_2592_1944);
+ retval = ov5645_download_firmware(pModeSetting, ArySize);
+
+ ov5645_dnld_af_fw();
+
+ } else if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) ||
+ (dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) {
+ /* change between subsampling and scaling
+ * go through exposure calucation */
+ retval = ov5645_change_mode_exposure_calc(frame_rate, mode);
+ } else {
+ /* change inside subsampling or scaling
+ * download firmware directly */
+ retval = ov5645_change_mode_direct(frame_rate, mode);
+ }
+
+ if (retval < 0)
+ goto err;
+
+ OV5645_set_AE_target(AE_Target);
+ OV5645_get_light_freq();
+ OV5645_set_bandingfilter();
+ ov5645_set_virtual_channel(ov5645_data.csi);
+
+ /* add delay to wait for sensor stable */
+ if (mode == ov5645_mode_QSXGA_2592_1944) {
+ /* dump the first two frames: 1/7.5*2
+ * the frame rate of QSXGA is 7.5fps */
+ msec_wait4stable = 267;
+ } else {
+ /* dump the first eighteen frames: 1/30*18 */
+ msec_wait4stable = 600;
+ }
+ msleep(msec_wait4stable);
+
+err:
+ return retval;
+}
+
+/*!
+ * ov5645_s_power - V4L2 sensor interface handler for VIDIOC_S_POWER ioctl
+ * @s: pointer to standard V4L2 device structure
+ * @on: indicates power mode (on or off)
+ *
+ * Turns the power on or off, depending on the value of on and returns the
+ * appropriate error code.
+ */
+static int ov5645_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5645 *sensor = to_ov5645(client);
+
+ if (on && !sensor->on) {
+ if (io_regulator)
+ if (regulator_enable(io_regulator) != 0)
+ return -EIO;
+ if (core_regulator)
+ if (regulator_enable(core_regulator) != 0)
+ return -EIO;
+ if (gpo_regulator)
+ if (regulator_enable(gpo_regulator) != 0)
+ return -EIO;
+ if (analog_regulator)
+ if (regulator_enable(analog_regulator) != 0)
+ return -EIO;
+ } else if (!on && sensor->on) {
+ if (analog_regulator)
+ regulator_disable(analog_regulator);
+ if (core_regulator)
+ regulator_disable(core_regulator);
+ if (io_regulator)
+ regulator_disable(io_regulator);
+ if (gpo_regulator)
+ regulator_disable(gpo_regulator);
+ }
+
+ sensor->on = on;
+
+ return 0;
+}
+
+/*!
+ * ov5645_g_parm - V4L2 sensor interface handler for VIDIOC_G_PARM ioctl
+ * @s: pointer to standard V4L2 sub device structure
+ * @a: pointer to standard V4L2 VIDIOC_G_PARM ioctl structure
+ *
+ * Returns the sensor's video CAPTURE parameters.
+ */
+static int ov5645_g_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5645 *sensor = to_ov5645(client);
+ struct v4l2_captureparm *cparm = &a->parm.capture;
+ int ret = 0;
+
+ switch (a->type) {
+ /* This is the only case currently handled. */
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ memset(a, 0, sizeof(*a));
+ a->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ cparm->capability = sensor->streamcap.capability;
+ cparm->timeperframe = sensor->streamcap.timeperframe;
+ cparm->capturemode = sensor->streamcap.capturemode;
+ ret = 0;
+ break;
+
+ /* These are all the possible cases. */
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ case V4L2_BUF_TYPE_VBI_CAPTURE:
+ case V4L2_BUF_TYPE_VBI_OUTPUT:
+ case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+ case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+ ret = -EINVAL;
+ break;
+
+ default:
+ pr_debug(" type is unknown - %d\n", a->type);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/*!
+ * ov5460_s_parm - V4L2 sensor interface handler for VIDIOC_S_PARM ioctl
+ * @s: pointer to standard V4L2 sub device structure
+ * @a: pointer to standard V4L2 VIDIOC_S_PARM ioctl structure
+ *
+ * Configures the sensor to use the input parameters, if possible. If
+ * not possible, reverts to the old parameters and returns the
+ * appropriate error code.
+ */
+static int ov5645_s_parm(struct v4l2_subdev *sd, struct v4l2_streamparm *a)
+{
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5645 *sensor = to_ov5645(client);
+ struct v4l2_fract *timeperframe = &a->parm.capture.timeperframe;
+ u32 tgt_fps; /* target frames per secound */
+ enum ov5645_frame_rate frame_rate;
+ enum ov5645_mode orig_mode;
+ int ret = 0;
+
+ switch (a->type) {
+ /* This is the only case currently handled. */
+ case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+ /* Check that the new frame rate is allowed. */
+ if ((timeperframe->numerator == 0) ||
+ (timeperframe->denominator == 0)) {
+ timeperframe->denominator = DEFAULT_FPS;
+ timeperframe->numerator = 1;
+ }
+
+ tgt_fps = timeperframe->denominator /
+ timeperframe->numerator;
+
+ if (tgt_fps > MAX_FPS) {
+ timeperframe->denominator = MAX_FPS;
+ timeperframe->numerator = 1;
+ } else if (tgt_fps < MIN_FPS) {
+ timeperframe->denominator = MIN_FPS;
+ timeperframe->numerator = 1;
+ }
+
+ /* Actual frame rate we use */
+ tgt_fps = timeperframe->denominator /
+ timeperframe->numerator;
+
+ if (tgt_fps == 15)
+ frame_rate = ov5645_15_fps;
+ else if (tgt_fps == 30)
+ frame_rate = ov5645_30_fps;
+ else {
+ pr_err(" The camera frame rate is not supported!\n");
+ return -EINVAL;
+ }
+
+ // This check is necessary because OpenCV doesn't call G_PARM on the
+ // zeroed structure before setting FPS through S_PARM
+ if ((u32)a->parm.capture.capturemode == 0) {
+ a->parm.capture.capturemode = get_capturemode(ov5645_data.pix.width,
+ ov5645_data.pix.height);
+ }
+
+ orig_mode = ov5645_data.loaded_mode;
+ if ((orig_mode != (u32)a->parm.capture.capturemode) ||
+ (frame_rate != ov5645_data.loaded_fps)) {
+ ret = ov5645_init_mode(frame_rate,
+ (u32)a->parm.capture.capturemode, orig_mode);
+ }
+ if (ret < 0)
+ return ret;
+
+ sensor->streamcap.timeperframe = *timeperframe;
+ sensor->streamcap.capturemode =
+ (u32)a->parm.capture.capturemode;
+ ov5645_data.loaded_mode = a->parm.capture.capturemode;
+ ov5645_data.loaded_fps = frame_rate;
+ break;
+
+ /* These are all the possible cases. */
+ case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+ case V4L2_BUF_TYPE_VIDEO_OVERLAY:
+ case V4L2_BUF_TYPE_VBI_CAPTURE:
+ case V4L2_BUF_TYPE_VBI_OUTPUT:
+ case V4L2_BUF_TYPE_SLICED_VBI_CAPTURE:
+ case V4L2_BUF_TYPE_SLICED_VBI_OUTPUT:
+ pr_debug(" type is not " \
+ "V4L2_BUF_TYPE_VIDEO_CAPTURE but %d\n",
+ a->type);
+ ret = -EINVAL;
+ break;
+
+ default:
+ pr_debug(" type is unknown - %d\n", a->type);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int ov5645_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct v4l2_mbus_framefmt *mf = &format->format;
+ const struct ov5645_datafmt *fmt = ov5645_find_datafmt(mf->code);
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5645 *sensor = to_ov5645(client);
+ int capturemode;
+
+ if (!fmt) {
+ mf->code = ov5645_colour_fmts[0].code;
+ mf->colorspace = ov5645_colour_fmts[0].colorspace;
+ }
+
+ mf->field = V4L2_FIELD_NONE;
+
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+ return 0;
+
+ sensor->fmt = fmt;
+
+ capturemode = get_capturemode(mf->width, mf->height);
+ if (capturemode >= 0) {
+ ov5645_data.streamcap.capturemode = capturemode;
+ ov5645_data.pix.width = mf->width;
+ ov5645_data.pix.height = mf->height;
+ return 0;
+ }
+
+ dev_err(&client->dev, "%s set fail\n", __func__);
+ return -EINVAL;
+}
+
+
+static int ov5645_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct v4l2_mbus_framefmt *mf = &format->format;
+ struct i2c_client *client = v4l2_get_subdevdata(sd);
+ struct ov5645 *sensor = to_ov5645(client);
+ const struct ov5645_datafmt *fmt = sensor->fmt;
+
+ if (format->pad)
+ return -EINVAL;
+
+ mf->code = fmt->code;
+ mf->colorspace = fmt->colorspace;
+ mf->field = V4L2_FIELD_NONE;
+
+ mf->width = ov5645_data.pix.width;
+ mf->height = ov5645_data.pix.height;
+
+ return 0;
+}
+
+static int ov5645_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ if (code->pad || code->index >= ARRAY_SIZE(ov5645_colour_fmts))
+ return -EINVAL;
+
+ code->code = ov5645_colour_fmts[code->index].code;
+ return 0;
+}
+
+/*!
+ * ov5645_enum_framesizes - V4L2 sensor interface handler for
+ * VIDIOC_ENUM_FRAMESIZES ioctl
+ * @s: pointer to standard V4L2 device structure
+ * @fsize: standard V4L2 VIDIOC_ENUM_FRAMESIZES ioctl structure
+ *
+ * Return 0 if successful, otherwise -EINVAL.
+ */
+static int ov5645_enum_framesizes(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ if (fse->index > ov5645_mode_MAX)
+ return -EINVAL;
+
+ fse->max_width =
+ max(ov5645_mode_info_data[0][fse->index].width,
+ ov5645_mode_info_data[1][fse->index].width);
+ fse->min_width = fse->max_width;
+ fse->max_height =
+ max(ov5645_mode_info_data[0][fse->index].height,
+ ov5645_mode_info_data[1][fse->index].height);
+ fse->min_height = fse->max_height;
+ return 0;
+}
+
+static int ov5645_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc)
+{
+ return -EINVAL;
+}
+
+/*!
+ * ov5645_enum_frameintervals - V4L2 sensor interface handler for
+ * VIDIOC_ENUM_FRAMEINTERVALS ioctl
+ * @s: pointer to standard V4L2 device structure
+ * @fival: standard V4L2 VIDIOC_ENUM_FRAMEINTERVALS ioctl structure
+ *
+ * Return 0 if successful, otherwise -EINVAL.
+ */
+static int ov5645_enum_frameintervals(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ int i, j, count = 0;
+
+ if (fie->index < 0 || fie->index > ov5645_mode_MAX)
+ return -EINVAL;
+
+ if (fie->width == 0 || fie->height == 0 ||
+ fie->code == 0) {
+ pr_warning("Please assign pixel format, width and height.\n");
+ return -EINVAL;
+ }
+
+ fie->interval.numerator = 1;
+
+ count = 0;
+ for (i = 0; i < ARRAY_SIZE(ov5645_mode_info_data); i++) {
+ for (j = 0; j < (ov5645_mode_MAX + 1); j++) {
+ if (fie->width == ov5645_mode_info_data[i][j].width
+ && fie->height == ov5645_mode_info_data[i][j].height
+ && ov5645_mode_info_data[i][j].init_data_ptr != NULL) {
+ count++;
+ }
+ if (fie->index == (count - 1)) {
+ fie->interval.denominator =
+ ov5645_framerates[i];
+ return 0;
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+
+/*!
+ * dev_init - V4L2 sensor init
+ * @s: pointer to standard V4L2 device structure
+ *
+ */
+static int init_device(void)
+{
+ u32 tgt_xclk; /* target xclk */
+ u32 tgt_fps; /* target frames per secound */
+ enum ov5645_frame_rate frame_rate;
+ int ret;
+
+ ov5645_data.on = true;
+
+ /* mclk */
+ tgt_xclk = ov5645_data.mclk;
+ tgt_xclk = min(tgt_xclk, (u32)OV5645_XCLK_MAX);
+ tgt_xclk = max(tgt_xclk, (u32)OV5645_XCLK_MIN);
+ ov5645_data.mclk = tgt_xclk;
+
+ pr_debug(" Setting mclk to %d MHz\n", tgt_xclk / 1000000);
+
+ /* Default camera frame rate is set in probe */
+ tgt_fps = ov5645_data.streamcap.timeperframe.denominator /
+ ov5645_data.streamcap.timeperframe.numerator;
+
+ if (tgt_fps == 15)
+ frame_rate = ov5645_15_fps;
+ else if (tgt_fps == 30)
+ frame_rate = ov5645_30_fps;
+ else
+ return -EINVAL; /* Only support 15fps or 30fps now. */
+
+ ret = ov5645_init_mode(frame_rate, ov5645_mode_INIT, ov5645_mode_INIT);
+
+ return ret;
+}
+
+static int ov5645_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ if (enable)
+ OV5645_stream_on();
+ else
+ OV5645_stream_off();
+ return 0;
+}
+
+static struct v4l2_subdev_video_ops ov5645_subdev_video_ops = {
+ .g_parm = ov5645_g_parm,
+ .s_parm = ov5645_s_parm,
+ .s_stream = ov5645_s_stream,
+};
+
+static const struct v4l2_subdev_pad_ops ov5645_subdev_pad_ops = {
+ .enum_frame_size = ov5645_enum_framesizes,
+ .enum_frame_interval = ov5645_enum_frameintervals,
+ .enum_mbus_code = ov5645_enum_mbus_code,
+ .set_fmt = ov5645_set_fmt,
+ .get_fmt = ov5645_get_fmt,
+};
+
+static struct v4l2_subdev_core_ops ov5645_subdev_core_ops = {
+ .s_power = ov5645_s_power,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .g_register = ov5645_get_register,
+ .s_register = ov5645_set_register,
+#endif
+ .queryctrl = ov5645_queryctrl,
+};
+
+static struct v4l2_subdev_ops ov5645_subdev_ops = {
+ .core = &ov5645_subdev_core_ops,
+ .video = &ov5645_subdev_video_ops,
+ .pad = &ov5645_subdev_pad_ops,
+};
+
+static void ov5645_adjust_setting_20mhz(void)
+{
+ struct reg_value *regsetting;
+ int i, array_size;
+
+ /* adjust for INIT mode */
+ regsetting = ov5645_init_setting_30fps_VGA;
+ array_size = ARRAY_SIZE(ov5645_init_setting_30fps_VGA);
+
+ for (i = 0; i < array_size; i++, regsetting++)
+ if (regsetting->u16RegAddr == 0x3037)
+ regsetting->u8Val = 0x17;
+}
+
+static int ov5645_set_regs(const char *buffer, struct kernel_param *kp)
+{
+ // Use this sysfs node to set the ov5645 isp regs by sending it a
+ // comma separated list of register value pairs in hex
+ unsigned int reg_addr, value;
+ int cnt, retval;
+ char *reg, *val;
+
+ char *in_string = kstrdup(buffer, GFP_KERNEL);
+ if (in_string == NULL)
+ return 0;
+
+ while (in_string != NULL) {
+ reg = strsep(&in_string, ",");
+ cnt = sscanf(reg, "%x", ®_addr);
+ if (cnt != 1)
+ break;
+
+ val = strsep(&in_string, ",");
+ cnt = sscanf(val, "%x", &value);
+ if (cnt != 1)
+ break;
+
+ pr_warning("%s: Writing Reg = %04x, val = %02x\n", __func__,
+ (unsigned int)reg_addr, (unsigned int)value);
+ retval = ov5645_write_reg(reg_addr & 0xffff, value & 0xff);
+
+ if (retval < 0)
+ break;
+ }
+ if (retval < 0)
+ pr_err("%s: Failed to write register: %s\n", __func__, reg);
+ return 0;
+}
+
+static int reg_addr_to_read;
+static int ov5645_set_print_reg(const char *buffer, struct kernel_param *kp)
+{
+ int cnt;
+ cnt = sscanf(buffer, "%x", ®_addr_to_read);
+ if (cnt != 1)
+ reg_addr_to_read = 0;
+
+ return 0;
+}
+
+static int ov5645_get_print_reg(char *buffer, struct kernel_param *kp)
+{
+ int cnt, retval;
+ u8 val;
+ if (reg_addr_to_read == 0)
+ return 0;
+ retval = ov5645_read_reg(reg_addr_to_read & 0xffff, &val);
+ if (retval < 0)
+ return 0;
+ cnt = sprintf(buffer, "0x%04x: 0x%02x",
+ (unsigned int)(reg_addr_to_read & 0xffff), (unsigned int)val);
+ return cnt;
+}
+
+static int ov5645_set_af_mode(const char *buffer, struct kernel_param *kp)
+{
+ int cnt, val;
+ cnt = sscanf(buffer, "%d", &val);
+
+ switch (val) {
+ case ov5645_af_release:
+ ov5645_release_af();
+ break;
+ case ov5645_af_lock:
+ ov5645_lock_af();
+ break;
+ case ov5645_af_cont:
+ ov5645_enable_cont_af();
+ break;
+ default:
+ pr_warning("%s: Incorrect value written to sysfs node\n", __func__);
+ }
+
+ return 0;
+}
+
+static int ov5645_read_af(char *buffer, struct kernel_param *kp)
+{
+ int cnt, retval;
+ u8 val;
+ retval = ov5645_read_reg(0x3029, &val);
+
+ cnt = sprintf(buffer, "%d", (unsigned int)val & 0xff);
+ return cnt;
+}
+
+static int ov5645_get_initialized(char *buffer, struct kernel_param *kp)
+{
+ int cnt;
+ cnt = sprintf(buffer, "%d", ov5645_data.initialized ? 1 : 0);
+ return cnt;
+}
+
+module_param_call(ov5645_set_regs, ov5645_set_regs, NULL, NULL, 0644);
+module_param_call(ov5645_print_reg, ov5645_set_print_reg, ov5645_get_print_reg,
+ NULL, 0644);
+module_param_call(ov5645_af, ov5645_set_af_mode, ov5645_read_af, NULL, 0644);
+module_param_call(ov5645_initialized, NULL, ov5645_get_initialized, NULL, 0644);
+
+/*!
+ * ov5645 I2C probe function
+ *
+ * @param adapter struct i2c_adapter *
+ * @return Error code indicating success or failure
+ */
+static int ov5645_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct pinctrl *pinctrl;
+ struct device *dev = &client->dev;
+ int retval;
+ u8 chip_id_high, chip_id_low;
+
+ /* ov5645 pinctrl */
+ pinctrl = devm_pinctrl_get_select_default(dev);
+ if (IS_ERR(pinctrl))
+ dev_warn(dev, "no pin available\n");
+
+ /* request power down pin */
+ pwn_gpio = of_get_named_gpio(dev->of_node, "pwn-gpios", 0);
+ if (!gpio_is_valid(pwn_gpio))
+ dev_warn(dev, "no sensor pwdn pin available");
+ else {
+ retval = devm_gpio_request_one(dev, pwn_gpio, GPIOF_OUT_INIT_HIGH,
+ "ov5645_mipi_pwdn");
+ if (retval < 0) {
+ dev_warn(dev, "Failed to set power pin\n");
+ dev_warn(dev, "retval=%d\n", retval);
+ return retval;
+ }
+ }
+
+ /* request reset pin */
+ rst_gpio = of_get_named_gpio(dev->of_node, "rst-gpios", 0);
+ if (!gpio_is_valid(rst_gpio))
+ dev_warn(dev, "no sensor reset pin available");
+ else {
+ retval = devm_gpio_request_one(dev, rst_gpio, GPIOF_OUT_INIT_HIGH,
+ "ov5645_mipi_reset");
+ if (retval < 0) {
+ dev_warn(dev, "Failed to set reset pin\n");
+ return retval;
+ }
+ }
+
+ /* Set initial values for the sensor struct. */
+ memset(&ov5645_data, 0, sizeof(ov5645_data));
+ ov5645_data.sensor_clk = devm_clk_get(dev, "csi_mclk");
+ if (IS_ERR(ov5645_data.sensor_clk)) {
+ /* assuming clock enabled by default */
+ ov5645_data.sensor_clk = NULL;
+ dev_err(dev, "clock-frequency missing or invalid\n");
+ return PTR_ERR(ov5645_data.sensor_clk);
+ }
+
+ retval = of_property_read_u32(dev->of_node, "mclk",
+ &(ov5645_data.mclk));
+ if (retval) {
+ dev_err(dev, "mclk missing or invalid\n");
+ return retval;
+ }
+
+ if (ov5645_data.mclk == OV5645_XCLK_20MHZ)
+ ov5645_adjust_setting_20mhz();
+
+ retval = of_property_read_u32(dev->of_node, "mclk_source",
+ (u32 *) &(ov5645_data.mclk_source));
+ if (retval) {
+ dev_err(dev, "mclk_source missing or invalid\n");
+ return retval;
+ }
+
+ retval = of_property_read_u32(dev->of_node, "csi_id",
+ &(ov5645_data.csi));
+ if (retval) {
+ dev_err(dev, "csi id missing or invalid\n");
+ return retval;
+ }
+
+ retval = of_property_read_u32(dev->of_node, "ae_target", &(AE_Target));
+ if (retval) {
+ dev_warn(dev,
+ "ae_target missing in dev tree, using default %d\n",
+ AE_Target);
+ }
+
+ clk_prepare_enable(ov5645_data.sensor_clk);
+
+ ov5645_data.io_init = ov5645_reset;
+ ov5645_data.i2c_client = client;
+ ov5645_data.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+ ov5645_data.pix.width = 640;
+ ov5645_data.pix.height = 480;
+ ov5645_data.streamcap.capability = V4L2_MODE_HIGHQUALITY |
+ V4L2_CAP_TIMEPERFRAME;
+ ov5645_data.streamcap.capturemode = 0;
+ ov5645_data.streamcap.timeperframe.denominator = DEFAULT_FPS;
+ ov5645_data.streamcap.timeperframe.numerator = 1;
+
+ ov5645_regulator_enable(&client->dev);
+
+ ov5645_reset();
+
+ ov5645_power_down(0);
+
+ retval = ov5645_read_reg(OV5645_CHIP_ID_HIGH_BYTE, &chip_id_high);
+ if (retval < 0 || chip_id_high != 0x56) {
+ pr_warning("camera ov5645_mipi is not found\n");
+ clk_disable_unprepare(ov5645_data.sensor_clk);
+ return -ENODEV;
+ }
+ retval = ov5645_read_reg(OV5645_CHIP_ID_LOW_BYTE, &chip_id_low);
+ if (retval < 0 || chip_id_low != 0x45) {
+ pr_warning("camera ov5645_mipi is not found\n");
+ clk_disable_unprepare(ov5645_data.sensor_clk);
+ return -ENODEV;
+ }
+
+ retval = init_device();
+ if (retval < 0) {
+ clk_disable_unprepare(ov5645_data.sensor_clk);
+ pr_warning("camera ov5645 init failed\n");
+ ov5645_power_down(1);
+ return retval;
+ }
+
+ v4l2_i2c_subdev_init(&ov5645_data.subdev, client, &ov5645_subdev_ops);
+
+ ov5645_data.subdev.grp_id = 678;
+ retval = v4l2_async_register_subdev(&ov5645_data.subdev);
+ if (retval < 0)
+ dev_err(&client->dev,
+ "%s--Async register failed, ret=%d\n", __func__, retval);
+
+ OV5645_stream_off();
+ pr_info("camera ov5645_mipi is found\n");
+ ov5645_data.initialized = true;
+ return retval;
+}
+
+/*!
+ * ov5645 I2C detach function
+ *
+ * @param client struct i2c_client *
+ * @return Error code indicating success or failure
+ */
+static int ov5645_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+
+ v4l2_async_unregister_subdev(sd);
+
+ clk_disable_unprepare(ov5645_data.sensor_clk);
+
+ ov5645_power_down(1);
+
+ if (gpo_regulator)
+ regulator_disable(gpo_regulator);
+
+ if (analog_regulator)
+ regulator_disable(analog_regulator);
+
+ if (core_regulator)
+ regulator_disable(core_regulator);
+
+ if (io_regulator)
+ regulator_disable(io_regulator);
+
+ return 0;
+}
+
+module_i2c_driver(ov5645_i2c_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("OV5645 MIPI Camera Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("CSI");
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig
index 5546839..32d4547 100644
--- a/drivers/staging/Kconfig
+++ b/drivers/staging/Kconfig
@@ -118,4 +118,6 @@
source "drivers/staging/pi433/Kconfig"
+source "drivers/staging/gasket/Kconfig"
+
endif # STAGING
diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile
index c603c4b..2e49d3b 100644
--- a/drivers/staging/Makefile
+++ b/drivers/staging/Makefile
@@ -50,3 +50,5 @@
obj-$(CONFIG_CRYPTO_DEV_CCREE) += ccree/
obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/
obj-$(CONFIG_PI433) += pi433/
+obj-$(CONFIG_BCM2708_VCHIQ) += vc04_services/
+obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket/
diff --git a/drivers/staging/gasket/Kconfig b/drivers/staging/gasket/Kconfig
new file mode 100644
index 0000000..970e299
--- /dev/null
+++ b/drivers/staging/gasket/Kconfig
@@ -0,0 +1,23 @@
+menu "Gasket devices"
+
+config STAGING_GASKET_FRAMEWORK
+ tristate "Gasket framework"
+ depends on PCI && (X86_64 || ARM64)
+ help
+ This framework supports Gasket-compatible devices, such as Apex.
+ It is required for any of the following module(s).
+
+ To compile this driver as a module, choose M here. The module
+ will be called "gasket".
+
+config STAGING_APEX_DRIVER
+ tristate "Apex Driver"
+ depends on STAGING_GASKET_FRAMEWORK
+ help
+ This driver supports the Apex device. Say Y if you want to
+ include this driver in the kernel.
+
+ To compile this driver as a module, choose M here. The module
+ will be called "apex".
+
+endmenu
diff --git a/drivers/staging/gasket/Makefile b/drivers/staging/gasket/Makefile
new file mode 100644
index 0000000..cec813e
--- /dev/null
+++ b/drivers/staging/gasket/Makefile
@@ -0,0 +1,9 @@
+#
+# Makefile for Gasket framework and dependent drivers.
+#
+
+obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket.o
+obj-$(CONFIG_STAGING_APEX_DRIVER) += apex.o
+
+gasket-objs := gasket_core.o gasket_ioctl.o gasket_interrupt.o gasket_page_table.o gasket_sysfs.o
+apex-objs := apex_driver.o
diff --git a/drivers/staging/gasket/TODO b/drivers/staging/gasket/TODO
new file mode 100644
index 0000000..6ff8e01
--- /dev/null
+++ b/drivers/staging/gasket/TODO
@@ -0,0 +1,9 @@
+This is a list of things that need to be done to get this driver out of the
+staging directory.
+- Document sysfs files with Documentation/ABI/ entries.
+- Use misc interface instead of major number for driver version description.
+- Add descriptions of module_param's
+- apex_get_status() should actually check status.
+- "drivers" should never be dealing with "raw" sysfs calls or mess around with
+ kobjects at all. The driver core should handle all of this for you
+ automaically. There should not be a need for raw attribute macros.
diff --git a/drivers/staging/gasket/apex.h b/drivers/staging/gasket/apex.h
new file mode 100644
index 0000000..5537d96
--- /dev/null
+++ b/drivers/staging/gasket/apex.h
@@ -0,0 +1,45 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Apex kernel-userspace interface definitions.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+#ifndef __APEX_H__
+#define __APEX_H__
+
+#include <linux/ioctl.h>
+
+/* Clock Gating ioctl. */
+struct apex_gate_clock_ioctl {
+ /* Enter or leave clock gated state. */
+ u64 enable;
+
+ /* If set, enter clock gating state, regardless of custom block's
+ * internal idle state
+ */
+ u64 force_idle;
+};
+
+/* Performance expectation ioctl. */
+enum apex_performance_expectation {
+ APEX_PERFORMANCE_LOW = 0,
+ APEX_PERFORMANCE_MED = 1,
+ APEX_PERFORMANCE_HIGH = 2,
+ APEX_PERFORMANCE_MAX = 3,
+};
+
+struct apex_performance_expectation_ioctl {
+ /* Expected performance from apex. */
+ uint32_t performance;
+};
+
+/* Base number for all Apex-common IOCTLs */
+#define APEX_IOCTL_BASE 0x7F
+
+/* Enable/Disable clock gating. */
+#define APEX_IOCTL_GATE_CLOCK \
+ _IOW(APEX_IOCTL_BASE, 0, struct apex_gate_clock_ioctl)
+
+#define APEX_IOCTL_PERFORMANCE_EXPECTATION _IOW(APEX_IOCTL_BASE, 1, struct apex_performance_expectation_ioctl)
+
+#endif /* __APEX_H__ */
diff --git a/drivers/staging/gasket/apex_driver.c b/drivers/staging/gasket/apex_driver.c
new file mode 100644
index 0000000..4476b67
--- /dev/null
+++ b/drivers/staging/gasket/apex_driver.c
@@ -0,0 +1,1166 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for the Apex chip.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+
+#include <linux/atomic.h>
+#include <linux/compiler.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/pci.h>
+#include <linux/printk.h>
+#include <linux/sched.h>
+#include <linux/uaccess.h>
+#include <linux/workqueue.h>
+
+#include "apex.h"
+
+#include "gasket_core.h"
+#include "gasket_interrupt.h"
+#include "gasket_page_table.h"
+#include "gasket_sysfs.h"
+
+/* Constants */
+#define APEX_DEVICE_NAME "Apex"
+#define APEX_DRIVER_VERSION "1.1"
+
+/* CSRs are in BAR 2. */
+#define APEX_BAR_INDEX 2
+
+#define APEX_PCI_VENDOR_ID 0x1ac1
+#define APEX_PCI_DEVICE_ID 0x089a
+
+/* Bar Offsets. */
+#define APEX_BAR_OFFSET 0
+#define APEX_CM_OFFSET 0x1000000
+
+/* The sizes of each Apex BAR 2. */
+#define APEX_BAR_BYTES 0x100000
+#define APEX_CH_MEM_BYTES (PAGE_SIZE * MAX_NUM_COHERENT_PAGES)
+
+/* The number of user-mappable memory ranges in BAR2 of a Apex chip. */
+#define NUM_REGIONS 3
+
+/* The number of nodes in a Apex chip. */
+#define NUM_NODES 1
+
+/*
+ * The total number of entries in the page table. Should match the value read
+ * from the register APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_SIZE.
+ */
+#define APEX_PAGE_TABLE_TOTAL_ENTRIES 8192
+
+#define APEX_EXTENDED_SHIFT 63 /* Extended address bit position. */
+
+/* Check reset 120 times */
+#define APEX_RESET_RETRY 120
+/* Wait 100 ms between checks. Total 12 sec wait maximum. */
+#define APEX_RESET_DELAY 100
+
+/* Interval between temperature polls, 0 disables polling */
+#define DEFAULT_APEX_TEMP_POLL_INTERVAL 0
+
+/* apex device private data */
+struct apex_dev {
+ struct gasket_dev *gasket_dev_ptr;
+ struct delayed_work check_temperature_work;
+ u32 adc_trip_points[3];
+ atomic_t temp_poll_interval;
+};
+
+/* Enumeration of the supported sysfs entries. */
+enum sysfs_attribute_type {
+ ATTR_KERNEL_HIB_PAGE_TABLE_SIZE,
+ ATTR_KERNEL_HIB_SIMPLE_PAGE_TABLE_SIZE,
+ ATTR_KERNEL_HIB_NUM_ACTIVE_PAGES,
+ ATTR_TEMP,
+ ATTR_TEMP_WARN1,
+ ATTR_TEMP_WARN1_EN,
+ ATTR_TEMP_WARN2,
+ ATTR_TEMP_WARN2_EN,
+ ATTR_TEMP_TRIP0,
+ ATTR_TEMP_TRIP1,
+ ATTR_TEMP_TRIP2,
+ ATTR_TEMP_POLL_INTERVAL,
+};
+
+/*
+ * Register offsets into BAR2 memory.
+ * Only values necessary for driver implementation are defined.
+ */
+enum apex_bar2_regs {
+ APEX_BAR2_REG_SCU_BASE = 0x1A300,
+ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_SIZE = 0x46000,
+ APEX_BAR2_REG_KERNEL_HIB_EXTENDED_TABLE = 0x46008,
+ APEX_BAR2_REG_KERNEL_HIB_TRANSLATION_ENABLE = 0x46010,
+ APEX_BAR2_REG_KERNEL_HIB_INSTR_QUEUE_INTVECCTL = 0x46018,
+ APEX_BAR2_REG_KERNEL_HIB_INPUT_ACTV_QUEUE_INTVECCTL = 0x46020,
+ APEX_BAR2_REG_KERNEL_HIB_PARAM_QUEUE_INTVECCTL = 0x46028,
+ APEX_BAR2_REG_KERNEL_HIB_OUTPUT_ACTV_QUEUE_INTVECCTL = 0x46030,
+ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL = 0x46038,
+ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL = 0x46040,
+ APEX_BAR2_REG_KERNEL_HIB_FATAL_ERR_INTVECCTL = 0x46048,
+ APEX_BAR2_REG_KERNEL_HIB_DMA_PAUSE = 0x46050,
+ APEX_BAR2_REG_KERNEL_HIB_DMA_PAUSE_MASK = 0x46058,
+ APEX_BAR2_REG_KERNEL_HIB_STATUS_BLOCK_DELAY = 0x46060,
+ APEX_BAR2_REG_KERNEL_HIB_MSIX_PENDING_BIT_ARRAY0 = 0x46068,
+ APEX_BAR2_REG_KERNEL_HIB_MSIX_PENDING_BIT_ARRAY1 = 0x46070,
+ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_INIT = 0x46078,
+ APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE_INIT = 0x46080,
+ APEX_BAR2_REG_KERNEL_WIRE_INT_PENDING_BIT_ARRAY = 0x48778,
+ APEX_BAR2_REG_KERNEL_WIRE_INT_MASK_ARRAY = 0x48780,
+ APEX_BAR2_REG_USER_HIB_DMA_PAUSE = 0x486D8,
+ APEX_BAR2_REG_USER_HIB_DMA_PAUSED = 0x486E0,
+ APEX_BAR2_REG_IDLEGENERATOR_IDLEGEN_IDLEREGISTER = 0x4A000,
+ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE = 0x50000,
+ APEX_BAR2_REG_OMC0_D0 = 0x01a0d0,
+ APEX_BAR2_REG_OMC0_D4 = 0x01a0d4,
+ APEX_BAR2_REG_OMC0_D8 = 0x01a0d8,
+ APEX_BAR2_REG_OMC0_DC = 0x01a0dc,
+
+ /* Error registers - Used mostly for debug */
+ APEX_BAR2_REG_USER_HIB_ERROR_STATUS = 0x86f0,
+ APEX_BAR2_REG_SCALAR_CORE_ERROR_STATUS = 0x41a0,
+};
+
+/* Addresses for packed registers. */
+#define APEX_BAR2_REG_AXI_QUIESCE (APEX_BAR2_REG_SCU_BASE + 0x2C)
+#define APEX_BAR2_REG_GCB_CLOCK_GATE (APEX_BAR2_REG_SCU_BASE + 0x14)
+#define APEX_BAR2_REG_SCU_0 (APEX_BAR2_REG_SCU_BASE + 0xc)
+#define APEX_BAR2_REG_SCU_1 (APEX_BAR2_REG_SCU_BASE + 0x10)
+#define APEX_BAR2_REG_SCU_2 (APEX_BAR2_REG_SCU_BASE + 0x14)
+#define APEX_BAR2_REG_SCU_3 (APEX_BAR2_REG_SCU_BASE + 0x18)
+#define APEX_BAR2_REG_SCU_4 (APEX_BAR2_REG_SCU_BASE + 0x1c)
+#define APEX_BAR2_REG_SCU_5 (APEX_BAR2_REG_SCU_BASE + 0x20)
+
+#define SCU3_RG_PWR_STATE_OVR_BIT_OFFSET 26
+#define SCU3_RG_PWR_STATE_OVR_MASK_WIDTH 2
+#define SCU3_CUR_RST_GCB_BIT_MASK 0x10
+#define SCU2_RG_RST_GCB_BIT_MASK 0xc
+
+/* Configuration for page table. */
+static struct gasket_page_table_config apex_page_table_configs[NUM_NODES] = {
+ {
+ .id = 0,
+ .mode = GASKET_PAGE_TABLE_MODE_NORMAL,
+ .total_entries = APEX_PAGE_TABLE_TOTAL_ENTRIES,
+ .base_reg = APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE,
+ .extended_reg = APEX_BAR2_REG_KERNEL_HIB_EXTENDED_TABLE,
+ .extended_bit = APEX_EXTENDED_SHIFT,
+ },
+};
+
+/* The regions in the BAR2 space that can be mapped into user space. */
+static const struct gasket_mappable_region mappable_regions[NUM_REGIONS] = {
+ { 0x40000, 0x1000 },
+ { 0x44000, 0x1000 },
+ { 0x48000, 0x1000 },
+};
+
+/* Gasket device interrupts enums must be dense (i.e., no empty slots). */
+enum apex_interrupt {
+ APEX_INTERRUPT_INSTR_QUEUE = 0,
+ APEX_INTERRUPT_INPUT_ACTV_QUEUE = 1,
+ APEX_INTERRUPT_PARAM_QUEUE = 2,
+ APEX_INTERRUPT_OUTPUT_ACTV_QUEUE = 3,
+ APEX_INTERRUPT_SC_HOST_0 = 4,
+ APEX_INTERRUPT_SC_HOST_1 = 5,
+ APEX_INTERRUPT_SC_HOST_2 = 6,
+ APEX_INTERRUPT_SC_HOST_3 = 7,
+ APEX_INTERRUPT_TOP_LEVEL_0 = 8,
+ APEX_INTERRUPT_TOP_LEVEL_1 = 9,
+ APEX_INTERRUPT_TOP_LEVEL_2 = 10,
+ APEX_INTERRUPT_TOP_LEVEL_3 = 11,
+ APEX_INTERRUPT_FATAL_ERR = 12,
+ APEX_INTERRUPT_COUNT = 13,
+};
+
+/* Interrupt descriptors for Apex */
+static struct gasket_interrupt_desc apex_interrupts[] = {
+ {
+ APEX_INTERRUPT_INSTR_QUEUE,
+ APEX_BAR2_REG_KERNEL_HIB_INSTR_QUEUE_INTVECCTL,
+ UNPACKED,
+ },
+ {
+ APEX_INTERRUPT_INPUT_ACTV_QUEUE,
+ APEX_BAR2_REG_KERNEL_HIB_INPUT_ACTV_QUEUE_INTVECCTL,
+ UNPACKED
+ },
+ {
+ APEX_INTERRUPT_PARAM_QUEUE,
+ APEX_BAR2_REG_KERNEL_HIB_PARAM_QUEUE_INTVECCTL,
+ UNPACKED
+ },
+ {
+ APEX_INTERRUPT_OUTPUT_ACTV_QUEUE,
+ APEX_BAR2_REG_KERNEL_HIB_OUTPUT_ACTV_QUEUE_INTVECCTL,
+ UNPACKED
+ },
+ {
+ APEX_INTERRUPT_SC_HOST_0,
+ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL,
+ PACK_0
+ },
+ {
+ APEX_INTERRUPT_SC_HOST_1,
+ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL,
+ PACK_1
+ },
+ {
+ APEX_INTERRUPT_SC_HOST_2,
+ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL,
+ PACK_2
+ },
+ {
+ APEX_INTERRUPT_SC_HOST_3,
+ APEX_BAR2_REG_KERNEL_HIB_SC_HOST_INTVECCTL,
+ PACK_3
+ },
+ {
+ APEX_INTERRUPT_TOP_LEVEL_0,
+ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL,
+ PACK_0
+ },
+ {
+ APEX_INTERRUPT_TOP_LEVEL_1,
+ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL,
+ PACK_1
+ },
+ {
+ APEX_INTERRUPT_TOP_LEVEL_2,
+ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL,
+ PACK_2
+ },
+ {
+ APEX_INTERRUPT_TOP_LEVEL_3,
+ APEX_BAR2_REG_KERNEL_HIB_TOP_LEVEL_INTVECCTL,
+ PACK_3
+ },
+ {
+ APEX_INTERRUPT_FATAL_ERR,
+ APEX_BAR2_REG_KERNEL_HIB_FATAL_ERR_INTVECCTL,
+ UNPACKED
+ },
+};
+
+/* Allows device to enter power save upon driver close(). */
+static int allow_power_save = 1;
+
+/* Allows SW based clock gating. */
+static int allow_sw_clock_gating;
+
+/* Allows HW based clock gating. */
+/* Note: this is not mutual exclusive with SW clock gating. */
+static int allow_hw_clock_gating = 1;
+
+/* Act as if only GCB is instantiated. */
+static int bypass_top_level;
+
+module_param(allow_power_save, int, 0644);
+module_param(allow_sw_clock_gating, int, 0644);
+module_param(allow_hw_clock_gating, int, 0644);
+module_param(bypass_top_level, int, 0644);
+
+/* Temperature points in milli C at which DFS is toggled */
+#define DEFAULT_TRIP_POINT0_TEMP 85000
+#define DEFAULT_TRIP_POINT1_TEMP 90000
+#define DEFAULT_TRIP_POINT2_TEMP 95000
+
+static int trip_point0_temp = DEFAULT_TRIP_POINT0_TEMP;
+static int trip_point1_temp = DEFAULT_TRIP_POINT1_TEMP;
+static int trip_point2_temp = DEFAULT_TRIP_POINT2_TEMP;
+
+module_param(trip_point0_temp, int, 0644);
+module_param(trip_point1_temp, int, 0644);
+module_param(trip_point2_temp, int, 0644);
+
+/* Hardware monitored temperature trip points in milli C
+ Apex chip drives INTR line when reaching hw_temp_warn1 temperature,
+ and SD_ALARM line when reaching hw_temp_warn2 if corresponding
+ hw_temp_warn*_en is set to true.
+ */
+static int hw_temp_warn1 = 100000;
+static int hw_temp_warn2 = 110000;
+static bool hw_temp_warn1_en = false;
+static bool hw_temp_warn2_en = false;
+
+module_param(hw_temp_warn1, int, 0644);
+module_param(hw_temp_warn2, int, 0644);
+module_param(hw_temp_warn1_en, bool, 0644);
+module_param(hw_temp_warn2_en, bool, 0644);
+
+/* Temperature poll interval in ms */
+static int temp_poll_interval = DEFAULT_APEX_TEMP_POLL_INTERVAL;
+module_param(temp_poll_interval, int, 0644);
+
+/* Check the device status registers and return device status ALIVE or DEAD. */
+static int apex_get_status(struct gasket_dev *gasket_dev)
+{
+ /* TODO: Check device status. */
+ return GASKET_STATUS_ALIVE;
+}
+
+/* Enter GCB reset state. */
+static int apex_enter_reset(struct gasket_dev *gasket_dev)
+{
+ if (bypass_top_level)
+ return 0;
+
+ /*
+ * Software reset:
+ * Enable sleep mode
+ * - Software force GCB idle
+ * - Enable GCB idle
+ */
+ gasket_read_modify_write_64(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_IDLEGENERATOR_IDLEGEN_IDLEREGISTER,
+ 0x0, 1, 32);
+
+ /* - Initiate DMA pause */
+ gasket_dev_write_64(gasket_dev, 1, APEX_BAR_INDEX,
+ APEX_BAR2_REG_USER_HIB_DMA_PAUSE);
+
+ /* - Wait for DMA pause complete. */
+ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_USER_HIB_DMA_PAUSED, 1, 1,
+ APEX_RESET_DELAY, APEX_RESET_RETRY)) {
+ dev_err(gasket_dev->dev,
+ "DMAs did not quiesce within timeout (%d ms)\n",
+ APEX_RESET_RETRY * APEX_RESET_DELAY);
+ return -ETIMEDOUT;
+ }
+
+ /* - Enable GCB reset (0x1 to rg_rst_gcb) */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_2, 0x1, 2, 2);
+
+ /* - Enable GCB clock Gate (0x1 to rg_gated_gcb) */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_2, 0x1, 2, 18);
+
+ /* - Enable GCB memory shut down (0x3 to rg_force_ram_sd) */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3, 0x3, 2, 14);
+
+ /* - Wait for RAM shutdown. */
+ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3, 1 << 6, 1 << 6,
+ APEX_RESET_DELAY, APEX_RESET_RETRY)) {
+ dev_err(gasket_dev->dev,
+ "RAM did not shut down within timeout (%d ms)\n",
+ APEX_RESET_RETRY * APEX_RESET_DELAY);
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+/* Quit GCB reset state. */
+static int apex_quit_reset(struct gasket_dev *gasket_dev)
+{
+ u32 val0, val1;
+
+ if (bypass_top_level)
+ return 0;
+
+ /*
+ * Disable sleep mode:
+ * - Disable GCB memory shut down:
+ * - b00: Not forced (HW controlled)
+ * - b1x: Force disable
+ */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3, 0x0, 2, 14);
+
+ /*
+ * - Disable software clock gate:
+ * - b00: Not forced (HW controlled)
+ * - b1x: Force disable
+ */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_2, 0x0, 2, 18);
+
+ /*
+ * - Disable GCB reset (rg_rst_gcb):
+ * - b00: Not forced (HW controlled)
+ * - b1x: Force disable = Force not Reset
+ */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_2, 0x2, 2, 2);
+
+ /* - Wait for RAM enable. */
+ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3, 1 << 6, 0,
+ APEX_RESET_DELAY, APEX_RESET_RETRY)) {
+ dev_err(gasket_dev->dev,
+ "RAM did not enable within timeout (%d ms)\n",
+ APEX_RESET_RETRY * APEX_RESET_DELAY);
+ return -ETIMEDOUT;
+ }
+
+ /* - Wait for Reset complete. */
+ if (gasket_wait_with_reschedule(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3,
+ SCU3_CUR_RST_GCB_BIT_MASK, 0,
+ APEX_RESET_DELAY, APEX_RESET_RETRY)) {
+ dev_err(gasket_dev->dev,
+ "GCB did not leave reset within timeout (%d ms)\n",
+ APEX_RESET_RETRY * APEX_RESET_DELAY);
+ return -ETIMEDOUT;
+ }
+
+ if (!allow_hw_clock_gating) {
+ val0 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3);
+ /* Inactive and Sleep mode are disabled. */
+ gasket_read_modify_write_32(gasket_dev,
+ APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3, 0x3,
+ SCU3_RG_PWR_STATE_OVR_MASK_WIDTH,
+ SCU3_RG_PWR_STATE_OVR_BIT_OFFSET);
+ val1 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3);
+ dev_dbg(gasket_dev->dev,
+ "Disallow HW clock gating 0x%x -> 0x%x\n", val0, val1);
+ } else {
+ val0 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3);
+ /* Inactive mode enabled - Sleep mode disabled. */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3, 2,
+ SCU3_RG_PWR_STATE_OVR_MASK_WIDTH,
+ SCU3_RG_PWR_STATE_OVR_BIT_OFFSET);
+ val1 = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3);
+ dev_dbg(gasket_dev->dev, "Allow HW clock gating 0x%x -> 0x%x\n",
+ val0, val1);
+ }
+
+ return 0;
+}
+
+/* Reset the Apex hardware. Called on final close via device_close_cb. */
+static int apex_device_cleanup(struct gasket_dev *gasket_dev)
+{
+ u64 scalar_error;
+ u64 hib_error;
+ int ret = 0;
+
+ hib_error = gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_USER_HIB_ERROR_STATUS);
+ scalar_error = gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCALAR_CORE_ERROR_STATUS);
+
+ dev_dbg(gasket_dev->dev,
+ "%s 0x%p hib_error 0x%llx scalar_error 0x%llx\n",
+ __func__, gasket_dev, hib_error, scalar_error);
+
+ if (allow_power_save)
+ ret = apex_enter_reset(gasket_dev);
+
+ return ret;
+}
+
+/* Determine if GCB is in reset state. */
+static bool is_gcb_in_reset(struct gasket_dev *gasket_dev)
+{
+ u32 val = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3);
+
+ /* Masks rg_rst_gcb bit of SCU_CTRL_2 */
+ return (val & SCU3_CUR_RST_GCB_BIT_MASK);
+}
+
+/* Reset the hardware, then quit reset. Called on device open. */
+static int apex_reset(struct gasket_dev *gasket_dev)
+{
+ int ret;
+
+ if (bypass_top_level)
+ return 0;
+
+ if (!is_gcb_in_reset(gasket_dev)) {
+ /* We are not in reset - toggle the reset bit so as to force
+ * re-init of custom block
+ */
+ dev_dbg(gasket_dev->dev, "%s: toggle reset\n", __func__);
+
+ ret = apex_enter_reset(gasket_dev);
+ if (ret)
+ return ret;
+ }
+ ret = apex_quit_reset(gasket_dev);
+
+ return ret;
+}
+
+/*
+ * Check permissions for Apex ioctls.
+ * Returns true if the current user may execute this ioctl, and false otherwise.
+ */
+static bool apex_ioctl_check_permissions(struct file *filp, uint cmd)
+{
+ return !!(filp->f_mode & FMODE_WRITE);
+}
+
+/* Gates or un-gates Apex clock. */
+static long apex_clock_gating(struct gasket_dev *gasket_dev,
+ struct apex_gate_clock_ioctl __user *argp)
+{
+ struct apex_gate_clock_ioctl ibuf;
+
+ if (bypass_top_level || !allow_sw_clock_gating)
+ return 0;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf)))
+ return -EFAULT;
+
+ dev_dbg(gasket_dev->dev, "%s %llu\n", __func__, ibuf.enable);
+
+ if (ibuf.enable) {
+ /* Quiesce AXI, gate GCB clock. */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_AXI_QUIESCE, 0x1, 1,
+ 16);
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_GCB_CLOCK_GATE, 0x1,
+ 2, 18);
+ } else {
+ /* Un-gate GCB clock, un-quiesce AXI. */
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_GCB_CLOCK_GATE, 0x0,
+ 2, 18);
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_AXI_QUIESCE, 0x0, 1,
+ 16);
+ }
+ return 0;
+}
+
+/* apex_set_performance_expectation: Adjust clock rates for Apex. */
+static long apex_set_performance_expectation(
+ struct gasket_dev *gasket_dev,
+ struct apex_performance_expectation_ioctl __user *argp)
+{
+ struct apex_performance_expectation_ioctl ibuf;
+ uint32_t rg_gcb_clk_div = 0;
+ uint32_t rg_axi_clk_fixed = 0;
+ const int AXI_CLK_FIXED_SHIFT = 2;
+ const int MCU_CLK_FIXED_SHIFT = 3;
+
+ // 8051 clock is fixed for PCIe, as it's not used at all.
+ const uint32_t rg_8051_clk_fixed = 1;
+
+ if (bypass_top_level)
+ return 0;
+
+ if (copy_from_user(&ibuf, argp, sizeof(ibuf)))
+ return -EFAULT;
+
+ switch (ibuf.performance) {
+ case APEX_PERFORMANCE_LOW:
+ rg_gcb_clk_div = 3;
+ rg_axi_clk_fixed = 0;
+ break;
+
+ case APEX_PERFORMANCE_MED:
+ rg_gcb_clk_div = 2;
+ rg_axi_clk_fixed = 0;
+ break;
+
+ case APEX_PERFORMANCE_HIGH:
+ rg_gcb_clk_div = 1;
+ rg_axi_clk_fixed = 0;
+ break;
+
+ case APEX_PERFORMANCE_MAX:
+ rg_gcb_clk_div = 0;
+ rg_axi_clk_fixed = 0;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * Set clock rates for GCB, AXI, and 8051:
+ */
+ gasket_read_modify_write_32(
+ gasket_dev, APEX_BAR_INDEX, APEX_BAR2_REG_SCU_3,
+ (rg_gcb_clk_div | (rg_axi_clk_fixed << AXI_CLK_FIXED_SHIFT) | (rg_8051_clk_fixed << MCU_CLK_FIXED_SHIFT)),
+ /*mask_width=*/4, /*mask_shift=*/28);
+
+ return 0;
+}
+
+/* Apex-specific ioctl handler. */
+static long apex_ioctl(struct file *filp, uint cmd, void __user *argp)
+{
+ struct gasket_dev *gasket_dev = filp->private_data;
+
+ if (!apex_ioctl_check_permissions(filp, cmd))
+ return -EPERM;
+
+ switch (cmd) {
+ case APEX_IOCTL_GATE_CLOCK:
+ return apex_clock_gating(gasket_dev, argp);
+ case APEX_IOCTL_PERFORMANCE_EXPECTATION:
+ return apex_set_performance_expectation(gasket_dev, argp);
+ default:
+ return -ENOTTY; /* unknown command */
+ }
+}
+
+/* Linear fit optimized for 25C-100C */
+static int adc_to_millic(int adc)
+{
+ return (662 - adc) * 250 + 550;
+}
+
+static int millic_to_adc(int millic)
+{
+ return (550 - millic) / 250 + 662;
+}
+
+/* Display driver sysfs entries. */
+static ssize_t sysfs_show(struct device *device, struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ unsigned value;
+ struct gasket_dev *gasket_dev;
+ struct apex_dev *apex_dev;
+ struct gasket_sysfs_attribute *gasket_attr;
+ enum sysfs_attribute_type type;
+
+ gasket_dev = gasket_sysfs_get_device_data(device);
+ if (!gasket_dev) {
+ dev_err(device, "No Apex device sysfs mapping found\n");
+ return -ENODEV;
+ }
+
+ if (!gasket_dev->pci_dev ||
+ !(apex_dev = pci_get_drvdata(gasket_dev->pci_dev))) {
+ dev_err(device, "Can't find apex_dev data\n");
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return -ENODEV;
+ }
+
+ gasket_attr = gasket_sysfs_get_attr(device, attr);
+ if (!gasket_attr) {
+ dev_err(device, "No Apex device sysfs attr data found\n");
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return -ENODEV;
+ }
+
+ type = (enum sysfs_attribute_type)gasket_attr->data.attr_type;
+ switch (type) {
+ case ATTR_KERNEL_HIB_PAGE_TABLE_SIZE:
+ ret = scnprintf(buf, PAGE_SIZE, "%u\n",
+ gasket_page_table_num_entries(
+ gasket_dev->page_table[0]));
+ break;
+ case ATTR_KERNEL_HIB_SIMPLE_PAGE_TABLE_SIZE:
+ ret = scnprintf(buf, PAGE_SIZE, "%u\n",
+ gasket_page_table_num_entries(
+ gasket_dev->page_table[0]));
+ break;
+ case ATTR_KERNEL_HIB_NUM_ACTIVE_PAGES:
+ ret = scnprintf(buf, PAGE_SIZE, "%u\n",
+ gasket_page_table_num_active_pages(
+ gasket_dev->page_table[0]));
+ break;
+ case ATTR_TEMP:
+ value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_DC);
+ value = (value >> 16) & ((1 << 10) - 1);
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n", adc_to_millic(value));
+ break;
+ case ATTR_TEMP_WARN1:
+ value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D4);
+ value = (value >> 16) & ((1 << 10) - 1);
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n", adc_to_millic(value));
+ break;
+ case ATTR_TEMP_WARN2:
+ value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D8);
+ value = (value >> 16) & ((1 << 10) - 1);
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n", adc_to_millic(value));
+ break;
+ case ATTR_TEMP_WARN1_EN:
+ value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D4);
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n", value >> 31);
+ break;
+ case ATTR_TEMP_WARN2_EN:
+ value = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D8);
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n", value >> 31);
+ break;
+ case ATTR_TEMP_TRIP0:
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n",
+ adc_to_millic(apex_dev->adc_trip_points[0]));
+ break;
+ case ATTR_TEMP_TRIP1:
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n",
+ adc_to_millic(apex_dev->adc_trip_points[1]));
+ break;
+ case ATTR_TEMP_TRIP2:
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n",
+ adc_to_millic(apex_dev->adc_trip_points[2]));
+ break;
+ case ATTR_TEMP_POLL_INTERVAL:
+ ret = scnprintf(buf, PAGE_SIZE, "%i\n",
+ atomic_read(&apex_dev->temp_poll_interval));
+ break;
+ default:
+ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n",
+ attr->attr.name);
+ ret = 0;
+ break;
+ }
+
+ gasket_sysfs_put_attr(device, gasket_attr);
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return ret;
+}
+
+/* Set driver sysfs entries. */
+static ssize_t sysfs_store(struct device *device, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret = count, value;
+ struct gasket_dev *gasket_dev;
+ struct apex_dev *apex_dev;
+ struct gasket_sysfs_attribute *gasket_attr;
+ enum sysfs_attribute_type type;
+
+ if (kstrtoint(buf, 10, &value))
+ return -EINVAL;
+
+ gasket_dev = gasket_sysfs_get_device_data(device);
+ if (!gasket_dev) {
+ dev_err(device, "No Apex device sysfs mapping found\n");
+ return -ENODEV;
+ }
+
+ if (!gasket_dev->pci_dev ||
+ !(apex_dev = pci_get_drvdata(gasket_dev->pci_dev))) {
+ dev_err(device, "Can't find apex_dev data\n");
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return -ENODEV;
+ }
+
+ gasket_attr = gasket_sysfs_get_attr(device, attr);
+ if (!gasket_attr) {
+ dev_err(device, "No Apex device sysfs attr data found\n");
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return -ENODEV;
+ }
+
+ type = (enum sysfs_attribute_type)gasket_attr->data.attr_type;
+ switch (type) {
+ case ATTR_TEMP_WARN1:
+ value = millic_to_adc(value);
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D4, value, 10,
+ 16);
+ break;
+ case ATTR_TEMP_WARN2:
+ value = millic_to_adc(value);
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D8, value, 10,
+ 16);
+ break;
+ case ATTR_TEMP_WARN1_EN:
+ value = value > 0 ? 1 : 0;
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D4, value, 1,
+ 31);
+ break;
+ case ATTR_TEMP_WARN2_EN:
+ value = value > 0 ? 1 : 0;
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D8, value, 1,
+ 31);
+ break;
+ case ATTR_TEMP_TRIP0:
+ value = millic_to_adc(value);
+ /* Note: that adc values should be in descending order */
+ if (value >= apex_dev->adc_trip_points[1]) {
+ apex_dev->adc_trip_points[0] = value;
+ } else ret = -EINVAL;
+ break;
+ case ATTR_TEMP_TRIP1:
+ value = millic_to_adc(value);
+ if (value <= apex_dev->adc_trip_points[0] &&
+ value >= apex_dev->adc_trip_points[2]) {
+ apex_dev->adc_trip_points[1] = value;
+ } else ret = -EINVAL;
+ break;
+ case ATTR_TEMP_TRIP2:
+ value = millic_to_adc(value);
+ if (value <= apex_dev->adc_trip_points[1]) {
+ apex_dev->adc_trip_points[2] = value;
+ } else ret = -EINVAL;
+ break;
+ case ATTR_TEMP_POLL_INTERVAL:
+ cancel_delayed_work_sync(&apex_dev->check_temperature_work);
+ atomic_set(&apex_dev->temp_poll_interval, value);
+ if (value > 0)
+ schedule_delayed_work(&apex_dev->check_temperature_work,
+ msecs_to_jiffies(value));
+
+ break;
+ default:
+ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n",
+ attr->attr.name);
+ ret = 0;
+ break;
+ }
+
+ gasket_sysfs_put_attr(device, gasket_attr);
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return ret;
+}
+
+static struct gasket_sysfs_attribute apex_sysfs_attrs[] = {
+ GASKET_SYSFS_RO(node_0_page_table_entries, sysfs_show,
+ ATTR_KERNEL_HIB_PAGE_TABLE_SIZE),
+ GASKET_SYSFS_RO(node_0_simple_page_table_entries, sysfs_show,
+ ATTR_KERNEL_HIB_SIMPLE_PAGE_TABLE_SIZE),
+ GASKET_SYSFS_RO(node_0_num_mapped_pages, sysfs_show,
+ ATTR_KERNEL_HIB_NUM_ACTIVE_PAGES),
+ GASKET_SYSFS_RO(temp, sysfs_show, ATTR_TEMP),
+ GASKET_SYSFS_RW(hw_temp_warn1, sysfs_show, sysfs_store,
+ ATTR_TEMP_WARN1),
+ GASKET_SYSFS_RW(hw_temp_warn1_en, sysfs_show, sysfs_store,
+ ATTR_TEMP_WARN1_EN),
+ GASKET_SYSFS_RW(hw_temp_warn2, sysfs_show, sysfs_store,
+ ATTR_TEMP_WARN2),
+ GASKET_SYSFS_RW(hw_temp_warn2_en, sysfs_show, sysfs_store,
+ ATTR_TEMP_WARN2_EN),
+ GASKET_SYSFS_RW(trip_point0_temp, sysfs_show, sysfs_store,
+ ATTR_TEMP_TRIP0),
+ GASKET_SYSFS_RW(trip_point1_temp, sysfs_show, sysfs_store,
+ ATTR_TEMP_TRIP1),
+ GASKET_SYSFS_RW(trip_point2_temp, sysfs_show, sysfs_store,
+ ATTR_TEMP_TRIP2),
+ GASKET_SYSFS_RW(temp_poll_interval, sysfs_show, sysfs_store,
+ ATTR_TEMP_POLL_INTERVAL),
+ GASKET_END_OF_ATTR_ARRAY
+};
+
+static void apply_module_params(struct apex_dev *apex_dev) {
+ kernel_param_lock(THIS_MODULE);
+
+ /* use defaults if trip point temperatures are not in ascending order */
+ if (trip_point0_temp > trip_point1_temp ||
+ trip_point1_temp > trip_point2_temp) {
+ dev_warn(apex_dev->gasket_dev_ptr->dev,
+ "Invalid module parameters for temperature trip points"
+ ", using defaults\n");
+ trip_point0_temp = DEFAULT_TRIP_POINT0_TEMP;
+ trip_point1_temp = DEFAULT_TRIP_POINT1_TEMP;
+ trip_point2_temp = DEFAULT_TRIP_POINT2_TEMP;
+ }
+
+ apex_dev->adc_trip_points[0] = millic_to_adc(trip_point0_temp);
+ apex_dev->adc_trip_points[1] = millic_to_adc(trip_point1_temp);
+ apex_dev->adc_trip_points[2] = millic_to_adc(trip_point2_temp);
+ atomic_set(&apex_dev->temp_poll_interval, temp_poll_interval);
+
+ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D4,
+ millic_to_adc(hw_temp_warn1), 10, 16);
+ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D8,
+ millic_to_adc(hw_temp_warn2), 10, 16);
+ if (hw_temp_warn1_en)
+ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr,
+ APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D4, 1, 1, 31);
+
+ if (hw_temp_warn2_en)
+ gasket_read_modify_write_32(apex_dev->gasket_dev_ptr,
+ APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D8, 1, 1, 31);
+
+ kernel_param_unlock(THIS_MODULE);
+}
+
+static void check_temperature_work_handler(struct work_struct *work) {
+ int i;
+ u32 adc_temp, clk_div, tmp;
+ const u32 mask = ((1 << 2) - 1) << 28;
+ struct apex_dev *apex_dev =
+ container_of(work, struct apex_dev,
+ check_temperature_work.work);
+ struct gasket_dev *gasket_dev = apex_dev->gasket_dev_ptr;
+
+ mutex_lock(&gasket_dev->mutex);
+
+ /* Read current temperature */
+ adc_temp = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_DC);
+ adc_temp = (adc_temp >> 16) & ((1 << 10) - 1);
+
+ /* Find closest trip point
+ Note: that adc values are in descending order */
+ for (i = ARRAY_SIZE(apex_dev->adc_trip_points) - 1; i >= 0; --i) {
+ if (adc_temp <= apex_dev->adc_trip_points[i])
+ break;
+ }
+ /* Compute divider value and shift into appropriate bit location */
+ clk_div = (i + 1) << 28;
+
+ /* Modify gcb clk divider if it's different from current one */
+ tmp = gasket_dev_read_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3);
+ if (clk_div != (tmp & mask)) {
+ tmp = (tmp & ~mask) | clk_div;
+ gasket_dev_write_32(gasket_dev, tmp, APEX_BAR_INDEX,
+ APEX_BAR2_REG_SCU_3);
+ dev_warn(gasket_dev->dev,
+ "Apex performance %sthrottled due to temperature\n",
+ i == -1 ? "not " : "");
+ }
+
+ mutex_unlock(&gasket_dev->mutex);
+
+ int temp_poll_interval = atomic_read(&apex_dev->temp_poll_interval);
+ if (temp_poll_interval > 0)
+ schedule_delayed_work(&apex_dev->check_temperature_work,
+ msecs_to_jiffies(temp_poll_interval));
+}
+
+/* On device open, perform a core reinit reset. */
+static int apex_device_open_cb(struct gasket_dev *gasket_dev)
+{
+ return gasket_reset_nolock(gasket_dev);
+}
+
+static const struct pci_device_id apex_pci_ids[] = {
+ { PCI_DEVICE(APEX_PCI_VENDOR_ID, APEX_PCI_DEVICE_ID) }, { 0 }
+};
+
+static void apex_pci_fixup_class(struct pci_dev *pdev)
+{
+ pdev->class = (PCI_CLASS_SYSTEM_OTHER << 8) | pdev->class;
+}
+DECLARE_PCI_FIXUP_CLASS_HEADER(APEX_PCI_VENDOR_ID, APEX_PCI_DEVICE_ID,
+ PCI_ANY_ID, 8, apex_pci_fixup_class);
+
+static int apex_pci_probe(struct pci_dev *pci_dev,
+ const struct pci_device_id *id)
+{
+ int ret;
+ ulong page_table_ready, msix_table_ready;
+ int retries = 0;
+ struct gasket_dev *gasket_dev;
+ struct apex_dev *apex_dev;
+
+ ret = pci_enable_device(pci_dev);
+#ifdef MODULE
+ if (ret) {
+ apex_pci_fixup_class(pci_dev);
+ pci_bus_assign_resources(pci_dev->bus);
+ ret = pci_enable_device(pci_dev);
+ }
+#endif
+ if (ret) {
+ dev_err(&pci_dev->dev, "error enabling PCI device\n");
+ return ret;
+ }
+
+ pci_set_master(pci_dev);
+
+ ret = gasket_pci_add_device(pci_dev, &gasket_dev);
+ if (ret) {
+ dev_err(&pci_dev->dev, "error adding gasket device\n");
+ pci_disable_device(pci_dev);
+ return ret;
+ }
+
+ apex_dev = kzalloc(sizeof(*apex_dev), GFP_KERNEL);
+ if (!apex_dev) {
+ dev_err(&pci_dev->dev, "no memory for device\n");
+ ret = -ENOMEM;
+ goto remove_device;
+ }
+
+ INIT_DELAYED_WORK(&apex_dev->check_temperature_work,
+ check_temperature_work_handler);
+ apex_dev->gasket_dev_ptr = gasket_dev;
+ apply_module_params(apex_dev);
+ pci_set_drvdata(pci_dev, apex_dev);
+ apex_reset(gasket_dev);
+
+ while (retries < APEX_RESET_RETRY) {
+ page_table_ready =
+ gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_KERNEL_HIB_PAGE_TABLE_INIT);
+ msix_table_ready =
+ gasket_dev_read_64(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE_INIT);
+ if (page_table_ready && msix_table_ready)
+ break;
+ schedule_timeout(msecs_to_jiffies(APEX_RESET_DELAY));
+ retries++;
+ }
+
+ if (retries == APEX_RESET_RETRY) {
+ if (!page_table_ready)
+ dev_err(gasket_dev->dev, "Page table init timed out\n");
+ if (!msix_table_ready)
+ dev_err(gasket_dev->dev, "MSI-X table init timed out\n");
+ ret = -ETIMEDOUT;
+ goto remove_device;
+ }
+
+ // Enable thermal sensor clocks
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D0, 0x1, 1, 7);
+
+ // Enable thermal sensor (ENAD ENVR ENBG)
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_D8, 0x7, 3, 0);
+
+ // Enable OMC thermal sensor controller
+ // This bit should be asserted 100 us after ENAD ENVR ENBG
+ schedule_timeout(usecs_to_jiffies(100));
+ gasket_read_modify_write_32(gasket_dev, APEX_BAR_INDEX,
+ APEX_BAR2_REG_OMC0_DC, 0x1, 1, 0);
+
+ ret = gasket_sysfs_create_entries(gasket_dev->dev_info.device,
+ apex_sysfs_attrs);
+ if (ret)
+ dev_err(&pci_dev->dev, "error creating device sysfs entries\n");
+
+ ret = gasket_enable_device(gasket_dev);
+ if (ret) {
+ dev_err(&pci_dev->dev, "error enabling gasket device\n");
+ goto remove_device;
+ }
+
+ /* Place device in low power mode until opened */
+ if (allow_power_save)
+ apex_enter_reset(gasket_dev);
+
+ /* Enable thermal polling */
+ int temp_poll_interval = atomic_read(&apex_dev->temp_poll_interval);
+ if (temp_poll_interval > 0)
+ schedule_delayed_work(&apex_dev->check_temperature_work,
+ msecs_to_jiffies(temp_poll_interval));
+ return 0;
+
+remove_device:
+ gasket_pci_remove_device(pci_dev);
+ pci_disable_device(pci_dev);
+ kfree(apex_dev);
+ return ret;
+}
+
+static void apex_pci_remove(struct pci_dev *pci_dev)
+{
+ struct apex_dev *apex_dev = pci_get_drvdata(pci_dev);
+ struct gasket_dev *gasket_dev;
+
+ if (!apex_dev) {
+ dev_err(&pci_dev->dev, "NULL apex_dev\n");
+ goto remove_device;
+ }
+ gasket_dev = apex_dev->gasket_dev_ptr;
+
+ cancel_delayed_work_sync(&apex_dev->check_temperature_work);
+ kfree(apex_dev);
+
+ gasket_disable_device(gasket_dev);
+remove_device:
+ gasket_pci_remove_device(pci_dev);
+ pci_disable_device(pci_dev);
+}
+
+static struct gasket_driver_desc apex_desc = {
+ .name = "apex",
+ .driver_version = APEX_DRIVER_VERSION,
+ .major = 120,
+ .minor = 0,
+ .module = THIS_MODULE,
+ .pci_id_table = apex_pci_ids,
+
+ .num_page_tables = NUM_NODES,
+ .page_table_bar_index = APEX_BAR_INDEX,
+ .page_table_configs = apex_page_table_configs,
+ .page_table_extended_bit = APEX_EXTENDED_SHIFT,
+
+ .bar_descriptions = {
+ GASKET_UNUSED_BAR,
+ GASKET_UNUSED_BAR,
+ { APEX_BAR_BYTES, (VM_WRITE | VM_READ), APEX_BAR_OFFSET,
+ NUM_REGIONS, mappable_regions, PCI_BAR },
+ GASKET_UNUSED_BAR,
+ GASKET_UNUSED_BAR,
+ GASKET_UNUSED_BAR,
+ },
+ .coherent_buffer_description = {
+ APEX_CH_MEM_BYTES,
+ (VM_WRITE | VM_READ),
+ APEX_CM_OFFSET,
+ },
+ .interrupt_type = PCI_MSIX,
+ .interrupt_bar_index = APEX_BAR_INDEX,
+ .num_interrupts = APEX_INTERRUPT_COUNT,
+ .interrupts = apex_interrupts,
+ .interrupt_pack_width = 7,
+
+ .device_open_cb = apex_device_open_cb,
+ .device_close_cb = apex_device_cleanup,
+
+ .ioctl_handler_cb = apex_ioctl,
+ .device_status_cb = apex_get_status,
+ .hardware_revision_cb = NULL,
+ .device_reset_cb = apex_reset,
+};
+
+static struct pci_driver apex_pci_driver = {
+ .name = "apex",
+ .probe = apex_pci_probe,
+ .remove = apex_pci_remove,
+ .id_table = apex_pci_ids,
+};
+
+static int __init apex_init(void)
+{
+ int ret;
+
+ ret = gasket_register_device(&apex_desc);
+ if (ret)
+ return ret;
+ ret = pci_register_driver(&apex_pci_driver);
+ if (ret)
+ gasket_unregister_device(&apex_desc);
+ return ret;
+}
+
+static void apex_exit(void)
+{
+ pci_unregister_driver(&apex_pci_driver);
+ gasket_unregister_device(&apex_desc);
+}
+MODULE_DESCRIPTION("Google Apex driver");
+MODULE_VERSION(APEX_DRIVER_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("John Joseph <jnjoseph@google.com>");
+MODULE_DEVICE_TABLE(pci, apex_pci_ids);
+module_init(apex_init);
+module_exit(apex_exit);
diff --git a/drivers/staging/gasket/gasket.h b/drivers/staging/gasket/gasket.h
new file mode 100644
index 0000000..a0f065c
--- /dev/null
+++ b/drivers/staging/gasket/gasket.h
@@ -0,0 +1,122 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Common Gasket device kernel and user space declarations.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+#ifndef __GASKET_H__
+#define __GASKET_H__
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/* ioctl structure declarations */
+
+/* Ioctl structures are padded to a multiple of 64 bits */
+/* and padded to put 64 bit values on 64 bit boundaries. */
+/* Unsigned 64 bit integers are used to hold pointers. */
+/* This helps compatibility between 32 and 64 bits. */
+
+/*
+ * Common structure for ioctls associating an eventfd with a device interrupt,
+ * when using the Gasket interrupt module.
+ */
+struct gasket_interrupt_eventfd {
+ u64 interrupt;
+ u64 event_fd;
+};
+
+/*
+ * Common structure for ioctls mapping and unmapping buffers when using the
+ * Gasket page_table module.
+ */
+struct gasket_page_table_ioctl {
+ u64 page_table_index;
+ u64 size;
+ u64 host_address;
+ u64 device_address;
+};
+
+/*
+ * Common structure for ioctls mapping and unmapping buffers when using the
+ * Gasket page_table module.
+ * dma_address: phys addr start of coherent memory, allocated by kernel
+ */
+struct gasket_coherent_alloc_config_ioctl {
+ u64 page_table_index;
+ u64 enable;
+ u64 size;
+ u64 dma_address;
+};
+
+/* Base number for all Gasket-common IOCTLs */
+#define GASKET_IOCTL_BASE 0xDC
+
+/* Reset the device. */
+#define GASKET_IOCTL_RESET _IO(GASKET_IOCTL_BASE, 0)
+
+/* Associate the specified [event]fd with the specified interrupt. */
+#define GASKET_IOCTL_SET_EVENTFD \
+ _IOW(GASKET_IOCTL_BASE, 1, struct gasket_interrupt_eventfd)
+
+/*
+ * Clears any eventfd associated with the specified interrupt. The (ulong)
+ * argument is the interrupt number to clear.
+ */
+#define GASKET_IOCTL_CLEAR_EVENTFD _IOW(GASKET_IOCTL_BASE, 2, unsigned long)
+
+/*
+ * [Loopbacks only] Requests that the loopback device send the specified
+ * interrupt to the host. The (ulong) argument is the number of the interrupt to
+ * send.
+ */
+#define GASKET_IOCTL_LOOPBACK_INTERRUPT \
+ _IOW(GASKET_IOCTL_BASE, 3, unsigned long)
+
+/* Queries the kernel for the number of page tables supported by the device. */
+#define GASKET_IOCTL_NUMBER_PAGE_TABLES _IOR(GASKET_IOCTL_BASE, 4, u64)
+
+/*
+ * Queries the kernel for the maximum size of the page table. Only the size and
+ * page_table_index fields are used from the struct gasket_page_table_ioctl.
+ */
+#define GASKET_IOCTL_PAGE_TABLE_SIZE \
+ _IOWR(GASKET_IOCTL_BASE, 5, struct gasket_page_table_ioctl)
+
+/*
+ * Queries the kernel for the current simple page table size. Only the size and
+ * page_table_index fields are used from the struct gasket_page_table_ioctl.
+ */
+#define GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE \
+ _IOWR(GASKET_IOCTL_BASE, 6, struct gasket_page_table_ioctl)
+
+/*
+ * Tells the kernel to change the split between the number of simple and
+ * extended entries in the given page table. Only the size and page_table_index
+ * fields are used from the struct gasket_page_table_ioctl.
+ */
+#define GASKET_IOCTL_PARTITION_PAGE_TABLE \
+ _IOW(GASKET_IOCTL_BASE, 7, struct gasket_page_table_ioctl)
+
+/*
+ * Tells the kernel to map size bytes at host_address to device_address in
+ * page_table_index page table.
+ */
+#define GASKET_IOCTL_MAP_BUFFER \
+ _IOW(GASKET_IOCTL_BASE, 8, struct gasket_page_table_ioctl)
+
+/*
+ * Tells the kernel to unmap size bytes at host_address from device_address in
+ * page_table_index page table.
+ */
+#define GASKET_IOCTL_UNMAP_BUFFER \
+ _IOW(GASKET_IOCTL_BASE, 9, struct gasket_page_table_ioctl)
+
+/* Clear the interrupt counts stored for this device. */
+#define GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS _IO(GASKET_IOCTL_BASE, 10)
+
+/* Enable/Disable and configure the coherent allocator. */
+#define GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR \
+ _IOWR(GASKET_IOCTL_BASE, 11, struct gasket_coherent_alloc_config_ioctl)
+
+#endif /* __GASKET_H__ */
diff --git a/drivers/staging/gasket/gasket_constants.h b/drivers/staging/gasket/gasket_constants.h
new file mode 100644
index 0000000..50d87c7
--- /dev/null
+++ b/drivers/staging/gasket/gasket_constants.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2018 Google, Inc. */
+#ifndef __GASKET_CONSTANTS_H__
+#define __GASKET_CONSTANTS_H__
+
+#define GASKET_FRAMEWORK_VERSION "1.1.2"
+
+/*
+ * The maximum number of simultaneous device types supported by the framework.
+ */
+#define GASKET_FRAMEWORK_DESC_MAX 2
+
+/* The maximum devices per each type. */
+#define GASKET_DEV_MAX 256
+
+/* The number of supported (and possible) PCI BARs. */
+#define GASKET_NUM_BARS 6
+
+/* The number of supported Gasket page tables per device. */
+#define GASKET_MAX_NUM_PAGE_TABLES 1
+
+/* Maximum length of device names (driver name + minor number suffix + NULL). */
+#define GASKET_NAME_MAX 32
+
+/* Device status enumeration. */
+enum gasket_status {
+ /*
+ * A device is DEAD if it has not been initialized or has had an error.
+ */
+ GASKET_STATUS_DEAD = 0,
+ /*
+ * A device is LAMED if the hardware is healthy but the kernel was
+ * unable to enable some functionality (e.g. interrupts).
+ */
+ GASKET_STATUS_LAMED,
+
+ /* A device is ALIVE if it is ready for operation. */
+ GASKET_STATUS_ALIVE,
+
+ /*
+ * This status is set when the driver is exiting and waiting for all
+ * handles to be closed.
+ */
+ GASKET_STATUS_DRIVER_EXIT,
+};
+
+#endif
diff --git a/drivers/staging/gasket/gasket_core.c b/drivers/staging/gasket/gasket_core.c
new file mode 100644
index 0000000..fe55ab0
--- /dev/null
+++ b/drivers/staging/gasket/gasket_core.c
@@ -0,0 +1,1823 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Gasket generic driver framework. This file contains the implementation
+ * for the Gasket generic driver framework - the functionality that is common
+ * across Gasket devices.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "gasket_core.h"
+
+#include "gasket_interrupt.h"
+#include "gasket_ioctl.h"
+#include "gasket_page_table.h"
+#include "gasket_sysfs.h"
+
+#include <linux/capability.h>
+#include <linux/compiler.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/pid_namespace.h>
+#include <linux/printk.h>
+#include <linux/sched.h>
+
+#ifdef GASKET_KERNEL_TRACE_SUPPORT
+#define CREATE_TRACE_POINTS
+#include <trace/events/gasket_mmap.h>
+#else
+#define trace_gasket_mmap_exit(x)
+#define trace_gasket_mmap_entry(x, ...)
+#endif
+
+/*
+ * "Private" members of gasket_driver_desc.
+ *
+ * Contains internal per-device type tracking data, i.e., data not appropriate
+ * as part of the public interface for the generic framework.
+ */
+struct gasket_internal_desc {
+ /* Device-specific-driver-provided configuration information. */
+ const struct gasket_driver_desc *driver_desc;
+
+ /* Protects access to per-driver data (i.e. this structure). */
+ struct mutex mutex;
+
+ /* Kernel-internal device class. */
+ struct class *class;
+
+ /* Instantiated / present devices of this type. */
+ struct gasket_dev *devs[GASKET_DEV_MAX];
+};
+
+/* do_map_region() needs be able to return more than just true/false. */
+enum do_map_region_status {
+ /* The region was successfully mapped. */
+ DO_MAP_REGION_SUCCESS,
+
+ /* Attempted to map region and failed. */
+ DO_MAP_REGION_FAILURE,
+
+ /* The requested region to map was not part of a mappable region. */
+ DO_MAP_REGION_INVALID,
+};
+
+/* Global data definitions. */
+/* Mutex - only for framework-wide data. Other data should be protected by
+ * finer-grained locks.
+ */
+static DEFINE_MUTEX(g_mutex);
+
+/* List of all registered device descriptions & their supporting data. */
+static struct gasket_internal_desc g_descs[GASKET_FRAMEWORK_DESC_MAX];
+
+/* Mapping of statuses to human-readable strings. Must end with {0,NULL}. */
+static const struct gasket_num_name gasket_status_name_table[] = {
+ { GASKET_STATUS_DEAD, "DEAD" },
+ { GASKET_STATUS_ALIVE, "ALIVE" },
+ { GASKET_STATUS_LAMED, "LAMED" },
+ { GASKET_STATUS_DRIVER_EXIT, "DRIVER_EXITING" },
+ { 0, NULL },
+};
+
+/* Enumeration of the automatic Gasket framework sysfs nodes. */
+enum gasket_sysfs_attribute_type {
+ ATTR_BAR_OFFSETS,
+ ATTR_BAR_SIZES,
+ ATTR_DRIVER_VERSION,
+ ATTR_FRAMEWORK_VERSION,
+ ATTR_DEVICE_TYPE,
+ ATTR_HARDWARE_REVISION,
+ ATTR_PCI_ADDRESS,
+ ATTR_STATUS,
+ ATTR_IS_DEVICE_OWNED,
+ ATTR_DEVICE_OWNER,
+ ATTR_WRITE_OPEN_COUNT,
+ ATTR_RESET_COUNT,
+ ATTR_USER_MEM_RANGES
+};
+
+/* Perform a standard Gasket callback. */
+static inline int
+check_and_invoke_callback(struct gasket_dev *gasket_dev,
+ int (*cb_function)(struct gasket_dev *))
+{
+ int ret = 0;
+
+ dev_dbg(gasket_dev->dev, "check_and_invoke_callback %p\n",
+ cb_function);
+ if (cb_function) {
+ mutex_lock(&gasket_dev->mutex);
+ ret = cb_function(gasket_dev);
+ mutex_unlock(&gasket_dev->mutex);
+ }
+ return ret;
+}
+
+/* Perform a standard Gasket callback without grabbing gasket_dev->mutex. */
+static inline int
+gasket_check_and_invoke_callback_nolock(struct gasket_dev *gasket_dev,
+ int (*cb_function)(struct gasket_dev *))
+{
+ int ret = 0;
+
+ if (cb_function) {
+ dev_dbg(gasket_dev->dev,
+ "Invoking device-specific callback.\n");
+ ret = cb_function(gasket_dev);
+ }
+ return ret;
+}
+
+/*
+ * Return nonzero if the gasket_cdev_info is owned by the current thread group
+ * ID.
+ */
+static int gasket_owned_by_current_tgid(struct gasket_cdev_info *info)
+{
+ return (info->ownership.is_owned &&
+ (info->ownership.owner == current->tgid));
+}
+
+/*
+ * Find the next free gasket_internal_dev slot.
+ *
+ * Returns the located slot number on success or a negative number on failure.
+ */
+static int gasket_find_dev_slot(struct gasket_internal_desc *internal_desc,
+ const char *kobj_name)
+{
+ int i;
+
+ mutex_lock(&internal_desc->mutex);
+
+ /* Search for a previous instance of this device. */
+ for (i = 0; i < GASKET_DEV_MAX; i++) {
+ if (internal_desc->devs[i] &&
+ strcmp(internal_desc->devs[i]->kobj_name, kobj_name) == 0) {
+ pr_err("Duplicate device %s\n", kobj_name);
+ mutex_unlock(&internal_desc->mutex);
+ return -EBUSY;
+ }
+ }
+
+ /* Find a free device slot. */
+ for (i = 0; i < GASKET_DEV_MAX; i++) {
+ if (!internal_desc->devs[i])
+ break;
+ }
+
+ if (i == GASKET_DEV_MAX) {
+ pr_err("Too many registered devices; max %d\n", GASKET_DEV_MAX);
+ mutex_unlock(&internal_desc->mutex);
+ return -EBUSY;
+ }
+
+ mutex_unlock(&internal_desc->mutex);
+ return i;
+}
+
+/*
+ * Allocate and initialize a Gasket device structure, add the device to the
+ * device list.
+ *
+ * Returns 0 if successful, a negative error code otherwise.
+ */
+static int gasket_alloc_dev(struct gasket_internal_desc *internal_desc,
+ struct device *parent, struct gasket_dev **pdev)
+{
+ int dev_idx;
+ const struct gasket_driver_desc *driver_desc =
+ internal_desc->driver_desc;
+ struct gasket_dev *gasket_dev;
+ struct gasket_cdev_info *dev_info;
+ const char *parent_name = dev_name(parent);
+
+ pr_debug("Allocating a Gasket device, parent %s.\n", parent_name);
+
+ *pdev = NULL;
+
+ dev_idx = gasket_find_dev_slot(internal_desc, parent_name);
+ if (dev_idx < 0)
+ return dev_idx;
+
+ gasket_dev = *pdev = kzalloc(sizeof(*gasket_dev), GFP_KERNEL);
+ if (!gasket_dev) {
+ pr_err("no memory for device, parent %s\n", parent_name);
+ return -ENOMEM;
+ }
+ internal_desc->devs[dev_idx] = gasket_dev;
+
+ mutex_init(&gasket_dev->mutex);
+
+ gasket_dev->internal_desc = internal_desc;
+ gasket_dev->dev_idx = dev_idx;
+ snprintf(gasket_dev->kobj_name, GASKET_NAME_MAX, "%s", parent_name);
+ gasket_dev->dev = get_device(parent);
+ /* gasket_bar_data is uninitialized. */
+ gasket_dev->num_page_tables = driver_desc->num_page_tables;
+ /* max_page_table_size and *page table are uninit'ed */
+ /* interrupt_data is not initialized. */
+ /* status is 0, or GASKET_STATUS_DEAD */
+
+ dev_info = &gasket_dev->dev_info;
+ snprintf(dev_info->name, GASKET_NAME_MAX, "%s_%u", driver_desc->name,
+ gasket_dev->dev_idx);
+ dev_info->devt =
+ MKDEV(driver_desc->major, driver_desc->minor +
+ gasket_dev->dev_idx);
+ dev_info->device =
+ device_create(internal_desc->class, parent, dev_info->devt,
+ gasket_dev, dev_info->name);
+
+ /* cdev has not yet been added; cdev_added is 0 */
+ dev_info->gasket_dev_ptr = gasket_dev;
+ /* ownership is all 0, indicating no owner or opens. */
+
+ return 0;
+}
+
+/* Free a Gasket device. */
+static void gasket_free_dev(struct gasket_dev *gasket_dev)
+{
+ struct gasket_internal_desc *internal_desc = gasket_dev->internal_desc;
+
+ mutex_lock(&internal_desc->mutex);
+ internal_desc->devs[gasket_dev->dev_idx] = NULL;
+ mutex_unlock(&internal_desc->mutex);
+ put_device(gasket_dev->dev);
+ kfree(gasket_dev);
+}
+
+/*
+ * Maps the specified bar into kernel space.
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ * A zero-sized BAR will not be mapped, but is not an error.
+ */
+static int gasket_map_pci_bar(struct gasket_dev *gasket_dev, int bar_num)
+{
+ struct gasket_internal_desc *internal_desc = gasket_dev->internal_desc;
+ const struct gasket_driver_desc *driver_desc =
+ internal_desc->driver_desc;
+ ulong desc_bytes = driver_desc->bar_descriptions[bar_num].size;
+ int ret;
+
+ if (desc_bytes == 0)
+ return 0;
+
+ if (driver_desc->bar_descriptions[bar_num].type != PCI_BAR) {
+ /* not PCI: skip this entry */
+ return 0;
+ }
+ /*
+ * pci_resource_start and pci_resource_len return a "resource_size_t",
+ * which is safely castable to ulong (which itself is the arg to
+ * request_mem_region).
+ */
+ gasket_dev->bar_data[bar_num].phys_base =
+ (ulong)pci_resource_start(gasket_dev->pci_dev, bar_num);
+ if (!gasket_dev->bar_data[bar_num].phys_base) {
+ dev_err(gasket_dev->dev, "Cannot get BAR%u base address\n",
+ bar_num);
+ return -EINVAL;
+ }
+
+ gasket_dev->bar_data[bar_num].length_bytes =
+ (ulong)pci_resource_len(gasket_dev->pci_dev, bar_num);
+ if (gasket_dev->bar_data[bar_num].length_bytes < desc_bytes) {
+ dev_err(gasket_dev->dev,
+ "PCI BAR %u space is too small: %lu; expected >= %lu\n",
+ bar_num, gasket_dev->bar_data[bar_num].length_bytes,
+ desc_bytes);
+ return -ENOMEM;
+ }
+
+ if (!request_mem_region(gasket_dev->bar_data[bar_num].phys_base,
+ gasket_dev->bar_data[bar_num].length_bytes,
+ gasket_dev->dev_info.name)) {
+ dev_err(gasket_dev->dev,
+ "Cannot get BAR %d memory region %p\n",
+ bar_num, &gasket_dev->pci_dev->resource[bar_num]);
+ return -EINVAL;
+ }
+
+ gasket_dev->bar_data[bar_num].virt_base =
+ ioremap_nocache(gasket_dev->bar_data[bar_num].phys_base,
+ gasket_dev->bar_data[bar_num].length_bytes);
+ if (!gasket_dev->bar_data[bar_num].virt_base) {
+ dev_err(gasket_dev->dev,
+ "Cannot remap BAR %d memory region %p\n",
+ bar_num, &gasket_dev->pci_dev->resource[bar_num]);
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ dma_set_mask(&gasket_dev->pci_dev->dev, DMA_BIT_MASK(64));
+ dma_set_coherent_mask(&gasket_dev->pci_dev->dev, DMA_BIT_MASK(64));
+
+ return 0;
+
+fail:
+ iounmap(gasket_dev->bar_data[bar_num].virt_base);
+ release_mem_region(gasket_dev->bar_data[bar_num].phys_base,
+ gasket_dev->bar_data[bar_num].length_bytes);
+ return ret;
+}
+
+/*
+ * Releases PCI BAR mapping.
+ *
+ * A zero-sized or not-mapped BAR will not be unmapped, but is not an error.
+ */
+static void gasket_unmap_pci_bar(struct gasket_dev *dev, int bar_num)
+{
+ ulong base, bytes;
+ struct gasket_internal_desc *internal_desc = dev->internal_desc;
+ const struct gasket_driver_desc *driver_desc =
+ internal_desc->driver_desc;
+
+ if (driver_desc->bar_descriptions[bar_num].size == 0 ||
+ !dev->bar_data[bar_num].virt_base)
+ return;
+
+ if (driver_desc->bar_descriptions[bar_num].type != PCI_BAR)
+ return;
+
+ iounmap(dev->bar_data[bar_num].virt_base);
+ dev->bar_data[bar_num].virt_base = NULL;
+
+ base = pci_resource_start(dev->pci_dev, bar_num);
+ if (!base) {
+ dev_err(dev->dev, "cannot get PCI BAR%u base address\n",
+ bar_num);
+ return;
+ }
+
+ bytes = pci_resource_len(dev->pci_dev, bar_num);
+ release_mem_region(base, bytes);
+}
+
+/*
+ * Setup PCI memory mapping for the specified device.
+ *
+ * Reads the BAR registers and sets up pointers to the device's memory mapped
+ * IO space.
+ *
+ * Returns 0 on success and a negative value otherwise.
+ */
+static int gasket_setup_pci(struct pci_dev *pci_dev,
+ struct gasket_dev *gasket_dev)
+{
+ int i, mapped_bars, ret;
+
+ for (i = 0; i < GASKET_NUM_BARS; i++) {
+ ret = gasket_map_pci_bar(gasket_dev, i);
+ if (ret) {
+ mapped_bars = i;
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ for (i = 0; i < mapped_bars; i++)
+ gasket_unmap_pci_bar(gasket_dev, i);
+
+ return -ENOMEM;
+}
+
+/* Unmaps memory for the specified device. */
+static void gasket_cleanup_pci(struct gasket_dev *gasket_dev)
+{
+ int i;
+
+ for (i = 0; i < GASKET_NUM_BARS; i++)
+ gasket_unmap_pci_bar(gasket_dev, i);
+}
+
+/* Determine the health of the Gasket device. */
+static int gasket_get_hw_status(struct gasket_dev *gasket_dev)
+{
+ int status;
+ int i;
+ const struct gasket_driver_desc *driver_desc =
+ gasket_dev->internal_desc->driver_desc;
+
+ status = gasket_check_and_invoke_callback_nolock(gasket_dev,
+ driver_desc->device_status_cb);
+ if (status != GASKET_STATUS_ALIVE) {
+ dev_dbg(gasket_dev->dev, "Hardware reported status %d.\n",
+ status);
+ return status;
+ }
+
+ status = gasket_interrupt_system_status(gasket_dev);
+ if (status != GASKET_STATUS_ALIVE) {
+ dev_dbg(gasket_dev->dev,
+ "Interrupt system reported status %d.\n", status);
+ return status;
+ }
+
+ for (i = 0; i < driver_desc->num_page_tables; ++i) {
+ status = gasket_page_table_system_status(gasket_dev->page_table[i]);
+ if (status != GASKET_STATUS_ALIVE) {
+ dev_dbg(gasket_dev->dev,
+ "Page table %d reported status %d.\n",
+ i, status);
+ return status;
+ }
+ }
+
+ return GASKET_STATUS_ALIVE;
+}
+
+static ssize_t
+gasket_write_mappable_regions(char *buf,
+ const struct gasket_driver_desc *driver_desc,
+ int bar_index)
+{
+ int i;
+ ssize_t written;
+ ssize_t total_written = 0;
+ ulong min_addr, max_addr;
+ struct gasket_bar_desc bar_desc =
+ driver_desc->bar_descriptions[bar_index];
+
+ if (bar_desc.permissions == GASKET_NOMAP)
+ return 0;
+ for (i = 0;
+ i < bar_desc.num_mappable_regions && total_written < PAGE_SIZE;
+ i++) {
+ min_addr = bar_desc.mappable_regions[i].start -
+ driver_desc->legacy_mmap_address_offset;
+ max_addr = bar_desc.mappable_regions[i].start -
+ driver_desc->legacy_mmap_address_offset +
+ bar_desc.mappable_regions[i].length_bytes;
+ written = scnprintf(buf, PAGE_SIZE - total_written,
+ "0x%08lx-0x%08lx\n", min_addr, max_addr);
+ total_written += written;
+ buf += written;
+ }
+ return total_written;
+}
+
+static ssize_t gasket_sysfs_data_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ int i, ret = 0;
+ ssize_t current_written = 0;
+ const struct gasket_driver_desc *driver_desc;
+ struct gasket_dev *gasket_dev;
+ struct gasket_sysfs_attribute *gasket_attr;
+ const struct gasket_bar_desc *bar_desc;
+ enum gasket_sysfs_attribute_type sysfs_type;
+
+ gasket_dev = gasket_sysfs_get_device_data(device);
+ if (!gasket_dev) {
+ dev_err(device, "No sysfs mapping found for device\n");
+ return 0;
+ }
+
+ gasket_attr = gasket_sysfs_get_attr(device, attr);
+ if (!gasket_attr) {
+ dev_err(device, "No sysfs attr found for device\n");
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return 0;
+ }
+
+ driver_desc = gasket_dev->internal_desc->driver_desc;
+
+ sysfs_type =
+ (enum gasket_sysfs_attribute_type)gasket_attr->data.attr_type;
+ switch (sysfs_type) {
+ case ATTR_BAR_OFFSETS:
+ for (i = 0; i < GASKET_NUM_BARS; i++) {
+ bar_desc = &driver_desc->bar_descriptions[i];
+ if (bar_desc->size == 0)
+ continue;
+ current_written =
+ snprintf(buf, PAGE_SIZE - ret, "%d: 0x%lx\n", i,
+ (ulong)bar_desc->base);
+ buf += current_written;
+ ret += current_written;
+ }
+ break;
+ case ATTR_BAR_SIZES:
+ for (i = 0; i < GASKET_NUM_BARS; i++) {
+ bar_desc = &driver_desc->bar_descriptions[i];
+ if (bar_desc->size == 0)
+ continue;
+ current_written =
+ snprintf(buf, PAGE_SIZE - ret, "%d: 0x%lx\n", i,
+ (ulong)bar_desc->size);
+ buf += current_written;
+ ret += current_written;
+ }
+ break;
+ case ATTR_DRIVER_VERSION:
+ ret = snprintf(buf, PAGE_SIZE, "%s\n",
+ gasket_dev->internal_desc->driver_desc->driver_version);
+ break;
+ case ATTR_FRAMEWORK_VERSION:
+ ret = snprintf(buf, PAGE_SIZE, "%s\n",
+ GASKET_FRAMEWORK_VERSION);
+ break;
+ case ATTR_DEVICE_TYPE:
+ ret = snprintf(buf, PAGE_SIZE, "%s\n",
+ gasket_dev->internal_desc->driver_desc->name);
+ break;
+ case ATTR_HARDWARE_REVISION:
+ ret = snprintf(buf, PAGE_SIZE, "%d\n",
+ gasket_dev->hardware_revision);
+ break;
+ case ATTR_PCI_ADDRESS:
+ ret = snprintf(buf, PAGE_SIZE, "%s\n", gasket_dev->kobj_name);
+ break;
+ case ATTR_STATUS:
+ ret = snprintf(buf, PAGE_SIZE, "%s\n",
+ gasket_num_name_lookup(gasket_dev->status,
+ gasket_status_name_table));
+ break;
+ case ATTR_IS_DEVICE_OWNED:
+ ret = snprintf(buf, PAGE_SIZE, "%d\n",
+ gasket_dev->dev_info.ownership.is_owned);
+ break;
+ case ATTR_DEVICE_OWNER:
+ ret = snprintf(buf, PAGE_SIZE, "%d\n",
+ gasket_dev->dev_info.ownership.owner);
+ break;
+ case ATTR_WRITE_OPEN_COUNT:
+ ret = snprintf(buf, PAGE_SIZE, "%d\n",
+ gasket_dev->dev_info.ownership.write_open_count);
+ break;
+ case ATTR_RESET_COUNT:
+ ret = snprintf(buf, PAGE_SIZE, "%d\n", gasket_dev->reset_count);
+ break;
+ case ATTR_USER_MEM_RANGES:
+ for (i = 0; i < GASKET_NUM_BARS; ++i) {
+ current_written =
+ gasket_write_mappable_regions(buf, driver_desc,
+ i);
+ buf += current_written;
+ ret += current_written;
+ }
+ break;
+ default:
+ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n",
+ attr->attr.name);
+ ret = 0;
+ break;
+ }
+
+ gasket_sysfs_put_attr(device, gasket_attr);
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return ret;
+}
+
+/* These attributes apply to all Gasket driver instances. */
+static const struct gasket_sysfs_attribute gasket_sysfs_generic_attrs[] = {
+ GASKET_SYSFS_RO(bar_offsets, gasket_sysfs_data_show, ATTR_BAR_OFFSETS),
+ GASKET_SYSFS_RO(bar_sizes, gasket_sysfs_data_show, ATTR_BAR_SIZES),
+ GASKET_SYSFS_RO(driver_version, gasket_sysfs_data_show,
+ ATTR_DRIVER_VERSION),
+ GASKET_SYSFS_RO(framework_version, gasket_sysfs_data_show,
+ ATTR_FRAMEWORK_VERSION),
+ GASKET_SYSFS_RO(device_type, gasket_sysfs_data_show, ATTR_DEVICE_TYPE),
+ GASKET_SYSFS_RO(revision, gasket_sysfs_data_show,
+ ATTR_HARDWARE_REVISION),
+ GASKET_SYSFS_RO(pci_address, gasket_sysfs_data_show, ATTR_PCI_ADDRESS),
+ GASKET_SYSFS_RO(status, gasket_sysfs_data_show, ATTR_STATUS),
+ GASKET_SYSFS_RO(is_device_owned, gasket_sysfs_data_show,
+ ATTR_IS_DEVICE_OWNED),
+ GASKET_SYSFS_RO(device_owner, gasket_sysfs_data_show,
+ ATTR_DEVICE_OWNER),
+ GASKET_SYSFS_RO(write_open_count, gasket_sysfs_data_show,
+ ATTR_WRITE_OPEN_COUNT),
+ GASKET_SYSFS_RO(reset_count, gasket_sysfs_data_show, ATTR_RESET_COUNT),
+ GASKET_SYSFS_RO(user_mem_ranges, gasket_sysfs_data_show,
+ ATTR_USER_MEM_RANGES),
+ GASKET_END_OF_ATTR_ARRAY
+};
+
+/* Add a char device and related info. */
+static int gasket_add_cdev(struct gasket_cdev_info *dev_info,
+ const struct file_operations *file_ops,
+ struct module *owner)
+{
+ int ret;
+
+ cdev_init(&dev_info->cdev, file_ops);
+ dev_info->cdev.owner = owner;
+ ret = cdev_add(&dev_info->cdev, dev_info->devt, 1);
+ if (ret) {
+ dev_err(dev_info->gasket_dev_ptr->dev,
+ "cannot add char device [ret=%d]\n", ret);
+ return ret;
+ }
+ dev_info->cdev_added = 1;
+
+ return 0;
+}
+
+/* Disable device operations. */
+void gasket_disable_device(struct gasket_dev *gasket_dev)
+{
+ const struct gasket_driver_desc *driver_desc =
+ gasket_dev->internal_desc->driver_desc;
+ int i;
+
+ /* Only delete the device if it has been successfully added. */
+ if (gasket_dev->dev_info.cdev_added)
+ cdev_del(&gasket_dev->dev_info.cdev);
+
+ gasket_dev->status = GASKET_STATUS_DEAD;
+
+ gasket_interrupt_cleanup(gasket_dev);
+
+ for (i = 0; i < driver_desc->num_page_tables; ++i) {
+ if (gasket_dev->page_table[i]) {
+ gasket_page_table_reset(gasket_dev->page_table[i]);
+ gasket_page_table_cleanup(gasket_dev->page_table[i]);
+ }
+ }
+}
+EXPORT_SYMBOL(gasket_disable_device);
+
+/*
+ * Registered driver descriptor lookup for PCI devices.
+ *
+ * Precondition: Called with g_mutex held (to avoid a race on return).
+ * Returns NULL if no matching device was found.
+ */
+static struct gasket_internal_desc *
+lookup_pci_internal_desc(struct pci_dev *pci_dev)
+{
+ int i;
+
+ __must_hold(&g_mutex);
+ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) {
+ if (g_descs[i].driver_desc &&
+ g_descs[i].driver_desc->pci_id_table &&
+ pci_match_id(g_descs[i].driver_desc->pci_id_table, pci_dev))
+ return &g_descs[i];
+ }
+
+ return NULL;
+}
+
+/*
+ * Verifies that the user has permissions to perform the requested mapping and
+ * that the provided descriptor/range is of adequate size to hold the range to
+ * be mapped.
+ */
+static bool gasket_mmap_has_permissions(struct gasket_dev *gasket_dev,
+ struct vm_area_struct *vma,
+ int bar_permissions)
+{
+ int requested_permissions;
+ /* Always allow sysadmin to access. */
+ if (capable(CAP_SYS_ADMIN))
+ return true;
+
+ /* Never allow non-sysadmins to access to a dead device. */
+ if (gasket_dev->status != GASKET_STATUS_ALIVE) {
+ dev_dbg(gasket_dev->dev, "Device is dead.\n");
+ return false;
+ }
+
+ /* Make sure that no wrong flags are set. */
+ requested_permissions =
+ (vma->vm_flags & (VM_WRITE | VM_READ | VM_EXEC));
+ if (requested_permissions & ~(bar_permissions)) {
+ dev_dbg(gasket_dev->dev,
+ "Attempting to map a region with requested permissions "
+ "0x%x, but region has permissions 0x%x.\n",
+ requested_permissions, bar_permissions);
+ return false;
+ }
+
+ /* Do not allow a non-owner to write. */
+ if ((vma->vm_flags & VM_WRITE) &&
+ !gasket_owned_by_current_tgid(&gasket_dev->dev_info)) {
+ dev_dbg(gasket_dev->dev,
+ "Attempting to mmap a region for write without owning "
+ "device.\n");
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Verifies that the input address is within the region allocated to coherent
+ * buffer.
+ */
+static bool
+gasket_is_coherent_region(const struct gasket_driver_desc *driver_desc,
+ ulong address)
+{
+ struct gasket_coherent_buffer_desc coh_buff_desc =
+ driver_desc->coherent_buffer_description;
+
+ if (coh_buff_desc.permissions != GASKET_NOMAP) {
+ if ((address >= coh_buff_desc.base) &&
+ (address < coh_buff_desc.base + coh_buff_desc.size)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static int gasket_get_bar_index(const struct gasket_dev *gasket_dev,
+ ulong phys_addr)
+{
+ int i;
+ const struct gasket_driver_desc *driver_desc;
+
+ driver_desc = gasket_dev->internal_desc->driver_desc;
+ for (i = 0; i < GASKET_NUM_BARS; ++i) {
+ struct gasket_bar_desc bar_desc =
+ driver_desc->bar_descriptions[i];
+
+ if (bar_desc.permissions != GASKET_NOMAP) {
+ if (phys_addr >= bar_desc.base &&
+ phys_addr < (bar_desc.base + bar_desc.size)) {
+ return i;
+ }
+ }
+ }
+ /* If we haven't found the address by now, it is invalid. */
+ return -EINVAL;
+}
+
+/*
+ * Sets the actual bounds to map, given the device's mappable region.
+ *
+ * Given the device's mappable region, along with the user-requested mapping
+ * start offset and length of the user region, determine how much of this
+ * mappable region can be mapped into the user's region (start/end offsets),
+ * and the physical offset (phys_offset) into the BAR where the mapping should
+ * begin (either the VMA's or region lower bound).
+ *
+ * In other words, this calculates the overlap between the VMA
+ * (bar_offset, requested_length) and the given gasket_mappable_region.
+ *
+ * Returns true if there's anything to map, and false otherwise.
+ */
+static bool
+gasket_mm_get_mapping_addrs(const struct gasket_mappable_region *region,
+ ulong bar_offset, ulong requested_length,
+ struct gasket_mappable_region *mappable_region,
+ ulong *virt_offset)
+{
+ ulong range_start = region->start;
+ ulong range_length = region->length_bytes;
+ ulong range_end = range_start + range_length;
+
+ *virt_offset = 0;
+ if (bar_offset + requested_length < range_start) {
+ /*
+ * If the requested region is completely below the range,
+ * there is nothing to map.
+ */
+ return false;
+ } else if (bar_offset <= range_start) {
+ /* If the bar offset is below this range's start
+ * but the requested length continues into it:
+ * 1) Only map starting from the beginning of this
+ * range's phys. offset, so we don't map unmappable
+ * memory.
+ * 2) The length of the virtual memory to not map is the
+ * delta between the bar offset and the
+ * mappable start (and since the mappable start is
+ * bigger, start - req.)
+ * 3) The map length is the minimum of the mappable
+ * requested length (requested_length - virt_offset)
+ * and the actual mappable length of the range.
+ */
+ mappable_region->start = range_start;
+ *virt_offset = range_start - bar_offset;
+ mappable_region->length_bytes =
+ min(requested_length - *virt_offset, range_length);
+ return true;
+ } else if (bar_offset > range_start &&
+ bar_offset < range_end) {
+ /*
+ * If the bar offset is within this range:
+ * 1) Map starting from the bar offset.
+ * 2) Because there is no forbidden memory between the
+ * bar offset and the range start,
+ * virt_offset is 0.
+ * 3) The map length is the minimum of the requested
+ * length and the remaining length in the buffer
+ * (range_end - bar_offset)
+ */
+ mappable_region->start = bar_offset;
+ *virt_offset = 0;
+ mappable_region->length_bytes =
+ min(requested_length, range_end - bar_offset);
+ return true;
+ }
+
+ /*
+ * If the requested [start] offset is above range_end,
+ * there's nothing to map.
+ */
+ return false;
+}
+
+/*
+ * Calculates the offset where the VMA range begins in its containing BAR.
+ * The offset is written into bar_offset on success.
+ * Returns zero on success, anything else on error.
+ */
+static int gasket_mm_vma_bar_offset(const struct gasket_dev *gasket_dev,
+ const struct vm_area_struct *vma,
+ ulong *bar_offset)
+{
+ ulong raw_offset;
+ int bar_index;
+ const struct gasket_driver_desc *driver_desc =
+ gasket_dev->internal_desc->driver_desc;
+
+ raw_offset = (vma->vm_pgoff << PAGE_SHIFT) +
+ driver_desc->legacy_mmap_address_offset;
+ bar_index = gasket_get_bar_index(gasket_dev, raw_offset);
+ if (bar_index < 0) {
+ dev_err(gasket_dev->dev,
+ "Unable to find matching bar for address 0x%lx\n",
+ raw_offset);
+ trace_gasket_mmap_exit(bar_index);
+ return bar_index;
+ }
+ *bar_offset =
+ raw_offset - driver_desc->bar_descriptions[bar_index].base;
+
+ return 0;
+}
+
+int gasket_mm_unmap_region(const struct gasket_dev *gasket_dev,
+ struct vm_area_struct *vma,
+ const struct gasket_mappable_region *map_region)
+{
+ ulong bar_offset;
+ ulong virt_offset;
+ struct gasket_mappable_region mappable_region;
+ int ret;
+
+ if (map_region->length_bytes == 0)
+ return 0;
+
+ ret = gasket_mm_vma_bar_offset(gasket_dev, vma, &bar_offset);
+ if (ret)
+ return ret;
+
+ if (!gasket_mm_get_mapping_addrs(map_region, bar_offset,
+ vma->vm_end - vma->vm_start,
+ &mappable_region, &virt_offset))
+ return 1;
+
+ /*
+ * The length passed to zap_vma_ptes MUST BE A MULTIPLE OF
+ * PAGE_SIZE! Trust me. I have the scars.
+ *
+ * Next multiple of y: ceil_div(x, y) * y
+ */
+ zap_vma_ptes(vma, vma->vm_start + virt_offset,
+ DIV_ROUND_UP(mappable_region.length_bytes, PAGE_SIZE) *
+ PAGE_SIZE);
+ return 0;
+}
+EXPORT_SYMBOL(gasket_mm_unmap_region);
+
+/* Maps a virtual address + range to a physical offset of a BAR. */
+static enum do_map_region_status
+do_map_region(const struct gasket_dev *gasket_dev, struct vm_area_struct *vma,
+ struct gasket_mappable_region *mappable_region)
+{
+ /* Maximum size of a single call to io_remap_pfn_range. */
+ /* I pulled this number out of thin air. */
+ const ulong max_chunk_size = 64 * 1024 * 1024;
+ ulong chunk_size, mapped_bytes = 0;
+
+ const struct gasket_driver_desc *driver_desc =
+ gasket_dev->internal_desc->driver_desc;
+
+ ulong bar_offset, virt_offset;
+ struct gasket_mappable_region region_to_map;
+ ulong phys_offset, map_length;
+ ulong virt_base, phys_base;
+ int bar_index, ret;
+
+ ret = gasket_mm_vma_bar_offset(gasket_dev, vma, &bar_offset);
+ if (ret)
+ return DO_MAP_REGION_INVALID;
+
+ if (!gasket_mm_get_mapping_addrs(mappable_region, bar_offset,
+ vma->vm_end - vma->vm_start,
+ ®ion_to_map, &virt_offset))
+ return DO_MAP_REGION_INVALID;
+ phys_offset = region_to_map.start;
+ map_length = region_to_map.length_bytes;
+
+ virt_base = vma->vm_start + virt_offset;
+ bar_index =
+ gasket_get_bar_index(gasket_dev,
+ (vma->vm_pgoff << PAGE_SHIFT) +
+ driver_desc->legacy_mmap_address_offset);
+ phys_base = gasket_dev->bar_data[bar_index].phys_base + phys_offset;
+ while (mapped_bytes < map_length) {
+ /*
+ * io_remap_pfn_range can take a while, so we chunk its
+ * calls and call cond_resched between each.
+ */
+ chunk_size = min(max_chunk_size, map_length - mapped_bytes);
+
+ cond_resched();
+ ret = io_remap_pfn_range(vma, virt_base + mapped_bytes,
+ (phys_base + mapped_bytes) >>
+ PAGE_SHIFT, chunk_size,
+ vma->vm_page_prot);
+ if (ret) {
+ dev_err(gasket_dev->dev,
+ "Error remapping PFN range.\n");
+ goto fail;
+ }
+ mapped_bytes += chunk_size;
+ }
+
+ return DO_MAP_REGION_SUCCESS;
+
+fail:
+ /* Unmap the partial chunk we mapped. */
+ mappable_region->length_bytes = mapped_bytes;
+ if (gasket_mm_unmap_region(gasket_dev, vma, mappable_region))
+ dev_err(gasket_dev->dev,
+ "Error unmapping partial region 0x%lx (0x%lx bytes)\n",
+ (ulong)virt_offset,
+ (ulong)mapped_bytes);
+
+ return DO_MAP_REGION_FAILURE;
+}
+
+/* Map a region of coherent memory. */
+static int gasket_mmap_coherent(struct gasket_dev *gasket_dev,
+ struct vm_area_struct *vma)
+{
+ const struct gasket_driver_desc *driver_desc =
+ gasket_dev->internal_desc->driver_desc;
+ const ulong requested_length = vma->vm_end - vma->vm_start;
+ int ret;
+ ulong permissions;
+
+ if (requested_length == 0 || requested_length >
+ gasket_dev->coherent_buffer.length_bytes) {
+ trace_gasket_mmap_exit(-EINVAL);
+ return -EINVAL;
+ }
+
+ permissions = driver_desc->coherent_buffer_description.permissions;
+ if (!gasket_mmap_has_permissions(gasket_dev, vma, permissions)) {
+ dev_err(gasket_dev->dev, "Permission checking failed.\n");
+ trace_gasket_mmap_exit(-EPERM);
+ return -EPERM;
+ }
+
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ ret = remap_pfn_range(vma, vma->vm_start,
+ (gasket_dev->coherent_buffer.phys_base) >>
+ PAGE_SHIFT, requested_length, vma->vm_page_prot);
+ if (ret) {
+ dev_err(gasket_dev->dev, "Error remapping PFN range err=%d.\n",
+ ret);
+ trace_gasket_mmap_exit(ret);
+ return ret;
+ }
+
+ /* Record the user virtual to dma_address mapping that was
+ * created by the kernel.
+ */
+ gasket_set_user_virt(gasket_dev, requested_length,
+ gasket_dev->coherent_buffer.phys_base,
+ vma->vm_start);
+ return 0;
+}
+
+/* Map a device's BARs into user space. */
+static int gasket_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ int i, ret;
+ int bar_index;
+ int has_mapped_anything = 0;
+ ulong permissions;
+ ulong raw_offset, vma_size;
+ bool is_coherent_region;
+ const struct gasket_driver_desc *driver_desc;
+ struct gasket_dev *gasket_dev = (struct gasket_dev *)filp->private_data;
+ const struct gasket_bar_desc *bar_desc;
+ struct gasket_mappable_region *map_regions = NULL;
+ int num_map_regions = 0;
+ enum do_map_region_status map_status;
+
+ driver_desc = gasket_dev->internal_desc->driver_desc;
+
+ if (vma->vm_start & ~PAGE_MASK) {
+ dev_err(gasket_dev->dev,
+ "Base address not page-aligned: 0x%lx\n",
+ vma->vm_start);
+ trace_gasket_mmap_exit(-EINVAL);
+ return -EINVAL;
+ }
+
+ /* Calculate the offset of this range into physical mem. */
+ raw_offset = (vma->vm_pgoff << PAGE_SHIFT) +
+ driver_desc->legacy_mmap_address_offset;
+ vma_size = vma->vm_end - vma->vm_start;
+ trace_gasket_mmap_entry(gasket_dev->dev_info.name, raw_offset,
+ vma_size);
+
+ /*
+ * Check if the raw offset is within a bar region. If not, check if it
+ * is a coherent region.
+ */
+ bar_index = gasket_get_bar_index(gasket_dev, raw_offset);
+ is_coherent_region = gasket_is_coherent_region(driver_desc, raw_offset);
+ if (bar_index < 0 && !is_coherent_region) {
+ dev_err(gasket_dev->dev,
+ "Unable to find matching bar for address 0x%lx\n",
+ raw_offset);
+ trace_gasket_mmap_exit(bar_index);
+ return bar_index;
+ }
+ if (bar_index > 0 && is_coherent_region) {
+ dev_err(gasket_dev->dev,
+ "double matching bar and coherent buffers for address "
+ "0x%lx\n",
+ raw_offset);
+ trace_gasket_mmap_exit(bar_index);
+ return -EINVAL;
+ }
+
+ vma->vm_private_data = gasket_dev;
+
+ if (is_coherent_region)
+ return gasket_mmap_coherent(gasket_dev, vma);
+
+ /* Everything in the rest of this function is for normal BAR mapping. */
+
+ /*
+ * Subtract the base of the bar from the raw offset to get the
+ * memory location within the bar to map.
+ */
+ bar_desc = &driver_desc->bar_descriptions[bar_index];
+ permissions = bar_desc->permissions;
+ if (!gasket_mmap_has_permissions(gasket_dev, vma, permissions)) {
+ dev_err(gasket_dev->dev, "Permission checking failed.\n");
+ trace_gasket_mmap_exit(-EPERM);
+ return -EPERM;
+ }
+
+ if (driver_desc->get_mappable_regions_cb) {
+ ret = driver_desc->get_mappable_regions_cb(gasket_dev,
+ bar_index,
+ &map_regions,
+ &num_map_regions);
+ if (ret)
+ return ret;
+ } else {
+ if (!gasket_mmap_has_permissions(gasket_dev, vma,
+ bar_desc->permissions)) {
+ dev_err(gasket_dev->dev,
+ "Permission checking failed.\n");
+ trace_gasket_mmap_exit(-EPERM);
+ return -EPERM;
+ }
+ num_map_regions = bar_desc->num_mappable_regions;
+ map_regions = kcalloc(num_map_regions,
+ sizeof(*bar_desc->mappable_regions),
+ GFP_KERNEL);
+ if (map_regions) {
+ memcpy(map_regions, bar_desc->mappable_regions,
+ num_map_regions *
+ sizeof(*bar_desc->mappable_regions));
+ }
+ }
+
+ if (!map_regions || num_map_regions == 0) {
+ dev_err(gasket_dev->dev, "No mappable regions returned!\n");
+ return -EINVAL;
+ }
+
+ /* Marks the VMA's pages as uncacheable. */
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+ for (i = 0; i < num_map_regions; i++) {
+ map_status = do_map_region(gasket_dev, vma, &map_regions[i]);
+ /* Try the next region if this one was not mappable. */
+ if (map_status == DO_MAP_REGION_INVALID)
+ continue;
+ if (map_status == DO_MAP_REGION_FAILURE) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ has_mapped_anything = 1;
+ }
+
+ kfree(map_regions);
+
+ /* If we could not map any memory, the request was invalid. */
+ if (!has_mapped_anything) {
+ dev_err(gasket_dev->dev,
+ "Map request did not contain a valid region.\n");
+ trace_gasket_mmap_exit(-EINVAL);
+ return -EINVAL;
+ }
+
+ trace_gasket_mmap_exit(0);
+ return 0;
+
+fail:
+ /* Need to unmap any mapped ranges. */
+ num_map_regions = i;
+ for (i = 0; i < num_map_regions; i++)
+ if (gasket_mm_unmap_region(gasket_dev, vma,
+ &bar_desc->mappable_regions[i]))
+ dev_err(gasket_dev->dev, "Error unmapping range %d.\n",
+ i);
+ kfree(map_regions);
+
+ return ret;
+}
+
+/*
+ * Open the char device file.
+ *
+ * If the open is for writing, and the device is not owned, this process becomes
+ * the owner. If the open is for writing and the device is already owned by
+ * some other process, it is an error. If this process is the owner, increment
+ * the open count.
+ *
+ * Returns 0 if successful, a negative error number otherwise.
+ */
+static int gasket_open(struct inode *inode, struct file *filp)
+{
+ int ret;
+ struct gasket_dev *gasket_dev;
+ const struct gasket_driver_desc *driver_desc;
+ struct gasket_ownership *ownership;
+ char task_name[TASK_COMM_LEN];
+ struct gasket_cdev_info *dev_info =
+ container_of(inode->i_cdev, struct gasket_cdev_info, cdev);
+ struct pid_namespace *pid_ns = task_active_pid_ns(current);
+ bool is_root = ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN);
+
+ gasket_dev = dev_info->gasket_dev_ptr;
+ driver_desc = gasket_dev->internal_desc->driver_desc;
+ ownership = &dev_info->ownership;
+ get_task_comm(task_name, current);
+ filp->private_data = gasket_dev;
+ inode->i_size = 0;
+
+ dev_dbg(gasket_dev->dev,
+ "Attempting to open with tgid %u (%s) (f_mode: 0%03o, "
+ "fmode_write: %d is_root: %u)\n",
+ current->tgid, task_name, filp->f_mode,
+ (filp->f_mode & FMODE_WRITE), is_root);
+
+ /* Always allow non-writing accesses. */
+ if (!(filp->f_mode & FMODE_WRITE)) {
+ dev_dbg(gasket_dev->dev, "Allowing read-only opening.\n");
+ return 0;
+ }
+
+ mutex_lock(&gasket_dev->mutex);
+
+ dev_dbg(gasket_dev->dev,
+ "Current owner open count (owning tgid %u): %d.\n",
+ ownership->owner, ownership->write_open_count);
+
+ /* Opening a node owned by another TGID is an error (unless root) */
+ if (ownership->is_owned && ownership->owner != current->tgid &&
+ !is_root) {
+ dev_err(gasket_dev->dev,
+ "Process %u is opening a node held by %u.\n",
+ current->tgid, ownership->owner);
+ mutex_unlock(&gasket_dev->mutex);
+ return -EPERM;
+ }
+
+ /* If the node is not owned, assign it to the current TGID. */
+ if (!ownership->is_owned) {
+ ret = gasket_check_and_invoke_callback_nolock(gasket_dev,
+ driver_desc->device_open_cb);
+ if (ret) {
+ dev_err(gasket_dev->dev,
+ "Error in device open cb: %d\n", ret);
+ mutex_unlock(&gasket_dev->mutex);
+ return ret;
+ }
+ ownership->is_owned = 1;
+ ownership->owner = current->tgid;
+ dev_dbg(gasket_dev->dev, "Device owner is now tgid %u\n",
+ ownership->owner);
+ }
+
+ ownership->write_open_count++;
+
+ dev_dbg(gasket_dev->dev, "New open count (owning tgid %u): %d\n",
+ ownership->owner, ownership->write_open_count);
+
+ mutex_unlock(&gasket_dev->mutex);
+ return 0;
+}
+
+/*
+ * Called on a close of the device file. If this process is the owner,
+ * decrement the open count. On last close by the owner, free up buffers and
+ * eventfd contexts, and release ownership.
+ *
+ * Returns 0 if successful, a negative error number otherwise.
+ */
+static int gasket_release(struct inode *inode, struct file *file)
+{
+ int i;
+ struct gasket_dev *gasket_dev;
+ struct gasket_ownership *ownership;
+ const struct gasket_driver_desc *driver_desc;
+ char task_name[TASK_COMM_LEN];
+ struct gasket_cdev_info *dev_info =
+ container_of(inode->i_cdev, struct gasket_cdev_info, cdev);
+ struct pid_namespace *pid_ns = task_active_pid_ns(current);
+ bool is_root = ns_capable(pid_ns->user_ns, CAP_SYS_ADMIN);
+
+ gasket_dev = dev_info->gasket_dev_ptr;
+ driver_desc = gasket_dev->internal_desc->driver_desc;
+ ownership = &dev_info->ownership;
+ get_task_comm(task_name, current);
+ mutex_lock(&gasket_dev->mutex);
+
+ dev_dbg(gasket_dev->dev,
+ "Releasing device node. Call origin: tgid %u (%s) "
+ "(f_mode: 0%03o, fmode_write: %d, is_root: %u)\n",
+ current->tgid, task_name, file->f_mode,
+ (file->f_mode & FMODE_WRITE), is_root);
+ dev_dbg(gasket_dev->dev, "Current open count (owning tgid %u): %d\n",
+ ownership->owner, ownership->write_open_count);
+
+ if (file->f_mode & FMODE_WRITE) {
+ ownership->write_open_count--;
+ if (ownership->write_open_count == 0) {
+ dev_dbg(gasket_dev->dev, "Device is now free\n");
+ ownership->is_owned = 0;
+ ownership->owner = 0;
+
+ /* Forces chip reset before we unmap the page tables. */
+ driver_desc->device_reset_cb(gasket_dev);
+
+ for (i = 0; i < driver_desc->num_page_tables; ++i) {
+ gasket_page_table_unmap_all(gasket_dev->page_table[i]);
+ gasket_page_table_garbage_collect(gasket_dev->page_table[i]);
+ gasket_free_coherent_memory_all(gasket_dev, i);
+ }
+
+ /* Closes device, enters power save. */
+ gasket_check_and_invoke_callback_nolock(gasket_dev,
+ driver_desc->device_close_cb);
+ }
+ }
+
+ dev_dbg(gasket_dev->dev, "New open count (owning tgid %u): %d\n",
+ ownership->owner, ownership->write_open_count);
+ mutex_unlock(&gasket_dev->mutex);
+ return 0;
+}
+
+/*
+ * Gasket ioctl dispatch function.
+ *
+ * Check if the ioctl is a generic ioctl. If not, pass the ioctl to the
+ * ioctl_handler_cb registered in the driver description.
+ * If the ioctl is a generic ioctl, pass it to gasket_ioctl_handler.
+ */
+static long gasket_ioctl(struct file *filp, uint cmd, ulong arg)
+{
+ struct gasket_dev *gasket_dev;
+ const struct gasket_driver_desc *driver_desc;
+ void __user *argp = (void __user *)arg;
+ char path[256];
+
+ gasket_dev = (struct gasket_dev *)filp->private_data;
+ driver_desc = gasket_dev->internal_desc->driver_desc;
+ if (!driver_desc) {
+ dev_dbg(gasket_dev->dev,
+ "Unable to find device descriptor for file %s\n",
+ d_path(&filp->f_path, path, 256));
+ return -ENODEV;
+ }
+
+ if (!gasket_is_supported_ioctl(cmd)) {
+ /*
+ * The ioctl handler is not a standard Gasket callback, since
+ * it requires different arguments. This means we can't use
+ * check_and_invoke_callback.
+ */
+ if (driver_desc->ioctl_handler_cb)
+ return driver_desc->ioctl_handler_cb(filp, cmd, argp);
+
+ dev_dbg(gasket_dev->dev, "Received unknown ioctl 0x%x\n", cmd);
+ return -EINVAL;
+ }
+
+ return gasket_handle_ioctl(filp, cmd, argp);
+}
+
+/* File operations for all Gasket devices. */
+static const struct file_operations gasket_file_ops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .mmap = gasket_mmap,
+ .open = gasket_open,
+ .release = gasket_release,
+ .unlocked_ioctl = gasket_ioctl,
+ .compat_ioctl = gasket_ioctl,
+};
+
+/* Perform final init and marks the device as active. */
+int gasket_enable_device(struct gasket_dev *gasket_dev)
+{
+ int tbl_idx;
+ int ret;
+ const struct gasket_driver_desc *driver_desc =
+ gasket_dev->internal_desc->driver_desc;
+
+ ret = gasket_interrupt_init(gasket_dev);
+ if (ret) {
+ dev_err(gasket_dev->dev,
+ "Critical failure to allocate interrupts: %d\n", ret);
+ gasket_interrupt_cleanup(gasket_dev);
+ return ret;
+ }
+
+ for (tbl_idx = 0; tbl_idx < driver_desc->num_page_tables; tbl_idx++) {
+ dev_dbg(gasket_dev->dev, "Initializing page table %d.\n",
+ tbl_idx);
+ ret = gasket_page_table_init(&gasket_dev->page_table[tbl_idx],
+ &gasket_dev->bar_data[driver_desc->page_table_bar_index],
+ &driver_desc->page_table_configs[tbl_idx],
+ gasket_dev->dev,
+ gasket_dev->pci_dev);
+ if (ret) {
+ dev_err(gasket_dev->dev,
+ "Couldn't init page table %d: %d\n",
+ tbl_idx, ret);
+ return ret;
+ }
+ /*
+ * Make sure that the page table is clear and set to simple
+ * addresses.
+ */
+ gasket_page_table_reset(gasket_dev->page_table[tbl_idx]);
+ }
+
+ /*
+ * hardware_revision_cb returns a positive integer (the rev) if
+ * successful.)
+ */
+ ret = check_and_invoke_callback(gasket_dev,
+ driver_desc->hardware_revision_cb);
+ if (ret < 0) {
+ dev_err(gasket_dev->dev,
+ "Error getting hardware revision: %d\n", ret);
+ return ret;
+ }
+ gasket_dev->hardware_revision = ret;
+
+ /* device_status_cb returns a device status, not an error code. */
+ gasket_dev->status = gasket_get_hw_status(gasket_dev);
+ if (gasket_dev->status == GASKET_STATUS_DEAD)
+ dev_err(gasket_dev->dev, "Device reported as unhealthy.\n");
+
+ ret = gasket_add_cdev(&gasket_dev->dev_info, &gasket_file_ops,
+ driver_desc->module);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL(gasket_enable_device);
+
+static int __gasket_add_device(struct device *parent_dev,
+ struct gasket_internal_desc *internal_desc,
+ struct gasket_dev **gasket_devp)
+{
+ int ret;
+ struct gasket_dev *gasket_dev;
+ const struct gasket_driver_desc *driver_desc =
+ internal_desc->driver_desc;
+
+ ret = gasket_alloc_dev(internal_desc, parent_dev, &gasket_dev);
+ if (ret)
+ return ret;
+ if (IS_ERR(gasket_dev->dev_info.device)) {
+ dev_err(parent_dev, "Cannot create %s device %s [ret = %ld]\n",
+ driver_desc->name, gasket_dev->dev_info.name,
+ PTR_ERR(gasket_dev->dev_info.device));
+ ret = -ENODEV;
+ goto free_gasket_dev;
+ }
+
+ ret = gasket_sysfs_create_mapping(gasket_dev->dev_info.device,
+ gasket_dev);
+ if (ret)
+ goto remove_device;
+
+ ret = gasket_sysfs_create_entries(gasket_dev->dev_info.device,
+ gasket_sysfs_generic_attrs);
+ if (ret)
+ goto remove_sysfs_mapping;
+
+ *gasket_devp = gasket_dev;
+ return 0;
+
+remove_sysfs_mapping:
+ gasket_sysfs_remove_mapping(gasket_dev->dev_info.device);
+remove_device:
+ device_destroy(internal_desc->class, gasket_dev->dev_info.devt);
+free_gasket_dev:
+ gasket_free_dev(gasket_dev);
+ return ret;
+}
+
+static void __gasket_remove_device(struct gasket_internal_desc *internal_desc,
+ struct gasket_dev *gasket_dev)
+{
+ gasket_sysfs_remove_mapping(gasket_dev->dev_info.device);
+ device_destroy(internal_desc->class, gasket_dev->dev_info.devt);
+ gasket_free_dev(gasket_dev);
+}
+
+/*
+ * Add PCI gasket device.
+ *
+ * Called by Gasket device probe function.
+ * Allocates device metadata and maps device memory. The device driver must
+ * call gasket_enable_device after driver init is complete to place the device
+ * in active use.
+ */
+int gasket_pci_add_device(struct pci_dev *pci_dev,
+ struct gasket_dev **gasket_devp)
+{
+ int ret;
+ struct gasket_internal_desc *internal_desc;
+ struct gasket_dev *gasket_dev;
+ struct device *parent;
+
+ dev_dbg(&pci_dev->dev, "add PCI gasket device\n");
+
+ mutex_lock(&g_mutex);
+ internal_desc = lookup_pci_internal_desc(pci_dev);
+ mutex_unlock(&g_mutex);
+ if (!internal_desc) {
+ dev_err(&pci_dev->dev,
+ "PCI add device called for unknown driver type\n");
+ return -ENODEV;
+ }
+
+ parent = &pci_dev->dev;
+ ret = __gasket_add_device(parent, internal_desc, &gasket_dev);
+ if (ret)
+ return ret;
+
+ gasket_dev->pci_dev = pci_dev;
+ ret = gasket_setup_pci(pci_dev, gasket_dev);
+ if (ret)
+ goto cleanup_pci;
+
+ /*
+ * Once we've created the mapping structures successfully, attempt to
+ * create a symlink to the pci directory of this object.
+ */
+ ret = sysfs_create_link(&gasket_dev->dev_info.device->kobj,
+ &pci_dev->dev.kobj, dev_name(&pci_dev->dev));
+ if (ret) {
+ dev_err(gasket_dev->dev,
+ "Cannot create sysfs pci link: %d\n", ret);
+ goto cleanup_pci;
+ }
+
+ *gasket_devp = gasket_dev;
+ return 0;
+
+cleanup_pci:
+ gasket_cleanup_pci(gasket_dev);
+ __gasket_remove_device(internal_desc, gasket_dev);
+ return ret;
+}
+EXPORT_SYMBOL(gasket_pci_add_device);
+
+/* Remove a PCI gasket device. */
+void gasket_pci_remove_device(struct pci_dev *pci_dev)
+{
+ int i;
+ struct gasket_internal_desc *internal_desc;
+ struct gasket_dev *gasket_dev = NULL;
+ /* Find the device desc. */
+ mutex_lock(&g_mutex);
+ internal_desc = lookup_pci_internal_desc(pci_dev);
+ if (!internal_desc) {
+ mutex_unlock(&g_mutex);
+ return;
+ }
+ mutex_unlock(&g_mutex);
+
+ /* Now find the specific device */
+ mutex_lock(&internal_desc->mutex);
+ for (i = 0; i < GASKET_DEV_MAX; i++) {
+ if (internal_desc->devs[i] &&
+ internal_desc->devs[i]->pci_dev == pci_dev) {
+ gasket_dev = internal_desc->devs[i];
+ break;
+ }
+ }
+ mutex_unlock(&internal_desc->mutex);
+
+ if (!gasket_dev)
+ return;
+
+ dev_dbg(gasket_dev->dev, "remove %s PCI gasket device\n",
+ internal_desc->driver_desc->name);
+
+ gasket_cleanup_pci(gasket_dev);
+ __gasket_remove_device(internal_desc, gasket_dev);
+}
+EXPORT_SYMBOL(gasket_pci_remove_device);
+
+/**
+ * Lookup a name by number in a num_name table.
+ * @num: Number to lookup.
+ * @table: Array of num_name structures, the table for the lookup.
+ *
+ * Description: Searches for num in the table. If found, the
+ * corresponding name is returned; otherwise NULL
+ * is returned.
+ *
+ * The table must have a NULL name pointer at the end.
+ */
+const char *gasket_num_name_lookup(uint num,
+ const struct gasket_num_name *table)
+{
+ uint i = 0;
+
+ while (table[i].snn_name) {
+ if (num == table[i].snn_num)
+ break;
+ ++i;
+ }
+
+ return table[i].snn_name;
+}
+EXPORT_SYMBOL(gasket_num_name_lookup);
+
+int gasket_reset(struct gasket_dev *gasket_dev)
+{
+ int ret;
+
+ mutex_lock(&gasket_dev->mutex);
+ ret = gasket_reset_nolock(gasket_dev);
+ mutex_unlock(&gasket_dev->mutex);
+ return ret;
+}
+EXPORT_SYMBOL(gasket_reset);
+
+int gasket_reset_nolock(struct gasket_dev *gasket_dev)
+{
+ int ret;
+ int i;
+ const struct gasket_driver_desc *driver_desc;
+
+ driver_desc = gasket_dev->internal_desc->driver_desc;
+ if (!driver_desc->device_reset_cb)
+ return 0;
+
+ ret = driver_desc->device_reset_cb(gasket_dev);
+ if (ret) {
+ dev_dbg(gasket_dev->dev, "Device reset cb returned %d.\n",
+ ret);
+ return ret;
+ }
+
+ /* Reinitialize the page tables and interrupt framework. */
+ for (i = 0; i < driver_desc->num_page_tables; ++i)
+ gasket_page_table_reset(gasket_dev->page_table[i]);
+
+ ret = gasket_interrupt_reinit(gasket_dev);
+ if (ret) {
+ dev_dbg(gasket_dev->dev, "Unable to reinit interrupts: %d.\n",
+ ret);
+ return ret;
+ }
+
+ /* Get current device health. */
+ gasket_dev->status = gasket_get_hw_status(gasket_dev);
+ if (gasket_dev->status == GASKET_STATUS_DEAD) {
+ dev_dbg(gasket_dev->dev, "Device reported as dead.\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(gasket_reset_nolock);
+
+gasket_ioctl_permissions_cb_t
+gasket_get_ioctl_permissions_cb(struct gasket_dev *gasket_dev)
+{
+ return gasket_dev->internal_desc->driver_desc->ioctl_permissions_cb;
+}
+EXPORT_SYMBOL(gasket_get_ioctl_permissions_cb);
+
+/* Get the driver structure for a given gasket_dev.
+ * @dev: pointer to gasket_dev, implementing the requested driver.
+ */
+const struct gasket_driver_desc *gasket_get_driver_desc(struct gasket_dev *dev)
+{
+ return dev->internal_desc->driver_desc;
+}
+
+/* Get the device structure for a given gasket_dev.
+ * @dev: pointer to gasket_dev, implementing the requested driver.
+ */
+struct device *gasket_get_device(struct gasket_dev *dev)
+{
+ return dev->dev;
+}
+
+/**
+ * Asynchronously waits on device.
+ * @gasket_dev: Device struct.
+ * @bar: Bar
+ * @offset: Register offset
+ * @mask: Register mask
+ * @val: Expected value
+ * @max_retries: number of sleep periods
+ * @delay_ms: Timeout in milliseconds
+ *
+ * Description: Busy waits for a specific combination of bits to be set on a
+ * Gasket register.
+ **/
+int gasket_wait_with_reschedule(struct gasket_dev *gasket_dev, int bar,
+ u64 offset, u64 mask, u64 val,
+ uint max_retries, u64 delay_ms)
+{
+ uint retries = 0;
+ u64 tmp;
+
+ while (retries < max_retries) {
+ tmp = gasket_dev_read_64(gasket_dev, bar, offset);
+ if ((tmp & mask) == val)
+ return 0;
+ msleep(delay_ms);
+ retries++;
+ }
+ dev_dbg(gasket_dev->dev, "%s timeout: reg %llx timeout (%llu ms)\n",
+ __func__, offset, max_retries * delay_ms);
+ return -ETIMEDOUT;
+}
+EXPORT_SYMBOL(gasket_wait_with_reschedule);
+
+/* See gasket_core.h for description. */
+int gasket_register_device(const struct gasket_driver_desc *driver_desc)
+{
+ int i, ret;
+ int desc_idx = -1;
+ struct gasket_internal_desc *internal;
+
+ pr_debug("Loading %s driver version %s\n", driver_desc->name,
+ driver_desc->driver_version);
+ /* Check for duplicates and find a free slot. */
+ mutex_lock(&g_mutex);
+
+ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) {
+ if (g_descs[i].driver_desc == driver_desc) {
+ pr_err("%s driver already loaded/registered\n",
+ driver_desc->name);
+ mutex_unlock(&g_mutex);
+ return -EBUSY;
+ }
+ }
+
+ /* This and the above loop could be combined, but this reads easier. */
+ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) {
+ if (!g_descs[i].driver_desc) {
+ g_descs[i].driver_desc = driver_desc;
+ desc_idx = i;
+ break;
+ }
+ }
+ mutex_unlock(&g_mutex);
+
+ if (desc_idx == -1) {
+ pr_err("too many drivers loaded, max %d\n",
+ GASKET_FRAMEWORK_DESC_MAX);
+ return -EBUSY;
+ }
+
+ internal = &g_descs[desc_idx];
+ mutex_init(&internal->mutex);
+ memset(internal->devs, 0, sizeof(struct gasket_dev *) * GASKET_DEV_MAX);
+ internal->class =
+ class_create(driver_desc->module, driver_desc->name);
+
+ if (IS_ERR(internal->class)) {
+ pr_err("Cannot register %s class [ret=%ld]\n",
+ driver_desc->name, PTR_ERR(internal->class));
+ ret = PTR_ERR(internal->class);
+ goto unregister_gasket_driver;
+ }
+
+ ret = register_chrdev_region(MKDEV(driver_desc->major,
+ driver_desc->minor), GASKET_DEV_MAX,
+ driver_desc->name);
+ if (ret) {
+ pr_err("cannot register %s char driver [ret=%d]\n",
+ driver_desc->name, ret);
+ goto destroy_class;
+ }
+
+ return 0;
+
+destroy_class:
+ class_destroy(internal->class);
+
+unregister_gasket_driver:
+ mutex_lock(&g_mutex);
+ g_descs[desc_idx].driver_desc = NULL;
+ mutex_unlock(&g_mutex);
+ return ret;
+}
+EXPORT_SYMBOL(gasket_register_device);
+
+/* See gasket_core.h for description. */
+void gasket_unregister_device(const struct gasket_driver_desc *driver_desc)
+{
+ int i, desc_idx;
+ struct gasket_internal_desc *internal_desc = NULL;
+
+ mutex_lock(&g_mutex);
+ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) {
+ if (g_descs[i].driver_desc == driver_desc) {
+ internal_desc = &g_descs[i];
+ desc_idx = i;
+ break;
+ }
+ }
+
+ if (!internal_desc) {
+ mutex_unlock(&g_mutex);
+ pr_err("request to unregister unknown desc: %s, %d:%d\n",
+ driver_desc->name, driver_desc->major,
+ driver_desc->minor);
+ return;
+ }
+
+ unregister_chrdev_region(MKDEV(driver_desc->major, driver_desc->minor),
+ GASKET_DEV_MAX);
+
+ class_destroy(internal_desc->class);
+
+ /* Finally, effectively "remove" the driver. */
+ g_descs[desc_idx].driver_desc = NULL;
+ mutex_unlock(&g_mutex);
+
+ pr_debug("removed %s driver\n", driver_desc->name);
+}
+EXPORT_SYMBOL(gasket_unregister_device);
+
+static int __init gasket_init(void)
+{
+ int i;
+
+ mutex_lock(&g_mutex);
+ for (i = 0; i < GASKET_FRAMEWORK_DESC_MAX; i++) {
+ g_descs[i].driver_desc = NULL;
+ mutex_init(&g_descs[i].mutex);
+ }
+
+ gasket_sysfs_init();
+
+ mutex_unlock(&g_mutex);
+ return 0;
+}
+
+MODULE_DESCRIPTION("Google Gasket driver framework");
+MODULE_VERSION(GASKET_FRAMEWORK_VERSION);
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Rob Springer <rspringer@google.com>");
+module_init(gasket_init);
diff --git a/drivers/staging/gasket/gasket_core.h b/drivers/staging/gasket/gasket_core.h
new file mode 100644
index 0000000..56805d3
--- /dev/null
+++ b/drivers/staging/gasket/gasket_core.h
@@ -0,0 +1,644 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Gasket generic driver. Defines the set of data types and functions necessary
+ * to define a driver using the Gasket generic driver framework.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+#ifndef __GASKET_CORE_H__
+#define __GASKET_CORE_H__
+
+#include <linux/cdev.h>
+#include <linux/compiler.h>
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#define writeq_relaxed writeq
+#define writeq(__value, __reg) \
+ (*(volatile u64 __force *)(__reg) = (cpu_to_le64(__value)))
+#define readq_relaxed readq
+#define readq(__reg) le64_to_cpu(*(volatile u64 __force *)(__reg))
+
+#include "gasket_constants.h"
+
+/**
+ * struct gasket_num_name - Map numbers to names.
+ * @ein_num: Number.
+ * @ein_name: Name associated with the number, a char pointer.
+ *
+ * This structure maps numbers to names. It is used to provide printable enum
+ * names, e.g {0, "DEAD"} or {1, "ALIVE"}.
+ */
+struct gasket_num_name {
+ uint snn_num;
+ const char *snn_name;
+};
+
+/*
+ * Register location for packed interrupts.
+ * Each value indicates the location of an interrupt field (in units of
+ * gasket_driver_desc->interrupt_pack_width) within the containing register.
+ * In other words, this indicates the shift to use when creating a mask to
+ * extract/set bits within a register for a given interrupt.
+ */
+enum gasket_interrupt_packing {
+ PACK_0 = 0,
+ PACK_1 = 1,
+ PACK_2 = 2,
+ PACK_3 = 3,
+ UNPACKED = 4,
+};
+
+/* Type of the interrupt supported by the device. */
+enum gasket_interrupt_type {
+ PCI_MSIX = 0,
+};
+
+/*
+ * Used to describe a Gasket interrupt. Contains an interrupt index, a register,
+ * and packing data for that interrupt. The register and packing data
+ * fields are relevant only for PCI_MSIX interrupt type and can be
+ * set to 0 for everything else.
+ */
+struct gasket_interrupt_desc {
+ /* Device-wide interrupt index/number. */
+ int index;
+ /* The register offset controlling this interrupt. */
+ u64 reg;
+ /* The location of this interrupt inside register reg, if packed. */
+ int packing;
+};
+
+/*
+ * This enum is used to identify memory regions being part of the physical
+ * memory that belongs to a device.
+ */
+enum mappable_area_type {
+ PCI_BAR = 0, /* Default */
+ BUS_REGION, /* For SYSBUS devices, i.e. AXI etc... */
+ COHERENT_MEMORY
+};
+
+/*
+ * Metadata for each BAR mapping.
+ * This struct is used so as to track PCI memory, I/O space, AXI and coherent
+ * memory area... i.e. memory objects which can be referenced in the device's
+ * mmap function.
+ */
+struct gasket_bar_data {
+ /* Virtual base address. */
+ u8 __iomem *virt_base;
+
+ /* Physical base address. */
+ ulong phys_base;
+
+ /* Length of the mapping. */
+ ulong length_bytes;
+
+ /* Type of mappable area */
+ enum mappable_area_type type;
+};
+
+/* Maintains device open ownership data. */
+struct gasket_ownership {
+ /* 1 if the device is owned, 0 otherwise. */
+ int is_owned;
+
+ /* TGID of the owner. */
+ pid_t owner;
+
+ /* Count of current device opens in write mode. */
+ int write_open_count;
+};
+
+/* Page table modes of operation. */
+enum gasket_page_table_mode {
+ /* The page table is partitionable as normal, all simple by default. */
+ GASKET_PAGE_TABLE_MODE_NORMAL,
+
+ /* All entries are always simple. */
+ GASKET_PAGE_TABLE_MODE_SIMPLE,
+
+ /* All entries are always extended. No extended bit is used. */
+ GASKET_PAGE_TABLE_MODE_EXTENDED,
+};
+
+/* Page table configuration. One per table. */
+struct gasket_page_table_config {
+ /* The identifier/index of this page table. */
+ int id;
+
+ /* The operation mode of this page table. */
+ enum gasket_page_table_mode mode;
+
+ /* Total (first-level) entries in this page table. */
+ ulong total_entries;
+
+ /* Base register for the page table. */
+ int base_reg;
+
+ /*
+ * Register containing the extended page table. This value is unused in
+ * GASKET_PAGE_TABLE_MODE_SIMPLE and GASKET_PAGE_TABLE_MODE_EXTENDED
+ * modes.
+ */
+ int extended_reg;
+
+ /* The bit index indicating whether a PT entry is extended. */
+ int extended_bit;
+};
+
+/* Maintains information about a device node. */
+struct gasket_cdev_info {
+ /* The internal name of this device. */
+ char name[GASKET_NAME_MAX];
+
+ /* Device number. */
+ dev_t devt;
+
+ /* Kernel-internal device structure. */
+ struct device *device;
+
+ /* Character device for real. */
+ struct cdev cdev;
+
+ /* Flag indicating if cdev_add has been called for the devices. */
+ int cdev_added;
+
+ /* Pointer to the overall gasket_dev struct for this device. */
+ struct gasket_dev *gasket_dev_ptr;
+
+ /* Ownership data for the device in question. */
+ struct gasket_ownership ownership;
+};
+
+/* Describes the offset and length of mmapable device BAR regions. */
+struct gasket_mappable_region {
+ u64 start;
+ u64 length_bytes;
+};
+
+/* Describe the offset, size, and permissions for a device bar. */
+struct gasket_bar_desc {
+ /*
+ * The size of each PCI BAR range, in bytes. If a value is 0, that BAR
+ * will not be mapped into kernel space at all.
+ * For devices with 64 bit BARs, only elements 0, 2, and 4 should be
+ * populated, and 1, 3, and 5 should be set to 0.
+ * For example, for a device mapping 1M in each of the first two 64-bit
+ * BARs, this field would be set as { 0x100000, 0, 0x100000, 0, 0, 0 }
+ * (one number per bar_desc struct.)
+ */
+ u64 size;
+ /* The permissions for this bar. (Should be VM_WRITE/VM_READ/VM_EXEC,
+ * and can be or'd.) If set to GASKET_NOMAP, the bar will
+ * not be used for mmapping.
+ */
+ ulong permissions;
+ /* The memory address corresponding to the base of this bar, if used. */
+ u64 base;
+ /* The number of mappable regions in this bar. */
+ int num_mappable_regions;
+
+ /* The mappable subregions of this bar. */
+ const struct gasket_mappable_region *mappable_regions;
+
+ /* Type of mappable area */
+ enum mappable_area_type type;
+};
+
+/* Describes the offset, size, and permissions for a coherent buffer. */
+struct gasket_coherent_buffer_desc {
+ /* The size of the coherent buffer. */
+ u64 size;
+
+ /* The permissions for this bar. (Should be VM_WRITE/VM_READ/VM_EXEC,
+ * and can be or'd.) If set to GASKET_NOMAP, the bar will
+ * not be used for mmaping.
+ */
+ ulong permissions;
+
+ /* device side address. */
+ u64 base;
+};
+
+/* Coherent buffer structure. */
+struct gasket_coherent_buffer {
+ /* Virtual base address. */
+ u8 __iomem *virt_base;
+
+ /* Physical base address. */
+ ulong phys_base;
+
+ /* Length of the mapping. */
+ ulong length_bytes;
+};
+
+/* Description of Gasket-specific permissions in the mmap field. */
+enum gasket_mapping_options { GASKET_NOMAP = 0 };
+
+/* This struct represents an undefined bar that should never be mapped. */
+#define GASKET_UNUSED_BAR \
+ { \
+ 0, GASKET_NOMAP, 0, 0, NULL, 0 \
+ }
+
+/* Internal data for a Gasket device. See gasket_core.c for more information. */
+struct gasket_internal_desc;
+
+#define MAX_NUM_COHERENT_PAGES 16
+
+/*
+ * Device data for Gasket device instances.
+ *
+ * This structure contains the data required to manage a Gasket device.
+ */
+struct gasket_dev {
+ /* Pointer to the internal driver description for this device. */
+ struct gasket_internal_desc *internal_desc;
+
+ /* Device info */
+ struct device *dev;
+
+ /* PCI subsystem metadata. */
+ struct pci_dev *pci_dev;
+
+ /* This device's index into internal_desc->devs. */
+ int dev_idx;
+
+ /* The name of this device, as reported by the kernel. */
+ char kobj_name[GASKET_NAME_MAX];
+
+ /* Virtual address of mapped BAR memory range. */
+ struct gasket_bar_data bar_data[GASKET_NUM_BARS];
+
+ /* Coherent buffer. */
+ struct gasket_coherent_buffer coherent_buffer;
+
+ /* Number of page tables for this device. */
+ int num_page_tables;
+
+ /* Address translations. Page tables have a private implementation. */
+ struct gasket_page_table *page_table[GASKET_MAX_NUM_PAGE_TABLES];
+
+ /* Interrupt data for this device. */
+ struct gasket_interrupt_data *interrupt_data;
+
+ /* Status for this device - GASKET_STATUS_ALIVE or _DEAD. */
+ uint status;
+
+ /* Number of times this device has been reset. */
+ uint reset_count;
+
+ /* Dev information for the cdev node. */
+ struct gasket_cdev_info dev_info;
+
+ /* Hardware revision value for this device. */
+ int hardware_revision;
+
+ /* Protects access to per-device data (i.e. this structure). */
+ struct mutex mutex;
+
+ /* cdev hash tracking/membership structure, Accel and legacy. */
+ /* Unused until Accel is upstreamed. */
+ struct hlist_node hlist_node;
+ struct hlist_node legacy_hlist_node;
+};
+
+/* Type of the ioctl handler callback. */
+typedef long (*gasket_ioctl_handler_cb_t)(struct file *file, uint cmd,
+ void __user *argp);
+/* Type of the ioctl permissions check callback. See below. */
+typedef int (*gasket_ioctl_permissions_cb_t)(struct file *filp, uint cmd,
+ void __user *argp);
+
+/*
+ * Device type descriptor.
+ *
+ * This structure contains device-specific data needed to identify and address a
+ * type of device to be administered via the Gasket generic driver.
+ *
+ * Device IDs are per-driver. In other words, two drivers using the Gasket
+ * framework will each have a distinct device 0 (for example).
+ */
+struct gasket_driver_desc {
+ /* The name of this device type. */
+ const char *name;
+
+ /* The name of this specific device model. */
+ const char *chip_model;
+
+ /* The version of the chip specified in chip_model. */
+ const char *chip_version;
+
+ /* The version of this driver: "1.0.0", "2.1.3", etc. */
+ const char *driver_version;
+
+ /*
+ * Non-zero if we should create "legacy" (device and device-class-
+ * specific) character devices and sysfs nodes.
+ */
+ /* Unused until Accel is upstreamed. */
+ int legacy_support;
+
+ /* Major and minor numbers identifying the device. */
+ int major, minor;
+
+ /* Module structure for this driver. */
+ struct module *module;
+
+ /* PCI ID table. */
+ const struct pci_device_id *pci_id_table;
+
+ /* The number of page tables handled by this driver. */
+ int num_page_tables;
+
+ /* The index of the bar containing the page tables. */
+ int page_table_bar_index;
+
+ /* Registers used to control each page table. */
+ const struct gasket_page_table_config *page_table_configs;
+
+ /* The bit index indicating whether a PT entry is extended. */
+ int page_table_extended_bit;
+
+ /*
+ * Legacy mmap address adjusment for legacy devices only. Should be 0
+ * for any new device.
+ */
+ ulong legacy_mmap_address_offset;
+
+ /* Set of 6 bar descriptions that describe all PCIe bars.
+ * Note that BUS/AXI devices (i.e. non PCI devices) use those.
+ */
+ struct gasket_bar_desc bar_descriptions[GASKET_NUM_BARS];
+
+ /*
+ * Coherent buffer description.
+ */
+ struct gasket_coherent_buffer_desc coherent_buffer_description;
+
+ /* Interrupt type. (One of gasket_interrupt_type). */
+ int interrupt_type;
+
+ /* Index of the bar containing the interrupt registers to program. */
+ int interrupt_bar_index;
+
+ /* Number of interrupts in the gasket_interrupt_desc array */
+ int num_interrupts;
+
+ /* Description of the interrupts for this device. */
+ const struct gasket_interrupt_desc *interrupts;
+
+ /*
+ * If this device packs multiple interrupt->MSI-X mappings into a
+ * single register (i.e., "uses packed interrupts"), only a single bit
+ * width is supported for each interrupt mapping (unpacked/"full-width"
+ * interrupts are always supported). This value specifies that width. If
+ * packed interrupts are not used, this value is ignored.
+ */
+ int interrupt_pack_width;
+
+ /* Driver callback functions - all may be NULL */
+ /*
+ * device_open_cb: Callback for when a device node is opened in write
+ * mode.
+ * @dev: The gasket_dev struct for this driver instance.
+ *
+ * This callback should perform device-specific setup that needs to
+ * occur only once when a device is first opened.
+ */
+ int (*device_open_cb)(struct gasket_dev *dev);
+
+ /*
+ * device_release_cb: Callback when a device is closed.
+ * @gasket_dev: The gasket_dev struct for this driver instance.
+ *
+ * This callback is called whenever a device node fd is closed, as
+ * opposed to device_close_cb, which is called when the _last_
+ * descriptor for an open file is closed. This call is intended to
+ * handle any per-user or per-fd cleanup.
+ */
+ int (*device_release_cb)(struct gasket_dev *gasket_dev,
+ struct file *file);
+
+ /*
+ * device_close_cb: Callback for when a device node is closed for the
+ * last time.
+ * @dev: The gasket_dev struct for this driver instance.
+ *
+ * This callback should perform device-specific cleanup that only
+ * needs to occur when the last reference to a device node is closed.
+ *
+ * This call is intended to handle and device-wide cleanup, as opposed
+ * to per-fd cleanup (which should be handled by device_release_cb).
+ */
+ int (*device_close_cb)(struct gasket_dev *dev);
+
+ /*
+ * get_mappable_regions_cb: Get descriptors of mappable device memory.
+ * @gasket_dev: Pointer to the struct gasket_dev for this device.
+ * @bar_index: BAR for which to retrieve memory ranges.
+ * @mappable_regions: Out-pointer to the list of mappable regions on the
+ * device/BAR for this process.
+ * @num_mappable_regions: Out-pointer for the size of mappable_regions.
+ *
+ * Called when handling mmap(), this callback is used to determine which
+ * regions of device memory may be mapped by the current process. This
+ * information is then compared to mmap request to determine which
+ * regions to actually map.
+ */
+ int (*get_mappable_regions_cb)(struct gasket_dev *gasket_dev,
+ int bar_index,
+ struct gasket_mappable_region **mappable_regions,
+ int *num_mappable_regions);
+
+ /*
+ * ioctl_permissions_cb: Check permissions for generic ioctls.
+ * @filp: File structure pointer describing this node usage session.
+ * @cmd: ioctl number to handle.
+ * @arg: ioctl-specific data pointer.
+ *
+ * Returns 1 if the ioctl may be executed, 0 otherwise. If this callback
+ * isn't specified a default routine will be used, that only allows the
+ * original device opener (i.e, the "owner") to execute state-affecting
+ * ioctls.
+ */
+ gasket_ioctl_permissions_cb_t ioctl_permissions_cb;
+
+ /*
+ * ioctl_handler_cb: Callback to handle device-specific ioctls.
+ * @filp: File structure pointer describing this node usage session.
+ * @cmd: ioctl number to handle.
+ * @arg: ioctl-specific data pointer.
+ *
+ * Invoked whenever an ioctl is called that the generic Gasket
+ * framework doesn't support. If no cb is registered, unknown ioctls
+ * return -EINVAL. Should return an error status (either -EINVAL or
+ * the error result of the ioctl being handled).
+ */
+ gasket_ioctl_handler_cb_t ioctl_handler_cb;
+
+ /*
+ * device_status_cb: Callback to determine device health.
+ * @dev: Pointer to the gasket_dev struct for this device.
+ *
+ * Called to determine if the device is healthy or not. Should return
+ * a member of the gasket_status_type enum.
+ *
+ */
+ int (*device_status_cb)(struct gasket_dev *dev);
+
+ /*
+ * hardware_revision_cb: Get the device's hardware revision.
+ * @dev: Pointer to the gasket_dev struct for this device.
+ *
+ * Called to determine the reported rev of the physical hardware.
+ * Revision should be >0. A negative return value is an error.
+ */
+ int (*hardware_revision_cb)(struct gasket_dev *dev);
+
+ /*
+ * device_reset_cb: Reset the hardware in question.
+ * @dev: Pointer to the gasket_dev structure for this device.
+ *
+ * Called by reset ioctls. This function should not
+ * lock the gasket_dev mutex. It should return 0 on success
+ * and an error on failure.
+ */
+ int (*device_reset_cb)(struct gasket_dev *dev);
+};
+
+/*
+ * Register the specified device type with the framework.
+ * @desc: Populated/initialized device type descriptor.
+ *
+ * This function does _not_ take ownership of desc; the underlying struct must
+ * exist until the matching call to gasket_unregister_device.
+ * This function should be called from your driver's module_init function.
+ */
+int gasket_register_device(const struct gasket_driver_desc *desc);
+
+/*
+ * Remove the specified device type from the framework.
+ * @desc: Descriptor for the device type to unregister; it should have been
+ * passed to gasket_register_device in a previous call.
+ *
+ * This function should be called from your driver's module_exit function.
+ */
+void gasket_unregister_device(const struct gasket_driver_desc *desc);
+
+/* Add a PCI gasket device. */
+int gasket_pci_add_device(struct pci_dev *pci_dev,
+ struct gasket_dev **gasket_devp);
+/* Remove a PCI gasket device. */
+void gasket_pci_remove_device(struct pci_dev *pci_dev);
+
+/* Enable a Gasket device. */
+int gasket_enable_device(struct gasket_dev *gasket_dev);
+
+/* Disable a Gasket device. */
+void gasket_disable_device(struct gasket_dev *gasket_dev);
+
+/*
+ * Reset the Gasket device.
+ * @gasket_dev: Gasket device struct.
+ *
+ * Calls device_reset_cb. Returns 0 on success and an error code othewrise.
+ * gasket_reset_nolock will not lock the mutex, gasket_reset will.
+ *
+ */
+int gasket_reset(struct gasket_dev *gasket_dev);
+int gasket_reset_nolock(struct gasket_dev *gasket_dev);
+
+/*
+ * Memory management functions. These will likely be spun off into their own
+ * file in the future.
+ */
+
+/* Unmaps the specified mappable region from a VMA. */
+int gasket_mm_unmap_region(const struct gasket_dev *gasket_dev,
+ struct vm_area_struct *vma,
+ const struct gasket_mappable_region *map_region);
+
+/*
+ * Get the ioctl permissions callback.
+ * @gasket_dev: Gasket device structure.
+ */
+gasket_ioctl_permissions_cb_t
+gasket_get_ioctl_permissions_cb(struct gasket_dev *gasket_dev);
+
+/**
+ * Lookup a name by number in a num_name table.
+ * @num: Number to lookup.
+ * @table: Array of num_name structures, the table for the lookup.
+ *
+ */
+const char *gasket_num_name_lookup(uint num,
+ const struct gasket_num_name *table);
+
+/* Handy inlines */
+static inline u64 gasket_dev_read_64(struct gasket_dev *gasket_dev, int bar,
+ ulong location)
+{
+ return readq_relaxed(&gasket_dev->bar_data[bar].virt_base[location]);
+}
+
+static inline void gasket_dev_write_64(struct gasket_dev *dev, u64 value,
+ int bar, ulong location)
+{
+ writeq_relaxed(value, &dev->bar_data[bar].virt_base[location]);
+}
+
+static inline void gasket_dev_write_32(struct gasket_dev *dev, u32 value,
+ int bar, ulong location)
+{
+ writel_relaxed(value, &dev->bar_data[bar].virt_base[location]);
+}
+
+static inline u32 gasket_dev_read_32(struct gasket_dev *dev, int bar,
+ ulong location)
+{
+ return readl_relaxed(&dev->bar_data[bar].virt_base[location]);
+}
+
+static inline void gasket_read_modify_write_64(struct gasket_dev *dev, int bar,
+ ulong location, u64 value,
+ u64 mask_width, u64 mask_shift)
+{
+ u64 mask, tmp;
+
+ tmp = gasket_dev_read_64(dev, bar, location);
+ mask = ((1ULL << mask_width) - 1) << mask_shift;
+ tmp = (tmp & ~mask) | (value << mask_shift);
+ gasket_dev_write_64(dev, tmp, bar, location);
+}
+
+static inline void gasket_read_modify_write_32(struct gasket_dev *dev, int bar,
+ ulong location, u32 value,
+ u32 mask_width, u32 mask_shift)
+{
+ u32 mask, tmp;
+
+ tmp = gasket_dev_read_32(dev, bar, location);
+ mask = ((1 << mask_width) - 1) << mask_shift;
+ tmp = (tmp & ~mask) | (value << mask_shift);
+ gasket_dev_write_32(dev, tmp, bar, location);
+}
+
+/* Get the Gasket driver structure for a given device. */
+const struct gasket_driver_desc *gasket_get_driver_desc(struct gasket_dev *dev);
+
+/* Get the device structure for a given device. */
+struct device *gasket_get_device(struct gasket_dev *dev);
+
+/* Helper function, Asynchronous waits on a given set of bits. */
+int gasket_wait_with_reschedule(struct gasket_dev *gasket_dev, int bar,
+ u64 offset, u64 mask, u64 val,
+ uint max_retries, u64 delay_ms);
+
+#endif /* __GASKET_CORE_H__ */
diff --git a/drivers/staging/gasket/gasket_interrupt.c b/drivers/staging/gasket/gasket_interrupt.c
new file mode 100644
index 0000000..3cf7235
--- /dev/null
+++ b/drivers/staging/gasket/gasket_interrupt.c
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018 Google, Inc. */
+
+#include "gasket_interrupt.h"
+
+#include "gasket_constants.h"
+#include "gasket_core.h"
+#include "gasket_sysfs.h"
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/printk.h>
+#include <linux/rwlock.h>
+#include <linux/version.h>
+#ifdef GASKET_KERNEL_TRACE_SUPPORT
+#define CREATE_TRACE_POINTS
+#include <trace/events/gasket_interrupt.h>
+#else
+#define trace_gasket_interrupt_event(x, ...)
+#endif
+/* Retry attempts if the requested number of interrupts aren't available. */
+#define MSIX_RETRY_COUNT 3
+
+/* Instance interrupt management data. */
+struct gasket_interrupt_data {
+ /* The name associated with this interrupt data. */
+ const char *name;
+
+ /* Interrupt type. See gasket_interrupt_type in gasket_core.h */
+ int type;
+
+ /* The PCI device [if any] associated with the owning device. */
+ struct pci_dev *pci_dev;
+
+ /* Set to 1 if MSI-X has successfully been configred, 0 otherwise. */
+ int msix_configured;
+
+ /* The number of interrupts requested by the owning device. */
+ int num_interrupts;
+
+ /* A pointer to the interrupt descriptor struct for this device. */
+ const struct gasket_interrupt_desc *interrupts;
+
+ /* The index of the bar into which interrupts should be mapped. */
+ int interrupt_bar_index;
+
+ /* The width of a single interrupt in a packed interrupt register. */
+ int pack_width;
+
+ /*
+ * Design-wise, these elements should be bundled together, but
+ * pci_enable_msix's interface requires that they be managed
+ * individually (requires array of struct msix_entry).
+ */
+
+ /* The number of successfully configured interrupts. */
+ int num_configured;
+
+ /* The MSI-X data for each requested/configured interrupt. */
+ struct msix_entry *msix_entries;
+
+ /* The eventfd "callback" data for each interrupt. */
+ struct eventfd_ctx **eventfd_ctxs;
+
+ /* Spinlock to protect read/write races to eventfd_ctxs. */
+ rwlock_t eventfd_ctx_lock;
+
+ /* The number of times each interrupt has been called. */
+ ulong *interrupt_counts;
+
+ /* Linux IRQ number. */
+ int irq;
+};
+
+/* Structures to display interrupt counts in sysfs. */
+enum interrupt_sysfs_attribute_type {
+ ATTR_INTERRUPT_COUNTS,
+};
+
+/* Set up device registers for interrupt handling. */
+static void gasket_interrupt_setup(struct gasket_dev *gasket_dev)
+{
+ int i;
+ int pack_shift;
+ ulong mask;
+ ulong value;
+ struct gasket_interrupt_data *interrupt_data =
+ gasket_dev->interrupt_data;
+
+ if (!interrupt_data) {
+ dev_dbg(gasket_dev->dev, "Interrupt data is not initialized\n");
+ return;
+ }
+
+ dev_dbg(gasket_dev->dev, "Running interrupt setup\n");
+
+ /* Setup the MSIX table. */
+
+ for (i = 0; i < interrupt_data->num_interrupts; i++) {
+ /*
+ * If the interrupt is not packed, we can write the index into
+ * the register directly. If not, we need to deal with a read-
+ * modify-write and shift based on the packing index.
+ */
+ dev_dbg(gasket_dev->dev,
+ "Setting up interrupt index %d with index 0x%llx and "
+ "packing %d\n",
+ interrupt_data->interrupts[i].index,
+ interrupt_data->interrupts[i].reg,
+ interrupt_data->interrupts[i].packing);
+ if (interrupt_data->interrupts[i].packing == UNPACKED) {
+ value = interrupt_data->interrupts[i].index;
+ } else {
+ switch (interrupt_data->interrupts[i].packing) {
+ case PACK_0:
+ pack_shift = 0;
+ break;
+ case PACK_1:
+ pack_shift = interrupt_data->pack_width;
+ break;
+ case PACK_2:
+ pack_shift = 2 * interrupt_data->pack_width;
+ break;
+ case PACK_3:
+ pack_shift = 3 * interrupt_data->pack_width;
+ break;
+ default:
+ dev_dbg(gasket_dev->dev,
+ "Found interrupt description with "
+ "unknown enum %d\n",
+ interrupt_data->interrupts[i].packing);
+ return;
+ }
+
+ mask = ~(0xFFFF << pack_shift);
+ value = gasket_dev_read_64(gasket_dev,
+ interrupt_data->interrupt_bar_index,
+ interrupt_data->interrupts[i].reg);
+ value &= mask;
+ value |= interrupt_data->interrupts[i].index
+ << pack_shift;
+ }
+ gasket_dev_write_64(gasket_dev, value,
+ interrupt_data->interrupt_bar_index,
+ interrupt_data->interrupts[i].reg);
+ }
+}
+
+static void
+gasket_handle_interrupt(struct gasket_interrupt_data *interrupt_data,
+ int interrupt_index)
+{
+ struct eventfd_ctx *ctx;
+
+ trace_gasket_interrupt_event(interrupt_data->name, interrupt_index);
+ read_lock(&interrupt_data->eventfd_ctx_lock);
+ ctx = interrupt_data->eventfd_ctxs[interrupt_index];
+ if (ctx)
+ eventfd_signal(ctx, 1);
+ read_unlock(&interrupt_data->eventfd_ctx_lock);
+
+ ++(interrupt_data->interrupt_counts[interrupt_index]);
+}
+
+static irqreturn_t gasket_msix_interrupt_handler(int irq, void *dev_id)
+{
+ struct gasket_interrupt_data *interrupt_data = dev_id;
+ int interrupt = -1;
+ int i;
+
+ /* If this linear lookup is a problem, we can maintain a map/hash. */
+ for (i = 0; i < interrupt_data->num_interrupts; i++) {
+ if (interrupt_data->msix_entries[i].vector == irq) {
+ interrupt = interrupt_data->msix_entries[i].entry;
+ break;
+ }
+ }
+ if (interrupt == -1) {
+ pr_err("Received unknown irq %d\n", irq);
+ return IRQ_HANDLED;
+ }
+ gasket_handle_interrupt(interrupt_data, interrupt);
+ return IRQ_HANDLED;
+}
+
+static int
+gasket_interrupt_msix_init(struct gasket_interrupt_data *interrupt_data)
+{
+ int ret = 1;
+ int i;
+
+ interrupt_data->msix_entries =
+ kcalloc(interrupt_data->num_interrupts,
+ sizeof(struct msix_entry), GFP_KERNEL);
+ if (!interrupt_data->msix_entries)
+ return -ENOMEM;
+
+ for (i = 0; i < interrupt_data->num_interrupts; i++) {
+ interrupt_data->msix_entries[i].entry = i;
+ interrupt_data->msix_entries[i].vector = 0;
+ interrupt_data->eventfd_ctxs[i] = NULL;
+ }
+
+ /* Retry MSIX_RETRY_COUNT times if not enough IRQs are available. */
+ for (i = 0; i < MSIX_RETRY_COUNT && ret > 0; i++)
+ ret = pci_enable_msix_exact(interrupt_data->pci_dev,
+ interrupt_data->msix_entries,
+ interrupt_data->num_interrupts);
+
+ if (ret)
+ return ret > 0 ? -EBUSY : ret;
+ interrupt_data->msix_configured = 1;
+
+ for (i = 0; i < interrupt_data->num_interrupts; i++) {
+ ret = request_irq(interrupt_data->msix_entries[i].vector,
+ gasket_msix_interrupt_handler, 0,
+ interrupt_data->name, interrupt_data);
+
+ if (ret) {
+ dev_err(&interrupt_data->pci_dev->dev,
+ "Cannot get IRQ for interrupt %d, vector %d; "
+ "%d\n",
+ i, interrupt_data->msix_entries[i].vector, ret);
+ return ret;
+ }
+
+ interrupt_data->num_configured++;
+ }
+
+ return 0;
+}
+
+/*
+ * On QCM DragonBoard, we exit gasket_interrupt_msix_init() and kernel interrupt
+ * setup code with MSIX vectors masked. This is wrong because nothing else in
+ * the driver will normally touch the MSIX vectors.
+ *
+ * As a temporary hack, force unmasking there.
+ *
+ * TODO: Figure out why QCM kernel doesn't unmask the MSIX vectors, after
+ * gasket_interrupt_msix_init(), and remove this code.
+ */
+static void force_msix_interrupt_unmasking(struct gasket_dev *gasket_dev)
+{
+ int i;
+#define MSIX_VECTOR_SIZE 16
+#define MSIX_MASK_BIT_OFFSET 12
+#define APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE 0x46800
+ for (i = 0; i < gasket_dev->interrupt_data->num_configured; i++) {
+ /* Check if the MSIX vector is unmasked */
+ ulong location = APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE +
+ MSIX_MASK_BIT_OFFSET + i * MSIX_VECTOR_SIZE;
+ u32 mask =
+ gasket_dev_read_32(gasket_dev,
+ gasket_dev->interrupt_data->interrupt_bar_index,
+ location);
+ if (!(mask & 1))
+ continue;
+ /* Unmask the msix vector (clear 32 bits) */
+ gasket_dev_write_32(gasket_dev, 0,
+ gasket_dev->interrupt_data->interrupt_bar_index,
+ location);
+ }
+#undef MSIX_VECTOR_SIZE
+#undef MSIX_MASK_BIT_OFFSET
+#undef APEX_BAR2_REG_KERNEL_HIB_MSIX_TABLE
+}
+
+static ssize_t interrupt_sysfs_show(struct device *device,
+ struct device_attribute *attr, char *buf)
+{
+ int i, ret;
+ ssize_t written = 0, total_written = 0;
+ struct gasket_interrupt_data *interrupt_data;
+ struct gasket_dev *gasket_dev;
+ struct gasket_sysfs_attribute *gasket_attr;
+ enum interrupt_sysfs_attribute_type sysfs_type;
+
+ gasket_dev = gasket_sysfs_get_device_data(device);
+ if (!gasket_dev) {
+ dev_dbg(device, "No sysfs mapping found for device\n");
+ return 0;
+ }
+
+ gasket_attr = gasket_sysfs_get_attr(device, attr);
+ if (!gasket_attr) {
+ dev_dbg(device, "No sysfs attr data found for device\n");
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return 0;
+ }
+
+ sysfs_type = (enum interrupt_sysfs_attribute_type)
+ gasket_attr->data.attr_type;
+ interrupt_data = gasket_dev->interrupt_data;
+ switch (sysfs_type) {
+ case ATTR_INTERRUPT_COUNTS:
+ for (i = 0; i < interrupt_data->num_interrupts; ++i) {
+ written =
+ scnprintf(buf, PAGE_SIZE - total_written,
+ "0x%02x: %ld\n", i,
+ interrupt_data->interrupt_counts[i]);
+ total_written += written;
+ buf += written;
+ }
+ ret = total_written;
+ break;
+ default:
+ dev_dbg(gasket_dev->dev, "Unknown attribute: %s\n",
+ attr->attr.name);
+ ret = 0;
+ break;
+ }
+
+ gasket_sysfs_put_attr(device, gasket_attr);
+ gasket_sysfs_put_device_data(device, gasket_dev);
+ return ret;
+}
+
+static struct gasket_sysfs_attribute interrupt_sysfs_attrs[] = {
+ GASKET_SYSFS_RO(interrupt_counts, interrupt_sysfs_show,
+ ATTR_INTERRUPT_COUNTS),
+ GASKET_END_OF_ATTR_ARRAY,
+};
+
+int gasket_interrupt_init(struct gasket_dev *gasket_dev)
+{
+ int ret;
+ struct gasket_interrupt_data *interrupt_data;
+ const struct gasket_driver_desc *driver_desc =
+ gasket_get_driver_desc(gasket_dev);
+
+ interrupt_data = kzalloc(sizeof(struct gasket_interrupt_data),
+ GFP_KERNEL);
+ if (!interrupt_data)
+ return -ENOMEM;
+ gasket_dev->interrupt_data = interrupt_data;
+ interrupt_data->name = driver_desc->name;
+ interrupt_data->type = driver_desc->interrupt_type;
+ interrupt_data->pci_dev = gasket_dev->pci_dev;
+ interrupt_data->num_interrupts = driver_desc->num_interrupts;
+ interrupt_data->interrupts = driver_desc->interrupts;
+ interrupt_data->interrupt_bar_index = driver_desc->interrupt_bar_index;
+ interrupt_data->pack_width = driver_desc->interrupt_pack_width;
+ interrupt_data->num_configured = 0;
+
+ interrupt_data->eventfd_ctxs = kcalloc(driver_desc->num_interrupts,
+ sizeof(struct eventfd_ctx *),
+ GFP_KERNEL);
+ if (!interrupt_data->eventfd_ctxs) {
+ kfree(interrupt_data);
+ return -ENOMEM;
+ }
+
+ interrupt_data->interrupt_counts = kcalloc(driver_desc->num_interrupts,
+ sizeof(ulong),
+ GFP_KERNEL);
+ if (!interrupt_data->interrupt_counts) {
+ kfree(interrupt_data->eventfd_ctxs);
+ kfree(interrupt_data);
+ return -ENOMEM;
+ }
+
+ rwlock_init(&interrupt_data->eventfd_ctx_lock);
+
+ switch (interrupt_data->type) {
+ case PCI_MSIX:
+ ret = gasket_interrupt_msix_init(interrupt_data);
+ if (ret)
+ break;
+ force_msix_interrupt_unmasking(gasket_dev);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ /* Failing to setup interrupts will cause the device to report
+ * GASKET_STATUS_LAMED. But it is not fatal.
+ */
+ dev_warn(gasket_dev->dev,
+ "Couldn't initialize interrupts: %d\n", ret);
+ return 0;
+ }
+
+ gasket_interrupt_setup(gasket_dev);
+ gasket_sysfs_create_entries(gasket_dev->dev_info.device,
+ interrupt_sysfs_attrs);
+
+ return 0;
+}
+
+static void
+gasket_interrupt_msix_cleanup(struct gasket_interrupt_data *interrupt_data)
+{
+ int i;
+
+ for (i = 0; i < interrupt_data->num_configured; i++) {
+ gasket_interrupt_clear_eventfd(interrupt_data, i);
+ free_irq(interrupt_data->msix_entries[i].vector,
+ interrupt_data);
+ }
+ interrupt_data->num_configured = 0;
+
+ if (interrupt_data->msix_configured)
+ pci_disable_msix(interrupt_data->pci_dev);
+ interrupt_data->msix_configured = 0;
+ kfree(interrupt_data->msix_entries);
+}
+
+int gasket_interrupt_reinit(struct gasket_dev *gasket_dev)
+{
+ int ret;
+
+ if (!gasket_dev->interrupt_data) {
+ dev_dbg(gasket_dev->dev,
+ "Attempted to reinit uninitialized interrupt data\n");
+ return -EINVAL;
+ }
+
+ switch (gasket_dev->interrupt_data->type) {
+ case PCI_MSIX:
+ gasket_interrupt_msix_cleanup(gasket_dev->interrupt_data);
+ ret = gasket_interrupt_msix_init(gasket_dev->interrupt_data);
+ if (ret)
+ break;
+ force_msix_interrupt_unmasking(gasket_dev);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ if (ret) {
+ /* Failing to setup interrupts will cause the device
+ * to report GASKET_STATUS_LAMED, but is not fatal.
+ */
+ dev_warn(gasket_dev->dev, "Couldn't reinit interrupts: %d\n",
+ ret);
+ return 0;
+ }
+
+ gasket_interrupt_setup(gasket_dev);
+
+ return 0;
+}
+
+/* See gasket_interrupt.h for description. */
+int gasket_interrupt_reset_counts(struct gasket_dev *gasket_dev)
+{
+ dev_dbg(gasket_dev->dev, "Clearing interrupt counts\n");
+ memset(gasket_dev->interrupt_data->interrupt_counts, 0,
+ gasket_dev->interrupt_data->num_interrupts *
+ sizeof(*gasket_dev->interrupt_data->interrupt_counts));
+ return 0;
+}
+
+/* See gasket_interrupt.h for description. */
+void gasket_interrupt_cleanup(struct gasket_dev *gasket_dev)
+{
+ struct gasket_interrupt_data *interrupt_data =
+ gasket_dev->interrupt_data;
+ /*
+ * It is possible to get an error code from gasket_interrupt_init
+ * before interrupt_data has been allocated, so check it.
+ */
+ if (!interrupt_data)
+ return;
+
+ switch (interrupt_data->type) {
+ case PCI_MSIX:
+ gasket_interrupt_msix_cleanup(interrupt_data);
+ break;
+
+ default:
+ break;
+ }
+
+ kfree(interrupt_data->interrupt_counts);
+ kfree(interrupt_data->eventfd_ctxs);
+ kfree(interrupt_data);
+ gasket_dev->interrupt_data = NULL;
+}
+
+int gasket_interrupt_system_status(struct gasket_dev *gasket_dev)
+{
+ if (!gasket_dev->interrupt_data) {
+ dev_dbg(gasket_dev->dev, "Interrupt data is null\n");
+ return GASKET_STATUS_DEAD;
+ }
+
+ if (!gasket_dev->interrupt_data->msix_configured) {
+ dev_dbg(gasket_dev->dev, "Interrupt not initialized\n");
+ return GASKET_STATUS_LAMED;
+ }
+
+ if (gasket_dev->interrupt_data->num_configured !=
+ gasket_dev->interrupt_data->num_interrupts) {
+ dev_dbg(gasket_dev->dev,
+ "Not all interrupts were configured\n");
+ return GASKET_STATUS_LAMED;
+ }
+
+ return GASKET_STATUS_ALIVE;
+}
+
+int gasket_interrupt_set_eventfd(struct gasket_interrupt_data *interrupt_data,
+ int interrupt, int event_fd)
+{
+ struct eventfd_ctx *ctx;
+ ulong flags;
+
+ if (interrupt < 0 || interrupt >= interrupt_data->num_interrupts)
+ return -EINVAL;
+
+ ctx = eventfd_ctx_fdget(event_fd);
+ if (IS_ERR(ctx))
+ return PTR_ERR(ctx);
+
+ /* Put the old eventfd ctx before setting, else we leak the ref. */
+ write_lock_irqsave(&interrupt_data->eventfd_ctx_lock, flags);
+ if (interrupt_data->eventfd_ctxs[interrupt] != NULL)
+ eventfd_ctx_put(interrupt_data->eventfd_ctxs[interrupt]);
+ interrupt_data->eventfd_ctxs[interrupt] = ctx;
+ write_unlock_irqrestore(&interrupt_data->eventfd_ctx_lock, flags);
+ return 0;
+}
+
+int gasket_interrupt_clear_eventfd(struct gasket_interrupt_data *interrupt_data,
+ int interrupt)
+{
+ ulong flags;
+
+ if (interrupt < 0 || interrupt >= interrupt_data->num_interrupts)
+ return -EINVAL;
+
+ /* Put the old eventfd ctx before clearing, else we leak the ref. */
+ write_lock_irqsave(&interrupt_data->eventfd_ctx_lock, flags);
+ if (interrupt_data->eventfd_ctxs[interrupt] != NULL)
+ eventfd_ctx_put(interrupt_data->eventfd_ctxs[interrupt]);
+ interrupt_data->eventfd_ctxs[interrupt] = NULL;
+ write_unlock_irqrestore(&interrupt_data->eventfd_ctx_lock, flags);
+ return 0;
+}
diff --git a/drivers/staging/gasket/gasket_interrupt.h b/drivers/staging/gasket/gasket_interrupt.h
new file mode 100644
index 0000000..85526a1
--- /dev/null
+++ b/drivers/staging/gasket/gasket_interrupt.h
@@ -0,0 +1,95 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Gasket common interrupt module. Defines functions for enabling
+ * eventfd-triggered interrupts between a Gasket device and a host process.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+#ifndef __GASKET_INTERRUPT_H__
+#define __GASKET_INTERRUPT_H__
+
+#include <linux/eventfd.h>
+#include <linux/pci.h>
+
+#include "gasket_core.h"
+
+/* Note that this currently assumes that device interrupts are a dense set,
+ * numbered from 0 - (num_interrupts - 1). Should this have to change, these
+ * APIs will have to be updated.
+ */
+
+/* Opaque type used to hold interrupt subsystem data. */
+struct gasket_interrupt_data;
+
+/*
+ * Initialize the interrupt module.
+ * @gasket_dev: The Gasket device structure for the device to be initted.
+ */
+int gasket_interrupt_init(struct gasket_dev *gasket_dev);
+
+/*
+ * Clean up a device's interrupt structure.
+ * @gasket_dev: The Gasket information structure for this device.
+ *
+ * Cleans up the device's interrupts and deallocates data.
+ */
+void gasket_interrupt_cleanup(struct gasket_dev *gasket_dev);
+
+/*
+ * Clean up and re-initialize the MSI-x subsystem.
+ * @gasket_dev: The Gasket information structure for this device.
+ *
+ * Performs a teardown of the MSI-x subsystem and re-initializes it. Does not
+ * free the underlying data structures. Returns 0 on success and an error code
+ * on error.
+ */
+int gasket_interrupt_reinit(struct gasket_dev *gasket_dev);
+
+/*
+ * Reset the counts stored in the interrupt subsystem.
+ * @gasket_dev: The Gasket information structure for this device.
+ *
+ * Sets the counts of all interrupts in the subsystem to 0.
+ */
+int gasket_interrupt_reset_counts(struct gasket_dev *gasket_dev);
+
+/*
+ * Associates an eventfd with a device interrupt.
+ * @data: Pointer to device interrupt data.
+ * @interrupt: The device interrupt to configure.
+ * @event_fd: The eventfd to associate with the interrupt.
+ *
+ * Prepares the host to receive notification of device interrupts by associating
+ * event_fd with interrupt. Upon receipt of a device interrupt, event_fd will be
+ * signaled, after successful configuration.
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ */
+int gasket_interrupt_set_eventfd(struct gasket_interrupt_data *interrupt_data,
+ int interrupt, int event_fd);
+
+/*
+ * Removes an interrupt-eventfd association.
+ * @data: Pointer to device interrupt data.
+ * @interrupt: The device interrupt to de-associate.
+ *
+ * Removes any eventfd associated with the specified interrupt, if any.
+ */
+int gasket_interrupt_clear_eventfd(struct gasket_interrupt_data *interrupt_data,
+ int interrupt);
+
+/*
+ * The below functions exist for backwards compatibility.
+ * No new uses should be written.
+ */
+/*
+ * Get the health of the interrupt subsystem.
+ * @gasket_dev: The Gasket device struct.
+ *
+ * Returns DEAD if not set up, LAMED if initialization failed, and ALIVE
+ * otherwise.
+ */
+
+int gasket_interrupt_system_status(struct gasket_dev *gasket_dev);
+
+#endif
diff --git a/drivers/staging/gasket/gasket_ioctl.c b/drivers/staging/gasket/gasket_ioctl.c
new file mode 100644
index 0000000..d32bce9
--- /dev/null
+++ b/drivers/staging/gasket/gasket_ioctl.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018 Google, Inc. */
+#include "gasket.h"
+#include "gasket_ioctl.h"
+#include "gasket_constants.h"
+#include "gasket_core.h"
+#include "gasket_interrupt.h"
+#include "gasket_page_table.h"
+#include <linux/compiler.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/uaccess.h>
+
+#ifdef GASKET_KERNEL_TRACE_SUPPORT
+#define CREATE_TRACE_POINTS
+#include <trace/events/gasket_ioctl.h>
+#else
+#define trace_gasket_ioctl_entry(x, ...)
+#define trace_gasket_ioctl_exit(x)
+#define trace_gasket_ioctl_integer_data(x)
+#define trace_gasket_ioctl_eventfd_data(x, ...)
+#define trace_gasket_ioctl_page_table_data(x, ...)
+#define trace_gasket_ioctl_config_coherent_allocator(x, ...)
+#endif
+
+/* Associate an eventfd with an interrupt. */
+static int gasket_set_event_fd(struct gasket_dev *gasket_dev,
+ struct gasket_interrupt_eventfd __user *argp)
+{
+ struct gasket_interrupt_eventfd die;
+
+ if (copy_from_user(&die, argp, sizeof(struct gasket_interrupt_eventfd)))
+ return -EFAULT;
+
+ trace_gasket_ioctl_eventfd_data(die.interrupt, die.event_fd);
+
+ return gasket_interrupt_set_eventfd(
+ gasket_dev->interrupt_data, die.interrupt, die.event_fd);
+}
+
+/* Read the size of the page table. */
+static int gasket_read_page_table_size(
+ struct gasket_dev *gasket_dev,
+ struct gasket_page_table_ioctl __user *argp)
+{
+ int ret = 0;
+ struct gasket_page_table_ioctl ibuf;
+
+ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl)))
+ return -EFAULT;
+
+ if (ibuf.page_table_index >= gasket_dev->num_page_tables)
+ return -EFAULT;
+
+ ibuf.size = gasket_page_table_num_entries(
+ gasket_dev->page_table[ibuf.page_table_index]);
+
+ trace_gasket_ioctl_page_table_data(
+ ibuf.page_table_index, ibuf.size, ibuf.host_address,
+ ibuf.device_address);
+
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf)))
+ return -EFAULT;
+
+ return ret;
+}
+
+/* Read the size of the simple page table. */
+static int gasket_read_simple_page_table_size(
+ struct gasket_dev *gasket_dev,
+ struct gasket_page_table_ioctl __user *argp)
+{
+ int ret = 0;
+ struct gasket_page_table_ioctl ibuf;
+
+ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl)))
+ return -EFAULT;
+
+ if (ibuf.page_table_index >= gasket_dev->num_page_tables)
+ return -EFAULT;
+
+ ibuf.size =
+ gasket_page_table_num_simple_entries(gasket_dev->page_table[ibuf.page_table_index]);
+
+ trace_gasket_ioctl_page_table_data(ibuf.page_table_index, ibuf.size,
+ ibuf.host_address,
+ ibuf.device_address);
+
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf)))
+ return -EFAULT;
+
+ return ret;
+}
+
+/* Set the boundary between the simple and extended page tables. */
+static int gasket_partition_page_table(
+ struct gasket_dev *gasket_dev,
+ struct gasket_page_table_ioctl __user *argp)
+{
+ int ret;
+ struct gasket_page_table_ioctl ibuf;
+ uint max_page_table_size;
+
+ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl)))
+ return -EFAULT;
+
+ trace_gasket_ioctl_page_table_data(
+ ibuf.page_table_index, ibuf.size, ibuf.host_address,
+ ibuf.device_address);
+
+ if (ibuf.page_table_index >= gasket_dev->num_page_tables)
+ return -EFAULT;
+ max_page_table_size = gasket_page_table_max_size(
+ gasket_dev->page_table[ibuf.page_table_index]);
+
+ if (ibuf.size > max_page_table_size) {
+ dev_dbg(gasket_dev->dev,
+ "Partition request 0x%llx too large, max is 0x%x\n",
+ ibuf.size, max_page_table_size);
+ return -EINVAL;
+ }
+
+ mutex_lock(&gasket_dev->mutex);
+
+ ret = gasket_page_table_partition(
+ gasket_dev->page_table[ibuf.page_table_index], ibuf.size);
+ mutex_unlock(&gasket_dev->mutex);
+
+ return ret;
+}
+
+/* Map a userspace buffer to a device virtual address. */
+static int gasket_map_buffers(struct gasket_dev *gasket_dev,
+ struct gasket_page_table_ioctl __user *argp)
+{
+ struct gasket_page_table_ioctl ibuf;
+
+ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl)))
+ return -EFAULT;
+
+ trace_gasket_ioctl_page_table_data(ibuf.page_table_index, ibuf.size,
+ ibuf.host_address,
+ ibuf.device_address);
+
+ if (ibuf.page_table_index >= gasket_dev->num_page_tables)
+ return -EFAULT;
+
+ if (gasket_page_table_are_addrs_bad(gasket_dev->page_table[ibuf.page_table_index],
+ ibuf.host_address,
+ ibuf.device_address, ibuf.size))
+ return -EINVAL;
+
+ return gasket_page_table_map(gasket_dev->page_table[ibuf.page_table_index],
+ ibuf.host_address, ibuf.device_address,
+ ibuf.size / PAGE_SIZE);
+}
+
+/* Unmap a userspace buffer from a device virtual address. */
+static int gasket_unmap_buffers(struct gasket_dev *gasket_dev,
+ struct gasket_page_table_ioctl __user *argp)
+{
+ struct gasket_page_table_ioctl ibuf;
+
+ if (copy_from_user(&ibuf, argp, sizeof(struct gasket_page_table_ioctl)))
+ return -EFAULT;
+
+ trace_gasket_ioctl_page_table_data(ibuf.page_table_index, ibuf.size,
+ ibuf.host_address,
+ ibuf.device_address);
+
+ if (ibuf.page_table_index >= gasket_dev->num_page_tables)
+ return -EFAULT;
+
+ if (gasket_page_table_is_dev_addr_bad(gasket_dev->page_table[ibuf.page_table_index],
+ ibuf.device_address, ibuf.size))
+ return -EINVAL;
+
+ gasket_page_table_unmap(gasket_dev->page_table[ibuf.page_table_index],
+ ibuf.device_address, ibuf.size / PAGE_SIZE);
+
+ return 0;
+}
+
+/*
+ * Reserve structures for coherent allocation, and allocate or free the
+ * corresponding memory.
+ */
+static int gasket_config_coherent_allocator(
+ struct gasket_dev *gasket_dev,
+ struct gasket_coherent_alloc_config_ioctl __user *argp)
+{
+ int ret;
+ struct gasket_coherent_alloc_config_ioctl ibuf;
+ dma_addr_t dma_address;
+
+ if (copy_from_user(&ibuf, argp,
+ sizeof(struct gasket_coherent_alloc_config_ioctl)))
+ return -EFAULT;
+
+ trace_gasket_ioctl_config_coherent_allocator(ibuf.enable, ibuf.size,
+ ibuf.dma_address);
+
+ if (ibuf.page_table_index >= gasket_dev->num_page_tables)
+ return -EFAULT;
+
+ if (ibuf.size > PAGE_SIZE * MAX_NUM_COHERENT_PAGES)
+ return -ENOMEM;
+
+ if (ibuf.enable == 0) {
+ dma_address = ibuf.dma_address;
+ ret = gasket_free_coherent_memory(gasket_dev, ibuf.size,
+ dma_address,
+ ibuf.page_table_index);
+ } else {
+ ret = gasket_alloc_coherent_memory(gasket_dev, ibuf.size,
+ &dma_address,
+ ibuf.page_table_index);
+ }
+ if (ret)
+ return ret;
+
+ if (ibuf.enable != 0)
+ ibuf.dma_address = dma_address;
+
+ if (copy_to_user(argp, &ibuf, sizeof(ibuf)))
+ return -EFAULT;
+
+ return 0;
+}
+
+/* Check permissions for Gasket ioctls. */
+static bool gasket_ioctl_check_permissions(struct file *filp, uint cmd)
+{
+ bool alive;
+ bool read, write;
+ struct gasket_dev *gasket_dev = (struct gasket_dev *)filp->private_data;
+
+ alive = (gasket_dev->status == GASKET_STATUS_ALIVE);
+ if (!alive)
+ dev_dbg(gasket_dev->dev, "%s alive %d status %d\n",
+ __func__, alive, gasket_dev->status);
+
+ read = !!(filp->f_mode & FMODE_READ);
+ write = !!(filp->f_mode & FMODE_WRITE);
+
+ switch (cmd) {
+ case GASKET_IOCTL_RESET:
+ case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS:
+ return write;
+
+ case GASKET_IOCTL_PAGE_TABLE_SIZE:
+ case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE:
+ case GASKET_IOCTL_NUMBER_PAGE_TABLES:
+ return read;
+
+ case GASKET_IOCTL_PARTITION_PAGE_TABLE:
+ case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR:
+ return alive && write;
+
+ case GASKET_IOCTL_MAP_BUFFER:
+ case GASKET_IOCTL_UNMAP_BUFFER:
+ return alive && write;
+
+ case GASKET_IOCTL_CLEAR_EVENTFD:
+ case GASKET_IOCTL_SET_EVENTFD:
+ return alive && write;
+ }
+
+ return false; /* unknown permissions */
+}
+
+/*
+ * standard ioctl dispatch function.
+ * @filp: File structure pointer describing this node usage session.
+ * @cmd: ioctl number to handle.
+ * @argp: ioctl-specific data pointer.
+ *
+ * Standard ioctl dispatcher; forwards operations to individual handlers.
+ */
+long gasket_handle_ioctl(struct file *filp, uint cmd, void __user *argp)
+{
+ struct gasket_dev *gasket_dev;
+ unsigned long arg = (unsigned long)argp;
+ gasket_ioctl_permissions_cb_t ioctl_permissions_cb;
+ int retval;
+
+ gasket_dev = (struct gasket_dev *)filp->private_data;
+ trace_gasket_ioctl_entry(gasket_dev->dev_info.name, cmd);
+
+ ioctl_permissions_cb = gasket_get_ioctl_permissions_cb(gasket_dev);
+ if (ioctl_permissions_cb) {
+ retval = ioctl_permissions_cb(filp, cmd, argp);
+ if (retval < 0) {
+ trace_gasket_ioctl_exit(retval);
+ return retval;
+ } else if (retval == 0) {
+ trace_gasket_ioctl_exit(-EPERM);
+ return -EPERM;
+ }
+ } else if (!gasket_ioctl_check_permissions(filp, cmd)) {
+ trace_gasket_ioctl_exit(-EPERM);
+ dev_dbg(gasket_dev->dev, "ioctl cmd=%x noperm\n", cmd);
+ return -EPERM;
+ }
+
+ /* Tracing happens in this switch statement for all ioctls with
+ * an integer argrument, but ioctls with a struct argument
+ * that needs copying and decoding, that tracing is done within
+ * the handler call.
+ */
+ switch (cmd) {
+ case GASKET_IOCTL_RESET:
+ retval = gasket_reset(gasket_dev);
+ break;
+ case GASKET_IOCTL_SET_EVENTFD:
+ retval = gasket_set_event_fd(gasket_dev, argp);
+ break;
+ case GASKET_IOCTL_CLEAR_EVENTFD:
+ trace_gasket_ioctl_integer_data(arg);
+ retval =
+ gasket_interrupt_clear_eventfd(gasket_dev->interrupt_data,
+ (int)arg);
+ break;
+ case GASKET_IOCTL_PARTITION_PAGE_TABLE:
+ trace_gasket_ioctl_integer_data(arg);
+ retval = gasket_partition_page_table(gasket_dev, argp);
+ break;
+ case GASKET_IOCTL_NUMBER_PAGE_TABLES:
+ trace_gasket_ioctl_integer_data(gasket_dev->num_page_tables);
+ if (copy_to_user(argp, &gasket_dev->num_page_tables,
+ sizeof(uint64_t)))
+ retval = -EFAULT;
+ else
+ retval = 0;
+ break;
+ case GASKET_IOCTL_PAGE_TABLE_SIZE:
+ retval = gasket_read_page_table_size(gasket_dev, argp);
+ break;
+ case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE:
+ retval = gasket_read_simple_page_table_size(gasket_dev, argp);
+ break;
+ case GASKET_IOCTL_MAP_BUFFER:
+ retval = gasket_map_buffers(gasket_dev, argp);
+ break;
+ case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR:
+ retval = gasket_config_coherent_allocator(gasket_dev, argp);
+ break;
+ case GASKET_IOCTL_UNMAP_BUFFER:
+ retval = gasket_unmap_buffers(gasket_dev, argp);
+ break;
+ case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS:
+ /* Clear interrupt counts doesn't take an arg, so use 0. */
+ trace_gasket_ioctl_integer_data(0);
+ retval = gasket_interrupt_reset_counts(gasket_dev);
+ break;
+ default:
+ /* If we don't understand the ioctl, the best we can do is trace
+ * the arg.
+ */
+ trace_gasket_ioctl_integer_data(arg);
+ dev_dbg(gasket_dev->dev,
+ "Unknown ioctl cmd=0x%x not caught by "
+ "gasket_is_supported_ioctl\n",
+ cmd);
+ retval = -EINVAL;
+ break;
+ }
+
+ trace_gasket_ioctl_exit(retval);
+ return retval;
+}
+
+/*
+ * Determines if an ioctl is part of the standard Gasket framework.
+ * @cmd: The ioctl number to handle.
+ *
+ * Returns 1 if the ioctl is supported and 0 otherwise.
+ */
+long gasket_is_supported_ioctl(uint cmd)
+{
+ switch (cmd) {
+ case GASKET_IOCTL_RESET:
+ case GASKET_IOCTL_SET_EVENTFD:
+ case GASKET_IOCTL_CLEAR_EVENTFD:
+ case GASKET_IOCTL_PARTITION_PAGE_TABLE:
+ case GASKET_IOCTL_NUMBER_PAGE_TABLES:
+ case GASKET_IOCTL_PAGE_TABLE_SIZE:
+ case GASKET_IOCTL_SIMPLE_PAGE_TABLE_SIZE:
+ case GASKET_IOCTL_MAP_BUFFER:
+ case GASKET_IOCTL_UNMAP_BUFFER:
+ case GASKET_IOCTL_CLEAR_INTERRUPT_COUNTS:
+ case GASKET_IOCTL_CONFIG_COHERENT_ALLOCATOR:
+ return 1;
+ default:
+ return 0;
+ }
+}
diff --git a/drivers/staging/gasket/gasket_ioctl.h b/drivers/staging/gasket/gasket_ioctl.h
new file mode 100644
index 0000000..51f468c
--- /dev/null
+++ b/drivers/staging/gasket/gasket_ioctl.h
@@ -0,0 +1,28 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* Copyright (C) 2018 Google, Inc. */
+#ifndef __GASKET_IOCTL_H__
+#define __GASKET_IOCTL_H__
+
+#include "gasket_core.h"
+
+#include <linux/compiler.h>
+
+/*
+ * Handle Gasket common ioctls.
+ * @filp: Pointer to the ioctl's file.
+ * @cmd: Ioctl command.
+ * @arg: Ioctl argument pointer.
+ *
+ * Returns 0 on success and nonzero on failure.
+ */
+long gasket_handle_ioctl(struct file *filp, uint cmd, void __user *argp);
+
+/*
+ * Determines if an ioctl is part of the standard Gasket framework.
+ * @cmd: The ioctl number to handle.
+ *
+ * Returns 1 if the ioctl is supported and 0 otherwise.
+ */
+long gasket_is_supported_ioctl(uint cmd);
+
+#endif
diff --git a/drivers/staging/gasket/gasket_page_table.c b/drivers/staging/gasket/gasket_page_table.c
new file mode 100644
index 0000000..3aff2aa
--- /dev/null
+++ b/drivers/staging/gasket/gasket_page_table.c
@@ -0,0 +1,1375 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Implementation of Gasket page table support.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+
+/*
+ * Implementation of Gasket page table support.
+ *
+ * This file assumes 4kB pages throughout; can be factored out when necessary.
+ *
+ * Address format is as follows:
+ * Simple addresses - those whose containing pages are directly placed in the
+ * device's address translation registers - are laid out as:
+ * [ 63 - 40: Unused | 39 - 28: 0 | 27 - 12: page index | 11 - 0: page offset ]
+ * page index: The index of the containing page in the device's address
+ * translation registers.
+ * page offset: The index of the address into the containing page.
+ *
+ * Extended address - those whose containing pages are contained in a second-
+ * level page table whose address is present in the device's address translation
+ * registers - are laid out as:
+ * [ 63 - 40: Unused | 39: flag | 38 - 37: 0 | 36 - 21: dev/level 0 index |
+ * 20 - 12: host/level 1 index | 11 - 0: page offset ]
+ * flag: Marker indicating that this is an extended address. Always 1.
+ * dev index: The index of the first-level page in the device's extended
+ * address translation registers.
+ * host index: The index of the containing page in the [host-resident] second-
+ * level page table.
+ * page offset: The index of the address into the containing [second-level]
+ * page.
+ */
+#include "gasket_page_table.h"
+
+#include <linux/device.h>
+#include <linux/file.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/pagemap.h>
+#include <linux/vmalloc.h>
+
+#include "gasket_constants.h"
+#include "gasket_core.h"
+
+/* Constants & utility macros */
+/* The number of pages that can be mapped into each second-level page table. */
+#define GASKET_PAGES_PER_SUBTABLE 512
+
+/* The starting position of the page index in a simple virtual address. */
+#define GASKET_SIMPLE_PAGE_SHIFT 12
+
+/* Flag indicating that a [device] slot is valid for use. */
+#define GASKET_VALID_SLOT_FLAG 1
+
+/*
+ * The starting position of the level 0 page index (i.e., the entry in the
+ * device's extended address registers) in an extended address.
+ * Also can be thought of as (log2(PAGE_SIZE) + log2(PAGES_PER_SUBTABLE)),
+ * or (12 + 9).
+ */
+#define GASKET_EXTENDED_LVL0_SHIFT 21
+
+/*
+ * Number of first level pages that Gasket chips support. Equivalent to
+ * log2(NUM_LVL0_PAGE_TABLES)
+ *
+ * At a maximum, allowing for a 34 bits address space (or 16GB)
+ * = GASKET_EXTENDED_LVL0_WIDTH + (log2(PAGE_SIZE) + log2(PAGES_PER_SUBTABLE)
+ * or, = 13 + 9 + 12
+ */
+#define GASKET_EXTENDED_LVL0_WIDTH 13
+
+/*
+ * The starting position of the level 1 page index (i.e., the entry in the
+ * host second-level/sub- table) in an extended address.
+ */
+#define GASKET_EXTENDED_LVL1_SHIFT 12
+
+/* Type declarations */
+/* Valid states for a struct gasket_page_table_entry. */
+enum pte_status {
+ PTE_FREE,
+ PTE_INUSE,
+};
+
+/*
+ * Mapping metadata for a single page.
+ *
+ * In this file, host-side page table entries are referred to as that (or PTEs).
+ * Where device vs. host entries are differentiated, device-side or -visible
+ * entries are called "slots". A slot may be either an entry in the device's
+ * address translation table registers or an entry in a second-level page
+ * table ("subtable").
+ *
+ * The full data in this structure is visible on the host [of course]. Only
+ * the address contained in dma_addr is communicated to the device; that points
+ * to the actual page mapped and described by this structure.
+ */
+struct gasket_page_table_entry {
+ /* The status of this entry/slot: free or in use. */
+ enum pte_status status;
+
+ /* Address of the page in DMA space. */
+ dma_addr_t dma_addr;
+
+ /* Linux page descriptor for the page described by this structure. */
+ struct page *page;
+
+ /*
+ * Index for alignment into host vaddrs.
+ * When a user specifies a host address for a mapping, that address may
+ * not be page-aligned. Offset is the index into the containing page of
+ * the host address (i.e., host_vaddr & (PAGE_SIZE - 1)).
+ * This is necessary for translating between user-specified addresses
+ * and page-aligned addresses.
+ */
+ int offset;
+
+ /*
+ * If this is an extended and first-level entry, sublevel points
+ * to the second-level entries underneath this entry.
+ */
+ struct gasket_page_table_entry *sublevel;
+};
+
+/*
+ * Maintains virtual to physical address mapping for a coherent page that is
+ * allocated by this module for a given device.
+ * Note that coherent pages mappings virt mapping cannot be tracked by the
+ * Linux kernel, and coherent pages don't have a struct page associated,
+ * hence Linux kernel cannot perform a get_user_page_xx() on a phys address
+ * that was allocated coherent.
+ * This structure trivially implements this mechanism.
+ */
+struct gasket_coherent_page_entry {
+ /* Phys address, dma'able by the owner device */
+ dma_addr_t paddr;
+
+ /* Kernel virtual address */
+ u64 user_virt;
+
+ /* User virtual address that was mapped by the mmap kernel subsystem */
+ dma_addr_t kernel_virt;
+
+ /*
+ * Whether this page has been mapped into a user land process virtual
+ * space
+ */
+ u32 in_use;
+};
+
+/*
+ * [Host-side] page table descriptor.
+ *
+ * This structure tracks the metadata necessary to manage both simple and
+ * extended page tables.
+ */
+struct gasket_page_table {
+ /* The config used to create this page table. */
+ struct gasket_page_table_config config;
+
+ /* The number of simple (single-level) entries in the page table. */
+ uint num_simple_entries;
+
+ /* The number of extended (two-level) entries in the page table. */
+ uint num_extended_entries;
+
+ /* Array of [host-side] page table entries. */
+ struct gasket_page_table_entry *entries;
+
+ /* Number of actively mapped kernel pages in this table. */
+ uint num_active_pages;
+
+ /* Device register: base of/first slot in the page table. */
+ u64 __iomem *base_slot;
+
+ /* Device register: holds the offset indicating the start of the
+ * extended address region of the device's address translation table.
+ */
+ u64 __iomem *extended_offset_reg;
+
+ /* Device structure for the underlying device. Only used for logging. */
+ struct device *device;
+
+ /* PCI system descriptor for the underlying device. */
+ struct pci_dev *pci_dev;
+
+ /* Location of the extended address bit for this Gasket device. */
+ u64 extended_flag;
+
+ /* Mutex to protect page table internals. */
+ struct mutex mutex;
+
+ /* Number of coherent pages accessible thru by this page table */
+ int num_coherent_pages;
+
+ /*
+ * List of coherent memory (physical) allocated for a device.
+ *
+ * This structure also remembers the user virtual mapping, this is
+ * hacky, but we need to do this because the kernel doesn't keep track
+ * of the user coherent pages (pfn pages), and virt to coherent page
+ * mapping.
+ * TODO: use find_vma() APIs to convert host address to vm_area, to
+ * dma_addr_t instead of storing user virtu address in
+ * gasket_coherent_page_entry
+ *
+ * Note that the user virtual mapping is created by the driver, in
+ * gasket_mmap function, so user_virt belongs in the driver anyhow.
+ */
+ struct gasket_coherent_page_entry *coherent_pages;
+};
+
+/* See gasket_page_table.h for description. */
+int gasket_page_table_init(struct gasket_page_table **ppg_tbl,
+ const struct gasket_bar_data *bar_data,
+ const struct gasket_page_table_config *page_table_config,
+ struct device *device, struct pci_dev *pci_dev)
+{
+ ulong bytes;
+ struct gasket_page_table *pg_tbl;
+ ulong total_entries = page_table_config->total_entries;
+
+ /*
+ * TODO: Verify config->total_entries against value read from the
+ * hardware register that contains the page table size.
+ */
+ if (total_entries == ULONG_MAX) {
+ dev_dbg(device, "Error reading page table size. "
+ "Initializing page table with size 0\n");
+ total_entries = 0;
+ }
+
+ dev_dbg(device,
+ "Attempting to initialize page table of size 0x%lx\n",
+ total_entries);
+
+ dev_dbg(device,
+ "Table has base reg 0x%x, extended offset reg 0x%x\n",
+ page_table_config->base_reg,
+ page_table_config->extended_reg);
+
+ *ppg_tbl = kzalloc(sizeof(**ppg_tbl), GFP_KERNEL);
+ if (!*ppg_tbl) {
+ dev_dbg(device, "No memory for page table\n");
+ return -ENOMEM;
+ }
+
+ pg_tbl = *ppg_tbl;
+ bytes = total_entries * sizeof(struct gasket_page_table_entry);
+ if (bytes != 0) {
+ pg_tbl->entries = vzalloc(bytes);
+ if (!pg_tbl->entries) {
+ dev_dbg(device,
+ "No memory for address translation metadata\n");
+ kfree(pg_tbl);
+ *ppg_tbl = NULL;
+ return -ENOMEM;
+ }
+ }
+
+ mutex_init(&pg_tbl->mutex);
+ memcpy(&pg_tbl->config, page_table_config, sizeof(*page_table_config));
+ if (pg_tbl->config.mode == GASKET_PAGE_TABLE_MODE_NORMAL ||
+ pg_tbl->config.mode == GASKET_PAGE_TABLE_MODE_SIMPLE) {
+ pg_tbl->num_simple_entries = total_entries;
+ pg_tbl->num_extended_entries = 0;
+ pg_tbl->extended_flag = 1ull << page_table_config->extended_bit;
+ } else {
+ pg_tbl->num_simple_entries = 0;
+ pg_tbl->num_extended_entries = total_entries;
+ pg_tbl->extended_flag = 0;
+ }
+ pg_tbl->num_active_pages = 0;
+ pg_tbl->base_slot =
+ (u64 __iomem *)&bar_data->virt_base[page_table_config->base_reg];
+ pg_tbl->extended_offset_reg =
+ (u64 __iomem *)&bar_data->virt_base[page_table_config->extended_reg];
+ pg_tbl->device = get_device(device);
+ pg_tbl->pci_dev = pci_dev;
+
+ dev_dbg(device, "Page table initialized successfully\n");
+
+ return 0;
+}
+
+/*
+ * Check if a range of PTEs is free.
+ * The page table mutex must be held by the caller.
+ */
+static bool gasket_is_pte_range_free(struct gasket_page_table_entry *ptes,
+ uint num_entries)
+{
+ int i;
+
+ for (i = 0; i < num_entries; i++) {
+ if (ptes[i].status != PTE_FREE)
+ return false;
+ }
+
+ return true;
+}
+
+/*
+ * Free a second level page [sub]table.
+ * The page table mutex must be held before this call.
+ */
+static void gasket_free_extended_subtable(struct gasket_page_table *pg_tbl,
+ struct gasket_page_table_entry *pte,
+ u64 __iomem *slot)
+{
+ /* Release the page table from the driver */
+ pte->status = PTE_FREE;
+
+ /* Release the page table from the device */
+ writeq(0, slot);
+
+ if (pte->dma_addr)
+ dma_unmap_page(pg_tbl->device, pte->dma_addr, PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+
+ vfree(pte->sublevel);
+
+ if (pte->page)
+ free_page((ulong)page_address(pte->page));
+
+ memset(pte, 0, sizeof(struct gasket_page_table_entry));
+}
+
+/*
+ * Actually perform collection.
+ * The page table mutex must be held by the caller.
+ */
+static void
+gasket_page_table_garbage_collect_nolock(struct gasket_page_table *pg_tbl)
+{
+ struct gasket_page_table_entry *pte;
+ u64 __iomem *slot;
+
+ /* XXX FIX ME XXX -- more efficient to keep a usage count */
+ /* rather than scanning the second level page tables */
+
+ for (pte = pg_tbl->entries + pg_tbl->num_simple_entries,
+ slot = pg_tbl->base_slot + pg_tbl->num_simple_entries;
+ pte < pg_tbl->entries + pg_tbl->config.total_entries;
+ pte++, slot++) {
+ if (pte->status == PTE_INUSE) {
+ if (gasket_is_pte_range_free(pte->sublevel,
+ GASKET_PAGES_PER_SUBTABLE))
+ gasket_free_extended_subtable(pg_tbl, pte,
+ slot);
+ }
+ }
+}
+
+/* See gasket_page_table.h for description. */
+void gasket_page_table_garbage_collect(struct gasket_page_table *pg_tbl)
+{
+ mutex_lock(&pg_tbl->mutex);
+ gasket_page_table_garbage_collect_nolock(pg_tbl);
+ mutex_unlock(&pg_tbl->mutex);
+}
+
+/* See gasket_page_table.h for description. */
+void gasket_page_table_cleanup(struct gasket_page_table *pg_tbl)
+{
+ /* Deallocate free second-level tables. */
+ gasket_page_table_garbage_collect(pg_tbl);
+
+ /* TODO: Check that all PTEs have been freed? */
+
+ vfree(pg_tbl->entries);
+ pg_tbl->entries = NULL;
+
+ put_device(pg_tbl->device);
+ kfree(pg_tbl);
+}
+
+/* See gasket_page_table.h for description. */
+int gasket_page_table_partition(struct gasket_page_table *pg_tbl,
+ uint num_simple_entries)
+{
+ int i, start;
+
+ mutex_lock(&pg_tbl->mutex);
+ if (num_simple_entries > pg_tbl->config.total_entries) {
+ mutex_unlock(&pg_tbl->mutex);
+ return -EINVAL;
+ }
+
+ gasket_page_table_garbage_collect_nolock(pg_tbl);
+
+ start = min(pg_tbl->num_simple_entries, num_simple_entries);
+
+ for (i = start; i < pg_tbl->config.total_entries; i++) {
+ if (pg_tbl->entries[i].status != PTE_FREE) {
+ dev_err(pg_tbl->device, "entry %d is not free\n", i);
+ mutex_unlock(&pg_tbl->mutex);
+ return -EBUSY;
+ }
+ }
+
+ pg_tbl->num_simple_entries = num_simple_entries;
+ pg_tbl->num_extended_entries =
+ pg_tbl->config.total_entries - num_simple_entries;
+ writeq(num_simple_entries, pg_tbl->extended_offset_reg);
+
+ mutex_unlock(&pg_tbl->mutex);
+ return 0;
+}
+EXPORT_SYMBOL(gasket_page_table_partition);
+
+/*
+ * Return whether a host buffer was mapped as coherent memory.
+ *
+ * A Gasket page_table currently support one contiguous dma range, mapped to one
+ * contiguous virtual memory range. Check if the host_addr is within that range.
+ */
+static int is_coherent(struct gasket_page_table *pg_tbl, ulong host_addr)
+{
+ u64 min, max;
+
+ /* whether the host address is within user virt range */
+ if (!pg_tbl->coherent_pages)
+ return 0;
+
+ min = (u64)pg_tbl->coherent_pages[0].user_virt;
+ max = min + PAGE_SIZE * pg_tbl->num_coherent_pages;
+
+ return min <= host_addr && host_addr < max;
+}
+
+/*
+ * Get and map last level page table buffers.
+ *
+ * slots is the location(s) to write device-mapped page address. If this is a
+ * simple mapping, these will be address translation registers. If this is
+ * an extended mapping, these will be within a second-level page table
+ * allocated by the host and so must have their __iomem attribute casted away.
+ */
+static int gasket_perform_mapping(struct gasket_page_table *pg_tbl,
+ struct gasket_page_table_entry *ptes,
+ u64 __iomem *slots, ulong host_addr,
+ uint num_pages, int is_simple_mapping)
+{
+ int ret;
+ ulong offset;
+ struct page *page;
+ dma_addr_t dma_addr;
+ ulong page_addr;
+ int i;
+
+ for (i = 0; i < num_pages; i++) {
+ page_addr = host_addr + i * PAGE_SIZE;
+ offset = page_addr & (PAGE_SIZE - 1);
+ dev_dbg(pg_tbl->device, "%s i %d\n", __func__, i);
+ if (is_coherent(pg_tbl, host_addr)) {
+ u64 off =
+ (u64)host_addr -
+ (u64)pg_tbl->coherent_pages[0].user_virt;
+ ptes[i].page = NULL;
+ ptes[i].offset = offset;
+ ptes[i].dma_addr = pg_tbl->coherent_pages[0].paddr +
+ off + i * PAGE_SIZE;
+ } else {
+ ret = get_user_pages_fast(page_addr - offset, 1, 1,
+ &page);
+
+ if (ret <= 0) {
+ dev_err(pg_tbl->device,
+ "get user pages failed for addr=0x%lx, "
+ "offset=0x%lx [ret=%d]\n",
+ page_addr, offset, ret);
+ return ret ? ret : -ENOMEM;
+ }
+ ++pg_tbl->num_active_pages;
+
+ ptes[i].page = page;
+ ptes[i].offset = offset;
+
+ /* Map the page into DMA space. */
+ ptes[i].dma_addr =
+ dma_map_page(pg_tbl->device, page, 0, PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+ dev_dbg(pg_tbl->device,
+ "%s i %d pte %p pfn %p -> mapped %llx\n",
+ __func__, i, &ptes[i],
+ (void *)page_to_pfn(page),
+ (unsigned long long)ptes[i].dma_addr);
+
+ if (dma_mapping_error(pg_tbl->device,
+ ptes[i].dma_addr)) {
+ dev_dbg(pg_tbl->device,
+ "%s i %d -> fail to map page %llx "
+ "[pfn %p ohys %p]\n",
+ __func__, i,
+ (unsigned long long)ptes[i].dma_addr,
+ (void *)page_to_pfn(page),
+ (void *)page_to_phys(page));
+ return -1;
+ }
+ }
+
+ /* Make the DMA-space address available to the device. */
+ dma_addr = (ptes[i].dma_addr + offset) | GASKET_VALID_SLOT_FLAG;
+
+ if (is_simple_mapping) {
+ writeq(dma_addr, &slots[i]);
+ } else {
+ ((u64 __force *)slots)[i] = dma_addr;
+ /* Extended page table vectors are in DRAM,
+ * and so need to be synced each time they are updated.
+ */
+ dma_map_single(pg_tbl->device,
+ (void *)&((u64 __force *)slots)[i],
+ sizeof(u64), DMA_TO_DEVICE);
+ }
+ ptes[i].status = PTE_INUSE;
+ }
+ return 0;
+}
+
+/*
+ * Return the index of the page for the address in the simple table.
+ * Does not perform validity checking.
+ */
+static int gasket_simple_page_idx(struct gasket_page_table *pg_tbl,
+ u64 dev_addr)
+{
+ return (dev_addr >> GASKET_SIMPLE_PAGE_SHIFT) &
+ (pg_tbl->config.total_entries - 1);
+}
+
+/*
+ * Return the level 0 page index for the given address.
+ * Does not perform validity checking.
+ */
+static ulong gasket_extended_lvl0_page_idx(struct gasket_page_table *pg_tbl,
+ u64 dev_addr)
+{
+ return (dev_addr >> GASKET_EXTENDED_LVL0_SHIFT) &
+ ((1 << GASKET_EXTENDED_LVL0_WIDTH) - 1);
+}
+
+/*
+ * Return the level 1 page index for the given address.
+ * Does not perform validity checking.
+ */
+static ulong gasket_extended_lvl1_page_idx(struct gasket_page_table *pg_tbl,
+ u64 dev_addr)
+{
+ return (dev_addr >> GASKET_EXTENDED_LVL1_SHIFT) &
+ (GASKET_PAGES_PER_SUBTABLE - 1);
+}
+
+/*
+ * Allocate page table entries in a simple table.
+ * The page table mutex must be held by the caller.
+ */
+static int gasket_alloc_simple_entries(struct gasket_page_table *pg_tbl,
+ u64 dev_addr, uint num_pages)
+{
+ if (!gasket_is_pte_range_free(pg_tbl->entries +
+ gasket_simple_page_idx(pg_tbl, dev_addr),
+ num_pages))
+ return -EBUSY;
+
+ return 0;
+}
+
+/* Safely return a page to the OS. */
+static bool gasket_release_page(struct page *page)
+{
+ if (!page)
+ return false;
+
+ if (!PageReserved(page))
+ SetPageDirty(page);
+ put_page(page);
+
+ return true;
+}
+
+/*
+ * Unmap and release mapped pages.
+ * The page table mutex must be held by the caller.
+ */
+static void gasket_perform_unmapping(struct gasket_page_table *pg_tbl,
+ struct gasket_page_table_entry *ptes,
+ u64 __iomem *slots, uint num_pages,
+ int is_simple_mapping)
+{
+ int i;
+ /*
+ * For each page table entry and corresponding entry in the device's
+ * address translation table:
+ */
+ for (i = 0; i < num_pages; i++) {
+ /* release the address from the device, */
+ if (is_simple_mapping || ptes[i].status == PTE_INUSE) {
+ writeq(0, &slots[i]);
+ } else {
+ ((u64 __force *)slots)[i] = 0;
+ /* sync above PTE update before updating mappings */
+ wmb();
+ }
+
+ /* release the address from the driver, */
+ if (ptes[i].status == PTE_INUSE) {
+ if (ptes[i].dma_addr) {
+ dma_unmap_page(pg_tbl->device, ptes[i].dma_addr,
+ PAGE_SIZE, DMA_FROM_DEVICE);
+ }
+ if (gasket_release_page(ptes[i].page))
+ --pg_tbl->num_active_pages;
+ }
+ ptes[i].status = PTE_FREE;
+
+ /* and clear the PTE. */
+ memset(&ptes[i], 0, sizeof(struct gasket_page_table_entry));
+ }
+}
+
+/*
+ * Unmap and release pages mapped to simple addresses.
+ * The page table mutex must be held by the caller.
+ */
+static void gasket_unmap_simple_pages(struct gasket_page_table *pg_tbl,
+ u64 dev_addr, uint num_pages)
+{
+ uint slot = gasket_simple_page_idx(pg_tbl, dev_addr);
+
+ gasket_perform_unmapping(pg_tbl, pg_tbl->entries + slot,
+ pg_tbl->base_slot + slot, num_pages, 1);
+}
+
+/*
+ * Unmap and release buffers to extended addresses.
+ * The page table mutex must be held by the caller.
+ */
+static void gasket_unmap_extended_pages(struct gasket_page_table *pg_tbl,
+ u64 dev_addr, uint num_pages)
+{
+ uint slot_idx, remain, len;
+ struct gasket_page_table_entry *pte;
+ u64 __iomem *slot_base;
+
+ remain = num_pages;
+ slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
+ pte = pg_tbl->entries + pg_tbl->num_simple_entries +
+ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
+
+ while (remain > 0) {
+ /* TODO: Add check to ensure pte remains valid? */
+ len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx);
+
+ if (pte->status == PTE_INUSE) {
+ slot_base = (u64 __iomem *)(page_address(pte->page) +
+ pte->offset);
+ gasket_perform_unmapping(pg_tbl,
+ pte->sublevel + slot_idx,
+ slot_base + slot_idx, len, 0);
+ }
+
+ remain -= len;
+ slot_idx = 0;
+ pte++;
+ }
+}
+
+/* Evaluates to nonzero if the specified virtual address is simple. */
+static inline bool gasket_addr_is_simple(struct gasket_page_table *pg_tbl,
+ u64 addr)
+{
+ return !((addr) & (pg_tbl)->extended_flag);
+}
+
+/*
+ * Convert (simple, page, offset) into a device address.
+ * Examples:
+ * Simple page 0, offset 32:
+ * Input (0, 0, 32), Output 0x20
+ * Simple page 1000, offset 511:
+ * Input (0, 1000, 512), Output 0x3E81FF
+ * Extended page 0, offset 32:
+ * Input (0, 0, 32), Output 0x8000000020
+ * Extended page 1000, offset 511:
+ * Input (1, 1000, 512), Output 0x8003E81FF
+ */
+static ulong gasket_components_to_dev_address(struct gasket_page_table *pg_tbl,
+ int is_simple, uint page_index,
+ uint offset)
+{
+ ulong lvl0_index, lvl1_index;
+
+ if (is_simple) {
+ /* Return simple addresses directly. */
+ lvl0_index = page_index & (pg_tbl->config.total_entries - 1);
+ return (lvl0_index << GASKET_SIMPLE_PAGE_SHIFT) | offset;
+ }
+
+ /*
+ * This could be compressed into fewer statements, but
+ * A) the compiler should optimize it
+ * B) this is not slow
+ * C) this is an uncommon operation
+ * D) this is actually readable this way.
+ */
+ lvl0_index = page_index / GASKET_PAGES_PER_SUBTABLE;
+ lvl1_index = page_index & (GASKET_PAGES_PER_SUBTABLE - 1);
+ return (pg_tbl)->extended_flag |
+ (lvl0_index << GASKET_EXTENDED_LVL0_SHIFT) |
+ (lvl1_index << GASKET_EXTENDED_LVL1_SHIFT) | offset;
+}
+
+/*
+ * Validity checking for simple addresses.
+ *
+ * Verify that address translation commutes (from address to/from page + offset)
+ * and that the requested page range starts and ends within the set of
+ * currently-partitioned simple pages.
+ */
+static bool gasket_is_simple_dev_addr_bad(struct gasket_page_table *pg_tbl,
+ u64 dev_addr, uint num_pages)
+{
+ ulong page_offset = dev_addr & (PAGE_SIZE - 1);
+ ulong page_index =
+ (dev_addr / PAGE_SIZE) & (pg_tbl->config.total_entries - 1);
+
+ if (gasket_components_to_dev_address(pg_tbl, 1, page_index,
+ page_offset) != dev_addr) {
+ dev_err(pg_tbl->device, "address is invalid, 0x%llX\n",
+ dev_addr);
+ return true;
+ }
+
+ if (page_index >= pg_tbl->num_simple_entries) {
+ dev_err(pg_tbl->device,
+ "starting slot at %lu is too large, max is < %u\n",
+ page_index, pg_tbl->num_simple_entries);
+ return true;
+ }
+
+ if (page_index + num_pages > pg_tbl->num_simple_entries) {
+ dev_err(pg_tbl->device,
+ "ending slot at %lu is too large, max is <= %u\n",
+ page_index + num_pages, pg_tbl->num_simple_entries);
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Validity checking for extended addresses.
+ *
+ * Verify that address translation commutes (from address to/from page +
+ * offset) and that the requested page range starts and ends within the set of
+ * currently-partitioned extended pages.
+ */
+static bool gasket_is_extended_dev_addr_bad(struct gasket_page_table *pg_tbl,
+ u64 dev_addr, uint num_pages)
+{
+ /* Starting byte index of dev_addr into the first mapped page */
+ ulong page_offset = dev_addr & (PAGE_SIZE - 1);
+ ulong page_global_idx, page_lvl0_idx;
+ ulong num_lvl0_pages;
+ u64 addr;
+
+ /* check if the device address is out of bound */
+ addr = dev_addr & ~((pg_tbl)->extended_flag);
+ if (addr >> (GASKET_EXTENDED_LVL0_WIDTH + GASKET_EXTENDED_LVL0_SHIFT)) {
+ dev_err(pg_tbl->device, "device address out of bounds: 0x%llx\n",
+ dev_addr);
+ return true;
+ }
+
+ /* Find the starting sub-page index in the space of all sub-pages. */
+ page_global_idx = (dev_addr / PAGE_SIZE) &
+ (pg_tbl->config.total_entries * GASKET_PAGES_PER_SUBTABLE - 1);
+
+ /* Find the starting level 0 index. */
+ page_lvl0_idx = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
+
+ /* Get the count of affected level 0 pages. */
+ num_lvl0_pages = (num_pages + GASKET_PAGES_PER_SUBTABLE - 1) /
+ GASKET_PAGES_PER_SUBTABLE;
+
+ if (gasket_components_to_dev_address(pg_tbl, 0, page_global_idx,
+ page_offset) != dev_addr) {
+ dev_err(pg_tbl->device, "address is invalid: 0x%llx\n",
+ dev_addr);
+ return true;
+ }
+
+ if (page_lvl0_idx >= pg_tbl->num_extended_entries) {
+ dev_err(pg_tbl->device,
+ "starting level 0 slot at %lu is too large, max is < "
+ "%u\n", page_lvl0_idx, pg_tbl->num_extended_entries);
+ return true;
+ }
+
+ if (page_lvl0_idx + num_lvl0_pages > pg_tbl->num_extended_entries) {
+ dev_err(pg_tbl->device,
+ "ending level 0 slot at %lu is too large, max is <= %u\n",
+ page_lvl0_idx + num_lvl0_pages,
+ pg_tbl->num_extended_entries);
+ return true;
+ }
+
+ return false;
+}
+
+/*
+ * Non-locking entry to unmapping routines.
+ * The page table mutex must be held by the caller.
+ */
+static void gasket_page_table_unmap_nolock(struct gasket_page_table *pg_tbl,
+ u64 dev_addr, uint num_pages)
+{
+ if (!num_pages)
+ return;
+
+ if (gasket_addr_is_simple(pg_tbl, dev_addr))
+ gasket_unmap_simple_pages(pg_tbl, dev_addr, num_pages);
+ else
+ gasket_unmap_extended_pages(pg_tbl, dev_addr, num_pages);
+}
+
+/*
+ * Allocate and map pages to simple addresses.
+ * If there is an error, no pages are mapped.
+ */
+static int gasket_map_simple_pages(struct gasket_page_table *pg_tbl,
+ ulong host_addr, ulong dev_addr,
+ uint num_pages)
+{
+ int ret;
+ uint slot_idx = gasket_simple_page_idx(pg_tbl, dev_addr);
+
+ ret = gasket_alloc_simple_entries(pg_tbl, dev_addr, num_pages);
+ if (ret) {
+ dev_err(pg_tbl->device,
+ "page table slots %u (@ 0x%llx) to %u are not available\n",
+ slot_idx, dev_addr, slot_idx + num_pages - 1);
+ return ret;
+ }
+
+ ret = gasket_perform_mapping(pg_tbl, pg_tbl->entries + slot_idx,
+ pg_tbl->base_slot + slot_idx, host_addr,
+ num_pages, 1);
+
+ if (ret) {
+ gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
+ dev_err(pg_tbl->device, "gasket_perform_mapping %d\n", ret);
+ }
+ return ret;
+}
+
+/*
+ * Allocate a second level page table.
+ * The page table mutex must be held by the caller.
+ */
+static int gasket_alloc_extended_subtable(struct gasket_page_table *pg_tbl,
+ struct gasket_page_table_entry *pte,
+ u64 __iomem *slot)
+{
+ ulong page_addr, subtable_bytes;
+ dma_addr_t dma_addr;
+
+ /* XXX FIX ME XXX this is inefficient for non-4K page sizes */
+
+ /* GFP_DMA flag must be passed to architectures for which
+ * part of the memory range is not considered DMA'able.
+ * This seems to be the case for Juno board with 4.5.0 Linaro kernel
+ */
+ page_addr = get_zeroed_page(GFP_KERNEL | GFP_DMA);
+ if (!page_addr)
+ return -ENOMEM;
+ pte->page = virt_to_page((void *)page_addr);
+ pte->offset = 0;
+
+ subtable_bytes = sizeof(struct gasket_page_table_entry) *
+ GASKET_PAGES_PER_SUBTABLE;
+ pte->sublevel = vzalloc(subtable_bytes);
+ if (!pte->sublevel) {
+ free_page(page_addr);
+ memset(pte, 0, sizeof(struct gasket_page_table_entry));
+ return -ENOMEM;
+ }
+
+ /* Map the page into DMA space. */
+ pte->dma_addr = dma_map_page(pg_tbl->device, pte->page, 0, PAGE_SIZE,
+ DMA_BIDIRECTIONAL);
+
+ /* make the addresses available to the device */
+ dma_addr = (pte->dma_addr + pte->offset) | GASKET_VALID_SLOT_FLAG;
+ writeq(dma_addr, slot);
+
+ pte->status = PTE_INUSE;
+
+ return 0;
+}
+
+/*
+ * Allocate slots in an extended page table. Check to see if a range of page
+ * table slots are available. If necessary, memory is allocated for second level
+ * page tables.
+ *
+ * Note that memory for second level page tables is allocated as needed, but
+ * that memory is only freed on the final close of the device file, when the
+ * page tables are repartitioned, or the the device is removed. If there is an
+ * error or if the full range of slots is not available, any memory
+ * allocated for second level page tables remains allocated until final close,
+ * repartition, or device removal.
+ *
+ * The page table mutex must be held by the caller.
+ */
+static int gasket_alloc_extended_entries(struct gasket_page_table *pg_tbl,
+ u64 dev_addr, uint num_entries)
+{
+ int ret = 0;
+ uint remain, subtable_slot_idx, len;
+ struct gasket_page_table_entry *pte;
+ u64 __iomem *slot;
+
+ remain = num_entries;
+ subtable_slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
+ pte = pg_tbl->entries + pg_tbl->num_simple_entries +
+ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
+ slot = pg_tbl->base_slot + pg_tbl->num_simple_entries +
+ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
+
+ while (remain > 0) {
+ len = min(remain,
+ GASKET_PAGES_PER_SUBTABLE - subtable_slot_idx);
+
+ if (pte->status == PTE_FREE) {
+ ret = gasket_alloc_extended_subtable(pg_tbl, pte, slot);
+ if (ret) {
+ dev_err(pg_tbl->device,
+ "no memory for extended addr subtable\n");
+ return ret;
+ }
+ } else {
+ if (!gasket_is_pte_range_free(pte->sublevel +
+ subtable_slot_idx, len))
+ return -EBUSY;
+ }
+
+ remain -= len;
+ subtable_slot_idx = 0;
+ pte++;
+ slot++;
+ }
+
+ return 0;
+}
+
+/*
+ * gasket_map_extended_pages - Get and map buffers to extended addresses.
+ * If there is an error, no pages are mapped.
+ */
+static int gasket_map_extended_pages(struct gasket_page_table *pg_tbl,
+ ulong host_addr, ulong dev_addr,
+ uint num_pages)
+{
+ int ret;
+ ulong dev_addr_end;
+ uint slot_idx, remain, len;
+ struct gasket_page_table_entry *pte;
+ u64 __iomem *slot_base;
+
+ ret = gasket_alloc_extended_entries(pg_tbl, dev_addr, num_pages);
+ if (ret) {
+ dev_addr_end = dev_addr + (num_pages / PAGE_SIZE) - 1;
+ dev_err(pg_tbl->device,
+ "page table slots (%lu,%llu) (@ 0x%lx) to (%lu,%lu) are "
+ "not available\n",
+ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr),
+ dev_addr,
+ gasket_extended_lvl1_page_idx(pg_tbl, dev_addr),
+ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr_end),
+ gasket_extended_lvl1_page_idx(pg_tbl, dev_addr_end));
+ return ret;
+ }
+
+ remain = num_pages;
+ slot_idx = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
+ pte = pg_tbl->entries + pg_tbl->num_simple_entries +
+ gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
+
+ while (remain > 0) {
+ len = min(remain, GASKET_PAGES_PER_SUBTABLE - slot_idx);
+
+ slot_base =
+ (u64 __iomem *)(page_address(pte->page) + pte->offset);
+ ret = gasket_perform_mapping(pg_tbl, pte->sublevel + slot_idx,
+ slot_base + slot_idx, host_addr,
+ len, 0);
+ if (ret) {
+ gasket_page_table_unmap_nolock(pg_tbl, dev_addr,
+ num_pages);
+ return ret;
+ }
+
+ remain -= len;
+ slot_idx = 0;
+ pte++;
+ host_addr += len * PAGE_SIZE;
+ }
+
+ return 0;
+}
+
+/*
+ * See gasket_page_table.h for general description.
+ *
+ * gasket_page_table_map calls either gasket_map_simple_pages() or
+ * gasket_map_extended_pages() to actually perform the mapping.
+ *
+ * The page table mutex is held for the entire operation.
+ */
+int gasket_page_table_map(struct gasket_page_table *pg_tbl, ulong host_addr,
+ ulong dev_addr, uint num_pages)
+{
+ int ret;
+
+ if (!num_pages)
+ return 0;
+
+ mutex_lock(&pg_tbl->mutex);
+
+ if (gasket_addr_is_simple(pg_tbl, dev_addr)) {
+ ret = gasket_map_simple_pages(pg_tbl, host_addr, dev_addr,
+ num_pages);
+ } else {
+ ret = gasket_map_extended_pages(pg_tbl, host_addr, dev_addr,
+ num_pages);
+ }
+
+ mutex_unlock(&pg_tbl->mutex);
+
+ dev_dbg(pg_tbl->device,
+ "%s done: ha %llx daddr %llx num %d, ret %d\n",
+ __func__, (unsigned long long)host_addr,
+ (unsigned long long)dev_addr, num_pages, ret);
+ return ret;
+}
+EXPORT_SYMBOL(gasket_page_table_map);
+
+/*
+ * See gasket_page_table.h for general description.
+ *
+ * gasket_page_table_unmap takes the page table lock and calls either
+ * gasket_unmap_simple_pages() or gasket_unmap_extended_pages() to
+ * actually unmap the pages from device space.
+ *
+ * The page table mutex is held for the entire operation.
+ */
+void gasket_page_table_unmap(struct gasket_page_table *pg_tbl, u64 dev_addr,
+ uint num_pages)
+{
+ if (!num_pages)
+ return;
+
+ mutex_lock(&pg_tbl->mutex);
+ gasket_page_table_unmap_nolock(pg_tbl, dev_addr, num_pages);
+ mutex_unlock(&pg_tbl->mutex);
+}
+EXPORT_SYMBOL(gasket_page_table_unmap);
+
+static void gasket_page_table_unmap_all_nolock(struct gasket_page_table *pg_tbl)
+{
+ gasket_unmap_simple_pages(pg_tbl,
+ gasket_components_to_dev_address(pg_tbl, 1, 0,
+ 0),
+ pg_tbl->num_simple_entries);
+ gasket_unmap_extended_pages(pg_tbl,
+ gasket_components_to_dev_address(pg_tbl, 0,
+ 0, 0),
+ pg_tbl->num_extended_entries *
+ GASKET_PAGES_PER_SUBTABLE);
+}
+
+/* See gasket_page_table.h for description. */
+void gasket_page_table_unmap_all(struct gasket_page_table *pg_tbl)
+{
+ mutex_lock(&pg_tbl->mutex);
+ gasket_page_table_unmap_all_nolock(pg_tbl);
+ mutex_unlock(&pg_tbl->mutex);
+}
+EXPORT_SYMBOL(gasket_page_table_unmap_all);
+
+/* See gasket_page_table.h for description. */
+void gasket_page_table_reset(struct gasket_page_table *pg_tbl)
+{
+ mutex_lock(&pg_tbl->mutex);
+ gasket_page_table_unmap_all_nolock(pg_tbl);
+ writeq(pg_tbl->config.total_entries, pg_tbl->extended_offset_reg);
+ mutex_unlock(&pg_tbl->mutex);
+}
+
+/* See gasket_page_table.h for description. */
+int gasket_page_table_lookup_page(
+ struct gasket_page_table *pg_tbl, u64 dev_addr, struct page **ppage,
+ ulong *poffset)
+{
+ uint page_num;
+ struct gasket_page_table_entry *pte;
+
+ mutex_lock(&pg_tbl->mutex);
+ if (gasket_addr_is_simple(pg_tbl, dev_addr)) {
+ page_num = gasket_simple_page_idx(pg_tbl, dev_addr);
+ if (page_num >= pg_tbl->num_simple_entries)
+ goto fail;
+
+ pte = pg_tbl->entries + page_num;
+ if (pte->status != PTE_INUSE)
+ goto fail;
+ } else {
+ /* Find the level 0 entry, */
+ page_num = gasket_extended_lvl0_page_idx(pg_tbl, dev_addr);
+ if (page_num >= pg_tbl->num_extended_entries)
+ goto fail;
+
+ pte = pg_tbl->entries + pg_tbl->num_simple_entries + page_num;
+ if (pte->status != PTE_INUSE)
+ goto fail;
+
+ /* and its contained level 1 entry. */
+ page_num = gasket_extended_lvl1_page_idx(pg_tbl, dev_addr);
+ pte = pte->sublevel + page_num;
+ if (pte->status != PTE_INUSE)
+ goto fail;
+ }
+
+ *ppage = pte->page;
+ *poffset = pte->offset;
+ mutex_unlock(&pg_tbl->mutex);
+ return 0;
+
+fail:
+ *ppage = NULL;
+ *poffset = 0;
+ mutex_unlock(&pg_tbl->mutex);
+ return -1;
+}
+
+/* See gasket_page_table.h for description. */
+bool gasket_page_table_are_addrs_bad(
+ struct gasket_page_table *pg_tbl, ulong host_addr, ulong dev_addr,
+ ulong bytes)
+{
+ if (host_addr & (PAGE_SIZE - 1)) {
+ dev_err(pg_tbl->device,
+ "host mapping address 0x%lx must be page aligned\n",
+ host_addr);
+ return true;
+ }
+
+ return gasket_page_table_is_dev_addr_bad(pg_tbl, dev_addr, bytes);
+}
+EXPORT_SYMBOL(gasket_page_table_are_addrs_bad);
+
+/* See gasket_page_table.h for description. */
+bool gasket_page_table_is_dev_addr_bad(
+ struct gasket_page_table *pg_tbl, u64 dev_addr, ulong bytes)
+{
+ uint num_pages = bytes / PAGE_SIZE;
+
+ if (bytes & (PAGE_SIZE - 1)) {
+ dev_err(pg_tbl->device,
+ "mapping size 0x%lX must be page aligned\n", bytes);
+ return true;
+ }
+
+ if (num_pages == 0) {
+ dev_err(pg_tbl->device,
+ "requested mapping is less than one page: %lu / %lu\n",
+ bytes, PAGE_SIZE);
+ return true;
+ }
+
+ if (gasket_addr_is_simple(pg_tbl, dev_addr))
+ return gasket_is_simple_dev_addr_bad(pg_tbl, dev_addr,
+ num_pages);
+ return gasket_is_extended_dev_addr_bad(pg_tbl, dev_addr, num_pages);
+}
+EXPORT_SYMBOL(gasket_page_table_is_dev_addr_bad);
+
+/* See gasket_page_table.h for description. */
+uint gasket_page_table_max_size(struct gasket_page_table *page_table)
+{
+ if (!page_table)
+ return 0;
+ return page_table->config.total_entries;
+}
+EXPORT_SYMBOL(gasket_page_table_max_size);
+
+/* See gasket_page_table.h for description. */
+uint gasket_page_table_num_entries(struct gasket_page_table *pg_tbl)
+{
+ if (!pg_tbl)
+ return 0;
+ return pg_tbl->num_simple_entries + pg_tbl->num_extended_entries;
+}
+EXPORT_SYMBOL(gasket_page_table_num_entries);
+
+/* See gasket_page_table.h for description. */
+uint gasket_page_table_num_simple_entries(struct gasket_page_table *pg_tbl)
+{
+ if (!pg_tbl)
+ return 0;
+ return pg_tbl->num_simple_entries;
+}
+EXPORT_SYMBOL(gasket_page_table_num_simple_entries);
+
+/* See gasket_page_table.h for description. */
+uint gasket_page_table_num_active_pages(struct gasket_page_table *pg_tbl)
+{
+ if (!pg_tbl)
+ return 0;
+ return pg_tbl->num_active_pages;
+}
+EXPORT_SYMBOL(gasket_page_table_num_active_pages);
+
+/* See gasket_page_table.h */
+int gasket_page_table_system_status(struct gasket_page_table *page_table)
+{
+ if (!page_table)
+ return GASKET_STATUS_LAMED;
+
+ if (gasket_page_table_num_entries(page_table) == 0) {
+ dev_dbg(page_table->device, "Page table size is 0\n");
+ return GASKET_STATUS_LAMED;
+ }
+
+ return GASKET_STATUS_ALIVE;
+}
+
+/* Record the host_addr to coherent dma memory mapping. */
+int gasket_set_user_virt(
+ struct gasket_dev *gasket_dev, u64 size, dma_addr_t dma_address,
+ ulong vma)
+{
+ int j;
+ struct gasket_page_table *pg_tbl;
+
+ unsigned int num_pages = size / PAGE_SIZE;
+
+ /*
+ * TODO: for future chipset, better handling of the case where multiple
+ * page tables are supported on a given device
+ */
+ pg_tbl = gasket_dev->page_table[0];
+ if (!pg_tbl) {
+ dev_dbg(gasket_dev->dev, "%s: invalid page table index\n",
+ __func__);
+ return 0;
+ }
+ for (j = 0; j < num_pages; j++) {
+ pg_tbl->coherent_pages[j].user_virt =
+ (u64)vma + j * PAGE_SIZE;
+ }
+ return 0;
+}
+
+/* Allocate a block of coherent memory. */
+int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, u64 size,
+ dma_addr_t *dma_address, u64 index)
+{
+ dma_addr_t handle;
+ void *mem;
+ int j;
+ unsigned int num_pages = (size + PAGE_SIZE - 1) / PAGE_SIZE;
+ const struct gasket_driver_desc *driver_desc =
+ gasket_get_driver_desc(gasket_dev);
+
+ if (!gasket_dev->page_table[index])
+ return -EFAULT;
+
+ if (num_pages == 0)
+ return -EINVAL;
+
+ mem = dma_alloc_coherent(gasket_get_device(gasket_dev),
+ num_pages * PAGE_SIZE, &handle, 0);
+ if (!mem)
+ goto nomem;
+
+ gasket_dev->page_table[index]->num_coherent_pages = num_pages;
+
+ /* allocate the physical memory block */
+ gasket_dev->page_table[index]->coherent_pages =
+ kcalloc(num_pages, sizeof(struct gasket_coherent_page_entry),
+ GFP_KERNEL);
+ if (!gasket_dev->page_table[index]->coherent_pages)
+ goto nomem;
+ *dma_address = 0;
+
+ gasket_dev->coherent_buffer.length_bytes =
+ PAGE_SIZE * (num_pages);
+ gasket_dev->coherent_buffer.phys_base = handle;
+ gasket_dev->coherent_buffer.virt_base = mem;
+
+ *dma_address = driver_desc->coherent_buffer_description.base;
+ for (j = 0; j < num_pages; j++) {
+ gasket_dev->page_table[index]->coherent_pages[j].paddr =
+ handle + j * PAGE_SIZE;
+ gasket_dev->page_table[index]->coherent_pages[j].kernel_virt =
+ (dma_addr_t)mem + j * PAGE_SIZE;
+ }
+
+ if (*dma_address == 0)
+ goto nomem;
+ return 0;
+
+nomem:
+ if (mem) {
+ dma_free_coherent(gasket_get_device(gasket_dev),
+ num_pages * PAGE_SIZE, mem, handle);
+ }
+
+ kfree(gasket_dev->page_table[index]->coherent_pages);
+ gasket_dev->page_table[index]->coherent_pages = NULL;
+ gasket_dev->page_table[index]->num_coherent_pages = 0;
+ return -ENOMEM;
+}
+
+/* Free a block of coherent memory. */
+int gasket_free_coherent_memory(struct gasket_dev *gasket_dev, u64 size,
+ dma_addr_t dma_address, u64 index)
+{
+ const struct gasket_driver_desc *driver_desc;
+
+ if (!gasket_dev->page_table[index])
+ return -EFAULT;
+
+ driver_desc = gasket_get_driver_desc(gasket_dev);
+
+ if (driver_desc->coherent_buffer_description.base != dma_address)
+ return -EADDRNOTAVAIL;
+
+ if (gasket_dev->coherent_buffer.length_bytes) {
+ dma_free_coherent(gasket_get_device(gasket_dev),
+ gasket_dev->coherent_buffer.length_bytes,
+ gasket_dev->coherent_buffer.virt_base,
+ gasket_dev->coherent_buffer.phys_base);
+ gasket_dev->coherent_buffer.length_bytes = 0;
+ gasket_dev->coherent_buffer.virt_base = NULL;
+ gasket_dev->coherent_buffer.phys_base = 0;
+ }
+ return 0;
+}
+
+/* Release all coherent memory. */
+void gasket_free_coherent_memory_all(
+ struct gasket_dev *gasket_dev, u64 index)
+{
+ if (!gasket_dev->page_table[index])
+ return;
+
+ if (gasket_dev->coherent_buffer.length_bytes) {
+ dma_free_coherent(gasket_get_device(gasket_dev),
+ gasket_dev->coherent_buffer.length_bytes,
+ gasket_dev->coherent_buffer.virt_base,
+ gasket_dev->coherent_buffer.phys_base);
+ gasket_dev->coherent_buffer.length_bytes = 0;
+ gasket_dev->coherent_buffer.virt_base = NULL;
+ gasket_dev->coherent_buffer.phys_base = 0;
+ }
+}
diff --git a/drivers/staging/gasket/gasket_page_table.h b/drivers/staging/gasket/gasket_page_table.h
new file mode 100644
index 0000000..783f594
--- /dev/null
+++ b/drivers/staging/gasket/gasket_page_table.h
@@ -0,0 +1,249 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Gasket Page Table functionality. This file describes the address
+ * translation/paging functionality supported by the Gasket driver framework.
+ * As much as possible, internal details are hidden to simplify use -
+ * all calls are thread-safe (protected by an internal mutex) except where
+ * indicated otherwise.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+
+#ifndef __GASKET_PAGE_TABLE_H__
+#define __GASKET_PAGE_TABLE_H__
+
+#include <linux/pci.h>
+#include <linux/types.h>
+
+#include "gasket_constants.h"
+#include "gasket_core.h"
+
+/*
+ * Structure used for managing address translation on a device. All details are
+ * internal to the implementation.
+ */
+struct gasket_page_table;
+
+/*
+ * Allocate and init address translation data.
+ * @ppage_table: Pointer to Gasket page table pointer. Set by this call.
+ * @att_base_reg: [Mapped] pointer to the first entry in the device's address
+ * translation table.
+ * @extended_offset_reg: [Mapped] pointer to the device's register containing
+ * the starting index of the extended translation table.
+ * @extended_bit_location: The index of the bit indicating whether an address
+ * is extended.
+ * @total_entries: The total number of entries in the device's address
+ * translation table.
+ * @device: Device structure for the underlying device. Only used for logging.
+ * @pci_dev: PCI system descriptor for the underlying device.
+ * whether the driver will supply its own.
+ *
+ * Description: Allocates and initializes data to track address translation -
+ * simple and extended page table metadata. Initially, the page table is
+ * partitioned such that all addresses are "simple" (single-level lookup).
+ * gasket_partition_page_table can be called to change this paritioning.
+ *
+ * Returns 0 on success, a negative error code otherwise.
+ */
+int gasket_page_table_init(struct gasket_page_table **ppg_tbl,
+ const struct gasket_bar_data *bar_data,
+ const struct gasket_page_table_config *page_table_config,
+ struct device *device, struct pci_dev *pci_dev);
+
+/*
+ * Deallocate and cleanup page table data.
+ * @page_table: Gasket page table pointer.
+ *
+ * Description: The inverse of gasket_init; frees page_table and its contained
+ * elements.
+ *
+ * Because this call destroys the page table, it cannot be
+ * thread-safe (mutex-protected)!
+ */
+void gasket_page_table_cleanup(struct gasket_page_table *page_table);
+
+/*
+ * Sets the size of the simple page table.
+ * @page_table: Gasket page table pointer.
+ * @num_simple_entries: Desired size of the simple page table (in entries).
+ *
+ * Description: gasket_partition_page_table checks to see if the simple page
+ * size can be changed (i.e., if there are no active extended
+ * mappings in the new simple size range), and, if so,
+ * sets the new simple and extended page table sizes.
+ *
+ * Returns 0 if successful, or non-zero if the page table entries
+ * are not free.
+ */
+int gasket_page_table_partition(struct gasket_page_table *page_table,
+ uint num_simple_entries);
+
+/*
+ * Get and map [host] user space pages into device memory.
+ * @page_table: Gasket page table pointer.
+ * @host_addr: Starting host virtual memory address of the pages.
+ * @dev_addr: Starting device address of the pages.
+ * @num_pages: Number of [4kB] pages to map.
+ *
+ * Description: Maps the "num_pages" pages of host memory pointed to by
+ * host_addr to the address "dev_addr" in device memory.
+ *
+ * The caller is responsible for checking the addresses ranges.
+ *
+ * Returns 0 if successful or a non-zero error number otherwise.
+ * If there is an error, no pages are mapped.
+ */
+int gasket_page_table_map(struct gasket_page_table *page_table, ulong host_addr,
+ ulong dev_addr, uint num_pages);
+
+/*
+ * Un-map host pages from device memory.
+ * @page_table: Gasket page table pointer.
+ * @dev_addr: Starting device address of the pages to unmap.
+ * @num_pages: The number of [4kB] pages to unmap.
+ *
+ * Description: The inverse of gasket_map_pages. Unmaps pages from the device.
+ */
+void gasket_page_table_unmap(struct gasket_page_table *page_table,
+ u64 dev_addr, uint num_pages);
+
+/*
+ * Unmap ALL host pages from device memory.
+ * @page_table: Gasket page table pointer.
+ */
+void gasket_page_table_unmap_all(struct gasket_page_table *page_table);
+
+/*
+ * Unmap all host pages from device memory and reset the table to fully simple
+ * addressing.
+ * @page_table: Gasket page table pointer.
+ */
+void gasket_page_table_reset(struct gasket_page_table *page_table);
+
+/*
+ * Reclaims unused page table memory.
+ * @page_table: Gasket page table pointer.
+ *
+ * Description: Examines the page table and frees any currently-unused
+ * allocations. Called internally on gasket_cleanup().
+ */
+void gasket_page_table_garbage_collect(struct gasket_page_table *page_table);
+
+/*
+ * Retrieve the backing page for a device address.
+ * @page_table: Gasket page table pointer.
+ * @dev_addr: Gasket device address.
+ * @ppage: Pointer to a page pointer for the returned page.
+ * @poffset: Pointer to an unsigned long for the returned offset.
+ *
+ * Description: Interprets the address and looks up the corresponding page
+ * in the page table and the offset in that page. (We need an
+ * offset because the host page may be larger than the Gasket chip
+ * page it contains.)
+ *
+ * Returns 0 if successful, -1 for an error. The page pointer
+ * and offset are returned through the pointers, if successful.
+ */
+int gasket_page_table_lookup_page(struct gasket_page_table *page_table,
+ u64 dev_addr, struct page **page,
+ ulong *poffset);
+
+/*
+ * Checks validity for input addrs and size.
+ * @page_table: Gasket page table pointer.
+ * @host_addr: Host address to check.
+ * @dev_addr: Gasket device address.
+ * @bytes: Size of the range to check (in bytes).
+ *
+ * Description: This call performs a number of checks to verify that the ranges
+ * specified by both addresses and the size are valid for mapping pages into
+ * device memory.
+ *
+ * Returns true if the mapping is bad, false otherwise.
+ */
+bool gasket_page_table_are_addrs_bad(struct gasket_page_table *page_table,
+ ulong host_addr, ulong dev_addr,
+ ulong bytes);
+
+/*
+ * Checks validity for input dev addr and size.
+ * @page_table: Gasket page table pointer.
+ * @dev_addr: Gasket device address.
+ * @bytes: Size of the range to check (in bytes).
+ *
+ * Description: This call performs a number of checks to verify that the range
+ * specified by the device address and the size is valid for mapping pages into
+ * device memory.
+ *
+ * Returns true if the address is bad, false otherwise.
+ */
+bool gasket_page_table_is_dev_addr_bad(struct gasket_page_table *page_table,
+ u64 dev_addr, ulong bytes);
+
+/*
+ * Gets maximum size for the given page table.
+ * @page_table: Gasket page table pointer.
+ */
+uint gasket_page_table_max_size(struct gasket_page_table *page_table);
+
+/*
+ * Gets the total number of entries in the arg.
+ * @page_table: Gasket page table pointer.
+ */
+uint gasket_page_table_num_entries(struct gasket_page_table *page_table);
+
+/*
+ * Gets the number of simple entries.
+ * @page_table: Gasket page table pointer.
+ */
+uint gasket_page_table_num_simple_entries(struct gasket_page_table *page_table);
+
+/*
+ * Gets the number of actively pinned pages.
+ * @page_table: Gasket page table pointer.
+ */
+uint gasket_page_table_num_active_pages(struct gasket_page_table *page_table);
+
+/*
+ * Get status of page table managed by @page_table.
+ * @page_table: Gasket page table pointer.
+ */
+int gasket_page_table_system_status(struct gasket_page_table *page_table);
+
+/*
+ * Allocate a block of coherent memory.
+ * @gasket_dev: Gasket Device.
+ * @size: Size of the memory block.
+ * @dma_address: Dma address allocated by the kernel.
+ * @index: Index of the gasket_page_table within this Gasket device
+ *
+ * Description: Allocate a contiguous coherent memory block, DMA'ble
+ * by this device.
+ */
+int gasket_alloc_coherent_memory(struct gasket_dev *gasket_dev, uint64_t size,
+ dma_addr_t *dma_address, uint64_t index);
+/* Release a block of contiguous coherent memory, in use by a device. */
+int gasket_free_coherent_memory(struct gasket_dev *gasket_dev, uint64_t size,
+ dma_addr_t dma_address, uint64_t index);
+
+/* Release all coherent memory. */
+void gasket_free_coherent_memory_all(struct gasket_dev *gasket_dev,
+ uint64_t index);
+
+/*
+ * Records the host_addr to coherent dma memory mapping.
+ * @gasket_dev: Gasket Device.
+ * @size: Size of the virtual address range to map.
+ * @dma_address: Dma address within the coherent memory range.
+ * @vma: Virtual address we wish to map to coherent memory.
+ *
+ * Description: For each page in the virtual address range, record the
+ * coherent page mapping.
+ *
+ * Does not perform validity checking.
+ */
+int gasket_set_user_virt(struct gasket_dev *gasket_dev, uint64_t size,
+ dma_addr_t dma_address, ulong vma);
+
+#endif /* __GASKET_PAGE_TABLE_H__ */
diff --git a/drivers/staging/gasket/gasket_sysfs.c b/drivers/staging/gasket/gasket_sysfs.c
new file mode 100644
index 0000000..fc45f0d
--- /dev/null
+++ b/drivers/staging/gasket/gasket_sysfs.c
@@ -0,0 +1,400 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (C) 2018 Google, Inc. */
+#include "gasket_sysfs.h"
+
+#include "gasket_core.h"
+
+#include <linux/device.h>
+#include <linux/printk.h>
+
+/*
+ * Pair of kernel device and user-specified pointer. Used in lookups in sysfs
+ * "show" functions to return user data.
+ */
+
+struct gasket_sysfs_mapping {
+ /*
+ * The device bound to this mapping. If this is NULL, then this mapping
+ * is free.
+ */
+ struct device *device;
+
+ /* The Gasket descriptor for this device. */
+ struct gasket_dev *gasket_dev;
+
+ /* This device's set of sysfs attributes/nodes. */
+ struct gasket_sysfs_attribute *attributes;
+
+ /* The number of live elements in "attributes". */
+ int attribute_count;
+
+ /* Protects structure from simultaneous access. */
+ struct mutex mutex;
+
+ /* Tracks active users of this mapping. */
+ struct kref refcount;
+};
+
+/*
+ * Data needed to manage users of this sysfs utility.
+ * Currently has a fixed size; if space is a concern, this can be dynamically
+ * allocated.
+ */
+/*
+ * 'Global' (file-scoped) list of mappings between devices and gasket_data
+ * pointers. This removes the requirement to have a gasket_sysfs_data
+ * handle in all files.
+ */
+static struct gasket_sysfs_mapping dev_mappings[GASKET_SYSFS_NUM_MAPPINGS];
+
+/* Callback when a mapping's refcount goes to zero. */
+static void release_entry(struct kref *ref)
+{
+ /* All work is done after the return from kref_put. */
+}
+
+/* Look up mapping information for the given device. */
+static struct gasket_sysfs_mapping *get_mapping(struct device *device)
+{
+ int i;
+
+ for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) {
+ mutex_lock(&dev_mappings[i].mutex);
+ if (dev_mappings[i].device == device) {
+ kref_get(&dev_mappings[i].refcount);
+ mutex_unlock(&dev_mappings[i].mutex);
+ return &dev_mappings[i];
+ }
+ mutex_unlock(&dev_mappings[i].mutex);
+ }
+
+ dev_dbg(device, "%s: Mapping to device %s not found\n",
+ __func__, device->kobj.name);
+ return NULL;
+}
+
+/* Put a reference to a mapping. */
+static void put_mapping(struct gasket_sysfs_mapping *mapping)
+{
+ int i;
+ int num_files_to_remove = 0;
+ struct device_attribute *files_to_remove;
+ struct device *device;
+
+ if (!mapping) {
+ pr_debug("%s: Mapping should not be NULL\n", __func__);
+ return;
+ }
+
+ mutex_lock(&mapping->mutex);
+ if (kref_put(&mapping->refcount, release_entry)) {
+ dev_dbg(mapping->device, "Removing Gasket sysfs mapping\n");
+ /*
+ * We can't remove the sysfs nodes in the kref callback, since
+ * device_remove_file() blocks until the node is free.
+ * Readers/writers of sysfs nodes, though, will be blocked on
+ * the mapping mutex, resulting in deadlock. To fix this, the
+ * sysfs nodes are removed outside the lock.
+ */
+ device = mapping->device;
+ num_files_to_remove = mapping->attribute_count;
+ files_to_remove = kcalloc(num_files_to_remove,
+ sizeof(*files_to_remove),
+ GFP_KERNEL);
+ if (files_to_remove)
+ for (i = 0; i < num_files_to_remove; i++)
+ files_to_remove[i] =
+ mapping->attributes[i].attr;
+ else
+ num_files_to_remove = 0;
+
+ kfree(mapping->attributes);
+ mapping->attributes = NULL;
+ mapping->attribute_count = 0;
+ put_device(mapping->device);
+ mapping->device = NULL;
+ mapping->gasket_dev = NULL;
+ }
+ mutex_unlock(&mapping->mutex);
+
+ if (num_files_to_remove != 0) {
+ for (i = 0; i < num_files_to_remove; ++i)
+ device_remove_file(device, &files_to_remove[i]);
+ kfree(files_to_remove);
+ }
+}
+
+/*
+ * Put a reference to a mapping N times.
+ *
+ * In higher-level resource acquire/release function pairs, the release function
+ * will need to release a mapping 2x - once for the refcount taken in the
+ * release function itself, and once for the count taken in the acquire call.
+ */
+static void put_mapping_n(struct gasket_sysfs_mapping *mapping, int times)
+{
+ int i;
+
+ for (i = 0; i < times; i++)
+ put_mapping(mapping);
+}
+
+void gasket_sysfs_init(void)
+{
+ int i;
+
+ for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) {
+ dev_mappings[i].device = NULL;
+ mutex_init(&dev_mappings[i].mutex);
+ }
+}
+
+int gasket_sysfs_create_mapping(struct device *device,
+ struct gasket_dev *gasket_dev)
+{
+ struct gasket_sysfs_mapping *mapping;
+ int map_idx = -1;
+
+ /*
+ * We need a function-level mutex to protect against the same device
+ * being added [multiple times] simultaneously.
+ */
+ static DEFINE_MUTEX(function_mutex);
+
+ mutex_lock(&function_mutex);
+ dev_dbg(device, "Creating sysfs entries for device\n");
+
+ /* Check that the device we're adding hasn't already been added. */
+ mapping = get_mapping(device);
+ if (mapping) {
+ dev_err(device,
+ "Attempting to re-initialize sysfs mapping for device\n");
+ put_mapping(mapping);
+ mutex_unlock(&function_mutex);
+ return -EBUSY;
+ }
+
+ /* Find the first empty entry in the array. */
+ for (map_idx = 0; map_idx < GASKET_SYSFS_NUM_MAPPINGS; ++map_idx) {
+ mutex_lock(&dev_mappings[map_idx].mutex);
+ if (!dev_mappings[map_idx].device)
+ /* Break with the mutex held! */
+ break;
+ mutex_unlock(&dev_mappings[map_idx].mutex);
+ }
+
+ if (map_idx == GASKET_SYSFS_NUM_MAPPINGS) {
+ dev_err(device, "All mappings have been exhausted\n");
+ mutex_unlock(&function_mutex);
+ return -ENOMEM;
+ }
+
+ dev_dbg(device, "Creating sysfs mapping for device %s\n",
+ device->kobj.name);
+
+ mapping = &dev_mappings[map_idx];
+ mapping->attributes = kcalloc(GASKET_SYSFS_MAX_NODES,
+ sizeof(*mapping->attributes),
+ GFP_KERNEL);
+ if (!mapping->attributes) {
+ dev_dbg(device, "Unable to allocate sysfs attribute array\n");
+ mutex_unlock(&mapping->mutex);
+ mutex_unlock(&function_mutex);
+ return -ENOMEM;
+ }
+
+ kref_init(&mapping->refcount);
+ mapping->device = get_device(device);
+ mapping->gasket_dev = gasket_dev;
+ mapping->attribute_count = 0;
+ mutex_unlock(&mapping->mutex);
+ mutex_unlock(&function_mutex);
+
+ /* Don't decrement the refcount here! One open count keeps it alive! */
+ return 0;
+}
+
+int gasket_sysfs_create_entries(struct device *device,
+ const struct gasket_sysfs_attribute *attrs)
+{
+ int i;
+ int ret;
+ struct gasket_sysfs_mapping *mapping = get_mapping(device);
+
+ if (!mapping) {
+ dev_dbg(device,
+ "Creating entries for device without first "
+ "initializing mapping\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&mapping->mutex);
+ for (i = 0; strcmp(attrs[i].attr.attr.name, GASKET_ARRAY_END_MARKER);
+ i++) {
+ if (mapping->attribute_count == GASKET_SYSFS_MAX_NODES) {
+ dev_err(device,
+ "Maximum number of sysfs nodes reached for "
+ "device\n");
+ mutex_unlock(&mapping->mutex);
+ put_mapping(mapping);
+ return -ENOMEM;
+ }
+
+ ret = device_create_file(device, &attrs[i].attr);
+ if (ret) {
+ dev_dbg(device, "Unable to create device entries\n");
+ mutex_unlock(&mapping->mutex);
+ put_mapping(mapping);
+ return ret;
+ }
+
+ mapping->attributes[mapping->attribute_count] = attrs[i];
+ ++mapping->attribute_count;
+ }
+
+ mutex_unlock(&mapping->mutex);
+ put_mapping(mapping);
+ return 0;
+}
+EXPORT_SYMBOL(gasket_sysfs_create_entries);
+
+void gasket_sysfs_remove_mapping(struct device *device)
+{
+ struct gasket_sysfs_mapping *mapping = get_mapping(device);
+
+ if (!mapping) {
+ dev_err(device,
+ "Attempted to remove non-existent sysfs mapping to "
+ "device\n");
+ return;
+ }
+
+ put_mapping_n(mapping, 2);
+}
+
+struct gasket_dev *gasket_sysfs_get_device_data(struct device *device)
+{
+ struct gasket_sysfs_mapping *mapping = get_mapping(device);
+
+ if (!mapping) {
+ dev_err(device, "device not registered\n");
+ return NULL;
+ }
+
+ return mapping->gasket_dev;
+}
+EXPORT_SYMBOL(gasket_sysfs_get_device_data);
+
+void gasket_sysfs_put_device_data(struct device *device, struct gasket_dev *dev)
+{
+ struct gasket_sysfs_mapping *mapping = get_mapping(device);
+
+ if (!mapping)
+ return;
+
+ /* See comment of put_mapping_n() for why the '2' is necessary. */
+ put_mapping_n(mapping, 2);
+}
+EXPORT_SYMBOL(gasket_sysfs_put_device_data);
+
+struct gasket_sysfs_attribute *
+gasket_sysfs_get_attr(struct device *device, struct device_attribute *attr)
+{
+ int i;
+ int num_attrs;
+ struct gasket_sysfs_mapping *mapping = get_mapping(device);
+ struct gasket_sysfs_attribute *attrs = NULL;
+
+ if (!mapping)
+ return NULL;
+
+ attrs = mapping->attributes;
+ num_attrs = mapping->attribute_count;
+ for (i = 0; i < num_attrs; ++i) {
+ if (!strcmp(attrs[i].attr.attr.name, attr->attr.name))
+ return &attrs[i];
+ }
+
+ dev_err(device, "Unable to find match for device_attribute %s\n",
+ attr->attr.name);
+ return NULL;
+}
+EXPORT_SYMBOL(gasket_sysfs_get_attr);
+
+void gasket_sysfs_put_attr(struct device *device,
+ struct gasket_sysfs_attribute *attr)
+{
+ int i;
+ int num_attrs;
+ struct gasket_sysfs_mapping *mapping = get_mapping(device);
+ struct gasket_sysfs_attribute *attrs = NULL;
+
+ if (!mapping)
+ return;
+
+ attrs = mapping->attributes;
+ num_attrs = mapping->attribute_count;
+ for (i = 0; i < num_attrs; ++i) {
+ if (&attrs[i] == attr) {
+ put_mapping_n(mapping, 2);
+ return;
+ }
+ }
+
+ dev_err(device, "Unable to put unknown attribute: %s\n",
+ attr->attr.attr.name);
+}
+EXPORT_SYMBOL(gasket_sysfs_put_attr);
+
+ssize_t gasket_sysfs_register_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ ulong parsed_value = 0;
+ struct gasket_sysfs_mapping *mapping;
+ struct gasket_dev *gasket_dev;
+ struct gasket_sysfs_attribute *gasket_attr;
+
+ if (count < 3 || buf[0] != '0' || buf[1] != 'x') {
+ dev_err(device,
+ "sysfs register write format: \"0x<hex value>\"\n");
+ return -EINVAL;
+ }
+
+ if (kstrtoul(buf, 16, &parsed_value) != 0) {
+ dev_err(device,
+ "Unable to parse input as 64-bit hex value: %s\n", buf);
+ return -EINVAL;
+ }
+
+ mapping = get_mapping(device);
+ if (!mapping) {
+ dev_err(device, "Device driver may have been removed\n");
+ return 0;
+ }
+
+ gasket_dev = mapping->gasket_dev;
+ if (!gasket_dev) {
+ dev_err(device, "Device driver may have been removed\n");
+ return 0;
+ }
+
+ gasket_attr = gasket_sysfs_get_attr(device, attr);
+ if (!gasket_attr) {
+ put_mapping(mapping);
+ return count;
+ }
+
+ gasket_dev_write_64(gasket_dev, parsed_value,
+ gasket_attr->data.bar_address.bar,
+ gasket_attr->data.bar_address.offset);
+
+ if (gasket_attr->write_callback)
+ gasket_attr->write_callback(gasket_dev, gasket_attr,
+ parsed_value);
+
+ gasket_sysfs_put_attr(device, gasket_attr);
+ put_mapping(mapping);
+ return count;
+}
+EXPORT_SYMBOL(gasket_sysfs_register_store);
diff --git a/drivers/staging/gasket/gasket_sysfs.h b/drivers/staging/gasket/gasket_sysfs.h
new file mode 100644
index 0000000..e8f29a3
--- /dev/null
+++ b/drivers/staging/gasket/gasket_sysfs.h
@@ -0,0 +1,186 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Set of common sysfs utilities.
+ *
+ * Copyright (C) 2018 Google, Inc.
+ */
+
+/* The functions described here are a set of utilities to allow each file in the
+ * Gasket driver framework to manage their own set of sysfs entries, instead of
+ * centralizing all that work in one file.
+ *
+ * The goal of these utilities is to allow for sysfs entries to be easily
+ * created without causing a proliferation of sysfs "show" functions. This
+ * requires O(N) string lookups during show function execution, but as reading
+ * sysfs entries is rarely performance-critical, this is likely acceptible.
+ */
+#ifndef __GASKET_SYSFS_H__
+#define __GASKET_SYSFS_H__
+
+#include "gasket_constants.h"
+#include "gasket_core.h"
+#include <linux/device.h>
+#include <linux/stringify.h>
+#include <linux/sysfs.h>
+
+/* The maximum number of mappings/devices a driver needs to support. */
+#define GASKET_SYSFS_NUM_MAPPINGS (GASKET_FRAMEWORK_DESC_MAX * GASKET_DEV_MAX)
+
+/* The maximum number of sysfs nodes in a directory.
+ */
+#define GASKET_SYSFS_MAX_NODES 196
+
+/* End markers for sysfs struct arrays. */
+#define GASKET_ARRAY_END_TOKEN GASKET_RESERVED_ARRAY_END
+#define GASKET_ARRAY_END_MARKER __stringify(GASKET_ARRAY_END_TOKEN)
+
+/*
+ * Terminator struct for a gasket_sysfs_attr array. Must be at the end of
+ * all gasket_sysfs_attribute arrays.
+ */
+#define GASKET_END_OF_ATTR_ARRAY \
+ { \
+ .attr = __ATTR(GASKET_ARRAY_END_TOKEN, S_IRUGO, NULL, NULL), \
+ .data.attr_type = 0, \
+ }
+
+/*
+ * Pairing of sysfs attribute and user data.
+ * Used in lookups in sysfs "show" functions to return attribute metadata.
+ */
+struct gasket_sysfs_attribute {
+ /* The underlying sysfs device attribute associated with this data. */
+ struct device_attribute attr;
+
+ /* User-specified data to associate with the attribute. */
+ union {
+ struct bar_address_ {
+ ulong bar;
+ ulong offset;
+ } bar_address;
+ uint attr_type;
+ } data;
+
+ /*
+ * Function pointer to a callback to be invoked when this attribute is
+ * written (if so configured). The arguments are to the Gasket device
+ * pointer, the enclosing gasket_attr structure, and the value written.
+ * The callback should perform any logging necessary, as errors cannot
+ * be returned from the callback.
+ */
+ void (*write_callback)(struct gasket_dev *dev,
+ struct gasket_sysfs_attribute *attr,
+ ulong value);
+};
+
+#define GASKET_SYSFS_RO(_name, _show_function, _attr_type) \
+ { \
+ .attr = __ATTR(_name, S_IRUGO, _show_function, NULL), \
+ .data.attr_type = _attr_type \
+ }
+
+#define GASKET_SYSFS_RW(_name, _show_function, _store_function, _attr_type) \
+ { \
+ .attr = __ATTR(_name, S_IWUSR | S_IWGRP | S_IRUGO, \
+ _show_function, _store_function), \
+ .data.attr_type = _attr_type \
+ }
+
+/* Initializes the Gasket sysfs subsystem.
+ *
+ * Description: Performs one-time initialization. Must be called before usage
+ * at [Gasket] module load time.
+ */
+void gasket_sysfs_init(void);
+
+/*
+ * Create an entry in mapping_data between a device and a Gasket device.
+ * @device: Device struct to map to.
+ * @gasket_dev: The dev struct associated with the driver controlling @device.
+ *
+ * Description: This function maps a gasket_dev* to a device*. This mapping can
+ * be used in sysfs_show functions to get a handle to the gasket_dev struct
+ * controlling the device node.
+ *
+ * If this function is not called before gasket_sysfs_create_entries, a warning
+ * will be logged.
+ */
+int gasket_sysfs_create_mapping(struct device *device,
+ struct gasket_dev *gasket_dev);
+
+/*
+ * Creates bulk entries in sysfs.
+ * @device: Kernel device structure.
+ * @attrs: List of attributes/sysfs entries to create.
+ *
+ * Description: Creates each sysfs entry described in "attrs". Can be called
+ * multiple times for a given @device. If the gasket_dev specified in
+ * gasket_sysfs_create_mapping had a legacy device, the entries will be created
+ * for it, as well.
+ */
+int gasket_sysfs_create_entries(struct device *device,
+ const struct gasket_sysfs_attribute *attrs);
+
+/*
+ * Removes a device mapping from the global table.
+ * @device: Device to unmap.
+ *
+ * Description: Removes the device->Gasket device mapping from the internal
+ * table.
+ */
+void gasket_sysfs_remove_mapping(struct device *device);
+
+/*
+ * User data lookup based on kernel device structure.
+ * @device: Kernel device structure.
+ *
+ * Description: Returns the user data associated with "device" in a prior call
+ * to gasket_sysfs_create_entries. Returns NULL if no mapping can be found.
+ * Upon success, this call take a reference to internal sysfs data that must be
+ * released with gasket_sysfs_put_device_data. While this reference is held, the
+ * underlying device sysfs information/structure will remain valid/will not be
+ * deleted.
+ */
+struct gasket_dev *gasket_sysfs_get_device_data(struct device *device);
+
+/*
+ * Releases a references to internal data.
+ * @device: Kernel device structure.
+ * @dev: Gasket device descriptor (returned by gasket_sysfs_get_device_data).
+ */
+void gasket_sysfs_put_device_data(struct device *device,
+ struct gasket_dev *gasket_dev);
+
+/*
+ * Gasket-specific attribute lookup.
+ * @device: Kernel device structure.
+ * @attr: Device attribute to look up.
+ *
+ * Returns the Gasket sysfs attribute associated with the kernel device
+ * attribute and device structure itself. Upon success, this call will take a
+ * reference to internal sysfs data that must be released with a call to
+ * gasket_sysfs_put_attr. While this reference is held, the underlying device
+ * sysfs information/structure will remain valid/will not be deleted.
+ */
+struct gasket_sysfs_attribute *
+gasket_sysfs_get_attr(struct device *device, struct device_attribute *attr);
+
+/*
+ * Releases a references to internal data.
+ * @device: Kernel device structure.
+ * @attr: Gasket sysfs attribute descriptor (returned by
+ * gasket_sysfs_get_attr).
+ */
+void gasket_sysfs_put_attr(struct device *device,
+ struct gasket_sysfs_attribute *attr);
+
+/*
+ * Write to a register sysfs node.
+ * @buf: NULL-terminated data being written.
+ * @count: number of bytes in the "buf" argument.
+ */
+ssize_t gasket_sysfs_register_store(struct device *device,
+ struct device_attribute *attr,
+ const char *buf, size_t count);
+
+#endif /* __GASKET_SYSFS_H__ */
diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h
index fb143fd..c3370f3 100644
--- a/include/media/v4l2-subdev.h
+++ b/include/media/v4l2-subdev.h
@@ -163,6 +163,8 @@ struct v4l2_subdev_io_pin_config {
* @s_gpio: set GPIO pins. Very simple right now, might need to be extended with
* a direction argument if needed.
*
+ * @queryctrl: callback for VIDIOC_QUERYCTL ioctl handler code.
+ *
* @ioctl: called at the end of ioctl() syscall handler at the V4L2 core.
* used to provide support for private ioctls used on the driver.
*
@@ -195,6 +197,7 @@ struct v4l2_subdev_core_ops {
int (*load_fw)(struct v4l2_subdev *sd);
int (*reset)(struct v4l2_subdev *sd, u32 val);
int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
+ int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);
long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
#ifdef CONFIG_COMPAT
long (*compat_ioctl32)(struct v4l2_subdev *sd, unsigned int cmd,
diff --git a/include/sound/rt5645.h b/include/sound/rt5645.h
index d0c33a9..44113a7 100644
--- a/include/sound/rt5645.h
+++ b/include/sound/rt5645.h
@@ -21,10 +21,12 @@ struct rt5645_platform_data {
/* 0 = IN2P; 1 = GPIO6; 2 = GPIO10; 3 = GPIO12 */
unsigned int jd_mode;
- /* Use level triggered irq */
- bool level_trigger_irq;
- /* Invert JD1_1 status polarity */
- bool inv_jd1_1;
+
+ /* Invert JD when jack insert */
+ bool jd_invert;
+
+ /* Toggle HPO_L voltage low during a jack detect event */
+ bool jd_low_volt_enable;
};
#endif
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index c0abad2..46eade3 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -47,6 +47,7 @@
source "sound/soc/davinci/Kconfig"
source "sound/soc/dwc/Kconfig"
source "sound/soc/fsl/Kconfig"
+source "sound/soc/google/Kconfig"
source "sound/soc/hisilicon/Kconfig"
source "sound/soc/jz4740/Kconfig"
source "sound/soc/nuc900/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index bf8c1e2..0033dc4 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -28,6 +28,7 @@
obj-$(CONFIG_SND_SOC) += davinci/
obj-$(CONFIG_SND_SOC) += dwc/
obj-$(CONFIG_SND_SOC) += fsl/
+obj-$(CONFIG_SND_SOC) += google/
obj-$(CONFIG_SND_SOC) += hisilicon/
obj-$(CONFIG_SND_SOC) += jz4740/
obj-$(CONFIG_SND_SOC) += img/
diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index 79adf36..448eb70 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -797,7 +797,8 @@
tristate
config SND_SOC_RT5645
- tristate
+ tristate "Realtek ALC5645/ACL5650 CODEC"
+ depends on I2C
config SND_SOC_RT5651
tristate
diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c
index a98647a..1a38ee9 100644
--- a/sound/soc/codecs/rt5645.c
+++ b/sound/soc/codecs/rt5645.c
@@ -34,17 +34,6 @@
#include "rl6231.h"
#include "rt5645.h"
-#define QUIRK_INV_JD1_1(q) ((q) & 1)
-#define QUIRK_LEVEL_IRQ(q) (((q) >> 1) & 1)
-#define QUIRK_IN2_DIFF(q) (((q) >> 2) & 1)
-#define QUIRK_JD_MODE(q) (((q) >> 4) & 7)
-#define QUIRK_DMIC1_DATA_PIN(q) (((q) >> 8) & 3)
-#define QUIRK_DMIC2_DATA_PIN(q) (((q) >> 12) & 3)
-
-static unsigned int quirk = -1;
-module_param(quirk, uint, 0444);
-MODULE_PARM_DESC(quirk, "RT5645 pdata quirk override");
-
#define RT5645_DEVICE_ID 0x6308
#define RT5650_DEVICE_ID 0x6419
@@ -70,7 +59,7 @@ static const struct regmap_range_cfg rt5645_ranges[] = {
static const struct reg_sequence init_list[] = {
{RT5645_PR_BASE + 0x3d, 0x3600},
- {RT5645_PR_BASE + 0x1c, 0xfd70},
+ {RT5645_PR_BASE + 0x1c, 0xfd20},
{RT5645_PR_BASE + 0x20, 0x611f},
{RT5645_PR_BASE + 0x21, 0x4040},
{RT5645_PR_BASE + 0x23, 0x0004},
@@ -3120,7 +3109,19 @@ static int rt5645_jack_detect(struct snd_soc_codec *codec, int jack_insert)
unsigned int val;
if (jack_insert) {
- regmap_write(rt5645->regmap, RT5645_CHARGE_PUMP, 0x0e06);
+ regmap_write(rt5645->regmap, RT5645_CHARGE_PUMP, 0x0006);
+
+ /* If the DT says we need low voltage, we need to raise
+ the HP amp detection threshold for the headphone
+ charge pump to prevent misdetects because the GPIO
+ used for the interrupt is most-likely wired directly
+ off of HPO_L. Failing to do this causes the HPO_L
+ line to hover at 2V, which is enough to cause
+ mis-reads of the GPIO value. */
+ if (rt5645->pdata.jd_low_volt_enable) {
+ regmap_update_bits(rt5645->regmap, RT5645_CHARGE_PUMP,
+ 3 << 11, 3 << 11);
+ }
/* for jack type detect */
snd_soc_dapm_force_enable_pin(dapm, "LDO2");
@@ -3142,7 +3143,12 @@ static int rt5645_jack_detect(struct snd_soc_codec *codec, int jack_insert)
regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2,
RT5645_CBJ_MN_JD, RT5645_CBJ_MN_JD);
regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL1,
- RT5645_CBJ_BST1_EN, RT5645_CBJ_BST1_EN);
+ RT5645_CBJ_BST1_EN, 0);
+
+ /* Toggle reserved bit to do... something? */
+ regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2,
+ 1 << 11, 0);
+
msleep(100);
regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2,
RT5645_CBJ_MN_JD, 0);
@@ -3162,7 +3168,7 @@ static int rt5645_jack_detect(struct snd_soc_codec *codec, int jack_insert)
snd_soc_dapm_sync(dapm);
rt5645->jack_type = SND_JACK_HEADPHONE;
}
- if (rt5645->pdata.level_trigger_irq)
+ if (rt5645->pdata.jd_invert)
regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2,
RT5645_JD_1_1_MASK, RT5645_JD_1_1_NOR);
} else { /* jack out */
@@ -3183,7 +3189,7 @@ static int rt5645_jack_detect(struct snd_soc_codec *codec, int jack_insert)
snd_soc_dapm_disable_pin(dapm, "LDO2");
snd_soc_dapm_disable_pin(dapm, "Mic Det Power");
snd_soc_dapm_sync(dapm);
- if (rt5645->pdata.level_trigger_irq)
+ if (rt5645->pdata.jd_invert)
regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2,
RT5645_JD_1_1_MASK, RT5645_JD_1_1_INV);
}
@@ -3220,6 +3226,24 @@ int rt5645_set_jack_detect(struct snd_soc_codec *codec,
RT5645_GP1_PIN_IRQ, RT5645_GP1_PIN_IRQ);
regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL1,
RT5645_DIG_GATE_CTRL, RT5645_DIG_GATE_CTRL);
+ } else if (rt5645->codec_type == CODEC_TYPE_RT5645) {
+ regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL2,
+ RT5645_CBJ_DET_MODE, RT5645_CBJ_DET_MODE);
+ regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL2,
+ 0x1 << 4, 0x1 << 4);
+
+ /* Turn on combo jack input buffer power */
+ regmap_update_bits(rt5645->regmap, RT5645_GEN_CTRL3,
+ RT5645_EN_IBUF_BST1,
+ RT5645_EN_IBUF_BST1);
+
+ /* Turn on MIC input from RING2 */
+ regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL1,
+ RT5645_CBJ_MIC_SEL_L, RT5645_CBJ_MIC_SEL_L);
+
+ /* Disable IN1P Tie Ground */
+ regmap_update_bits(rt5645->regmap, RT5645_IN1_CTRL3,
+ RT5645_CBJ_TIE_G_L, 0);
}
rt5645_irq(0, rt5645);
@@ -3240,8 +3264,6 @@ static void rt5645_jack_detect_work(struct work_struct *work)
case 0: /* Not using rt5645 JD */
if (rt5645->gpiod_hp_det) {
gpio_state = gpiod_get_value(rt5645->gpiod_hp_det);
- dev_dbg(rt5645->codec->dev, "gpio_state = %d\n",
- gpio_state);
report = rt5645_jack_detect(rt5645->codec, gpio_state);
}
snd_soc_jack_report(rt5645->hp_jack,
@@ -3249,16 +3271,24 @@ static void rt5645_jack_detect_work(struct work_struct *work)
snd_soc_jack_report(rt5645->mic_jack,
report, SND_JACK_MICROPHONE);
return;
- default: /* read rt5645 jd1_1 status */
- val = snd_soc_read(rt5645->codec, RT5645_INT_IRQ_ST) & 0x1000;
+ case 1: /* 2 port */
+ val = snd_soc_read(rt5645->codec, RT5645_A_JD_CTRL1) & 0x0070;
+ break;
+ default: /* 1 port */
+ val = snd_soc_read(rt5645->codec, RT5645_A_JD_CTRL1) & 0x0020;
break;
}
- if (!val && (rt5645->jack_type == 0)) { /* jack in */
- report = rt5645_jack_detect(rt5645->codec, 1);
- } else if (!val && rt5645->jack_type != 0) {
- /* for push button and jack out */
+ switch (val) {
+ /* jack in */
+ case 0x30: /* 2 port */
+ case 0x0: /* 1 port or 2 port */
+ if (rt5645->jack_type == 0) {
+ report = rt5645_jack_detect(rt5645->codec, 1);
+ /* for push button and jack out */
+ break;
+ }
btn_type = 0;
if (snd_soc_read(rt5645->codec, RT5645_INT_IRQ_ST) & 0x4) {
/* button pressed */
@@ -3305,12 +3335,19 @@ static void rt5645_jack_detect_work(struct work_struct *work)
mod_timer(&rt5645->btn_check_timer,
msecs_to_jiffies(100));
}
- } else {
- /* jack out */
+
+ break;
+ /* jack out */
+ case 0x70: /* 2 port */
+ case 0x10: /* 2 port */
+ case 0x20: /* 1 port */
report = 0;
snd_soc_update_bits(rt5645->codec,
RT5645_INT_IRQ_ST, 0x1, 0x0);
rt5645_jack_detect(rt5645->codec, 0);
+ break;
+ default:
+ break;
}
snd_soc_jack_report(rt5645->hp_jack, report, SND_JACK_HEADPHONE);
@@ -3473,7 +3510,7 @@ static struct snd_soc_dai_driver rt5645_dai[] = {
},
};
-static const struct snd_soc_codec_driver soc_codec_dev_rt5645 = {
+static struct snd_soc_codec_driver soc_codec_dev_rt5645 = {
.probe = rt5645_probe,
.remove = rt5645_remove,
.suspend = rt5645_suspend,
@@ -3538,28 +3575,17 @@ static const struct i2c_device_id rt5645_i2c_id[] = {
};
MODULE_DEVICE_TABLE(i2c, rt5645_i2c_id);
-#ifdef CONFIG_OF
-static const struct of_device_id rt5645_of_match[] = {
- { .compatible = "realtek,rt5645", },
- { .compatible = "realtek,rt5650", },
- { }
-};
-MODULE_DEVICE_TABLE(of, rt5645_of_match);
-#endif
-
#ifdef CONFIG_ACPI
static const struct acpi_device_id rt5645_acpi_match[] = {
{ "10EC5645", 0 },
- { "10EC5648", 0 },
{ "10EC5650", 0 },
{ "10EC5640", 0 },
- { "10EC3270", 0 },
{},
};
MODULE_DEVICE_TABLE(acpi, rt5645_acpi_match);
#endif
-static const struct rt5645_platform_data general_platform_data = {
+static struct rt5645_platform_data general_platform_data = {
.dmic1_data_pin = RT5645_DMIC1_DISABLE,
.dmic2_data_pin = RT5645_DMIC_DATA_IN2P,
.jd_mode = 3,
@@ -3593,14 +3619,14 @@ static const struct dmi_system_id dmi_platform_intel_braswell[] = {
{ }
};
-static const struct rt5645_platform_data buddy_platform_data = {
+static struct rt5645_platform_data buddy_platform_data = {
.dmic1_data_pin = RT5645_DMIC_DATA_GPIO5,
.dmic2_data_pin = RT5645_DMIC_DATA_IN2P,
.jd_mode = 3,
- .level_trigger_irq = true,
+ .jd_invert = true,
};
-static const struct dmi_system_id dmi_platform_intel_broadwell[] = {
+static struct dmi_system_id dmi_platform_intel_broadwell[] = {
{
.ident = "Chrome Buddy",
.matches = {
@@ -3610,72 +3636,13 @@ static const struct dmi_system_id dmi_platform_intel_broadwell[] = {
{ }
};
-static const struct rt5645_platform_data gpd_win_platform_data = {
- .jd_mode = 3,
- .inv_jd1_1 = true,
-};
-
-static const struct dmi_system_id dmi_platform_gpd_win[] = {
- {
- /*
- * Match for the GPDwin which unfortunately uses somewhat
- * generic dmi strings, which is why we test for 4 strings.
- * Comparing against 23 other byt/cht boards, board_vendor
- * and board_name are unique to the GPDwin, where as only one
- * other board has the same board_serial and 3 others have
- * the same default product_name. Also the GPDwin is the
- * only device to have both board_ and product_name not set.
- */
- .ident = "GPD Win",
- .matches = {
- DMI_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"),
- DMI_MATCH(DMI_BOARD_NAME, "Default string"),
- DMI_MATCH(DMI_BOARD_SERIAL, "Default string"),
- DMI_MATCH(DMI_PRODUCT_NAME, "Default string"),
- },
- },
- {}
-};
-
-static struct rt5645_platform_data general_platform_data2 = {
- .dmic1_data_pin = RT5645_DMIC_DATA_IN2N,
- .dmic2_data_pin = RT5645_DMIC2_DISABLE,
- .jd_mode = 3,
- .inv_jd1_1 = true,
-};
-
-static struct dmi_system_id dmi_platform_asus_t100ha[] = {
- {
- .ident = "ASUS T100HAN",
- .matches = {
- DMI_EXACT_MATCH(DMI_SYS_VENDOR, "ASUSTeK COMPUTER INC."),
- DMI_MATCH(DMI_PRODUCT_NAME, "T100HAN"),
- },
- },
- { }
-};
-
-static struct rt5645_platform_data minix_z83_4_platform_data = {
- .jd_mode = 3,
-};
-
-static struct dmi_system_id dmi_platform_minix_z83_4[] = {
- {
- .ident = "MINIX Z83-4",
- .matches = {
- DMI_EXACT_MATCH(DMI_SYS_VENDOR, "MINIX"),
- DMI_MATCH(DMI_PRODUCT_NAME, "Z83-4"),
- },
- },
- { }
-};
-
static bool rt5645_check_dp(struct device *dev)
{
if (device_property_present(dev, "realtek,in2-differential") ||
- device_property_present(dev, "realtek,dmic1-data-pin") ||
- device_property_present(dev, "realtek,dmic2-data-pin") ||
- device_property_present(dev, "realtek,jd-mode"))
+ device_property_present(dev, "realtek,dmic1-data-pin") ||
+ device_property_present(dev, "realtek,dmic2-data-pin") ||
+ device_property_present(dev, "realtek,jd-mode") ||
+ device_property_present(dev, "realtek,jd-low-volt-enable"))
return true;
return false;
@@ -3691,6 +3658,13 @@ static int rt5645_parse_dt(struct rt5645_priv *rt5645, struct device *dev)
"realtek,dmic2-data-pin", &rt5645->pdata.dmic2_data_pin);
device_property_read_u32(dev,
"realtek,jd-mode", &rt5645->pdata.jd_mode);
+ rt5645->pdata.jd_low_volt_enable =
+ device_property_read_bool(dev, "realtek,jd-low-volt-enable");
+
+ if (rt5645->pdata.jd_low_volt_enable) {
+ printk("rt5645: Raising HP charge pump threshold to prevent jack event "
+ "mis-detects.\n");
+ }
return 0;
}
@@ -3720,34 +3694,13 @@ static int rt5645_i2c_probe(struct i2c_client *i2c,
rt5645_parse_dt(rt5645, &i2c->dev);
else if (dmi_check_system(dmi_platform_intel_braswell))
rt5645->pdata = general_platform_data;
- else if (dmi_check_system(dmi_platform_gpd_win))
- rt5645->pdata = gpd_win_platform_data;
- else if (dmi_check_system(dmi_platform_asus_t100ha))
- rt5645->pdata = general_platform_data2;
- else if (dmi_check_system(dmi_platform_minix_z83_4))
- rt5645->pdata = minix_z83_4_platform_data;
-
- if (quirk != -1) {
- rt5645->pdata.in2_diff = QUIRK_IN2_DIFF(quirk);
- rt5645->pdata.level_trigger_irq = QUIRK_LEVEL_IRQ(quirk);
- rt5645->pdata.inv_jd1_1 = QUIRK_INV_JD1_1(quirk);
- rt5645->pdata.jd_mode = QUIRK_JD_MODE(quirk);
- rt5645->pdata.dmic1_data_pin = QUIRK_DMIC1_DATA_PIN(quirk);
- rt5645->pdata.dmic2_data_pin = QUIRK_DMIC2_DATA_PIN(quirk);
- }
rt5645->gpiod_hp_det = devm_gpiod_get_optional(&i2c->dev, "hp-detect",
GPIOD_IN);
if (IS_ERR(rt5645->gpiod_hp_det)) {
- dev_info(&i2c->dev, "failed to initialize gpiod\n");
- ret = PTR_ERR(rt5645->gpiod_hp_det);
- /*
- * Continue if optional gpiod is missing, bail for all other
- * errors, including -EPROBE_DEFER
- */
- if (ret != -ENOENT)
- return ret;
+ dev_err(&i2c->dev, "failed to initialize gpiod\n");
+ return PTR_ERR(rt5645->gpiod_hp_det);
}
for (i = 0; i < ARRAY_SIZE(rt5645->supplies); i++)
@@ -3816,8 +3769,6 @@ static int rt5645_i2c_probe(struct i2c_client *i2c,
ret);
}
- regmap_update_bits(rt5645->regmap, RT5645_CLSD_OUT_CTRL, 0xc0, 0xc0);
-
if (rt5645->pdata.in2_diff)
regmap_update_bits(rt5645->regmap, RT5645_IN2_CTRL,
RT5645_IN_DF2, RT5645_IN_DF2);
@@ -3921,16 +3872,19 @@ static int rt5645_i2c_probe(struct i2c_client *i2c,
default:
break;
}
- if (rt5645->pdata.inv_jd1_1) {
- regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2,
- RT5645_JD_1_1_MASK, RT5645_JD_1_1_INV);
- }
}
regmap_update_bits(rt5645->regmap, RT5645_ADDA_CLK1,
RT5645_I2S_PD1_MASK, RT5645_I2S_PD1_2);
- if (rt5645->pdata.level_trigger_irq) {
+ /* Add wind filter */
+ regmap_update_bits(rt5645->regmap, RT5645_ADJ_HPF1,
+ RT5645_2ND_HPF_MASK, RT5645_2ND_HPF_DIS);
+ regmap_write(rt5645->regmap, RT5645_ADJ_HPF2, 0x0606);
+ regmap_update_bits(rt5645->regmap, RT5645_ADJ_HPF1,
+ RT5645_2ND_HPF_MASK, RT5645_2ND_HPF_EN);
+
+ if (rt5645->pdata.jd_invert) {
regmap_update_bits(rt5645->regmap, RT5645_IRQ_CTRL2,
RT5645_JD_1_1_MASK, RT5645_JD_1_1_INV);
}
@@ -3974,7 +3928,6 @@ static int rt5645_i2c_remove(struct i2c_client *i2c)
cancel_delayed_work_sync(&rt5645->jack_detect_work);
cancel_delayed_work_sync(&rt5645->rcclock_work);
- del_timer_sync(&rt5645->btn_check_timer);
snd_soc_unregister_codec(&i2c->dev);
regulator_bulk_disable(ARRAY_SIZE(rt5645->supplies), rt5645->supplies);
@@ -3999,7 +3952,6 @@ static void rt5645_i2c_shutdown(struct i2c_client *i2c)
static struct i2c_driver rt5645_i2c_driver = {
.driver = {
.name = "rt5645",
- .of_match_table = of_match_ptr(rt5645_of_match),
.acpi_match_table = ACPI_PTR(rt5645_acpi_match),
},
.probe = rt5645_i2c_probe,
diff --git a/sound/soc/codecs/rt5645.h b/sound/soc/codecs/rt5645.h
index cfc5f97..5620371 100644
--- a/sound/soc/codecs/rt5645.h
+++ b/sound/soc/codecs/rt5645.h
@@ -2131,6 +2131,8 @@ enum {
#define RT5645_DET_CLK_MODE2 (0x2 << 9)
#define RT5645_MICINDET_MANU (0x1 << 7)
#define RT5645_RING2_SLEEVE_GND (0x1 << 5)
+#define RT5645_EN_IBUF_BST1 (0x1 << 8)
+#define RT5645_RING2_GND (0x1 << 15)
/* Vendor ID (0xfd) */
#define RT5645_VER_C 0x2
diff --git a/sound/soc/fsl/Kconfig b/sound/soc/fsl/Kconfig
index 1dfad91..418e3e2 100644
--- a/sound/soc/fsl/Kconfig
+++ b/sound/soc/fsl/Kconfig
@@ -503,7 +503,7 @@
help
ALSA SoC Audio support with ASRC feature for Freescale SoCs that have
ESAI/SAI/SSI and connect with external CODECs such as WM8962, CS42888,
- CS4271, CS4272, and SGTL5000.
+ CS4271, CS4272, SGTL5000, and ALC5645.
Say Y if you want to add support for Freescale Generic ASoC Sound Card.
config SND_SOC_IMX_SI476X
diff --git a/sound/soc/fsl/fsl-asoc-card.c b/sound/soc/fsl/fsl-asoc-card.c
index 2db4d0c..475a7c7 100644
--- a/sound/soc/fsl/fsl-asoc-card.c
+++ b/sound/soc/fsl/fsl-asoc-card.c
@@ -19,6 +19,7 @@
#endif
#include <sound/pcm_params.h>
#include <sound/soc.h>
+#include <sound/jack.h>
#include "fsl_esai.h"
#include "fsl_sai.h"
@@ -27,6 +28,7 @@
#include "../codecs/sgtl5000.h"
#include "../codecs/wm8962.h"
#include "../codecs/wm8960.h"
+#include "../codecs/rt5645.h"
#define CS427x_SYSCLK_MCLK 0
@@ -36,6 +38,17 @@
/* Default DAI format without Master and Slave flag */
#define DAI_FMT_BASE (SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF)
+enum {
+ FSL_CODEC_UNKNOWN,
+ FSL_CODEC_CS42888,
+ FSL_CODEC_CS427X,
+ FSL_CODEC_SGTL5000,
+ FSL_CODEC_WM8962,
+ FSL_CODEC_WM8960,
+ FSL_CODEC_AC97,
+ FSL_CODEC_RT5645
+};
+
/**
* CODEC private data
*
@@ -43,12 +56,14 @@
* @mclk_id: MCLK (or main clock) id for set_sysclk()
* @fll_id: FLL (or secordary clock) id for set_sysclk()
* @pll_id: PLL id for set_pll()
+ * @codec_type: which codec we're configured for
*/
struct codec_priv {
unsigned long mclk_freq;
u32 mclk_id;
u32 fll_id;
u32 pll_id;
+ int codec_type;
};
/**
@@ -107,20 +122,20 @@ struct fsl_asoc_card_priv {
*/
static const struct snd_soc_dapm_route audio_map[] = {
/* 1st half -- Normal DAPM routes */
- {"Playback", NULL, "CPU-Playback"},
- {"CPU-Capture", NULL, "Capture"},
+ {"Playback", NULL, "CPU-Playback"},
+ {"CPU-Capture", NULL, "Capture"},
/* 2nd half -- ASRC DAPM routes */
- {"CPU-Playback", NULL, "ASRC-Playback"},
- {"ASRC-Capture", NULL, "CPU-Capture"},
+ {"CPU-Playback", NULL, "ASRC-Playback"},
+ {"ASRC-Capture", NULL, "CPU-Capture"},
};
static const struct snd_soc_dapm_route audio_map_ac97[] = {
/* 1st half -- Normal DAPM routes */
- {"Playback", NULL, "AC97 Playback"},
- {"AC97 Capture", NULL, "Capture"},
+ {"Playback", NULL, "AC97 Playback"},
+ {"AC97 Capture", NULL, "Capture"},
/* 2nd half -- ASRC DAPM routes */
- {"AC97 Playback", NULL, "ASRC-Playback"},
- {"ASRC-Capture", NULL, "AC97 Capture"},
+ {"AC97 Playback", NULL, "ASRC-Playback"},
+ {"ASRC-Capture", NULL, "AC97 Capture"},
};
/* Add all possible widgets into here without being redundant */
@@ -134,11 +149,31 @@ static const struct snd_soc_dapm_widget fsl_asoc_card_dapm_widgets[] = {
SND_SOC_DAPM_MIC("DMIC", NULL),
};
+static struct snd_soc_jack headset_jack;
+
+static struct snd_soc_jack_pin headset_jack_pin = {
+ .pin = "Headphone Jack",
+ .mask = 0xFFFFF,
+ .invert = 0
+};
+
+static const struct snd_kcontrol_new additional_kcontrols[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Ext Spk"),
+ SOC_DAPM_PIN_SWITCH("DMIC"),
+};
+
static bool fsl_asoc_card_is_ac97(struct fsl_asoc_card_priv *priv)
{
return priv->dai_fmt == SND_SOC_DAIFMT_AC97;
}
+static bool runtime_uses_rt5645_dai(struct snd_soc_pcm_runtime *rtd)
+{
+ return (rtd->codec_dai->id == RT5645_AIF1) ||
+ (rtd->codec_dai->id == RT5645_AIF2);
+}
+
static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
struct snd_pcm_hw_params *params)
{
@@ -180,6 +215,35 @@ static int fsl_asoc_card_hw_params(struct snd_pcm_substream *substream,
}
}
+ if (priv->codec_priv.codec_type == FSL_CODEC_RT5645) {
+ /* set codec PLL source to the 24.576MHz (MCLK) platform clock */
+ ret = snd_soc_dai_set_pll(rtd->codec_dai, 0, RT5645_PLL1_S_MCLK,
+ priv->codec_priv.mclk_freq,
+ priv->sample_rate * 512);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, RT5645_SCLK_S_PLL1,
+ priv->sample_rate * 512,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, RT5645_SCLK_S_PLL1,
+ priv->sample_rate * 512,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+ }
+
return 0;
}
@@ -205,7 +269,7 @@ static int be_hw_params_fixup(struct snd_soc_pcm_runtime *rtd,
}
static struct snd_soc_dai_link fsl_asoc_card_dai[] = {
- /* Default ASoC DAI Link*/
+ /* Default ASoC DAI Link */
{
.name = "HiFi",
.stream_name = "HiFi",
@@ -284,11 +348,13 @@ static int fsl_asoc_card_set_bias_level(struct snd_soc_card *card,
codec_priv->mclk_freq,
SND_SOC_CLOCK_IN);
if (ret) {
- dev_err(dev, "failed to switch away from FLL: %d\n", ret);
+ dev_err(dev, "failed to switch away from FLL: %d\n",
+ ret);
return ret;
}
- ret = snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0);
+ ret =
+ snd_soc_dai_set_pll(codec_dai, codec_priv->pll_id, 0, 0, 0);
if (ret) {
dev_err(dev, "failed to stop FLL: %d\n", ret);
return ret;
@@ -336,43 +402,37 @@ static int fsl_asoc_card_audmux_init(struct device_node *np,
switch (priv->dai_fmt & SND_SOC_DAIFMT_MASTER_MASK) {
case SND_SOC_DAIFMT_CBM_CFM:
int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
- IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
- IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
- IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
- IMX_AUDMUX_V2_PTCR_RFSDIR |
- IMX_AUDMUX_V2_PTCR_RCLKDIR |
- IMX_AUDMUX_V2_PTCR_TFSDIR |
- IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
+ IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TFSDIR | IMX_AUDMUX_V2_PTCR_TCLKDIR;
break;
case SND_SOC_DAIFMT_CBM_CFS:
int_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | ext_port) |
- IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
- IMX_AUDMUX_V2_PTCR_RCLKDIR |
- IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR | IMX_AUDMUX_V2_PTCR_TCLKDIR;
ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
- IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
- IMX_AUDMUX_V2_PTCR_RFSDIR |
- IMX_AUDMUX_V2_PTCR_TFSDIR;
+ IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR | IMX_AUDMUX_V2_PTCR_TFSDIR;
break;
case SND_SOC_DAIFMT_CBS_CFM:
int_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | ext_port) |
- IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
- IMX_AUDMUX_V2_PTCR_RFSDIR |
- IMX_AUDMUX_V2_PTCR_TFSDIR;
+ IMX_AUDMUX_V2_PTCR_TFSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR | IMX_AUDMUX_V2_PTCR_TFSDIR;
ext_ptcr = IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
- IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
- IMX_AUDMUX_V2_PTCR_RCLKDIR |
- IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR | IMX_AUDMUX_V2_PTCR_TCLKDIR;
break;
case SND_SOC_DAIFMT_CBS_CFS:
ext_ptcr = IMX_AUDMUX_V2_PTCR_RFSEL(8 | int_port) |
- IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
- IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
- IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
- IMX_AUDMUX_V2_PTCR_RFSDIR |
- IMX_AUDMUX_V2_PTCR_RCLKDIR |
- IMX_AUDMUX_V2_PTCR_TFSDIR |
- IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ IMX_AUDMUX_V2_PTCR_RCSEL(8 | int_port) |
+ IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_TCSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_RFSDIR |
+ IMX_AUDMUX_V2_PTCR_RCLKDIR |
+ IMX_AUDMUX_V2_PTCR_TFSDIR | IMX_AUDMUX_V2_PTCR_TCLKDIR;
break;
default:
if (!fsl_asoc_card_is_ac97(priv))
@@ -381,20 +441,18 @@ static int fsl_asoc_card_audmux_init(struct device_node *np,
if (fsl_asoc_card_is_ac97(priv)) {
int_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
- IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
- IMX_AUDMUX_V2_PTCR_TCLKDIR;
+ IMX_AUDMUX_V2_PTCR_TCSEL(ext_port) |
+ IMX_AUDMUX_V2_PTCR_TCLKDIR;
ext_ptcr = IMX_AUDMUX_V2_PTCR_SYN |
- IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
- IMX_AUDMUX_V2_PTCR_TFSDIR;
+ IMX_AUDMUX_V2_PTCR_TFSEL(int_port) |
+ IMX_AUDMUX_V2_PTCR_TFSDIR;
}
/* Asynchronous mode can not be set along with RCLKDIR */
if (!fsl_asoc_card_is_ac97(priv)) {
- unsigned int pdcr =
- IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port);
+ unsigned int pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(ext_port);
- ret = imx_audmux_v2_configure_port(int_port, 0,
- pdcr);
+ ret = imx_audmux_v2_configure_port(int_port, 0, pdcr);
if (ret) {
dev_err(dev, "audmux internal port setup failed\n");
return ret;
@@ -409,11 +467,9 @@ static int fsl_asoc_card_audmux_init(struct device_node *np,
}
if (!fsl_asoc_card_is_ac97(priv)) {
- unsigned int pdcr =
- IMX_AUDMUX_V2_PDCR_RXDSEL(int_port);
+ unsigned int pdcr = IMX_AUDMUX_V2_PDCR_RXDSEL(int_port);
- ret = imx_audmux_v2_configure_port(ext_port, 0,
- pdcr);
+ ret = imx_audmux_v2_configure_port(ext_port, 0, pdcr);
if (ret) {
dev_err(dev, "audmux external port setup failed\n");
return ret;
@@ -433,8 +489,8 @@ static int fsl_asoc_card_audmux_init(struct device_node *np,
static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
{
struct fsl_asoc_card_priv *priv = snd_soc_card_get_drvdata(card);
- struct snd_soc_pcm_runtime *rtd = list_first_entry(
- &card->rtd_list, struct snd_soc_pcm_runtime, list);
+ struct snd_soc_pcm_runtime *rtd =
+ list_first_entry(&card->rtd_list, struct snd_soc_pcm_runtime, list);
struct snd_soc_dai *codec_dai = rtd->codec_dai;
struct codec_priv *codec_priv = &priv->codec_priv;
struct device *dev = card->dev;
@@ -457,6 +513,34 @@ static int fsl_asoc_card_late_probe(struct snd_soc_card *card)
return 0;
}
+ if (codec_priv->codec_type == FSL_CODEC_RT5645) {
+#if IS_ENABLED(CONFIG_SND_SOC_RT5645)
+ printk("rt5645_sel_asrc_clk_src");
+ rt5645_sel_asrc_clk_src(rtd->codec,
+ RT5645_DA_STEREO_FILTER |
+ RT5645_AD_STEREO_FILTER |
+ RT5645_DA_MONO_L_FILTER |
+ RT5645_DA_MONO_R_FILTER,
+ RT5645_CLK_SEL_I2S1_ASRC);
+
+ ret = snd_soc_card_jack_new(card, "Headphone Jack",
+ SND_JACK_HEADPHONE,
+ &headset_jack,
+ &headset_jack_pin, 1);
+ if (ret) {
+ dev_err(card->dev, "Setting up headphone jack failed! %d\n", ret);
+ return ret;
+ }
+
+ ret = rt5645_set_jack_detect(rtd->codec, &headset_jack, NULL, NULL);
+
+ if (ret) {
+ dev_err(card->dev, "Setting up jack detect failed! %d\n", ret);
+ return ret;
+ }
+#endif
+ }
+
ret = snd_soc_dai_set_sysclk(codec_dai, codec_priv->mclk_id,
codec_priv->mclk_freq, SND_SOC_CLOCK_IN);
if (ret) {
@@ -475,7 +559,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
struct platform_device *cpu_pdev;
struct fsl_asoc_card_priv *priv;
struct i2c_client *codec_dev;
- const char *codec_dai_name;
+ const char *codec_dai_name = NULL;
u32 width;
int ret;
@@ -527,10 +611,13 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
/* Assign a default DAI format, and allow each card to overwrite it */
priv->dai_fmt = DAI_FMT_BASE;
+ priv->codec_priv.codec_type = FSL_CODEC_UNKNOWN;
+
/* Diversify the card configurations */
if (of_device_is_compatible(np, "fsl,imx-audio-cs42888")) {
codec_dai_name = "cs42888";
priv->card.set_bias_level = NULL;
+ priv->codec_priv.codec_type = FSL_CODEC_CS42888;
priv->cpu_priv.sysclk_freq[TX] = priv->codec_priv.mclk_freq;
priv->cpu_priv.sysclk_freq[RX] = priv->codec_priv.mclk_freq;
priv->cpu_priv.sysclk_dir[TX] = SND_SOC_CLOCK_OUT;
@@ -539,15 +626,18 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->dai_fmt |= SND_SOC_DAIFMT_CBS_CFS;
} else if (of_device_is_compatible(np, "fsl,imx-audio-cs427x")) {
codec_dai_name = "cs4271-hifi";
+ priv->codec_priv.codec_type = FSL_CODEC_CS427X;
priv->codec_priv.mclk_id = CS427x_SYSCLK_MCLK;
priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
} else if (of_device_is_compatible(np, "fsl,imx-audio-sgtl5000")) {
codec_dai_name = "sgtl5000";
+ priv->codec_priv.codec_type = FSL_CODEC_SGTL5000;
priv->codec_priv.mclk_id = SGTL5000_SYSCLK;
priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
} else if (of_device_is_compatible(np, "fsl,imx-audio-wm8962")) {
codec_dai_name = "wm8962";
priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
+ priv->codec_priv.codec_type = FSL_CODEC_WM8962;
priv->codec_priv.mclk_id = WM8962_SYSCLK_MCLK;
priv->codec_priv.fll_id = WM8962_SYSCLK_FLL;
priv->codec_priv.pll_id = WM8962_FLL;
@@ -555,13 +645,28 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
} else if (of_device_is_compatible(np, "fsl,imx-audio-wm8960")) {
codec_dai_name = "wm8960-hifi";
priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
+ priv->codec_priv.codec_type = FSL_CODEC_WM8960;
priv->codec_priv.fll_id = WM8960_SYSCLK_AUTO;
priv->codec_priv.pll_id = WM8960_SYSCLK_AUTO;
priv->dai_fmt |= SND_SOC_DAIFMT_CBM_CFM;
} else if (of_device_is_compatible(np, "fsl,imx-audio-ac97")) {
codec_dai_name = "ac97-hifi";
priv->card.set_bias_level = NULL;
+ priv->codec_priv.codec_type = FSL_CODEC_AC97;
priv->dai_fmt = SND_SOC_DAIFMT_AC97;
+ } else if (of_device_is_compatible(np, "fsl,imx-audio-rt5645")) {
+ codec_dai_name = "rt5645-aif1";
+ priv->card.set_bias_level = fsl_asoc_card_set_bias_level;
+ priv->card.controls = additional_kcontrols;
+ priv->card.num_controls = ARRAY_SIZE(additional_kcontrols);
+ priv->dai_fmt |=
+ SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS;
+ priv->codec_priv.codec_type = FSL_CODEC_RT5645;
+ priv->codec_priv.pll_id = 0;
+ priv->codec_priv.fll_id = RT5645_SCLK_S_PLL1;
+ priv->codec_priv.mclk_id = RT5645_SCLK_S_MCLK;
+ priv->codec_priv.mclk_freq = priv->codec_priv.mclk_freq;
} else {
dev_err(&pdev->dev, "unknown Device Tree compatible\n");
ret = -EINVAL;
@@ -590,9 +695,11 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->cpu_priv.sysclk_id[0] = FSL_SAI_CLK_MAST1;
}
- snprintf(priv->name, sizeof(priv->name), "%s-audio",
- fsl_asoc_card_is_ac97(priv) ? "ac97" :
- codec_dev->name);
+ if (codec_dev) {
+ snprintf(priv->name, sizeof(priv->name), "%s-audio",
+ fsl_asoc_card_is_ac97(priv) ? "ac97" :
+ codec_dev->name);
+ }
/* Initialize sound card */
priv->pdev = pdev;
@@ -600,7 +707,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->card.name = priv->name;
priv->card.dai_link = priv->dai_link;
priv->card.dapm_routes = fsl_asoc_card_is_ac97(priv) ?
- audio_map_ac97 : audio_map;
+ audio_map_ac97 : audio_map;
priv->card.late_probe = fsl_asoc_card_late_probe;
priv->card.num_dapm_routes = ARRAY_SIZE(audio_map);
priv->card.dapm_widgets = fsl_asoc_card_dapm_widgets;
@@ -623,22 +730,20 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->dai_link[0].cpu_of_node = cpu_np;
priv->dai_link[0].codec_dai_name = codec_dai_name;
- if (!fsl_asoc_card_is_ac97(priv))
+ if (!fsl_asoc_card_is_ac97(priv) && codec_np)
priv->dai_link[0].codec_of_node = codec_np;
else {
u32 idx;
ret = of_property_read_u32(cpu_np, "cell-index", &idx);
if (ret) {
- dev_err(&pdev->dev,
- "cannot get CPU index property\n");
+ dev_err(&pdev->dev, "cannot get CPU index property\n");
goto asrc_fail;
}
priv->dai_link[0].codec_name =
- devm_kasprintf(&pdev->dev, GFP_KERNEL,
- "ac97-codec.%u",
- (unsigned int)idx);
+ devm_kasprintf(&pdev->dev, GFP_KERNEL,
+ "ac97-codec.%u", (unsigned int)idx);
}
priv->dai_link[0].platform_of_node = cpu_np;
@@ -651,8 +756,7 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
priv->dai_link[1].platform_of_node = asrc_np;
priv->dai_link[2].codec_dai_name = codec_dai_name;
priv->dai_link[2].codec_of_node = codec_np;
- priv->dai_link[2].codec_name =
- priv->dai_link[0].codec_name;
+ priv->dai_link[2].codec_name = priv->dai_link[0].codec_name;
priv->dai_link[2].cpu_of_node = cpu_np;
priv->dai_link[2].dai_fmt = priv->dai_fmt;
priv->card.num_links = 3;
@@ -696,24 +800,27 @@ static int fsl_asoc_card_probe(struct platform_device *pdev)
}
static const struct of_device_id fsl_asoc_card_dt_ids[] = {
- { .compatible = "fsl,imx-audio-ac97", },
- { .compatible = "fsl,imx-audio-cs42888", },
- { .compatible = "fsl,imx-audio-cs427x", },
- { .compatible = "fsl,imx-audio-sgtl5000", },
- { .compatible = "fsl,imx-audio-wm8962", },
- { .compatible = "fsl,imx-audio-wm8960", },
+ {.compatible = "fsl,imx-audio-ac97",},
+ {.compatible = "fsl,imx-audio-cs42888",},
+ {.compatible = "fsl,imx-audio-cs427x",},
+ {.compatible = "fsl,imx-audio-sgtl5000",},
+ {.compatible = "fsl,imx-audio-wm8962",},
+ {.compatible = "fsl,imx-audio-wm8960",},
+ {.compatible = "fsl,imx-audio-rt5645",},
{}
};
+
MODULE_DEVICE_TABLE(of, fsl_asoc_card_dt_ids);
static struct platform_driver fsl_asoc_card_driver = {
.probe = fsl_asoc_card_probe,
.driver = {
- .name = "fsl-asoc-card",
- .pm = &snd_soc_pm_ops,
- .of_match_table = fsl_asoc_card_dt_ids,
+ .name = "fsl-asoc-card",
+ .pm = &snd_soc_pm_ops,
+ .of_match_table = fsl_asoc_card_dt_ids,
},
};
+
module_platform_driver(fsl_asoc_card_driver);
MODULE_DESCRIPTION("Freescale Generic ASoC Sound Card driver with ASRC");
diff --git a/sound/soc/fsl/fsl_sai.c b/sound/soc/fsl/fsl_sai.c
index 673c84c..3a6da03 100644
--- a/sound/soc/fsl/fsl_sai.c
+++ b/sound/soc/fsl/fsl_sai.c
@@ -540,13 +540,6 @@ static int fsl_sai_set_bclk(struct snd_soc_dai *dai, bool tx, u32 freq)
ret = clk_rate - ratio * freq;
- /*
- * Drop the source that can not be
- * divided into the required rate.
- */
- if (ret != 0 && clk_rate / ret < 1000)
- continue;
-
dev_dbg(dai->dev,
"ratio %d for freq %dHz based on clock %ldHz\n",
ratio, freq, clk_rate);
diff --git a/sound/soc/google/Kconfig b/sound/soc/google/Kconfig
new file mode 100644
index 0000000..b9c9851
--- /dev/null
+++ b/sound/soc/google/Kconfig
@@ -0,0 +1,9 @@
+menu "SoC Audio for Google boards"
+
+config SND_GOOGLE_EDGETPU_CARD
+ tristate "Google Edge TPU sound card"
+ depends on SND_SOC_RT5645
+ help
+ Say Y if you want to add support for Google Edge TPU audio
+
+endmenu
diff --git a/sound/soc/google/Makefile b/sound/soc/google/Makefile
new file mode 100644
index 0000000..c9f420f
--- /dev/null
+++ b/sound/soc/google/Makefile
@@ -0,0 +1,2 @@
+snd-edgetpu-card := snd-edgetpu-card.o
+obj-$(CONFIG_SND_GOOGLE_EDGETPU_CARD) += snd-edgetpu-card.o
diff --git a/sound/soc/google/snd-edgetpu-card.c b/sound/soc/google/snd-edgetpu-card.c
new file mode 100644
index 0000000..e9b8794
--- /dev/null
+++ b/sound/soc/google/snd-edgetpu-card.c
@@ -0,0 +1,258 @@
+/*
+ * ASoC Driver for the Coral Edge TPU dev board
+ *
+ * Author: June Tate-Gans <jtgans@google.com>
+ * Copyright 2018
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/i2c.h>
+
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+
+#include "../codecs/rt5645.h"
+#include "../fsl/fsl_sai.h"
+
+#define PLATFORM_CLOCK 2457600
+static unsigned long codec_clock = PLATFORM_CLOCK;
+
+static struct snd_soc_jack headset_jack;
+
+static struct snd_soc_jack_pin headset_jack_pin = {
+ .pin = "Headphone Jack",
+ .mask = 0xFFFFF,
+ .invert = 0
+};
+
+static int card_init(struct snd_soc_pcm_runtime *rtd) {
+ int ret;
+
+ rt5645_sel_asrc_clk_src(rtd->codec,
+ RT5645_DA_STEREO_FILTER |
+ RT5645_AD_STEREO_FILTER |
+ RT5645_DA_MONO_L_FILTER |
+ RT5645_DA_MONO_R_FILTER,
+ RT5645_CLK_SEL_I2S1_ASRC);
+
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, RT5645_SCLK_S_MCLK,
+ codec_clock, SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->card->dev, "can't set sysclk: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_card_jack_new(rtd->card, "Headphone Jack",
+ SND_JACK_HEADSET,
+ &headset_jack, &headset_jack_pin, 1);
+ if (ret < 0) {
+ dev_err(rtd->card->dev, "can't add headphone jack: %d\n", ret);
+ return ret;
+ }
+
+ return rt5645_set_jack_detect(rtd->codec, &headset_jack, NULL, NULL);
+}
+
+static int hw_params(
+ struct snd_pcm_substream *substream,
+ struct snd_pcm_hw_params *params) {
+ int ret = 0;
+ struct snd_soc_pcm_runtime *rtd = substream->private_data;
+ unsigned int freq = params_rate(params) * 512;
+
+ /* set codec PLL source to the 24.576MHz (MCLK) platform clock */
+ ret = snd_soc_dai_set_pll(rtd->codec_dai, 0, RT5645_PLL1_S_MCLK,
+ codec_clock, freq);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec pll: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, RT5645_SCLK_S_PLL1, freq,
+ SND_SOC_CLOCK_IN);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk in: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(rtd->codec_dai, RT5645_SCLK_S_PLL1, freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set codec sysclk out: %d\n", ret);
+ return ret;
+ }
+
+ ret = snd_soc_dai_set_sysclk(rtd->cpu_dai, FSL_SAI_CLK_MAST1, freq,
+ SND_SOC_CLOCK_OUT);
+ if (ret < 0) {
+ dev_err(rtd->dev, "can't set cpu sysclk out: %d\n", ret);
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct snd_soc_ops ops = {
+ .hw_params = hw_params,
+};
+
+static struct snd_soc_dai_link card_dai[] = {
+ {
+ .name = "rt5645",
+ .stream_name = "Coral Edge TPU HiFi",
+ .codec_dai_name = "rt5645-aif1",
+ .dai_fmt = SND_SOC_DAIFMT_I2S |
+ SND_SOC_DAIFMT_NB_NF |
+ SND_SOC_DAIFMT_CBS_CFS,
+ .ops = &ops,
+ .init = card_init
+ },
+};
+
+static const struct snd_soc_dapm_widget card_widgets[] = {
+ SND_SOC_DAPM_HP("Headphone Jack", NULL),
+ SND_SOC_DAPM_SPK("Speaker", NULL),
+ SND_SOC_DAPM_MIC("Internal Mic", NULL),
+ SND_SOC_DAPM_MIC("Headphone Mic", NULL),
+};
+
+static const struct snd_soc_dapm_route audio_routes[] = {
+ {"AIF1 Capture", NULL, "CPU-Capture"},
+ {"micbias1", NULL, "Headphone Mic"},
+ {"IN1P", NULL, "micbias1"},
+ {"Speaker", NULL, "SPOL"},
+ {"Speaker", NULL, "SPOR"},
+ {"DMIC L1", NULL, "Internal Mic"},
+ {"DMIC R1", NULL, "Internal Mic"},
+ {"Headphone Jack", NULL, "HPOR"},
+ {"Headphone Jack", NULL, "HPOL"},
+};
+
+static const struct snd_kcontrol_new card_controls[] = {
+ SOC_DAPM_PIN_SWITCH("Headphone Jack"),
+ SOC_DAPM_PIN_SWITCH("Speaker"),
+ SOC_DAPM_PIN_SWITCH("Internal Mic"),
+ SOC_DAPM_PIN_SWITCH("Headphone Mic"),
+};
+
+static struct snd_soc_card snd_edgetpu_card = {
+ .name = "snd-edgetpu-card",
+ .owner = THIS_MODULE,
+ .dai_link = card_dai,
+ .num_links = ARRAY_SIZE(card_dai),
+ .dapm_routes = audio_routes,
+ .num_dapm_routes = ARRAY_SIZE(audio_routes),
+ .dapm_widgets = card_widgets,
+ .num_dapm_widgets = ARRAY_SIZE(card_widgets),
+ .controls = card_controls,
+ .num_controls = ARRAY_SIZE(card_controls),
+ .fully_routed = true,
+};
+
+static int probe(struct platform_device *pdev) {
+ int ret = 0;
+ struct snd_soc_dai_link *dai = &card_dai[0];
+ struct snd_soc_card *card = &snd_edgetpu_card;
+ struct device *dev = &pdev->dev;
+ struct i2c_client *codec_dev;
+ struct device_node *i2s_node;
+ struct clk *codec_clk;
+
+ if (!dev) {
+ printk(KERN_ERR "edgetpu-audio-card: no device for this platform_device?!\n");
+ return -EINVAL;
+ }
+
+ card->dev = dev;
+
+ if (!dev->of_node) {
+ dev_err(dev, "this device requires a devicetree node!\n");
+ return -EINVAL;
+ }
+
+ dai->codec_name = NULL;
+ dai->codec_of_node = of_parse_phandle(dev->of_node, "audio-codec", 0);
+
+ if (!dai->codec_of_node) {
+ dev_err(dev, "can't parse codec node\n");
+ return -EINVAL;
+ }
+
+ codec_dev = of_find_i2c_device_by_node(dai->codec_of_node);
+
+ if (!codec_dev) {
+ dev_err(dev, "can't find codec device!\n");
+ return -EINVAL;
+ }
+
+ codec_clk = clk_get(&codec_dev->dev, NULL);
+ if (IS_ERR(codec_clk)) {
+ dev_warn(dev, "can't find clock -- using defaults!\n");
+ } else {
+ codec_clock = clk_get_rate(codec_clk);
+ clk_put(codec_clk);
+ dev_info(dev, "clock set to %ld\n", codec_clock);
+ }
+
+ i2s_node = of_parse_phandle(dev->of_node, "audio-cpu", 0);
+
+ if (!i2s_node) {
+ dev_err(dev, "can't parse cpu node\n");
+ return -EINVAL;
+ }
+
+ dai->cpu_dai_name = NULL;
+ dai->cpu_of_node = i2s_node;
+ dai->platform_name = NULL;
+ dai->platform_of_node = i2s_node;
+
+ ret = snd_soc_of_parse_card_name(card, "google,model");
+ if (ret < 0) {
+ dev_err(dev, "can't parse card name: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_snd_soc_register_card(dev, card);
+ if (ret < 0) {
+ dev_err(dev, "can't register card: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_match[] = {
+ {
+ .compatible = "google,edgetpu-audio-card",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_match);
+
+static struct platform_driver card_driver = {
+ .driver = {
+ .name = "edgetpu-audio-card",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match,
+ },
+ .probe = probe,
+};
+module_platform_driver(card_driver);
+
+MODULE_AUTHOR("June Tate-Gans <jtgans@google.com>");
+MODULE_DESCRIPTION("ASoC Driver for Coral Edge TPU");
+MODULE_LICENSE("GPL v2");