diff --git a/sys-kernel/pinephone-pro-sources/files/.gitlab-ci.yml b/sys-kernel/pinephone-pro-sources/files/.gitlab-ci.yml index d245ca9..0a10378 100644 --- a/sys-kernel/pinephone-pro-sources/files/.gitlab-ci.yml +++ b/sys-kernel/pinephone-pro-sources/files/.gitlab-ci.yml @@ -1,38 +1,14 @@ -### Generic CI Template for chrootbuild -# -# -# Usage: chrootbuild [options] -# -# -b Branch to use: -# (unstable/testing/stable-staging/stable; -# arm-unstable/arm-testing/arm-stable) -# default: unstable / arm-unstable -# -c Start with clean chroot fs -# -h This help -# -i Install package(s) to chroot fs -# (for multiple packages repeat -i flag) -# -l List(s) to build -# (for multiple lists repeat -l flag) -# -n Install built pkg to chroot fs -# -p Package(s) to build -# (for multiple packages repeat -p flag) -# -r Remove previously built packages in $PKGDEST -# -s Sign package(s) - build-package: tags: - # change the tag to your gitlab-runner - bigbuilds - aarch64 - fosshost - xlarge script: - # build pkg via chrootbuild - export PKG=${PWD##*/} - cd .. - sudo chrootbuild -b arm-unstable -cp $PKG - mv -v ./*.pkg.tar.* $PKG - #- sudo pacman -Syy && makepkg -scr --noconfirm artifacts: paths: - ./*.pkg.tar.* diff --git a/sys-kernel/pinephone-pro-sources/files/0001-base-property-Swap-order-of-search-for-connection-to.patch b/sys-kernel/pinephone-pro-sources/files/0001-base-property-Swap-order-of-search-for-connection-to.patch new file mode 100644 index 0000000..8a3b62c --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0001-base-property-Swap-order-of-search-for-connection-to.patch @@ -0,0 +1,35 @@ +From: Ondrej Jirman +Date: Mon, 15 Nov 2021 04:09:50 +0100 +Subject: [PATCH 01/36] base: property: Swap order of search for connection to + "devcon, graph" + +This avoids confusing error in kernel log, when using devcon matching +in DT. Example: + +"OF: graph: no port node found in /i2c@ff3d0000/typec-portc@22" + +This suggest there's some error because graph based search failed, +but there is no error, because devcon based matching succeeds. + +Signed-off-by: Ondrej Jirman +--- + drivers/base/property.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/base/property.c b/drivers/base/property.c +index f1f35b4..68cb945 100644 +--- a/drivers/base/property.c ++++ b/drivers/base/property.c +@@ -1261,10 +1261,10 @@ void *fwnode_connection_find_match(struct fwnode_handle *fwnode, + if (!fwnode || !match) + return NULL; + +- ret = fwnode_graph_devcon_match(fwnode, con_id, data, match); ++ ret = fwnode_devcon_match(fwnode, con_id, data, match); + if (ret) + return ret; + +- return fwnode_devcon_match(fwnode, con_id, data, match); ++ return fwnode_graph_devcon_match(fwnode, con_id, data, match); + } + EXPORT_SYMBOL_GPL(fwnode_connection_find_match); diff --git a/sys-kernel/pinephone-pro-sources/files/0002-clk-rk3399-Export-SCLK_CIF_OUT_SRC-to-device-tree.patch b/sys-kernel/pinephone-pro-sources/files/0002-clk-rk3399-Export-SCLK_CIF_OUT_SRC-to-device-tree.patch new file mode 100644 index 0000000..fd8585a --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0002-clk-rk3399-Export-SCLK_CIF_OUT_SRC-to-device-tree.patch @@ -0,0 +1,37 @@ +From: Ondrej Jirman +Date: Fri, 22 Oct 2021 18:09:09 +0200 +Subject: [PATCH 19/36] clk: rk3399: Export SCLK_CIF_OUT_SRC to device tree + +So that it can be used in assigned-clock-parents. + +Signed-off-by: Ondrej Jirman +--- + drivers/clk/rockchip/clk-rk3399.c | 2 +- + include/dt-bindings/clock/rk3399-cru.h | 1 + + 2 files changed, 2 insertions(+), 1 deletion(-) + +diff --git a/drivers/clk/rockchip/clk-rk3399.c b/drivers/clk/rockchip/clk-rk3399.c +index 306910a..897581e 100644 +--- a/drivers/clk/rockchip/clk-rk3399.c ++++ b/drivers/clk/rockchip/clk-rk3399.c +@@ -1259,7 +1259,7 @@ static struct rockchip_clk_branch rk3399_clk_branches[] __initdata = { + RK3399_CLKGATE_CON(27), 6, GFLAGS), + + /* cif */ +- COMPOSITE_NODIV(0, "clk_cifout_src", mux_pll_src_cpll_gpll_npll_p, 0, ++ COMPOSITE_NODIV(SCLK_CIF_OUT_SRC, "clk_cifout_src", mux_pll_src_cpll_gpll_npll_p, 0, + RK3399_CLKSEL_CON(56), 6, 2, MFLAGS, + RK3399_CLKGATE_CON(10), 7, GFLAGS), + +diff --git a/include/dt-bindings/clock/rk3399-cru.h b/include/dt-bindings/clock/rk3399-cru.h +index 44e0a31..e83b3fb 100644 +--- a/include/dt-bindings/clock/rk3399-cru.h ++++ b/include/dt-bindings/clock/rk3399-cru.h +@@ -125,6 +125,7 @@ + #define SCLK_DDRC 168 + #define SCLK_TESTCLKOUT1 169 + #define SCLK_TESTCLKOUT2 170 ++#define SCLK_CIF_OUT_SRC 171 + + #define DCLK_VOP0 180 + #define DCLK_VOP1 181 diff --git a/sys-kernel/pinephone-pro-sources/files/0003-media-rockchip-rga-Fix-probe-bugs.patch b/sys-kernel/pinephone-pro-sources/files/0003-media-rockchip-rga-Fix-probe-bugs.patch new file mode 100644 index 0000000..a0e574f --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0003-media-rockchip-rga-Fix-probe-bugs.patch @@ -0,0 +1,39 @@ +From: Ondrej Jirman +Date: Sun, 21 Nov 2021 17:00:09 +0100 +Subject: [PATCH 24/36] media: rockchip: rga: Fix probe bugs + +The check for dst_mmu_pages allocation failure was inverted. + +rga_parse_dt is missing a error return, so if some of the resources +return DEFER_PROBE, probe will succeed without these resources. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/platform/rockchip/rga/rga.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c +index 4de5e8d..82cc0c0 100644 +--- a/drivers/media/platform/rockchip/rga/rga.c ++++ b/drivers/media/platform/rockchip/rga/rga.c +@@ -815,8 +815,10 @@ static int rga_probe(struct platform_device *pdev) + mutex_init(&rga->mutex); + + ret = rga_parse_dt(rga); +- if (ret) ++ if (ret) { + dev_err(&pdev->dev, "Unable to parse OF data\n"); ++ return ret; ++ } + + pm_runtime_enable(rga->dev); + +@@ -892,7 +894,7 @@ static int rga_probe(struct platform_device *pdev) + } + rga->dst_mmu_pages = + (unsigned int *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 3); +- if (rga->dst_mmu_pages) { ++ if (!rga->dst_mmu_pages) { + ret = -ENOMEM; + goto free_src_pages; + } diff --git a/sys-kernel/pinephone-pro-sources/files/0004-drm-dw-mipi-dsi-rockchip-Ensure-that-lane-is-properl.patch b/sys-kernel/pinephone-pro-sources/files/0004-drm-dw-mipi-dsi-rockchip-Ensure-that-lane-is-properl.patch new file mode 100644 index 0000000..686dc3e --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0004-drm-dw-mipi-dsi-rockchip-Ensure-that-lane-is-properl.patch @@ -0,0 +1,34 @@ +From: =?utf-8?q?Kamil_Trzci=C5=84ski?= +Date: Fri, 8 Jan 2021 00:19:23 +0100 +Subject: [PATCH 02/36] drm: dw-mipi-dsi-rockchip: Ensure that lane is + properly configured +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +??? + +Signed-of-by: Kamil Trzciński +--- + drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c +index a9acbcc..53c8b40 100644 +--- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c ++++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c +@@ -406,6 +406,14 @@ static int dw_mipi_dsi_phy_init(void *priv_data) + */ + vco = (dsi->lane_mbps < 200) ? 0 : (dsi->lane_mbps + 100) / 200; + ++ if (dsi->cdata->lanecfg1_grf_reg) { ++ regmap_write(dsi->grf_regmap, dsi->cdata->lanecfg1_grf_reg, ++ dsi->cdata->lanecfg1); ++ ++ dev_info(dsi->dev, "dw_mipi_dsi_phy_init / dw_mipi_dsi_rockchip_config: %08x => set=%08x\n", ++ dsi->cdata->lanecfg1_grf_reg, dsi->cdata->lanecfg1); ++ } ++ + i = max_mbps_to_parameter(dsi->lane_mbps); + if (i < 0) { + DRM_DEV_ERROR(dsi->dev, diff --git a/sys-kernel/pinephone-pro-sources/files/0005-drm-rockchip-dw-mipi-dsi-Fix-missing-clk_disable_unp.patch b/sys-kernel/pinephone-pro-sources/files/0005-drm-rockchip-dw-mipi-dsi-Fix-missing-clk_disable_unp.patch new file mode 100644 index 0000000..425c986 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0005-drm-rockchip-dw-mipi-dsi-Fix-missing-clk_disable_unp.patch @@ -0,0 +1,48 @@ +From: Ondrej Jirman +Date: Sun, 17 Oct 2021 18:04:21 +0200 +Subject: [PATCH 03/36] drm: rockchip: dw-mipi-dsi: Fix missing + clk_disable_unprepare for pllref_clk + +In some error paths, clk_disable_unprepare function was not called. + +Signed-off-by: Ondrej Jirman +--- + drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c | 10 +++++++--- + 1 file changed, 7 insertions(+), 3 deletions(-) + +diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c +index 53c8b40..095d0f1 100644 +--- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c ++++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c +@@ -960,7 +960,7 @@ static int dw_mipi_dsi_rockchip_bind(struct device *dev, + ret = clk_prepare_enable(dsi->grf_clk); + if (ret) { + DRM_DEV_ERROR(dsi->dev, "Failed to enable grf_clk: %d\n", ret); +- return ret; ++ goto err_pllref_disable; + } + + dw_mipi_dsi_rockchip_config(dsi); +@@ -972,16 +972,20 @@ static int dw_mipi_dsi_rockchip_bind(struct device *dev, + ret = rockchip_dsi_drm_create_encoder(dsi, drm_dev); + if (ret) { + DRM_DEV_ERROR(dev, "Failed to create drm encoder\n"); +- return ret; ++ goto err_pllref_disable; + } + + ret = dw_mipi_dsi_bind(dsi->dmd, &dsi->encoder); + if (ret) { + DRM_DEV_ERROR(dev, "Failed to bind: %d\n", ret); +- return ret; ++ goto err_pllref_disable; + } + + return 0; ++ ++err_pllref_disable: ++ clk_disable_unprepare(dsi->pllref_clk); ++ return ret; + } + + static void dw_mipi_dsi_rockchip_unbind(struct device *dev, diff --git a/sys-kernel/pinephone-pro-sources/files/0006-drm-bridge-dw-mipi-dsi-Fix-enable-disable-of-dsi-con.patch b/sys-kernel/pinephone-pro-sources/files/0006-drm-bridge-dw-mipi-dsi-Fix-enable-disable-of-dsi-con.patch new file mode 100644 index 0000000..e2eddd2 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0006-drm-bridge-dw-mipi-dsi-Fix-enable-disable-of-dsi-con.patch @@ -0,0 +1,145 @@ +From: Ondrej Jirman +Date: Sun, 17 Oct 2021 20:14:25 +0200 +Subject: [PATCH 04/36] drm: bridge: dw-mipi-dsi: Fix enable/disable of dsi + controller + +The driver had it all wrong. mode_set is not for enabling the +DSI controller, that should be done in pre_enable so that +panel driver can initialize the panel (working dsi controller +is needed for that). Having dsi powerup in mode_set led to +all kind of fun, because disable would be called more often +than mode_set. + +The whole panel/dsi enable/disable dance is such (for future +reference): + +- dsi: mode set +- panel: prepare (we turn on panel power) +- dsi: pre enable (we enable and setup DSI) +- dsi: enable (we enable video data) +- panel: enable (we configure and turn on the display) + +For disable: + +- panel: disable (we turn off the display nicely) +- dsi: disable (we disable DSI) +- dsi: post disable +- panel: unprepare (we power off display power, panel should + be safely sleeping now) + +Signed-off-by: Ondrej Jirman +--- + drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c | 45 +++++++++++++++++++-------- + 1 file changed, 32 insertions(+), 13 deletions(-) + +diff --git a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +index e44e18a..9cce2ab 100644 +--- a/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c ++++ b/drivers/gpu/drm/bridge/synopsys/dw-mipi-dsi.c +@@ -266,6 +266,7 @@ struct dw_mipi_dsi { + struct dw_mipi_dsi *master; /* dual-dsi master ptr */ + struct dw_mipi_dsi *slave; /* dual-dsi slave ptr */ + ++ struct drm_display_mode mode; + const struct dw_mipi_dsi_plat_data *plat_data; + }; + +@@ -597,6 +598,8 @@ static void dw_mipi_dsi_set_mode(struct dw_mipi_dsi *dsi, + { + u32 val; + ++ dev_info(dsi->dev, "mode %d\n", (int)mode_flags); ++ + dsi_write(dsi, DSI_PWR_UP, RESET); + + if (mode_flags & MIPI_DSI_MODE_VIDEO) { +@@ -871,11 +874,20 @@ static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi) + dsi_write(dsi, DSI_INT_MSK1, 0); + } + ++static void dw_mipi_dsi_bridge_disable(struct drm_bridge *bridge) ++{ ++ struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); ++ ++ dev_info(dsi->dev, "disable\n"); ++} ++ + static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge) + { + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; + ++ dev_info(dsi->dev, "post disable\n"); ++ + /* + * Switch to command mode before panel-bridge post_disable & + * panel unprepare. +@@ -884,15 +896,6 @@ static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge) + */ + dw_mipi_dsi_set_mode(dsi, 0); + +- /* +- * TODO Only way found to call panel-bridge post_disable & +- * panel unprepare before the dsi "final" disable... +- * This needs to be fixed in the drm_bridge framework and the API +- * needs to be updated to manage our own call chains... +- */ +- if (dsi->panel_bridge->funcs->post_disable) +- dsi->panel_bridge->funcs->post_disable(dsi->panel_bridge); +- + if (phy_ops->power_off) + phy_ops->power_off(dsi->plat_data->priv_data); + +@@ -921,7 +924,7 @@ static unsigned int dw_mipi_dsi_get_lanes(struct dw_mipi_dsi *dsi) + return dsi->lanes; + } + +-static void dw_mipi_dsi_mode_set(struct dw_mipi_dsi *dsi, ++static void dw_mipi_dsi_enable(struct dw_mipi_dsi *dsi, + const struct drm_display_mode *adjusted_mode) + { + const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops; +@@ -973,16 +976,30 @@ static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge, + { + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + +- dw_mipi_dsi_mode_set(dsi, adjusted_mode); ++ dev_info(dsi->dev, "mode set\n"); ++ ++ /* Store the display mode for plugin/DKMS poweron events */ ++ memcpy(&dsi->mode, mode, sizeof(dsi->mode)); ++} ++ ++static void dw_mipi_dsi_bridge_pre_enable(struct drm_bridge *bridge) ++{ ++ struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); ++ ++ dev_info(dsi->dev, "pre enable\n"); ++ ++ /* power up the dsi ctl into a command mode */ ++ dw_mipi_dsi_enable(dsi, &dsi->mode); + if (dsi->slave) +- dw_mipi_dsi_mode_set(dsi->slave, adjusted_mode); ++ dw_mipi_dsi_enable(dsi->slave, &dsi->mode); + } + + static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge) + { + struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge); + +- /* Switch to video mode for panel-bridge enable & panel enable */ ++ dev_info(dsi->dev, "enable\n"); ++ + dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO); + if (dsi->slave) + dw_mipi_dsi_set_mode(dsi->slave, MIPI_DSI_MODE_VIDEO); +@@ -1033,7 +1050,9 @@ static int dw_mipi_dsi_bridge_attach(struct drm_bridge *bridge, + + static const struct drm_bridge_funcs dw_mipi_dsi_bridge_funcs = { + .mode_set = dw_mipi_dsi_bridge_mode_set, ++ .pre_enable = dw_mipi_dsi_bridge_pre_enable, + .enable = dw_mipi_dsi_bridge_enable, ++ .disable = dw_mipi_dsi_bridge_disable, + .post_disable = dw_mipi_dsi_bridge_post_disable, + .mode_valid = dw_mipi_dsi_bridge_mode_valid, + .attach = dw_mipi_dsi_bridge_attach, diff --git a/sys-kernel/pinephone-pro-sources/files/0007-drm-dw-mipi-dsi-rockchip-Never-allow-lane-bandwidth-.patch b/sys-kernel/pinephone-pro-sources/files/0007-drm-dw-mipi-dsi-rockchip-Never-allow-lane-bandwidth-.patch new file mode 100644 index 0000000..003fd7c --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0007-drm-dw-mipi-dsi-rockchip-Never-allow-lane-bandwidth-.patch @@ -0,0 +1,28 @@ +From: Ondrej Jirman +Date: Tue, 16 Nov 2021 21:16:26 +0100 +Subject: [PATCH 07/36] drm: dw-mipi-dsi-rockchip: Never allow lane bandwidth + to be less than requested + +Bandwidth can be less than requested in some cases, because the search +for best values only checked for absolute difference from ideal value. + +This is likely not intentional. + +Signed-off-by: Ondrej Jirman +--- + drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c +index 095d0f1..bd488a8 100644 +--- a/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c ++++ b/drivers/gpu/drm/rockchip/dw-mipi-dsi-rockchip.c +@@ -612,7 +612,7 @@ dw_mipi_dsi_get_lane_mbps(void *priv_data, const struct drm_display_mode *mode, + continue; + + delta = abs(fout - tmp); +- if (delta < min_delta) { ++ if (delta < min_delta && fout < tmp) { + best_prediv = _prediv; + best_fbdiv = _fbdiv; + min_delta = delta; diff --git a/sys-kernel/pinephone-pro-sources/files/0008-drm-rockchip-cdn-dp-Disable-CDN-DP-on-disconnect.patch b/sys-kernel/pinephone-pro-sources/files/0008-drm-rockchip-cdn-dp-Disable-CDN-DP-on-disconnect.patch new file mode 100644 index 0000000..187e5e2 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0008-drm-rockchip-cdn-dp-Disable-CDN-DP-on-disconnect.patch @@ -0,0 +1,23 @@ +From: Ondrej Jirman +Date: Sat, 20 Nov 2021 14:35:52 +0100 +Subject: [PATCH 09/36] drm: rockchip: cdn-dp: Disable CDN DP on disconnect + +Why not? + +Signed-off-by: Ondrej Jirman +--- + drivers/gpu/drm/rockchip/cdn-dp-core.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/drivers/gpu/drm/rockchip/cdn-dp-core.c b/drivers/gpu/drm/rockchip/cdn-dp-core.c +index 16497c3..f4dcd9e 100644 +--- a/drivers/gpu/drm/rockchip/cdn-dp-core.c ++++ b/drivers/gpu/drm/rockchip/cdn-dp-core.c +@@ -934,6 +934,7 @@ static void cdn_dp_pd_event_work(struct work_struct *work) + DRM_DEV_INFO(dp->dev, "Not connected. Disabling cdn\n"); + dp->connected = false; + ++ cdn_dp_disable(dp); + /* Connected but not enabled, enable the block */ + } else if (!dp->active) { + DRM_DEV_INFO(dp->dev, "Connected, not enabled. Enabling cdn\n"); diff --git a/sys-kernel/pinephone-pro-sources/files/0009-video-fbdev-Add-events-for-early-fb-event-support.patch b/sys-kernel/pinephone-pro-sources/files/0009-video-fbdev-Add-events-for-early-fb-event-support.patch new file mode 100644 index 0000000..4ceaad0 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0009-video-fbdev-Add-events-for-early-fb-event-support.patch @@ -0,0 +1,69 @@ +From: Ondrej Jirman +Date: Sun, 17 Oct 2021 12:46:01 +0200 +Subject: [PATCH 10/36] video: fbdev: Add events for early fb event support + +This patch adds FB_EARLY_EVENT_BLANK and FB_R_EARLY_EVENT_BLANK +event mode supports. first, fb_notifier_call_chain() is called with +FB_EARLY_EVENT_BLANK and fb_blank() of specific fb driver is called +and then fb_notifier_call_chain() is called with FB_EVENT_BLANK again +at fb_blank(). and if fb_blank() was failed then fb_nitifier_call_chain() +would be called with FB_R_EARLY_EVENT_BLANK to revert the previous effects. + +Signed-off-by: Inki Dae +Signed-off-by: Kyungmin Park +--- + drivers/video/fbdev/core/fbmem.c | 12 +++++++++++- + include/linux/fb.h | 5 +++++ + 2 files changed, 16 insertions(+), 1 deletion(-) + +diff --git a/drivers/video/fbdev/core/fbmem.c b/drivers/video/fbdev/core/fbmem.c +index 0fa7ede..71d9d4a 100644 +--- a/drivers/video/fbdev/core/fbmem.c ++++ b/drivers/video/fbdev/core/fbmem.c +@@ -1067,7 +1067,7 @@ int + fb_blank(struct fb_info *info, int blank) + { + struct fb_event event; +- int ret = -EINVAL; ++ int ret = -EINVAL, early_ret; + + if (blank > FB_BLANK_POWERDOWN) + blank = FB_BLANK_POWERDOWN; +@@ -1075,11 +1075,21 @@ fb_blank(struct fb_info *info, int blank) + event.info = info; + event.data = ␣ + ++ early_ret = fb_notifier_call_chain(FB_EARLY_EVENT_BLANK, &event); ++ + if (info->fbops->fb_blank) + ret = info->fbops->fb_blank(blank, info); + + if (!ret) + fb_notifier_call_chain(FB_EVENT_BLANK, &event); ++ else { ++ /* ++ * if fb_blank is failed then revert effects of ++ * the early blank event. ++ */ ++ if (!early_ret) ++ fb_notifier_call_chain(FB_R_EARLY_EVENT_BLANK, &event); ++ } + + return ret; + } +diff --git a/include/linux/fb.h b/include/linux/fb.h +index 3da9584..5ac24d0 100644 +--- a/include/linux/fb.h ++++ b/include/linux/fb.h +@@ -137,6 +137,11 @@ struct fb_cursor_user { + /* A display blank is requested */ + #define FB_EVENT_BLANK 0x09 + ++/* A hardware display blank early change occured */ ++#define FB_EARLY_EVENT_BLANK 0x10 ++/* A hardware display blank revert early change occured */ ++#define FB_R_EARLY_EVENT_BLANK 0x11 ++ + struct fb_event { + struct fb_info *info; + void *data; diff --git a/sys-kernel/pinephone-pro-sources/files/0010-power-rk818-Configure-rk808-clkout2-function.patch b/sys-kernel/pinephone-pro-sources/files/0010-power-rk818-Configure-rk808-clkout2-function.patch new file mode 100644 index 0000000..8ff73f2 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0010-power-rk818-Configure-rk808-clkout2-function.patch @@ -0,0 +1,40 @@ +From: =?utf-8?q?Kamil_Trzci=C5=84ski?= +Date: Mon, 4 Jan 2021 17:57:49 +0100 +Subject: [PATCH 11/36] power: rk818: Configure `rk808-clkout2` function +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +??? + +Signed-of-by: Kamil Trzciński +--- + drivers/mfd/rk808.c | 1 + + include/linux/mfd/rk808.h | 2 ++ + 2 files changed, 3 insertions(+) + +diff --git a/drivers/mfd/rk808.c b/drivers/mfd/rk808.c +index b181fe4..1a6857e 100644 +--- a/drivers/mfd/rk808.c ++++ b/drivers/mfd/rk808.c +@@ -303,6 +303,7 @@ static const struct rk808_reg_data rk818_pre_init_reg[] = { + { RK818_H5V_EN_REG, BIT(0), RK818_H5V_EN }, + { RK808_VB_MON_REG, MASK_ALL, VB_LO_ACT | + VB_LO_SEL_3500MV }, ++ { RK808_CLK32OUT_REG, CLK32KOUT2_FUNC_MASK, CLK32KOUT2_FUNC }, + }; + + static const struct regmap_irq rk805_irqs[] = { +diff --git a/include/linux/mfd/rk808.h b/include/linux/mfd/rk808.h +index a96e6d4..2ec0520 100644 +--- a/include/linux/mfd/rk808.h ++++ b/include/linux/mfd/rk808.h +@@ -381,6 +381,8 @@ enum rk805_reg { + + #define VOUT_LO_INT BIT(0) + #define CLK32KOUT2_EN BIT(0) ++#define CLK32KOUT2_FUNC (0 << 1) ++#define CLK32KOUT2_FUNC_MASK BIT(1) + + #define TEMP115C 0x0c + #define TEMP_HOTDIE_MSK 0x0c diff --git a/sys-kernel/pinephone-pro-sources/files/0011-power-rk818-battery-Add-battery-driver-for-RK818.patch b/sys-kernel/pinephone-pro-sources/files/0011-power-rk818-battery-Add-battery-driver-for-RK818.patch new file mode 100644 index 0000000..fd0eb7e --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0011-power-rk818-battery-Add-battery-driver-for-RK818.patch @@ -0,0 +1,3964 @@ +From: =?utf-8?q?Kamil_Trzci=C5=84ski?= +Date: Sun, 3 Jan 2021 11:43:38 +0100 +Subject: [PATCH 12/36] power: rk818-battery: Add battery driver for RK818 +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +This is forward ported driver from Rockchip BSP. + +Signed-of-by: Kamil Trzciński +--- + drivers/mfd/rk808.c | 40 +- + drivers/power/supply/Kconfig | 8 + + drivers/power/supply/Makefile | 1 + + drivers/power/supply/rk818_battery.c | 3568 ++++++++++++++++++++++++++++++++++ + drivers/power/supply/rk818_battery.h | 168 ++ + include/linux/mfd/rk808.h | 81 +- + 6 files changed, 3863 insertions(+), 3 deletions(-) + create mode 100644 drivers/power/supply/rk818_battery.c + create mode 100644 drivers/power/supply/rk818_battery.h + +diff --git a/drivers/mfd/rk808.c b/drivers/mfd/rk808.c +index 1a6857e..7d1f000 100644 +--- a/drivers/mfd/rk808.c ++++ b/drivers/mfd/rk808.c +@@ -76,12 +76,47 @@ static bool rk817_is_volatile_reg(struct device *dev, unsigned int reg) + return true; + } + ++static bool rk818_is_volatile_reg(struct device *dev, unsigned int reg) ++{ ++ /* ++ * Notes: ++ * - Technically the ROUND_30s bit makes RTC_CTRL_REG volatile, but ++ * we don't use that feature. It's better to cache. ++ * - It's unlikely we care that RK808_DEVCTRL_REG is volatile since ++ * bits are cleared in case when we shutoff anyway, but better safe. ++ */ ++ ++ switch (reg) { ++ case RK808_SECONDS_REG ... RK808_WEEKS_REG: ++ case RK808_RTC_STATUS_REG: ++ case RK808_VB_MON_REG: ++ case RK808_THERMAL_REG: ++ case RK808_DCDC_EN_REG: ++ case RK808_LDO_EN_REG: ++ case RK808_DCDC_UV_STS_REG: ++ case RK808_LDO_UV_STS_REG: ++ case RK808_DCDC_PG_REG: ++ case RK808_LDO_PG_REG: ++ case RK808_DEVCTRL_REG: ++ case RK808_INT_STS_REG1: ++ case RK808_INT_STS_REG2: ++ case RK808_INT_STS_MSK_REG1: ++ case RK808_INT_STS_MSK_REG2: ++ case RK818_LDO8_ON_VSEL_REG: // TODO(ayufan):?? ++ case RK818_LDO8_SLP_VSEL_REG: // TODO(ayufan):?? ++ case RK818_SUP_STS_REG ... RK818_SAVE_DATA19: ++ return true; ++ } ++ ++ return false; ++} ++ + static const struct regmap_config rk818_regmap_config = { + .reg_bits = 8, + .val_bits = 8, +- .max_register = RK818_USB_CTRL_REG, ++ .max_register = RK818_SAVE_DATA19, + .cache_type = REGCACHE_RBTREE, +- .volatile_reg = rk808_is_volatile_reg, ++ .volatile_reg = rk818_is_volatile_reg, + }; + + static const struct regmap_config rk805_regmap_config = { +@@ -170,6 +205,7 @@ static const struct mfd_cell rk817s[] = { + static const struct mfd_cell rk818s[] = { + { .name = "rk808-clkout", }, + { .name = "rk808-regulator", }, ++ { .name = "rk818-battery", .of_compatible = "rk818-battery", }, + { + .name = "rk808-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index 5cf5bb5..f5d4434 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -854,4 +854,12 @@ config CHARGER_SURFACE + Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3, + Surface Book 3, and Surface Laptop Go. + ++config BATTERY_RK818 ++ bool "RK818 Battery driver" ++ depends on MFD_RK808 ++ default n ++ help ++ If you say yes here you will get support for the battery of RK818 PMIC. ++ This driver can give support for Rk818 Battery Charge Interface. ++ + endif # POWER_SUPPLY +diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile +index 4e55a11aab..1c725ee 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -104,3 +104,4 @@ obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o + obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o + obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o + obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o ++obj-$(CONFIG_BATTERY_RK818) += rk818_battery.o +diff --git a/drivers/power/supply/rk818_battery.c b/drivers/power/supply/rk818_battery.c +new file mode 100644 +index 00000000..f09f456 +--- /dev/null ++++ b/drivers/power/supply/rk818_battery.c +@@ -0,0 +1,3568 @@ ++/* ++ * rk818 battery driver ++ * ++ * Copyright (C) 2016 Rockchip Electronics Co., Ltd ++ * chenjh ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++//#include ++#include ++//#include ++#include ++#include ++#include ++//#include ++#include ++#include "rk818_battery.h" ++ ++static int dbg_enable = 0; ++module_param_named(dbg_level, dbg_enable, int, 0644); ++ ++#define DBG(args...) \ ++ do { \ ++ if (dbg_enable) { \ ++ pr_info(args); \ ++ } \ ++ } while (0) ++ ++#define BAT_INFO(fmt, args...) pr_info("rk818-bat: "fmt, ##args) ++ ++/* default param */ ++#define DEFAULT_BAT_RES 135 ++#define DEFAULT_SLP_ENTER_CUR 300 ++#define DEFAULT_SLP_EXIT_CUR 300 ++#define DEFAULT_SLP_FILTER_CUR 100 ++#define DEFAULT_PWROFF_VOL_THRESD 3400 ++#define DEFAULT_MONITOR_SEC 5 ++#define DEFAULT_ALGR_VOL_THRESD1 3850 ++#define DEFAULT_ALGR_VOL_THRESD2 3950 ++#define DEFAULT_MAX_SOC_OFFSET 60 ++#define DEFAULT_FB_TEMP TEMP_105C ++#define DEFAULT_ZERO_RESERVE_DSOC 10 ++#define DEFAULT_POFFSET 42 ++#define DEFAULT_COFFSET 0x832 ++#define DEFAULT_SAMPLE_RES 20 ++#define DEFAULT_ENERGY_MODE 0 ++#define INVALID_COFFSET_MIN 0x780 ++#define INVALID_COFFSET_MAX 0x980 ++#define INVALID_VOL_THRESD 2500 ++ ++/* sample resistor and division */ ++#define SAMPLE_RES_10MR 10 ++#define SAMPLE_RES_20MR 20 ++#define SAMPLE_RES_DIV1 1 ++#define SAMPLE_RES_DIV2 2 ++ ++/* virtual params */ ++#define VIRTUAL_CURRENT 1000 ++#define VIRTUAL_VOLTAGE 3888 ++#define VIRTUAL_SOC 66 ++#define VIRTUAL_PRESET 1 ++#define VIRTUAL_TEMPERATURE 188 ++#define VIRTUAL_STATUS POWER_SUPPLY_STATUS_CHARGING ++ ++/* charge */ ++#define FINISH_CHRG_CUR1 1000 ++#define FINISH_CHRG_CUR2 1500 ++#define FINISH_MAX_SOC_DELAY 20 ++#define TERM_CHRG_DSOC 88 ++#define TERM_CHRG_CURR 600 ++#define TERM_CHRG_K 650 ++#define SIMULATE_CHRG_INTV 8 ++#define SIMULATE_CHRG_CURR 400 ++#define SIMULATE_CHRG_K 1500 ++#define FULL_CHRG_K 400 ++ ++/* zero algorithm */ ++#define PWROFF_THRESD 3400 ++#define MIN_ZERO_DSOC_ACCURACY 10 /*0.01%*/ ++#define MIN_ZERO_OVERCNT 100 ++#define MIN_ACCURACY 1 ++#define DEF_PWRPATH_RES 50 ++#define WAIT_DSOC_DROP_SEC 15 ++#define WAIT_SHTD_DROP_SEC 30 ++#define ZERO_GAP_XSOC1 10 ++#define ZERO_GAP_XSOC2 5 ++#define ZERO_GAP_XSOC3 3 ++#define ZERO_LOAD_LVL1 1400 ++#define ZERO_LOAD_LVL2 600 ++#define ZERO_GAP_CALIB 5 ++ ++#define ADC_CALIB_THRESHOLD 4 ++#define ADC_CALIB_LMT_MIN 3 ++#define ADC_CALIB_CNT 5 ++#define NTC_CALC_FACTOR 7 ++ ++/* time */ ++#define POWER_ON_SEC_BASE 1 ++#define MINUTE(x) ((x) * 60) ++ ++/* sleep */ ++#define SLP_CURR_MAX 40 ++#define SLP_CURR_MIN 6 ++#define DISCHRG_TIME_STEP1 MINUTE(10) ++#define DISCHRG_TIME_STEP2 MINUTE(60) ++#define SLP_DSOC_VOL_THRESD 3600 ++#define REBOOT_PERIOD_SEC 180 ++#define REBOOT_MAX_CNT 80 ++ ++/* fcc */ ++#define MIN_FCC 500 ++ ++/* TS detect battery temperature */ ++#define ADC_CUR_MSK 0x03 ++#define ADC_CUR_20UA 0x00 ++#define ADC_CUR_40UA 0x01 ++#define ADC_CUR_60UA 0x02 ++#define ADC_CUR_80UA 0x03 ++ ++#define NTC_CALC_FACTOR_80UA 7 ++#define NTC_CALC_FACTOR_60UA 9 ++#define NTC_CALC_FACTOR_40UA 13 ++#define NTC_CALC_FACTOR_20UA 27 ++#define NTC_80UA_MAX_MEASURE 27500 ++#define NTC_60UA_MAX_MEASURE 36666 ++#define NTC_40UA_MAX_MEASURE 55000 ++#define NTC_20UA_MAX_MEASURE 110000 ++ ++static const char *bat_status[] = { ++ "charge off", "dead charge", "trickle charge", "cc cv", ++ "finish", "usb over vol", "bat temp error", "timer error", ++}; ++ ++struct rk818_battery { ++ struct platform_device *pdev; ++ struct rk808 *rk818; ++ struct regmap *regmap; ++ struct device *dev; ++ struct power_supply *bat; ++ struct power_supply *usb_psy; ++ struct power_supply *ac_psy; ++ struct battery_platform_data *pdata; ++ struct workqueue_struct *bat_monitor_wq; ++ struct delayed_work bat_delay_work; ++ struct delayed_work calib_delay_work; ++ // struct wake_lock wake_lock; ++ struct notifier_block fb_nb; ++ struct timer_list caltimer; ++ time64_t rtc_base; ++ int bat_res; ++ int chrg_status; ++ bool is_initialized; ++ bool is_first_power_on; ++ u8 res_div; ++ int current_max; ++ int voltage_max; ++ int current_avg; ++ int voltage_avg; ++ int voltage_ocv; ++ int voltage_relax; ++ int voltage_k; ++ int voltage_b; ++ int remain_cap; ++ int design_cap; ++ int nac; ++ int fcc; ++ int qmax; ++ int dsoc; ++ int rsoc; ++ int poffset; ++ int age_ocv_soc; ++ bool age_allow_update; ++ int age_level; ++ int age_ocv_cap; ++ int age_voltage; ++ int age_adjust_cap; ++ unsigned long age_keep_sec; ++ int zero_timeout_cnt; ++ int zero_remain_cap; ++ int zero_dsoc; ++ int zero_linek; ++ u64 zero_drop_sec; ++ u64 shtd_drop_sec; ++ int sm_remain_cap; ++ int sm_linek; ++ int sm_chrg_dsoc; ++ int sm_dischrg_dsoc; ++ int algo_rest_val; ++ int algo_rest_mode; ++ int sleep_sum_cap; ++ int sleep_remain_cap; ++ unsigned long sleep_dischrg_sec; ++ unsigned long sleep_sum_sec; ++ bool sleep_chrg_online; ++ u8 sleep_chrg_status; ++ bool adc_allow_update; ++ int fb_blank; ++ bool s2r; /*suspend to resume*/ ++ u32 work_mode; ++ int temperature; ++ u32 monitor_ms; ++ u32 pwroff_min; ++ u32 adc_calib_cnt; ++ unsigned long finish_base; ++ unsigned long boot_base; ++ unsigned long flat_match_sec; ++ unsigned long plug_in_base; ++ unsigned long plug_out_base; ++ u8 halt_cnt; ++ bool is_halt; ++ bool is_max_soc_offset; ++ bool is_sw_reset; ++ bool is_ocv_calib; ++ bool is_first_on; ++ bool is_force_calib; ++ int last_dsoc; ++ int ocv_pre_dsoc; ++ int ocv_new_dsoc; ++ int max_pre_dsoc; ++ int max_new_dsoc; ++ int force_pre_dsoc; ++ int force_new_dsoc; ++ int dbg_cap_low0; ++ int dbg_pwr_dsoc; ++ int dbg_pwr_rsoc; ++ int dbg_pwr_vol; ++ int dbg_chrg_min[10]; ++ int dbg_meet_soc; ++ int dbg_calc_dsoc; ++ int dbg_calc_rsoc; ++ u8 ac_in; ++ u8 usb_in; ++ int is_charging; ++ unsigned long charge_count; ++}; ++ ++#define DIV(x) ((x) ? (x) : 1) ++ ++static void rk_send_wakeup_key(void) ++{ ++ // TODO: WHAT TO DO HERE? ++} ++ ++static u64 get_boot_sec(void) ++{ ++ struct timespec64 ts; ++ ++ ktime_get_boottime_ts64(&ts); ++ ++ return ts.tv_sec; ++} ++ ++static unsigned long base2sec(unsigned long x) ++{ ++ if (x) ++ return (get_boot_sec() > x) ? (get_boot_sec() - x) : 0; ++ else ++ return 0; ++} ++ ++static unsigned long base2min(unsigned long x) ++{ ++ return base2sec(x) / 60; ++} ++ ++static u32 interpolate(int value, u32 *table, int size) ++{ ++ u8 i; ++ u16 d; ++ ++ for (i = 0; i < size; i++) { ++ if (value < table[i]) ++ break; ++ } ++ ++ if ((i > 0) && (i < size)) { ++ d = (value - table[i - 1]) * (MAX_INTERPOLATE / (size - 1)); ++ d /= table[i] - table[i - 1]; ++ d = d + (i - 1) * (MAX_INTERPOLATE / (size - 1)); ++ } else { ++ d = i * ((MAX_INTERPOLATE + size / 2) / size); ++ } ++ ++ if (d > 1000) ++ d = 1000; ++ ++ return d; ++} ++ ++/* (a*b)/c */ ++static int32_t ab_div_c(u32 a, u32 b, u32 c) ++{ ++ bool sign; ++ u32 ans = MAX_INT; ++ int tmp; ++ ++ sign = ((((a ^ b) ^ c) & 0x80000000) != 0); ++ if (c != 0) { ++ if (sign) ++ c = -c; ++ tmp = (a * b + (c >> 1)) / c; ++ if (tmp < MAX_INT) ++ ans = tmp; ++ } ++ ++ if (sign) ++ ans = -ans; ++ ++ return ans; ++} ++ ++static int rk818_bat_read(struct rk818_battery *di, u8 reg) ++{ ++ int ret, val; ++ ++ ret = regmap_read(di->regmap, reg, &val); ++ if (ret) ++ dev_err(di->dev, "read reg:0x%x failed\n", reg); ++ ++ return val; ++} ++ ++static int rk818_bat_write(struct rk818_battery *di, u8 reg, u8 buf) ++{ ++ int ret; ++ ++ ret = regmap_write(di->regmap, reg, buf); ++ if (ret) ++ dev_err(di->dev, "i2c write reg: 0x%2x error\n", reg); ++ ++ return ret; ++} ++ ++static int rk818_bat_set_bits(struct rk818_battery *di, u8 reg, u8 mask, u8 buf) ++{ ++ int ret; ++ ++ ret = regmap_update_bits(di->regmap, reg, mask, buf); ++ if (ret) ++ dev_err(di->dev, "write reg:0x%x failed\n", reg); ++ ++ return ret; ++} ++ ++static int rk818_bat_clear_bits(struct rk818_battery *di, u8 reg, u8 mask) ++{ ++ int ret; ++ ++ ret = regmap_update_bits(di->regmap, reg, mask, 0); ++ if (ret) ++ dev_err(di->dev, "clr reg:0x%02x failed\n", reg); ++ ++ return ret; ++} ++ ++static void rk818_bat_dump_regs(struct rk818_battery *di, u8 start, u8 end) ++{ ++ int i; ++ ++ if (!dbg_enable) ++ return; ++ ++ DBG("dump regs from: 0x%x-->0x%x\n", start, end); ++ for (i = start; i < end; i++) ++ DBG("0x%x: 0x%0x\n", i, rk818_bat_read(di, i)); ++} ++ ++static bool rk818_bat_chrg_online(struct rk818_battery *di) ++{ ++ u8 buf; ++ ++ buf = rk818_bat_read(di, RK818_VB_MON_REG); ++ ++ return (buf & PLUG_IN_STS) ? true : false; ++} ++ ++static int rk818_bat_get_coulomb_cap(struct rk818_battery *di) ++{ ++ int val = 0; ++ ++ val |= rk818_bat_read(di, RK818_GASCNT3_REG) << 24; ++ val |= rk818_bat_read(di, RK818_GASCNT2_REG) << 16; ++ val |= rk818_bat_read(di, RK818_GASCNT1_REG) << 8; ++ val |= rk818_bat_read(di, RK818_GASCNT0_REG) << 0; ++ ++ return (val / 2390) * di->res_div; ++} ++ ++static int rk818_bat_get_rsoc(struct rk818_battery *di) ++{ ++ int remain_cap; ++ ++ remain_cap = rk818_bat_get_coulomb_cap(di); ++ return (remain_cap + di->fcc / 200) * 100 / DIV(di->fcc); ++} ++ ++static ssize_t bat_info_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ char cmd; ++ struct rk818_battery *di = dev_get_drvdata(dev); ++ ++ sscanf(buf, "%c", &cmd); ++ ++ if (cmd == 'n') ++ rk818_bat_set_bits(di, RK818_MISC_MARK_REG, ++ FG_RESET_NOW, FG_RESET_NOW); ++ else if (cmd == 'm') ++ rk818_bat_set_bits(di, RK818_MISC_MARK_REG, ++ FG_RESET_LATE, FG_RESET_LATE); ++ else if (cmd == 'c') ++ rk818_bat_clear_bits(di, RK818_MISC_MARK_REG, ++ FG_RESET_LATE | FG_RESET_NOW); ++ else if (cmd == 'r') ++ BAT_INFO("0x%2x\n", rk818_bat_read(di, RK818_MISC_MARK_REG)); ++ else ++ BAT_INFO("command error\n"); ++ ++ return count; ++} ++ ++static struct device_attribute rk818_bat_attr[] = { ++ __ATTR(bat, 0664, NULL, bat_info_store), ++}; ++ ++static void rk818_bat_enable_gauge(struct rk818_battery *di) ++{ ++ u8 buf; ++ ++ buf = rk818_bat_read(di, RK818_TS_CTRL_REG); ++ buf |= GG_EN; ++ rk818_bat_write(di, RK818_TS_CTRL_REG, buf); ++} ++ ++static void rk818_bat_save_age_level(struct rk818_battery *di, u8 level) ++{ ++ rk818_bat_write(di, RK818_UPDAT_LEVE_REG, level); ++} ++ ++static u8 rk818_bat_get_age_level(struct rk818_battery *di) ++{ ++ return rk818_bat_read(di, RK818_UPDAT_LEVE_REG); ++} ++ ++static int rk818_bat_get_vcalib0(struct rk818_battery *di) ++{ ++ int val = 0; ++ ++ val |= rk818_bat_read(di, RK818_VCALIB0_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_VCALIB0_REGH) << 8; ++ ++ DBG("<%s>. voffset0: 0x%x\n", __func__, val); ++ return val; ++} ++ ++static int rk818_bat_get_vcalib1(struct rk818_battery *di) ++{ ++ int val = 0; ++ ++ val |= rk818_bat_read(di, RK818_VCALIB1_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_VCALIB1_REGH) << 8; ++ ++ DBG("<%s>. voffset1: 0x%x\n", __func__, val); ++ return val; ++} ++ ++static int rk818_bat_get_ioffset(struct rk818_battery *di) ++{ ++ int val = 0; ++ ++ val |= rk818_bat_read(di, RK818_IOFFSET_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_IOFFSET_REGH) << 8; ++ ++ DBG("<%s>. ioffset: 0x%x\n", __func__, val); ++ return val; ++} ++ ++static int rk818_bat_get_coffset(struct rk818_battery *di) ++{ ++ int val = 0; ++ ++ val |= rk818_bat_read(di, RK818_CAL_OFFSET_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_CAL_OFFSET_REGH) << 8; ++ ++ DBG("<%s>. coffset: 0x%x\n", __func__, val); ++ return val; ++} ++ ++static void rk818_bat_set_coffset(struct rk818_battery *di, int val) ++{ ++ u8 buf; ++ ++ if ((val < INVALID_COFFSET_MIN) || (val > INVALID_COFFSET_MAX)) { ++ BAT_INFO("set invalid coffset=0x%x\n", val); ++ return; ++ } ++ ++ buf = (val >> 8) & 0xff; ++ rk818_bat_write(di, RK818_CAL_OFFSET_REGH, buf); ++ buf = (val >> 0) & 0xff; ++ rk818_bat_write(di, RK818_CAL_OFFSET_REGL, buf); ++ DBG("<%s>. coffset: 0x%x\n", __func__, val); ++} ++ ++static void rk818_bat_init_voltage_kb(struct rk818_battery *di) ++{ ++ int vcalib0, vcalib1; ++ ++ vcalib0 = rk818_bat_get_vcalib0(di); ++ vcalib1 = rk818_bat_get_vcalib1(di); ++ di->voltage_k = (4200 - 3000) * 1000 / DIV(vcalib1 - vcalib0); ++ di->voltage_b = 4200 - (di->voltage_k * vcalib1) / 1000; ++ ++ DBG("voltage_k=%d(*1000),voltage_b=%d\n", di->voltage_k, di->voltage_b); ++} ++ ++static int rk818_bat_get_ocv_voltage(struct rk818_battery *di) ++{ ++ int vol, val = 0; ++ ++ val |= rk818_bat_read(di, RK818_BAT_OCV_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_BAT_OCV_REGH) << 8; ++ ++ vol = di->voltage_k * val / 1000 + di->voltage_b; ++ ++ return vol; ++} ++ ++static int rk818_bat_get_avg_voltage(struct rk818_battery *di) ++{ ++ int vol, val = 0; ++ ++ val |= rk818_bat_read(di, RK818_BAT_VOL_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_BAT_VOL_REGH) << 8; ++ ++ vol = di->voltage_k * val / 1000 + di->voltage_b; ++ ++ return vol; ++} ++ ++static bool is_rk818_bat_relax_mode(struct rk818_battery *di) ++{ ++ u8 status; ++ ++ status = rk818_bat_read(di, RK818_GGSTS_REG); ++ if (!(status & RELAX_VOL1_UPD) || !(status & RELAX_VOL2_UPD)) ++ return false; ++ else ++ return true; ++} ++ ++static u16 rk818_bat_get_relax_vol1(struct rk818_battery *di) ++{ ++ u16 vol, val = 0; ++ ++ val |= rk818_bat_read(di, RK818_RELAX_VOL1_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_RELAX_VOL1_REGH) << 8; ++ vol = di->voltage_k * val / 1000 + di->voltage_b; ++ ++ return vol; ++} ++ ++static u16 rk818_bat_get_relax_vol2(struct rk818_battery *di) ++{ ++ u16 vol, val = 0; ++ ++ val |= rk818_bat_read(di, RK818_RELAX_VOL2_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_RELAX_VOL2_REGH) << 8; ++ vol = di->voltage_k * val / 1000 + di->voltage_b; ++ ++ return vol; ++} ++ ++static u16 rk818_bat_get_relax_voltage(struct rk818_battery *di) ++{ ++ u16 relax_vol1, relax_vol2; ++ ++ if (!is_rk818_bat_relax_mode(di)) ++ return 0; ++ ++ relax_vol1 = rk818_bat_get_relax_vol1(di); ++ relax_vol2 = rk818_bat_get_relax_vol2(di); ++ ++ return relax_vol1 > relax_vol2 ? relax_vol1 : relax_vol2; ++} ++ ++static int rk818_bat_get_avg_current(struct rk818_battery *di) ++{ ++ int cur, val = 0; ++ ++ val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGH) << 8; ++ ++ if (val & 0x800) ++ val -= 4096; ++ cur = val * di->res_div * 1506 / 1000; ++ ++ return cur; ++} ++ ++static int rk818_bat_vol_to_ocvsoc(struct rk818_battery *di, int voltage) ++{ ++ u32 *ocv_table, temp; ++ int ocv_size, ocv_soc; ++ ++ ocv_table = di->pdata->ocv_table; ++ ocv_size = di->pdata->ocv_size; ++ temp = interpolate(voltage, ocv_table, ocv_size); ++ ocv_soc = ab_div_c(temp, MAX_PERCENTAGE, MAX_INTERPOLATE); ++ ++ return ocv_soc; ++} ++ ++static int rk818_bat_vol_to_ocvcap(struct rk818_battery *di, int voltage) ++{ ++ u32 *ocv_table, temp; ++ int ocv_size, cap; ++ ++ ocv_table = di->pdata->ocv_table; ++ ocv_size = di->pdata->ocv_size; ++ temp = interpolate(voltage, ocv_table, ocv_size); ++ cap = ab_div_c(temp, di->fcc, MAX_INTERPOLATE); ++ ++ return cap; ++} ++ ++static int rk818_bat_vol_to_zerosoc(struct rk818_battery *di, int voltage) ++{ ++ u32 *ocv_table, temp; ++ int ocv_size, ocv_soc; ++ ++ ocv_table = di->pdata->zero_table; ++ ocv_size = di->pdata->ocv_size; ++ temp = interpolate(voltage, ocv_table, ocv_size); ++ ocv_soc = ab_div_c(temp, MAX_PERCENTAGE, MAX_INTERPOLATE); ++ ++ return ocv_soc; ++} ++ ++static int rk818_bat_vol_to_zerocap(struct rk818_battery *di, int voltage) ++{ ++ u32 *ocv_table, temp; ++ int ocv_size, cap; ++ ++ ocv_table = di->pdata->zero_table; ++ ocv_size = di->pdata->ocv_size; ++ temp = interpolate(voltage, ocv_table, ocv_size); ++ cap = ab_div_c(temp, di->fcc, MAX_INTERPOLATE); ++ ++ return cap; ++} ++ ++static int rk818_bat_get_iadc(struct rk818_battery *di) ++{ ++ int val = 0; ++ ++ val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGL) << 0; ++ val |= rk818_bat_read(di, RK818_BAT_CUR_AVG_REGH) << 8; ++ if (val > 2047) ++ val -= 4096; ++ ++ return val; ++} ++ ++static bool rk818_bat_adc_calib(struct rk818_battery *di) ++{ ++ int i, ioffset, coffset, adc, save_coffset; ++ ++ if ((di->chrg_status != CHARGE_FINISH) || ++ (di->adc_calib_cnt > ADC_CALIB_CNT) || ++ (base2min(di->boot_base) < ADC_CALIB_LMT_MIN) || ++ (abs(di->current_avg) < ADC_CALIB_THRESHOLD)) ++ return false; ++ ++ di->adc_calib_cnt++; ++ save_coffset = rk818_bat_get_coffset(di); ++ for (i = 0; i < 5; i++) { ++ adc = rk818_bat_get_iadc(di); ++ if (!rk818_bat_chrg_online(di)) { ++ rk818_bat_set_coffset(di, save_coffset); ++ BAT_INFO("quit, charger plugout when calib adc\n"); ++ return false; ++ } ++ coffset = rk818_bat_get_coffset(di); ++ rk818_bat_set_coffset(di, coffset + adc); ++ msleep(2000); ++ adc = rk818_bat_get_iadc(di); ++ if (abs(adc) < ADC_CALIB_THRESHOLD) { ++ coffset = rk818_bat_get_coffset(di); ++ ioffset = rk818_bat_get_ioffset(di); ++ di->poffset = coffset - ioffset; ++ rk818_bat_write(di, RK818_POFFSET_REG, di->poffset); ++ BAT_INFO("new offset:c=0x%x, i=0x%x, p=0x%x\n", ++ coffset, ioffset, di->poffset); ++ return true; ++ } else { ++ BAT_INFO("coffset calib again %d.., max_cnt=%d\n", ++ i, di->adc_calib_cnt); ++ rk818_bat_set_coffset(di, coffset); ++ msleep(2000); ++ } ++ } ++ ++ rk818_bat_set_coffset(di, save_coffset); ++ ++ return false; ++} ++ ++static void rk818_bat_set_ioffset_sample(struct rk818_battery *di) ++{ ++ u8 ggcon; ++ ++ ggcon = rk818_bat_read(di, RK818_GGCON_REG); ++ ggcon &= ~ADC_CAL_MIN_MSK; ++ ggcon |= ADC_CAL_8MIN; ++ rk818_bat_write(di, RK818_GGCON_REG, ggcon); ++} ++ ++static void rk818_bat_set_ocv_sample(struct rk818_battery *di) ++{ ++ u8 ggcon; ++ ++ ggcon = rk818_bat_read(di, RK818_GGCON_REG); ++ ggcon &= ~OCV_SAMP_MIN_MSK; ++ ggcon |= OCV_SAMP_8MIN; ++ rk818_bat_write(di, RK818_GGCON_REG, ggcon); ++} ++ ++static void rk818_bat_restart_relax(struct rk818_battery *di) ++{ ++ u8 ggsts; ++ ++ ggsts = rk818_bat_read(di, RK818_GGSTS_REG); ++ ggsts &= ~RELAX_VOL12_UPD_MSK; ++ rk818_bat_write(di, RK818_GGSTS_REG, ggsts); ++} ++ ++static void rk818_bat_set_relax_sample(struct rk818_battery *di) ++{ ++ u8 buf; ++ int enter_thres, exit_thres; ++ struct battery_platform_data *pdata = di->pdata; ++ ++ enter_thres = pdata->sleep_enter_current * 1000 / 1506 / DIV(di->res_div); ++ exit_thres = pdata->sleep_exit_current * 1000 / 1506 / DIV(di->res_div); ++ ++ /* set relax enter and exit threshold */ ++ buf = enter_thres & 0xff; ++ rk818_bat_write(di, RK818_RELAX_ENTRY_THRES_REGL, buf); ++ buf = (enter_thres >> 8) & 0xff; ++ rk818_bat_write(di, RK818_RELAX_ENTRY_THRES_REGH, buf); ++ ++ buf = exit_thres & 0xff; ++ rk818_bat_write(di, RK818_RELAX_EXIT_THRES_REGL, buf); ++ buf = (exit_thres >> 8) & 0xff; ++ rk818_bat_write(di, RK818_RELAX_EXIT_THRES_REGH, buf); ++ ++ /* reset relax update state */ ++ rk818_bat_restart_relax(di); ++ DBG("<%s>. sleep_enter_current = %d, sleep_exit_current = %d\n", ++ __func__, pdata->sleep_enter_current, pdata->sleep_exit_current); ++} ++ ++static bool is_rk818_bat_exist(struct rk818_battery *di) ++{ ++ return (rk818_bat_read(di, RK818_SUP_STS_REG) & BAT_EXS) ? true : false; ++} ++ ++static bool is_rk818_bat_first_pwron(struct rk818_battery *di) ++{ ++ u8 buf; ++ ++ buf = rk818_bat_read(di, RK818_GGSTS_REG); ++ if (buf & BAT_CON) { ++ buf &= ~BAT_CON; ++ rk818_bat_write(di, RK818_GGSTS_REG, buf); ++ return true; ++ } ++ ++ return false; ++} ++ ++static u8 rk818_bat_get_pwroff_min(struct rk818_battery *di) ++{ ++ u8 cur, last; ++ ++ cur = rk818_bat_read(di, RK818_NON_ACT_TIMER_CNT_REG); ++ last = rk818_bat_read(di, RK818_NON_ACT_TIMER_CNT_SAVE_REG); ++ rk818_bat_write(di, RK818_NON_ACT_TIMER_CNT_SAVE_REG, cur); ++ ++ return (cur != last) ? cur : 0; ++} ++ ++static u8 is_rk818_bat_initialized(struct rk818_battery *di) ++{ ++ u8 val = rk818_bat_read(di, RK818_MISC_MARK_REG); ++ ++ if (val & FG_INIT) { ++ val &= ~FG_INIT; ++ rk818_bat_write(di, RK818_MISC_MARK_REG, val); ++ return true; ++ } else { ++ return false; ++ } ++} ++ ++static bool is_rk818_bat_ocv_valid(struct rk818_battery *di) ++{ ++ return (!di->is_initialized && di->pwroff_min >= 30) ? true : false; ++} ++ ++static void rk818_bat_init_age_algorithm(struct rk818_battery *di) ++{ ++ int age_level, ocv_soc, ocv_cap, ocv_vol; ++ ++ if (di->is_first_power_on || is_rk818_bat_ocv_valid(di)) { ++ DBG("<%s> enter.\n", __func__); ++ ocv_vol = rk818_bat_get_ocv_voltage(di); ++ ocv_soc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); ++ ocv_cap = rk818_bat_vol_to_ocvcap(di, ocv_vol); ++ if (ocv_soc < 20) { ++ di->age_voltage = ocv_vol; ++ di->age_ocv_cap = ocv_cap; ++ di->age_ocv_soc = ocv_soc; ++ di->age_adjust_cap = 0; ++ ++ if (ocv_soc <= 0) ++ di->age_level = 100; ++ else if (ocv_soc < 5) ++ di->age_level = 95; ++ else if (ocv_soc < 10) ++ di->age_level = 90; ++ else ++ di->age_level = 80; ++ ++ age_level = rk818_bat_get_age_level(di); ++ if (age_level > di->age_level) { ++ di->age_allow_update = false; ++ age_level -= 5; ++ if (age_level <= 80) ++ age_level = 80; ++ rk818_bat_save_age_level(di, age_level); ++ } else { ++ di->age_allow_update = true; ++ di->age_keep_sec = get_boot_sec(); ++ } ++ ++ BAT_INFO("init_age_algorithm: " ++ "age_vol:%d, age_ocv_cap:%d, " ++ "age_ocv_soc:%d, old_age_level:%d, " ++ "age_allow_update:%d, new_age_level:%d\n", ++ di->age_voltage, di->age_ocv_cap, ++ ocv_soc, age_level, di->age_allow_update, ++ di->age_level); ++ } ++ } ++} ++ ++static enum power_supply_property rk818_bat_props[] = { ++ POWER_SUPPLY_PROP_CURRENT_NOW, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_HEALTH, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_TEMP, ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_CHARGE_COUNTER, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ POWER_SUPPLY_PROP_VOLTAGE_MAX, ++ POWER_SUPPLY_PROP_CURRENT_MAX, ++}; ++ ++static int rk818_bat_get_usb_psy(struct device *dev, void *data) ++{ ++ struct rk818_battery *di = data; ++ struct power_supply *psy = dev_get_drvdata(dev); ++ ++ if (psy->desc->type == POWER_SUPPLY_TYPE_USB) { ++ di->usb_psy = psy; ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static int rk818_bat_get_ac_psy(struct device *dev, void *data) ++{ ++ struct rk818_battery *di = data; ++ struct power_supply *psy = dev_get_drvdata(dev); ++ ++ if (psy->desc->type == POWER_SUPPLY_TYPE_MAINS) { ++ di->ac_psy = psy; ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static void rk818_bat_get_chrg_psy(struct rk818_battery *di) ++{ ++ if (!di->usb_psy) ++ class_for_each_device(power_supply_class, NULL, (void *)di, ++ rk818_bat_get_usb_psy); ++ if (!di->ac_psy) ++ class_for_each_device(power_supply_class, NULL, (void *)di, ++ rk818_bat_get_ac_psy); ++} ++ ++static int rk818_bat_get_charge_state(struct rk818_battery *di) ++{ ++ union power_supply_propval val; ++ int ret; ++ ++ if (!di->usb_psy || !di->ac_psy) ++ rk818_bat_get_chrg_psy(di); ++ ++ if (di->usb_psy) { ++ ret = di->usb_psy->desc->get_property(di->usb_psy, ++ POWER_SUPPLY_PROP_ONLINE, ++ &val); ++ if (!ret) ++ di->usb_in = val.intval; ++ } ++ ++ if (di->ac_psy) { ++ ret = di->ac_psy->desc->get_property(di->ac_psy, ++ POWER_SUPPLY_PROP_ONLINE, ++ &val); ++ if (!ret) ++ di->ac_in = val.intval; ++ } ++ ++ DBG("%s: ac_online=%d, usb_online=%d\n", ++ __func__, di->ac_in, di->usb_in); ++ ++ return (di->usb_in || di->ac_in); ++} ++ ++static int rk818_battery_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct rk818_battery *di = power_supply_get_drvdata(psy); ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CURRENT_NOW: ++ val->intval = di->current_avg * 1000;/*uA*/ ++ if (di->pdata->bat_mode == MODE_VIRTUAL) ++ val->intval = VIRTUAL_CURRENT * 1000; ++ break; ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ val->intval = di->voltage_avg * 1000;/*uV*/ ++ if (di->pdata->bat_mode == MODE_VIRTUAL) ++ val->intval = VIRTUAL_VOLTAGE * 1000; ++ break; ++ case POWER_SUPPLY_PROP_PRESENT: ++ val->intval = is_rk818_bat_exist(di); ++ if (di->pdata->bat_mode == MODE_VIRTUAL) ++ val->intval = VIRTUAL_PRESET; ++ break; ++ case POWER_SUPPLY_PROP_CAPACITY: ++ val->intval = di->dsoc; ++ if (di->pdata->bat_mode == MODE_VIRTUAL) ++ val->intval = VIRTUAL_SOC; ++ DBG("<%s>. report dsoc: %d\n", __func__, val->intval); ++ break; ++ case POWER_SUPPLY_PROP_HEALTH: ++ val->intval = POWER_SUPPLY_HEALTH_GOOD; ++ break; ++ case POWER_SUPPLY_PROP_TEMP: ++ val->intval = di->temperature; ++ if (di->pdata->bat_mode == MODE_VIRTUAL) ++ val->intval = VIRTUAL_TEMPERATURE; ++ break; ++ case POWER_SUPPLY_PROP_STATUS: ++ if (di->pdata->bat_mode == MODE_VIRTUAL) ++ val->intval = VIRTUAL_STATUS; ++ else if (di->dsoc == 100) ++ val->intval = POWER_SUPPLY_STATUS_FULL; ++ else if (rk818_bat_get_charge_state(di)) ++ val->intval = POWER_SUPPLY_STATUS_CHARGING; ++ else ++ val->intval = POWER_SUPPLY_STATUS_DISCHARGING; ++ break; ++ case POWER_SUPPLY_PROP_CHARGE_COUNTER: ++ val->intval = di->charge_count; ++ break; ++ case POWER_SUPPLY_PROP_CHARGE_FULL: ++ val->intval = di->pdata->design_capacity * 1000;/* uAh */ ++ break; ++ case POWER_SUPPLY_PROP_VOLTAGE_MAX: ++ val->intval = di->voltage_max * 1000; /* uV */ ++ break; ++ case POWER_SUPPLY_PROP_CURRENT_MAX: ++ val->intval = di->current_max * 1000; /* uA */ ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static const struct power_supply_desc rk818_bat_desc = { ++ .name = "battery", ++ .type = POWER_SUPPLY_TYPE_BATTERY, ++ .properties = rk818_bat_props, ++ .num_properties = ARRAY_SIZE(rk818_bat_props), ++ .get_property = rk818_battery_get_property, ++}; ++ ++static int rk818_bat_init_power_supply(struct rk818_battery *di) ++{ ++ struct power_supply_config psy_cfg = { .drv_data = di, }; ++ ++ di->bat = devm_power_supply_register(di->dev, &rk818_bat_desc, &psy_cfg); ++ if (IS_ERR(di->bat)) { ++ dev_err(di->dev, "register bat power supply fail\n"); ++ return PTR_ERR(di->bat); ++ } ++ ++ return 0; ++} ++ ++static void rk818_bat_save_cap(struct rk818_battery *di, int cap) ++{ ++ u8 buf; ++ static u32 old_cap; ++ ++ if (cap >= di->qmax) ++ cap = di->qmax; ++ if (cap <= 0) ++ cap = 0; ++ if (old_cap == cap) ++ return; ++ ++ old_cap = cap; ++ buf = (cap >> 24) & 0xff; ++ rk818_bat_write(di, RK818_REMAIN_CAP_REG3, buf); ++ buf = (cap >> 16) & 0xff; ++ rk818_bat_write(di, RK818_REMAIN_CAP_REG2, buf); ++ buf = (cap >> 8) & 0xff; ++ rk818_bat_write(di, RK818_REMAIN_CAP_REG1, buf); ++ buf = (cap >> 0) & 0xff; ++ rk818_bat_write(di, RK818_REMAIN_CAP_REG0, buf); ++} ++ ++static int rk818_bat_get_prev_cap(struct rk818_battery *di) ++{ ++ int val = 0; ++ ++ val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG3) << 24; ++ val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG2) << 16; ++ val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG1) << 8; ++ val |= rk818_bat_read(di, RK818_REMAIN_CAP_REG0) << 0; ++ ++ return val; ++} ++ ++static void rk818_bat_save_fcc(struct rk818_battery *di, u32 fcc) ++{ ++ u8 buf; ++ ++ buf = (fcc >> 24) & 0xff; ++ rk818_bat_write(di, RK818_NEW_FCC_REG3, buf); ++ buf = (fcc >> 16) & 0xff; ++ rk818_bat_write(di, RK818_NEW_FCC_REG2, buf); ++ buf = (fcc >> 8) & 0xff; ++ rk818_bat_write(di, RK818_NEW_FCC_REG1, buf); ++ buf = (fcc >> 0) & 0xff; ++ rk818_bat_write(di, RK818_NEW_FCC_REG0, buf); ++ ++ BAT_INFO("save fcc: %d\n", fcc); ++} ++ ++static int rk818_bat_get_fcc(struct rk818_battery *di) ++{ ++ u32 fcc = 0; ++ ++ fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG3) << 24; ++ fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG2) << 16; ++ fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG1) << 8; ++ fcc |= rk818_bat_read(di, RK818_NEW_FCC_REG0) << 0; ++ ++ if (fcc < MIN_FCC) { ++ BAT_INFO("invalid fcc(%d), use design cap", fcc); ++ fcc = di->pdata->design_capacity; ++ rk818_bat_save_fcc(di, fcc); ++ } else if (fcc > di->pdata->design_qmax) { ++ BAT_INFO("invalid fcc(%d), use qmax", fcc); ++ fcc = di->pdata->design_qmax; ++ rk818_bat_save_fcc(di, fcc); ++ } ++ ++ return fcc; ++} ++ ++static void rk818_bat_init_coulomb_cap(struct rk818_battery *di, u32 capacity) ++{ ++ u8 buf; ++ u32 cap; ++ ++ cap = capacity * 2390 / DIV(di->res_div); ++ buf = (cap >> 24) & 0xff; ++ rk818_bat_write(di, RK818_GASCNT_CAL_REG3, buf); ++ buf = (cap >> 16) & 0xff; ++ rk818_bat_write(di, RK818_GASCNT_CAL_REG2, buf); ++ buf = (cap >> 8) & 0xff; ++ rk818_bat_write(di, RK818_GASCNT_CAL_REG1, buf); ++ buf = ((cap >> 0) & 0xff); ++ rk818_bat_write(di, RK818_GASCNT_CAL_REG0, buf); ++ ++ DBG("<%s>. new coulomb cap = %d\n", __func__, capacity); ++ di->remain_cap = capacity; ++ di->rsoc = rk818_bat_get_rsoc(di); ++} ++ ++static void rk818_bat_save_dsoc(struct rk818_battery *di, u8 save_soc) ++{ ++ static int last_soc = -1; ++ ++ if (last_soc != save_soc) { ++ rk818_bat_write(di, RK818_SOC_REG, save_soc); ++ last_soc = save_soc; ++ } ++} ++ ++static int rk818_bat_get_prev_dsoc(struct rk818_battery *di) ++{ ++ return rk818_bat_read(di, RK818_SOC_REG); ++} ++ ++static void rk818_bat_save_reboot_cnt(struct rk818_battery *di, u8 save_cnt) ++{ ++ rk818_bat_write(di, RK818_REBOOT_CNT_REG, save_cnt); ++} ++ ++static int rk818_bat_fb_notifier(struct notifier_block *nb, ++ unsigned long event, void *data) ++{ ++ struct rk818_battery *di; ++ struct fb_event *evdata = data; ++ ++ if (event != FB_EARLY_EVENT_BLANK && event != FB_EVENT_BLANK) ++ return NOTIFY_OK; ++ ++ di = container_of(nb, struct rk818_battery, fb_nb); ++ di->fb_blank = *(int *)evdata->data; ++ ++ return 0; ++} ++ ++static int rk818_bat_register_fb_notify(struct rk818_battery *di) ++{ ++ memset(&di->fb_nb, 0, sizeof(di->fb_nb)); ++ di->fb_nb.notifier_call = rk818_bat_fb_notifier; ++ ++ return fb_register_client(&di->fb_nb); ++} ++ ++static int rk818_bat_unregister_fb_notify(struct rk818_battery *di) ++{ ++ return fb_unregister_client(&di->fb_nb); ++} ++ ++static u8 rk818_bat_get_halt_cnt(struct rk818_battery *di) ++{ ++ return rk818_bat_read(di, RK818_HALT_CNT_REG); ++} ++ ++static void rk818_bat_inc_halt_cnt(struct rk818_battery *di) ++{ ++ u8 cnt; ++ ++ cnt = rk818_bat_read(di, RK818_HALT_CNT_REG); ++ rk818_bat_write(di, RK818_HALT_CNT_REG, ++cnt); ++} ++ ++static bool is_rk818_bat_last_halt(struct rk818_battery *di) ++{ ++ int pre_cap = rk818_bat_get_prev_cap(di); ++ int now_cap = rk818_bat_get_coulomb_cap(di); ++ ++ /* over 10%: system halt last time */ ++ if (abs(now_cap - pre_cap) > (di->fcc / 10)) { ++ rk818_bat_inc_halt_cnt(di); ++ return true; ++ } else { ++ return false; ++ } ++} ++ ++static void rk818_bat_first_pwron(struct rk818_battery *di) ++{ ++ int ocv_vol; ++ ++ rk818_bat_save_fcc(di, di->design_cap); ++ ocv_vol = rk818_bat_get_ocv_voltage(di); ++ di->fcc = rk818_bat_get_fcc(di); ++ di->nac = rk818_bat_vol_to_ocvcap(di, ocv_vol); ++ di->rsoc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); ++ di->dsoc = di->rsoc; ++ di->is_first_on = true; ++ ++ BAT_INFO("first on: dsoc=%d, rsoc=%d cap=%d, fcc=%d, ov=%d\n", ++ di->dsoc, di->rsoc, di->nac, di->fcc, ocv_vol); ++} ++ ++static void rk818_bat_not_first_pwron(struct rk818_battery *di) ++{ ++ int now_cap, pre_soc, pre_cap, ocv_cap, ocv_soc, ocv_vol; ++ ++ di->fcc = rk818_bat_get_fcc(di); ++ pre_soc = rk818_bat_get_prev_dsoc(di); ++ pre_cap = rk818_bat_get_prev_cap(di); ++ now_cap = rk818_bat_get_coulomb_cap(di); ++ di->is_halt = is_rk818_bat_last_halt(di); ++ di->halt_cnt = rk818_bat_get_halt_cnt(di); ++ di->is_initialized = is_rk818_bat_initialized(di); ++ di->is_ocv_calib = is_rk818_bat_ocv_valid(di); ++ ++ if (di->is_initialized) { ++ BAT_INFO("initialized yet..\n"); ++ goto finish; ++ } else if (di->is_halt) { ++ BAT_INFO("system halt last time... cap: pre=%d, now=%d\n", ++ pre_cap, now_cap); ++ if (now_cap < 0) ++ now_cap = 0; ++ rk818_bat_init_coulomb_cap(di, now_cap); ++ pre_cap = now_cap; ++ pre_soc = di->rsoc; ++ goto finish; ++ } else if (di->is_ocv_calib) { ++ ocv_vol = rk818_bat_get_ocv_voltage(di); ++ ocv_soc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); ++ ocv_cap = rk818_bat_vol_to_ocvcap(di, ocv_vol); ++ pre_cap = ocv_cap; ++ di->ocv_pre_dsoc = pre_soc; ++ di->ocv_new_dsoc = ocv_soc; ++ if (abs(ocv_soc - pre_soc) >= di->pdata->max_soc_offset) { ++ di->ocv_pre_dsoc = pre_soc; ++ di->ocv_new_dsoc = ocv_soc; ++ di->is_max_soc_offset = true; ++ BAT_INFO("trigger max soc offset, dsoc: %d -> %d\n", ++ pre_soc, ocv_soc); ++ pre_soc = ocv_soc; ++ } ++ BAT_INFO("OCV calib: cap=%d, rsoc=%d\n", ocv_cap, ocv_soc); ++ } else if (di->pwroff_min > 0) { ++ ocv_vol = rk818_bat_get_ocv_voltage(di); ++ ocv_soc = rk818_bat_vol_to_ocvsoc(di, ocv_vol); ++ ocv_cap = rk818_bat_vol_to_ocvcap(di, ocv_vol); ++ di->force_pre_dsoc = pre_soc; ++ di->force_new_dsoc = ocv_soc; ++ if (abs(ocv_soc - pre_soc) >= 80) { ++ di->is_force_calib = true; ++ BAT_INFO("dsoc force calib: %d -> %d\n", ++ pre_soc, ocv_soc); ++ pre_soc = ocv_soc; ++ pre_cap = ocv_cap; ++ } ++ } ++ ++finish: ++ di->dsoc = pre_soc; ++ di->nac = pre_cap; ++ if (di->nac < 0) ++ di->nac = 0; ++ ++ BAT_INFO("dsoc=%d cap=%d v=%d ov=%d rv=%d min=%d psoc=%d pcap=%d\n", ++ di->dsoc, di->nac, rk818_bat_get_avg_voltage(di), ++ rk818_bat_get_ocv_voltage(di), rk818_bat_get_relax_voltage(di), ++ di->pwroff_min, rk818_bat_get_prev_dsoc(di), ++ rk818_bat_get_prev_cap(di)); ++} ++ ++static bool rk818_bat_ocv_sw_reset(struct rk818_battery *di) ++{ ++ u8 buf; ++ ++ buf = rk818_bat_read(di, RK818_MISC_MARK_REG); ++ if (((buf & FG_RESET_LATE) && di->pwroff_min >= 30) || ++ (buf & FG_RESET_NOW)) { ++ buf &= ~FG_RESET_LATE; ++ buf &= ~FG_RESET_NOW; ++ rk818_bat_write(di, RK818_MISC_MARK_REG, buf); ++ BAT_INFO("manual reset fuel gauge\n"); ++ return true; ++ } else { ++ return false; ++ } ++} ++ ++static void rk818_bat_init_rsoc(struct rk818_battery *di) ++{ ++ di->is_first_power_on = is_rk818_bat_first_pwron(di); ++ di->is_sw_reset = rk818_bat_ocv_sw_reset(di); ++ di->pwroff_min = rk818_bat_get_pwroff_min(di); ++ ++ if (di->is_first_power_on || di->is_sw_reset) ++ rk818_bat_first_pwron(di); ++ else ++ rk818_bat_not_first_pwron(di); ++} ++ ++static u8 rk818_bat_get_chrg_status(struct rk818_battery *di) ++{ ++ u8 status; ++ ++ status = rk818_bat_read(di, RK818_SUP_STS_REG) & CHRG_STATUS_MSK; ++ switch (status) { ++ case CHARGE_OFF: ++ DBG("CHARGE-OFF ...\n"); ++ break; ++ case DEAD_CHARGE: ++ BAT_INFO("DEAD CHARGE...\n"); ++ break; ++ case TRICKLE_CHARGE: ++ BAT_INFO("TRICKLE CHARGE...\n "); ++ break; ++ case CC_OR_CV: ++ DBG("CC or CV...\n"); ++ break; ++ case CHARGE_FINISH: ++ DBG("CHARGE FINISH...\n"); ++ break; ++ case USB_OVER_VOL: ++ BAT_INFO("USB OVER VOL...\n"); ++ break; ++ case BAT_TMP_ERR: ++ BAT_INFO("BAT TMP ERROR...\n"); ++ break; ++ case TIMER_ERR: ++ BAT_INFO("TIMER ERROR...\n"); ++ break; ++ case USB_EXIST: ++ BAT_INFO("USB EXIST...\n"); ++ break; ++ case USB_EFF: ++ BAT_INFO("USB EFF...\n"); ++ break; ++ default: ++ return -EINVAL; ++ } ++ ++ return status; ++} ++ ++static u8 rk818_bat_parse_fb_temperature(struct rk818_battery *di) ++{ ++ u8 reg; ++ int index, fb_temp; ++ ++ reg = DEFAULT_FB_TEMP; ++ fb_temp = di->pdata->fb_temp; ++ for (index = 0; index < ARRAY_SIZE(feedback_temp_array); index++) { ++ if (fb_temp < feedback_temp_array[index]) ++ break; ++ reg = (index << FB_TEMP_SHIFT); ++ } ++ ++ return reg; ++} ++ ++static u8 rk818_bat_parse_finish_ma(struct rk818_battery *di, int fcc) ++{ ++ u8 ma; ++ ++ if (di->pdata->sample_res == SAMPLE_RES_10MR) ++ ma = FINISH_100MA; ++ else if (fcc > 5000) ++ ma = FINISH_250MA; ++ else if (fcc >= 4000) ++ ma = FINISH_200MA; ++ else if (fcc >= 3000) ++ ma = FINISH_150MA; ++ else ++ ma = FINISH_100MA; ++ ++ return ma; ++} ++ ++static void rk818_bat_init_chrg_config(struct rk818_battery *di) ++{ ++ u8 usb_ctrl, chrg_ctrl2, chrg_ctrl3; ++ u8 thermal, ggcon, finish_ma, fb_temp; ++ ++ finish_ma = rk818_bat_parse_finish_ma(di, di->fcc); ++ fb_temp = rk818_bat_parse_fb_temperature(di); ++ ++ ggcon = rk818_bat_read(di, RK818_GGCON_REG); ++ thermal = rk818_bat_read(di, RK818_THERMAL_REG); ++ usb_ctrl = rk818_bat_read(di, RK818_USB_CTRL_REG); ++ chrg_ctrl2 = rk818_bat_read(di, RK818_CHRG_CTRL_REG2); ++ chrg_ctrl3 = rk818_bat_read(di, RK818_CHRG_CTRL_REG3); ++ ++ /* set charge finish current */ ++ chrg_ctrl3 |= CHRG_TERM_DIG_SIGNAL; ++ chrg_ctrl2 &= ~FINISH_CUR_MSK; ++ chrg_ctrl2 |= finish_ma; ++ ++ /* disable cccv mode */ ++ chrg_ctrl3 &= ~CHRG_TIMER_CCCV_EN; ++ ++ /* set feed back temperature */ ++ if (di->pdata->fb_temp) ++ usb_ctrl |= CHRG_CT_EN; ++ else ++ usb_ctrl &= ~CHRG_CT_EN; ++ thermal &= ~FB_TEMP_MSK; ++ thermal |= fb_temp; ++ ++ /* adc current mode */ ++ ggcon |= ADC_CUR_MODE; ++ ++ rk818_bat_write(di, RK818_GGCON_REG, ggcon); ++ rk818_bat_write(di, RK818_THERMAL_REG, thermal); ++ rk818_bat_write(di, RK818_USB_CTRL_REG, usb_ctrl); ++ rk818_bat_write(di, RK818_CHRG_CTRL_REG2, chrg_ctrl2); ++ rk818_bat_write(di, RK818_CHRG_CTRL_REG3, chrg_ctrl3); ++} ++ ++static void rk818_bat_init_coffset(struct rk818_battery *di) ++{ ++ int coffset, ioffset; ++ ++ ioffset = rk818_bat_get_ioffset(di); ++ di->poffset = rk818_bat_read(di, RK818_POFFSET_REG); ++ if (!di->poffset) ++ di->poffset = DEFAULT_POFFSET; ++ ++ coffset = di->poffset + ioffset; ++ if (coffset < INVALID_COFFSET_MIN || coffset > INVALID_COFFSET_MAX) ++ coffset = DEFAULT_COFFSET; ++ ++ rk818_bat_set_coffset(di, coffset); ++ ++ DBG("<%s>. offset: p=0x%x, i=0x%x, c=0x%x\n", ++ __func__, di->poffset, ioffset, rk818_bat_get_coffset(di)); ++} ++ ++static void rk818_bat_caltimer_isr(struct timer_list *t) ++{ ++ struct rk818_battery *di = from_timer(di, t, caltimer); ++ ++ mod_timer(&di->caltimer, jiffies + MINUTE(8) * HZ); ++ queue_delayed_work(di->bat_monitor_wq, &di->calib_delay_work, ++ msecs_to_jiffies(10)); ++} ++ ++static void rk818_bat_internal_calib(struct work_struct *work) ++{ ++ int ioffset, poffset; ++ struct rk818_battery *di = container_of(work, ++ struct rk818_battery, calib_delay_work.work); ++ ++ /* calib coffset */ ++ poffset = rk818_bat_read(di, RK818_POFFSET_REG); ++ if (poffset) ++ di->poffset = poffset; ++ else ++ di->poffset = DEFAULT_POFFSET; ++ ++ ioffset = rk818_bat_get_ioffset(di); ++ rk818_bat_set_coffset(di, ioffset + di->poffset); ++ ++ /* calib voltage kb */ ++ rk818_bat_init_voltage_kb(di); ++ BAT_INFO("caltimer: ioffset=0x%x, coffset=0x%x, poffset=%d\n", ++ ioffset, rk818_bat_get_coffset(di), di->poffset); ++} ++ ++static void rk818_bat_init_caltimer(struct rk818_battery *di) ++{ ++ timer_setup(&di->caltimer, rk818_bat_caltimer_isr, 0); ++ di->caltimer.expires = jiffies + MINUTE(8) * HZ; ++ add_timer(&di->caltimer); ++ INIT_DELAYED_WORK(&di->calib_delay_work, rk818_bat_internal_calib); ++} ++ ++static void rk818_bat_init_zero_table(struct rk818_battery *di) ++{ ++ int i, diff, min, max; ++ size_t ocv_size, length; ++ ++ ocv_size = di->pdata->ocv_size; ++ length = sizeof(di->pdata->zero_table) * ocv_size; ++ di->pdata->zero_table = ++ devm_kzalloc(di->dev, length, GFP_KERNEL); ++ if (!di->pdata->zero_table) { ++ di->pdata->zero_table = di->pdata->ocv_table; ++ dev_err(di->dev, "malloc zero table fail\n"); ++ return; ++ } ++ ++ min = di->pdata->pwroff_vol, ++ max = di->pdata->ocv_table[ocv_size - 4]; ++ diff = (max - min) / DIV(ocv_size - 1); ++ for (i = 0; i < ocv_size; i++) ++ di->pdata->zero_table[i] = min + (i * diff); ++ ++ for (i = 0; i < ocv_size; i++) ++ DBG("zero[%d] = %d\n", i, di->pdata->zero_table[i]); ++ ++ for (i = 0; i < ocv_size; i++) ++ DBG("ocv[%d] = %d\n", i, di->pdata->ocv_table[i]); ++} ++ ++static void rk818_bat_calc_sm_linek(struct rk818_battery *di) ++{ ++ int linek, current_avg; ++ u8 diff, delta; ++ ++ delta = abs(di->dsoc - di->rsoc); ++ diff = delta * 3;/* speed:3/4 */ ++ current_avg = rk818_bat_get_avg_current(di); ++ if (current_avg >= 0) { ++ if (di->dsoc < di->rsoc) ++ linek = 1000 * (delta + diff) / DIV(diff); ++ else if (di->dsoc > di->rsoc) ++ linek = 1000 * diff / DIV(delta + diff); ++ else ++ linek = 1000; ++ di->dbg_meet_soc = (di->dsoc >= di->rsoc) ? ++ (di->dsoc + diff) : (di->rsoc + diff); ++ } else { ++ if (di->dsoc < di->rsoc) ++ linek = -1000 * diff / DIV(delta + diff); ++ else if (di->dsoc > di->rsoc) ++ linek = -1000 * (delta + diff) / DIV(diff); ++ else ++ linek = -1000; ++ di->dbg_meet_soc = (di->dsoc >= di->rsoc) ? ++ (di->dsoc - diff) : (di->rsoc - diff); ++ } ++ ++ di->sm_linek = linek; ++ di->sm_remain_cap = di->remain_cap; ++ di->dbg_calc_dsoc = di->dsoc; ++ di->dbg_calc_rsoc = di->rsoc; ++ ++ DBG("<%s>.diff=%d, k=%d, cur=%d\n", __func__, diff, linek, current_avg); ++} ++ ++static void rk818_bat_calc_zero_linek(struct rk818_battery *di) ++{ ++ int dead_voltage, ocv_voltage; ++ int voltage_avg, current_avg, vsys; ++ int ocv_cap, dead_cap, xsoc; ++ int ocv_soc, dead_soc; ++ int pwroff_vol; ++ int i, cnt, vol_old, vol_now; ++ int org_linek = 0, min_gap_xsoc; ++ ++ if ((abs(di->current_avg) < 500) && (di->dsoc > 10)) ++ pwroff_vol = di->pdata->pwroff_vol + 50; ++ else ++ pwroff_vol = di->pdata->pwroff_vol; ++ ++ do { ++ vol_old = rk818_bat_get_avg_voltage(di); ++ msleep(100); ++ vol_now = rk818_bat_get_avg_voltage(di); ++ cnt++; ++ } while ((vol_old == vol_now) && (cnt < 11)); ++ ++ voltage_avg = 0; ++ for (i = 0; i < 10; i++) { ++ voltage_avg += rk818_bat_get_avg_voltage(di); ++ msleep(100); ++ } ++ ++ /* calc estimate ocv voltage */ ++ voltage_avg /= 10; ++ current_avg = rk818_bat_get_avg_current(di); ++ vsys = voltage_avg + (current_avg * DEF_PWRPATH_RES) / 1000; ++ ++ DBG("ZERO0: shtd_vol: org = %d, now = %d, zero_reserve_dsoc = %d\n", ++ di->pdata->pwroff_vol, pwroff_vol, di->pdata->zero_reserve_dsoc); ++ ++ dead_voltage = pwroff_vol - current_avg * ++ (di->bat_res + DEF_PWRPATH_RES) / 1000; ++ ocv_voltage = voltage_avg - (current_avg * di->bat_res) / 1000; ++ DBG("ZERO0: dead_voltage(shtd) = %d, ocv_voltage(now) = %d\n", ++ dead_voltage, ocv_voltage); ++ ++ /* calc estimate soc and cap */ ++ dead_soc = rk818_bat_vol_to_zerosoc(di, dead_voltage); ++ dead_cap = rk818_bat_vol_to_zerocap(di, dead_voltage); ++ DBG("ZERO0: dead_soc = %d, dead_cap = %d\n", ++ dead_soc, dead_cap); ++ ++ ocv_soc = rk818_bat_vol_to_zerosoc(di, ocv_voltage); ++ ocv_cap = rk818_bat_vol_to_zerocap(di, ocv_voltage); ++ DBG("ZERO0: ocv_soc = %d, ocv_cap = %d\n", ++ ocv_soc, ocv_cap); ++ ++ /* xsoc: available rsoc */ ++ xsoc = ocv_soc - dead_soc; ++ ++ /* min_gap_xsoc: reserve xsoc */ ++ if (abs(current_avg) > ZERO_LOAD_LVL1) ++ min_gap_xsoc = ZERO_GAP_XSOC3; ++ else if (abs(current_avg) > ZERO_LOAD_LVL2) ++ min_gap_xsoc = ZERO_GAP_XSOC2; ++ else ++ min_gap_xsoc = ZERO_GAP_XSOC1; ++ ++ if ((xsoc <= 30) && (di->dsoc >= di->pdata->zero_reserve_dsoc)) ++ min_gap_xsoc = min_gap_xsoc + ZERO_GAP_CALIB; ++ ++ di->zero_remain_cap = di->remain_cap; ++ di->zero_timeout_cnt = 0; ++ if ((di->dsoc <= 1) && (xsoc > 0)) { ++ di->zero_linek = 400; ++ di->zero_drop_sec = 0; ++ } else if (xsoc >= 0) { ++ di->zero_drop_sec = 0; ++ di->zero_linek = (di->zero_dsoc + xsoc / 2) / DIV(xsoc); ++ org_linek = di->zero_linek; ++ /* battery energy mode to use up voltage */ ++ if ((di->pdata->energy_mode) && ++ (xsoc - di->dsoc >= ZERO_GAP_XSOC3) && ++ (di->dsoc <= 10) && (di->zero_linek < 300)) { ++ di->zero_linek = 300; ++ DBG("ZERO-new: zero_linek adjust step0...\n"); ++ /* reserve enough power yet, slow down any way */ ++ } else if ((xsoc - di->dsoc >= min_gap_xsoc) || ++ ((xsoc - di->dsoc >= ZERO_GAP_XSOC2) && ++ (di->dsoc <= 10) && (xsoc > 15))) { ++ if (xsoc <= 20 && ++ di->dsoc >= di->pdata->zero_reserve_dsoc) ++ di->zero_linek = 1200; ++ else if (xsoc - di->dsoc >= 2 * min_gap_xsoc) ++ di->zero_linek = 400; ++ else if (xsoc - di->dsoc >= 3 + min_gap_xsoc) ++ di->zero_linek = 600; ++ else ++ di->zero_linek = 800; ++ DBG("ZERO-new: zero_linek adjust step1...\n"); ++ /* control zero mode beginning enter */ ++ } else if ((di->zero_linek > 1800) && (di->dsoc > 70)) { ++ di->zero_linek = 1800; ++ DBG("ZERO-new: zero_linek adjust step2...\n"); ++ /* dsoc close to xsoc: it must reserve power */ ++ } else if ((di->zero_linek > 1000) && (di->zero_linek < 1200)) { ++ di->zero_linek = 1200; ++ DBG("ZERO-new: zero_linek adjust step3...\n"); ++ /* dsoc[5~15], dsoc < xsoc */ ++ } else if ((di->dsoc <= 15 && di->dsoc > 5) && ++ (di->zero_linek <= 1200)) { ++ /* slow down */ ++ if (xsoc - di->dsoc >= min_gap_xsoc) ++ di->zero_linek = 800; ++ /* reserve power */ ++ else ++ di->zero_linek = 1200; ++ DBG("ZERO-new: zero_linek adjust step4...\n"); ++ /* dsoc[5, 100], dsoc < xsoc */ ++ } else if ((di->zero_linek < 1000) && (di->dsoc >= 5)) { ++ if ((xsoc - di->dsoc) < min_gap_xsoc) { ++ /* reserve power */ ++ di->zero_linek = 1200; ++ } else { ++ if (abs(di->current_avg) > 500)/* heavy */ ++ di->zero_linek = 900; ++ else ++ di->zero_linek = 1000; ++ } ++ DBG("ZERO-new: zero_linek adjust step5...\n"); ++ /* dsoc[0~5], dsoc < xsoc */ ++ } else if ((di->zero_linek < 1000) && (di->dsoc <= 5)) { ++ if ((xsoc - di->dsoc) <= 3) ++ di->zero_linek = 1200; ++ else ++ di->zero_linek = 800; ++ DBG("ZERO-new: zero_linek adjust step6...\n"); ++ } ++ } else { ++ /* xsoc < 0 */ ++ di->zero_linek = 1000; ++ if (!di->zero_drop_sec) ++ di->zero_drop_sec = get_boot_sec(); ++ if (base2sec(di->zero_drop_sec) >= WAIT_DSOC_DROP_SEC) { ++ DBG("ZERO0: t=%lu\n", base2sec(di->zero_drop_sec)); ++ di->zero_drop_sec = 0; ++ di->dsoc--; ++ di->zero_dsoc = (di->dsoc + 1) * 1000 - ++ MIN_ACCURACY; ++ } ++ } ++ ++ if (voltage_avg < pwroff_vol - 70) { ++ if (!di->shtd_drop_sec) ++ di->shtd_drop_sec = get_boot_sec(); ++ if (base2sec(di->shtd_drop_sec) > WAIT_SHTD_DROP_SEC) { ++ BAT_INFO("voltage extreme low...soc:%d->0\n", di->dsoc); ++ di->shtd_drop_sec = 0; ++ di->dsoc = 0; ++ } ++ } else { ++ di->shtd_drop_sec = 0; ++ } ++ ++ DBG("ZERO-new: org_linek=%d, zero_linek=%d, dsoc=%d, Xsoc=%d, " ++ "rsoc=%d, gap=%d, v=%d, vsys=%d\n" ++ "ZERO-new: di->zero_dsoc=%d, zero_remain_cap=%d, zero_drop=%ld, " ++ "sht_drop=%ld\n\n", ++ org_linek, di->zero_linek, di->dsoc, xsoc, di->rsoc, ++ min_gap_xsoc, voltage_avg, vsys, di->zero_dsoc, di->zero_remain_cap, ++ base2sec(di->zero_drop_sec), base2sec(di->shtd_drop_sec)); ++} ++ ++static void rk818_bat_finish_algo_prepare(struct rk818_battery *di) ++{ ++ di->finish_base = get_boot_sec(); ++ if (!di->finish_base) ++ di->finish_base = 1; ++} ++ ++static void rk818_bat_smooth_algo_prepare(struct rk818_battery *di) ++{ ++ int tmp_soc; ++ ++ tmp_soc = di->sm_chrg_dsoc / 1000; ++ if (tmp_soc != di->dsoc) ++ di->sm_chrg_dsoc = di->dsoc * 1000; ++ ++ tmp_soc = di->sm_dischrg_dsoc / 1000; ++ if (tmp_soc != di->dsoc) ++ di->sm_dischrg_dsoc = ++ (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ ++ DBG("<%s>. tmp_soc=%d, dsoc=%d, dsoc:sm_dischrg=%d, sm_chrg=%d\n", ++ __func__, tmp_soc, di->dsoc, di->sm_dischrg_dsoc, di->sm_chrg_dsoc); ++ ++ rk818_bat_calc_sm_linek(di); ++} ++ ++static void rk818_bat_zero_algo_prepare(struct rk818_battery *di) ++{ ++ int tmp_dsoc; ++ ++ di->zero_timeout_cnt = 0; ++ tmp_dsoc = di->zero_dsoc / 1000; ++ if (tmp_dsoc != di->dsoc) ++ di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ ++ DBG("<%s>. first calc, reinit linek\n", __func__); ++ ++ rk818_bat_calc_zero_linek(di); ++} ++ ++static void rk818_bat_calc_zero_algorithm(struct rk818_battery *di) ++{ ++ int tmp_soc = 0, sm_delta_dsoc = 0; ++ ++ tmp_soc = di->zero_dsoc / 1000; ++ if (tmp_soc == di->dsoc) ++ goto out; ++ ++ DBG("<%s>. enter: dsoc=%d, rsoc=%d\n", __func__, di->dsoc, di->rsoc); ++ /* when discharge slow down, take sm chrg into calc */ ++ if (di->dsoc < di->rsoc) { ++ /* take sm charge rest into calc */ ++ tmp_soc = di->sm_chrg_dsoc / 1000; ++ if (tmp_soc == di->dsoc) { ++ sm_delta_dsoc = di->sm_chrg_dsoc - di->dsoc * 1000; ++ di->sm_chrg_dsoc = di->dsoc * 1000; ++ di->zero_dsoc += sm_delta_dsoc; ++ DBG("ZERO1: take sm chrg,delta=%d\n", sm_delta_dsoc); ++ } ++ } ++ ++ /* when discharge speed up, take sm dischrg into calc */ ++ if (di->dsoc > di->rsoc) { ++ /* take sm discharge rest into calc */ ++ tmp_soc = di->sm_dischrg_dsoc / 1000; ++ if (tmp_soc == di->dsoc) { ++ sm_delta_dsoc = di->sm_dischrg_dsoc - ++ ((di->dsoc + 1) * 1000 - MIN_ACCURACY); ++ di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - ++ MIN_ACCURACY; ++ di->zero_dsoc += sm_delta_dsoc; ++ DBG("ZERO1: take sm dischrg,delta=%d\n", sm_delta_dsoc); ++ } ++ } ++ ++ /* check overflow */ ++ if (di->zero_dsoc > (di->dsoc + 1) * 1000 - MIN_ACCURACY) { ++ DBG("ZERO1: zero dsoc overflow: %d\n", di->zero_dsoc); ++ di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ } ++ ++ /* check new dsoc */ ++ tmp_soc = di->zero_dsoc / 1000; ++ if (tmp_soc != di->dsoc) { ++ /* avoid dsoc jump when heavy load */ ++ if ((di->dsoc - tmp_soc) > 1) { ++ di->dsoc--; ++ di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ DBG("ZERO1: heavy load...\n"); ++ } else { ++ di->dsoc = tmp_soc; ++ } ++ di->zero_drop_sec = 0; ++ } ++ ++out: ++ DBG("ZERO1: zero_dsoc(Y0)=%d, dsoc=%d, rsoc=%d, tmp_soc=%d\n", ++ di->zero_dsoc, di->dsoc, di->rsoc, tmp_soc); ++ DBG("ZERO1: sm_dischrg_dsoc=%d, sm_chrg_dsoc=%d\n", ++ di->sm_dischrg_dsoc, di->sm_chrg_dsoc); ++} ++ ++static void rk818_bat_zero_algorithm(struct rk818_battery *di) ++{ ++ int delta_cap = 0, delta_soc = 0; ++ ++ di->zero_timeout_cnt++; ++ delta_cap = di->zero_remain_cap - di->remain_cap; ++ delta_soc = di->zero_linek * (delta_cap * 100) / DIV(di->fcc); ++ ++ DBG("ZERO1: zero_linek=%d, zero_dsoc(Y0)=%d, dsoc=%d, rsoc=%d\n" ++ "ZERO1: delta_soc(X0)=%d, delta_cap=%d, zero_remain_cap = %d\n" ++ "ZERO1: timeout_cnt=%d, sm_dischrg=%d, sm_chrg=%d\n\n", ++ di->zero_linek, di->zero_dsoc, di->dsoc, di->rsoc, ++ delta_soc, delta_cap, di->zero_remain_cap, ++ di->zero_timeout_cnt, di->sm_dischrg_dsoc, di->sm_chrg_dsoc); ++ ++ if ((delta_soc >= MIN_ZERO_DSOC_ACCURACY) || ++ (di->zero_timeout_cnt > MIN_ZERO_OVERCNT) || ++ (di->zero_linek == 0)) { ++ DBG("ZERO1:--------- enter calc -----------\n"); ++ di->zero_timeout_cnt = 0; ++ di->zero_dsoc -= delta_soc; ++ rk818_bat_calc_zero_algorithm(di); ++ rk818_bat_calc_zero_linek(di); ++ } ++} ++ ++static void rk818_bat_dump_time_table(struct rk818_battery *di) ++{ ++ u8 i; ++ static int old_index; ++ static int old_min; ++ int mod = di->dsoc % 10; ++ int index = di->dsoc / 10; ++ u32 time; ++ ++ if (rk818_bat_chrg_online(di)) ++ time = base2min(di->plug_in_base); ++ else ++ time = base2min(di->plug_out_base); ++ ++ if ((mod == 0) && (index > 0) && (old_index != index)) { ++ di->dbg_chrg_min[index - 1] = time - old_min; ++ old_min = time; ++ old_index = index; ++ } ++ ++ for (i = 1; i < 11; i++) ++ DBG("Time[%d]=%d, ", (i * 10), di->dbg_chrg_min[i - 1]); ++ DBG("\n"); ++} ++ ++static void rk818_bat_debug_info(struct rk818_battery *di) ++{ ++ u8 sup_tst, ggcon, ggsts, vb_mod, ts_ctrl, reboot_cnt; ++ u8 usb_ctrl, chrg_ctrl1, thermal; ++ u8 int_sts1, int_sts2; ++ u8 int_msk1, int_msk2; ++ u8 chrg_ctrl2, chrg_ctrl3, rtc, misc, dcdc_en; ++ char *work_mode[] = {"ZERO", "FINISH", "UN", "UN", "SMOOTH"}; ++ char *bat_mode[] = {"BAT", "VIRTUAL"}; ++ ++ if (rk818_bat_chrg_online(di)) ++ di->plug_out_base = get_boot_sec(); ++ else ++ di->plug_in_base = get_boot_sec(); ++ ++ rk818_bat_dump_time_table(di); ++ ++ if (!dbg_enable) ++ return; ++ ++ ts_ctrl = rk818_bat_read(di, RK818_TS_CTRL_REG); ++ misc = rk818_bat_read(di, RK818_MISC_MARK_REG); ++ ggcon = rk818_bat_read(di, RK818_GGCON_REG); ++ ggsts = rk818_bat_read(di, RK818_GGSTS_REG); ++ sup_tst = rk818_bat_read(di, RK818_SUP_STS_REG); ++ vb_mod = rk818_bat_read(di, RK818_VB_MON_REG); ++ usb_ctrl = rk818_bat_read(di, RK818_USB_CTRL_REG); ++ chrg_ctrl1 = rk818_bat_read(di, RK818_CHRG_CTRL_REG1); ++ chrg_ctrl2 = rk818_bat_read(di, RK818_CHRG_CTRL_REG2); ++ chrg_ctrl3 = rk818_bat_read(di, RK818_CHRG_CTRL_REG3); ++ rtc = rk818_bat_read(di, 0); ++ thermal = rk818_bat_read(di, RK818_THERMAL_REG); ++ int_sts1 = rk818_bat_read(di, RK818_INT_STS_REG1); ++ int_sts2 = rk818_bat_read(di, RK818_INT_STS_REG2); ++ int_msk1 = rk818_bat_read(di, RK818_INT_STS_MSK_REG1); ++ int_msk2 = rk818_bat_read(di, RK818_INT_STS_MSK_REG2); ++ dcdc_en = rk818_bat_read(di, RK818_DCDC_EN_REG); ++ reboot_cnt = rk818_bat_read(di, RK818_REBOOT_CNT_REG); ++ ++ DBG("\n------- DEBUG REGS, [Ver: %s] -------------------\n" ++ "GGCON=0x%2x, GGSTS=0x%2x, RTC=0x%2x, DCDC_EN2=0x%2x\n" ++ "SUP_STS= 0x%2x, VB_MOD=0x%2x, USB_CTRL=0x%2x\n" ++ "THERMAL=0x%2x, MISC_MARK=0x%2x, TS_CTRL=0x%2x\n" ++ "CHRG_CTRL:REG1=0x%2x, REG2=0x%2x, REG3=0x%2x\n" ++ "INT_STS: REG1=0x%2x, REG2=0x%2x\n" ++ "INT_MSK: REG1=0x%2x, REG2=0x%2x\n", ++ DRIVER_VERSION, ggcon, ggsts, rtc, dcdc_en, ++ sup_tst, vb_mod, usb_ctrl, ++ thermal, misc, ts_ctrl, ++ chrg_ctrl1, chrg_ctrl2, chrg_ctrl3, ++ int_sts1, int_sts2, int_msk1, int_msk2 ++ ); ++ ++ DBG("###############################################################\n" ++ "Dsoc=%d, Rsoc=%d, Vavg=%d, Iavg=%d, Cap=%d, Fcc=%d, d=%d\n" ++ "K=%d, Mode=%s, Oldcap=%d, Is=%d, Ip=%d, Vs=%d\n" ++ "fb_temp=%d, bat_temp=%d, sample_res=%d, USB=%d, DC=%d\n" ++ "off:i=0x%x, c=0x%x, p=%d, Rbat=%d, age_ocv_cap=%d, fb=%d, hot=%d\n" ++ "adp:finish=%lu, boot_min=%lu, sleep_min=%lu, adc=%d, Vsys=%d\n" ++ "bat:%s, meet: soc=%d, calc: dsoc=%d, rsoc=%d, Vocv=%d\n" ++ "pwr: dsoc=%d, rsoc=%d, vol=%d, halt: st=%d, cnt=%d, reboot=%d\n" ++ "ocv_c=%d: %d -> %d; max_c=%d: %d -> %d; force_c=%d: %d -> %d\n" ++ "min=%d, init=%d, sw=%d, below0=%d, first=%d, changed=%d\n" ++ "###############################################################\n", ++ di->dsoc, di->rsoc, di->voltage_avg, di->current_avg, ++ di->remain_cap, di->fcc, di->rsoc - di->dsoc, ++ di->sm_linek, work_mode[di->work_mode], di->sm_remain_cap, ++ di->res_div * chrg_cur_sel_array[chrg_ctrl1 & 0x0f], ++ chrg_cur_input_array[usb_ctrl & 0x0f], ++ chrg_vol_sel_array[(chrg_ctrl1 & 0x70) >> 4], ++ feedback_temp_array[(thermal & 0x0c) >> 2], di->temperature, ++ di->pdata->sample_res, di->usb_in, di->ac_in, ++ rk818_bat_get_ioffset(di), ++ rk818_bat_get_coffset(di), di->poffset, di->bat_res, ++ di->age_adjust_cap, di->fb_blank, !!(thermal & HOTDIE_STS), ++ base2min(di->finish_base), ++ base2min(di->boot_base), di->sleep_sum_sec / 60, ++ di->adc_allow_update, ++ di->voltage_avg + di->current_avg * DEF_PWRPATH_RES / 1000, ++ bat_mode[di->pdata->bat_mode], di->dbg_meet_soc, di->dbg_calc_dsoc, ++ di->dbg_calc_rsoc, di->voltage_ocv, di->dbg_pwr_dsoc, ++ di->dbg_pwr_rsoc, di->dbg_pwr_vol, di->is_halt, di->halt_cnt, ++ reboot_cnt, di->is_ocv_calib, di->ocv_pre_dsoc, di->ocv_new_dsoc, ++ di->is_max_soc_offset, di->max_pre_dsoc, di->max_new_dsoc, ++ di->is_force_calib, di->force_pre_dsoc, di->force_new_dsoc, ++ di->pwroff_min, di->is_initialized, di->is_sw_reset, ++ di->dbg_cap_low0, di->is_first_on, di->last_dsoc ++ ); ++} ++ ++static void rk818_bat_init_capacity(struct rk818_battery *di, u32 cap) ++{ ++ int delta_cap; ++ ++ delta_cap = cap - di->remain_cap; ++ if (!delta_cap) ++ return; ++ ++ di->age_adjust_cap += delta_cap; ++ rk818_bat_init_coulomb_cap(di, cap); ++ rk818_bat_smooth_algo_prepare(di); ++ rk818_bat_zero_algo_prepare(di); ++} ++ ++static void rk818_bat_update_age_fcc(struct rk818_battery *di) ++{ ++ int fcc, remain_cap, age_keep_min, lock_fcc; ++ ++ lock_fcc = rk818_bat_get_coulomb_cap(di); ++ remain_cap = lock_fcc - di->age_ocv_cap - di->age_adjust_cap; ++ age_keep_min = base2min(di->age_keep_sec); ++ ++ DBG("%s: lock_fcc=%d, age_ocv_cap=%d, age_adjust_cap=%d, remain_cap=%d," ++ "age_allow_update=%d, age_keep_min=%d\n", ++ __func__, lock_fcc, di->age_ocv_cap, di->age_adjust_cap, remain_cap, ++ di->age_allow_update, age_keep_min); ++ ++ if ((di->chrg_status == CHARGE_FINISH) && (di->age_allow_update) && ++ (age_keep_min < 1200)) { ++ di->age_allow_update = false; ++ fcc = remain_cap * 100 / DIV(100 - di->age_ocv_soc); ++ BAT_INFO("lock_fcc=%d, calc_cap=%d, age: soc=%d, cap=%d, " ++ "level=%d, fcc:%d->%d?\n", ++ lock_fcc, remain_cap, di->age_ocv_soc, ++ di->age_ocv_cap, di->age_level, di->fcc, fcc); ++ ++ if ((fcc < di->qmax) && (fcc > MIN_FCC)) { ++ BAT_INFO("fcc:%d->%d!\n", di->fcc, fcc); ++ di->fcc = fcc; ++ rk818_bat_init_capacity(di, di->fcc); ++ rk818_bat_save_fcc(di, di->fcc); ++ rk818_bat_save_age_level(di, di->age_level); ++ } ++ } ++} ++ ++static void rk818_bat_wait_finish_sig(struct rk818_battery *di) ++{ ++ int chrg_finish_vol = di->pdata->max_chrg_voltage; ++ ++ if (!rk818_bat_chrg_online(di)) ++ return; ++ ++ if ((di->chrg_status == CHARGE_FINISH) && (di->adc_allow_update) && ++ (di->voltage_avg > chrg_finish_vol - 150)) { ++ rk818_bat_update_age_fcc(di); ++ if (rk818_bat_adc_calib(di)) ++ di->adc_allow_update = false; ++ } ++} ++ ++static void rk818_bat_finish_algorithm(struct rk818_battery *di) ++{ ++ unsigned long finish_sec, soc_sec; ++ int plus_soc, finish_current, rest = 0; ++ ++ /* rsoc */ ++ if ((di->remain_cap != di->fcc) && ++ (rk818_bat_get_chrg_status(di) == CHARGE_FINISH)) { ++ di->age_adjust_cap += (di->fcc - di->remain_cap); ++ rk818_bat_init_coulomb_cap(di, di->fcc); ++ } ++ ++ /* dsoc */ ++ if (di->dsoc < 100) { ++ if (!di->finish_base) ++ di->finish_base = get_boot_sec(); ++ finish_current = (di->rsoc - di->dsoc) > FINISH_MAX_SOC_DELAY ? ++ FINISH_CHRG_CUR2 : FINISH_CHRG_CUR1; ++ finish_sec = base2sec(di->finish_base); ++ soc_sec = di->fcc * 3600 / 100 / DIV(finish_current); ++ plus_soc = finish_sec / DIV(soc_sec); ++ if (finish_sec > soc_sec) { ++ rest = finish_sec % soc_sec; ++ di->dsoc += plus_soc; ++ di->finish_base = get_boot_sec(); ++ if (di->finish_base > rest) ++ di->finish_base = get_boot_sec() - rest; ++ } ++ DBG("<%s>.CHARGE_FINISH:dsoc<100,dsoc=%d\n" ++ "soc_time=%lu, sec_finish=%lu, plus_soc=%d, rest=%d\n", ++ __func__, di->dsoc, soc_sec, finish_sec, plus_soc, rest); ++ } ++} ++ ++static void rk818_bat_calc_smooth_dischrg(struct rk818_battery *di) ++{ ++ int tmp_soc = 0, sm_delta_dsoc = 0, zero_delta_dsoc = 0; ++ ++ tmp_soc = di->sm_dischrg_dsoc / 1000; ++ if (tmp_soc == di->dsoc) ++ goto out; ++ ++ DBG("<%s>. enter: dsoc=%d, rsoc=%d\n", __func__, di->dsoc, di->rsoc); ++ /* when dischrge slow down, take sm charge rest into calc */ ++ if (di->dsoc < di->rsoc) { ++ tmp_soc = di->sm_chrg_dsoc / 1000; ++ if (tmp_soc == di->dsoc) { ++ sm_delta_dsoc = di->sm_chrg_dsoc - di->dsoc * 1000; ++ di->sm_chrg_dsoc = di->dsoc * 1000; ++ di->sm_dischrg_dsoc += sm_delta_dsoc; ++ DBG("<%s>. take sm dischrg, delta=%d\n", ++ __func__, sm_delta_dsoc); ++ } ++ } ++ ++ /* when discharge speed up, take zero discharge rest into calc */ ++ if (di->dsoc > di->rsoc) { ++ tmp_soc = di->zero_dsoc / 1000; ++ if (tmp_soc == di->dsoc) { ++ zero_delta_dsoc = di->zero_dsoc - ((di->dsoc + 1) * ++ 1000 - MIN_ACCURACY); ++ di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ di->sm_dischrg_dsoc += zero_delta_dsoc; ++ DBG("<%s>. take zero schrg, delta=%d\n", ++ __func__, zero_delta_dsoc); ++ } ++ } ++ ++ /* check up overflow */ ++ if ((di->sm_dischrg_dsoc) > ((di->dsoc + 1) * 1000 - MIN_ACCURACY)) { ++ DBG("<%s>. dischrg_dsoc up overflow\n", __func__); ++ di->sm_dischrg_dsoc = (di->dsoc + 1) * ++ 1000 - MIN_ACCURACY; ++ } ++ ++ /* check new dsoc */ ++ tmp_soc = di->sm_dischrg_dsoc / 1000; ++ if (tmp_soc != di->dsoc) { ++ di->dsoc = tmp_soc; ++ di->sm_chrg_dsoc = di->dsoc * 1000; ++ } ++out: ++ DBG("<%s>. dsoc=%d, rsoc=%d, dsoc:sm_dischrg=%d, sm_chrg=%d, zero=%d\n", ++ __func__, di->dsoc, di->rsoc, di->sm_dischrg_dsoc, di->sm_chrg_dsoc, ++ di->zero_dsoc); ++ ++} ++ ++static void rk818_bat_calc_smooth_chrg(struct rk818_battery *di) ++{ ++ int tmp_soc = 0, sm_delta_dsoc = 0, zero_delta_dsoc = 0; ++ ++ tmp_soc = di->sm_chrg_dsoc / 1000; ++ if (tmp_soc == di->dsoc) ++ goto out; ++ ++ DBG("<%s>. enter: dsoc=%d, rsoc=%d\n", __func__, di->dsoc, di->rsoc); ++ /* when charge slow down, take zero & sm dischrg into calc */ ++ if (di->dsoc > di->rsoc) { ++ /* take sm discharge rest into calc */ ++ tmp_soc = di->sm_dischrg_dsoc / 1000; ++ if (tmp_soc == di->dsoc) { ++ sm_delta_dsoc = di->sm_dischrg_dsoc - ++ ((di->dsoc + 1) * 1000 - MIN_ACCURACY); ++ di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - ++ MIN_ACCURACY; ++ di->sm_chrg_dsoc += sm_delta_dsoc; ++ DBG("<%s>. take sm dischrg, delta=%d\n", ++ __func__, sm_delta_dsoc); ++ } ++ ++ /* take zero discharge rest into calc */ ++ tmp_soc = di->zero_dsoc / 1000; ++ if (tmp_soc == di->dsoc) { ++ zero_delta_dsoc = di->zero_dsoc - ++ ((di->dsoc + 1) * 1000 - MIN_ACCURACY); ++ di->zero_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ di->sm_chrg_dsoc += zero_delta_dsoc; ++ DBG("<%s>. take zero dischrg, delta=%d\n", ++ __func__, zero_delta_dsoc); ++ } ++ } ++ ++ /* check down overflow */ ++ if (di->sm_chrg_dsoc < di->dsoc * 1000) { ++ DBG("<%s>. chrg_dsoc down overflow\n", __func__); ++ di->sm_chrg_dsoc = di->dsoc * 1000; ++ } ++ ++ /* check new dsoc */ ++ tmp_soc = di->sm_chrg_dsoc / 1000; ++ if (tmp_soc != di->dsoc) { ++ di->dsoc = tmp_soc; ++ di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ } ++out: ++ DBG("<%s>.dsoc=%d, rsoc=%d, dsoc: sm_dischrg=%d, sm_chrg=%d, zero=%d\n", ++ __func__, di->dsoc, di->rsoc, di->sm_dischrg_dsoc, di->sm_chrg_dsoc, ++ di->zero_dsoc); ++} ++ ++static void rk818_bat_smooth_algorithm(struct rk818_battery *di) ++{ ++ int ydsoc = 0, delta_cap = 0, old_cap = 0; ++ unsigned long tgt_sec = 0; ++ ++ di->remain_cap = rk818_bat_get_coulomb_cap(di); ++ ++ /* full charge: slow down */ ++ if ((di->dsoc == 99) && (di->chrg_status == CC_OR_CV) && ++ (di->current_avg > 0)) { ++ di->sm_linek = FULL_CHRG_K; ++ /* terminal charge, slow down */ ++ } else if ((di->current_avg >= TERM_CHRG_CURR) && ++ (di->chrg_status == CC_OR_CV) && (di->dsoc >= TERM_CHRG_DSOC)) { ++ di->sm_linek = TERM_CHRG_K; ++ DBG("<%s>. terminal mode..\n", __func__); ++ /* simulate charge, speed up */ ++ } else if ((di->current_avg <= SIMULATE_CHRG_CURR) && ++ (di->current_avg > 0) && (di->chrg_status == CC_OR_CV) && ++ (di->dsoc < TERM_CHRG_DSOC) && ++ ((di->rsoc - di->dsoc) >= SIMULATE_CHRG_INTV)) { ++ di->sm_linek = SIMULATE_CHRG_K; ++ DBG("<%s>. simulate mode..\n", __func__); ++ } else { ++ /* charge and discharge switch */ ++ if ((di->sm_linek * di->current_avg <= 0) || ++ (di->sm_linek == TERM_CHRG_K) || ++ (di->sm_linek == FULL_CHRG_K) || ++ (di->sm_linek == SIMULATE_CHRG_K)) { ++ DBG("<%s>. linek mode, retinit sm linek..\n", __func__); ++ rk818_bat_calc_sm_linek(di); ++ } ++ } ++ ++ old_cap = di->sm_remain_cap; ++ /* ++ * when dsoc equal rsoc(not include full, term, simulate case), ++ * sm_linek should change to -1000/1000 smoothly to avoid dsoc+1/-1 ++ * right away, so change it after flat seconds ++ */ ++ if ((di->dsoc == di->rsoc) && (abs(di->sm_linek) != 1000) && ++ (di->sm_linek != FULL_CHRG_K && di->sm_linek != TERM_CHRG_K && ++ di->sm_linek != SIMULATE_CHRG_K)) { ++ if (!di->flat_match_sec) ++ di->flat_match_sec = get_boot_sec(); ++ tgt_sec = di->fcc * 3600 / 100 / DIV(abs(di->current_avg)) / 3; ++ if (base2sec(di->flat_match_sec) >= tgt_sec) { ++ di->flat_match_sec = 0; ++ di->sm_linek = (di->current_avg >= 0) ? 1000 : -1000; ++ } ++ DBG("<%s>. flat_sec=%ld, tgt_sec=%ld, sm_k=%d\n", __func__, ++ base2sec(di->flat_match_sec), tgt_sec, di->sm_linek); ++ } else { ++ di->flat_match_sec = 0; ++ } ++ ++ /* abs(k)=1000 or dsoc=100, stop calc */ ++ if ((abs(di->sm_linek) == 1000) || (di->current_avg >= 0 && ++ di->chrg_status == CC_OR_CV && di->dsoc >= 100)) { ++ DBG("<%s>. sm_linek=%d\n", __func__, di->sm_linek); ++ if (abs(di->sm_linek) == 1000) { ++ di->dsoc = di->rsoc; ++ di->sm_linek = (di->sm_linek > 0) ? 1000 : -1000; ++ DBG("<%s>. dsoc == rsoc, sm_linek=%d\n", ++ __func__, di->sm_linek); ++ } ++ di->sm_remain_cap = di->remain_cap; ++ di->sm_chrg_dsoc = di->dsoc * 1000; ++ di->sm_dischrg_dsoc = (di->dsoc + 1) * 1000 - MIN_ACCURACY; ++ DBG("<%s>. sm_dischrg_dsoc=%d, sm_chrg_dsoc=%d\n", ++ __func__, di->sm_dischrg_dsoc, di->sm_chrg_dsoc); ++ } else { ++ delta_cap = di->remain_cap - di->sm_remain_cap; ++ if (delta_cap == 0) { ++ DBG("<%s>. delta_cap = 0\n", __func__); ++ return; ++ } ++ ydsoc = di->sm_linek * abs(delta_cap) * 100 / DIV(di->fcc); ++ if (ydsoc == 0) { ++ DBG("<%s>. ydsoc = 0\n", __func__); ++ return; ++ } ++ di->sm_remain_cap = di->remain_cap; ++ ++ DBG("<%s>. k=%d, ydsoc=%d; cap:old=%d, new:%d; delta_cap=%d\n", ++ __func__, di->sm_linek, ydsoc, old_cap, ++ di->sm_remain_cap, delta_cap); ++ ++ /* discharge mode */ ++ if (ydsoc < 0) { ++ di->sm_dischrg_dsoc += ydsoc; ++ rk818_bat_calc_smooth_dischrg(di); ++ /* charge mode */ ++ } else { ++ di->sm_chrg_dsoc += ydsoc; ++ rk818_bat_calc_smooth_chrg(di); ++ } ++ ++ if (di->s2r) { ++ di->s2r = false; ++ rk818_bat_calc_sm_linek(di); ++ } ++ } ++} ++ ++/* ++ * cccv and finish switch all the time will cause dsoc freeze, ++ * if so, do finish chrg, 100ma is less than min finish_ma. ++ */ ++static bool rk818_bat_fake_finish_mode(struct rk818_battery *di) ++{ ++ if ((di->rsoc == 100) && (rk818_bat_get_chrg_status(di) == CC_OR_CV) && ++ (abs(di->current_avg) <= 100)) ++ return true; ++ else ++ return false; ++} ++ ++static void rk818_bat_display_smooth(struct rk818_battery *di) ++{ ++ /* discharge: reinit "zero & smooth" algorithm to avoid handling dsoc */ ++ if (di->s2r && !di->sleep_chrg_online) { ++ DBG("s2r: discharge, reset algorithm...\n"); ++ di->s2r = false; ++ rk818_bat_zero_algo_prepare(di); ++ rk818_bat_smooth_algo_prepare(di); ++ return; ++ } ++ ++ if (di->work_mode == MODE_FINISH) { ++ DBG("step1: charge finish...\n"); ++ rk818_bat_finish_algorithm(di); ++ if ((rk818_bat_get_chrg_status(di) != CHARGE_FINISH) && ++ !rk818_bat_fake_finish_mode(di)) { ++ if ((di->current_avg < 0) && ++ (di->voltage_avg < di->pdata->zero_algorithm_vol)) { ++ DBG("step1: change to zero mode...\n"); ++ rk818_bat_zero_algo_prepare(di); ++ di->work_mode = MODE_ZERO; ++ } else { ++ DBG("step1: change to smooth mode...\n"); ++ rk818_bat_smooth_algo_prepare(di); ++ di->work_mode = MODE_SMOOTH; ++ } ++ } ++ } else if (di->work_mode == MODE_ZERO) { ++ DBG("step2: zero algorithm...\n"); ++ rk818_bat_zero_algorithm(di); ++ if ((di->voltage_avg >= di->pdata->zero_algorithm_vol + 50) || ++ (di->current_avg >= 0)) { ++ DBG("step2: change to smooth mode...\n"); ++ rk818_bat_smooth_algo_prepare(di); ++ di->work_mode = MODE_SMOOTH; ++ } else if ((rk818_bat_get_chrg_status(di) == CHARGE_FINISH) || ++ rk818_bat_fake_finish_mode(di)) { ++ DBG("step2: change to finish mode...\n"); ++ rk818_bat_finish_algo_prepare(di); ++ di->work_mode = MODE_FINISH; ++ } ++ } else { ++ DBG("step3: smooth algorithm...\n"); ++ rk818_bat_smooth_algorithm(di); ++ if ((di->current_avg < 0) && ++ (di->voltage_avg < di->pdata->zero_algorithm_vol)) { ++ DBG("step3: change to zero mode...\n"); ++ rk818_bat_zero_algo_prepare(di); ++ di->work_mode = MODE_ZERO; ++ } else if ((rk818_bat_get_chrg_status(di) == CHARGE_FINISH) || ++ rk818_bat_fake_finish_mode(di)) { ++ DBG("step3: change to finish mode...\n"); ++ rk818_bat_finish_algo_prepare(di); ++ di->work_mode = MODE_FINISH; ++ } ++ } ++} ++ ++static void rk818_bat_relax_vol_calib(struct rk818_battery *di) ++{ ++ int soc, cap, vol; ++ ++ vol = di->voltage_relax; ++ soc = rk818_bat_vol_to_ocvsoc(di, vol); ++ cap = rk818_bat_vol_to_ocvcap(di, vol); ++ rk818_bat_init_capacity(di, cap); ++ BAT_INFO("sleep ocv calib: rsoc=%d, cap=%d\n", soc, cap); ++} ++ ++static void rk818_bat_relife_age_flag(struct rk818_battery *di) ++{ ++ u8 ocv_soc, ocv_cap, soc_level; ++ ++ if (di->voltage_relax <= 0) ++ return; ++ ++ ocv_soc = rk818_bat_vol_to_ocvsoc(di, di->voltage_relax); ++ ocv_cap = rk818_bat_vol_to_ocvcap(di, di->voltage_relax); ++ DBG("<%s>. ocv_soc=%d, min=%lu, vol=%d\n", __func__, ++ ocv_soc, di->sleep_dischrg_sec / 60, di->voltage_relax); ++ ++ /* sleep enough time and ocv_soc enough low */ ++ if (!di->age_allow_update && ocv_soc <= 10) { ++ di->age_voltage = di->voltage_relax; ++ di->age_ocv_cap = ocv_cap; ++ di->age_ocv_soc = ocv_soc; ++ di->age_adjust_cap = 0; ++ ++ if (ocv_soc <= 1) ++ di->age_level = 100; ++ else if (ocv_soc < 5) ++ di->age_level = 90; ++ else ++ di->age_level = 80; ++ ++ soc_level = rk818_bat_get_age_level(di); ++ if (soc_level > di->age_level) { ++ di->age_allow_update = false; ++ } else { ++ di->age_allow_update = true; ++ di->age_keep_sec = get_boot_sec(); ++ } ++ ++ BAT_INFO("resume: age_vol:%d, age_ocv_cap:%d, age_ocv_soc:%d, " ++ "soc_level:%d, age_allow_update:%d, " ++ "age_level:%d\n", ++ di->age_voltage, di->age_ocv_cap, ocv_soc, soc_level, ++ di->age_allow_update, di->age_level); ++ } ++} ++ ++static int rk818_bat_sleep_dischrg(struct rk818_battery *di) ++{ ++ bool ocv_soc_updated = false; ++ int tgt_dsoc, gap_soc, sleep_soc = 0; ++ int pwroff_vol = di->pdata->pwroff_vol; ++ unsigned long sleep_sec = di->sleep_dischrg_sec; ++ ++ DBG("<%s>. enter: dsoc=%d, rsoc=%d, rv=%d, v=%d, sleep_min=%lu\n", ++ __func__, di->dsoc, di->rsoc, di->voltage_relax, ++ di->voltage_avg, sleep_sec / 60); ++ ++ if (di->voltage_relax >= di->voltage_avg) { ++ rk818_bat_relax_vol_calib(di); ++ rk818_bat_restart_relax(di); ++ rk818_bat_relife_age_flag(di); ++ ocv_soc_updated = true; ++ } ++ ++ /* handle dsoc */ ++ if (di->dsoc <= di->rsoc) { ++ di->sleep_sum_cap = (SLP_CURR_MIN * sleep_sec / 3600); ++ sleep_soc = di->sleep_sum_cap * 100 / DIV(di->fcc); ++ tgt_dsoc = di->dsoc - sleep_soc; ++ if (sleep_soc > 0) { ++ BAT_INFO("calib0: rl=%d, dl=%d, intval=%d\n", ++ di->rsoc, di->dsoc, sleep_soc); ++ if (di->dsoc < 5) { ++ di->dsoc--; ++ } else if ((tgt_dsoc < 5) && (di->dsoc >= 5)) { ++ if (di->dsoc == 5) ++ di->dsoc--; ++ else ++ di->dsoc = 5; ++ } else if (tgt_dsoc > 5) { ++ di->dsoc = tgt_dsoc; ++ } ++ } ++ ++ DBG("%s: dsoc<=rsoc, sum_cap=%d==>sleep_soc=%d, tgt_dsoc=%d\n", ++ __func__, di->sleep_sum_cap, sleep_soc, tgt_dsoc); ++ } else { ++ /* di->dsoc > di->rsoc */ ++ di->sleep_sum_cap = (SLP_CURR_MAX * sleep_sec / 3600); ++ sleep_soc = di->sleep_sum_cap / DIV(di->fcc / 100); ++ gap_soc = di->dsoc - di->rsoc; ++ ++ BAT_INFO("calib1: rsoc=%d, dsoc=%d, intval=%d\n", ++ di->rsoc, di->dsoc, sleep_soc); ++ if (gap_soc > sleep_soc) { ++ if ((gap_soc - 5) > (sleep_soc * 2)) ++ di->dsoc -= (sleep_soc * 2); ++ else ++ di->dsoc -= sleep_soc; ++ } else { ++ di->dsoc = di->rsoc; ++ } ++ ++ DBG("%s: dsoc>rsoc, sum_cap=%d=>sleep_soc=%d, gap_soc=%d\n", ++ __func__, di->sleep_sum_cap, sleep_soc, gap_soc); ++ } ++ ++ if (di->voltage_avg <= pwroff_vol - 70) { ++ di->dsoc = 0; ++ rk_send_wakeup_key(); ++ BAT_INFO("low power sleeping, shutdown... %d\n", di->dsoc); ++ } ++ ++ if (ocv_soc_updated && sleep_soc && (di->rsoc - di->dsoc) < 5 && ++ di->dsoc < 40) { ++ di->dsoc--; ++ BAT_INFO("low power sleeping, reserved... %d\n", di->dsoc); ++ } ++ ++ if (di->dsoc <= 0) { ++ di->dsoc = 0; ++ rk_send_wakeup_key(); ++ BAT_INFO("sleep dsoc is %d...\n", di->dsoc); ++ } ++ ++ DBG("<%s>. out: dsoc=%d, rsoc=%d, sum_cap=%d\n", ++ __func__, di->dsoc, di->rsoc, di->sleep_sum_cap); ++ ++ return sleep_soc; ++} ++ ++static void rk818_bat_power_supply_changed(struct rk818_battery *di) ++{ ++ u8 status, thermal; ++ static int old_soc = -1; ++ ++ if (di->dsoc > 100) ++ di->dsoc = 100; ++ else if (di->dsoc < 0) ++ di->dsoc = 0; ++ ++ if (di->dsoc == old_soc) ++ return; ++ ++ thermal = rk818_bat_read(di, RK818_THERMAL_REG); ++ status = rk818_bat_read(di, RK818_SUP_STS_REG); ++ status = (status & CHRG_STATUS_MSK) >> 4; ++ old_soc = di->dsoc; ++ di->last_dsoc = di->dsoc; ++ power_supply_changed(di->bat); ++ BAT_INFO("changed: dsoc=%d, rsoc=%d, v=%d, ov=%d c=%d, " ++ "cap=%d, f=%d, st=%s, hotdie=%d\n", ++ di->dsoc, di->rsoc, di->voltage_avg, di->voltage_ocv, ++ di->current_avg, di->remain_cap, di->fcc, bat_status[status], ++ !!(thermal & HOTDIE_STS)); ++ ++ BAT_INFO("dl=%d, rl=%d, v=%d, halt=%d, halt_n=%d, max=%d, " ++ "init=%d, sw=%d, calib=%d, below0=%d, force=%d\n", ++ di->dbg_pwr_dsoc, di->dbg_pwr_rsoc, di->dbg_pwr_vol, ++ di->is_halt, di->halt_cnt, di->is_max_soc_offset, ++ di->is_initialized, di->is_sw_reset, di->is_ocv_calib, ++ di->dbg_cap_low0, di->is_force_calib); ++} ++ ++static u8 rk818_bat_check_reboot(struct rk818_battery *di) ++{ ++ u8 cnt; ++ ++ cnt = rk818_bat_read(di, RK818_REBOOT_CNT_REG); ++ cnt++; ++ ++ if (cnt >= REBOOT_MAX_CNT) { ++ BAT_INFO("reboot: %d --> %d\n", di->dsoc, di->rsoc); ++ di->dsoc = di->rsoc; ++ if (di->dsoc > 100) ++ di->dsoc = 100; ++ else if (di->dsoc < 0) ++ di->dsoc = 0; ++ rk818_bat_save_dsoc(di, di->dsoc); ++ cnt = REBOOT_MAX_CNT; ++ } ++ ++ rk818_bat_save_reboot_cnt(di, cnt); ++ DBG("reboot cnt: %d\n", cnt); ++ ++ return cnt; ++} ++ ++static void rk818_bat_rsoc_daemon(struct rk818_battery *di) ++{ ++ int est_vol, remain_cap; ++ static unsigned long sec; ++ ++ if ((di->remain_cap < 0) && (di->fb_blank != 0)) { ++ if (!sec) ++ sec = get_boot_sec(); ++ // wake_lock_timeout(&di->wake_lock, ++ // (di->pdata->monitor_sec + 1) * HZ); ++ ++ DBG("sec=%ld, hold_sec=%ld\n", sec, base2sec(sec)); ++ if (base2sec(sec) >= 60) { ++ sec = 0; ++ di->dbg_cap_low0++; ++ est_vol = di->voltage_avg - ++ (di->bat_res * di->current_avg) / 1000; ++ remain_cap = rk818_bat_vol_to_ocvcap(di, est_vol); ++ rk818_bat_init_capacity(di, remain_cap); ++ BAT_INFO("adjust cap below 0 --> %d, rsoc=%d\n", ++ di->remain_cap, di->rsoc); ++ // wake_unlock(&di->wake_lock); ++ } ++ } else { ++ sec = 0; ++ } ++} ++ ++static void rk818_bat_update_info(struct rk818_battery *di) ++{ ++ int is_charging; ++ ++ di->voltage_avg = rk818_bat_get_avg_voltage(di); ++ di->current_avg = rk818_bat_get_avg_current(di); ++ di->voltage_relax = rk818_bat_get_relax_voltage(di); ++ di->rsoc = rk818_bat_get_rsoc(di); ++ di->remain_cap = rk818_bat_get_coulomb_cap(di); ++ di->chrg_status = rk818_bat_get_chrg_status(di); ++ is_charging = rk818_bat_get_charge_state(di); ++ if (is_charging != di->is_charging) { ++ di->is_charging = is_charging; ++ if (is_charging) ++ di->charge_count++; ++ } ++ if (di->voltage_avg > di->voltage_max) ++ di->voltage_max = di->voltage_avg; ++ if (di->current_avg > di->current_max) ++ di->current_max = di->current_avg; ++ ++ /* smooth charge */ ++ if (di->remain_cap > di->fcc) { ++ di->sm_remain_cap -= (di->remain_cap - di->fcc); ++ DBG("<%s>. cap: remain=%d, sm_remain=%d\n", ++ __func__, di->remain_cap, di->sm_remain_cap); ++ rk818_bat_init_coulomb_cap(di, di->fcc); ++ } ++ ++ if (di->chrg_status != CHARGE_FINISH) ++ di->finish_base = get_boot_sec(); ++ ++ /* ++ * we need update fcc in continuous charging state, if discharge state ++ * keep at least 2 hour, we decide not to update fcc, so clear the ++ * fcc update flag: age_allow_update. ++ */ ++ if (base2min(di->plug_out_base) > 120) ++ di->age_allow_update = false; ++ ++ /* do adc calib: status must from cccv mode to finish mode */ ++ if (di->chrg_status == CC_OR_CV) { ++ di->adc_allow_update = true; ++ di->adc_calib_cnt = 0; ++ } ++} ++ ++static void rk818_bat_init_ts1_detect(struct rk818_battery *di) ++{ ++ u8 buf; ++ u32 *ntc_table = di->pdata->ntc_table; ++ ++ if (!di->pdata->ntc_size) ++ return; ++ ++ /* select ua */ ++ buf = rk818_bat_read(di, RK818_TS_CTRL_REG); ++ buf &= ~TS1_CUR_MSK; ++ /* chose suitable UA for temperature detect */ ++ if (ntc_table[0] < NTC_80UA_MAX_MEASURE) { ++ di->pdata->ntc_factor = NTC_CALC_FACTOR_80UA; ++ di->pdata->ntc_uA = 80; ++ buf |= ADC_CUR_80UA; ++ } else if (ntc_table[0] < NTC_60UA_MAX_MEASURE) { ++ di->pdata->ntc_factor = NTC_CALC_FACTOR_60UA; ++ di->pdata->ntc_uA = 60; ++ buf |= ADC_CUR_60UA; ++ } else if (ntc_table[0] < NTC_40UA_MAX_MEASURE) { ++ di->pdata->ntc_factor = NTC_CALC_FACTOR_40UA; ++ di->pdata->ntc_uA = 40; ++ buf |= ADC_CUR_40UA; ++ } else { ++ di->pdata->ntc_factor = NTC_CALC_FACTOR_20UA; ++ di->pdata->ntc_uA = 20; ++ buf |= ADC_CUR_20UA; ++ } ++ rk818_bat_write(di, RK818_TS_CTRL_REG, buf); ++ ++ /* enable ADC_TS1_EN */ ++ buf = rk818_bat_read(di, RK818_ADC_CTRL_REG); ++ buf |= ADC_TS1_EN; ++ rk818_bat_write(di, RK818_ADC_CTRL_REG, buf); ++} ++ ++/* ++ * Due to hardware design issue, Vdelta = "(R_sample + R_other) * I_avg" will be ++ * included into TS1 adc value. We must subtract it to get correct adc value. ++ * The solution: ++ * ++ * (1) calculate Vdelta: ++ * ++ * adc1 - Vdelta ua1 (adc2 * ua1) - (adc1 * ua2) ++ * ------------- = ----- ==> equals: Vdelta = ----------------------------- ++ * adc2 - Vdelta ua2 ua1 - ua2 ++ * ++ * ++ * (2) calculate correct ADC value: ++ * ++ * charging: ADC = adc1 - abs(Vdelta); ++ * discharging: ADC = adc1 + abs(Vdelta); ++ */ ++static int rk818_bat_get_ntc_res(struct rk818_battery *di) ++{ ++ int adc1 = 0, adc2 = 0; ++ int ua1, ua2, v_delta, res, val; ++ u8 buf; ++ ++ /* read sample ua1 */ ++ buf = rk818_bat_read(di, RK818_TS_CTRL_REG); ++ DBG("<%s>. read adc1, sample uA=%d\n", ++ __func__, ((buf & 0x03) + 1) * 20); ++ ++ /* read adc adc1 */ ++ ua1 = di->pdata->ntc_uA; ++ adc1 |= rk818_bat_read(di, RK818_TS1_ADC_REGL) << 0; ++ adc1 |= rk818_bat_read(di, RK818_TS1_ADC_REGH) << 8; ++ ++ /* chose reference UA for adc2 */ ++ ua2 = (ua1 != 20) ? 20 : 40; ++ buf = rk818_bat_read(di, RK818_TS_CTRL_REG); ++ buf &= ~TS1_CUR_MSK; ++ buf |= ((ua2 - 20) / 20); ++ rk818_bat_write(di, RK818_TS_CTRL_REG, buf); ++ ++ /* read adc adc2 */ ++ msleep(1000); ++ ++ /* read sample ua2 */ ++ buf = rk818_bat_read(di, RK818_TS_CTRL_REG); ++ DBG("<%s>. read adc2, sample uA=%d\n", ++ __func__, ((buf & 0x03) + 1) * 20); ++ ++ adc2 |= rk818_bat_read(di, RK818_TS1_ADC_REGL) << 0; ++ adc2 |= rk818_bat_read(di, RK818_TS1_ADC_REGH) << 8; ++ ++ DBG("<%s>. ua1=%d, ua2=%d, adc1=%d, adc2=%d\n", ++ __func__, ua1, ua2, adc1, adc2); ++ ++ /* calculate delta voltage */ ++ if (adc2 != adc1) ++ v_delta = abs((adc2 * ua1 - adc1 * ua2) / (ua2 - ua1)); ++ else ++ v_delta = 0; ++ ++ /* considering current avg direction, calcuate real adc value */ ++ val = (di->current_avg >= 0) ? (adc1 - v_delta) : (adc1 + v_delta); ++ ++ DBG("<%s>. Iavg=%d, Vdelta=%d, Vadc=%d\n", ++ __func__, di->current_avg, v_delta, val); ++ ++ res = val * di->pdata->ntc_factor; ++ ++ DBG("<%s>. val=%d, ntc_res=%d, ntc_factor=%d, Rdelta=%d\n", ++ __func__, val, res, di->pdata->ntc_factor, ++ v_delta * di->pdata->ntc_factor); ++ ++ DBG("<%s>. t=[%d'C(%d) ~ %dC(%d)]\n", __func__, ++ di->pdata->ntc_degree_from, di->pdata->ntc_table[0], ++ di->pdata->ntc_degree_from + di->pdata->ntc_size - 1, ++ di->pdata->ntc_table[di->pdata->ntc_size - 1]); ++ ++ rk818_bat_init_ts1_detect(di); ++ ++ return res; ++} ++ ++static BLOCKING_NOTIFIER_HEAD(rk818_bat_notifier_chain); ++ ++int rk818_bat_temp_notifier_register(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_register(&rk818_bat_notifier_chain, nb); ++} ++ ++int rk818_bat_temp_notifier_unregister(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_unregister(&rk818_bat_notifier_chain, nb); ++} ++ ++static void rk818_bat_temp_notifier_callback(int temp) ++{ ++ blocking_notifier_call_chain(&rk818_bat_notifier_chain, temp, NULL); ++} ++ ++static void rk818_bat_update_temperature(struct rk818_battery *di) ++{ ++ static int old_temp, first_time = 1; ++ u32 ntc_size, *ntc_table; ++ int i, res, temp; ++ ++ ntc_table = di->pdata->ntc_table; ++ ntc_size = di->pdata->ntc_size; ++ di->temperature = VIRTUAL_TEMPERATURE; ++ ++ if (ntc_size) { ++ res = rk818_bat_get_ntc_res(di); ++ if (res < ntc_table[ntc_size - 1]) { ++ di->temperature = di->pdata->ntc_degree_from + ++ di->pdata->ntc_size - 1; ++ BAT_INFO("bat ntc upper max degree: R=%d\n", res); ++ } else if (res > ntc_table[0]) { ++ di->temperature = di->pdata->ntc_degree_from; ++ BAT_INFO("bat ntc lower min degree: R=%d\n", res); ++ } else { ++ for (i = 0; i < ntc_size; i++) { ++ if (res >= ntc_table[i]) ++ break; ++ } ++ ++ /* if first in, init old_temp */ ++ temp = (i + di->pdata->ntc_degree_from) * 10; ++ if (first_time == 1) { ++ di->temperature = temp; ++ old_temp = temp; ++ first_time = 0; ++ } ++ ++ /* ++ * compare with old one, it's invalid when over 50 ++ * and we should use old data. ++ */ ++ if (abs(temp - old_temp) > 50) ++ temp = old_temp; ++ else ++ old_temp = temp; ++ ++ di->temperature = temp; ++ DBG("<%s>. temperature = %d\n", ++ __func__, di->temperature); ++ rk818_bat_temp_notifier_callback(di->temperature / 10); ++ } ++ } ++} ++ ++static void rk818_bat_init_dsoc_algorithm(struct rk818_battery *di) ++{ ++ u8 buf; ++ int16_t rest = 0; ++ unsigned long soc_sec; ++ const char *mode_name[] = { "MODE_ZERO", "MODE_FINISH", ++ "MODE_SMOOTH_CHRG", "MODE_SMOOTH_DISCHRG", "MODE_SMOOTH", }; ++ ++ /* get rest */ ++ rest |= rk818_bat_read(di, RK818_CALC_REST_REGH) << 8; ++ rest |= rk818_bat_read(di, RK818_CALC_REST_REGL) << 0; ++ ++ /* get mode */ ++ buf = rk818_bat_read(di, RK818_MISC_MARK_REG); ++ di->algo_rest_mode = (buf & ALGO_REST_MODE_MSK) >> ALGO_REST_MODE_SHIFT; ++ ++ if (rk818_bat_get_chrg_status(di) == CHARGE_FINISH) { ++ if (di->algo_rest_mode == MODE_FINISH) { ++ soc_sec = di->fcc * 3600 / 100 / FINISH_CHRG_CUR1; ++ if ((rest / DIV(soc_sec)) > 0) { ++ if (di->dsoc < 100) { ++ di->dsoc++; ++ di->algo_rest_val = rest % soc_sec; ++ BAT_INFO("algorithm rest(%d) dsoc " ++ "inc: %d\n", ++ rest, di->dsoc); ++ } else { ++ di->algo_rest_val = 0; ++ } ++ } else { ++ di->algo_rest_val = rest; ++ } ++ } else { ++ di->algo_rest_val = rest; ++ } ++ } else { ++ /* charge speed up */ ++ if ((rest / 1000) > 0 && rk818_bat_chrg_online(di)) { ++ if (di->dsoc < di->rsoc) { ++ di->dsoc++; ++ di->algo_rest_val = rest % 1000; ++ BAT_INFO("algorithm rest(%d) dsoc inc: %d\n", ++ rest, di->dsoc); ++ } else { ++ di->algo_rest_val = 0; ++ } ++ /* discharge speed up */ ++ } else if (((rest / 1000) < 0) && !rk818_bat_chrg_online(di)) { ++ if (di->dsoc > di->rsoc) { ++ di->dsoc--; ++ di->algo_rest_val = rest % 1000; ++ BAT_INFO("algorithm rest(%d) dsoc sub: %d\n", ++ rest, di->dsoc); ++ } else { ++ di->algo_rest_val = 0; ++ } ++ } else { ++ di->algo_rest_val = rest; ++ } ++ } ++ ++ if (di->dsoc >= 100) ++ di->dsoc = 100; ++ else if (di->dsoc <= 0) ++ di->dsoc = 0; ++ ++ /* init current mode */ ++ di->voltage_avg = rk818_bat_get_avg_voltage(di); ++ di->current_avg = rk818_bat_get_avg_current(di); ++ if (rk818_bat_get_chrg_status(di) == CHARGE_FINISH) { ++ rk818_bat_finish_algo_prepare(di); ++ di->work_mode = MODE_FINISH; ++ } else { ++ rk818_bat_smooth_algo_prepare(di); ++ di->work_mode = MODE_SMOOTH; ++ } ++ ++ DBG("<%s>. init: org_rest=%d, rest=%d, mode=%s; " ++ "doc(x1000): zero=%d, chrg=%d, dischrg=%d, finish=%lu\n", ++ __func__, rest, di->algo_rest_val, mode_name[di->algo_rest_mode], ++ di->zero_dsoc, di->sm_chrg_dsoc, di->sm_dischrg_dsoc, ++ di->finish_base); ++} ++ ++static void rk818_bat_save_algo_rest(struct rk818_battery *di) ++{ ++ u8 buf, mode; ++ int16_t algo_rest = 0; ++ int tmp_soc; ++ int zero_rest = 0, sm_chrg_rest = 0; ++ int sm_dischrg_rest = 0, finish_rest = 0; ++ const char *mode_name[] = { "MODE_ZERO", "MODE_FINISH", ++ "MODE_SMOOTH_CHRG", "MODE_SMOOTH_DISCHRG", "MODE_SMOOTH", }; ++ ++ /* zero dischrg */ ++ tmp_soc = (di->zero_dsoc) / 1000; ++ if (tmp_soc == di->dsoc) ++ zero_rest = di->zero_dsoc - ((di->dsoc + 1) * 1000 - ++ MIN_ACCURACY); ++ ++ /* sm chrg */ ++ tmp_soc = di->sm_chrg_dsoc / 1000; ++ if (tmp_soc == di->dsoc) ++ sm_chrg_rest = di->sm_chrg_dsoc - di->dsoc * 1000; ++ ++ /* sm dischrg */ ++ tmp_soc = (di->sm_dischrg_dsoc) / 1000; ++ if (tmp_soc == di->dsoc) ++ sm_dischrg_rest = di->sm_dischrg_dsoc - ((di->dsoc + 1) * 1000 - ++ MIN_ACCURACY); ++ ++ /* last time is also finish chrg, then add last rest */ ++ if (di->algo_rest_mode == MODE_FINISH && di->algo_rest_val) ++ finish_rest = base2sec(di->finish_base) + di->algo_rest_val; ++ else ++ finish_rest = base2sec(di->finish_base); ++ ++ /* total calc */ ++ if ((rk818_bat_chrg_online(di) && (di->dsoc > di->rsoc)) || ++ (!rk818_bat_chrg_online(di) && (di->dsoc < di->rsoc)) || ++ (di->dsoc == di->rsoc)) { ++ di->algo_rest_val = 0; ++ algo_rest = 0; ++ DBG("<%s>. step1..\n", __func__); ++ } else if (di->work_mode == MODE_FINISH) { ++ algo_rest = finish_rest; ++ DBG("<%s>. step2..\n", __func__); ++ } else if (di->algo_rest_mode == MODE_FINISH) { ++ algo_rest = zero_rest + sm_dischrg_rest + sm_chrg_rest; ++ DBG("<%s>. step3..\n", __func__); ++ } else { ++ if (rk818_bat_chrg_online(di) && (di->dsoc < di->rsoc)) ++ algo_rest = sm_chrg_rest + di->algo_rest_val; ++ else if (!rk818_bat_chrg_online(di) && (di->dsoc > di->rsoc)) ++ algo_rest = zero_rest + sm_dischrg_rest + ++ di->algo_rest_val; ++ else ++ algo_rest = zero_rest + sm_dischrg_rest + sm_chrg_rest + ++ di->algo_rest_val; ++ DBG("<%s>. step4..\n", __func__); ++ } ++ ++ /* check mode */ ++ if ((di->work_mode == MODE_FINISH) || (di->work_mode == MODE_ZERO)) { ++ mode = di->work_mode; ++ } else {/* MODE_SMOOTH */ ++ if (di->sm_linek > 0) ++ mode = MODE_SMOOTH_CHRG; ++ else ++ mode = MODE_SMOOTH_DISCHRG; ++ } ++ ++ /* save mode */ ++ buf = rk818_bat_read(di, RK818_MISC_MARK_REG); ++ buf &= ~ALGO_REST_MODE_MSK; ++ buf |= (mode << ALGO_REST_MODE_SHIFT); ++ rk818_bat_write(di, RK818_MISC_MARK_REG, buf); ++ ++ /* save rest */ ++ buf = (algo_rest >> 8) & 0xff; ++ rk818_bat_write(di, RK818_CALC_REST_REGH, buf); ++ buf = (algo_rest >> 0) & 0xff; ++ rk818_bat_write(di, RK818_CALC_REST_REGL, buf); ++ ++ DBG("<%s>. rest: algo=%d, mode=%s, last_rest=%d; zero=%d, " ++ "chrg=%d, dischrg=%d, finish=%lu\n", ++ __func__, algo_rest, mode_name[mode], di->algo_rest_val, zero_rest, ++ sm_chrg_rest, sm_dischrg_rest, base2sec(di->finish_base)); ++} ++ ++static void rk818_bat_save_data(struct rk818_battery *di) ++{ ++ rk818_bat_save_dsoc(di, di->dsoc); ++ rk818_bat_save_cap(di, di->remain_cap); ++ rk818_bat_save_algo_rest(di); ++} ++ ++static void rk818_battery_work(struct work_struct *work) ++{ ++ struct rk818_battery *di = ++ container_of(work, struct rk818_battery, bat_delay_work.work); ++ ++ rk818_bat_update_info(di); ++ rk818_bat_wait_finish_sig(di); ++ rk818_bat_rsoc_daemon(di); ++ rk818_bat_update_temperature(di); ++ rk818_bat_display_smooth(di); ++ rk818_bat_power_supply_changed(di); ++ rk818_bat_save_data(di); ++ rk818_bat_debug_info(di); ++ ++ queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work, ++ msecs_to_jiffies(di->monitor_ms)); ++} ++ ++static irqreturn_t rk818_vb_low_irq(int irq, void *bat) ++{ ++ struct rk818_battery *di = (struct rk818_battery *)bat; ++ ++ di->dsoc = 0; ++ rk_send_wakeup_key(); ++ BAT_INFO("lower power yet, power off system! v=%d, c=%d, dsoc=%d\n", ++ di->voltage_avg, di->current_avg, di->dsoc); ++ ++ return IRQ_HANDLED; ++} ++ ++static void rk818_bat_init_sysfs(struct rk818_battery *di) ++{ ++ int i, ret; ++ ++ for (i = 0; i < ARRAY_SIZE(rk818_bat_attr); i++) { ++ ret = sysfs_create_file(&di->dev->kobj, ++ &rk818_bat_attr[i].attr); ++ if (ret) ++ dev_err(di->dev, "create bat node(%s) error\n", ++ rk818_bat_attr[i].attr.name); ++ } ++} ++ ++static int rk818_bat_init_irqs(struct rk818_battery *di) ++{ ++ struct rk808 *rk818 = di->rk818; ++ struct platform_device *pdev = di->pdev; ++ int ret, vb_lo_irq; ++ ++ vb_lo_irq = regmap_irq_get_virq(rk818->irq_data, RK818_IRQ_VB_LO); ++ if (vb_lo_irq < 0) { ++ dev_err(di->dev, "vb_lo_irq request failed!\n"); ++ return vb_lo_irq; ++ } ++ ++ ret = devm_request_threaded_irq(di->dev, vb_lo_irq, NULL, ++ rk818_vb_low_irq, ++ IRQF_TRIGGER_HIGH | IRQF_ONESHOT, ++ "rk818_vb_low", di); ++ if (ret) { ++ dev_err(&pdev->dev, "vb_lo_irq request failed!\n"); ++ return ret; ++ } ++ enable_irq_wake(vb_lo_irq); ++ ++ return 0; ++} ++ ++static void rk818_bat_init_info(struct rk818_battery *di) ++{ ++ di->design_cap = di->pdata->design_capacity; ++ di->qmax = di->pdata->design_qmax; ++ di->bat_res = di->pdata->bat_res; ++ di->monitor_ms = di->pdata->monitor_sec * TIMER_MS_COUNTS; ++ di->boot_base = POWER_ON_SEC_BASE; ++ di->res_div = (di->pdata->sample_res == SAMPLE_RES_20MR) ? ++ SAMPLE_RES_DIV1 : SAMPLE_RES_DIV2; ++} ++ ++static time64_t rk818_get_rtc_sec(void) ++{ ++ int err; ++ struct rtc_time tm; ++ struct rtc_device *rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); ++ ++ err = rtc_read_time(rtc, &tm); ++ if (err) { ++ dev_err(rtc->dev.parent, "read hardware clk failed\n"); ++ return 0; ++ } ++ ++ err = rtc_valid_tm(&tm); ++ if (err) { ++ dev_err(rtc->dev.parent, "invalid date time\n"); ++ return 0; ++ } ++ ++ return rtc_tm_to_time64(&tm); ++} ++ ++static int rk818_bat_rtc_sleep_sec(struct rk818_battery *di) ++{ ++ int interval_sec; ++ ++ interval_sec = rk818_get_rtc_sec() - di->rtc_base; ++ ++ return (interval_sec > 0) ? interval_sec : 0; ++} ++ ++static void rk818_bat_set_shtd_vol(struct rk818_battery *di) ++{ ++ u8 val; ++ ++ /* set vbat lowest 3.0v shutdown */ ++ val = rk818_bat_read(di, RK818_VB_MON_REG); ++ val &= ~(VBAT_LOW_VOL_MASK | VBAT_LOW_ACT_MASK); ++ val |= (RK818_VBAT_LOW_3V0 | EN_VABT_LOW_SHUT_DOWN); ++ rk818_bat_write(di, RK818_VB_MON_REG, val); ++ ++ /* disable low irq */ ++ rk818_bat_set_bits(di, RK818_INT_STS_MSK_REG1, ++ VB_LOW_INT_EN, VB_LOW_INT_EN); ++} ++ ++static void rk818_bat_init_fg(struct rk818_battery *di) ++{ ++ rk818_bat_enable_gauge(di); ++ rk818_bat_init_voltage_kb(di); ++ rk818_bat_init_coffset(di); ++ rk818_bat_set_relax_sample(di); ++ rk818_bat_set_ioffset_sample(di); ++ rk818_bat_set_ocv_sample(di); ++ rk818_bat_init_ts1_detect(di); ++ rk818_bat_init_rsoc(di); ++ rk818_bat_init_coulomb_cap(di, di->nac); ++ rk818_bat_init_age_algorithm(di); ++ rk818_bat_init_chrg_config(di); ++ rk818_bat_set_shtd_vol(di); ++ rk818_bat_init_zero_table(di); ++ rk818_bat_init_caltimer(di); ++ rk818_bat_init_dsoc_algorithm(di); ++ ++ di->voltage_avg = rk818_bat_get_avg_voltage(di); ++ di->voltage_ocv = rk818_bat_get_ocv_voltage(di); ++ di->voltage_relax = rk818_bat_get_relax_voltage(di); ++ di->current_avg = rk818_bat_get_avg_current(di); ++ di->remain_cap = rk818_bat_get_coulomb_cap(di); ++ di->dbg_pwr_dsoc = di->dsoc; ++ di->dbg_pwr_rsoc = di->rsoc; ++ di->dbg_pwr_vol = di->voltage_avg; ++ ++ rk818_bat_dump_regs(di, 0x99, 0xee); ++ DBG("nac=%d cap=%d ov=%d v=%d rv=%d dl=%d rl=%d c=%d\n", ++ di->nac, di->remain_cap, di->voltage_ocv, di->voltage_avg, ++ di->voltage_relax, di->dsoc, di->rsoc, di->current_avg); ++} ++ ++#ifdef CONFIG_OF ++static int rk818_bat_parse_dt(struct rk818_battery *di) ++{ ++ u32 out_value; ++ int length, ret; ++ size_t size; ++ struct device_node *np = di->dev->of_node; ++ struct battery_platform_data *pdata; ++ struct device *dev = di->dev; ++ ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); ++ if (!pdata) ++ return -ENOMEM; ++ ++ di->pdata = pdata; ++ /* init default param */ ++ pdata->bat_res = DEFAULT_BAT_RES; ++ pdata->monitor_sec = DEFAULT_MONITOR_SEC; ++ pdata->pwroff_vol = DEFAULT_PWROFF_VOL_THRESD; ++ pdata->sleep_exit_current = DEFAULT_SLP_EXIT_CUR; ++ pdata->sleep_enter_current = DEFAULT_SLP_ENTER_CUR; ++ pdata->bat_mode = MODE_BATTARY; ++ pdata->max_soc_offset = DEFAULT_MAX_SOC_OFFSET; ++ pdata->sample_res = DEFAULT_SAMPLE_RES; ++ pdata->energy_mode = DEFAULT_ENERGY_MODE; ++ pdata->fb_temp = DEFAULT_FB_TEMP; ++ pdata->zero_reserve_dsoc = DEFAULT_ZERO_RESERVE_DSOC; ++ ++ /* parse necessary param */ ++ if (!of_find_property(np, "ocv_table", &length)) { ++ dev_err(dev, "ocv_table not found!\n"); ++ return -EINVAL; ++ } ++ ++ pdata->ocv_size = length / sizeof(u32); ++ if (pdata->ocv_size <= 0) { ++ dev_err(dev, "invalid ocv table\n"); ++ return -EINVAL; ++ } ++ ++ size = sizeof(*pdata->ocv_table) * pdata->ocv_size; ++ pdata->ocv_table = devm_kzalloc(di->dev, size, GFP_KERNEL); ++ if (!pdata->ocv_table) ++ return -ENOMEM; ++ ++ ret = of_property_read_u32_array(np, "ocv_table", ++ pdata->ocv_table, ++ pdata->ocv_size); ++ if (ret < 0) ++ return ret; ++ ++ ret = of_property_read_u32(np, "design_capacity", &out_value); ++ if (ret < 0) { ++ dev_err(dev, "design_capacity not found!\n"); ++ return ret; ++ } ++ pdata->design_capacity = out_value; ++ ++ ret = of_property_read_u32(np, "design_qmax", &out_value); ++ if (ret < 0) { ++ dev_err(dev, "design_qmax not found!\n"); ++ return ret; ++ } ++ pdata->design_qmax = out_value; ++ ret = of_property_read_u32(np, "max_chrg_voltage", &out_value); ++ if (ret < 0) { ++ dev_err(dev, "max_chrg_voltage missing!\n"); ++ return ret; ++ } ++ pdata->max_chrg_voltage = out_value; ++ if (out_value >= 4300) ++ pdata->zero_algorithm_vol = DEFAULT_ALGR_VOL_THRESD2; ++ else ++ pdata->zero_algorithm_vol = DEFAULT_ALGR_VOL_THRESD1; ++ ++ ret = of_property_read_u32(np, "fb_temperature", &pdata->fb_temp); ++ if (ret < 0) ++ dev_err(dev, "fb_temperature missing!\n"); ++ ++ ret = of_property_read_u32(np, "sample_res", &pdata->sample_res); ++ if (ret < 0) ++ dev_err(dev, "sample_res missing!\n"); ++ ++ ret = of_property_read_u32(np, "energy_mode", &pdata->energy_mode); ++ if (ret < 0) ++ dev_err(dev, "energy_mode missing!\n"); ++ ++ ret = of_property_read_u32(np, "max_soc_offset", ++ &pdata->max_soc_offset); ++ if (ret < 0) ++ dev_err(dev, "max_soc_offset missing!\n"); ++ ++ ret = of_property_read_u32(np, "monitor_sec", &pdata->monitor_sec); ++ if (ret < 0) ++ dev_err(dev, "monitor_sec missing!\n"); ++ ++ ret = of_property_read_u32(np, "zero_algorithm_vol", ++ &pdata->zero_algorithm_vol); ++ if (ret < 0) ++ dev_err(dev, "zero_algorithm_vol missing!\n"); ++ ++ ret = of_property_read_u32(np, "zero_reserve_dsoc", ++ &pdata->zero_reserve_dsoc); ++ ++ ret = of_property_read_u32(np, "virtual_power", &pdata->bat_mode); ++ if (ret < 0) ++ dev_err(dev, "virtual_power missing!\n"); ++ ++ ret = of_property_read_u32(np, "bat_res", &pdata->bat_res); ++ if (ret < 0) ++ dev_err(dev, "bat_res missing!\n"); ++ ++ ret = of_property_read_u32(np, "sleep_enter_current", ++ &pdata->sleep_enter_current); ++ if (ret < 0) ++ dev_err(dev, "sleep_enter_current missing!\n"); ++ ++ ret = of_property_read_u32(np, "sleep_exit_current", ++ &pdata->sleep_exit_current); ++ if (ret < 0) ++ dev_err(dev, "sleep_exit_current missing!\n"); ++ ++ ret = of_property_read_u32(np, "power_off_thresd", &pdata->pwroff_vol); ++ if (ret < 0) ++ dev_err(dev, "power_off_thresd missing!\n"); ++ ++ if (!of_find_property(np, "ntc_table", &length)) { ++ pdata->ntc_size = 0; ++ } else { ++ /* get ntc degree base value */ ++ ret = of_property_read_u32_index(np, "ntc_degree_from", 1, ++ &pdata->ntc_degree_from); ++ if (ret) { ++ dev_err(dev, "invalid ntc_degree_from\n"); ++ return -EINVAL; ++ } ++ ++ of_property_read_u32_index(np, "ntc_degree_from", 0, ++ &out_value); ++ if (out_value) ++ pdata->ntc_degree_from = -pdata->ntc_degree_from; ++ ++ pdata->ntc_size = length / sizeof(u32); ++ } ++ ++ if (pdata->ntc_size) { ++ size = sizeof(*pdata->ntc_table) * pdata->ntc_size; ++ pdata->ntc_table = devm_kzalloc(di->dev, size, GFP_KERNEL); ++ if (!pdata->ntc_table) ++ return -ENOMEM; ++ ++ ret = of_property_read_u32_array(np, "ntc_table", ++ pdata->ntc_table, ++ pdata->ntc_size); ++ if (ret < 0) ++ return ret; ++ } ++ ++ DBG("the battery dts info dump:\n" ++ "bat_res:%d\n" ++ "design_capacity:%d\n" ++ "design_qmax :%d\n" ++ "sleep_enter_current:%d\n" ++ "sleep_exit_current:%d\n" ++ "zero_algorithm_vol:%d\n" ++ "zero_reserve_dsoc:%d\n" ++ "monitor_sec:%d\n" ++ "max_soc_offset:%d\n" ++ "virtual_power:%d\n" ++ "pwroff_vol:%d\n" ++ "sample_res:%d\n" ++ "ntc_size=%d\n" ++ "ntc_degree_from:%d\n" ++ "ntc_degree_to:%d\n", ++ pdata->bat_res, pdata->design_capacity, pdata->design_qmax, ++ pdata->sleep_enter_current, pdata->sleep_exit_current, ++ pdata->zero_algorithm_vol, pdata->zero_reserve_dsoc, ++ pdata->monitor_sec, ++ pdata->max_soc_offset, pdata->bat_mode, pdata->pwroff_vol, ++ pdata->sample_res, pdata->ntc_size, pdata->ntc_degree_from, ++ pdata->ntc_degree_from + pdata->ntc_size - 1 ++ ); ++ ++ return 0; ++} ++#else ++static int rk818_bat_parse_dt(struct rk818_battery *di) ++{ ++ return -ENODEV; ++} ++#endif ++ ++static const struct of_device_id rk818_battery_of_match[] = { ++ {.compatible = "rk818-battery",}, ++ { }, ++}; ++ ++static int rk818_battery_probe(struct platform_device *pdev) ++{ ++ const struct of_device_id *of_id = ++ of_match_device(rk818_battery_of_match, &pdev->dev); ++ struct rk818_battery *di; ++ struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent); ++ int ret; ++ ++ if (!of_id) { ++ dev_err(&pdev->dev, "Failed to find matching dt id\n"); ++ return -ENODEV; ++ } ++ ++ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); ++ if (!di) ++ return -ENOMEM; ++ ++ di->rk818 = rk818; ++ di->pdev = pdev; ++ di->dev = &pdev->dev; ++ di->regmap = rk818->regmap; ++ platform_set_drvdata(pdev, di); ++ ++ ret = rk818_bat_parse_dt(di); ++ if (ret < 0) { ++ dev_err(di->dev, "rk818 battery parse dt failed!\n"); ++ return ret; ++ } ++ ++ if (!is_rk818_bat_exist(di)) { ++ di->pdata->bat_mode = MODE_VIRTUAL; ++ dev_err(di->dev, "no battery, virtual power mode\n"); ++ } ++ ++ ret = rk818_bat_init_irqs(di); ++ if (ret != 0) { ++ dev_err(di->dev, "rk818 bat init irqs failed!\n"); ++ return ret; ++ } ++ ++ ret = rk818_bat_init_power_supply(di); ++ if (ret) { ++ dev_err(di->dev, "rk818 power supply register failed!\n"); ++ return ret; ++ } ++ ++ rk818_bat_init_info(di); ++ rk818_bat_init_fg(di); ++ rk818_bat_init_sysfs(di); ++ rk818_bat_register_fb_notify(di); ++ //wake_lock_init(&di->wake_lock, WAKE_LOCK_SUSPEND, "rk818_bat_lock"); ++ di->bat_monitor_wq = alloc_ordered_workqueue("%s", ++ WQ_MEM_RECLAIM | WQ_FREEZABLE, "rk818-bat-monitor-wq"); ++ INIT_DELAYED_WORK(&di->bat_delay_work, rk818_battery_work); ++ queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work, ++ msecs_to_jiffies(TIMER_MS_COUNTS * 5)); ++ ++ BAT_INFO("driver version %s\n", DRIVER_VERSION); ++ ++ return ret; ++} ++ ++static int rk818_battery_suspend(struct platform_device *dev, ++ pm_message_t state) ++{ ++ struct rk818_battery *di = platform_get_drvdata(dev); ++ u8 val, st; ++ ++ cancel_delayed_work_sync(&di->bat_delay_work); ++ ++ di->s2r = false; ++ di->sleep_chrg_online = rk818_bat_chrg_online(di); ++ di->sleep_chrg_status = rk818_bat_get_chrg_status(di); ++ di->current_avg = rk818_bat_get_avg_current(di); ++ di->remain_cap = rk818_bat_get_coulomb_cap(di); ++ di->rsoc = rk818_bat_get_rsoc(di); ++ di->rtc_base = rk818_get_rtc_sec(); ++ rk818_bat_save_data(di); ++ st = (rk818_bat_read(di, RK818_SUP_STS_REG) & CHRG_STATUS_MSK) >> 4; ++ ++ /* if not CHARGE_FINISH, reinit finish_base. ++ * avoid sleep loop between suspend and resume ++ */ ++ if (di->sleep_chrg_status != CHARGE_FINISH) ++ di->finish_base = get_boot_sec(); ++ ++ /* avoid: enter suspend from MODE_ZERO: load from heavy to light */ ++ if ((di->work_mode == MODE_ZERO) && ++ (di->sleep_chrg_online) && (di->current_avg >= 0)) { ++ DBG("suspend: MODE_ZERO exit...\n"); ++ /* it need't do prepare for mode finish and smooth, it will ++ * be done in display_smooth ++ */ ++ if (di->sleep_chrg_status == CHARGE_FINISH) { ++ di->work_mode = MODE_FINISH; ++ di->finish_base = get_boot_sec(); ++ } else { ++ di->work_mode = MODE_SMOOTH; ++ rk818_bat_smooth_algo_prepare(di); ++ } ++ } ++ ++ /* set vbat low than 3.4v to generate a wakeup irq */ ++ val = rk818_bat_read(di, RK818_VB_MON_REG); ++ val &= (~(VBAT_LOW_VOL_MASK | VBAT_LOW_ACT_MASK)); ++ val |= (RK818_VBAT_LOW_3V4 | EN_VBAT_LOW_IRQ); ++ rk818_bat_write(di, RK818_VB_MON_REG, val); ++ rk818_bat_set_bits(di, RK818_INT_STS_MSK_REG1, VB_LOW_INT_EN, 0); ++ ++ BAT_INFO("suspend: dl=%d rl=%d c=%d v=%d cap=%d at=%ld ch=%d st=%s\n", ++ di->dsoc, di->rsoc, di->current_avg, ++ rk818_bat_get_avg_voltage(di), rk818_bat_get_coulomb_cap(di), ++ di->sleep_dischrg_sec, di->sleep_chrg_online, bat_status[st]); ++ ++ return 0; ++} ++ ++static int rk818_battery_resume(struct platform_device *dev) ++{ ++ struct rk818_battery *di = platform_get_drvdata(dev); ++ int interval_sec, time_step, pwroff_vol; ++ u8 val, st; ++ ++ di->s2r = true; ++ di->current_avg = rk818_bat_get_avg_current(di); ++ di->voltage_relax = rk818_bat_get_relax_voltage(di); ++ di->voltage_avg = rk818_bat_get_avg_voltage(di); ++ di->remain_cap = rk818_bat_get_coulomb_cap(di); ++ di->rsoc = rk818_bat_get_rsoc(di); ++ interval_sec = rk818_bat_rtc_sleep_sec(di); ++ di->sleep_sum_sec += interval_sec; ++ pwroff_vol = di->pdata->pwroff_vol; ++ st = (rk818_bat_read(di, RK818_SUP_STS_REG) & CHRG_STATUS_MSK) >> 4; ++ ++ if (!di->sleep_chrg_online) { ++ /* only add up discharge sleep seconds */ ++ di->sleep_dischrg_sec += interval_sec; ++ if (di->voltage_avg <= pwroff_vol + 50) ++ time_step = DISCHRG_TIME_STEP1; ++ else ++ time_step = DISCHRG_TIME_STEP2; ++ } ++ ++ BAT_INFO("resume: dl=%d rl=%d c=%d v=%d rv=%d " ++ "cap=%d dt=%d at=%ld ch=%d st=%s\n", ++ di->dsoc, di->rsoc, di->current_avg, di->voltage_avg, ++ di->voltage_relax, rk818_bat_get_coulomb_cap(di), interval_sec, ++ di->sleep_dischrg_sec, di->sleep_chrg_online, bat_status[st]); ++ ++ /* sleep: enough time and discharge */ ++ if ((di->sleep_dischrg_sec > time_step) && (!di->sleep_chrg_online)) { ++ if (rk818_bat_sleep_dischrg(di)) ++ di->sleep_dischrg_sec = 0; ++ } ++ ++ rk818_bat_save_data(di); ++ ++ /* set vbat lowest 3.0v shutdown */ ++ val = rk818_bat_read(di, RK818_VB_MON_REG); ++ val &= ~(VBAT_LOW_VOL_MASK | VBAT_LOW_ACT_MASK); ++ val |= (RK818_VBAT_LOW_3V0 | EN_VABT_LOW_SHUT_DOWN); ++ rk818_bat_write(di, RK818_VB_MON_REG, val); ++ rk818_bat_set_bits(di, RK818_INT_STS_MSK_REG1, ++ VB_LOW_INT_EN, VB_LOW_INT_EN); ++ ++ /* charge/lowpower lock: for battery work to update dsoc and rsoc */ ++ // if ((di->sleep_chrg_online) || ++ // (!di->sleep_chrg_online && di->voltage_avg < di->pdata->pwroff_vol)) ++ // wake_lock_timeout(&di->wake_lock, msecs_to_jiffies(2000)); ++ ++ queue_delayed_work(di->bat_monitor_wq, &di->bat_delay_work, ++ msecs_to_jiffies(1000)); ++ ++ return 0; ++} ++ ++static void rk818_battery_shutdown(struct platform_device *dev) ++{ ++ u8 cnt = 0; ++ struct rk818_battery *di = platform_get_drvdata(dev); ++ ++ cancel_delayed_work_sync(&di->bat_delay_work); ++ cancel_delayed_work_sync(&di->calib_delay_work); ++ rk818_bat_unregister_fb_notify(di); ++ del_timer(&di->caltimer); ++ if (base2sec(di->boot_base) < REBOOT_PERIOD_SEC) ++ cnt = rk818_bat_check_reboot(di); ++ else ++ rk818_bat_save_reboot_cnt(di, 0); ++ ++ BAT_INFO("shutdown: dl=%d rl=%d c=%d v=%d cap=%d f=%d ch=%d n=%d " ++ "mode=%d rest=%d\n", ++ di->dsoc, di->rsoc, di->current_avg, di->voltage_avg, ++ di->remain_cap, di->fcc, rk818_bat_chrg_online(di), cnt, ++ di->algo_rest_mode, di->algo_rest_val); ++} ++ ++static struct platform_driver rk818_battery_driver = { ++ .probe = rk818_battery_probe, ++ .suspend = rk818_battery_suspend, ++ .resume = rk818_battery_resume, ++ .shutdown = rk818_battery_shutdown, ++ .driver = { ++ .name = "rk818-battery", ++ .of_match_table = rk818_battery_of_match, ++ }, ++}; ++ ++static int __init battery_init(void) ++{ ++ return platform_driver_register(&rk818_battery_driver); ++} ++fs_initcall_sync(battery_init); ++ ++static void __exit battery_exit(void) ++{ ++ platform_driver_unregister(&rk818_battery_driver); ++} ++module_exit(battery_exit); ++ ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("platform:rk818-battery"); ++MODULE_AUTHOR("chenjh"); +\ No newline at end of file +diff --git a/drivers/power/supply/rk818_battery.h b/drivers/power/supply/rk818_battery.h +new file mode 100644 +index 00000000..2f4430a +--- /dev/null ++++ b/drivers/power/supply/rk818_battery.h +@@ -0,0 +1,168 @@ ++/* ++ * rk818_battery.h: fuel gauge driver structures ++ * ++ * Copyright (C) 2016 Rockchip Electronics Co., Ltd ++ * Author: chenjh ++ * ++ * This program is free software; you can redistribute it and/or modify it ++ * under the terms and conditions of the GNU General Public License, ++ * version 2, as published by the Free Software Foundation. ++ * ++ * This program is distributed in the hope 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. ++ */ ++ ++#ifndef RK818_BATTERY ++#define RK818_BATTERY ++ ++/* RK818_INT_STS_MSK_REG2 */ ++#define PLUG_IN_MSK BIT(0) ++#define PLUG_OUT_MSK BIT(1) ++#define CHRG_CVTLMT_INT_MSK BIT(6) ++ ++/* RK818_TS_CTRL_REG */ ++#define GG_EN BIT(7) ++#define ADC_CUR_EN BIT(6) ++#define ADC_TS1_EN BIT(5) ++#define ADC_TS2_EN BIT(4) ++#define TS1_CUR_MSK 0x03 ++ ++/* RK818_GGCON */ ++#define OCV_SAMP_MIN_MSK 0x0c ++#define OCV_SAMP_8MIN (0x00 << 2) ++ ++#define ADC_CAL_MIN_MSK 0x30 ++#define ADC_CAL_8MIN (0x00 << 4) ++#define ADC_CUR_MODE BIT(1) ++ ++/* RK818_GGSTS */ ++#define BAT_CON BIT(4) ++#define RELAX_VOL1_UPD BIT(3) ++#define RELAX_VOL2_UPD BIT(2) ++#define RELAX_VOL12_UPD_MSK (RELAX_VOL1_UPD | RELAX_VOL2_UPD) ++ ++/* RK818_SUP_STS_REG */ ++#define CHRG_STATUS_MSK 0x70 ++#define BAT_EXS BIT(7) ++#define CHARGE_OFF (0x0 << 4) ++#define DEAD_CHARGE (0x1 << 4) ++#define TRICKLE_CHARGE (0x2 << 4) ++#define CC_OR_CV (0x3 << 4) ++#define CHARGE_FINISH (0x4 << 4) ++#define USB_OVER_VOL (0x5 << 4) ++#define BAT_TMP_ERR (0x6 << 4) ++#define TIMER_ERR (0x7 << 4) ++#define USB_VLIMIT_EN BIT(3) ++#define USB_CLIMIT_EN BIT(2) ++#define USB_EXIST BIT(1) ++#define USB_EFF BIT(0) ++ ++/* RK818_USB_CTRL_REG */ ++#define CHRG_CT_EN BIT(7) ++#define FINISH_CUR_MSK 0xc0 ++#define TEMP_105C (0x02 << 2) ++#define FINISH_100MA (0x00 << 6) ++#define FINISH_150MA (0x01 << 6) ++#define FINISH_200MA (0x02 << 6) ++#define FINISH_250MA (0x03 << 6) ++ ++/* RK818_CHRG_CTRL_REG3 */ ++#define CHRG_TERM_MODE_MSK BIT(5) ++#define CHRG_TERM_ANA_SIGNAL (0 << 5) ++#define CHRG_TERM_DIG_SIGNAL BIT(5) ++#define CHRG_TIMER_CCCV_EN BIT(2) ++#define CHRG_EN BIT(7) ++ ++/* RK818_VB_MON_REG */ ++#define RK818_VBAT_LOW_3V0 0x02 ++#define RK818_VBAT_LOW_3V4 0x06 ++#define PLUG_IN_STS BIT(6) ++ ++/* RK818_THERMAL_REG */ ++#define FB_TEMP_MSK 0x0c ++#define HOTDIE_STS BIT(1) ++ ++/* RK818_INT_STS_MSK_REG1 */ ++#define VB_LOW_INT_EN BIT(1) ++ ++/* RK818_MISC_MARK_REG */ ++#define FG_INIT BIT(5) ++#define FG_RESET_LATE BIT(4) ++#define FG_RESET_NOW BIT(3) ++#define ALGO_REST_MODE_MSK (0xc0) ++#define ALGO_REST_MODE_SHIFT 6 ++ ++/* bit shift */ ++#define FB_TEMP_SHIFT 2 ++ ++/* parse ocv table param */ ++#define TIMER_MS_COUNTS 1000 ++#define MAX_PERCENTAGE 100 ++#define MAX_INTERPOLATE 1000 ++#define MAX_INT 0x7FFF ++ ++#define DRIVER_VERSION "7.1" ++ ++struct battery_platform_data { ++ u32 *ocv_table; ++ u32 *zero_table; ++ u32 *ntc_table; ++ u32 ocv_size; ++ u32 max_chrg_voltage; ++ u32 ntc_size; ++ int ntc_degree_from; ++ u32 pwroff_vol; ++ u32 monitor_sec; ++ u32 zero_algorithm_vol; ++ u32 zero_reserve_dsoc; ++ u32 bat_res; ++ u32 design_capacity; ++ u32 design_qmax; ++ u32 sleep_enter_current; ++ u32 sleep_exit_current; ++ u32 max_soc_offset; ++ u32 sample_res; ++ u32 bat_mode; ++ u32 fb_temp; ++ u32 energy_mode; ++ u32 cccv_hour; ++ u32 ntc_uA; ++ u32 ntc_factor; ++}; ++ ++enum work_mode { ++ MODE_ZERO = 0, ++ MODE_FINISH, ++ MODE_SMOOTH_CHRG, ++ MODE_SMOOTH_DISCHRG, ++ MODE_SMOOTH, ++}; ++ ++enum bat_mode { ++ MODE_BATTARY = 0, ++ MODE_VIRTUAL, ++}; ++ ++static const u16 feedback_temp_array[] = { ++ 85, 95, 105, 115 ++}; ++ ++static const u16 chrg_vol_sel_array[] = { ++ 4050, 4100, 4150, 4200, 4250, 4300, 4350 ++}; ++ ++static const u16 chrg_cur_sel_array[] = { ++ 1000, 1200, 1400, 1600, 1800, 2000, 2250, 2400, 2600, 2800, 3000 ++}; ++ ++static const u16 chrg_cur_input_array[] = { ++ 450, 80, 850, 1000, 1250, 1500, 1750, 2000, 2250, 2500, 2750, 3000 ++}; ++ ++void kernel_power_off(void); ++int rk818_bat_temp_notifier_register(struct notifier_block *nb); ++int rk818_bat_temp_notifier_unregister(struct notifier_block *nb); ++ ++#endif +\ No newline at end of file +diff --git a/include/linux/mfd/rk808.h b/include/linux/mfd/rk808.h +index 2ec0520..5e33996 100644 +--- a/include/linux/mfd/rk808.h ++++ b/include/linux/mfd/rk808.h +@@ -138,6 +138,8 @@ enum rk818_reg { + RK818_ID_OTG_SWITCH, + }; + ++#define RK818_VB_MON_REG 0x21 ++#define RK818_THERMAL_REG 0x22 + #define RK818_DCDC_EN_REG 0x23 + #define RK818_LDO_EN_REG 0x24 + #define RK818_SLEEP_SET_OFF_REG1 0x25 +@@ -184,13 +186,90 @@ enum rk818_reg { + #define RK818_INT_STS_REG2 0x4e + #define RK818_INT_STS_MSK_REG2 0x4f + #define RK818_IO_POL_REG 0x50 ++#define RK818_OTP_VDD_EN_REG 0x51 + #define RK818_H5V_EN_REG 0x52 + #define RK818_SLEEP_SET_OFF_REG3 0x53 + #define RK818_BOOST_LDO9_ON_VSEL_REG 0x54 + #define RK818_BOOST_LDO9_SLP_VSEL_REG 0x55 + #define RK818_BOOST_CTRL_REG 0x56 +-#define RK818_DCDC_ILMAX 0x90 ++#define RK818_DCDC_ILMAX_REG 0x90 ++#define RK818_CHRG_COMP_REG 0x9a ++#define RK818_SUP_STS_REG 0xa0 + #define RK818_USB_CTRL_REG 0xa1 ++#define RK818_CHRG_CTRL_REG1 0xa3 ++#define RK818_CHRG_CTRL_REG2 0xa4 ++#define RK818_CHRG_CTRL_REG3 0xa5 ++#define RK818_BAT_CTRL_REG 0xa6 ++#define RK818_BAT_HTS_TS1_REG 0xa8 ++#define RK818_BAT_LTS_TS1_REG 0xa9 ++#define RK818_BAT_HTS_TS2_REG 0xaa ++#define RK818_BAT_LTS_TS2_REG 0xab ++#define RK818_TS_CTRL_REG 0xac ++#define RK818_ADC_CTRL_REG 0xad ++#define RK818_ON_SOURCE_REG 0xae ++#define RK818_OFF_SOURCE_REG 0xaf ++#define RK818_GGCON_REG 0xb0 ++#define RK818_GGSTS_REG 0xb1 ++#define RK818_FRAME_SMP_INTERV_REG 0xb2 ++#define RK818_AUTO_SLP_CUR_THR_REG 0xb3 ++#define RK818_GASCNT_CAL_REG3 0xb4 ++#define RK818_GASCNT_CAL_REG2 0xb5 ++#define RK818_GASCNT_CAL_REG1 0xb6 ++#define RK818_GASCNT_CAL_REG0 0xb7 ++#define RK818_GASCNT3_REG 0xb8 ++#define RK818_GASCNT2_REG 0xb9 ++#define RK818_GASCNT1_REG 0xba ++#define RK818_GASCNT0_REG 0xbb ++#define RK818_BAT_CUR_AVG_REGH 0xbc ++#define RK818_BAT_CUR_AVG_REGL 0xbd ++#define RK818_TS1_ADC_REGH 0xbe ++#define RK818_TS1_ADC_REGL 0xbf ++#define RK818_TS2_ADC_REGH 0xc0 ++#define RK818_TS2_ADC_REGL 0xc1 ++#define RK818_BAT_OCV_REGH 0xc2 ++#define RK818_BAT_OCV_REGL 0xc3 ++#define RK818_BAT_VOL_REGH 0xc4 ++#define RK818_BAT_VOL_REGL 0xc5 ++#define RK818_RELAX_ENTRY_THRES_REGH 0xc6 ++#define RK818_RELAX_ENTRY_THRES_REGL 0xc7 ++#define RK818_RELAX_EXIT_THRES_REGH 0xc8 ++#define RK818_RELAX_EXIT_THRES_REGL 0xc9 ++#define RK818_RELAX_VOL1_REGH 0xca ++#define RK818_RELAX_VOL1_REGL 0xcb ++#define RK818_RELAX_VOL2_REGH 0xcc ++#define RK818_RELAX_VOL2_REGL 0xcd ++#define RK818_BAT_CUR_R_CALC_REGH 0xce ++#define RK818_BAT_CUR_R_CALC_REGL 0xcf ++#define RK818_BAT_VOL_R_CALC_REGH 0xd0 ++#define RK818_BAT_VOL_R_CALC_REGL 0xd1 ++#define RK818_CAL_OFFSET_REGH 0xd2 ++#define RK818_CAL_OFFSET_REGL 0xd3 ++#define RK818_NON_ACT_TIMER_CNT_REG 0xd4 ++#define RK818_VCALIB0_REGH 0xd5 ++#define RK818_VCALIB0_REGL 0xd6 ++#define RK818_VCALIB1_REGH 0xd7 ++#define RK818_VCALIB1_REGL 0xd8 ++#define RK818_IOFFSET_REGH 0xdd ++#define RK818_IOFFSET_REGL 0xde ++#define RK818_SOC_REG 0xe0 ++#define RK818_REMAIN_CAP_REG3 0xe1 ++#define RK818_REMAIN_CAP_REG2 0xe2 ++#define RK818_REMAIN_CAP_REG1 0xe3 ++#define RK818_REMAIN_CAP_REG0 0xe4 ++#define RK818_UPDAT_LEVE_REG 0xe5 ++#define RK818_NEW_FCC_REG3 0xe6 ++#define RK818_NEW_FCC_REG2 0xe7 ++#define RK818_NEW_FCC_REG1 0xe8 ++#define RK818_NEW_FCC_REG0 0xe9 ++#define RK818_NON_ACT_TIMER_CNT_SAVE_REG 0xea ++#define RK818_OCV_VOL_VALID_REG 0xeb ++#define RK818_REBOOT_CNT_REG 0xec ++#define RK818_POFFSET_REG 0xed ++#define RK818_MISC_MARK_REG 0xee ++#define RK818_HALT_CNT_REG 0xef ++#define RK818_CALC_REST_REGH 0xf0 ++#define RK818_CALC_REST_REGL 0xf1 ++#define RK818_SAVE_DATA19 0xf2 + + #define RK818_H5V_EN BIT(0) + #define RK818_REF_RDY_CTRL BIT(1) diff --git a/sys-kernel/pinephone-pro-sources/files/0012-power-supply-rk818-battery-Use-a-more-propper-compat.patch b/sys-kernel/pinephone-pro-sources/files/0012-power-supply-rk818-battery-Use-a-more-propper-compat.patch new file mode 100644 index 0000000..fd97202 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0012-power-supply-rk818-battery-Use-a-more-propper-compat.patch @@ -0,0 +1,46 @@ +From: Ondrej Jirman +Date: Sun, 7 Nov 2021 19:30:07 +0100 +Subject: [PATCH 13/36] power: supply: rk818-battery: Use a more propper + compatible string + +Prefix with vendor name. + +Signed-off-by: Ondrej Jirman +--- + drivers/mfd/rk808.c | 2 +- + drivers/power/supply/rk818_battery.c | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/drivers/mfd/rk808.c b/drivers/mfd/rk808.c +index 7d1f000..a99fec0 100644 +--- a/drivers/mfd/rk808.c ++++ b/drivers/mfd/rk808.c +@@ -205,7 +205,7 @@ static const struct mfd_cell rk817s[] = { + static const struct mfd_cell rk818s[] = { + { .name = "rk808-clkout", }, + { .name = "rk808-regulator", }, +- { .name = "rk818-battery", .of_compatible = "rk818-battery", }, ++ { .name = "rk818-battery", .of_compatible = "rockchip,rk818-battery", }, + { + .name = "rk808-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), +diff --git a/drivers/power/supply/rk818_battery.c b/drivers/power/supply/rk818_battery.c +index f09f456..665f043 100644 +--- a/drivers/power/supply/rk818_battery.c ++++ b/drivers/power/supply/rk818_battery.c +@@ -3339,7 +3339,7 @@ static int rk818_bat_parse_dt(struct rk818_battery *di) + #endif + + static const struct of_device_id rk818_battery_of_match[] = { +- {.compatible = "rk818-battery",}, ++ { .compatible = "rockchip,rk818-battery", }, + { }, + }; + +@@ -3565,4 +3565,4 @@ module_exit(battery_exit); + + MODULE_LICENSE("GPL"); + MODULE_ALIAS("platform:rk818-battery"); +-MODULE_AUTHOR("chenjh"); +\ No newline at end of file ++MODULE_AUTHOR("chenjh"); diff --git a/sys-kernel/pinephone-pro-sources/files/0013-power-supply-core-Don-t-ignore-max_current-of-0-when.patch b/sys-kernel/pinephone-pro-sources/files/0013-power-supply-core-Don-t-ignore-max_current-of-0-when.patch new file mode 100644 index 0000000..69f1677 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0013-power-supply-core-Don-t-ignore-max_current-of-0-when.patch @@ -0,0 +1,85 @@ +From: Ondrej Jirman +Date: Sun, 14 Nov 2021 21:24:05 +0100 +Subject: [PATCH 14/36] power: supply: core: Don't ignore max_current of 0 + when setting current limit + +If we ignore current limit of 0, the dependent power source will not +set input current limit to that value when the supplier changes max +current to 0. This may happen when USB power is disconnected from the +device. + +On next connection, the dependent power supply will start consuming +power at the previously set limit even before the PD/BC1.2 power +negotiation has a chance to complete. + +Signed-off-by: Ondrej Jirman +--- + drivers/power/supply/power_supply_core.c | 47 ++++++++++++++------------------ + 1 file changed, 20 insertions(+), 27 deletions(-) + +diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c +index 6093754..1c5be0d 100644 +--- a/drivers/power/supply/power_supply_core.c ++++ b/drivers/power/supply/power_supply_core.c +@@ -375,41 +375,34 @@ int power_supply_is_system_supplied(void) + } + EXPORT_SYMBOL_GPL(power_supply_is_system_supplied); + +-static int __power_supply_get_supplier_max_current(struct device *dev, +- void *data) +-{ +- union power_supply_propval ret = {0,}; +- struct power_supply *epsy = dev_get_drvdata(dev); +- struct power_supply *psy = data; +- +- if (__power_supply_is_supplied_by(epsy, psy)) +- if (!epsy->desc->get_property(epsy, +- POWER_SUPPLY_PROP_CURRENT_MAX, +- &ret)) +- return ret.intval; +- +- return 0; +-} +- + int power_supply_set_input_current_limit_from_supplier(struct power_supply *psy) + { + union power_supply_propval val = {0,}; +- int curr; ++ struct class_dev_iter iter; ++ struct power_supply *epsy; ++ struct device *dev; ++ int ret; + + if (!psy->desc->set_property) + return -EINVAL; + +- /* +- * This function is not intended for use with a supply with multiple +- * suppliers, we simply pick the first supply to report a non 0 +- * max-current. +- */ +- curr = class_for_each_device(power_supply_class, NULL, psy, +- __power_supply_get_supplier_max_current); +- if (curr <= 0) +- return (curr == 0) ? -ENODEV : curr; ++ class_dev_iter_init(&iter, power_supply_class, NULL, NULL); ++ while ((dev = class_dev_iter_next(&iter))) { ++ epsy = dev_get_drvdata(dev); ++ ++ if (!__power_supply_is_supplied_by(epsy, psy)) ++ continue; + +- val.intval = curr; ++ ret = epsy->desc->get_property(epsy, ++ POWER_SUPPLY_PROP_CURRENT_MAX, ++ &val); ++ if (!ret) ++ break; ++ } ++ class_dev_iter_exit(&iter); ++ ++ if (ret) ++ return ret; + + return psy->desc->set_property(psy, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val); diff --git a/sys-kernel/pinephone-pro-sources/files/0014-power-supply-rk818-charger-Implement-charger-driver-.patch b/sys-kernel/pinephone-pro-sources/files/0014-power-supply-rk818-charger-Implement-charger-driver-.patch new file mode 100644 index 0000000..232c70e --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0014-power-supply-rk818-charger-Implement-charger-driver-.patch @@ -0,0 +1,699 @@ +From: Ondrej Jirman +Date: Sun, 7 Nov 2021 20:09:02 +0100 +Subject: [PATCH 15/36] power: supply: rk818-charger: Implement charger driver + for RK818 PMIC + +For now this driver is just meant to watch Type-C power supply +and apply current limits to RK818, to not overload the Type-C +partner. + +Signed-off-by: Ondrej Jirman +--- + drivers/mfd/rk808.c | 1 + + drivers/power/supply/Kconfig | 8 + + drivers/power/supply/Makefile | 1 + + drivers/power/supply/rk818_charger.c | 637 +++++++++++++++++++++++++++++++++++ + 4 files changed, 647 insertions(+) + create mode 100644 drivers/power/supply/rk818_charger.c + +diff --git a/drivers/mfd/rk808.c b/drivers/mfd/rk808.c +index a99fec0..75d18de 100644 +--- a/drivers/mfd/rk808.c ++++ b/drivers/mfd/rk808.c +@@ -206,6 +206,7 @@ static const struct mfd_cell rk818s[] = { + { .name = "rk808-clkout", }, + { .name = "rk808-regulator", }, + { .name = "rk818-battery", .of_compatible = "rockchip,rk818-battery", }, ++ { .name = "rk818-charger", .of_compatible = "rockchip,rk818-charger", }, + { + .name = "rk808-rtc", + .num_resources = ARRAY_SIZE(rtc_resources), +diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig +index f5d4434..6bf0834 100644 +--- a/drivers/power/supply/Kconfig ++++ b/drivers/power/supply/Kconfig +@@ -862,4 +862,12 @@ config BATTERY_RK818 + If you say yes here you will get support for the battery of RK818 PMIC. + This driver can give support for Rk818 Battery Charge Interface. + ++config CHARGER_RK818 ++ bool "RK818 Charger driver" ++ depends on MFD_RK808 ++ default n ++ help ++ If you say yes here you will get support for the charger of RK818 PMIC. ++ This driver can give support for Rk818 Charger Interface. ++ + endif # POWER_SUPPLY +diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile +index 1c725ee..3a1526f 100644 +--- a/drivers/power/supply/Makefile ++++ b/drivers/power/supply/Makefile +@@ -105,3 +105,4 @@ obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o + obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o + obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o + obj-$(CONFIG_BATTERY_RK818) += rk818_battery.o ++obj-$(CONFIG_CHARGER_RK818) += rk818_charger.o +diff --git a/drivers/power/supply/rk818_charger.c b/drivers/power/supply/rk818_charger.c +new file mode 100644 +index 00000000..097f30b +--- /dev/null ++++ b/drivers/power/supply/rk818_charger.c +@@ -0,0 +1,637 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * rk818 usb power driver ++ * ++ * Copyright (c) 2021 Ondřej Jirman ++ */ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define RK818_CHG_STS_MASK (7u << 4) /* charger status */ ++#define RK818_CHG_STS_NONE (0u << 4) ++#define RK818_CHG_STS_WAKEUP_CUR (1u << 4) ++#define RK818_CHG_STS_TRICKLE_CUR (2u << 4) ++#define RK818_CHG_STS_CC_OR_CV (3u << 4) ++#define RK818_CHG_STS_TERMINATED (4u << 4) ++#define RK818_CHG_STS_USB_OV (5u << 4) ++#define RK818_CHG_STS_BAT_TEMP_FAULT (6u << 4) ++#define RK818_CHG_STS_TIMEOUT (7u << 4) ++ ++/* RK818_SUP_STS_REG */ ++#define RK818_SUP_STS_USB_VLIM_EN BIT(3) /* input voltage limit enable */ ++#define RK818_SUP_STS_USB_ILIM_EN BIT(2) /* input current limit enable */ ++#define RK818_SUP_STS_USB_EXS BIT(1) /* USB power connected */ ++#define RK818_SUP_STS_USB_EFF BIT(0) /* USB fault */ ++ ++/* RK818_USB_CTRL_REG */ ++#define RK818_USB_CTRL_USB_ILIM_MASK (0xfu) ++#define RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET 4 ++#define RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK (0x7u << 4) ++ ++/* RK818_CHRG_CTRL_REG1 */ ++#define RK818_CHRG_CTRL_REG1_CHRG_EN BIT(7) ++#define RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET 4 ++#define RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK (0x7u << 4) ++#define RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET 0 ++#define RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK (0xfu << 0) ++ ++/* RK818_CHRG_CTRL_REG3 */ ++#define RK818_CHRG_CTRL_REG3_CHRG_TERM_DIGITAL BIT(5) ++ ++struct rk818_charger { ++ struct device *dev; ++ struct rk808 *rk818; ++ struct regmap *regmap; ++ ++ struct power_supply *usb_psy; ++ struct power_supply *charger_psy; ++}; ++ ++// {{{ USB supply ++ ++static int rk818_usb_set_input_current_max(struct rk818_charger *cg, ++ int val) ++{ ++ int ret; ++ unsigned reg; ++ ++ if (val < 450000) ++ reg = 1; ++ else if (val < 850000) ++ reg = 0; ++ else if (val < 1000000) ++ reg = 2; ++ else if (val < 3000000) ++ reg = 3 + (val - 1000000) / 250000; ++ else ++ reg = 11; ++ ++ ret = regmap_update_bits(cg->regmap, RK818_USB_CTRL_REG, ++ RK818_USB_CTRL_USB_ILIM_MASK, reg); ++ if (ret) ++ dev_err(cg->dev, ++ "USB input current limit setting failed (%d)\n", ret); ++ ++ return ret; ++} ++ ++static int rk818_usb_get_input_current_max(struct rk818_charger *cg, ++ int *val) ++{ ++ unsigned reg; ++ int ret; ++ ++ ret = regmap_read(cg->regmap, RK818_USB_CTRL_REG, ®); ++ if (ret) { ++ dev_err(cg->dev, ++ "USB input current limit getting failed (%d)\n", ret); ++ return ret; ++ } ++ ++ reg &= RK818_USB_CTRL_USB_ILIM_MASK; ++ if (reg == 0) ++ *val = 450000; ++ else if (reg == 1) ++ *val = 80000; ++ else if (reg == 2) ++ *val = 850000; ++ else if (reg < 11) ++ *val = 1000000 + (reg - 3) * 250000; ++ else ++ *val = 3000000; ++ ++ return 0; ++} ++ ++static int rk818_usb_set_input_voltage_min(struct rk818_charger *cg, ++ int val) ++{ ++ unsigned reg; ++ int ret; ++ ++ if (val < 2780000) ++ reg = 0; ++ else if (val < 3270000) ++ reg = (val - 2780000) / 70000; ++ else ++ reg = 7; ++ ++ ret = regmap_update_bits(cg->regmap, RK818_USB_CTRL_REG, ++ RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK, ++ reg << RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET); ++ if (ret) ++ dev_err(cg->dev, ++ "USB input voltage limit setting failed (%d)\n", ret); ++ ++ return ret; ++} ++ ++static int rk818_usb_get_input_voltage_min(struct rk818_charger *cg, ++ int *val) ++{ ++ unsigned reg; ++ int ret; ++ ++ ret = regmap_read(cg->regmap, RK818_USB_CTRL_REG, ®); ++ if (ret) { ++ dev_err(cg->dev, ++ "USB input voltage limit getting failed (%d)\n", ret); ++ return ret; ++ } ++ ++ reg &= RK818_USB_CTRL_USB_CHG_SD_VSEL_MASK; ++ reg >>= RK818_USB_CTRL_USB_CHG_SD_VSEL_OFFSET; ++ ++ *val = 2780000 + (reg * 70000); ++ ++ return 0; ++} ++ ++static int rk818_usb_power_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct rk818_charger *cg = power_supply_get_drvdata(psy); ++ unsigned reg; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_PRESENT: ++ ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); ++ if (ret) ++ return ret; ++ ++ val->intval = !!(reg & RK818_SUP_STS_USB_EXS); ++ break; ++ ++ case POWER_SUPPLY_PROP_HEALTH: ++ ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); ++ if (ret) ++ return ret; ++ ++ if (!(reg & RK818_SUP_STS_USB_EXS)) { ++ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; ++ } else if (reg & RK818_SUP_STS_USB_EFF) { ++ val->intval = POWER_SUPPLY_HEALTH_GOOD; ++ } else { ++ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; ++ } ++ ++ break; ++ ++ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: ++ return rk818_usb_get_input_voltage_min(cg, &val->intval); ++ ++ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ++ return rk818_usb_get_input_current_max(cg, &val->intval); ++ ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int rk818_usb_power_set_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ const union power_supply_propval *val) ++{ ++ struct rk818_charger *cg = power_supply_get_drvdata(psy); ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: ++ return rk818_usb_set_input_voltage_min(cg, val->intval); ++ ++ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ++ return rk818_usb_set_input_current_max(cg, val->intval); ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int rk818_usb_power_prop_writeable(struct power_supply *psy, ++ enum power_supply_property psp) ++{ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: ++ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: ++ return 1; ++ ++ default: ++ return 0; ++ } ++} ++ ++/* Sync the input-current-limit with our parent supply (if we have one) */ ++static void rk818_usb_power_external_power_changed(struct power_supply *psy) ++{ ++ struct rk818_charger *cg = power_supply_get_drvdata(psy); ++ ++ power_supply_set_input_current_limit_from_supplier(cg->usb_psy); ++} ++ ++static enum power_supply_property rk818_usb_power_props[] = { ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_HEALTH, ++ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, ++ POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, ++}; ++ ++static const struct power_supply_desc rk818_usb_desc = { ++ .name = "rk818-usb", ++ .type = POWER_SUPPLY_TYPE_USB, ++ .properties = rk818_usb_power_props, ++ .num_properties = ARRAY_SIZE(rk818_usb_power_props), ++ .property_is_writeable = rk818_usb_power_prop_writeable, ++ .get_property = rk818_usb_power_get_property, ++ .set_property = rk818_usb_power_set_property, ++ .external_power_changed = rk818_usb_power_external_power_changed, ++}; ++ ++// }}} ++// {{{ Charger supply ++ ++static int rk818_charger_set_current_max(struct rk818_charger *cg, int val) ++{ ++ unsigned reg; ++ int ret; ++ ++ if (val < 1000000) ++ reg = 0; ++ else if (val < 3000000) ++ reg = (val - 1000000) / 200000; ++ else ++ reg = 10; ++ ++ ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, ++ RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK, ++ reg << RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET); ++ if (ret) ++ dev_err(cg->dev, ++ "Charging max current setting failed (%d)\n", ret); ++ ++ return ret; ++} ++ ++static int rk818_charger_get_current_max(struct rk818_charger *cg, int *val) ++{ ++ unsigned reg; ++ int ret; ++ ++ ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); ++ if (ret) { ++ dev_err(cg->dev, ++ "Charging max current getting failed (%d)\n", ret); ++ return ret; ++ } ++ ++ reg &= RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_MASK; ++ reg >>= RK818_CHRG_CTRL_REG1_CHRG_CUR_SEL_OFFSET; ++ ++ *val = 1000000 + reg * 200000; ++ ++ return 0; ++} ++ ++static int rk818_charger_set_voltage_max(struct rk818_charger *cg, int val) ++{ ++ unsigned reg; ++ int ret; ++ ++ if (val < 4050000) ++ reg = 0; ++ else if (val < 4350000) ++ reg = (val - 4050000) / 50000; ++ else ++ reg = 6; ++ ++ ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, ++ RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK, ++ reg << RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET); ++ if (ret) ++ dev_err(cg->dev, ++ "Charging end voltage setting failed (%d)\n", ret); ++ ++ return ret; ++} ++ ++static int rk818_charger_get_voltage_max(struct rk818_charger *cg, int *val) ++{ ++ unsigned reg; ++ int ret; ++ ++ ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); ++ if (ret) { ++ dev_err(cg->dev, ++ "Charging end voltage getting failed (%d)\n", ret); ++ return ret; ++ } ++ ++ reg &= RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_MASK; ++ reg >>= RK818_CHRG_CTRL_REG1_CHRG_VOL_SEL_OFFSET; ++ ++ *val = 4050000 + reg * 50000; ++ ++ return 0; ++} ++ ++static int rk818_charger_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct rk818_charger *cg = power_supply_get_drvdata(psy); ++ unsigned reg; ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG1, ®); ++ if (ret) { ++ dev_err(cg->dev, "failed to read the charger state (%d)\n", ret); ++ return ret; ++ } ++ ++ val->intval = !!(reg & RK818_CHRG_CTRL_REG1_CHRG_EN); ++ break; ++ ++ case POWER_SUPPLY_PROP_STATUS: ++ ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); ++ if (ret) ++ return ret; ++ ++ switch (reg & RK818_CHG_STS_MASK) { ++ case RK818_CHG_STS_WAKEUP_CUR: ++ case RK818_CHG_STS_TRICKLE_CUR: ++ case RK818_CHG_STS_CC_OR_CV: ++ val->intval = POWER_SUPPLY_STATUS_CHARGING; ++ break; ++ case RK818_CHG_STS_TERMINATED: ++ default: ++ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; ++ break; ++ } ++ ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_TYPE: ++ ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); ++ if (ret) ++ return ret; ++ ++ switch (reg & RK818_CHG_STS_MASK) { ++ case RK818_CHG_STS_WAKEUP_CUR: ++ case RK818_CHG_STS_TRICKLE_CUR: ++ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; ++ break; ++ case RK818_CHG_STS_CC_OR_CV: ++ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; ++ break; ++ case RK818_CHG_STS_TERMINATED: ++ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; ++ break; ++ default: ++ val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; ++ break; ++ } ++ ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: ++ ret = regmap_read(cg->regmap, RK818_CHRG_CTRL_REG2, ®); ++ if (ret) ++ return ret; ++ ++ val->intval = 100000 + ((reg >> 6) & 3) * 50000; ++ break; ++ ++ case POWER_SUPPLY_PROP_HEALTH: ++ ret = regmap_read(cg->regmap, RK818_SUP_STS_REG, ®); ++ if (ret) ++ return ret; ++ ++ switch (reg & RK818_CHG_STS_MASK) { ++ case RK818_CHG_STS_USB_OV: ++ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; ++ break; ++ case RK818_CHG_STS_BAT_TEMP_FAULT: ++ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; ++ break; ++ case RK818_CHG_STS_TIMEOUT: ++ val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; ++ break; ++ default: ++ val->intval = POWER_SUPPLY_HEALTH_GOOD; ++ break; ++ } ++ ++ break; ++ ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ++ return rk818_charger_get_voltage_max(cg, &val->intval); ++ ++ case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: ++ ret = rk818_charger_get_current_max(cg, &val->intval); ++ val->intval /= 10; ++ return ret; ++ ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ return rk818_charger_get_current_max(cg, &val->intval); ++ ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: ++ val->intval = 4350000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: ++ val->intval = 3000000; ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int rk818_charger_set_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ const union power_supply_propval *val) ++{ ++ struct rk818_charger *cg = power_supply_get_drvdata(psy); ++ int ret; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG1, ++ RK818_CHRG_CTRL_REG1_CHRG_EN, ++ val->intval ? RK818_CHRG_CTRL_REG1_CHRG_EN : 0); ++ if (ret) ++ dev_err(cg->dev, "failed to setup the charger (%d)\n", ret); ++ ++ return ret; ++ ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ++ return rk818_charger_set_voltage_max(cg, val->intval); ++ ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ return rk818_charger_set_current_max(cg, val->intval); ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static int rk818_charger_prop_writeable(struct power_supply *psy, ++ enum power_supply_property psp) ++{ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: ++ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: ++ case POWER_SUPPLY_PROP_ONLINE: ++ return 1; ++ ++ default: ++ return 0; ++ } ++} ++ ++static enum power_supply_property rk818_charger_props[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++ POWER_SUPPLY_PROP_HEALTH, ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_CHARGE_TYPE, ++ POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, ++ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, ++ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, ++ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, ++ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, ++ POWER_SUPPLY_PROP_PRECHARGE_CURRENT, ++}; ++ ++/* ++ * TODO: This functionality should be in a battery driver/supply, but that one ++ * is such a mess, I don't want to touch it now. Let's have a separate supply ++ * for controlling the charger for now, and a prayer for the poor soul that ++ * will have to understand and clean up the battery driver. ++ */ ++static const struct power_supply_desc rk818_charger_desc = { ++ .name = "rk818-charger", ++ .type = POWER_SUPPLY_TYPE_MAINS, ++ .properties = rk818_charger_props, ++ .num_properties = ARRAY_SIZE(rk818_charger_props), ++ .property_is_writeable = rk818_charger_prop_writeable, ++ .get_property = rk818_charger_get_property, ++ .set_property = rk818_charger_set_property, ++}; ++ ++// }}} ++ ++static int rk818_charger_probe(struct platform_device *pdev) ++{ ++ struct rk808 *rk818 = dev_get_drvdata(pdev->dev.parent); ++ struct power_supply_config psy_cfg = { }; ++ struct device *dev = &pdev->dev; ++ struct rk818_charger *cg; ++ int ret; ++ ++ cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL); ++ if (!cg) ++ return -ENOMEM; ++ ++ cg->rk818 = rk818; ++ cg->dev = dev; ++ cg->regmap = rk818->regmap; ++ platform_set_drvdata(pdev, cg); ++ ++ psy_cfg.drv_data = cg; ++ psy_cfg.of_node = dev->of_node; ++ ++ cg->usb_psy = devm_power_supply_register(dev, &rk818_usb_desc, ++ &psy_cfg); ++ if (IS_ERR(cg->usb_psy)) ++ return dev_err_probe(dev, PTR_ERR(cg->usb_psy), ++ "register usb power supply fail\n"); ++ ++ cg->charger_psy = devm_power_supply_register(dev, &rk818_charger_desc, ++ &psy_cfg); ++ if (IS_ERR(cg->charger_psy)) ++ return dev_err_probe(dev, PTR_ERR(cg->charger_psy), ++ "register charger power supply fail\n"); ++ ++ /* disable voltage limit and enable input current limit */ ++ ret = regmap_update_bits(cg->regmap, RK818_SUP_STS_REG, ++ RK818_SUP_STS_USB_ILIM_EN | RK818_SUP_STS_USB_VLIM_EN, ++ RK818_SUP_STS_USB_ILIM_EN); ++ if (ret) ++ dev_warn(cg->dev, "failed to enable input current limit (%d)\n", ret); ++ ++ /* make sure analog control loop is enabled */ ++ ret = regmap_update_bits(cg->regmap, RK818_CHRG_CTRL_REG3, ++ RK818_CHRG_CTRL_REG3_CHRG_TERM_DIGITAL, ++ 0); ++ if (ret) ++ dev_warn(cg->dev, "failed to enable analog control loop (%d)\n", ret); ++ ++ /* enable charger and set some reasonable limits on each boot */ ++ ret = regmap_write(cg->regmap, RK818_CHRG_CTRL_REG1, ++ RK818_CHRG_CTRL_REG1_CHRG_EN ++ | (1) /* 1.2A */ ++ | (5 << 4) /* 4.3V */); ++ if (ret) ++ dev_warn(cg->dev, "failed to enable charger (%d)\n", ret); ++ ++ power_supply_set_input_current_limit_from_supplier(cg->usb_psy); ++ ++ return 0; ++} ++ ++static int rk818_charger_remove(struct platform_device *pdev) ++{ ++ //struct rk818_charger *cg = platform_get_drvdata(pdev); ++ ++ return 0; ++} ++ ++static void rk818_charger_shutdown(struct platform_device *pdev) ++{ ++} ++ ++static int rk818_charger_suspend(struct platform_device *pdev, ++ pm_message_t state) ++{ ++ return 0; ++} ++ ++static int rk818_charger_resume(struct platform_device *pdev) ++{ ++ return 0; ++} ++ ++static const struct of_device_id rk818_charger_of_match[] = { ++ { .compatible = "rockchip,rk818-charger", }, ++ { }, ++}; ++ ++static struct platform_driver rk818_charger_driver = { ++ .probe = rk818_charger_probe, ++ .remove = rk818_charger_remove, ++ .suspend = rk818_charger_suspend, ++ .resume = rk818_charger_resume, ++ .shutdown = rk818_charger_shutdown, ++ .driver = { ++ .name = "rk818-charger", ++ .of_match_table = rk818_charger_of_match, ++ }, ++}; ++ ++module_platform_driver(rk818_charger_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("platform:rk818-charger"); ++MODULE_AUTHOR("Ondřej Jirman "); diff --git a/sys-kernel/pinephone-pro-sources/files/0015-usb-typec-fusb302-Set-the-current-before-enabling-pu.patch b/sys-kernel/pinephone-pro-sources/files/0015-usb-typec-fusb302-Set-the-current-before-enabling-pu.patch new file mode 100644 index 0000000..f42aed2 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0015-usb-typec-fusb302-Set-the-current-before-enabling-pu.patch @@ -0,0 +1,47 @@ +From: Ondrej Jirman +Date: Sun, 7 Nov 2021 19:28:27 +0100 +Subject: [PATCH 26/36] usb: typec: fusb302: Set the current before enabling + pullups + +This seems more reasonable and should avoid short period of incorrect +current setting being applied to CC pin. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index 72f9001..776a949 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -635,6 +635,14 @@ static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) + goto done; + } + ++ /* adjust current for SRC */ ++ ret = fusb302_set_src_current(chip, cc_src_current[cc]); ++ if (ret < 0) { ++ fusb302_log(chip, "cannot set src current %s, ret=%d", ++ typec_cc_status_name[cc], ret); ++ goto done; ++ } ++ + ret = fusb302_i2c_mask_write(chip, FUSB_REG_SWITCHES0, + switches0_mask, switches0_data); + if (ret < 0) { +@@ -645,14 +653,6 @@ static int tcpm_set_cc(struct tcpc_dev *dev, enum typec_cc_status cc) + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; + +- /* adjust current for SRC */ +- ret = fusb302_set_src_current(chip, cc_src_current[cc]); +- if (ret < 0) { +- fusb302_log(chip, "cannot set src current %s, ret=%d", +- typec_cc_status_name[cc], ret); +- goto done; +- } +- + /* enable/disable interrupts, BC_LVL for SNK and COMP_CHNG for SRC */ + switch (cc) { + case TYPEC_CC_RP_DEF: diff --git a/sys-kernel/pinephone-pro-sources/files/0016-usb-typec-fusb302-Extend-debugging-interface-with-dr.patch b/sys-kernel/pinephone-pro-sources/files/0016-usb-typec-fusb302-Extend-debugging-interface-with-dr.patch new file mode 100644 index 0000000..0ab3229 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0016-usb-typec-fusb302-Extend-debugging-interface-with-dr.patch @@ -0,0 +1,108 @@ +From: Ondrej Jirman +Date: Sun, 7 Nov 2021 19:29:06 +0100 +Subject: [PATCH 27/36] usb: typec: fusb302: Extend debugging interface with + driver state dumps + +This is useful for debugging. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 78 ++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 78 insertions(+) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index 776a949..1a758e3 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -207,6 +207,81 @@ static int fusb302_debug_show(struct seq_file *s, void *v) + } + DEFINE_SHOW_ATTRIBUTE(fusb302_debug); + ++static const char * const typec_cc_status_name[]; ++static const char * const cc_polarity_name[]; ++static const char * const toggling_mode_name[] = { ++ [TOGGLING_MODE_OFF] = "Off", ++ [TOGGLING_MODE_DRP] = "DRP", ++ [TOGGLING_MODE_SNK] = "SNK", ++ [TOGGLING_MODE_SRC] = "SRC", ++}; ++static const char * const src_current_status_name[] = { ++ [SRC_CURRENT_DEFAULT] = "Default", ++ [SRC_CURRENT_MEDIUM] = "Medium", ++ [SRC_CURRENT_HIGH] = "High", ++}; ++ ++#define FUSB_REG(n) { n, #n }, ++struct fusb_reg { ++ u8 addr; ++ const char* name; ++} fusb_regs[] = { ++ FUSB_REG(FUSB_REG_DEVICE_ID) ++ FUSB_REG(FUSB_REG_SWITCHES0) ++ FUSB_REG(FUSB_REG_SWITCHES1) ++ FUSB_REG(FUSB_REG_MEASURE) ++ FUSB_REG(FUSB_REG_CONTROL0) ++ FUSB_REG(FUSB_REG_CONTROL1) ++ FUSB_REG(FUSB_REG_CONTROL2) ++ FUSB_REG(FUSB_REG_CONTROL3) ++ FUSB_REG(FUSB_REG_MASK) ++ FUSB_REG(FUSB_REG_POWER) ++ FUSB_REG(FUSB_REG_RESET) ++ FUSB_REG(FUSB_REG_MASKA) ++ FUSB_REG(FUSB_REG_MASKB) ++ FUSB_REG(FUSB_REG_STATUS0A) ++ FUSB_REG(FUSB_REG_STATUS1A) ++ FUSB_REG(FUSB_REG_INTERRUPTA) ++ FUSB_REG(FUSB_REG_INTERRUPTB) ++ FUSB_REG(FUSB_REG_STATUS0) ++ FUSB_REG(FUSB_REG_STATUS1) ++ FUSB_REG(FUSB_REG_INTERRUPT) ++}; ++ ++static int fusb302_i2c_read(struct fusb302_chip *chip, ++ u8 address, u8 *data); ++ ++static int fusb302_debug_regs_show(struct seq_file *s, void *v) ++{ ++ struct fusb302_chip *chip = (struct fusb302_chip *)s->private; ++ int i, ret; ++ ++ seq_printf(s, "chip->intr_togdone = %d\n", chip->intr_togdone); ++ seq_printf(s, "chip->intr_bc_lvl = %d\n", chip->intr_bc_lvl); ++ seq_printf(s, "chip->intr_comp_chng = %d\n", chip->intr_comp_chng); ++ seq_printf(s, "chip->vconn_on = %d\n", chip->vconn_on); ++ seq_printf(s, "chip->vbus_on = %d\n", chip->vbus_on); ++ seq_printf(s, "chip->charge_on = %d\n", chip->charge_on); ++ seq_printf(s, "chip->vbus_present = %d\n", chip->vbus_present); ++ seq_printf(s, "chip->cc_polarity = %s\n", cc_polarity_name[chip->cc_polarity]); ++ seq_printf(s, "chip->cc1 = %s\n", typec_cc_status_name[chip->cc1]); ++ seq_printf(s, "chip->cc2 = %s\n", typec_cc_status_name[chip->cc2]); ++ seq_printf(s, "chip->toggling_mode = %s\n", toggling_mode_name[chip->toggling_mode]); ++ seq_printf(s, "chip->src_current_status = %s\n", src_current_status_name[chip->src_current_status]); ++ ++ seq_printf(s, "\nRegisters:\n"); ++ for (i = 0; i < ARRAY_SIZE(fusb_regs); i++) { ++ u8 val = 0; ++ ++ ret = fusb302_i2c_read(chip, fusb_regs[i].addr, &val); ++ if (ret >= 0) ++ seq_printf(s, "%s = %02hhx\n", fusb_regs[i].name, val); ++ } ++ ++ return 0; ++} ++DEFINE_SHOW_ATTRIBUTE(fusb302_debug_regs); ++ + static void fusb302_debugfs_init(struct fusb302_chip *chip) + { + char name[NAME_MAX]; +@@ -216,6 +291,9 @@ static void fusb302_debugfs_init(struct fusb302_chip *chip) + chip->dentry = debugfs_create_dir(name, usb_debug_root); + debugfs_create_file("log", S_IFREG | 0444, chip->dentry, chip, + &fusb302_debug_fops); ++ ++ debugfs_create_file("regs", S_IFREG | 0444, chip->dentry, chip, ++ &fusb302_debug_regs_fops); + } + + static void fusb302_debugfs_exit(struct fusb302_chip *chip) diff --git a/sys-kernel/pinephone-pro-sources/files/0017-usb-typec-fusb302-Retry-reading-of-CC-pins-status-if.patch b/sys-kernel/pinephone-pro-sources/files/0017-usb-typec-fusb302-Retry-reading-of-CC-pins-status-if.patch new file mode 100644 index 0000000..096673e --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0017-usb-typec-fusb302-Retry-reading-of-CC-pins-status-if.patch @@ -0,0 +1,72 @@ +From: Ondrej Jirman +Date: Tue, 23 Nov 2021 17:53:27 +0100 +Subject: [PATCH 28/36] usb: typec: fusb302: Retry reading of CC pins status + if activity is detected + +This is just for testing, to see if this ever happens. It should +also help when this happens. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 34 ++++++++++++++++++++++++++++++++-- + 1 file changed, 32 insertions(+), 2 deletions(-) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index 1a758e3..7386805 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -1317,6 +1317,36 @@ static int fusb302_handle_togdone_snk(struct fusb302_chip *chip, + return ret; + } + ++static int fusb302_get_status0_stable(struct fusb302_chip *chip, u8 *status0) ++{ ++ int ret, tries = 0; ++ u8 reg; ++ ++try_again: ++ ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, ®); ++ if (ret < 0) ++ return ret; ++ ++ if (reg & FUSB_REG_STATUS0_ACTIVITY) { ++ fusb302_log(chip, "activity reading CC status"); ++ if (++tries == 5) { ++ fusb302_log(chip, "failed to read stable status0 value"); ++ ++ /* ++ * The best we can do is to return at least something. ++ */ ++ *status0 = reg; ++ return 0; ++ } ++ ++ usleep_range(50, 100); ++ goto try_again; ++ } ++ ++ *status0 = reg; ++ return 0; ++} ++ + /* On error returns < 0, otherwise a typec_cc_status value */ + static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + enum typec_cc_polarity cc_polarity, +@@ -1344,7 +1374,7 @@ static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + return ret; + + usleep_range(50, 100); +- ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); ++ ret = fusb302_get_status0_stable(chip, &status0); + if (ret < 0) + return ret; + +@@ -1360,7 +1390,7 @@ static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + return ret; + + usleep_range(50, 100); +- ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); ++ ret = fusb302_get_status0_stable(chip, &status0); + if (ret < 0) + return ret; + diff --git a/sys-kernel/pinephone-pro-sources/files/0018-usb-typec-fusb302-More-useful-of-logging-status-on-i.patch b/sys-kernel/pinephone-pro-sources/files/0018-usb-typec-fusb302-More-useful-of-logging-status-on-i.patch new file mode 100644 index 0000000..2cec92e --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0018-usb-typec-fusb302-More-useful-of-logging-status-on-i.patch @@ -0,0 +1,186 @@ +From: Ondrej Jirman +Date: Tue, 23 Nov 2021 17:55:34 +0100 +Subject: [PATCH 29/36] usb: typec: fusb302: More useful of logging status on + interrupt + +This is just for debugging. It prints more info that's useful to +see how hardware state changes in time. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 121 +++++++++++++++++++++++++++++++++------ + 1 file changed, 104 insertions(+), 17 deletions(-) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index 7386805..70b0e15 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -68,7 +68,7 @@ static const u8 rd_mda_value[] = { + }; + + #define LOG_BUFFER_ENTRIES 1024 +-#define LOG_BUFFER_ENTRY_SIZE 128 ++#define LOG_BUFFER_ENTRY_SIZE 256 + + struct fusb302_chip { + struct device *dev; +@@ -1598,6 +1598,84 @@ static irqreturn_t fusb302_irq_intn(int irq, void *dev_id) + return IRQ_HANDLED; + } + ++static void fusb302_print_state(struct fusb302_chip *chip) ++{ ++ u8 ctl0, ctl2, measure, status0, status1a, sw0, mask; ++ int ret; ++ ++ ret = fusb302_i2c_read(chip, FUSB_REG_CONTROL0, &ctl0); ++ if (ret < 0) ++ return; ++ ret = fusb302_i2c_read(chip, FUSB_REG_CONTROL2, &ctl2); ++ if (ret < 0) ++ return; ++ ret = fusb302_i2c_read(chip, FUSB_REG_MEASURE, &measure); ++ if (ret < 0) ++ return; ++ ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); ++ if (ret < 0) ++ return; ++ ret = fusb302_i2c_read(chip, FUSB_REG_STATUS1A, &status1a); ++ if (ret < 0) ++ return; ++ ret = fusb302_i2c_read(chip, FUSB_REG_SWITCHES0, &sw0); ++ if (ret < 0) ++ return; ++ ret = fusb302_i2c_read(chip, FUSB_REG_MASK, &mask); ++ if (ret < 0) ++ return; ++ ++ //FUSB_REG(FUSB_REG_POWER) // power control ++ ++ const char* host_cur = "?"; ++ switch ((ctl0 >> 2) & 3) { ++ case 0: host_cur = "none"; break; ++ case 1: host_cur = "80uA"; break; ++ case 2: host_cur = "160uA"; break; ++ case 3: host_cur = "330uA"; break; ++ } ++ ++ const char* bc_lvl = "?"; ++ switch (status0 & 3) { ++ case 0: bc_lvl = "0-200mV"; break; ++ case 1: bc_lvl = "200-660mV"; break; ++ case 2: bc_lvl = "660-1230mV"; break; ++ case 3: bc_lvl = ">1230mV"; break; ++ } ++ ++ // status0 ++ unsigned vbusok = !!(status0 & BIT(7)); ++ unsigned activity = !!(status0 & BIT(6)); ++ unsigned comp = !!(status0 & BIT(5)); ++ unsigned wake = !!(status0 & BIT(2)); ++ ++ // measure ++ unsigned mdac = ((measure & 0x3f) + 1) * 42 * (measure & BIT(6) ? 10 : 1); ++ ++ // status1a ++ unsigned togss = (status1a >> 3) & 7; ++ const char* togss_s = "?"; ++ switch (togss) { ++ case 0: togss_s = "running"; break; ++ case 1: togss_s = "src1"; break; ++ case 2: togss_s = "src2"; break; ++ case 5: togss_s = "snk1"; break; ++ case 6: togss_s = "snk2"; break; ++ case 7: togss_s = "audio"; break; ++ } ++ ++ // ctl2 print as is ++ ++#define SW(n) (!!(sw0 & BIT(n))) ++ ++ fusb302_log(chip, "state: cc(puen=%u%u vconn=%u%u meas=%u%u pdwn=%u%u) " ++ "host_cur=%s mdac=%umV comp=%u bc_lvl=%s vbusok=%u act=%u " ++ "wake=%u togss=%s ctl2=0x%02x mask=0x%02x", ++ SW(6), SW(7), SW(4), SW(5), SW(2), SW(3), SW(0), SW(1), ++ host_cur, mdac, comp, bc_lvl, vbusok, activity, ++ wake, togss_s, ctl2, mask); ++} ++ + static void fusb302_irq_work(struct work_struct *work) + { + struct fusb302_chip *chip = container_of(work, struct fusb302_chip, +@@ -1607,6 +1685,7 @@ static void fusb302_irq_work(struct work_struct *work) + u8 interrupta; + u8 interruptb; + u8 status0; ++ u8 mda; + bool vbus_present; + bool comp_result; + bool intr_togdone; +@@ -1632,9 +1711,10 @@ static void fusb302_irq_work(struct work_struct *work) + ret = fusb302_i2c_read(chip, FUSB_REG_STATUS0, &status0); + if (ret < 0) + goto done; +- fusb302_log(chip, +- "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x, status0: 0x%02x", +- interrupt, interrupta, interruptb, status0); ++ fusb302_log(chip, "IRQ: 0x%02x, a: 0x%02x, b: 0x%02x", ++ interrupt, interrupta, interruptb); ++ ++ fusb302_print_state(chip); + + if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { + vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); +@@ -1646,32 +1726,39 @@ static void fusb302_irq_work(struct work_struct *work) + } + } + +- if ((interrupta & FUSB_REG_INTERRUPTA_TOGDONE) && intr_togdone) { ++ if (interrupta & FUSB_REG_INTERRUPTA_TOGDONE) { + fusb302_log(chip, "IRQ: TOGDONE"); +- ret = fusb302_handle_togdone(chip); +- if (ret < 0) { +- fusb302_log(chip, +- "handle togdone error, ret=%d", ret); +- goto done; ++ if (intr_togdone) { ++ ret = fusb302_handle_togdone(chip); ++ if (ret < 0) { ++ fusb302_log(chip, ++ "handle togdone error, ret=%d", ret); ++ goto done; ++ } + } + } + +- if ((interrupt & FUSB_REG_INTERRUPT_BC_LVL) && intr_bc_lvl) { ++ if (interrupt & FUSB_REG_INTERRUPT_BC_LVL) { + fusb302_log(chip, "IRQ: BC_LVL, handler pending"); + /* + * as BC_LVL interrupt can be affected by PD activity, + * apply delay to for the handler to wait for the PD + * signaling to finish. + */ +- mod_delayed_work(chip->wq, &chip->bc_lvl_handler, +- msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); ++ if (intr_bc_lvl) ++ mod_delayed_work(chip->wq, &chip->bc_lvl_handler, ++ msecs_to_jiffies(T_BC_LVL_DEBOUNCE_DELAY_MS)); + } + +- if ((interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) && intr_comp_chng) { ++ if (interrupt & FUSB_REG_INTERRUPT_COMP_CHNG) { ++ ret = fusb302_i2c_read(chip, FUSB_REG_MEASURE, &mda); ++ if (ret < 0) ++ goto done; ++ + comp_result = !!(status0 & FUSB_REG_STATUS0_COMP); +- fusb302_log(chip, "IRQ: COMP_CHNG, comp=%s", +- comp_result ? "true" : "false"); +- if (comp_result) { ++ fusb302_log(chip, "IRQ: COMP_CHNG, cc* %s mdac (%u mV)", ++ comp_result ? ">" : "<", ((mda & 0x3f) + 1) * 42 * (mda & BIT(6) ? 10 : 1)); ++ if (comp_result && intr_comp_chng) { + /* cc level > Rd_threshold, detach */ + chip->cc1 = TYPEC_CC_OPEN; + chip->cc2 = TYPEC_CC_OPEN; diff --git a/sys-kernel/pinephone-pro-sources/files/0019-usb-typec-fusb302-Update-VBUS-state-even-if-VBUS-int.patch b/sys-kernel/pinephone-pro-sources/files/0019-usb-typec-fusb302-Update-VBUS-state-even-if-VBUS-int.patch new file mode 100644 index 0000000..9335999 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0019-usb-typec-fusb302-Update-VBUS-state-even-if-VBUS-int.patch @@ -0,0 +1,39 @@ +From: Ondrej Jirman +Date: Tue, 23 Nov 2021 17:57:06 +0100 +Subject: [PATCH 30/36] usb: typec: fusb302: Update VBUS state even if VBUS + interrupt is not triggered + +This seems to improve robustness. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index 70b0e15..1d5affa 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -1716,14 +1716,16 @@ static void fusb302_irq_work(struct work_struct *work) + + fusb302_print_state(chip); + +- if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) { +- vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); ++ vbus_present = !!(status0 & FUSB_REG_STATUS0_VBUSOK); ++ if (interrupt & FUSB_REG_INTERRUPT_VBUSOK) + fusb302_log(chip, "IRQ: VBUS_OK, vbus=%s", + vbus_present ? "On" : "Off"); +- if (vbus_present != chip->vbus_present) { +- chip->vbus_present = vbus_present; +- tcpm_vbus_change(chip->tcpm_port); +- } ++ if (vbus_present != chip->vbus_present) { ++ chip->vbus_present = vbus_present; ++ if (!(interrupt & FUSB_REG_INTERRUPT_VBUSOK)) ++ fusb302_log(chip, "IRQ: VBUS changed without interrupt, vbus=%s", ++ vbus_present ? "On" : "Off"); ++ tcpm_vbus_change(chip->tcpm_port); + } + + if (interrupta & FUSB_REG_INTERRUPTA_TOGDONE) { diff --git a/sys-kernel/pinephone-pro-sources/files/0020-usb-typec-fusb302-Make-tcpm-fusb302-logs-less-pollut.patch b/sys-kernel/pinephone-pro-sources/files/0020-usb-typec-fusb302-Make-tcpm-fusb302-logs-less-pollut.patch new file mode 100644 index 0000000..9a880fd --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0020-usb-typec-fusb302-Make-tcpm-fusb302-logs-less-pollut.patch @@ -0,0 +1,160 @@ +From: Ondrej Jirman +Date: Tue, 23 Nov 2021 17:58:05 +0100 +Subject: [PATCH 31/36] usb: typec: fusb302: Make tcpm/fusb302 logs less + polluted by PD comm stuff + +This adds clarity to debugging. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 18 ++++++++++-------- + drivers/usb/typec/tcpm/tcpm.c | 18 ++++++++++-------- + 2 files changed, 20 insertions(+), 16 deletions(-) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index 1d5affa..ae3b930 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -787,7 +787,7 @@ static int tcpm_get_cc(struct tcpc_dev *dev, enum typec_cc_status *cc1, + mutex_lock(&chip->lock); + *cc1 = chip->cc1; + *cc2 = chip->cc2; +- fusb302_log(chip, "cc1=%s, cc2=%s", typec_cc_status_name[*cc1], ++ fusb302_log(chip, "tcpm_get_cc => cc1=%s, cc2=%s (cached)", typec_cc_status_name[*cc1], + typec_cc_status_name[*cc2]); + mutex_unlock(&chip->lock); + +@@ -1073,8 +1073,8 @@ static int fusb302_pd_send_message(struct fusb302_chip *chip, + ret = fusb302_i2c_block_write(chip, FUSB_REG_FIFOS, pos, buf); + if (ret < 0) + return ret; +- fusb302_log(chip, "sending PD message header: %x", msg->header); +- fusb302_log(chip, "sending PD message len: %d", len); ++ //fusb302_log(chip, "sending PD message header: %x", msg->header); ++ //fusb302_log(chip, "sending PD message len: %d", len); + + return ret; + } +@@ -1365,8 +1365,10 @@ static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + if (ret < 0) + return ret; + ++ //XXX resolve activity conflicts while measuring ++ + fusb302_i2c_read(chip, FUSB_REG_SWITCHES0, &status0); +- fusb302_log(chip, "get_src_cc_status switches: 0x%0x", status0); ++ //fusb302_log(chip, "get_src_cc_status switches: 0x%0x", status0); + + /* Step 2: Set compararator volt to differentiate between Open and Rd */ + ret = fusb302_i2c_write(chip, FUSB_REG_MEASURE, rd_mda); +@@ -1378,7 +1380,7 @@ static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + if (ret < 0) + return ret; + +- fusb302_log(chip, "get_src_cc_status rd_mda status0: 0x%0x", status0); ++ //fusb302_log(chip, "get_src_cc_status rd_mda status0: 0x%0x", status0); + if (status0 & FUSB_REG_STATUS0_COMP) { + *cc = TYPEC_CC_OPEN; + return 0; +@@ -1394,7 +1396,7 @@ static int fusb302_get_src_cc_status(struct fusb302_chip *chip, + if (ret < 0) + return ret; + +- fusb302_log(chip, "get_src_cc_status ra_mda status0: 0x%0x", status0); ++ //fusb302_log(chip, "get_src_cc_status ra_mda status0: 0x%0x", status0); + if (status0 & FUSB_REG_STATUS0_COMP) + *cc = TYPEC_CC_RD; + else +@@ -1559,8 +1561,8 @@ static int fusb302_pd_read_message(struct fusb302_chip *chip, + ret = fusb302_i2c_block_read(chip, FUSB_REG_FIFOS, 4, crc); + if (ret < 0) + return ret; +- fusb302_log(chip, "PD message header: %x", msg->header); +- fusb302_log(chip, "PD message len: %d", len); ++ //fusb302_log(chip, "PD message header: %x", msg->header); ++ //fusb302_log(chip, "PD message len: %d", len); + + /* + * Check if we've read off a GoodCRC message. If so then indicate to +diff --git a/drivers/usb/typec/tcpm/tcpm.c b/drivers/usb/typec/tcpm/tcpm.c +index 59d4fa2..0451441 100644 +--- a/drivers/usb/typec/tcpm/tcpm.c ++++ b/drivers/usb/typec/tcpm/tcpm.c +@@ -776,7 +776,7 @@ static void tcpm_debugfs_exit(const struct tcpm_port *port) { } + + static void tcpm_set_cc(struct tcpm_port *port, enum typec_cc_status cc) + { +- tcpm_log(port, "cc:=%d", cc); ++ //tcpm_log(port, "cc:=%d", cc); + port->cc_req = cc; + port->tcpc->set_cc(port->tcpc, cc); + } +@@ -869,10 +869,12 @@ static int tcpm_pd_transmit(struct tcpm_port *port, + unsigned long timeout; + int ret; + ++ /* + if (msg) + tcpm_log(port, "PD TX, header: %#x", le16_to_cpu(msg->header)); + else + tcpm_log(port, "PD TX, type: %#x", type); ++ */ + + reinit_completion(&port->tx_complete); + ret = port->tcpc->pd_transmit(port->tcpc, type, msg, port->negotiated_rev); +@@ -918,7 +920,7 @@ static int tcpm_pd_transmit(struct tcpm_port *port, + void tcpm_pd_transmit_complete(struct tcpm_port *port, + enum tcpm_transmit_status status) + { +- tcpm_log(port, "PD TX complete, status: %u", status); ++ //tcpm_log(port, "PD TX complete, status: %u", status); + port->tx_status = status; + complete(&port->tx_complete); + } +@@ -951,7 +953,7 @@ static int tcpm_set_polarity(struct tcpm_port *port, + { + int ret; + +- tcpm_log(port, "polarity %d", polarity); ++ //tcpm_log(port, "polarity %d", polarity); + + ret = port->tcpc->set_polarity(port->tcpc, polarity); + if (ret < 0) +@@ -966,7 +968,7 @@ static int tcpm_set_vconn(struct tcpm_port *port, bool enable) + { + int ret; + +- tcpm_log(port, "vconn:=%d", enable); ++ //tcpm_log(port, "vconn:=%d", enable); + + ret = port->tcpc->set_vconn(port->tcpc, enable); + if (!ret) { +@@ -2871,8 +2873,8 @@ static void tcpm_pd_rx_handler(struct kthread_work *work) + + mutex_lock(&port->lock); + +- tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header), +- port->attached); ++ //tcpm_log(port, "PD RX, header: %#x [%d]", le16_to_cpu(msg->header), ++ //port->attached); + + if (port->attached) { + enum pd_ctrl_msg_type type = pd_header_type_le(msg->header); +@@ -5041,7 +5043,7 @@ static void _tcpm_cc_change(struct tcpm_port *port, enum typec_cc_status cc1, + + static void _tcpm_pd_vbus_on(struct tcpm_port *port) + { +- tcpm_log_force(port, "VBUS on"); ++ tcpm_log_force(port, "VBUS event received: on"); + port->vbus_present = true; + /* + * When vbus_present is true i.e. Voltage at VBUS is greater than VSAFE5V implicitly +@@ -5131,7 +5133,7 @@ static void _tcpm_pd_vbus_on(struct tcpm_port *port) + + static void _tcpm_pd_vbus_off(struct tcpm_port *port) + { +- tcpm_log_force(port, "VBUS off"); ++ tcpm_log_force(port, "VBUS event received: off"); + port->vbus_present = false; + port->vbus_never_low = false; + switch (port->state) { diff --git a/sys-kernel/pinephone-pro-sources/files/0021-usb-typec-fusb302-Add-OF-extcon-support.patch b/sys-kernel/pinephone-pro-sources/files/0021-usb-typec-fusb302-Add-OF-extcon-support.patch new file mode 100644 index 0000000..64f2ffa --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0021-usb-typec-fusb302-Add-OF-extcon-support.patch @@ -0,0 +1,34 @@ +From: Ondrej Jirman +Date: Sun, 14 Nov 2021 01:14:25 +0100 +Subject: [PATCH 32/36] usb: typec: fusb302: Add OF extcon support + +It's possible to create a dependency cycle between fusb302 and +other drivers via extcon device, so we retrieve the device on +demand after probe and not during probe. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index ae3b930..0c5dd00 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -518,6 +518,16 @@ static int tcpm_get_current_limit(struct tcpc_dev *dev) + int current_limit = 0; + unsigned long timeout; + ++ /* ++ * To avoid cycles in OF dependencies, we get extcon when necessary ++ * outside of probe function. ++ */ ++ if (of_property_read_bool(chip->dev->of_node, "extcon") && !chip->extcon) { ++ chip->extcon = extcon_get_edev_by_phandle(chip->dev, 0); ++ if (IS_ERR(chip->extcon)) ++ chip->extcon = NULL; ++ } ++ + if (!chip->extcon) + return 0; + diff --git a/sys-kernel/pinephone-pro-sources/files/0022-usb-typec-fusb302-Fix-register-definitions.patch b/sys-kernel/pinephone-pro-sources/files/0022-usb-typec-fusb302-Fix-register-definitions.patch new file mode 100644 index 0000000..f06e87f --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0022-usb-typec-fusb302-Fix-register-definitions.patch @@ -0,0 +1,45 @@ +From: Ondrej Jirman +Date: Sat, 20 Nov 2021 14:33:58 +0100 +Subject: [PATCH 33/36] usb: typec: fusb302: Fix register definitions + +MEASURE_VBUS bit is at position 6. MDAC bits are also wrong. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302_reg.h | 16 +++++++--------- + 1 file changed, 7 insertions(+), 9 deletions(-) + +diff --git a/drivers/usb/typec/tcpm/fusb302_reg.h b/drivers/usb/typec/tcpm/fusb302_reg.h +index edc0e4b..f37d226 100644 +--- a/drivers/usb/typec/tcpm/fusb302_reg.h ++++ b/drivers/usb/typec/tcpm/fusb302_reg.h +@@ -27,14 +27,13 @@ + #define FUSB_REG_SWITCHES1_TXCC2_EN BIT(1) + #define FUSB_REG_SWITCHES1_TXCC1_EN BIT(0) + #define FUSB_REG_MEASURE 0x04 +-#define FUSB_REG_MEASURE_MDAC5 BIT(7) +-#define FUSB_REG_MEASURE_MDAC4 BIT(6) +-#define FUSB_REG_MEASURE_MDAC3 BIT(5) +-#define FUSB_REG_MEASURE_MDAC2 BIT(4) +-#define FUSB_REG_MEASURE_MDAC1 BIT(3) +-#define FUSB_REG_MEASURE_MDAC0 BIT(2) +-#define FUSB_REG_MEASURE_VBUS BIT(1) +-#define FUSB_REG_MEASURE_XXXX5 BIT(0) ++#define FUSB_REG_MEASURE_VBUS BIT(6) ++#define FUSB_REG_MEASURE_MDAC5 BIT(5) ++#define FUSB_REG_MEASURE_MDAC4 BIT(4) ++#define FUSB_REG_MEASURE_MDAC3 BIT(3) ++#define FUSB_REG_MEASURE_MDAC2 BIT(2) ++#define FUSB_REG_MEASURE_MDAC1 BIT(1) ++#define FUSB_REG_MEASURE_MDAC0 BIT(0) + #define FUSB_REG_CONTROL0 0x06 + #define FUSB_REG_CONTROL0_TX_FLUSH BIT(6) + #define FUSB_REG_CONTROL0_INT_MASK BIT(5) +@@ -105,7 +104,6 @@ + #define FUSB_REG_STATUS0A_RX_SOFT_RESET BIT(1) + #define FUSB_REG_STATUS0A_RX_HARD_RESET BIT(0) + #define FUSB_REG_STATUS1A 0x3D +-#define FUSB_REG_STATUS1A_TOGSS BIT(3) + #define FUSB_REG_STATUS1A_TOGSS_RUNNING 0x0 + #define FUSB_REG_STATUS1A_TOGSS_SRC1 0x1 + #define FUSB_REG_STATUS1A_TOGSS_SRC2 0x2 diff --git a/sys-kernel/pinephone-pro-sources/files/0023-usb-typec-fusb302-Clear-interrupts-before-we-start-t.patch b/sys-kernel/pinephone-pro-sources/files/0023-usb-typec-fusb302-Clear-interrupts-before-we-start-t.patch new file mode 100644 index 0000000..5909161 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0023-usb-typec-fusb302-Clear-interrupts-before-we-start-t.patch @@ -0,0 +1,37 @@ +From: Ondrej Jirman +Date: Sat, 20 Nov 2021 14:35:10 +0100 +Subject: [PATCH 34/36] usb: typec: fusb302: Clear interrupts before we start + toggling + +This is recommended by the datasheet. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/tcpm/fusb302.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/usb/typec/tcpm/fusb302.c b/drivers/usb/typec/tcpm/fusb302.c +index 0c5dd00..011dce5 100644 +--- a/drivers/usb/typec/tcpm/fusb302.c ++++ b/drivers/usb/typec/tcpm/fusb302.c +@@ -586,6 +586,7 @@ static int fusb302_set_toggling(struct fusb302_chip *chip, + enum toggling_mode mode) + { + int ret = 0; ++ u8 reg; + + /* first disable toggling */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_CONTROL2, +@@ -644,6 +645,12 @@ static int fusb302_set_toggling(struct fusb302_chip *chip, + } else { + /* Datasheet says vconn MUST be off when toggling */ + WARN(chip->vconn_on, "Vconn is on during toggle start"); ++ ++ /* clear interrupts */ ++ ret = fusb302_i2c_read(chip, FUSB_REG_INTERRUPT, ®); ++ if (ret < 0) ++ return ret; ++ + /* unmask TOGDONE interrupt */ + ret = fusb302_i2c_clear_bits(chip, FUSB_REG_MASKA, + FUSB_REG_MASKA_TOGDONE); diff --git a/sys-kernel/pinephone-pro-sources/files/0024-usb-typec-typec-extcon-Add-typec-extcon-bridge-drive.patch b/sys-kernel/pinephone-pro-sources/files/0024-usb-typec-typec-extcon-Add-typec-extcon-bridge-drive.patch new file mode 100644 index 0000000..d8c007c --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0024-usb-typec-typec-extcon-Add-typec-extcon-bridge-drive.patch @@ -0,0 +1,388 @@ +From: Ondrej Jirman +Date: Sun, 7 Nov 2021 19:24:40 +0100 +Subject: [PATCH 35/36] usb: typec: typec-extcon: Add typec -> extcon bridge + driver + +This bridge connects standard Type C port interfaces for controling +muxes, switches and usb roles to muxes, switches and usb role +drivers controlled via extcon interface. + +Signed-off-by: Ondrej Jirman +--- + drivers/usb/typec/Kconfig | 7 + + drivers/usb/typec/Makefile | 1 + + drivers/usb/typec/typec-extcon.c | 337 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 345 insertions(+) + create mode 100644 drivers/usb/typec/typec-extcon.c + +diff --git a/drivers/usb/typec/Kconfig b/drivers/usb/typec/Kconfig +index ab480f3..01ecc5e 100644 +--- a/drivers/usb/typec/Kconfig ++++ b/drivers/usb/typec/Kconfig +@@ -88,6 +88,13 @@ config TYPEC_QCOM_PMIC + It will also enable the VBUS output to connected devices when a + DFP connection is made. + ++config TYPEC_EXTCON ++ tristate "Type-C switch/mux -> extcon interface bridge driver" ++ depends on USB_ROLE_SWITCH ++ help ++ Say Y or M here if your system needs bridging between typec class ++ and extcon interfaces. ++ + source "drivers/usb/typec/mux/Kconfig" + + source "drivers/usb/typec/altmodes/Kconfig" +diff --git a/drivers/usb/typec/Makefile b/drivers/usb/typec/Makefile +index a0adb89..d9d8293 100644 +--- a/drivers/usb/typec/Makefile ++++ b/drivers/usb/typec/Makefile +@@ -8,4 +8,5 @@ obj-$(CONFIG_TYPEC_TPS6598X) += tipd/ + obj-$(CONFIG_TYPEC_HD3SS3220) += hd3ss3220.o + obj-$(CONFIG_TYPEC_QCOM_PMIC) += qcom-pmic-typec.o + obj-$(CONFIG_TYPEC_STUSB160X) += stusb160x.o ++obj-$(CONFIG_TYPEC_EXTCON) += typec-extcon.o + obj-$(CONFIG_TYPEC) += mux/ +diff --git a/drivers/usb/typec/typec-extcon.c b/drivers/usb/typec/typec-extcon.c +new file mode 100644 +index 00000000..143ff24 +--- /dev/null ++++ b/drivers/usb/typec/typec-extcon.c +@@ -0,0 +1,337 @@ ++/* ++ * typec -> extcon bridge ++ * Copyright (c) 2021 Ondřej Jirman ++ * ++ * This driver bridges standard type-c interfaces to drivers that ++ * expect extcon interface. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++struct typec_extcon { ++ struct device *dev; ++ ++ /* consumers */ ++ struct usb_role_switch *role_sw; ++ struct typec_switch *sw; ++ struct typec_mux *mux; ++ ++ /* providers */ ++ struct extcon_dev *extcon; ++ struct notifier_block extcon_nb; ++ ++ /* cached state from typec controller */ ++ enum usb_role role; ++ enum typec_orientation orientation; ++ struct typec_altmode alt; ++ unsigned long mode; ++ bool has_alt; ++ struct mutex lock; ++}; ++ ++static const unsigned int typec_extcon_cable[] = { ++ EXTCON_DISP_DP, ++ ++ EXTCON_USB, ++ EXTCON_USB_HOST, ++ ++ EXTCON_CHG_USB_SDP, ++ EXTCON_CHG_USB_CDP, ++ EXTCON_CHG_USB_DCP, ++ EXTCON_CHG_USB_ACA, ++ ++ EXTCON_NONE, ++}; ++ ++static void typec_extcon_set_cable(struct typec_extcon *tce, int id, bool on, ++ union extcon_property_value prop_ss, ++ union extcon_property_value prop_or) ++{ ++ union extcon_property_value cur_ss, cur_or; ++ bool prop_diff = false; ++ int ret; ++ ++ ret = extcon_get_property(tce->extcon, id, ++ EXTCON_PROP_USB_SS, &cur_ss); ++ if (ret || cur_ss.intval != prop_ss.intval) ++ prop_diff = true; ++ ++ ret = extcon_get_property(tce->extcon, id, ++ EXTCON_PROP_USB_TYPEC_POLARITY, &cur_or); ++ if (ret || cur_or.intval != prop_or.intval) ++ prop_diff = true; ++ ++ if (!on && extcon_get_state(tce->extcon, id)) { ++ extcon_set_state_sync(tce->extcon, id, false); ++ } else if (on && (!extcon_get_state(tce->extcon, id) || prop_diff)) { ++ extcon_set_state(tce->extcon, id, true); ++ extcon_set_property(tce->extcon, id, ++ EXTCON_PROP_USB_SS, prop_ss); ++ extcon_set_property(tce->extcon, id, ++ EXTCON_PROP_USB_TYPEC_POLARITY, prop_or); ++ extcon_sync(tce->extcon, id); ++ } ++} ++ ++static int typec_extcon_sync_extcon(struct typec_extcon *tce) ++{ ++ union extcon_property_value prop_ss, prop_or; ++ bool has_dp = false; ++ ++ mutex_lock(&tce->lock); ++ ++ /* connector is disconnected */ ++ if (tce->orientation == TYPEC_ORIENTATION_NONE) { ++ typec_extcon_set_cable(tce, EXTCON_USB, false, prop_ss, prop_or); ++ typec_extcon_set_cable(tce, EXTCON_USB_HOST, false, prop_ss, prop_or); ++ typec_extcon_set_cable(tce, EXTCON_DISP_DP, false, prop_ss, prop_or); ++ ++ extcon_set_state_sync(tce->extcon, EXTCON_CHG_USB_SDP, false); ++ extcon_set_state_sync(tce->extcon, EXTCON_CHG_USB_DCP, false); ++ extcon_set_state_sync(tce->extcon, EXTCON_CHG_USB_CDP, false); ++ extcon_set_state_sync(tce->extcon, EXTCON_CHG_USB_ACA, false); ++ ++ goto out_unlock; ++ } ++ ++ prop_or.intval = tce->orientation == TYPEC_ORIENTATION_NORMAL ? 0 : 1; ++ prop_ss.intval = 0; ++ ++ if (tce->has_alt && tce->alt.svid == USB_TYPEC_DP_SID) { ++ switch (tce->mode) { ++ case TYPEC_STATE_SAFE: ++ break; ++ case TYPEC_DP_STATE_C: ++ case TYPEC_DP_STATE_E: ++ has_dp = true; ++ break; ++ case TYPEC_DP_STATE_D: ++ has_dp = true; ++ fallthrough; ++ case TYPEC_STATE_USB: ++ prop_ss.intval = 1; ++ break; ++ default: ++ dev_err(tce->dev, "unhandled mux mode=%lu\n", tce->mode); ++ break; ++ } ++ } ++ ++ typec_extcon_set_cable(tce, EXTCON_USB, ++ tce->role == USB_ROLE_DEVICE, prop_ss, prop_or); ++ typec_extcon_set_cable(tce, EXTCON_USB_HOST, ++ tce->role == USB_ROLE_HOST, prop_ss, prop_or); ++ ++ typec_extcon_set_cable(tce, EXTCON_DISP_DP, has_dp, prop_ss, prop_or); ++ ++out_unlock: ++ mutex_unlock(&tce->lock); ++ return 0; ++} ++ ++static int typec_extcon_sw_set(struct typec_switch *sw, ++ enum typec_orientation orientation) ++{ ++ struct typec_extcon *tce = typec_switch_get_drvdata(sw); ++ ++ dev_dbg(tce->dev, "SW SET: orientation=%d\n", orientation); ++ ++ mutex_lock(&tce->lock); ++ tce->orientation = orientation; ++ mutex_unlock(&tce->lock); ++ ++ typec_extcon_sync_extcon(tce); ++ ++ return 0; ++} ++ ++static int typec_extcon_mux_set(struct typec_mux *mux, ++ struct typec_mux_state *state) ++{ ++ struct typec_extcon *tce = typec_mux_get_drvdata(mux); ++ struct typec_altmode *alt = state->alt; ++ ++ dev_dbg(tce->dev, "MUX SET: state->mode=%lu\n", state->mode); ++ if (alt) ++ dev_dbg(tce->dev, " ...alt: svid=%04hx mode=%d vdo=%08x active=%u\n", ++ alt->svid, alt->mode, alt->vdo, alt->active); ++ ++ mutex_lock(&tce->lock); ++ tce->mode = state->mode; ++ tce->has_alt = alt != NULL; ++ if (alt) ++ tce->alt = *alt; ++ mutex_unlock(&tce->lock); ++ ++ typec_extcon_sync_extcon(tce); ++ ++ return 0; ++} ++ ++static int typec_extcon_usb_set_role(struct usb_role_switch *sw, ++ enum usb_role role) ++{ ++ struct typec_extcon *tce = usb_role_switch_get_drvdata(sw); ++ ++ dev_dbg(tce->dev, "ROLE SET: role=%d\n", role); ++ ++ mutex_lock(&tce->lock); ++ tce->role = role; ++ mutex_unlock(&tce->lock); ++ ++ typec_extcon_sync_extcon(tce); ++ ++ return 0; ++} ++ ++static int typec_extcon_notifier(struct notifier_block *nb, ++ unsigned long action, void *data) ++{ ++ struct typec_extcon *tce = container_of(nb, struct typec_extcon, extcon_nb); ++ ++ bool sdp = extcon_get_state(tce->extcon, EXTCON_CHG_USB_SDP); ++ bool cdp = extcon_get_state(tce->extcon, EXTCON_CHG_USB_CDP); ++ bool dcp = extcon_get_state(tce->extcon, EXTCON_CHG_USB_DCP); ++ bool usb = extcon_get_state(tce->extcon, EXTCON_USB); ++ bool usb_host = extcon_get_state(tce->extcon, EXTCON_USB_HOST); ++ bool dp = extcon_get_state(tce->extcon, EXTCON_DISP_DP); ++ ++ dev_info(tce->dev, "extcon changed sdp=%d cdp=%d dcp=%d usb=%d usb_host=%d dp=%d\n", ++ sdp, cdp, dcp, usb, usb_host, dp); ++ ++ return NOTIFY_OK; ++} ++ ++static int typec_extcon_probe(struct platform_device *pdev) ++{ ++ struct typec_switch_desc sw_desc = { }; ++ struct typec_mux_desc mux_desc = { }; ++ struct usb_role_switch_desc role_desc = { }; ++ struct device *dev = &pdev->dev; ++ struct typec_extcon *tce; ++ int ret = 0; ++ ++ tce = devm_kzalloc(dev, sizeof(*tce), GFP_KERNEL); ++ if (!tce) ++ return -ENOMEM; ++ ++ tce->dev = &pdev->dev; ++ mutex_init(&tce->lock); ++ tce->mode = TYPEC_STATE_SAFE; ++ ++ sw_desc.drvdata = tce; ++ sw_desc.fwnode = dev->fwnode; ++ sw_desc.set = typec_extcon_sw_set; ++ ++ tce->sw = typec_switch_register(dev, &sw_desc); ++ if (IS_ERR(tce->sw)) ++ return dev_err_probe(dev, PTR_ERR(tce->sw), ++ "Error registering typec switch\n"); ++ ++ mux_desc.drvdata = tce; ++ mux_desc.fwnode = dev->fwnode; ++ mux_desc.set = typec_extcon_mux_set; ++ ++ tce->mux = typec_mux_register(dev, &mux_desc); ++ if (IS_ERR(tce->mux)) { ++ ret = dev_err_probe(dev, PTR_ERR(tce->mux), ++ "Error registering typec mux\n"); ++ goto err_sw; ++ } ++ ++ role_desc.driver_data = tce; ++ role_desc.fwnode = dev->fwnode; ++ role_desc.name = fwnode_get_name(dev->fwnode); ++ role_desc.set = typec_extcon_usb_set_role; ++ ++ tce->role_sw = usb_role_switch_register(dev, &role_desc); ++ if (IS_ERR(tce->role_sw)) { ++ ret = dev_err_probe(dev, PTR_ERR(tce->role_sw), ++ "Error registering USB role switch\n"); ++ goto err_mux; ++ } ++ ++ tce->extcon = devm_extcon_dev_allocate(dev, typec_extcon_cable); ++ if (IS_ERR(tce->extcon)) { ++ ret = PTR_ERR(tce->extcon); ++ goto err_role; ++ } ++ ++ ret = devm_extcon_dev_register(dev, tce->extcon); ++ if (ret) { ++ ret = dev_err_probe(dev, ret, "failed to register extcon device\n"); ++ goto err_role; ++ } ++ ++ extcon_set_property_capability(tce->extcon, EXTCON_USB, ++ EXTCON_PROP_USB_SS); ++ extcon_set_property_capability(tce->extcon, EXTCON_USB, ++ EXTCON_PROP_USB_TYPEC_POLARITY); ++ extcon_set_property_capability(tce->extcon, EXTCON_USB_HOST, ++ EXTCON_PROP_USB_SS); ++ extcon_set_property_capability(tce->extcon, EXTCON_USB_HOST, ++ EXTCON_PROP_USB_TYPEC_POLARITY); ++ extcon_set_property_capability(tce->extcon, EXTCON_DISP_DP, ++ EXTCON_PROP_USB_SS); ++ extcon_set_property_capability(tce->extcon, EXTCON_DISP_DP, ++ EXTCON_PROP_USB_TYPEC_POLARITY); ++ ++ tce->extcon_nb.notifier_call = typec_extcon_notifier; ++ ret = devm_extcon_register_notifier_all(dev, tce->extcon, &tce->extcon_nb); ++ if (ret) { ++ dev_err_probe(dev, ret, "Failed to register extcon notifier\n"); ++ goto err_role; ++ } ++ ++ return 0; ++ ++err_role: ++ usb_role_switch_unregister(tce->role_sw); ++err_mux: ++ typec_mux_unregister(tce->mux); ++err_sw: ++ typec_switch_unregister(tce->sw); ++ return ret; ++} ++ ++static int typec_extcon_remove(struct platform_device *pdev) ++{ ++ struct typec_extcon *tce = platform_get_drvdata(pdev); ++ ++ usb_role_switch_unregister(tce->role_sw); ++ typec_mux_unregister(tce->mux); ++ typec_switch_unregister(tce->sw); ++ ++ return 0; ++} ++ ++static struct of_device_id typec_extcon_of_match_table[] = { ++ { .compatible = "linux,typec-extcon-bridge" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(of, typec_extcon_of_match_table); ++ ++static struct platform_driver typec_extcon_driver = { ++ .driver = { ++ .name = "typec-extcon", ++ .of_match_table = typec_extcon_of_match_table, ++ }, ++ .probe = typec_extcon_probe, ++ .remove = typec_extcon_remove, ++}; ++ ++module_platform_driver(typec_extcon_driver); ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("Ondrej Jirman "); ++MODULE_DESCRIPTION("typec -> extcon bridge driver"); diff --git a/sys-kernel/pinephone-pro-sources/files/0025-phy-rockchip-typec-Make-sure-the-plug-orientation-is.patch b/sys-kernel/pinephone-pro-sources/files/0025-phy-rockchip-typec-Make-sure-the-plug-orientation-is.patch new file mode 100644 index 0000000..abb3c5c --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0025-phy-rockchip-typec-Make-sure-the-plug-orientation-is.patch @@ -0,0 +1,64 @@ +From: Ondrej Jirman +Date: Tue, 23 Nov 2021 17:32:18 +0100 +Subject: [PATCH 36/36] phy: rockchip-typec: Make sure the plug orientation is + respected + +RK3399 TRM says about bit 8: + +typec_conn_dir_sel: TypeC connect direction select + +- 0: select typec_conn_dir (bit0 of this register) to TypeC PHY +- 1: select TCPC ouput typec_con_dir to TypeC PHY (default value) + +This means that by default, typec_conn_dir bit is not respected. +Fix setting of typec_conn_dir by setting typec_conn_dir to 0 first. + +Signed-off-by: Ondrej Jirman +--- + drivers/phy/rockchip/phy-rockchip-typec.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/drivers/phy/rockchip/phy-rockchip-typec.c b/drivers/phy/rockchip/phy-rockchip-typec.c +index d2bbdc9..fa10ee9 100644 +--- a/drivers/phy/rockchip/phy-rockchip-typec.c ++++ b/drivers/phy/rockchip/phy-rockchip-typec.c +@@ -350,6 +350,7 @@ struct usb3phy_reg { + * struct rockchip_usb3phy_port_cfg - usb3-phy port configuration. + * @reg: the base address for usb3-phy config. + * @typec_conn_dir: the register of type-c connector direction. ++ * @typec_conn_dir_sel: the register of type-c connector direction source. + * @usb3tousb2_en: the register of type-c force usb2 to usb2 enable. + * @external_psm: the register of type-c phy external psm clock. + * @pipe_status: the register of type-c phy pipe status. +@@ -360,6 +361,7 @@ struct usb3phy_reg { + struct rockchip_usb3phy_port_cfg { + unsigned int reg; + struct usb3phy_reg typec_conn_dir; ++ struct usb3phy_reg typec_conn_dir_sel; + struct usb3phy_reg usb3tousb2_en; + struct usb3phy_reg external_psm; + struct usb3phy_reg pipe_status; +@@ -434,6 +436,7 @@ static const struct rockchip_usb3phy_port_cfg rk3399_usb3phy_port_cfgs[] = { + { + .reg = 0xff7c0000, + .typec_conn_dir = { 0xe580, 0, 16 }, ++ .typec_conn_dir_sel = { 0xe580, 8, 16+8 }, + .usb3tousb2_en = { 0xe580, 3, 19 }, + .external_psm = { 0xe588, 14, 30 }, + .pipe_status = { 0xe5c0, 0, 0 }, +@@ -444,6 +447,7 @@ static const struct rockchip_usb3phy_port_cfg rk3399_usb3phy_port_cfgs[] = { + { + .reg = 0xff800000, + .typec_conn_dir = { 0xe58c, 0, 16 }, ++ .typec_conn_dir_sel = { 0xe58c, 8, 16+8 }, + .usb3tousb2_en = { 0xe58c, 3, 19 }, + .external_psm = { 0xe594, 14, 30 }, + .pipe_status = { 0xe5c0, 16, 16 }, +@@ -739,6 +743,7 @@ static int tcphy_phy_init(struct rockchip_typec_phy *tcphy, u8 mode) + + reset_control_deassert(tcphy->tcphy_rst); + ++ property_enable(tcphy, &cfg->typec_conn_dir_sel, 0); + property_enable(tcphy, &cfg->typec_conn_dir, tcphy->flip); + tcphy_dp_aux_set_flip(tcphy); + diff --git a/sys-kernel/pinephone-pro-sources/files/0026-media-i2c-imx258-Add-support-for-powerdown-gpio.patch b/sys-kernel/pinephone-pro-sources/files/0026-media-i2c-imx258-Add-support-for-powerdown-gpio.patch new file mode 100644 index 0000000..6d20e9e --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0026-media-i2c-imx258-Add-support-for-powerdown-gpio.patch @@ -0,0 +1,56 @@ +From: Ondrej Jirman +Date: Fri, 22 Oct 2021 18:10:18 +0200 +Subject: [PATCH 20/36] media: i2c: imx258: Add support for powerdown gpio + +On some boards powerdown signal needs to be deasserted for this +sensor to be enabled. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/imx258.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c +index c249507..be5adcc 100644 +--- a/drivers/media/i2c/imx258.c ++++ b/drivers/media/i2c/imx258.c +@@ -612,6 +612,8 @@ struct imx258 { + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + ++ struct gpio_desc *pwdn_gpio; ++ + /* Current mode */ + const struct imx258_mode *cur_mode; + +@@ -1010,6 +1012,8 @@ static int imx258_power_on(struct device *dev) + struct imx258 *imx258 = to_imx258(sd); + int ret; + ++ gpiod_set_value_cansleep(imx258->pwdn_gpio, 0); ++ + ret = clk_prepare_enable(imx258->clk); + if (ret) + dev_err(dev, "failed to enable clock\n"); +@@ -1024,6 +1028,8 @@ static int imx258_power_off(struct device *dev) + + clk_disable_unprepare(imx258->clk); + ++ gpiod_set_value_cansleep(imx258->pwdn_gpio, 1); ++ + return 0; + } + +@@ -1284,6 +1290,12 @@ static int imx258_probe(struct i2c_client *client) + if (ret || val != 180) + return -EINVAL; + ++ /* request optional power down pin */ ++ imx258->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(imx258->pwdn_gpio)) ++ return PTR_ERR(imx258->pwdn_gpio); ++ + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx258->sd, client, &imx258_subdev_ops); + diff --git a/sys-kernel/pinephone-pro-sources/files/0027-media-i2c-imx258-Don-t-be-too-strict-about-clock-rat.patch b/sys-kernel/pinephone-pro-sources/files/0027-media-i2c-imx258-Don-t-be-too-strict-about-clock-rat.patch new file mode 100644 index 0000000..ef345ec --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0027-media-i2c-imx258-Don-t-be-too-strict-about-clock-rat.patch @@ -0,0 +1,41 @@ +From: Ondrej Jirman +Date: Fri, 22 Oct 2021 18:11:26 +0200 +Subject: [PATCH 21/36] media: i2c: imx258: Don't be too strict about clock + rate + +On Pinephone Pro, we are not able to set 19.2MHz precisely. +Allow some slack in clock rate. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/imx258.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c +index be5adcc..e64fadc 100644 +--- a/drivers/media/i2c/imx258.c ++++ b/drivers/media/i2c/imx258.c +@@ -79,7 +79,9 @@ + #define REG_CONFIG_FLIP_TEST_PATTERN 0x02 + + /* Input clock frequency in Hz */ ++#define IMX258_INPUT_CLOCK_FREQ_MIN 19000000 + #define IMX258_INPUT_CLOCK_FREQ 19200000 ++#define IMX258_INPUT_CLOCK_FREQ_MAX 19400000 + + struct imx258_reg { + u16 address; +@@ -1277,8 +1279,11 @@ static int imx258_probe(struct i2c_client *client) + } else { + val = clk_get_rate(imx258->clk); + } +- if (val != IMX258_INPUT_CLOCK_FREQ) { +- dev_err(&client->dev, "input clock frequency not supported\n"); ++ ++ if (val < IMX258_INPUT_CLOCK_FREQ_MIN ++ || val > IMX258_INPUT_CLOCK_FREQ_MAX) { ++ dev_err(&client->dev, "input clock frequency %u not supported\n", ++ val); + return -EINVAL; + } + diff --git a/sys-kernel/pinephone-pro-sources/files/0028-media-i2c-imx258-Add-support-for-reset-gpio.patch b/sys-kernel/pinephone-pro-sources/files/0028-media-i2c-imx258-Add-support-for-reset-gpio.patch new file mode 100644 index 0000000..af85962 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0028-media-i2c-imx258-Add-support-for-reset-gpio.patch @@ -0,0 +1,57 @@ +From: Ondrej Jirman +Date: Fri, 22 Oct 2021 21:44:13 +0200 +Subject: [PATCH 22/36] media: i2c: imx258: Add support for reset gpio + +It was documented in DT, but not implemented. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/imx258.c | 14 +++++++++++++- + 1 file changed, 13 insertions(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c +index e64fadc..ec6e919 100644 +--- a/drivers/media/i2c/imx258.c ++++ b/drivers/media/i2c/imx258.c +@@ -615,6 +615,7 @@ struct imx258 { + struct v4l2_ctrl *exposure; + + struct gpio_desc *pwdn_gpio; ++ struct gpio_desc *reset_gpio; + + /* Current mode */ + const struct imx258_mode *cur_mode; +@@ -1020,7 +1021,11 @@ static int imx258_power_on(struct device *dev) + if (ret) + dev_err(dev, "failed to enable clock\n"); + +- return ret; ++ gpiod_set_value_cansleep(imx258->reset_gpio, 0); ++ ++ usleep_range(400, 500); ++ ++ return 0; + } + + static int imx258_power_off(struct device *dev) +@@ -1030,6 +1035,7 @@ static int imx258_power_off(struct device *dev) + + clk_disable_unprepare(imx258->clk); + ++ gpiod_set_value_cansleep(imx258->reset_gpio, 1); + gpiod_set_value_cansleep(imx258->pwdn_gpio, 1); + + return 0; +@@ -1301,6 +1307,12 @@ static int imx258_probe(struct i2c_client *client) + if (IS_ERR(imx258->pwdn_gpio)) + return PTR_ERR(imx258->pwdn_gpio); + ++ /* request optional reset pin */ ++ imx258->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", ++ GPIOD_OUT_HIGH); ++ if (IS_ERR(imx258->reset_gpio)) ++ return PTR_ERR(imx258->reset_gpio); ++ + /* Initialize subdev */ + v4l2_i2c_subdev_init(&imx258->sd, client, &imx258_subdev_ops); + diff --git a/sys-kernel/pinephone-pro-sources/files/0029-media-i2c-imx258-Add-support-for-power-supplies.patch b/sys-kernel/pinephone-pro-sources/files/0029-media-i2c-imx258-Add-support-for-power-supplies.patch new file mode 100644 index 0000000..23c09d8 --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0029-media-i2c-imx258-Add-support-for-power-supplies.patch @@ -0,0 +1,100 @@ +From: Ondrej Jirman +Date: Fri, 22 Oct 2021 21:44:30 +0200 +Subject: [PATCH 23/36] media: i2c: imx258: Add support for power supplies + +They were documented in DT, but not implemented. + +Signed-off-by: Ondrej Jirman +--- + drivers/media/i2c/imx258.c | 39 +++++++++++++++++++++++++++++++++++++-- + 1 file changed, 37 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/imx258.c b/drivers/media/i2c/imx258.c +index ec6e919..2570b51 100644 +--- a/drivers/media/i2c/imx258.c ++++ b/drivers/media/i2c/imx258.c +@@ -602,6 +602,15 @@ static const struct imx258_mode supported_modes[] = { + }, + }; + ++/* regulator supplies */ ++static const char * const imx258_supply_names[] = { ++ "vana", /* Analog (2.8V) supply */ ++ "vdig", /* Digital Core (1.5V) supply */ ++ "vif", /* Digital I/O (1.8V) supply */ ++}; ++ ++#define IMX258_SUPPLY_COUNT ARRAY_SIZE(imx258_supply_names) ++ + struct imx258 { + struct v4l2_subdev sd; + struct media_pad pad; +@@ -616,6 +625,7 @@ struct imx258 { + + struct gpio_desc *pwdn_gpio; + struct gpio_desc *reset_gpio; ++ struct regulator_bulk_data supplies[IMX258_SUPPLY_COUNT]; + + /* Current mode */ + const struct imx258_mode *cur_mode; +@@ -1015,11 +1025,26 @@ static int imx258_power_on(struct device *dev) + struct imx258 *imx258 = to_imx258(sd); + int ret; + ++ ret = regulator_bulk_enable(IMX258_SUPPLY_COUNT, imx258->supplies); ++ if (ret) { ++ dev_err(dev, "failed to enable regulators\n"); ++ return ret; ++ } ++ ++ mdelay(20); ++ + gpiod_set_value_cansleep(imx258->pwdn_gpio, 0); + ++ mdelay(5); ++ + ret = clk_prepare_enable(imx258->clk); +- if (ret) ++ if (ret) { + dev_err(dev, "failed to enable clock\n"); ++ regulator_bulk_disable(IMX258_SUPPLY_COUNT, imx258->supplies); ++ return ret; ++ } ++ ++ usleep_range(1000, 2000); + + gpiod_set_value_cansleep(imx258->reset_gpio, 0); + +@@ -1038,6 +1063,8 @@ static int imx258_power_off(struct device *dev) + gpiod_set_value_cansleep(imx258->reset_gpio, 1); + gpiod_set_value_cansleep(imx258->pwdn_gpio, 1); + ++ regulator_bulk_disable(IMX258_SUPPLY_COUNT, imx258->supplies); ++ + return 0; + } + +@@ -1266,7 +1293,7 @@ static void imx258_free_controls(struct imx258 *imx258) + static int imx258_probe(struct i2c_client *client) + { + struct imx258 *imx258; +- int ret; ++ int ret, i; + u32 val = 0; + + imx258 = devm_kzalloc(&client->dev, sizeof(*imx258), GFP_KERNEL); +@@ -1301,6 +1328,14 @@ static int imx258_probe(struct i2c_client *client) + if (ret || val != 180) + return -EINVAL; + ++ for (i = 0; i < IMX258_SUPPLY_COUNT; i++) ++ imx258->supplies[i].supply = imx258_supply_names[i]; ++ ret = devm_regulator_bulk_get(&client->dev, ++ IMX258_SUPPLY_COUNT, ++ imx258->supplies); ++ if (ret) ++ return dev_err_probe(&client->dev, ret, "Failed to get supplies\n"); ++ + /* request optional power down pin */ + imx258->pwdn_gpio = devm_gpiod_get_optional(&client->dev, "powerdown", + GPIOD_OUT_HIGH); diff --git a/sys-kernel/pinephone-pro-sources/files/0030-drm-panel-hx8394-Add-driver-for-HX8394-based-HannSta.patch b/sys-kernel/pinephone-pro-sources/files/0030-drm-panel-hx8394-Add-driver-for-HX8394-based-HannSta.patch new file mode 100644 index 0000000..fc6634b --- /dev/null +++ b/sys-kernel/pinephone-pro-sources/files/0030-drm-panel-hx8394-Add-driver-for-HX8394-based-HannSta.patch @@ -0,0 +1,464 @@ +From: =?utf-8?q?Kamil_Trzci=C5=84ski?= +Date: Wed, 8 Sep 2021 13:50:04 +0200 +Subject: [PATCH 05/36] drm: panel: hx8394: Add driver for HX8394 based + HannStar HSD060BHW4 panel +MIME-Version: 1.0 +Content-Type: text/plain; charset="utf-8" +Content-Transfer-Encoding: 8bit + +... + +Signed-off-by: Kamil Trzciński +Signed-off-by: Ondrej Jirman +--- + drivers/gpu/drm/panel/Kconfig | 9 + + drivers/gpu/drm/panel/Makefile | 1 + + drivers/gpu/drm/panel/panel-himax-hx8394.c | 410 +++++++++++++++++++++++++++++ + 3 files changed, 420 insertions(+) + create mode 100644 drivers/gpu/drm/panel/panel-himax-hx8394.c + +diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig +index cfc8d64..166bd4a 100644 +--- a/drivers/gpu/drm/panel/Kconfig ++++ b/drivers/gpu/drm/panel/Kconfig +@@ -129,6 +129,15 @@ config DRM_PANEL_FEIYANG_FY07024DI26A30D + Say Y if you want to enable support for panels based on the + Feiyang FY07024DI26A30-D MIPI-DSI interface. + ++config DRM_PANEL_HIMAX_HX8394 ++ tristate "HIMAX HX8394 MIPI-DSI LCD panel" ++ depends on OF ++ depends on DRM_MIPI_DSI ++ depends on BACKLIGHT_CLASS_DEVICE ++ help ++ Say Y if you want to enable support for panels based on the ++ HIMAX HX8394 MIPI-DSI interface. ++ + config DRM_PANEL_ILITEK_IL9322 + tristate "Ilitek ILI9322 320x240 QVGA panels" + depends on OF && SPI +diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile +index bca4cc1..486926c 100644 +--- a/drivers/gpu/drm/panel/Makefile ++++ b/drivers/gpu/drm/panel/Makefile +@@ -67,3 +67,4 @@ obj-$(CONFIG_DRM_PANEL_TRULY_NT35597_WQXGA) += panel-truly-nt35597.o + obj-$(CONFIG_DRM_PANEL_VISIONOX_RM69299) += panel-visionox-rm69299.o + obj-$(CONFIG_DRM_PANEL_WIDECHIPS_WS2401) += panel-widechips-ws2401.o + obj-$(CONFIG_DRM_PANEL_XINPENG_XPP055C272) += panel-xinpeng-xpp055c272.o ++obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o +diff --git a/drivers/gpu/drm/panel/panel-himax-hx8394.c b/drivers/gpu/drm/panel/panel-himax-hx8394.c +new file mode 100644 +index 00000000..14659cb +--- /dev/null ++++ b/drivers/gpu/drm/panel/panel-himax-hx8394.c +@@ -0,0 +1,410 @@ ++// SPDX-License-Identifier: GPL-2.0 ++/* ++ * Driver for panels based on Himax HX8394 controller, souch as: ++ * ++ * - HannStar HSD060BHW4 5.99" MIPI-DSI panel ++ * ++ * Copyright (C) Kamil Trzciński ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include